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 }