| /* |
| |
| 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.Shape; |
| import java.awt.Rectangle; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Rectangle2D; |
| import java.lang.ref.WeakReference; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.batik.gvt.event.GraphicsNodeChangeAdapter; |
| import org.apache.batik.gvt.event.GraphicsNodeChangeEvent; |
| import org.apache.batik.ext.awt.image.renderable.Filter; |
| |
| /** |
| * This class tracks the changes on a GVT tree |
| * |
| * @author <a href="mailto:Thomas.DeWeeese@Kodak.com">Thomas DeWeese</a> |
| * @version $Id$ |
| */ |
| public class UpdateTracker extends GraphicsNodeChangeAdapter { |
| |
| Map dirtyNodes = null; |
| Map fromBounds = new HashMap(); |
| protected static Rectangle2D NULL_RECT = new Rectangle(); |
| |
| public UpdateTracker(){ |
| } |
| |
| /** |
| * Tells whether the GVT tree has changed. |
| */ |
| public boolean hasChanged() { |
| return (dirtyNodes != null); |
| } |
| |
| /** |
| * Returns the list of dirty areas on GVT. |
| */ |
| public List getDirtyAreas() { |
| if (dirtyNodes == null) |
| return null; |
| |
| List ret = new LinkedList(); |
| Set keys = dirtyNodes.keySet(); |
| for (Object key : keys) { |
| WeakReference gnWRef = (WeakReference) key; |
| GraphicsNode gn = (GraphicsNode) gnWRef.get(); |
| // GraphicsNode srcGN = gn; |
| |
| // if the weak ref has been cleared then this node is no |
| // longer part of the GVT tree (and the change should be |
| // reflected in some ancestor that should also be in the |
| // dirty list). |
| if (gn == null) continue; |
| |
| AffineTransform oat; |
| oat = (AffineTransform) dirtyNodes.get(gnWRef); |
| if (oat != null) { |
| oat = new AffineTransform(oat); |
| } |
| |
| Rectangle2D srcORgn = (Rectangle2D) fromBounds.remove(gnWRef); |
| |
| Rectangle2D srcNRgn = null; |
| AffineTransform nat = null; |
| if (!(srcORgn instanceof ChngSrcRect)) { |
| // For change srcs don't use the new bounds of parent node. |
| srcNRgn = gn.getBounds(); |
| nat = gn.getTransform(); |
| if (nat != null) |
| nat = new AffineTransform(nat); |
| } |
| |
| |
| // System.out.println("Rgns: " + srcORgn + " - " + srcNRgn); |
| // System.out.println("ATs: " + oat + " - " + nat); |
| do { |
| // f.invalidateCache(oRng); |
| // f.invalidateCache(nRng); |
| |
| // f = gn.getEnableBackgroundGraphicsNodeRable(false); |
| // (need to push rgn through filter chain if any...) |
| // f.invalidateCache(oRng); |
| // f.invalidateCache(nRng); |
| |
| gn = gn.getParent(); |
| if (gn == null) |
| break; // We reached the top of the tree |
| |
| Filter f = gn.getFilter(); |
| if (f != null) { |
| srcNRgn = f.getBounds2D(); |
| nat = null; |
| } |
| |
| // Get the parent's current Affine |
| AffineTransform at = gn.getTransform(); |
| // Get the parent's Affine last time we rendered. |
| gnWRef = gn.getWeakReference(); |
| AffineTransform poat = (AffineTransform) dirtyNodes.get(gnWRef); |
| if (poat == null) poat = at; |
| if (poat != null) { |
| if (oat != null) |
| oat.preConcatenate(poat); |
| else |
| oat = new AffineTransform(poat); |
| } |
| |
| if (at != null) { |
| if (nat != null) |
| nat.preConcatenate(at); |
| else |
| nat = new AffineTransform(at); |
| } |
| } while (true); |
| |
| if (gn == null) { |
| // We made it to the root graphics node so add them. |
| // System.out.println |
| // ("Adding: " + oat + " - " + nat + "\n" + |
| // srcORgn + "\n" + srcNRgn + "\n"); |
| // <!> |
| Shape oRgn = srcORgn; |
| if ((oRgn != null) && (oRgn != NULL_RECT)) { |
| if (oat != null) |
| oRgn = oat.createTransformedShape(srcORgn); |
| // System.err.println("GN: " + srcGN); |
| // System.err.println("Src: " + oRgn.getBounds2D()); |
| ret.add(oRgn); |
| } |
| |
| if (srcNRgn != null) { |
| Shape nRgn = srcNRgn; |
| if (nat != null) |
| nRgn = nat.createTransformedShape(srcNRgn); |
| if (nRgn != null) |
| ret.add(nRgn); |
| } |
| } |
| } |
| |
| fromBounds.clear(); |
| dirtyNodes.clear(); |
| return ret; |
| } |
| |
| /** |
| * This returns the dirty region for gn in the coordinate system |
| * given by <code>at</code>. |
| * @param gn Node tree to return dirty region for. |
| * @param at Affine transform to coordinate space to accumulate |
| * dirty regions in. |
| */ |
| public Rectangle2D getNodeDirtyRegion(GraphicsNode gn, |
| AffineTransform at) { |
| WeakReference gnWRef = gn.getWeakReference(); |
| AffineTransform nat = (AffineTransform)dirtyNodes.get(gnWRef); |
| if (nat == null) nat = gn.getTransform(); |
| if (nat != null) { |
| at = new AffineTransform(at); |
| at.concatenate(nat); |
| } |
| |
| Filter f= gn.getFilter(); |
| Rectangle2D ret = null; |
| if (gn instanceof CompositeGraphicsNode) { |
| CompositeGraphicsNode cgn = (CompositeGraphicsNode)gn; |
| |
| for (Object aCgn : cgn) { |
| GraphicsNode childGN = (GraphicsNode) aCgn; |
| Rectangle2D r2d = getNodeDirtyRegion(childGN, at); |
| if (r2d != null) { |
| if (f != null) { |
| // If we have a filter and a change region |
| // Update our full filter extents. |
| Shape s = at.createTransformedShape(f.getBounds2D()); |
| ret = s.getBounds2D(); |
| break; |
| } |
| if ((ret == null) || (ret == NULL_RECT)) ret = r2d; |
| //else ret = ret.createUnion(r2d); |
| else ret.add(r2d); |
| } |
| } |
| } else { |
| ret = (Rectangle2D)fromBounds.remove(gnWRef); |
| if (ret == null) { |
| if (f != null) ret = f.getBounds2D(); |
| else ret = gn.getBounds(); |
| } else if (ret == NULL_RECT) |
| ret = null; |
| if (ret != null) |
| ret = at.createTransformedShape(ret).getBounds2D(); |
| } |
| return ret; |
| } |
| |
| public Rectangle2D getNodeDirtyRegion(GraphicsNode gn) { |
| return getNodeDirtyRegion(gn, new AffineTransform()); |
| } |
| |
| /** |
| * Receives notification of a change to a GraphicsNode. |
| * @param gnce The event object describing the GraphicsNode change. |
| */ |
| public void changeStarted(GraphicsNodeChangeEvent gnce) { |
| // System.out.println("A node has changed for: " + this); |
| GraphicsNode gn = gnce.getGraphicsNode(); |
| WeakReference gnWRef = gn.getWeakReference(); |
| |
| boolean doPut = false; |
| if (dirtyNodes == null) { |
| dirtyNodes = new HashMap(); |
| doPut = true; |
| } else if (!dirtyNodes.containsKey(gnWRef)) |
| doPut = true; |
| |
| if (doPut) { |
| AffineTransform at = gn.getTransform(); |
| if (at != null) at = (AffineTransform)at.clone(); |
| else at = new AffineTransform(); |
| dirtyNodes.put(gnWRef, at); |
| } |
| |
| GraphicsNode chngSrc = gnce.getChangeSrc(); |
| Rectangle2D rgn = null; |
| if (chngSrc != null) { |
| // A child node is moving in the tree so assign it's dirty |
| // regions to this node before it moves. |
| Rectangle2D drgn = getNodeDirtyRegion(chngSrc); |
| if (drgn != null) |
| rgn = new ChngSrcRect(drgn); |
| } else { |
| // Otherwise just use gn's current region. |
| rgn = gn.getBounds(); |
| } |
| // Add this dirty region to any existing dirty region. |
| Rectangle2D r2d = (Rectangle2D)fromBounds.remove(gnWRef); |
| if (rgn != null) { |
| if ((r2d != null) && (r2d != NULL_RECT)) { |
| // System.err.println("GN: " + gn); |
| // System.err.println("R2d: " + r2d); |
| // System.err.println("Rgn: " + rgn); |
| //r2d = r2d.createUnion(rgn); |
| r2d.add(rgn); |
| // System.err.println("Union: " + r2d); |
| } |
| else r2d = rgn; |
| } |
| |
| // if ((gn instanceof CompositeGraphicsNode) && |
| // (r2d.getWidth() > 200)) { |
| // new Exception("Adding Large: " + gn).printStackTrace(); |
| // } |
| |
| // Store the bounds for the future. |
| if (r2d == null) |
| r2d = NULL_RECT; |
| fromBounds.put(gnWRef, r2d); |
| } |
| |
| static class ChngSrcRect extends Rectangle2D.Float { |
| ChngSrcRect(Rectangle2D r2d) { |
| super((float)r2d.getX(), (float)r2d.getY(), |
| (float)r2d.getWidth(), (float)r2d.getHeight()); |
| } |
| } |
| |
| /** |
| * Clears the tracker. |
| */ |
| public void clear() { |
| dirtyNodes = null; |
| } |
| } |