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 }