001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2006, 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     * Marker.java
029     * -----------
030     * (C) Copyright 2002-2006, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   Nicolas Brodu;
034     *
035     * $Id: Marker.java,v 1.10.2.5 2006/09/05 14:34:23 mungady Exp $
036     *
037     * Changes (since 2-Jul-2002)
038     * --------------------------
039     * 02-Jul-2002 : Added extra constructor, standard header and Javadoc 
040     *               comments (DG);
041     * 20-Aug-2002 : Added the outline stroke attribute (DG);
042     * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
043     * 16-Oct-2002 : Added new constructor (DG);
044     * 26-Mar-2003 : Implemented Serializable (DG);
045     * 21-May-2003 : Added labels (DG);
046     * 11-Sep-2003 : Implemented Cloneable (NB);
047     * 05-Nov-2003 : Added checks to ensure some attributes are never null (DG);
048     * 11-Feb-2003 : Moved to org.jfree.chart.plot package, plus significant API 
049     *               changes to support IntervalMarker in plots (DG);
050     * 14-Jun-2004 : Updated equals() method (DG);
051     * 21-Jan-2005 : Added settings to control direction of horizontal and 
052     *               vertical label offsets (DG);
053     * 01-Jun-2005 : Modified to use only one label offset type - this will be 
054     *               applied to the domain or range axis as appropriate (DG);
055     * 06-Jun-2005 : Fix equals() method to handle GradientPaint (DG);
056     * 19-Aug-2005 : Changed constructor from public --> protected (DG);
057     * ------------- JFREECHART 1.0.0 ---------------------------------------------
058     * 05-Sep-2006 : Added MarkerChangeListener support (DG);
059     *
060     */
061    
062    package org.jfree.chart.plot;
063    
064    import java.awt.BasicStroke;
065    import java.awt.Color;
066    import java.awt.Font;
067    import java.awt.Paint;
068    import java.awt.Stroke;
069    import java.io.IOException;
070    import java.io.ObjectInputStream;
071    import java.io.ObjectOutputStream;
072    import java.io.Serializable;
073    import java.util.EventListener;
074    
075    import javax.swing.event.EventListenerList;
076    
077    import org.jfree.chart.event.MarkerChangeEvent;
078    import org.jfree.chart.event.MarkerChangeListener;
079    import org.jfree.io.SerialUtilities;
080    import org.jfree.ui.LengthAdjustmentType;
081    import org.jfree.ui.RectangleAnchor;
082    import org.jfree.ui.RectangleInsets;
083    import org.jfree.ui.TextAnchor;
084    import org.jfree.util.ObjectUtilities;
085    import org.jfree.util.PaintUtilities;
086    
087    /**
088     * The base class for markers that can be added to plots to highlight a value 
089     * or range of values.
090     * <br><br>
091     * An event notification mechanism was added to this class in JFreeChart 
092     * version 1.0.3.
093     */
094    public abstract class Marker implements Cloneable, Serializable {
095    
096        /** For serialization. */
097        private static final long serialVersionUID = -734389651405327166L;
098    
099        /** The paint. */
100        private transient Paint paint;
101    
102        /** The stroke. */
103        private transient Stroke stroke;
104        
105        /** The outline paint. */
106        private transient Paint outlinePaint;
107    
108        /** The outline stroke. */
109        private transient Stroke outlineStroke;
110    
111        /** The alpha transparency. */
112        private float alpha;
113    
114        /** The label. */
115        private String label = null;
116    
117        /** The label font. */
118        private Font labelFont;
119    
120        /** The label paint. */
121        private transient Paint labelPaint;
122    
123        /** The label position. */
124        private RectangleAnchor labelAnchor;
125        
126        /** The text anchor for the label. */
127        private TextAnchor labelTextAnchor;
128    
129        /** The label offset from the marker rectangle. */
130        private RectangleInsets labelOffset;
131        
132        /** 
133         * The offset type for the domain or range axis (never <code>null</code>). 
134         */
135        private LengthAdjustmentType labelOffsetType;
136        
137        /** Storage for registered change listeners. */
138        private transient EventListenerList listenerList;
139    
140        /**
141         * Creates a new marker with default attributes.
142         */
143        protected Marker() {
144            this(Color.gray);
145        }
146    
147        /**
148         * Constructs a new marker.
149         *
150         * @param paint  the paint (<code>null</code> not permitted).
151         */
152        protected Marker(Paint paint) {
153            this(paint, new BasicStroke(0.5f), Color.gray, new BasicStroke(0.5f), 
154                    0.80f);
155        }
156    
157        /**
158         * Constructs a new marker.
159         *
160         * @param paint  the paint (<code>null</code> not permitted).
161         * @param stroke  the stroke (<code>null</code> not permitted).
162         * @param outlinePaint  the outline paint (<code>null</code> permitted).
163         * @param outlineStroke  the outline stroke (<code>null</code> permitted).
164         * @param alpha  the alpha transparency (must be in the range 0.0f to 
165         *     1.0f).
166         *     
167         * @throws IllegalArgumentException if <code>paint</code> or 
168         *     <code>stroke</code> is <code>null</code>, or <code>alpha</code> is 
169         *     not in the specified range.
170         */
171        protected Marker(Paint paint, Stroke stroke, 
172                         Paint outlinePaint, Stroke outlineStroke, 
173                         float alpha) {
174    
175            if (paint == null) {
176                throw new IllegalArgumentException("Null 'paint' argument.");
177            }
178            if (stroke == null) {
179                throw new IllegalArgumentException("Null 'stroke' argument.");
180            }
181            if (alpha < 0.0f || alpha > 1.0f)
182                throw new IllegalArgumentException(
183                        "The 'alpha' value must be in the range 0.0f to 1.0f");
184            
185            this.paint = paint;
186            this.stroke = stroke;
187            this.outlinePaint = outlinePaint;
188            this.outlineStroke = outlineStroke;
189            this.alpha = alpha;
190            
191            this.labelFont = new Font("SansSerif", Font.PLAIN, 9);
192            this.labelPaint = Color.black;
193            this.labelAnchor = RectangleAnchor.TOP_LEFT;
194            this.labelOffset = new RectangleInsets(3.0, 3.0, 3.0, 3.0);
195            this.labelOffsetType = LengthAdjustmentType.CONTRACT;
196            this.labelTextAnchor = TextAnchor.CENTER;
197            
198            this.listenerList = new EventListenerList();
199        }
200    
201        /**
202         * Returns the paint.
203         *
204         * @return The paint (never <code>null</code>).
205         * 
206         * @see #setPaint(Paint)
207         */
208        public Paint getPaint() {
209            return this.paint;
210        }
211        
212        /**
213         * Sets the paint and sends a {@link MarkerChangeEvent} to all registered
214         * listeners.
215         * 
216         * @param paint  the paint (<code>null</code> not permitted).
217         * 
218         * @see #getPaint()
219         */
220        public void setPaint(Paint paint) {
221            if (paint == null) {
222                throw new IllegalArgumentException("Null 'paint' argument.");
223            }
224            this.paint = paint;
225            notifyListeners(new MarkerChangeEvent(this));
226        }
227    
228        /**
229         * Returns the stroke.
230         *
231         * @return The stroke (never <code>null</code>).
232         * 
233         * @see #setStroke(Stroke)
234         */
235        public Stroke getStroke() {
236            return this.stroke;
237        }
238        
239        /**
240         * Sets the stroke and sends a {@link MarkerChangeEvent} to all registered
241         * listeners.
242         * 
243         * @param stroke  the stroke (<code>null</code> not permitted).
244         * 
245         * @see #getStroke()
246         */
247        public void setStroke(Stroke stroke) {
248            if (stroke == null) {
249                throw new IllegalArgumentException("Null 'stroke' argument.");
250            }
251            this.stroke = stroke;
252            notifyListeners(new MarkerChangeEvent(this));
253        }
254    
255        /**
256         * Returns the outline paint.
257         *
258         * @return The outline paint (possibly <code>null</code>).
259         * 
260         * @see #setOutlinePaint(Paint)
261         */
262        public Paint getOutlinePaint() {
263            return this.outlinePaint;
264        }
265        
266        /**
267         * Sets the outline paint and sends a {@link MarkerChangeEvent} to all 
268         * registered listeners.
269         * 
270         * @param paint  the paint (<code>null</code> permitted).
271         * 
272         * @see #getOutlinePaint()
273         */
274        public void setOutlinePaint(Paint paint) {
275            this.outlinePaint = paint;
276            notifyListeners(new MarkerChangeEvent(this));
277        }
278    
279        /**
280         * Returns the outline stroke.
281         *
282         * @return The outline stroke (possibly <code>null</code>).
283         * 
284         * @see #setOutlineStroke(Stroke)
285         */
286        public Stroke getOutlineStroke() {
287            return this.outlineStroke;
288        }
289        
290        /**
291         * Sets the outline stroke and sends a {@link MarkerChangeEvent} to all 
292         * registered listeners.
293         * 
294         * @param stroke  the stroke (<code>null</code> permitted).
295         * 
296         * @see #getOutlineStroke()
297         */
298        public void setOutlineStroke(Stroke stroke) {
299            this.outlineStroke = stroke;
300            notifyListeners(new MarkerChangeEvent(this));
301        }
302    
303        /**
304         * Returns the alpha transparency.
305         *
306         * @return The alpha transparency.
307         * 
308         * @see #setAlpha(float)
309         */
310        public float getAlpha() {
311            return this.alpha;
312        }
313        
314        /**
315         * Sets the alpha transparency that should be used when drawing the 
316         * marker, and sends a {@link MarkerChangeEvent} to all registered 
317         * listeners.  The alpha transparency is a value in the range 0.0f 
318         * (completely transparent) to 1.0f (completely opaque).
319         * 
320         * @param alpha  the alpha transparency (must be in the range 0.0f to 
321         *     1.0f).
322         *     
323         * @throws IllegalArgumentException if <code>alpha</code> is not in the
324         *     specified range.
325         *     
326         * @see #getAlpha()
327         */
328        public void setAlpha(float alpha) {
329            if (alpha < 0.0f || alpha > 1.0f)
330                throw new IllegalArgumentException(
331                        "The 'alpha' value must be in the range 0.0f to 1.0f");
332            this.alpha = alpha;
333            notifyListeners(new MarkerChangeEvent(this));
334        }
335    
336        /**
337         * Returns the label (if <code>null</code> no label is displayed).
338         *
339         * @return The label (possibly <code>null</code>).
340         * 
341         * @see #setLabel(String)
342         */
343        public String getLabel() {
344            return this.label;
345        }
346    
347        /**
348         * Sets the label (if <code>null</code> no label is displayed) and sends a
349         * {@link MarkerChangeEvent} to all registered listeners.
350         *
351         * @param label  the label (<code>null</code> permitted).
352         * 
353         * @see #getLabel()
354         */
355        public void setLabel(String label) {
356            this.label = label;
357            notifyListeners(new MarkerChangeEvent(this));
358        }
359    
360        /**
361         * Returns the label font.
362         *
363         * @return The label font (never <code>null</code>).
364         * 
365         * @see #setLabelFont(Font)
366         */
367        public Font getLabelFont() {
368            return this.labelFont;
369        }
370    
371        /**
372         * Sets the label font and sends a {@link MarkerChangeEvent} to all 
373         * registered listeners.
374         *
375         * @param font  the font (<code>null</code> not permitted).
376         * 
377         * @see #getLabelFont()
378         */
379        public void setLabelFont(Font font) {
380            if (font == null) {
381                throw new IllegalArgumentException("Null 'font' argument.");
382            }
383            this.labelFont = font;
384            notifyListeners(new MarkerChangeEvent(this));
385        }
386    
387        /**
388         * Returns the label paint.
389         *
390         * @return The label paint (never </code>null</code>).
391         * 
392         * @see #setLabelPaint(Paint)
393         */
394        public Paint getLabelPaint() {
395            return this.labelPaint;
396        }
397    
398        /**
399         * Sets the label paint and sends a {@link MarkerChangeEvent} to all
400         * registered listeners.
401         *
402         * @param paint  the paint (<code>null</code> not permitted).
403         * 
404         * @see #getLabelPaint()
405         */
406        public void setLabelPaint(Paint paint) {
407            if (paint == null) {
408                throw new IllegalArgumentException("Null 'paint' argument.");
409            }
410            this.labelPaint = paint;
411            notifyListeners(new MarkerChangeEvent(this));
412        }
413    
414        /**
415         * Returns the label anchor.  This defines the position of the label 
416         * anchor, relative to the bounds of the marker.
417         *
418         * @return The label anchor (never <code>null</code>).
419         * 
420         * @see #setLabelAnchor(RectangleAnchor)
421         */
422        public RectangleAnchor getLabelAnchor() {
423            return this.labelAnchor;
424        }
425    
426        /**
427         * Sets the label anchor and sends a {@link MarkerChangeEvent} to all 
428         * registered listeners.  The anchor defines the position of the label 
429         * anchor, relative to the bounds of the marker.
430         *
431         * @param anchor  the anchor (<code>null</code> not permitted).
432         * 
433         * @see #getLabelAnchor()
434         */
435        public void setLabelAnchor(RectangleAnchor anchor) {
436            if (anchor == null) {
437                throw new IllegalArgumentException("Null 'anchor' argument.");
438            }
439            this.labelAnchor = anchor;
440            notifyListeners(new MarkerChangeEvent(this));
441        }
442    
443        /**
444         * Returns the label offset.
445         * 
446         * @return The label offset (never <code>null</code>).
447         * 
448         * @see #setLabelOffset(RectangleInsets)
449         */
450        public RectangleInsets getLabelOffset() {
451            return this.labelOffset;
452        }
453        
454        /**
455         * Sets the label offset and sends a {@link MarkerChangeEvent} to all
456         * registered listeners.
457         * 
458         * @param offset  the label offset (<code>null</code> not permitted).
459         * 
460         * @see #getLabelOffset()
461         */
462        public void setLabelOffset(RectangleInsets offset) {
463            if (offset == null) {
464                throw new IllegalArgumentException("Null 'offset' argument.");
465            }
466            this.labelOffset = offset;
467            notifyListeners(new MarkerChangeEvent(this));
468        }
469        
470        /**
471         * Returns the label offset type.
472         * 
473         * @return The type (never <code>null</code>).
474         * 
475         * @see #setLabelOffsetType(LengthAdjustmentType)
476         */
477        public LengthAdjustmentType getLabelOffsetType() {
478            return this.labelOffsetType;   
479        }
480        
481        /**
482         * Sets the label offset type and sends a {@link MarkerChangeEvent} to all
483         * registered listeners.
484         * 
485         * @param adj  the type (<code>null</code> not permitted).
486         * 
487         * @see #getLabelOffsetType()
488         */
489        public void setLabelOffsetType(LengthAdjustmentType adj) {
490            if (adj == null) {
491                throw new IllegalArgumentException("Null 'adj' argument.");
492            }
493            this.labelOffsetType = adj;    
494            notifyListeners(new MarkerChangeEvent(this));
495        }
496            
497        /**
498         * Returns the label text anchor.
499         * 
500         * @return The label text anchor (never <code>null</code>).
501         * 
502         * @see #setLabelTextAnchor(TextAnchor)
503         */
504        public TextAnchor getLabelTextAnchor() {
505            return this.labelTextAnchor;
506        }
507        
508        /**
509         * Sets the label text anchor and sends a {@link MarkerChangeEvent} to 
510         * all registered listeners.
511         * 
512         * @param anchor  the label text anchor (<code>null</code> not permitted).
513         * 
514         * @see #getLabelTextAnchor()
515         */
516        public void setLabelTextAnchor(TextAnchor anchor) {
517            if (anchor == null) { 
518                throw new IllegalArgumentException("Null 'anchor' argument.");
519            }
520            this.labelTextAnchor = anchor;
521            notifyListeners(new MarkerChangeEvent(this));
522        }
523        
524        /**
525         * Registers an object for notification of changes to the marker.
526         *
527         * @param listener  the object to be registered.
528         * 
529         * @since 1.0.3
530         */
531        public void addChangeListener(MarkerChangeListener listener) {
532            this.listenerList.add(MarkerChangeListener.class, listener);
533        }
534    
535        /**
536         * Unregisters an object for notification of changes to the marker.
537         *
538         * @param listener  the object to be unregistered.
539         * 
540         * @since 1.0.3
541         */
542        public void removeChangeListener(MarkerChangeListener listener) {
543            this.listenerList.remove(MarkerChangeListener.class, listener);
544        }
545    
546        /**
547         * Notifies all registered listeners that the marker has been modified.
548         *
549         * @param event  information about the change event.
550         * 
551         * @since 1.0.3
552         */
553        public void notifyListeners(MarkerChangeEvent event) {
554    
555            Object[] listeners = this.listenerList.getListenerList();
556            for (int i = listeners.length - 2; i >= 0; i -= 2) {
557                if (listeners[i] == MarkerChangeListener.class) {
558                    ((MarkerChangeListener) listeners[i + 1]).markerChanged(event);
559                }
560            }
561    
562        }
563    
564        /**
565         * Returns an array containing all the listeners of the specified type.
566         * 
567         * @param listenerType  the listener type.
568         * 
569         * @return The array of listeners.
570         * 
571         * @since 1.0.3
572         */
573        public EventListener[] getListeners(Class listenerType) {
574            return this.listenerList.getListeners(listenerType);    
575        }
576        
577        /**
578         * Tests the marker for equality with an arbitrary object.
579         * 
580         * @param obj  the object (<code>null</code> permitted).
581         * 
582         * @return A boolean.
583         */
584        public boolean equals(Object obj) {
585            if (obj == this) {
586                return true;
587            }
588            if (!(obj instanceof Marker)) {
589                return false;
590            }
591            Marker that = (Marker) obj;
592            if (!PaintUtilities.equal(this.paint, that.paint)) {
593                return false;   
594            }
595            if (!ObjectUtilities.equal(this.stroke, that.stroke)) {
596                return false;
597            }
598            if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) {
599                return false;   
600            }
601            if (!ObjectUtilities.equal(this.outlineStroke, that.outlineStroke)) {
602                return false;
603            }
604            if (this.alpha != that.alpha) {
605                return false;
606            }
607            if (!ObjectUtilities.equal(this.label, that.label)) {
608                return false;
609            }
610            if (!ObjectUtilities.equal(this.labelFont, that.labelFont)) {
611                return false;
612            }
613            if (!PaintUtilities.equal(this.labelPaint, that.labelPaint)) {
614                return false;
615            }
616            if (this.labelAnchor != that.labelAnchor) {
617                return false;
618            }
619            if (this.labelTextAnchor != that.labelTextAnchor) {
620                return false;   
621            }
622            if (!ObjectUtilities.equal(this.labelOffset, that.labelOffset)) {
623                return false;
624            }
625            if (!this.labelOffsetType.equals(that.labelOffsetType)) {
626                return false;
627            }
628            return true;
629        }
630        
631        /**
632         * Creates a clone of the marker.
633         * 
634         * @return A clone.
635         * 
636         * @throws CloneNotSupportedException never.
637         */
638        public Object clone() throws CloneNotSupportedException {
639            return super.clone();
640        }
641        
642        /**
643         * Provides serialization support.
644         *
645         * @param stream  the output stream.
646         *
647         * @throws IOException  if there is an I/O error.
648         */
649        private void writeObject(ObjectOutputStream stream) throws IOException {
650            stream.defaultWriteObject();
651            SerialUtilities.writePaint(this.paint, stream);
652            SerialUtilities.writeStroke(this.stroke, stream);
653            SerialUtilities.writePaint(this.outlinePaint, stream);
654            SerialUtilities.writeStroke(this.outlineStroke, stream);
655            SerialUtilities.writePaint(this.labelPaint, stream);
656        }
657    
658        /**
659         * Provides serialization support.
660         *
661         * @param stream  the input stream.
662         *
663         * @throws IOException  if there is an I/O error.
664         * @throws ClassNotFoundException  if there is a classpath problem.
665         */
666        private void readObject(ObjectInputStream stream) 
667            throws IOException, ClassNotFoundException {
668            stream.defaultReadObject();
669            this.paint = SerialUtilities.readPaint(stream);
670            this.stroke = SerialUtilities.readStroke(stream);
671            this.outlinePaint = SerialUtilities.readPaint(stream);
672            this.outlineStroke = SerialUtilities.readStroke(stream);
673            this.labelPaint = SerialUtilities.readPaint(stream);
674        }
675    
676    }