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     * $Id: Title.java,v 1.10.2.2 2007/01/24 11:07:07 mungady Exp $
037     *
038     * Changes (from 21-Aug-2001)
039     * --------------------------
040     * 21-Aug-2001 : Added standard header (DG);
041     * 18-Sep-2001 : Updated header (DG);
042     * 14-Nov-2001 : Package com.jrefinery.common.ui.* changed to 
043     *               com.jrefinery.ui.* (DG);
044     * 07-Feb-2002 : Changed blank space around title from Insets --> Spacer, to 
045     *               allow for relative or absolute spacing (DG);
046     * 25-Jun-2002 : Removed unnecessary imports (DG);
047     * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
048     * 14-Oct-2002 : Changed the event listener storage structure (DG);
049     * 11-Sep-2003 : Took care of listeners while cloning (NB);
050     * 22-Sep-2003 : Spacer cannot be null. Added nullpointer checks for this (TM);
051     * 08-Jan-2003 : Renamed AbstractTitle --> Title and moved to separate 
052     *               package (DG);
053     * 26-Oct-2004 : Refactored to implement Block interface, and removed redundant 
054     *               constants (DG);
055     * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0 
056     *               release (DG);
057     * 02-Feb-2005 : Changed Spacer --> RectangleInsets for padding (DG);
058     * 03-May-2005 : Fixed problem in equals() method (DG);
059     * 
060     */
061    
062    package org.jfree.chart.title;
063    
064    import java.awt.Graphics2D;
065    import java.awt.geom.Rectangle2D;
066    import java.io.IOException;
067    import java.io.ObjectInputStream;
068    import java.io.ObjectOutputStream;
069    import java.io.Serializable;
070    
071    import javax.swing.event.EventListenerList;
072    
073    import org.jfree.chart.block.AbstractBlock;
074    import org.jfree.chart.block.Block;
075    import org.jfree.chart.event.TitleChangeEvent;
076    import org.jfree.chart.event.TitleChangeListener;
077    import org.jfree.ui.HorizontalAlignment;
078    import org.jfree.ui.RectangleEdge;
079    import org.jfree.ui.RectangleInsets;
080    import org.jfree.ui.VerticalAlignment;
081    import org.jfree.util.ObjectUtilities;
082    
083    /**
084     * The base class for all chart titles.  A chart can have multiple titles, 
085     * appearing at the top, bottom, left or right of the chart.
086     * <P>
087     * Concrete implementations of this class will render text and images, and 
088     * hence do the actual work of drawing titles.
089     */
090    public abstract class Title extends AbstractBlock 
091                                implements Block, Cloneable, Serializable {
092    
093        /** For serialization. */
094        private static final long serialVersionUID = -6675162505277817221L;
095        
096        /** The default title position. */
097        public static final RectangleEdge DEFAULT_POSITION = RectangleEdge.TOP;
098    
099        /** The default horizontal alignment. */
100        public static final HorizontalAlignment 
101                DEFAULT_HORIZONTAL_ALIGNMENT = HorizontalAlignment.CENTER;
102    
103        /** The default vertical alignment. */
104        public static final VerticalAlignment 
105                DEFAULT_VERTICAL_ALIGNMENT = VerticalAlignment.CENTER;
106    
107        /** Default title padding. */
108        public static final RectangleInsets DEFAULT_PADDING = new RectangleInsets(
109                1, 1, 1, 1);
110    
111        /** The title position. */
112        private RectangleEdge position;
113    
114        /** The horizontal alignment of the title content. */
115        private HorizontalAlignment horizontalAlignment;
116    
117        /** The vertical alignment of the title content. */
118        private VerticalAlignment verticalAlignment;
119    
120        /** Storage for registered change listeners. */
121        private transient EventListenerList listenerList;
122    
123        /** 
124         * A flag that can be used to temporarily disable the listener mechanism. 
125         */
126        private boolean notify;
127    
128        /**
129         * Creates a new title, using default attributes where necessary.
130         */
131        protected Title() {
132            this(Title.DEFAULT_POSITION,
133                    Title.DEFAULT_HORIZONTAL_ALIGNMENT,
134                    Title.DEFAULT_VERTICAL_ALIGNMENT, Title.DEFAULT_PADDING);
135        }
136    
137        /**
138         * Creates a new title, using default attributes where necessary.
139         *
140         * @param position  the position of the title (<code>null</code> not 
141         *                  permitted).
142         * @param horizontalAlignment  the horizontal alignment of the title 
143         *                             (<code>null</code> not permitted).
144         * @param verticalAlignment  the vertical alignment of the title 
145         *                           (<code>null</code> not permitted).
146         */
147        protected Title(RectangleEdge position, 
148                        HorizontalAlignment horizontalAlignment, 
149                        VerticalAlignment verticalAlignment) {
150    
151            this(position, horizontalAlignment, verticalAlignment,
152                    Title.DEFAULT_PADDING);
153    
154        }
155    
156        /**
157         * Creates a new title.
158         *
159         * @param position  the position of the title (<code>null</code> not 
160         *                  permitted).
161         * @param horizontalAlignment  the horizontal alignment of the title (LEFT,
162         *                             CENTER or RIGHT, <code>null</code> not 
163         *                             permitted).
164         * @param verticalAlignment  the vertical alignment of the title (TOP, 
165         *                           MIDDLE or BOTTOM, <code>null</code> not 
166         *                           permitted).
167         * @param padding  the amount of space to leave around the outside of the 
168         *                 title (<code>null</code> not permitted).
169         */
170        protected Title(RectangleEdge position,
171                        HorizontalAlignment horizontalAlignment, 
172                        VerticalAlignment verticalAlignment,
173                        RectangleInsets padding) {
174    
175            // check arguments...
176            if (position == null) {
177                throw new IllegalArgumentException("Null 'position' argument.");
178            }
179            if (horizontalAlignment == null) {
180                throw new IllegalArgumentException(
181                        "Null 'horizontalAlignment' argument.");
182            }
183    
184            if (verticalAlignment == null) {
185                throw new IllegalArgumentException(
186                        "Null 'verticalAlignment' argument.");
187            }
188            if (padding == null) {
189                throw new IllegalArgumentException("Null 'spacer' argument.");
190            }
191    
192            this.position = position;
193            this.horizontalAlignment = horizontalAlignment;
194            this.verticalAlignment = verticalAlignment;
195            setPadding(padding);
196            this.listenerList = new EventListenerList();
197            this.notify = true;
198    
199        }
200    
201        /**
202         * Returns the position of the title.
203         *
204         * @return The title position (never <code>null</code>).
205         */
206        public RectangleEdge getPosition() {
207            return this.position;
208        }
209    
210        /**
211         * Sets the position for the title and sends a {@link TitleChangeEvent} to 
212         * all registered listeners.
213         *
214         * @param position  the position (<code>null</code> not permitted).
215         */
216        public void setPosition(RectangleEdge position) {
217            if (position == null) {
218                throw new IllegalArgumentException("Null 'position' argument.");
219            }
220            if (this.position != position) {
221                this.position = position;
222                notifyListeners(new TitleChangeEvent(this));
223            }
224        }
225    
226        /**
227         * Returns the horizontal alignment of the title.
228         *
229         * @return The horizontal alignment (never <code>null</code>).
230         */
231        public HorizontalAlignment getHorizontalAlignment() {
232            return this.horizontalAlignment;
233        }
234    
235        /**
236         * Sets the horizontal alignment for the title and sends a 
237         * {@link TitleChangeEvent} to all registered listeners.
238         *
239         * @param alignment  the horizontal alignment (<code>null</code> not 
240         *                   permitted).
241         */
242        public void setHorizontalAlignment(HorizontalAlignment alignment) {
243            if (alignment == null) {
244                throw new IllegalArgumentException("Null 'alignment' argument.");
245            }
246            if (this.horizontalAlignment != alignment) {
247                this.horizontalAlignment = alignment;
248                notifyListeners(new TitleChangeEvent(this));
249            }
250        }
251    
252        /**
253         * Returns the vertical alignment of the title.
254         *
255         * @return The vertical alignment (never <code>null</code>).
256         */
257        public VerticalAlignment getVerticalAlignment() {
258            return this.verticalAlignment;
259        }
260    
261        /**
262         * Sets the vertical alignment for the title, and notifies any registered
263         * listeners of the change.
264         *
265         * @param alignment  the new vertical alignment (TOP, MIDDLE or BOTTOM, 
266         *                   <code>null</code> not permitted).
267         */
268        public void setVerticalAlignment(VerticalAlignment alignment) {
269            if (alignment == null) {
270                throw new IllegalArgumentException("Null 'alignment' argument.");
271            }
272            if (this.verticalAlignment != alignment) {
273                this.verticalAlignment = alignment;
274                notifyListeners(new TitleChangeEvent(this));
275            }
276        }
277    
278        /**
279         * Returns the flag that indicates whether or not the notification 
280         * mechanism is enabled.
281         *
282         * @return The flag.
283         */
284        public boolean getNotify() {
285            return this.notify;
286        }
287    
288        /**
289         * Sets the flag that indicates whether or not the notification mechanism
290         * is enabled.  There are certain situations (such as cloning) where you
291         * want to turn notification off temporarily.
292         *
293         * @param flag  the new value of the flag.
294         */
295        public void setNotify(boolean flag) {
296            this.notify = flag;
297            if (flag) {
298                notifyListeners(new TitleChangeEvent(this));   
299            }
300        }
301    
302        /**
303         * Draws the title on a Java 2D graphics device (such as the screen or a 
304         * printer).
305         *
306         * @param g2  the graphics device.
307         * @param area  the area allocated for the title (subclasses should not
308         *              draw outside this area).
309         */
310        public abstract void draw(Graphics2D g2, Rectangle2D area);
311    
312        /**
313         * Returns a clone of the title.
314         * <P>
315         * One situation when this is useful is when editing the title properties -
316         * you can edit a clone, and then it is easier to cancel the changes if
317         * necessary.
318         *
319         * @return A clone of the title.
320         *
321         * @throws CloneNotSupportedException not thrown by this class, but it may 
322         *         be thrown by subclasses.
323         */
324        public Object clone() throws CloneNotSupportedException {
325    
326            Title duplicate = (Title) super.clone();
327            duplicate.listenerList = new EventListenerList();
328            // RectangleInsets is immutable => same reference in clone OK
329            return duplicate;
330        }
331    
332        /**
333         * Registers an object for notification of changes to the title.
334         *
335         * @param listener  the object that is being registered.
336         */
337        public void addChangeListener(TitleChangeListener listener) {
338            this.listenerList.add(TitleChangeListener.class, listener);
339        }
340    
341        /**
342         * Unregisters an object for notification of changes to the chart title.
343         *
344         * @param listener  the object that is being unregistered.
345         */
346        public void removeChangeListener(TitleChangeListener listener) {
347            this.listenerList.remove(TitleChangeListener.class, listener);
348        }
349    
350        /**
351         * Notifies all registered listeners that the chart title has changed in 
352         * some way.
353         *
354         * @param event  an object that contains information about the change to 
355         *               the title.
356         */
357        protected void notifyListeners(TitleChangeEvent event) {
358            if (this.notify) {
359                Object[] listeners = this.listenerList.getListenerList();
360                for (int i = listeners.length - 2; i >= 0; i -= 2) {
361                    if (listeners[i] == TitleChangeListener.class) {
362                        ((TitleChangeListener) listeners[i + 1]).titleChanged(
363                                event);
364                    }
365                }
366            }
367        }
368    
369        /**
370         * Tests an object for equality with this title.
371         *
372         * @param obj  the object (<code>null</code> not permitted).
373         *
374         * @return <code>true</code> or <code>false</code>.
375         */
376        public boolean equals(Object obj) {
377            if (obj == this) {
378                return true;
379            }
380            if (!(obj instanceof Title)) {
381                return false;
382            }
383            if (!super.equals(obj)) {
384                return false;   
385            }
386            Title that = (Title) obj;
387            if (this.position != that.position) {
388                return false;
389            }
390            if (this.horizontalAlignment != that.horizontalAlignment) {
391                return false;
392            }
393            if (this.verticalAlignment != that.verticalAlignment) {
394                return false;
395            }
396            if (this.notify != that.notify) {
397                return false;
398            }
399            return true;
400        }
401    
402        /**
403         * Returns a hashcode for the title.
404         * 
405         * @return The hashcode.
406         */
407        public int hashCode() {
408            int result = 193;
409            result = 37 * result + ObjectUtilities.hashCode(this.position);    
410            result = 37 * result 
411                    + ObjectUtilities.hashCode(this.horizontalAlignment);    
412            result = 37 * result + ObjectUtilities.hashCode(this.verticalAlignment);
413            return result;
414        }
415            
416        /**
417         * Provides serialization support.
418         *
419         * @param stream  the output stream.
420         *
421         * @throws IOException  if there is an I/O error.
422         */
423        private void writeObject(ObjectOutputStream stream) throws IOException {
424            stream.defaultWriteObject();
425        }
426    
427        /**
428         * Provides serialization support.
429         *
430         * @param stream  the input stream.
431         *
432         * @throws IOException  if there is an I/O error.
433         * @throws ClassNotFoundException  if there is a classpath problem.
434         */
435        private void readObject(ObjectInputStream stream) 
436            throws IOException, ClassNotFoundException {
437            stream.defaultReadObject();
438            this.listenerList = new EventListenerList();
439        }
440    
441    }