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