001 /*
002 * Stop.java
003 *
004 *
005 * The Salamander Project - 2D and 3D graphics libraries in Java
006 * Copyright (C) 2004 Mark McKay
007 *
008 * This library is free software; you can redistribute it and/or
009 * modify it under the terms of the GNU Lesser General Public
010 * License as published by the Free Software Foundation; either
011 * version 2.1 of the License, or (at your option) any later version.
012 *
013 * This library is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016 * Lesser General Public License for more details.
017 *
018 * You should have received a copy of the GNU Lesser General Public
019 * License along with this library; if not, write to the Free Software
020 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021 *
022 * Mark McKay can be contacted at mark@kitfox.com. Salamander and other
023 * projects can be found at http://www.kitfox.com
024 *
025 * Created on January 26, 2004, 1:56 AM
026 */
027
028 package com.kitfox.svg;
029
030 import com.kitfox.svg.xml.StyleAttribute;
031 import java.awt.Graphics2D;
032 import java.awt.Rectangle;
033 import java.awt.Shape;
034 import java.awt.geom.AffineTransform;
035 import java.awt.geom.Area;
036 import java.awt.geom.NoninvertibleTransformException;
037 import java.awt.geom.Point2D;
038 import java.awt.geom.Rectangle2D;
039 import java.util.Iterator;
040 import java.util.List;
041
042
043 /**
044 * @author Mark McKay
045 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
046 */
047 public class Group extends ShapeElement
048 {
049
050 //Cache bounding box for faster clip testing
051 Rectangle2D boundingBox;
052 Shape cachedShape;
053
054 //Cache clip bounds
055 // final Rectangle clipBounds = new Rectangle();
056
057 /** Creates a new instance of Stop */
058 public Group() {
059 }
060
061 /*
062 public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent)
063 {
064 //Load style string
065 super.loaderStartElement(helper, attrs, parent);
066
067 //String transform = attrs.getValue("transform");
068 }
069 */
070
071 /**
072 * Called after the start element but before the end element to indicate
073 * each child tag that has been processed
074 */
075 public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException
076 {
077 super.loaderAddChild(helper, child);
078
079 // members.add(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 (rect == null)
093 //{
094 // rect = getBoundingBox();
095 //}
096
097 // if (rect.intersects(clipBounds))
098 if (clip.intersects(rect))
099 {
100 return false;
101 }
102
103 return true;
104 }
105
106 void pick(Point2D point, boolean boundingBox, List retVec) throws SVGException
107 {
108 Point2D xPoint = new Point2D.Double(point.getX(), point.getY());
109 if (xform != null)
110 {
111 try
112 {
113 xform.inverseTransform(point, xPoint);
114 }
115 catch (NoninvertibleTransformException ex)
116 {
117 throw new SVGException(ex);
118 }
119 }
120
121
122 for (Iterator it = children.iterator(); it.hasNext();)
123 {
124 SVGElement ele = (SVGElement)it.next();
125 if (ele instanceof RenderableElement)
126 {
127 RenderableElement rendEle = (RenderableElement)ele;
128
129 rendEle.pick(xPoint, boundingBox, retVec);
130 }
131 }
132 }
133
134 void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List retVec) throws SVGException
135 {
136 if (xform != null)
137 {
138 ltw = new AffineTransform(ltw);
139 ltw.concatenate(xform);
140 }
141
142
143 for (Iterator it = children.iterator(); it.hasNext();)
144 {
145 SVGElement ele = (SVGElement)it.next();
146 if (ele instanceof RenderableElement)
147 {
148 RenderableElement rendEle = (RenderableElement)ele;
149
150 rendEle.pick(pickArea, ltw, boundingBox, retVec);
151 }
152 }
153 }
154
155 public void render(Graphics2D g) throws SVGException
156 {
157 //Don't process if not visible
158 StyleAttribute styleAttrib = new StyleAttribute();
159 if (getStyle(styleAttrib.setName("visibility")))
160 {
161 if (!styleAttrib.getStringValue().equals("visible")) return;
162 }
163
164 //Do not process offscreen groups
165 boolean ignoreClip = diagram.ignoringClipHeuristic();
166 if (!ignoreClip && outsideClip(g))
167 {
168 return;
169 }
170
171 beginLayer(g);
172
173 Iterator it = children.iterator();
174
175 // try
176 // {
177 // g.getClipBounds(clipBounds);
178 // }
179 // catch (Exception e)
180 // {
181 // //For some reason, getClipBounds can throw a null pointer exception for
182 // // some types of Graphics2D
183 // ignoreClip = true;
184 // }
185
186 Shape clip = g.getClip();
187 while (it.hasNext())
188 {
189 SVGElement ele = (SVGElement)it.next();
190 if (ele instanceof RenderableElement)
191 {
192 RenderableElement rendEle = (RenderableElement)ele;
193
194 // if (shapeEle == null) continue;
195
196 if (!(ele instanceof Group))
197 {
198 //Skip if clipping area is outside our bounds
199 if (!ignoreClip && clip != null
200 && !clip.intersects(rendEle.getBoundingBox()))
201 {
202 continue;
203 }
204 }
205
206 rendEle.render(g);
207 }
208 }
209
210 finishLayer(g);
211 }
212
213
214 /**
215 * Retrieves the cached bounding box of this group
216 */
217 public Shape getShape()
218 {
219 if (cachedShape == null) calcShape();
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) calcBoundingBox();
251 // calcBoundingBox();
252 return boundingBox;
253 }
254
255 /**
256 * Recalculates the bounding box by taking the union of the bounding boxes
257 * of all children. Caches the result.
258 */
259 public void calcBoundingBox() throws SVGException
260 {
261 // Rectangle2D retRect = new Rectangle2D.Float();
262 Rectangle2D retRect = null;
263
264 for (Iterator it = children.iterator(); it.hasNext();)
265 {
266 SVGElement ele = (SVGElement)it.next();
267
268 if (ele instanceof RenderableElement)
269 {
270 RenderableElement rendEle = (RenderableElement)ele;
271 Rectangle2D bounds = rendEle.getBoundingBox();
272 if (bounds != null)
273 {
274 if (retRect == null) retRect = bounds;
275 else retRect = retRect.createUnion(bounds);
276 }
277 }
278 }
279
280 // if (xform != null)
281 // {
282 // retRect = xform.createTransformedShape(retRect).getBounds2D();
283 // }
284
285 //If no contents, use degenerate rectangle
286 if (retRect == null) retRect = new Rectangle2D.Float();
287
288 boundingBox = boundsToParent(retRect);
289 }
290
291 public boolean updateTime(double curTime) throws SVGException
292 {
293 boolean changeState = super.updateTime(curTime);
294 Iterator it = children.iterator();
295
296 //Distribute message to all members of this group
297 while (it.hasNext())
298 {
299 SVGElement ele = (SVGElement)it.next();
300 boolean updateVal = ele.updateTime(curTime);
301
302 changeState = changeState || updateVal;
303
304 //Update our shape if shape aware children change
305 if (ele instanceof ShapeElement) cachedShape = null;
306 if (ele instanceof RenderableElement) boundingBox = null;
307 }
308
309 return changeState;
310 }
311 }