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    }