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