001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, 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-2008, 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 * 19-Sep-2008 : Added visibility flag (DG);
058 *
059 */
060
061 package org.jfree.chart.title;
062
063 import java.awt.Graphics2D;
064 import java.awt.geom.Rectangle2D;
065 import java.io.IOException;
066 import java.io.ObjectInputStream;
067 import java.io.ObjectOutputStream;
068 import java.io.Serializable;
069
070 import javax.swing.event.EventListenerList;
071
072 import org.jfree.chart.block.AbstractBlock;
073 import org.jfree.chart.block.Block;
074 import org.jfree.chart.event.TitleChangeEvent;
075 import org.jfree.chart.event.TitleChangeListener;
076 import org.jfree.ui.HorizontalAlignment;
077 import org.jfree.ui.RectangleEdge;
078 import org.jfree.ui.RectangleInsets;
079 import org.jfree.ui.VerticalAlignment;
080 import org.jfree.util.ObjectUtilities;
081
082 /**
083 * The base class for all chart titles. A chart can have multiple titles,
084 * appearing at the top, bottom, left or right of the chart.
085 * <P>
086 * Concrete implementations of this class will render text and images, and
087 * hence do the actual work of drawing titles.
088 */
089 public abstract class Title extends AbstractBlock
090 implements Block, Cloneable, Serializable {
091
092 /** For serialization. */
093 private static final long serialVersionUID = -6675162505277817221L;
094
095 /** The default title position. */
096 public static final RectangleEdge DEFAULT_POSITION = RectangleEdge.TOP;
097
098 /** The default horizontal alignment. */
099 public static final HorizontalAlignment
100 DEFAULT_HORIZONTAL_ALIGNMENT = HorizontalAlignment.CENTER;
101
102 /** The default vertical alignment. */
103 public static final VerticalAlignment
104 DEFAULT_VERTICAL_ALIGNMENT = VerticalAlignment.CENTER;
105
106 /** Default title padding. */
107 public static final RectangleInsets DEFAULT_PADDING = new RectangleInsets(
108 1, 1, 1, 1);
109
110 /**
111 * A flag that controls whether or not the title is visible.
112 *
113 * @since 1.0.11
114 */
115 public boolean visible;
116
117 /** The title position. */
118 private RectangleEdge position;
119
120 /** The horizontal alignment of the title content. */
121 private HorizontalAlignment horizontalAlignment;
122
123 /** The vertical alignment of the title content. */
124 private VerticalAlignment verticalAlignment;
125
126 /** Storage for registered change listeners. */
127 private transient EventListenerList listenerList;
128
129 /**
130 * A flag that can be used to temporarily disable the listener mechanism.
131 */
132 private boolean notify;
133
134 /**
135 * Creates a new title, using default attributes where necessary.
136 */
137 protected Title() {
138 this(Title.DEFAULT_POSITION,
139 Title.DEFAULT_HORIZONTAL_ALIGNMENT,
140 Title.DEFAULT_VERTICAL_ALIGNMENT, Title.DEFAULT_PADDING);
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(position, horizontalAlignment, verticalAlignment,
158 Title.DEFAULT_PADDING);
159
160 }
161
162 /**
163 * Creates a new title.
164 *
165 * @param position the position of the title (<code>null</code> not
166 * permitted).
167 * @param horizontalAlignment the horizontal alignment of the title (LEFT,
168 * CENTER or RIGHT, <code>null</code> not
169 * permitted).
170 * @param verticalAlignment the vertical alignment of the title (TOP,
171 * MIDDLE or BOTTOM, <code>null</code> not
172 * permitted).
173 * @param padding the amount of space to leave around the outside of the
174 * title (<code>null</code> not permitted).
175 */
176 protected Title(RectangleEdge position,
177 HorizontalAlignment horizontalAlignment,
178 VerticalAlignment verticalAlignment,
179 RectangleInsets padding) {
180
181 // check arguments...
182 if (position == null) {
183 throw new IllegalArgumentException("Null 'position' argument.");
184 }
185 if (horizontalAlignment == null) {
186 throw new IllegalArgumentException(
187 "Null 'horizontalAlignment' argument.");
188 }
189
190 if (verticalAlignment == null) {
191 throw new IllegalArgumentException(
192 "Null 'verticalAlignment' argument.");
193 }
194 if (padding == null) {
195 throw new IllegalArgumentException("Null 'spacer' argument.");
196 }
197
198 this.visible = true;
199 this.position = position;
200 this.horizontalAlignment = horizontalAlignment;
201 this.verticalAlignment = verticalAlignment;
202 setPadding(padding);
203 this.listenerList = new EventListenerList();
204 this.notify = true;
205
206 }
207
208 /**
209 * Returns a flag that controls whether or not the title should be
210 * drawn. The default value is <code>true</code>.
211 *
212 * @return A boolean.
213 *
214 * @see #setVisible(boolean)
215 *
216 * @since 1.0.11
217 */
218 public boolean isVisible() {
219 return this.visible;
220 }
221
222 /**
223 * Sets a flag that controls whether or not the title should be drawn, and
224 * sends a {@link TitleChangeEvent} to all registered listeners.
225 *
226 * @param visible the new flag value.
227 *
228 * @see #isVisible()
229 *
230 * @since 1.0.11
231 */
232 public void setVisible(boolean visible) {
233 this.visible = visible;
234 notifyListeners(new TitleChangeEvent(this));
235 }
236 /**
237 * Returns the position of the title.
238 *
239 * @return The title position (never <code>null</code>).
240 */
241 public RectangleEdge getPosition() {
242 return this.position;
243 }
244
245 /**
246 * Sets the position for the title and sends a {@link TitleChangeEvent} to
247 * all registered listeners.
248 *
249 * @param position the position (<code>null</code> not permitted).
250 */
251 public void setPosition(RectangleEdge position) {
252 if (position == null) {
253 throw new IllegalArgumentException("Null 'position' argument.");
254 }
255 if (this.position != position) {
256 this.position = position;
257 notifyListeners(new TitleChangeEvent(this));
258 }
259 }
260
261 /**
262 * Returns the horizontal alignment of the title.
263 *
264 * @return The horizontal alignment (never <code>null</code>).
265 */
266 public HorizontalAlignment getHorizontalAlignment() {
267 return this.horizontalAlignment;
268 }
269
270 /**
271 * Sets the horizontal alignment for the title and sends a
272 * {@link TitleChangeEvent} to all registered listeners.
273 *
274 * @param alignment the horizontal alignment (<code>null</code> not
275 * permitted).
276 */
277 public void setHorizontalAlignment(HorizontalAlignment alignment) {
278 if (alignment == null) {
279 throw new IllegalArgumentException("Null 'alignment' argument.");
280 }
281 if (this.horizontalAlignment != alignment) {
282 this.horizontalAlignment = alignment;
283 notifyListeners(new TitleChangeEvent(this));
284 }
285 }
286
287 /**
288 * Returns the vertical alignment of the title.
289 *
290 * @return The vertical alignment (never <code>null</code>).
291 */
292 public VerticalAlignment getVerticalAlignment() {
293 return this.verticalAlignment;
294 }
295
296 /**
297 * Sets the vertical alignment for the title, and notifies any registered
298 * listeners of the change.
299 *
300 * @param alignment the new vertical alignment (TOP, MIDDLE or BOTTOM,
301 * <code>null</code> not permitted).
302 */
303 public void setVerticalAlignment(VerticalAlignment alignment) {
304 if (alignment == null) {
305 throw new IllegalArgumentException("Null 'alignment' argument.");
306 }
307 if (this.verticalAlignment != alignment) {
308 this.verticalAlignment = alignment;
309 notifyListeners(new TitleChangeEvent(this));
310 }
311 }
312
313 /**
314 * Returns the flag that indicates whether or not the notification
315 * mechanism is enabled.
316 *
317 * @return The flag.
318 */
319 public boolean getNotify() {
320 return this.notify;
321 }
322
323 /**
324 * Sets the flag that indicates whether or not the notification mechanism
325 * is enabled. There are certain situations (such as cloning) where you
326 * want to turn notification off temporarily.
327 *
328 * @param flag the new value of the flag.
329 */
330 public void setNotify(boolean flag) {
331 this.notify = flag;
332 if (flag) {
333 notifyListeners(new TitleChangeEvent(this));
334 }
335 }
336
337 /**
338 * Draws the title on a Java 2D graphics device (such as the screen or a
339 * printer).
340 *
341 * @param g2 the graphics device.
342 * @param area the area allocated for the title (subclasses should not
343 * draw outside this area).
344 */
345 public abstract void draw(Graphics2D g2, Rectangle2D area);
346
347 /**
348 * Returns a clone of the title.
349 * <P>
350 * One situation when this is useful is when editing the title properties -
351 * you can edit a clone, and then it is easier to cancel the changes if
352 * necessary.
353 *
354 * @return A clone of the title.
355 *
356 * @throws CloneNotSupportedException not thrown by this class, but it may
357 * be thrown by subclasses.
358 */
359 public Object clone() throws CloneNotSupportedException {
360 Title duplicate = (Title) super.clone();
361 duplicate.listenerList = new EventListenerList();
362 // RectangleInsets is immutable => same reference in clone OK
363 return duplicate;
364 }
365
366 /**
367 * Registers an object for notification of changes to the title.
368 *
369 * @param listener the object that is being registered.
370 */
371 public void addChangeListener(TitleChangeListener listener) {
372 this.listenerList.add(TitleChangeListener.class, listener);
373 }
374
375 /**
376 * Unregisters an object for notification of changes to the chart title.
377 *
378 * @param listener the object that is being unregistered.
379 */
380 public void removeChangeListener(TitleChangeListener listener) {
381 this.listenerList.remove(TitleChangeListener.class, listener);
382 }
383
384 /**
385 * Notifies all registered listeners that the chart title has changed in
386 * some way.
387 *
388 * @param event an object that contains information about the change to
389 * the title.
390 */
391 protected void notifyListeners(TitleChangeEvent event) {
392 if (this.notify) {
393 Object[] listeners = this.listenerList.getListenerList();
394 for (int i = listeners.length - 2; i >= 0; i -= 2) {
395 if (listeners[i] == TitleChangeListener.class) {
396 ((TitleChangeListener) listeners[i + 1]).titleChanged(
397 event);
398 }
399 }
400 }
401 }
402
403 /**
404 * Tests an object for equality with this title.
405 *
406 * @param obj the object (<code>null</code> not permitted).
407 *
408 * @return <code>true</code> or <code>false</code>.
409 */
410 public boolean equals(Object obj) {
411 if (obj == this) {
412 return true;
413 }
414 if (!(obj instanceof Title)) {
415 return false;
416 }
417 Title that = (Title) obj;
418 if (this.visible != that.visible) {
419 return false;
420 }
421 if (this.position != that.position) {
422 return false;
423 }
424 if (this.horizontalAlignment != that.horizontalAlignment) {
425 return false;
426 }
427 if (this.verticalAlignment != that.verticalAlignment) {
428 return false;
429 }
430 if (this.notify != that.notify) {
431 return false;
432 }
433 return super.equals(obj);
434 }
435
436 /**
437 * Returns a hashcode for the title.
438 *
439 * @return The hashcode.
440 */
441 public int hashCode() {
442 int result = 193;
443 result = 37 * result + ObjectUtilities.hashCode(this.position);
444 result = 37 * result
445 + ObjectUtilities.hashCode(this.horizontalAlignment);
446 result = 37 * result + ObjectUtilities.hashCode(this.verticalAlignment);
447 return result;
448 }
449
450 /**
451 * Provides serialization support.
452 *
453 * @param stream the output stream.
454 *
455 * @throws IOException if there is an I/O error.
456 */
457 private void writeObject(ObjectOutputStream stream) throws IOException {
458 stream.defaultWriteObject();
459 }
460
461 /**
462 * Provides serialization support.
463 *
464 * @param stream the input stream.
465 *
466 * @throws IOException if there is an I/O error.
467 * @throws ClassNotFoundException if there is a classpath problem.
468 */
469 private void readObject(ObjectInputStream stream)
470 throws IOException, ClassNotFoundException {
471 stream.defaultReadObject();
472 this.listenerList = new EventListenerList();
473 }
474
475 }