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 /**
238 * Returns the position of the title.
239 *
240 * @return The title position (never <code>null</code>).
241 */
242 public RectangleEdge getPosition() {
243 return this.position;
244 }
245
246 /**
247 * Sets the position for the title and sends a {@link TitleChangeEvent} to
248 * all registered listeners.
249 *
250 * @param position the position (<code>null</code> not permitted).
251 */
252 public void setPosition(RectangleEdge position) {
253 if (position == null) {
254 throw new IllegalArgumentException("Null 'position' argument.");
255 }
256 if (this.position != position) {
257 this.position = position;
258 notifyListeners(new TitleChangeEvent(this));
259 }
260 }
261
262 /**
263 * Returns the horizontal alignment of the title.
264 *
265 * @return The horizontal alignment (never <code>null</code>).
266 */
267 public HorizontalAlignment getHorizontalAlignment() {
268 return this.horizontalAlignment;
269 }
270
271 /**
272 * Sets the horizontal alignment for the title and sends a
273 * {@link TitleChangeEvent} to all registered listeners.
274 *
275 * @param alignment the horizontal alignment (<code>null</code> not
276 * permitted).
277 */
278 public void setHorizontalAlignment(HorizontalAlignment alignment) {
279 if (alignment == null) {
280 throw new IllegalArgumentException("Null 'alignment' argument.");
281 }
282 if (this.horizontalAlignment != alignment) {
283 this.horizontalAlignment = alignment;
284 notifyListeners(new TitleChangeEvent(this));
285 }
286 }
287
288 /**
289 * Returns the vertical alignment of the title.
290 *
291 * @return The vertical alignment (never <code>null</code>).
292 */
293 public VerticalAlignment getVerticalAlignment() {
294 return this.verticalAlignment;
295 }
296
297 /**
298 * Sets the vertical alignment for the title, and notifies any registered
299 * listeners of the change.
300 *
301 * @param alignment the new vertical alignment (TOP, MIDDLE or BOTTOM,
302 * <code>null</code> not permitted).
303 */
304 public void setVerticalAlignment(VerticalAlignment alignment) {
305 if (alignment == null) {
306 throw new IllegalArgumentException("Null 'alignment' argument.");
307 }
308 if (this.verticalAlignment != alignment) {
309 this.verticalAlignment = alignment;
310 notifyListeners(new TitleChangeEvent(this));
311 }
312 }
313
314 /**
315 * Returns the flag that indicates whether or not the notification
316 * mechanism is enabled.
317 *
318 * @return The flag.
319 */
320 public boolean getNotify() {
321 return this.notify;
322 }
323
324 /**
325 * Sets the flag that indicates whether or not the notification mechanism
326 * is enabled. There are certain situations (such as cloning) where you
327 * want to turn notification off temporarily.
328 *
329 * @param flag the new value of the flag.
330 */
331 public void setNotify(boolean flag) {
332 this.notify = flag;
333 if (flag) {
334 notifyListeners(new TitleChangeEvent(this));
335 }
336 }
337
338 /**
339 * Draws the title on a Java 2D graphics device (such as the screen or a
340 * printer).
341 *
342 * @param g2 the graphics device.
343 * @param area the area allocated for the title (subclasses should not
344 * draw outside this area).
345 */
346 public abstract void draw(Graphics2D g2, Rectangle2D area);
347
348 /**
349 * Returns a clone of the title.
350 * <P>
351 * One situation when this is useful is when editing the title properties -
352 * you can edit a clone, and then it is easier to cancel the changes if
353 * necessary.
354 *
355 * @return A clone of the title.
356 *
357 * @throws CloneNotSupportedException not thrown by this class, but it may
358 * be thrown by subclasses.
359 */
360 public Object clone() throws CloneNotSupportedException {
361 Title duplicate = (Title) super.clone();
362 duplicate.listenerList = new EventListenerList();
363 // RectangleInsets is immutable => same reference in clone OK
364 return duplicate;
365 }
366
367 /**
368 * Registers an object for notification of changes to the title.
369 *
370 * @param listener the object that is being registered.
371 */
372 public void addChangeListener(TitleChangeListener listener) {
373 this.listenerList.add(TitleChangeListener.class, listener);
374 }
375
376 /**
377 * Unregisters an object for notification of changes to the chart title.
378 *
379 * @param listener the object that is being unregistered.
380 */
381 public void removeChangeListener(TitleChangeListener listener) {
382 this.listenerList.remove(TitleChangeListener.class, listener);
383 }
384
385 /**
386 * Notifies all registered listeners that the chart title has changed in
387 * some way.
388 *
389 * @param event an object that contains information about the change to
390 * the title.
391 */
392 protected void notifyListeners(TitleChangeEvent event) {
393 if (this.notify) {
394 Object[] listeners = this.listenerList.getListenerList();
395 for (int i = listeners.length - 2; i >= 0; i -= 2) {
396 if (listeners[i] == TitleChangeListener.class) {
397 ((TitleChangeListener) listeners[i + 1]).titleChanged(
398 event);
399 }
400 }
401 }
402 }
403
404 /**
405 * Tests an object for equality with this title.
406 *
407 * @param obj the object (<code>null</code> not permitted).
408 *
409 * @return <code>true</code> or <code>false</code>.
410 */
411 public boolean equals(Object obj) {
412 if (obj == this) {
413 return true;
414 }
415 if (!(obj instanceof Title)) {
416 return false;
417 }
418 Title that = (Title) obj;
419 if (this.visible != that.visible) {
420 return false;
421 }
422 if (this.position != that.position) {
423 return false;
424 }
425 if (this.horizontalAlignment != that.horizontalAlignment) {
426 return false;
427 }
428 if (this.verticalAlignment != that.verticalAlignment) {
429 return false;
430 }
431 if (this.notify != that.notify) {
432 return false;
433 }
434 return super.equals(obj);
435 }
436
437 /**
438 * Returns a hashcode for the title.
439 *
440 * @return The hashcode.
441 */
442 public int hashCode() {
443 int result = 193;
444 result = 37 * result + ObjectUtilities.hashCode(this.position);
445 result = 37 * result
446 + ObjectUtilities.hashCode(this.horizontalAlignment);
447 result = 37 * result + ObjectUtilities.hashCode(this.verticalAlignment);
448 return result;
449 }
450
451 /**
452 * Provides serialization support.
453 *
454 * @param stream the output stream.
455 *
456 * @throws IOException if there is an I/O error.
457 */
458 private void writeObject(ObjectOutputStream stream) throws IOException {
459 stream.defaultWriteObject();
460 }
461
462 /**
463 * Provides serialization support.
464 *
465 * @param stream the input stream.
466 *
467 * @throws IOException if there is an I/O error.
468 * @throws ClassNotFoundException if there is a classpath problem.
469 */
470 private void readObject(ObjectInputStream stream)
471 throws IOException, ClassNotFoundException {
472 stream.defaultReadObject();
473 this.listenerList = new EventListenerList();
474 }
475
476 }