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}