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    }