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 //Visibility can be overridden by children 153 154 if (getStyle(styleAttrib.setName("display"))) 155 { 156 if (styleAttrib.getStringValue().equals("none")) 157 { 158 return; 159 } 160 } 161 162 //Do not process offscreen groups 163 boolean ignoreClip = diagram.ignoringClipHeuristic(); 164// if (!ignoreClip && outsideClip(g)) 165// { 166// return; 167// } 168 169 beginLayer(g); 170 171 Iterator it = children.iterator(); 172 173// try 174// { 175// g.getClipBounds(clipBounds); 176// } 177// catch (Exception e) 178// { 179// //For some reason, getClipBounds can throw a null pointer exception for 180// // some types of Graphics2D 181// ignoreClip = true; 182// } 183 184 Shape clip = g.getClip(); 185 while (it.hasNext()) 186 { 187 SVGElement ele = (SVGElement) it.next(); 188 if (ele instanceof RenderableElement) 189 { 190 RenderableElement rendEle = (RenderableElement) ele; 191 192// if (shapeEle == null) continue; 193 194 if (!(ele instanceof Group)) 195 { 196 //Skip if clipping area is outside our bounds 197 if (!ignoreClip && clip != null 198 && !clip.intersects(rendEle.getBoundingBox())) 199 { 200 continue; 201 } 202 } 203 204 rendEle.render(g); 205 } 206 } 207 208 finishLayer(g); 209 } 210 211 /** 212 * Retrieves the cached bounding box of this group 213 */ 214 public Shape getShape() 215 { 216 if (cachedShape == null) 217 { 218 calcShape(); 219 } 220 return cachedShape; 221 } 222 223 public void calcShape() 224 { 225 Area retShape = new Area(); 226 227 for (Iterator it = children.iterator(); it.hasNext();) 228 { 229 SVGElement ele = (SVGElement) it.next(); 230 231 if (ele instanceof ShapeElement) 232 { 233 ShapeElement shpEle = (ShapeElement) ele; 234 Shape shape = shpEle.getShape(); 235 if (shape != null) 236 { 237 retShape.add(new Area(shape)); 238 } 239 } 240 } 241 242 cachedShape = shapeToParent(retShape); 243 } 244 245 /** 246 * Retrieves the cached bounding box of this group 247 */ 248 public Rectangle2D getBoundingBox() throws SVGException 249 { 250 if (boundingBox == null) 251 { 252 calcBoundingBox(); 253 } 254// calcBoundingBox(); 255 return boundingBox; 256 } 257 258 /** 259 * Recalculates the bounding box by taking the union of the bounding boxes 260 * of all children. Caches the result. 261 */ 262 public void calcBoundingBox() throws SVGException 263 { 264// Rectangle2D retRect = new Rectangle2D.Float(); 265 Rectangle2D retRect = null; 266 267 for (Iterator it = children.iterator(); it.hasNext();) 268 { 269 SVGElement ele = (SVGElement) it.next(); 270 271 if (ele instanceof RenderableElement) 272 { 273 RenderableElement rendEle = (RenderableElement) ele; 274 Rectangle2D bounds = rendEle.getBoundingBox(); 275 if (bounds != null && (bounds.getWidth() != 0 || bounds.getHeight() != 0)) 276 { 277 if (retRect == null) 278 { 279 retRect = bounds; 280 } 281 else 282 { 283 if (retRect.getWidth() != 0 || retRect.getHeight() != 0) 284 { 285 retRect = retRect.createUnion(bounds); 286 } 287 } 288 } 289 } 290 } 291 292// if (xform != null) 293// { 294// retRect = xform.createTransformedShape(retRect).getBounds2D(); 295// } 296 297 //If no contents, use degenerate rectangle 298 if (retRect == null) 299 { 300 retRect = new Rectangle2D.Float(); 301 } 302 303 boundingBox = boundsToParent(retRect); 304 } 305 306 public boolean updateTime(double curTime) throws SVGException 307 { 308 boolean changeState = super.updateTime(curTime); 309 Iterator it = children.iterator(); 310 311 //Distribute message to all members of this group 312 while (it.hasNext()) 313 { 314 SVGElement ele = (SVGElement) it.next(); 315 boolean updateVal = ele.updateTime(curTime); 316 317 changeState = changeState || updateVal; 318 319 //Update our shape if shape aware children change 320 if (ele instanceof ShapeElement) 321 { 322 cachedShape = null; 323 } 324 if (ele instanceof RenderableElement) 325 { 326 boundingBox = null; 327 } 328 } 329 330 return changeState; 331 } 332}