001    /*
002     * $Id: ShapePainter.java,v 1.11 2006/05/14 15:55:54 dmouse Exp $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    
022    package org.jdesktop.swingx.painter;
023    
024    import java.awt.Graphics2D;
025    import java.awt.Paint;
026    import java.awt.Shape;
027    import java.awt.Stroke;
028    import java.awt.geom.AffineTransform;
029    import java.awt.geom.Point2D;
030    import java.awt.geom.Rectangle2D;
031    import java.awt.geom.RoundRectangle2D;
032    import javax.swing.JComponent;
033    import org.jdesktop.swingx.util.Resize;
034    
035    /**
036     * <p>A Painter that paints Shapes. It uses a stroke and a fillPaint to do so. The
037     * shape is painted as is, at a specific location. If no Shape is specified, nothing
038     * will be painted. If no stroke is specified, the default for the Graphics2D
039     * will be used. If no fillPaint is specified, the component background color
040     * will be used. And if no location is specified, then the shape will be draw
041     * at the origin (0,0)</p>
042     * 
043     * <p>Here is an example that draws a lowly rectangle:
044     * <pre><code>
045     *  Rectangle2D.Double rect = new Rectangle2D.Double(0, 0, 50, 50);
046     *  ShapePainter p = new ShapePainter(rect);
047     *  p.setLocation(new Point2D.Double(20, 10));
048     * </code></pre>
049     * 
050     * 
051     * @author rbair
052     */
053    public class ShapePainter extends AbstractPainter {
054        /**
055         * Different available fill styles. BOTH indicates that both the outline,
056         * and the fill should be painted. This is the default. FILLED indicates that
057         * the shape should be filled, but no outline painted. OUTLINE specifies that
058         * the shape should be outlined, but not filled
059         */
060        public enum Style {BOTH, FILLED, OUTLINE}
061        
062        /**
063         * The Shape to fillPaint. If null, nothing is painted.
064         */
065        private Shape shape;
066        /**
067         * The Stroke to use when painting. If null, the default Stroke for
068         * the Graphics2D is used
069         */
070        private Stroke stroke;
071        /**
072         * The Paint to use when painting the shape. If null, then the component
073         * background color is used
074         */
075        private Paint fillPaint;
076        /**
077         * The Paint to use when stroking the shape (drawing the outline). If null,
078         * then the component foreground color is used
079         */
080        private Paint strokePaint;
081        /**
082         * The location at which to draw the shape. If null, 0,0 is used
083         */
084        private Point2D location = new Point2D.Double(0, 0);
085        /**
086         * Specifies if/how resizing (relocating) the location should occur.
087         */
088        private Resize resizeLocation = Resize.BOTH;
089        /**
090         * Specifies if/how resizing of the shape should occur
091         */
092        private Resize resize = Resize.BOTH;
093        /**
094         * Indicates whether the shape should be filled or outlined, or both
095         */
096        private Style style = Style.BOTH;
097        
098        /**
099         * Create a new ShapePainter
100         */
101        public ShapePainter() {
102            super();
103        }
104        
105        /**
106         * Create a new ShapePainter with the specified shape.
107         * 
108         * 
109         * @param shape the shape to fillPaint
110         */
111        public ShapePainter(Shape shape) {
112            super();
113            this.shape = shape;
114        }
115        
116        /**
117         * Create a new ShapePainter with the specified shape and fillPaint.
118         * 
119         * 
120         * @param shape the shape to fillPaint
121         * @param paint the fillPaint to be used to fillPaint the shape
122         */
123        public ShapePainter(Shape shape, Paint paint) {
124            super();
125            this.shape = shape;
126            this.fillPaint = paint;
127        }
128        
129        /**
130         * Create a new ShapePainter with the specified shape and fillPaint. The shape
131         * can be filled or stroked (only the ouline is painted).
132         * 
133         * 
134         * @param shape the shape to fillPaint
135         * @param paint the fillPaint to be used to fillPaint the shape
136         * @param style specifies the ShapePainter.Style to use for painting this shape.
137         *        If null, then Style.BOTH is used
138         */
139        public ShapePainter(Shape shape, Paint paint, Style style) {
140            super();
141            this.shape = shape;
142            this.fillPaint = paint;
143            this.style = style == null ? Style.BOTH : style;
144        }
145        
146        /**
147         * Sets the shape to fillPaint. This shape is not resized when the component
148         * bounds are. To do that, create a custom shape that is bound to the
149         * component width/height
150         * 
151         * 
152         * @param s the Shape to fillPaint. May be null
153         */
154        public void setShape(Shape s) {
155            Shape old = getShape();
156            this.shape = s;
157            firePropertyChange("shape", old, getShape());
158        }
159        
160        /**
161         * 
162         * 
163         * @return the Shape to fillPaint. May be null
164         */
165        public Shape getShape() {
166            return shape;
167        }
168        
169        /**
170         * Sets the stroke to use for painting. If null, then the default Graphics2D
171         * stroke use used
172         * 
173         * 
174         * @param s the Stroke to fillPaint with
175         */
176        public void setStroke(Stroke s) {
177            Stroke old = getStroke();
178            this.stroke = s;
179            firePropertyChange("stroke", old, getStroke());
180        }
181        
182        /**
183         * @return the Stroke to use for painting
184         */
185        public Stroke getStroke() {
186            return stroke;
187        }
188        
189        /**
190         * The Paint to use for filling the shape. Can be a Color, GradientPaint,
191         * TexturePaint, or any other kind of Paint. If null, the component
192         * background is used.
193         *
194         * @param p the Paint to use for painting the shape. May be null.
195         */
196        public void setFillPaint(Paint p) {
197            Paint old = getFillPaint();
198            this.fillPaint = p;
199            firePropertyChange("fillPaint", old, getFillPaint());
200        }
201        
202        /**
203         * @return the Paint used when painting the shape. May be null
204         */
205        public Paint getFillPaint() {
206            return fillPaint;
207        }
208        
209        /**
210         * The Paint to use for stroking the shape (painting the outline). 
211         * Can be a Color, GradientPaint, TexturePaint, or any other kind of Paint. 
212         * If null, the component foreground is used.
213         *
214         * @param p the Paint to use for stroking the shape. May be null.
215         */
216        public void setStrokePaint(Paint p) {
217            Paint old = getStrokePaint();
218            this.strokePaint = p;
219            firePropertyChange("strokePaint", old, getStrokePaint());
220        }
221        
222        /**
223         * @return the Paint used when stroking the shape. May be null
224         */
225        public Paint getStrokePaint() {
226            return strokePaint;
227        }
228        
229        /**
230         * Specifies the location at which to place the shape prior to painting.
231         * If null, the origin (0,0) is used
232         * 
233         * 
234         * @param location the Point2D at which to fillPaint the shape. may be null
235         */
236        public void setLocation(Point2D location) {
237            Point2D old = getLocation();
238            this.location = location == null ? new Point2D.Double(0, 0) : location;
239            firePropertyChange("location", old, getLocation());
240        }
241        
242        /**
243         * 
244         * 
245         * @return the Point2D location at which to fillPaint the shape. Will never be null
246         *         (if it was null, new Point2D.Double(0,0) will be returned)
247         */
248        public Point2D getLocation() {
249            return location;
250        }
251        
252        /**
253         * The shape can be filled or simply stroked (outlined), or both. By default,
254         * the shape is both filled and stroked. This property specifies the strategy to
255         * use.
256         *
257         * @param s the Style to use. If null, Style.BOTH is used
258         */
259        public void setStyle(Style s) {
260            Style old = getStyle();
261            this.style = s == null ? Style.BOTH : s;
262            firePropertyChange("style", old, getStyle());
263        }
264        
265        /**
266         * @return the Style used
267         */
268        public Style getStyle() {
269            return style;
270        }
271        
272        /**
273         * Specifies the resize behavior for the location property. If r is
274         * Resize.HORIZONTAL or Resize.BOTH, then the x value of the location
275         * will be treated as if it were a percentage of the width of the component.
276         * Likewise, Resize.VERTICAL or Resize.BOTH will affect the y value. For
277         * example, if I had a location (.3, .8) then the X will be situated at
278         * 30% of the width and the Y will be situated at 80% of the height.
279         *
280         * @param r value indicating whether/how to resize the Location property when
281         *        painting. If null, Resize.BOTH will be used
282         */
283        public void setResizeLocation(Resize r) {
284            Resize old = getResizeLocation();
285            this.resizeLocation = r == null ? Resize.NONE : r;
286            firePropertyChange("resizeLocation", old, getResizeLocation());
287        }
288    
289        /**
290         * @return value indication whether/how to resize the location property.
291         *         This will never be null
292         */
293        public Resize getResizeLocation() {
294            return resizeLocation;
295        }
296        
297        /**
298         * Specifies the resize behavior of the shape. As with all other properties
299         * that rely on Resize, the value of the width/height of the shape will
300         * represent a percentage of the width/height of the component, as a value
301         * between 0 and 1
302         *
303         * @param r value indication whether/how to resize the shape. If null,
304         *        Resize.NONE will be used
305         */
306        public void setResize(Resize r) {
307            Resize old = getResize();
308            this.resize = r == null ? Resize.NONE : r;
309            firePropertyChange("resize", old, getResize());
310        }
311        
312        /**
313         * @return value indication whether/how to resize the shape. Will never be null
314         */
315        public Resize getResize() {
316            return resize;
317        }
318        
319        /**
320         * @inheritDoc
321         */
322        public void paintBackground(Graphics2D g, JComponent component) {
323            //set the stroke if it is not null
324            Stroke s = getStroke();
325            if (s != null) {
326                g.setStroke(s);
327            }
328            
329            //handle the location
330            Point2D location = getLocation();
331            Resize resizeLocation = getResizeLocation();
332            double x = location.getX();
333            double y = location.getY();
334            if (resizeLocation == Resize.HORIZONTAL || resizeLocation == Resize.BOTH) {
335                x = x * component.getWidth();
336            }
337            if (resizeLocation == Resize.VERTICAL || resizeLocation == Resize.BOTH) {
338                y = y * component.getHeight();
339            }
340            g.translate(-location.getX(), -location.getY());
341            
342            //resize the shape if necessary
343            Shape shape = getShape();
344            Rectangle2D bounds = shape.getBounds2D();
345            double width = 1;
346            double height = 1;
347            Resize resize = getResize();
348            if (resize == Resize.HORIZONTAL || resize == Resize.BOTH) {
349                width = component.getWidth() - 1;
350            }
351            if (resize == Resize.VERTICAL || resize == Resize.BOTH) {
352                height = component.getHeight() - 1;
353            }
354            
355            if (shape instanceof RoundRectangle2D) {
356                RoundRectangle2D rect = (RoundRectangle2D)shape;
357                shape = new RoundRectangle2D.Double(
358                        rect.getX(), rect.getY(), width, height,
359                        rect.getArcWidth(), rect.getArcHeight());
360            } else {
361                shape = AffineTransform.getScaleInstance(
362                        width, height).createTransformedShape(shape);
363            }
364            
365            //draw/fill the shape
366            switch (getStyle()) {
367                case BOTH:
368                    g.setPaint(calculateStrokePaint(component));
369                    g.draw(shape);
370                    g.setPaint(calculateFillPaint(component));
371                    g.fill(shape);
372                    break;
373                case FILLED:
374                    g.setPaint(calculateFillPaint(component));
375                    g.fill(shape);
376                    break;
377                case OUTLINE:
378                    g.setPaint(calculateStrokePaint(component));
379                    g.draw(shape);
380                    break;
381            }
382        }
383        
384        private Paint calculateStrokePaint(JComponent component) {
385            Paint p = getStrokePaint();
386            if (p == null) {
387                p = component.getForeground();
388            }
389            return p;
390        }
391        
392        private Paint calculateFillPaint(JComponent component) {
393            //set the fillPaint
394            Paint p = getFillPaint();
395            if (p == null) {
396                p = component.getBackground();
397            }
398            return p;
399        }
400    }