| /* |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| package org.apache.batik.gvt; |
| |
| import java.awt.Graphics2D; |
| import java.awt.Rectangle; |
| import java.awt.Shape; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.GeneralPath; |
| import java.awt.geom.Point2D; |
| import java.awt.geom.Rectangle2D; |
| import java.util.Collection; |
| import java.util.ConcurrentModificationException; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.NoSuchElementException; |
| |
| import org.apache.batik.util.HaltingThread; |
| |
| /** |
| * A CompositeGraphicsNode is a graphics node that can contain graphics nodes. |
| * |
| * <br>Note: this class is a 'little bit aware of' other threads, but not really threadsafe. |
| * |
| * @author <a href="mailto:Thierry.Kormann@sophia.inria.fr">Thierry Kormann</a> |
| * @version $Id$ |
| */ |
| public class CompositeGraphicsNode extends AbstractGraphicsNode |
| implements List { |
| |
| public static final Rectangle2D VIEWPORT = new Rectangle(); |
| public static final Rectangle2D NULL_RECT = new Rectangle(); |
| |
| /** |
| * The children of this composite graphics node. |
| */ |
| protected GraphicsNode [] children; |
| |
| /** |
| * The number of children of this composite graphics node. |
| */ |
| protected volatile int count; |
| |
| /** |
| * The number of times the children list has been structurally modified. |
| */ |
| protected volatile int modCount; |
| |
| /** |
| * This flag indicates if this node has BackgroundEnable = 'new'. |
| * If so traversal of the gvt tree can halt here. |
| */ |
| protected Rectangle2D backgroundEnableRgn = null; |
| |
| /** |
| * Internal Cache: Geometry bounds for this node, not taking into |
| * account any of its children rendering attributes into account |
| */ |
| private volatile Rectangle2D geometryBounds; |
| |
| /** |
| * Internal Cache: Primitive bounds. |
| */ |
| private volatile Rectangle2D primitiveBounds; |
| |
| /** |
| * Internal Cache: Sensitive bounds. |
| */ |
| private volatile Rectangle2D sensitiveBounds; |
| |
| /** |
| * Internal Cache: the outline. |
| */ |
| private Shape outline; |
| |
| /** |
| * Constructs a new empty <code>CompositeGraphicsNode</code>. |
| */ |
| public CompositeGraphicsNode() {} |
| |
| // |
| // Structural methods |
| // |
| |
| /** |
| * Returns the list of children. |
| */ |
| public List getChildren() { |
| return this; |
| } |
| |
| /** |
| * Sets the enable background property to the specified rectangle. |
| * |
| * @param bgRgn the region that defines the background enable property |
| */ |
| public void setBackgroundEnable(Rectangle2D bgRgn) { |
| backgroundEnableRgn = bgRgn; |
| } |
| |
| /** |
| * Returns the region defining the background enable property. |
| */ |
| public Rectangle2D getBackgroundEnable() { |
| return backgroundEnableRgn; |
| } |
| |
| /** |
| * Sets if this node is visible or not depending on the specified value. |
| * Don't fire a graphicsNodeChange event because this doesn't really |
| * effect us (it effects our children through CSS inheritence). |
| * |
| * @param isVisible If true this node is visible |
| */ |
| public void setVisible(boolean isVisible) { |
| // fireGraphicsNodeChangeStarted(); |
| this.isVisible = isVisible; |
| // fireGraphicsNodeChangeCompleted(); |
| } |
| |
| |
| // |
| // Drawing methods |
| // |
| |
| /** |
| * Paints this node without applying Filter, Mask, Composite, and clip. |
| * |
| * @param g2d the Graphics2D to use |
| */ |
| public void primitivePaint(Graphics2D g2d) { |
| if (count == 0) { |
| return; |
| } |
| |
| // Thread.currentThread() is potentially expensive, so reuse my instance in hasBeenHalted() |
| Thread currentThread = Thread.currentThread(); |
| |
| // Paint children |
| for (int i=0; i < count; ++i) { |
| if (HaltingThread.hasBeenHalted( currentThread )) |
| return; |
| |
| GraphicsNode node = children[i]; |
| if (node == null) { |
| continue; |
| } |
| node.paint(g2d); |
| |
| } |
| } |
| |
| // |
| // Event support methods |
| // |
| |
| |
| // |
| // Geometric methods |
| // |
| |
| /** |
| * Invalidates the cached geometric bounds. This method is called |
| * each time an attribute that affects the bounds of this node |
| * changed. |
| */ |
| protected void invalidateGeometryCache() { |
| super.invalidateGeometryCache(); |
| geometryBounds = null; |
| primitiveBounds = null; |
| sensitiveBounds = null; |
| outline = null; |
| } |
| |
| /** |
| * Returns the bounds of the area covered by this node's primitive paint. |
| */ |
| public Rectangle2D getPrimitiveBounds() { |
| if (primitiveBounds != null) { |
| if (primitiveBounds == NULL_RECT) return null; |
| return primitiveBounds; |
| } |
| |
| // Thread.currentThread() is potentially expensive, so reuse my instance in hasBeenHalted() |
| Thread currentThread = Thread.currentThread(); |
| |
| int i=0; |
| Rectangle2D bounds = null; |
| while ((bounds == null) && i < count) { |
| bounds = children[i++].getTransformedBounds(IDENTITY); |
| if (((i & 0x0F) == 0) && HaltingThread.hasBeenHalted( currentThread )) |
| break; // check every 16 children if we have been interrupted. |
| } |
| if (HaltingThread.hasBeenHalted( currentThread )) { |
| invalidateGeometryCache(); |
| return null; |
| } |
| |
| if (bounds == null) { |
| primitiveBounds = NULL_RECT; |
| return null; |
| } |
| |
| primitiveBounds = bounds; |
| |
| while (i < count) { |
| Rectangle2D ctb = children[i++].getTransformedBounds(IDENTITY); |
| if (ctb != null) { |
| if (primitiveBounds == null) { |
| // another thread has set the primitive bounds to null, |
| // need to recall this function |
| return null; |
| } else { |
| primitiveBounds.add(ctb); |
| } |
| } |
| |
| if (((i & 0x0F) == 0) && HaltingThread.hasBeenHalted( currentThread )) |
| break; // check every 16 children if we have been interrupted. |
| } |
| |
| // Check If we should halt early. |
| if (HaltingThread.hasBeenHalted( currentThread )) { |
| // The Thread has been halted. |
| // Invalidate any cached values and proceed. |
| invalidateGeometryCache(); |
| } |
| return primitiveBounds; |
| } |
| |
| /** |
| * Transforms a Rectangle 2D by an affine transform. It assumes the transform |
| * is only scale/translate so there is no loss of precision over transforming |
| * the source geometry. |
| */ |
| public static Rectangle2D getTransformedBBox(Rectangle2D r2d, AffineTransform t) { |
| if ((t == null) || (r2d == null)) return r2d; |
| |
| double x = r2d.getX(); |
| double w = r2d.getWidth(); |
| double y = r2d.getY(); |
| double h = r2d.getHeight(); |
| |
| double sx = t.getScaleX(); |
| double sy = t.getScaleY(); |
| if (sx < 0) { |
| x = -(x + w); |
| sx = -sx; |
| } |
| if (sy < 0) { |
| y = -(y + h); |
| sy = -sy; |
| } |
| |
| return new Rectangle2D.Float |
| ((float)(x*sx+t.getTranslateX()), |
| (float)(y*sy+t.getTranslateY()), |
| (float)(w*sx), (float)(h*sy)); |
| } |
| |
| /** |
| * Returns the bounds of this node's primitivePaint after applying |
| * the input transform (if any), concatenated with this node's |
| * transform (if any). |
| * |
| * @param txf the affine transform with which this node's transform should |
| * be concatenated. Should not be null. |
| */ |
| public Rectangle2D getTransformedPrimitiveBounds(AffineTransform txf) { |
| AffineTransform t = txf; |
| if (transform != null) { |
| t = new AffineTransform(txf); |
| t.concatenate(transform); |
| } |
| |
| if ((t == null) || ((t.getShearX() == 0) && (t.getShearY() == 0))) { |
| // No rotation it's safe to simply transform our bounding box. |
| return getTransformedBBox(getPrimitiveBounds(), t); |
| } |
| |
| int i = 0; |
| Rectangle2D tpb = null; |
| while (tpb == null && i < count) { |
| tpb = children[i++].getTransformedBounds(t); |
| } |
| |
| while (i < count) { |
| Rectangle2D ctb = children[i++].getTransformedBounds(t); |
| if(ctb != null){ |
| tpb.add(ctb); |
| } |
| } |
| |
| return tpb; |
| } |
| |
| /** |
| * Returns the bounds of the area covered by this node, without |
| * taking any of its rendering attributes into account. That is, |
| * exclusive of any clipping, masking, filtering or stroking, for |
| * example. |
| */ |
| public Rectangle2D getGeometryBounds() { |
| if (geometryBounds == null) { |
| // System.err.println("geometryBounds are null"); |
| int i=0; |
| while(geometryBounds == null && i < count){ |
| geometryBounds = |
| children[i++].getTransformedGeometryBounds (IDENTITY); |
| } |
| |
| while (i<count) { |
| Rectangle2D cgb = children[i++].getTransformedGeometryBounds(IDENTITY); |
| if (cgb != null) { |
| if (geometryBounds == null) { |
| // another thread has set the geometry bounds to null, |
| // need to recall this function |
| return getGeometryBounds(); |
| } else { |
| geometryBounds.add(cgb); |
| } |
| } |
| } |
| } |
| |
| return geometryBounds; |
| } |
| |
| /** |
| * Returns the bounds of the area covered by this node, without taking any |
| * of its rendering attribute into accoun. That is, exclusive of any clipping, |
| * masking, filtering or stroking, for example. The returned value is |
| * transformed by the concatenation of the input transform and this node's |
| * transform. |
| * |
| * @param txf the affine transform with which this node's transform should |
| * be concatenated. Should not be null. |
| */ |
| public Rectangle2D getTransformedGeometryBounds(AffineTransform txf) { |
| AffineTransform t = txf; |
| if (transform != null) { |
| t = new AffineTransform(txf); |
| t.concatenate(transform); |
| } |
| |
| if ((t == null) || ((t.getShearX() == 0) && (t.getShearY() == 0))) { |
| // No rotation it's safe to simply transform our bounding box. |
| return getTransformedBBox(getGeometryBounds(), t); |
| } |
| |
| Rectangle2D gb = null; |
| int i=0; |
| while (gb == null && i < count) { |
| gb = children[i++].getTransformedGeometryBounds(t); |
| } |
| |
| Rectangle2D cgb = null; |
| while (i < count) { |
| cgb = children[i++].getTransformedGeometryBounds(t); |
| if (cgb != null) { |
| gb.add(cgb); |
| } |
| } |
| |
| return gb; |
| } |
| |
| /** |
| * Returns the bounds of the sensitive area covered by this node, |
| * This includes the stroked area but does not include the effects |
| * of clipping, masking or filtering. |
| */ |
| public Rectangle2D getSensitiveBounds() { |
| if (sensitiveBounds != null) |
| return sensitiveBounds; |
| |
| // System.out.println("sensitiveBoundsBounds are null"); |
| int i=0; |
| while(sensitiveBounds == null && i < count){ |
| sensitiveBounds = |
| children[i++].getTransformedSensitiveBounds(IDENTITY); |
| } |
| |
| while (i<count) { |
| Rectangle2D cgb = children[i++].getTransformedSensitiveBounds(IDENTITY); |
| if (cgb != null) { |
| if (sensitiveBounds == null) |
| // another thread has set the geometry bounds to null, |
| // need to recall this function |
| return getSensitiveBounds(); |
| |
| sensitiveBounds.add(cgb); |
| } |
| } |
| |
| return sensitiveBounds; |
| } |
| |
| /** |
| * Returns the bounds of the sensitive area covered by this node, |
| * This includes the stroked area but does not include the effects |
| * of clipping, masking or filtering. The returned value is |
| * transformed by the concatenation of the input transform and |
| * this node's transform. |
| * |
| * @param txf the affine transform with which this node's |
| * transform should be concatenated. Should not be null. |
| */ |
| public Rectangle2D getTransformedSensitiveBounds(AffineTransform txf) { |
| AffineTransform t = txf; |
| if (transform != null) { |
| t = new AffineTransform(txf); |
| t.concatenate(transform); |
| } |
| |
| if ((t == null) || ((t.getShearX() == 0) && (t.getShearY() == 0))) { |
| // No rotation it's safe to simply transform our bounding box. |
| return getTransformedBBox(getSensitiveBounds(), t); |
| } |
| |
| Rectangle2D sb = null; |
| int i=0; |
| while (sb == null && i < count) { |
| sb = children[i++].getTransformedSensitiveBounds(t); |
| } |
| |
| while (i < count) { |
| Rectangle2D csb = children[i++].getTransformedSensitiveBounds(t); |
| if (csb != null) { |
| sb.add(csb); |
| } |
| } |
| |
| return sb; |
| } |
| |
| /** |
| * Returns true if the specified Point2D is inside the boundary of this |
| * node, false otherwise. |
| * |
| * @param p the specified Point2D in the user space |
| */ |
| public boolean contains(Point2D p) { |
| Rectangle2D bounds = getSensitiveBounds(); |
| if (count > 0 && bounds != null && bounds.contains(p)) { |
| Point2D pt = null; |
| Point2D cp = null; // Propagated to children |
| for (int i=0; i < count; ++i) { |
| AffineTransform t = children[i].getInverseTransform(); |
| if(t != null){ |
| pt = t.transform(p, pt); |
| cp = pt; |
| } else { |
| cp = p; |
| } |
| if (children[i].contains(cp)) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| |
| /** |
| * Returns the GraphicsNode containing point p if this node or one of its |
| * children is sensitive to mouse events at p. |
| * |
| * @param p the specified Point2D in the user space |
| */ |
| public GraphicsNode nodeHitAt(Point2D p) { |
| Rectangle2D bounds = getSensitiveBounds(); |
| if (count > 0 && bounds != null && bounds.contains(p)) { |
| // Go backward because the children are in rendering order |
| Point2D pt = null; |
| Point2D cp = null; // Propagated to children |
| for (int i=count-1; i >= 0; --i) { |
| AffineTransform t = children[i].getInverseTransform(); |
| if(t != null){ |
| pt = t.transform(p, pt); |
| cp = pt; |
| } else { |
| cp = p; |
| } |
| GraphicsNode node = children[i].nodeHitAt(cp); |
| if (node != null) { |
| return node; |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Returns the outline of this node. |
| */ |
| public Shape getOutline() { |
| if (outline != null) |
| return outline; |
| |
| outline = new GeneralPath(); |
| for (int i = 0; i < count; i++) { |
| Shape childOutline = children[i].getOutline(); |
| if (childOutline != null) { |
| AffineTransform tr = children[i].getTransform(); |
| if (tr != null) { |
| ((GeneralPath)outline).append(tr.createTransformedShape(childOutline), false); |
| } else { |
| ((GeneralPath)outline).append(childOutline, false); |
| } |
| } |
| } |
| |
| return outline; |
| } |
| |
| // |
| // Structural info |
| // |
| |
| /** |
| * Sets the root node of this grahics node and modify all its children. |
| */ |
| protected void setRoot(RootGraphicsNode newRoot) { |
| super.setRoot(newRoot); |
| for (int i=0; i < count; ++i) { |
| GraphicsNode node = children[i]; |
| ((AbstractGraphicsNode)node).setRoot(newRoot); |
| } |
| } |
| |
| // |
| // List implementation |
| // |
| |
| /** |
| * Returns the number of children of this composite graphics node. |
| */ |
| public int size() { |
| return count; |
| } |
| |
| /** |
| * Returns true if this composite graphics node does not contain |
| * graphics node, false otherwise. |
| */ |
| public boolean isEmpty() { |
| return (count == 0); |
| } |
| |
| /** |
| * Returns true if this composite graphics node contains the |
| * specified graphics node, false otherwise. |
| * @param node the node to check |
| */ |
| public boolean contains(Object node) { |
| return (indexOf(node) >= 0); |
| } |
| |
| /** |
| * Returns an iterator over the children of this graphics node. |
| */ |
| public Iterator iterator() { |
| return new Itr(); |
| } |
| |
| /** |
| * Returns an array containing all of the graphics node in the children list |
| * of this composite graphics node in the correct order. |
| */ |
| public Object [] toArray() { |
| GraphicsNode [] result = new GraphicsNode[count]; |
| |
| System.arraycopy( children, 0, result, 0, count ); |
| |
| return result; |
| } |
| |
| /** |
| * Returns an array containing all of the graphics node in the |
| * children list of this composite graphics node in the correct |
| * order. If the children list fits in the specified array, it is |
| * returned therein. Otherwise, a new array is allocated. |
| * |
| * @param a the array to fit if possible |
| */ |
| public Object[] toArray(Object [] a) { |
| if (a.length < count) { |
| a = new GraphicsNode[count]; |
| } |
| System.arraycopy(children, 0, a, 0, count); |
| if (a.length > count) { |
| a[count] = null; |
| } |
| return a; |
| } |
| |
| /** |
| * Returns the graphics node at the specified position in the children list. |
| * |
| * @param index the index of the graphics node to return |
| * @exception IndexOutOfBoundsException if the index is out of range |
| */ |
| public Object get(int index) { |
| checkRange(index); |
| return children[index]; |
| } |
| |
| // Modification Operations |
| |
| /** |
| * Replaces the graphics node at the specified position in the children list |
| * with the specified graphics node. |
| * |
| * @param index the index of the graphics node to replace |
| * @param o the graphics node to be stored at the specified position |
| * @return the graphics node previously at the specified position |
| * @exception IndexOutOfBoundsException if the index is out of range |
| * @exception IllegalArgumentException if the node is not an |
| * instance of GraphicsNode |
| */ |
| public Object set(int index, Object o) { |
| // Check for correct arguments |
| if (!(o instanceof GraphicsNode)) { |
| throw new IllegalArgumentException(o+" is not a GraphicsNode"); |
| } |
| checkRange(index); |
| GraphicsNode node = (GraphicsNode) o; |
| { |
| fireGraphicsNodeChangeStarted(node); |
| } |
| // Reparent the graphics node and tidy up the tree's state |
| if (node.getParent() != null) { |
| node.getParent().getChildren().remove(node); |
| } |
| // Replace the node to the children list |
| GraphicsNode oldNode = children[index]; |
| children[index] = node; |
| // Set the parents of the graphics nodes |
| ((AbstractGraphicsNode) node).setParent(this); |
| ((AbstractGraphicsNode) oldNode).setParent(null); |
| // Set the root of the graphics node |
| ((AbstractGraphicsNode) node).setRoot(this.getRoot()); |
| ((AbstractGraphicsNode) oldNode).setRoot(null); |
| // Invalidates cached values |
| invalidateGeometryCache(); |
| // Create and dispatch events |
| // int id = CompositeGraphicsNodeEvent.GRAPHICS_NODE_REMOVED; |
| // dispatchEvent(new CompositeGraphicsNodeEvent(this, id, oldNode)); |
| // id = CompositeGraphicsNodeEvent.GRAPHICS_NODE_ADDED; |
| // dispatchEvent(new CompositeGraphicsNodeEvent(this, id, node)); |
| fireGraphicsNodeChangeCompleted(); |
| return oldNode; |
| } |
| |
| /** |
| * Adds the specified graphics node to this composite graphics node. |
| * |
| * @param o the graphics node to add |
| * @return true (as per the general contract of Collection.add) |
| * @exception IllegalArgumentException if the node is not an |
| * instance of GraphicsNode |
| */ |
| public boolean add(Object o) { |
| // Check for correct argument |
| if (!(o instanceof GraphicsNode)) { |
| throw new IllegalArgumentException(o+" is not a GraphicsNode"); |
| } |
| GraphicsNode node = (GraphicsNode) o; |
| { |
| fireGraphicsNodeChangeStarted(node); |
| } |
| // Reparent the graphics node and tidy up the tree's state |
| if (node.getParent() != null) { |
| node.getParent().getChildren().remove(node); |
| } |
| // Add the graphics node to the children list |
| ensureCapacity(count + 1); // Increments modCount!! |
| children[count++] = node; |
| // Set the parent of the graphics node |
| ((AbstractGraphicsNode) node).setParent(this); |
| // Set the root of the graphics node |
| ((AbstractGraphicsNode) node).setRoot(this.getRoot()); |
| // Invalidates cached values |
| invalidateGeometryCache(); |
| // Create and dispatch event |
| // int id = CompositeGraphicsNodeEvent.GRAPHICS_NODE_ADDED; |
| // dispatchEvent(new CompositeGraphicsNodeEvent(this, id, node)); |
| fireGraphicsNodeChangeCompleted(); |
| return true; |
| } |
| |
| /** |
| * Inserts the specified graphics node at the specified position in this |
| * children list. Shifts the graphics node currently at that position (if |
| * any) and any subsequent graphics nodes to the right (adds one to their |
| * indices). |
| * |
| * @param index the position at which the specified graphics node is to |
| * be inserted. |
| * @param o the graphics node to be inserted. |
| * @exception IndexOutOfBoundsException if the index is out of range |
| * @exception IllegalArgumentException if the node is not an |
| * instance of GraphicsNode |
| */ |
| public void add(int index, Object o) { |
| // Check for correct arguments |
| if (!(o instanceof GraphicsNode)) { |
| throw new IllegalArgumentException(o+" is not a GraphicsNode"); |
| } |
| if (index > count || index < 0) { |
| throw new IndexOutOfBoundsException( |
| "Index: "+index+", Size: "+count); |
| } |
| GraphicsNode node = (GraphicsNode) o; |
| { |
| fireGraphicsNodeChangeStarted(node); |
| } |
| // Reparent the graphics node and tidy up the tree's state |
| if (node.getParent() != null) { |
| node.getParent().getChildren().remove(node); |
| } |
| // Insert the node to the children list |
| ensureCapacity(count+1); // Increments modCount!! |
| System.arraycopy(children, index, children, index+1, count-index); |
| children[index] = node; |
| count++; |
| // Set parent of the graphics node |
| ((AbstractGraphicsNode) node).setParent(this); |
| // Set root of the graphics node |
| ((AbstractGraphicsNode) node).setRoot(this.getRoot()); |
| // Invalidates cached values |
| invalidateGeometryCache(); |
| // Create and dispatch event |
| // int id = CompositeGraphicsNodeEvent.GRAPHICS_NODE_ADDED; |
| // dispatchEvent(new CompositeGraphicsNodeEvent(this, id, node)); |
| fireGraphicsNodeChangeCompleted(); |
| } |
| |
| /** |
| * <b>Not supported</b> - |
| * Throws <code>UnsupportedOperationException</code> exception. |
| */ |
| public boolean addAll(Collection c) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * <b>Not supported</b> - |
| * Throws <code>UnsupportedOperationException</code> exception. |
| */ |
| public boolean addAll(int index, Collection c) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * Removes the first instance of the specified graphics node from the children list. |
| * |
| * @param o the node the remove |
| * @return true if the children list contains the specified graphics node |
| * @exception IllegalArgumentException if the node is not an |
| * instance of GraphicsNode |
| * @exception IndexOutOfBoundsException when o is not in children list |
| */ |
| public boolean remove(Object o) { |
| // Check for correct argument |
| if (!(o instanceof GraphicsNode)) { |
| throw new IllegalArgumentException(o+" is not a GraphicsNode"); |
| } |
| GraphicsNode node = (GraphicsNode) o; |
| if (node.getParent() != this) { |
| return false; |
| } |
| // Remove the node |
| int index = 0; |
| for (; node != children[index]; index++) { |
| // fires exception when node not found! |
| } |
| remove(index); |
| return true; |
| } |
| |
| /** |
| * Removes the graphics node at the specified position in the children list. |
| * Shifts any subsequent graphics nodes to the left (subtracts one from |
| * their indices). |
| * |
| * @param index the position of the graphics node to remove |
| * @return the graphics node that was removed |
| * @exception IndexOutOfBoundsException if index out of range |
| */ |
| public Object remove(int index) { |
| // Check for correct argument |
| checkRange(index); |
| GraphicsNode oldNode = children[index]; |
| { |
| fireGraphicsNodeChangeStarted(oldNode); |
| } |
| // Remove the node at the specified index |
| modCount++; |
| int numMoved = count - index - 1; |
| if (numMoved > 0) { |
| System.arraycopy(children, index+1, children, index, numMoved); |
| } |
| children[--count] = null; // Let gc do its work |
| if (count == 0) { |
| children = null; |
| } |
| // Set parent of the node |
| ((AbstractGraphicsNode) oldNode).setParent(null); |
| // Set root of the node |
| ((AbstractGraphicsNode) oldNode).setRoot(null); |
| // Invalidates cached values |
| invalidateGeometryCache(); |
| // Create and dispatch event |
| // int id = CompositeGraphicsNodeEvent.GRAPHICS_NODE_REMOVED; |
| // dispatchEvent(new CompositeGraphicsNodeEvent(this, id, oldNode)); |
| fireGraphicsNodeChangeCompleted(); |
| return oldNode; |
| } |
| |
| /** |
| * <b>Not supported</b> - |
| * Throws <code>UnsupportedOperationException</code> exception. |
| */ |
| public boolean removeAll(Collection c) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * <b>Not supported</b> - |
| * Throws <code>UnsupportedOperationException</code> exception. |
| */ |
| public boolean retainAll(Collection c) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * <b>Not supported</b> - |
| * Throws <code>UnsupportedOperationException</code> exception. |
| */ |
| public void clear() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * Returns true if this composite graphics node contains all the graphics |
| * node in the specified collection, false otherwise. |
| * |
| * @param c the collection to be checked for containment |
| */ |
| public boolean containsAll(Collection c) { |
| for (Object aC : c) { |
| if (!contains(aC)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Search Operations |
| |
| /** |
| * Returns the index in the children list of the specified graphics node or |
| * -1 if the children list does not contain this graphics node. |
| * |
| * @param node the graphics node to search for |
| */ |
| public int indexOf(Object node) { |
| if (node == null || !(node instanceof GraphicsNode)) { |
| return -1; |
| } |
| if (((GraphicsNode) node).getParent() == this) { |
| int iCount = count; // local is cheaper |
| GraphicsNode[] workList = children; // local is cheaper |
| for (int i = 0; i < iCount; i++) { |
| if (node == workList[ i ]) { |
| return i; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Returns the index in this children list of the last occurence of the |
| * specified graphics node, or -1 if the list does not contain this graphics |
| * node. |
| * |
| * @param node the graphics node to search for |
| */ |
| public int lastIndexOf(Object node) { |
| if (node == null || !(node instanceof GraphicsNode)) { |
| return -1; |
| } |
| if (((GraphicsNode) node).getParent() == this) { |
| for (int i = count-1; i >= 0; i--) { |
| if (node == children[i]) { |
| return i; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| // List Iterators |
| |
| /** |
| * Returns an iterator over the children of this graphics node. |
| */ |
| public ListIterator listIterator() { |
| return listIterator(0); |
| } |
| |
| /** |
| * Returns an iterator over the children of this graphics node, starting at |
| * the specified position in the children list. |
| * |
| * @param index the index of the first graphics node to return |
| * from the children list |
| */ |
| public ListIterator listIterator(int index) { |
| if (index < 0 || index > count) { |
| throw new IndexOutOfBoundsException("Index: "+index); |
| } |
| return new ListItr(index); |
| } |
| |
| // View |
| |
| /** |
| * <b>Not supported</b> - |
| * Throws <code>UnsupportedOperationException</code> exception. |
| */ |
| public List subList(int fromIndex, int toIndex) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * Checks if the given index is in range. If not, throws an appropriate |
| * runtime exception. |
| * |
| * @param index the index to check |
| */ |
| private void checkRange(int index) { |
| if (index >= count || index < 0) { |
| throw new IndexOutOfBoundsException( |
| "Index: "+index+", Size: "+count); |
| } |
| } |
| |
| /** |
| * Increases the capacity of the children list, if necessary, to ensure that |
| * it can hold at least the number of graphics nodes specified by the |
| * minimum capacity argument. |
| * |
| * @param minCapacity the desired minimum capacity. |
| */ |
| public void ensureCapacity(int minCapacity) { |
| if (children == null) { |
| children = new GraphicsNode[4]; |
| } |
| modCount++; |
| int oldCapacity = children.length; |
| if (minCapacity > oldCapacity) { |
| GraphicsNode [] oldData = children; |
| int newCapacity = oldCapacity + oldCapacity/2 + 1; |
| if (newCapacity < minCapacity) { |
| newCapacity = minCapacity; |
| } |
| children = new GraphicsNode[newCapacity]; |
| System.arraycopy(oldData, 0, children, 0, count); |
| } |
| } |
| |
| /** |
| * An implementation of the java.util.Iterator interface. |
| */ |
| private class Itr implements Iterator { |
| |
| /** |
| * Index of graphics node to be returned by subsequent call to next. |
| */ |
| int cursor = 0; |
| |
| /** |
| * Index of graphics node returned by most recent call to next or |
| * previous. Reset to -1 if this graphics node is deleted by a call |
| * to remove. |
| */ |
| int lastRet = -1; |
| |
| /** |
| * The modCount value that the iterator believes that the backing |
| * List should have. If this expectation is violated, the iterator |
| * has detected concurrent modification. |
| */ |
| int expectedModCount = modCount; |
| |
| public boolean hasNext() { |
| return cursor != count; |
| } |
| |
| public Object next() { |
| try { |
| Object next = get(cursor); |
| checkForComodification(); |
| lastRet = cursor++; |
| return next; |
| } catch(IndexOutOfBoundsException e) { |
| checkForComodification(); |
| throw new NoSuchElementException(); |
| } |
| } |
| |
| public void remove() { |
| if (lastRet == -1) { |
| throw new IllegalStateException(); |
| } |
| checkForComodification(); |
| |
| try { |
| CompositeGraphicsNode.this.remove(lastRet); |
| if (lastRet < cursor) { |
| cursor--; |
| } |
| lastRet = -1; |
| expectedModCount = modCount; |
| } catch(IndexOutOfBoundsException e) { |
| throw new ConcurrentModificationException(); |
| } |
| } |
| |
| final void checkForComodification() { |
| if (modCount != expectedModCount) { |
| throw new ConcurrentModificationException(); |
| } |
| } |
| } |
| |
| |
| /** |
| * An implementation of the java.util.ListIterator interface. |
| */ |
| private class ListItr extends Itr implements ListIterator { |
| |
| ListItr(int index) { |
| cursor = index; |
| } |
| |
| public boolean hasPrevious() { |
| return cursor != 0; |
| } |
| |
| public Object previous() { |
| try { |
| Object previous = get(--cursor); |
| checkForComodification(); |
| lastRet = cursor; |
| return previous; |
| } catch(IndexOutOfBoundsException e) { |
| checkForComodification(); |
| throw new NoSuchElementException(); |
| } |
| } |
| |
| public int nextIndex() { |
| return cursor; |
| } |
| |
| public int previousIndex() { |
| return cursor-1; |
| } |
| |
| public void set(Object o) { |
| if (lastRet == -1) { |
| throw new IllegalStateException(); |
| } |
| checkForComodification(); |
| try { |
| CompositeGraphicsNode.this.set(lastRet, o); |
| expectedModCount = modCount; |
| } catch(IndexOutOfBoundsException e) { |
| throw new ConcurrentModificationException(); |
| } |
| } |
| |
| public void add(Object o) { |
| checkForComodification(); |
| try { |
| CompositeGraphicsNode.this.add(cursor++, o); |
| lastRet = -1; |
| expectedModCount = modCount; |
| } catch(IndexOutOfBoundsException e) { |
| throw new ConcurrentModificationException(); |
| } |
| } |
| } |
| } |