001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2009, 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 * Crosshair.java
029 * --------------
030 * (C) Copyright 2009, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 * Changes:
036 * --------
037 * 13-Feb-2009 : Version 1 (DG);
038 *
039 */
040
041package org.jfree.chart.plot;
042
043import java.awt.BasicStroke;
044import java.awt.Color;
045import java.awt.Font;
046import java.awt.Paint;
047import java.awt.Stroke;
048import java.beans.PropertyChangeListener;
049import java.beans.PropertyChangeSupport;
050import java.io.IOException;
051import java.io.ObjectInputStream;
052import java.io.ObjectOutputStream;
053import java.io.Serializable;
054import org.jfree.chart.HashUtilities;
055import org.jfree.chart.labels.CrosshairLabelGenerator;
056import org.jfree.chart.labels.StandardCrosshairLabelGenerator;
057import org.jfree.io.SerialUtilities;
058import org.jfree.ui.RectangleAnchor;
059import org.jfree.util.PaintUtilities;
060import org.jfree.util.PublicCloneable;
061
062/**
063 * A crosshair for display on a plot.
064 *
065 * @since 1.0.13
066 */
067public class Crosshair implements Cloneable, PublicCloneable, Serializable {
068
069    /** Flag controlling visibility. */
070    private boolean visible;
071
072    /** The crosshair value. */
073    private double value;
074
075    /** The paint for the crosshair line. */
076    private transient Paint paint;
077
078    /** The stroke for the crosshair line. */
079    private transient Stroke stroke;
080
081    /**
082     * A flag that controls whether or not the crosshair has a label
083     * visible.
084     */
085    private boolean labelVisible;
086
087    /**
088     * The label anchor.
089     */
090    private RectangleAnchor labelAnchor;
091
092    /** A label generator. */
093    private CrosshairLabelGenerator labelGenerator;
094
095    /**
096     * The x-offset in Java2D units.
097     */
098    private double labelXOffset;
099
100    /**
101     * The y-offset in Java2D units.
102     */
103    private double labelYOffset;
104
105    /**
106     * The label font.
107     */
108    private Font labelFont;
109
110    /**
111     * The label paint.
112     */
113    private transient Paint labelPaint;
114
115    /**
116     * The label background paint.
117     */
118    private transient Paint labelBackgroundPaint;
119
120    /** A flag that controls the visibility of the label outline. */
121    private boolean labelOutlineVisible;
122
123    /** The label outline stroke. */
124    private transient Stroke labelOutlineStroke;
125
126    /** The label outline paint. */
127    private transient Paint labelOutlinePaint;
128
129    /** Property change support. */
130    private transient PropertyChangeSupport pcs;
131
132    /**
133     * Creates a new crosshair with value 0.0.
134     */
135    public Crosshair() {
136        this(0.0);
137    }
138
139    /**
140     * Creates a new crosshair with the specified value.
141     *
142     * @param value  the value.
143     */
144    public Crosshair(double value) {
145       this(value, Color.black, new BasicStroke(1.0f));
146    }
147
148    /**
149     * Creates a new crosshair value with the specified value and line style.
150     *
151     * @param value  the value.
152     * @param paint  the line paint (<code>null</code> not permitted).
153     * @param stroke  the line stroke (<code>null</code> not permitted).
154     */
155    public Crosshair(double value, Paint paint, Stroke stroke) {
156        if (paint == null) {
157            throw new IllegalArgumentException("Null 'paint' argument.");
158        }
159        if (stroke == null) {
160            throw new IllegalArgumentException("Null 'stroke' argument.");
161        }
162        this.visible = true;
163        this.value = value;
164        this.paint = paint;
165        this.stroke = stroke;
166        this.labelVisible = false;
167        this.labelGenerator = new StandardCrosshairLabelGenerator();
168        this.labelAnchor = RectangleAnchor.BOTTOM_LEFT;
169        this.labelXOffset = 3.0;
170        this.labelYOffset = 3.0;
171        this.labelFont = new Font("Tahoma", Font.PLAIN, 12);
172        this.labelPaint = Color.black;
173        this.labelBackgroundPaint = new Color(0, 0, 255, 63);
174        this.labelOutlineVisible = true;
175        this.labelOutlinePaint = Color.black;
176        this.labelOutlineStroke = new BasicStroke(0.5f);
177        this.pcs = new PropertyChangeSupport(this);
178    }
179
180    /**
181     * Returns the flag that indicates whether or not the crosshair is
182     * currently visible.
183     *
184     * @return A boolean.
185     */
186    public boolean isVisible() {
187        return this.visible;
188    }
189
190    /**
191     * Sets the flag that controls the visibility of the crosshair and sends
192     * a proerty change event (with the name 'visible') to all registered
193     * listeners.
194     *
195     * @param visible  the new flag value.
196     */
197    public void setVisible(boolean visible) {
198        boolean old = this.visible;
199        this.visible = visible;
200        this.pcs.firePropertyChange("visible", old, visible);
201    }
202
203    /**
204     * Returns the crosshair value.
205     *
206     * @return The crosshair value.
207     */
208    public double getValue() {
209        return this.value;
210    }
211
212    /**
213     * Sets the crosshair value and sends a property change event with the name
214     * 'value' to all registered listeners.
215     *
216     * @param value  the value.
217     */
218    public void setValue(double value) {
219        Double oldValue = new Double(this.value);
220        this.value = value;
221        this.pcs.firePropertyChange("value", oldValue, new Double(value));
222    }
223
224    /**
225     * Returns the paint for the crosshair line.
226     *
227     * @return The paint (never <code>null</code>).
228     */
229    public Paint getPaint() {
230        return this.paint;
231    }
232
233    /**
234     * Sets the paint for the crosshair line and sends a property change event
235     * with the name "paint" to all registered listeners.
236     *
237     * @param paint  the paint (<code>null</code> not permitted).
238     */
239    public void setPaint(Paint paint) {
240        Paint old = this.paint;
241        this.paint = paint;
242        this.pcs.firePropertyChange("paint", old, paint);
243    }
244
245    /**
246     * Returns the stroke for the crosshair line.
247     *
248     * @return The stroke (never <code>null</code>).
249     */
250    public Stroke getStroke() {
251        return this.stroke;
252    }
253
254    /**
255     * Sets the stroke for the crosshair line and sends a property change event
256     * with the name "stroke" to all registered listeners.
257     *
258     * @param stroke  the stroke (<code>null</code> not permitted).
259     */
260    public void setStroke(Stroke stroke) {
261        Stroke old = this.stroke;
262        this.stroke = stroke;
263        this.pcs.firePropertyChange("stroke", old, stroke);
264    }
265
266    /**
267     * Returns the flag that controls whether or not a label is drawn for
268     * this crosshair.
269     *
270     * @return A boolean.
271     */
272    public boolean isLabelVisible() {
273        return this.labelVisible;
274    }
275
276    /**
277     * Sets the flag that controls whether or not a label is drawn for the
278     * crosshair and sends a property change event (with the name
279     * 'labelVisible') to all registered listeners.
280     *
281     * @param visible  the new flag value.
282     */
283    public void setLabelVisible(boolean visible) {
284        boolean old = this.labelVisible;
285        this.labelVisible = visible;
286        this.pcs.firePropertyChange("labelVisible", old, visible);
287    }
288
289    /**
290     * Returns the crosshair label generator.
291     *
292     * @return The label crosshair generator (never <code>null</code>).
293     */
294    public CrosshairLabelGenerator getLabelGenerator() {
295        return this.labelGenerator;
296    }
297
298    /**
299     * Sets the crosshair label generator and sends a property change event
300     * (with the name 'labelGenerator') to all registered listeners.
301     *
302     * @param generator  the new generator (<code>null</code> not permitted).
303     */
304    public void setLabelGenerator(CrosshairLabelGenerator generator) {
305        if (generator == null) {
306            throw new IllegalArgumentException("Null 'generator' argument.");
307        }
308        CrosshairLabelGenerator old = this.labelGenerator;
309        this.labelGenerator = generator;
310        this.pcs.firePropertyChange("labelGenerator", old, generator);
311    }
312
313    /**
314     * Returns the label anchor point.
315     *
316     * @return the label anchor point (never <code>null</code>.
317     */
318    public RectangleAnchor getLabelAnchor() {
319        return this.labelAnchor;
320    }
321
322    /**
323     * Sets the label anchor point and sends a property change event (with the
324     * name 'labelAnchor') to all registered listeners.
325     *
326     * @param anchor  the anchor (<code>null</code> not permitted).
327     */
328    public void setLabelAnchor(RectangleAnchor anchor) {
329        RectangleAnchor old = this.labelAnchor;
330        this.labelAnchor = anchor;
331        this.pcs.firePropertyChange("labelAnchor", old, anchor);
332    }
333
334    /**
335     * Returns the x-offset for the label (in Java2D units).
336     *
337     * @return The x-offset.
338     */
339    public double getLabelXOffset() {
340        return this.labelXOffset;
341    }
342
343    /**
344     * Sets the x-offset and sends a property change event (with the name
345     * 'labelXOffset') to all registered listeners.
346     *
347     * @param offset  the new offset.
348     */
349    public void setLabelXOffset(double offset) {
350        Double old = new Double(this.labelXOffset);
351        this.labelXOffset = offset;
352        this.pcs.firePropertyChange("labelXOffset", old, new Double(offset));
353    }
354
355    /**
356     * Returns the y-offset for the label (in Java2D units).
357     *
358     * @return The y-offset.
359     */
360    public double getLabelYOffset() {
361        return this.labelYOffset;
362    }
363
364    /**
365     * Sets the y-offset and sends a property change event (with the name
366     * 'labelYOffset') to all registered listeners.
367     *
368     * @param offset  the new offset.
369     */
370    public void setLabelYOffset(double offset) {
371        Double old = new Double(this.labelYOffset);
372        this.labelYOffset = offset;
373        this.pcs.firePropertyChange("labelYOffset", old, new Double(offset));
374    }
375
376    /**
377     * Returns the label font.
378     *
379     * @return The label font (never <code>null</code>).
380     */
381    public Font getLabelFont() {
382        return this.labelFont;
383    }
384
385    /**
386     * Sets the label font and sends a property change event (with the name
387     * 'labelFont') to all registered listeners.
388     *
389     * @param font  the font (<code>null</code> not permitted).
390     */
391    public void setLabelFont(Font font) {
392        if (font == null) {
393            throw new IllegalArgumentException("Null 'font' argument.");
394        }
395        Font old = this.labelFont;
396        this.labelFont = font;
397        this.pcs.firePropertyChange("labelFont", old, font);
398    }
399
400    /**
401     * Returns the label paint.
402     *
403     * @return The label paint (never <code>null</code>).
404     */
405    public Paint getLabelPaint() {
406        return this.labelPaint;
407    }
408
409    /**
410     * Sets the label paint and sends a property change event (with the name
411     * 'labelPaint') to all registered listeners.
412     *
413     * @param paint  the paint (<code>null</code> not permitted).
414     */
415    public void setLabelPaint(Paint paint) {
416        if (paint == null) {
417            throw new IllegalArgumentException("Null 'paint' argument.");
418        }
419        Paint old = this.labelPaint;
420        this.labelPaint = paint;
421        this.pcs.firePropertyChange("labelPaint", old, paint);
422    }
423
424    /**
425     * Returns the label background paint.
426     *
427     * @return The label background paint (possibly <code>null</code>).
428     */
429    public Paint getLabelBackgroundPaint() {
430        return this.labelBackgroundPaint;
431    }
432
433    /**
434     * Sets the label background paint and sends a property change event with
435     * the name 'labelBackgroundPaint') to all registered listeners.
436     *
437     * @param paint  the paint (<code>null</code> permitted).
438     */
439    public void setLabelBackgroundPaint(Paint paint) {
440        Paint old = this.labelBackgroundPaint;
441        this.labelBackgroundPaint = paint;
442        this.pcs.firePropertyChange("labelBackgroundPaint", old, paint);
443    }
444
445    /**
446     * Returns the flag that controls the visibility of the label outline.
447     *
448     * @return A boolean.
449     */
450    public boolean isLabelOutlineVisible() {
451        return this.labelOutlineVisible;
452    }
453
454    /**
455     * Sets the flag that controls the visibility of the label outlines and
456     * sends a property change event (with the name "labelOutlineVisible") to
457     * all registered listeners.
458     *
459     * @param visible  the new flag value.
460     */
461    public void setLabelOutlineVisible(boolean visible) {
462        boolean old = this.labelOutlineVisible;
463        this.labelOutlineVisible = visible;
464        this.pcs.firePropertyChange("labelOutlineVisible", old, visible);
465    }
466
467    /**
468     * Returns the label outline paint.
469     *
470     * @return The label outline paint (never <code>null</code>).
471     */
472    public Paint getLabelOutlinePaint() {
473        return this.labelOutlinePaint;
474    }
475
476    /**
477     * Sets the label outline paint and sends a property change event (with the
478     * name "labelOutlinePaint") to all registered listeners.
479     *
480     * @param paint  the paint (<code>null</code> not permitted).
481     */
482    public void setLabelOutlinePaint(Paint paint) {
483        if (paint == null) {
484            throw new IllegalArgumentException("Null 'paint' argument.");
485        }
486        Paint old = this.labelOutlinePaint;
487        this.labelOutlinePaint = paint;
488        this.pcs.firePropertyChange("labelOutlinePaint", old, paint);
489    }
490
491    /**
492     * Returns the label outline stroke.
493     *
494     * @return The label outline stroke (never <code>null</code>).
495     */
496    public Stroke getLabelOutlineStroke() {
497        return this.labelOutlineStroke;
498    }
499
500    /**
501     * Sets the label outline stroke and sends a property change event (with
502     * the name 'labelOutlineStroke') to all registered listeners.
503     *
504     * @param stroke  the stroke (<code>null</code> not permitted).
505     */
506    public void setLabelOutlineStroke(Stroke stroke) {
507        if (stroke == null) {
508            throw new IllegalArgumentException("Null 'stroke' argument.");
509        }
510        Stroke old = this.labelOutlineStroke;
511        this.labelOutlineStroke = stroke;
512        this.pcs.firePropertyChange("labelOutlineStroke", old, stroke);
513    }
514
515    /**
516     * Tests this crosshair for equality with an arbitrary object.
517     *
518     * @param obj  the object (<code>null</code> permitted).
519     *
520     * @return A boolean.
521     */
522    public boolean equals(Object obj) {
523        if (obj == this) {
524            return true;
525        }
526        if (!(obj instanceof Crosshair)) {
527            return false;
528        }
529        Crosshair that = (Crosshair) obj;
530        if (this.visible != that.visible) {
531            return false;
532        }
533        if (this.value != that.value) {
534            return false;
535        }
536        if (!PaintUtilities.equal(this.paint, that.paint)) {
537            return false;
538        }
539        if (!this.stroke.equals(that.stroke)) {
540            return false;
541        }
542        if (this.labelVisible != that.labelVisible) {
543            return false;
544        }
545        if (!this.labelGenerator.equals(that.labelGenerator)) {
546            return false;
547        }
548        if (!this.labelAnchor.equals(that.labelAnchor)) {
549            return false;
550        }
551        if (this.labelXOffset != that.labelXOffset) {
552            return false;
553        }
554        if (this.labelYOffset != that.labelYOffset) {
555            return false;
556        }
557        if (!this.labelFont.equals(that.labelFont)) {
558            return false;
559        }
560        if (!PaintUtilities.equal(this.labelPaint, that.labelPaint)) {
561            return false;
562        }
563        if (!PaintUtilities.equal(this.labelBackgroundPaint,
564                that.labelBackgroundPaint)) {
565            return false;
566        }
567        if (this.labelOutlineVisible != that.labelOutlineVisible) {
568            return false;
569        }
570        if (!PaintUtilities.equal(this.labelOutlinePaint,
571                that.labelOutlinePaint)) {
572            return false;
573        }
574        if (!this.labelOutlineStroke.equals(that.labelOutlineStroke)) {
575            return false;
576        }
577        return true;  // can't find any difference
578    }
579
580    /**
581     * Returns a hash code for this instance.
582     *
583     * @return A hash code.
584     */
585    public int hashCode() {
586        int hash = 7;
587        hash = HashUtilities.hashCode(hash, this.visible);
588        hash = HashUtilities.hashCode(hash, this.value);
589        hash = HashUtilities.hashCode(hash, this.paint);
590        hash = HashUtilities.hashCode(hash, this.stroke);
591        hash = HashUtilities.hashCode(hash, this.labelVisible);
592        hash = HashUtilities.hashCode(hash, this.labelAnchor);
593        hash = HashUtilities.hashCode(hash, this.labelGenerator);
594        hash = HashUtilities.hashCode(hash, this.labelXOffset);
595        hash = HashUtilities.hashCode(hash, this.labelYOffset);
596        hash = HashUtilities.hashCode(hash, this.labelFont);
597        hash = HashUtilities.hashCode(hash, this.labelPaint);
598        hash = HashUtilities.hashCode(hash, this.labelBackgroundPaint);
599        hash = HashUtilities.hashCode(hash, this.labelOutlineVisible);
600        hash = HashUtilities.hashCode(hash, this.labelOutlineStroke);
601        hash = HashUtilities.hashCode(hash, this.labelOutlinePaint);
602        return hash;
603    }
604
605    /**
606     * Returns an independent copy of this instance.
607     *
608     * @return An independent copy of this instance.
609     *
610     * @throws java.lang.CloneNotSupportedException
611     */
612    public Object clone() throws CloneNotSupportedException {
613        // FIXME: clone generator
614        return super.clone();
615    }
616
617    /**
618     * Adds a property change listener.
619     *
620     * @param l  the listener.
621     */
622    public void addPropertyChangeListener(PropertyChangeListener l) {
623        this.pcs.addPropertyChangeListener(l);
624    }
625
626    /**
627     * Removes a property change listener.
628     *
629     * @param l  the listener.
630     */
631    public void removePropertyChangeListener(PropertyChangeListener l) {
632        this.pcs.removePropertyChangeListener(l);
633    }
634
635    /**
636     * Provides serialization support.
637     *
638     * @param stream  the output stream.
639     *
640     * @throws IOException  if there is an I/O error.
641     */
642    private void writeObject(ObjectOutputStream stream) throws IOException {
643        stream.defaultWriteObject();
644        SerialUtilities.writePaint(this.paint, stream);
645        SerialUtilities.writeStroke(this.stroke, stream);
646        SerialUtilities.writePaint(this.labelPaint, stream);
647        SerialUtilities.writePaint(this.labelBackgroundPaint, stream);
648        SerialUtilities.writeStroke(this.labelOutlineStroke, stream);
649        SerialUtilities.writePaint(this.labelOutlinePaint, stream);
650    }
651
652    /**
653     * Provides serialization support.
654     *
655     * @param stream  the input stream.
656     *
657     * @throws IOException  if there is an I/O error.
658     * @throws ClassNotFoundException  if there is a classpath problem.
659     */
660    private void readObject(ObjectInputStream stream)
661            throws IOException, ClassNotFoundException {
662        stream.defaultReadObject();
663        this.paint = SerialUtilities.readPaint(stream);
664        this.stroke = SerialUtilities.readStroke(stream);
665        this.labelPaint = SerialUtilities.readPaint(stream);
666        this.labelBackgroundPaint = SerialUtilities.readPaint(stream);
667        this.labelOutlineStroke = SerialUtilities.readStroke(stream);
668        this.labelOutlinePaint = SerialUtilities.readPaint(stream);
669        this.pcs = new PropertyChangeSupport(this);
670    }
671
672}