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}