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
061package org.jfree.chart.plot;
062
063import java.awt.BasicStroke;
064import java.awt.Color;
065import java.awt.Font;
066import java.awt.Paint;
067import java.awt.Stroke;
068import java.io.IOException;
069import java.io.ObjectInputStream;
070import java.io.ObjectOutputStream;
071import java.io.Serializable;
072import java.util.EventListener;
073
074import javax.swing.event.EventListenerList;
075
076import org.jfree.chart.event.MarkerChangeEvent;
077import org.jfree.chart.event.MarkerChangeListener;
078import org.jfree.io.SerialUtilities;
079import org.jfree.ui.LengthAdjustmentType;
080import org.jfree.ui.RectangleAnchor;
081import org.jfree.ui.RectangleInsets;
082import org.jfree.ui.TextAnchor;
083import org.jfree.util.ObjectUtilities;
084import 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 */
093public 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}