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    }