001/* 002 * SVG Salamander 003 * Copyright (c) 2004, Mark McKay 004 * All rights reserved. 005 * 006 * Redistribution and use in source and binary forms, with or 007 * without modification, are permitted provided that the following 008 * conditions are met: 009 * 010 * - Redistributions of source code must retain the above 011 * copyright notice, this list of conditions and the following 012 * disclaimer. 013 * - Redistributions in binary form must reproduce the above 014 * copyright notice, this list of conditions and the following 015 * disclaimer in the documentation and/or other materials 016 * provided with the distribution. 017 * 018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 019 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 020 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 021 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 022 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 025 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 026 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 027 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 028 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 029 * OF THE POSSIBILITY OF SUCH DAMAGE. 030 * 031 * Mark McKay can be contacted at mark@kitfox.com. Salamander and other 032 * projects can be found at http://www.kitfox.com 033 * 034 * Created on January 26, 2004, 1:56 AM 035 */ 036package com.kitfox.svg; 037 038import com.kitfox.svg.xml.StyleAttribute; 039import java.awt.Graphics2D; 040import java.awt.Shape; 041import java.awt.geom.AffineTransform; 042import java.awt.geom.Area; 043import java.awt.geom.NoninvertibleTransformException; 044import java.awt.geom.Point2D; 045import java.awt.geom.Rectangle2D; 046import java.util.Iterator; 047import java.util.List; 048 049/** 050 * @author Mark McKay 051 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a> 052 */ 053public class Group extends ShapeElement 054{ 055 public static final String TAG_NAME = "group"; 056 057 //Cache bounding box for faster clip testing 058 Rectangle2D boundingBox; 059 Shape cachedShape; 060 061 /** 062 * Creates a new instance of Stop 063 */ 064 public Group() 065 { 066 } 067 068 public String getTagName() 069 { 070 return TAG_NAME; 071 } 072 073 /** 074 * Called after the start element but before the end element to indicate 075 * each child tag that has been processed 076 */ 077 public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException 078 { 079 super.loaderAddChild(helper, child); 080 } 081 082 protected boolean outsideClip(Graphics2D g) throws SVGException 083 { 084 Shape clip = g.getClip(); 085 if (clip == null) 086 { 087 return false; 088 } 089 //g.getClipBounds(clipBounds); 090 Rectangle2D rect = getBoundingBox(); 091 092 if (clip.intersects(rect)) 093 { 094 return false; 095 } 096 097 return true; 098 } 099 100 void pick(Point2D point, boolean boundingBox, List retVec) throws SVGException 101 { 102 Point2D xPoint = new Point2D.Double(point.getX(), point.getY()); 103 if (xform != null) 104 { 105 try 106 { 107 xform.inverseTransform(point, xPoint); 108 } catch (NoninvertibleTransformException ex) 109 { 110 throw new SVGException(ex); 111 } 112 } 113 114 115 for (Iterator it = children.iterator(); it.hasNext();) 116 { 117 SVGElement ele = (SVGElement) it.next(); 118 if (ele instanceof RenderableElement) 119 { 120 RenderableElement rendEle = (RenderableElement) ele; 121 122 rendEle.pick(xPoint, boundingBox, retVec); 123 } 124 } 125 } 126 127 void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List retVec) throws SVGException 128 { 129 if (xform != null) 130 { 131 ltw = new AffineTransform(ltw); 132 ltw.concatenate(xform); 133 } 134 135 136 for (Iterator it = children.iterator(); it.hasNext();) 137 { 138 SVGElement ele = (SVGElement) it.next(); 139 if (ele instanceof RenderableElement) 140 { 141 RenderableElement rendEle = (RenderableElement) ele; 142 143 rendEle.pick(pickArea, ltw, boundingBox, retVec); 144 } 145 } 146 } 147 148 public void render(Graphics2D g) throws SVGException 149 { 150 //Don't process if not visible 151 StyleAttribute styleAttrib = new StyleAttribute(); 152 if (getStyle(styleAttrib.setName("visibility"))) 153 { 154 if (!styleAttrib.getStringValue().equals("visible")) 155 { 156 return; 157 } 158 } 159 160 //Do not process offscreen groups 161 boolean ignoreClip = diagram.ignoringClipHeuristic(); 162 if (!ignoreClip && outsideClip(g)) 163 { 164 return; 165 } 166 167 beginLayer(g); 168 169 Iterator it = children.iterator(); 170 171// try 172// { 173// g.getClipBounds(clipBounds); 174// } 175// catch (Exception e) 176// { 177// //For some reason, getClipBounds can throw a null pointer exception for 178// // some types of Graphics2D 179// ignoreClip = true; 180// } 181 182 Shape clip = g.getClip(); 183 while (it.hasNext()) 184 { 185 SVGElement ele = (SVGElement) it.next(); 186 if (ele instanceof RenderableElement) 187 { 188 RenderableElement rendEle = (RenderableElement) ele; 189 190// if (shapeEle == null) continue; 191 192 if (!(ele instanceof Group)) 193 { 194 //Skip if clipping area is outside our bounds 195 if (!ignoreClip && clip != null 196 && !clip.intersects(rendEle.getBoundingBox())) 197 { 198 continue; 199 } 200 } 201 202 rendEle.render(g); 203 } 204 } 205 206 finishLayer(g); 207 } 208 209 /** 210 * Retrieves the cached bounding box of this group 211 */ 212 public Shape getShape() 213 { 214 if (cachedShape == null) 215 { 216 calcShape(); 217 } 218 return cachedShape; 219 } 220 221 public void calcShape() 222 { 223 Area retShape = new Area(); 224 225 for (Iterator it = children.iterator(); it.hasNext();) 226 { 227 SVGElement ele = (SVGElement) it.next(); 228 229 if (ele instanceof ShapeElement) 230 { 231 ShapeElement shpEle = (ShapeElement) ele; 232 Shape shape = shpEle.getShape(); 233 if (shape != null) 234 { 235 retShape.add(new Area(shape)); 236 } 237 } 238 } 239 240 cachedShape = shapeToParent(retShape); 241 } 242 243 /** 244 * Retrieves the cached bounding box of this group 245 */ 246 public Rectangle2D getBoundingBox() throws SVGException 247 { 248 if (boundingBox == null) 249 { 250 calcBoundingBox(); 251 } 252// calcBoundingBox(); 253 return boundingBox; 254 } 255 256 /** 257 * Recalculates the bounding box by taking the union of the bounding boxes 258 * of all children. Caches the result. 259 */ 260 public void calcBoundingBox() throws SVGException 261 { 262// Rectangle2D retRect = new Rectangle2D.Float(); 263 Rectangle2D retRect = null; 264 265 for (Iterator it = children.iterator(); it.hasNext();) 266 { 267 SVGElement ele = (SVGElement) it.next(); 268 269 if (ele instanceof RenderableElement) 270 { 271 RenderableElement rendEle = (RenderableElement) ele; 272 Rectangle2D bounds = rendEle.getBoundingBox(); 273 if (bounds != null) 274 { 275 if (retRect == null) 276 { 277 retRect = bounds; 278 } else 279 { 280 retRect = retRect.createUnion(bounds); 281 } 282 } 283 } 284 } 285 286// if (xform != null) 287// { 288// retRect = xform.createTransformedShape(retRect).getBounds2D(); 289// } 290 291 //If no contents, use degenerate rectangle 292 if (retRect == null) 293 { 294 retRect = new Rectangle2D.Float(); 295 } 296 297 boundingBox = boundsToParent(retRect); 298 } 299 300 public boolean updateTime(double curTime) throws SVGException 301 { 302 boolean changeState = super.updateTime(curTime); 303 Iterator it = children.iterator(); 304 305 //Distribute message to all members of this group 306 while (it.hasNext()) 307 { 308 SVGElement ele = (SVGElement) it.next(); 309 boolean updateVal = ele.updateTime(curTime); 310 311 changeState = changeState || updateVal; 312 313 //Update our shape if shape aware children change 314 if (ele instanceof ShapeElement) 315 { 316 cachedShape = null; 317 } 318 if (ele instanceof RenderableElement) 319 { 320 boundingBox = null; 321 } 322 } 323 324 return changeState; 325 } 326}