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     * StandardDialFrame.java
029     * ----------------------
030     * (C) Copyright 2006-2008, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * Changes
036     * -------
037     * 03-Nov-2006 : Version 1 (DG);
038     * 08-Mar-2007 : Fix in hashCode() (DG);
039     * 29-Oct-2007 : Renamed StandardDialFrame (DG);
040     *
041     */
042    
043    package org.jfree.chart.plot.dial;
044    
045    import java.awt.BasicStroke;
046    import java.awt.Color;
047    import java.awt.Graphics2D;
048    import java.awt.Paint;
049    import java.awt.Shape;
050    import java.awt.Stroke;
051    import java.awt.geom.Area;
052    import java.awt.geom.Ellipse2D;
053    import java.awt.geom.Rectangle2D;
054    import java.io.IOException;
055    import java.io.ObjectInputStream;
056    import java.io.ObjectOutputStream;
057    import java.io.Serializable;
058    
059    import org.jfree.chart.HashUtilities;
060    import org.jfree.io.SerialUtilities;
061    import org.jfree.util.PaintUtilities;
062    import org.jfree.util.PublicCloneable;
063    
064    /**
065     * A simple circular frame for the {@link DialPlot} class.
066     *
067     * @since 1.0.7
068     */
069    public class StandardDialFrame extends AbstractDialLayer implements DialFrame,
070            Cloneable, PublicCloneable, Serializable {
071    
072        /** For serialization. */
073        static final long serialVersionUID = 1016585407507121596L;
074    
075        /** The outer radius, relative to the framing rectangle. */
076        private double radius;
077    
078        /**
079         * The color used for the front of the panel.  This field is transient
080         * because it requires special handling for serialization.
081         */
082        private transient Paint backgroundPaint;
083    
084        /**
085         * The color used for the border around the window. This field is transient
086         * because it requires special handling for serialization.
087         */
088        private transient Paint foregroundPaint;
089    
090        /**
091         * The stroke for drawing the frame outline.  This field is transient
092         * because it requires special handling for serialization.
093         */
094        private transient Stroke stroke;
095    
096        /**
097         * Creates a new instance of <code>StandardDialFrame</code>.
098         */
099        public StandardDialFrame() {
100            this.backgroundPaint = Color.gray;
101            this.foregroundPaint = Color.black;
102            this.stroke = new BasicStroke(2.0f);
103            this.radius = 0.95;
104        }
105    
106        /**
107         * Returns the radius, relative to the framing rectangle.
108         *
109         * @return The radius.
110         *
111         * @see #setRadius(double)
112         */
113        public double getRadius() {
114            return this.radius;
115        }
116    
117        /**
118         * Sets the radius and sends a {@link DialLayerChangeEvent} to all
119         * registered listeners.
120         *
121         * @param radius  the radius (must be positive).
122         *
123         * @see #getRadius()
124         */
125        public void setRadius(double radius) {
126            if (radius <= 0) {
127                throw new IllegalArgumentException(
128                        "The 'radius' must be positive.");
129            }
130            this.radius = radius;
131            notifyListeners(new DialLayerChangeEvent(this));
132        }
133    
134        /**
135         * Returns the background paint.
136         *
137         * @return The background paint (never <code>null</code>).
138         *
139         * @see #setBackgroundPaint(Paint)
140         */
141        public Paint getBackgroundPaint() {
142            return this.backgroundPaint;
143        }
144    
145        /**
146         * Sets the background paint and sends a {@link DialLayerChangeEvent} to
147         * all registered listeners.
148         *
149         * @param paint  the paint (<code>null</code> not permitted).
150         *
151         * @see #getBackgroundPaint()
152         */
153        public void setBackgroundPaint(Paint paint) {
154            if (paint == null) {
155                throw new IllegalArgumentException("Null 'paint' argument.");
156            }
157            this.backgroundPaint = paint;
158            notifyListeners(new DialLayerChangeEvent(this));
159        }
160    
161        /**
162         * Returns the foreground paint.
163         *
164         * @return The foreground paint (never <code>null</code>).
165         *
166         * @see #setForegroundPaint(Paint)
167         */
168        public Paint getForegroundPaint() {
169            return this.foregroundPaint;
170        }
171    
172        /**
173         * Sets the foreground paint and sends a {@link DialLayerChangeEvent} to
174         * all registered listeners.
175         *
176         * @param paint  the paint (<code>null</code> not permitted).
177         *
178         * @see #getForegroundPaint()
179         */
180        public void setForegroundPaint(Paint paint) {
181            if (paint == null) {
182                throw new IllegalArgumentException("Null 'paint' argument.");
183            }
184            this.foregroundPaint = paint;
185            notifyListeners(new DialLayerChangeEvent(this));
186        }
187    
188        /**
189         * Returns the stroke for the frame.
190         *
191         * @return The stroke (never <code>null</code>).
192         *
193         * @see #setStroke(Stroke)
194         */
195        public Stroke getStroke() {
196            return this.stroke;
197        }
198    
199        /**
200         * Sets the stroke and sends a {@link DialLayerChangeEvent} to all
201         * registered listeners.
202         *
203         * @param stroke  the stroke (<code>null</code> not permitted).
204         *
205         * @see #getStroke()
206         */
207        public void setStroke(Stroke stroke) {
208            if (stroke == null) {
209                throw new IllegalArgumentException("Null 'stroke' argument.");
210            }
211            this.stroke = stroke;
212            notifyListeners(new DialLayerChangeEvent(this));
213        }
214    
215        /**
216         * Returns the shape for the window for this dial.  Some dial layers will
217         * request that their drawing be clipped within this window.
218         *
219         * @param frame  the reference frame (<code>null</code> not permitted).
220         *
221         * @return The shape of the dial's window.
222         */
223        public Shape getWindow(Rectangle2D frame) {
224            Rectangle2D f = DialPlot.rectangleByRadius(frame, this.radius,
225                    this.radius);
226            return new Ellipse2D.Double(f.getX(), f.getY(), f.getWidth(),
227                    f.getHeight());
228        }
229    
230        /**
231         * Returns <code>false</code> to indicate that this dial layer is not
232         * clipped to the dial window.
233         *
234         * @return A boolean.
235         */
236        public boolean isClippedToWindow() {
237            return false;
238        }
239    
240        /**
241         * Draws the frame.  This method is called by the {@link DialPlot} class,
242         * you shouldn't need to call it directly.
243         *
244         * @param g2  the graphics target (<code>null</code> not permitted).
245         * @param plot  the plot (<code>null</code> not permitted).
246         * @param frame  the frame (<code>null</code> not permitted).
247         * @param view  the view (<code>null</code> not permitted).
248         */
249        public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
250                Rectangle2D view) {
251    
252            Shape window = getWindow(frame);
253    
254            Rectangle2D f = DialPlot.rectangleByRadius(frame, this.radius + 0.02,
255                    this.radius + 0.02);
256            Ellipse2D e = new Ellipse2D.Double(f.getX(), f.getY(), f.getWidth(),
257                    f.getHeight());
258    
259            Area area = new Area(e);
260            Area area2 = new Area(window);
261            area.subtract(area2);
262            g2.setPaint(this.backgroundPaint);
263            g2.fill(area);
264    
265            g2.setStroke(this.stroke);
266            g2.setPaint(this.foregroundPaint);
267            g2.draw(window);
268            g2.draw(e);
269        }
270    
271        /**
272         * Tests this instance for equality with an arbitrary object.
273         *
274         * @param obj  the object (<code>null</code> permitted).
275         *
276         * @return A boolean.
277         */
278        public boolean equals(Object obj) {
279            if (obj == this) {
280                return true;
281            }
282            if (!(obj instanceof StandardDialFrame)) {
283                return false;
284            }
285            StandardDialFrame that = (StandardDialFrame) obj;
286            if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
287                return false;
288            }
289            if (!PaintUtilities.equal(this.foregroundPaint, that.foregroundPaint)) {
290                return false;
291            }
292            if (this.radius != that.radius) {
293                return false;
294            }
295            if (!this.stroke.equals(that.stroke)) {
296                return false;
297            }
298            return super.equals(obj);
299        }
300    
301        /**
302         * Returns a hash code for this instance.
303         *
304         * @return The hash code.
305         */
306        public int hashCode() {
307            int result = 193;
308            long temp = Double.doubleToLongBits(this.radius);
309            result = 37 * result + (int) (temp ^ (temp >>> 32));
310            result = 37 * result + HashUtilities.hashCodeForPaint(
311                    this.backgroundPaint);
312            result = 37 * result + HashUtilities.hashCodeForPaint(
313                    this.foregroundPaint);
314            result = 37 * result + this.stroke.hashCode();
315            return result;
316        }
317    
318        /**
319         * Returns a clone of this instance.
320         *
321         * @return A clone.
322         *
323         * @throws CloneNotSupportedException if any of the frame's attributes
324         *     cannot be cloned.
325         */
326        public Object clone() throws CloneNotSupportedException {
327            return super.clone();
328        }
329    
330        /**
331         * Provides serialization support.
332         *
333         * @param stream  the output stream.
334         *
335         * @throws IOException  if there is an I/O error.
336         */
337        private void writeObject(ObjectOutputStream stream) throws IOException {
338            stream.defaultWriteObject();
339            SerialUtilities.writePaint(this.backgroundPaint, stream);
340            SerialUtilities.writePaint(this.foregroundPaint, stream);
341            SerialUtilities.writeStroke(this.stroke, stream);
342        }
343    
344        /**
345         * Provides serialization support.
346         *
347         * @param stream  the input stream.
348         *
349         * @throws IOException  if there is an I/O error.
350         * @throws ClassNotFoundException  if there is a classpath problem.
351         */
352        private void readObject(ObjectInputStream stream)
353                throws IOException, ClassNotFoundException {
354            stream.defaultReadObject();
355            this.backgroundPaint = SerialUtilities.readPaint(stream);
356            this.foregroundPaint = SerialUtilities.readPaint(stream);
357            this.stroke = SerialUtilities.readStroke(stream);
358        }
359    
360    }