FileDocCategorySizeDatePackage
AnimateMotion.javaAPI DocphoneME MR2 API (J2ME)15331Wed May 02 18:00:34 BST 2007com.sun.perseus.model

AnimateMotion.java

/*
 *
 *
 * Copyright  1990-2007 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */

package com.sun.perseus.model;

import com.sun.perseus.platform.MathSupport;

import com.sun.perseus.util.SVGConstants;

import org.w3c.dom.DOMException;

import org.w3c.dom.svg.SVGElement;

import java.util.Vector;

/**
 * <code>AnimateMotion</code> represents an SVG Tiny 
 * <code><animateMotion></code> element.
 *
 * @version $Id: AnimateMotion.java,v 1.5 2006/06/29 10:47:29 ln156897 Exp $
 */
public class AnimateMotion extends AbstractAnimate {
    /**
     * Rotate type when an angle is specified in the rotate attribute.
     */
    static final int ROTATE_ANGLE = 1;

    /**
     * Rotate type when the rotate attribute is set to auto.
     */
    static final int ROTATE_AUTO = 2;

    /**
     * Rotate type when the rotate attribute is set to auto-reverse
     */
    static final int ROTATE_AUTO_REVERSE = 3;

    /**
     * The path attribute value.
     */
    String path;

    /**
     * The keyPoints attribute value
     */
    float[] keyPoints;

    /**
     * The rotate angle. Used if rotateType is ROTATE_ANGLE
     */
    float rotate;

    /**
     * The rotation's cos value
     */
    float cosRotate = 1;

    /**
     * The rotation angle's sin value.
     */
    float sinRotate;

    /**
     * The rotate type. One of ROTATE_ANGLE (an angle was specified) or
     * ROTATE_AUTO, ROTATE_AUTO_REVERSE
     */
    int rotateType = ROTATE_ANGLE;

    /**
     * Used, temporarily, to hold the refValues for the path attribute.
     */
    RefValues pathRefValues;

    /**
     * Used, temporarily, to hold the refValues for mpath child.
     */
    RefValues mpathRefValues;

    /**
     * Builds a new AnimateMotion element that belongs to the given
     * document. This <code>AnimateMotion</code> will belong 
     * to the <code>DocumentNode</code>'s time container.
     *
     * @param ownerDocument the document this node belongs to.
     * @throws IllegalArgumentException if the input ownerDocument is null
     */
    public AnimateMotion(final DocumentNode ownerDocument) {
        super(ownerDocument, SVGConstants.SVG_ANIMATE_MOTION_TAG);

        // Default calcMode for animateMotion is paced.
        calcMode = CALC_MODE_PACED;

        // AnimateMotion operates on a fixed trait name
        traitName = SVGConstants.SVG_MOTION_PSEUDO_ATTRIBUTE;
    }

    /**
     * Used by <code>DocumentNode</code> to create a new instance from
     * a prototype <code>TimedElementNode</code>.
     *
     * @param doc the <code>DocumentNode</code> for which a new node is
     *        should be created.
     * @return a new <code>TimedElementNode</code> for the requested document.
     */
    public ElementNode newInstance(final DocumentNode doc) {
        return new AnimateMotion(doc);
    }

    /**
     * @param traitName the name of the trait which the element may support.
     * @return true if this element supports the given trait in one of the
     *         trait accessor methods.
     */
    boolean supportsTrait(final String traitName) {
        if (SVGConstants.SVG_PATH_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_KEY_POINTS_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_ROTATE_ATTRIBUTE == traitName) {
            return true;
        } else {
            return super.supportsTrait(traitName);
        }
    }

    // JAVADOC COMMENT ELIDED
    public String getTraitImpl(final String name)
        throws DOMException {
        if (SVGConstants.SVG_PATH_ATTRIBUTE == name) {
            return path;
        } else if (SVGConstants.SVG_KEY_POINTS_ATTRIBUTE == name) {
            return toStringTrait(keyPoints);
        } else if (SVGConstants.SVG_ROTATE_ATTRIBUTE == name) {
            switch (rotateType) {
            case ROTATE_ANGLE:
                return Float.toString(rotate);
            case ROTATE_AUTO:
                return SVGConstants.SVG_AUTO_VALUE;
            case ROTATE_AUTO_REVERSE:
            default:
                return SVGConstants.SVG_AUTO_REVERSE_VALUE;
            }
        } else {
            return super.getTraitImpl(name);
        }
    }

    /**
     * The following call lets the animate implementation map 
     * the time segment indices and the time segment penetration
     * into refValues indices and penetration, in case these are
     * different. Typically, these are the same, but they may be
     * different, for example in the case of animateMotion with
     * keyPoints.
     *
     * @param si the time segment index.
     * @param sp the segment penetration.
     * @param sisp an array where the mapped si and sp value should
     *        be stored. si is at index 0 and sp at index 1.
     */
    protected void mapToSegmentProgress(final int si, final float sp, 
                                        final float[] sisp) {
        // animateMotion also maps to segments directly unless there
        // is a keyPoints attribute specified, in which case it 
        // overrides the default behavior.
        if (keyPoints == null || actualCalcMode == CALC_MODE_PACED) {
            super.mapToSegmentProgress(si, sp, sisp);
            return;
        }

        // We are dealing with a keyPoints animateMotion.
        // We only do the computation on the first component, because
        // the input and output indices for animateMotion with keyTimes
        // should be the same on all components (by definition).
        float startDist = keyPoints[si];
        float endDist = keyPoints[si + 1];

        float dist = sp * endDist + (1 - sp) * startDist;

        // Now that we know how far along we should be on the path, 
        // find the corresponding segment.
        ((MotionRefValues) refValues).getSegmentAtDist(sisp, dist);
    }

    /**
     * @param name the trait's name.
     * @param value the trait's value.
     *
     * @throws DOMException with error code NOT_SUPPORTED_ERR if the requested
     * trait is not supported on this element or null.
     * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested
     * trait's value cannot be specified as a String
     * @throws DOMException with error code INVALID_ACCESS_ERR if the input
     * value is an invalid value for the given trait or null.
     * @throws DOMException with error code NO_MODIFICATION_ALLOWED_ERR: if
     * attempt is made to change readonly trait.
     */
    public void setTraitImpl(final String name, 
                             final String value)
        throws DOMException {
        if (SVGConstants.SVG_PATH_ATTRIBUTE == name) {
            checkWriteLoading(name);
            // path values are validated in the validate() method,
            // just like to/from/by and values are
            path = value;
        } else if (SVGConstants.SVG_KEY_POINTS_ATTRIBUTE == name) {
            checkWriteLoading(name);
            keyPoints = parseFloatArrayTrait(name, value, ';');
        } else if (SVGConstants.SVG_ROTATE_ATTRIBUTE == name) {
            checkWriteLoading(name);
            
            if (SVGConstants.SVG_AUTO_VALUE.equals(value)) {
                rotate = 0;
                rotateType = ROTATE_AUTO;
            } else if (SVGConstants.SVG_AUTO_REVERSE_VALUE.equals(value)) {
                rotate = 0;
                rotateType = ROTATE_AUTO_REVERSE;
            } else {
                // The value is neither 'auto' nor 'auto-reverse'. It 
                // must be an angle value.
                rotate = parseFloatTrait(name, value);
                cosRotate = MathSupport.cos(MathSupport.toRadians(rotate));
                sinRotate = MathSupport.sin(MathSupport.toRadians(rotate));
                rotateType = ROTATE_ANGLE;
            }
        } else {
            super.setTraitImpl(name, value);
        }
    }

    /**
     * Computes refTimes from the calcMode and keyTimes attributes. Validates
     * that the keyTimes attribute is compatible with the animate set up. This
     * may be overridden by subclasses (e.g., animateMotion), when there are 
     * special rules for checking keyTimes compatiblity.
     */
    protected void computeRefTimes() throws DOMException {
        // if there is no keyPoints defined or if the calcMode is
        // paced, we use the default behavior.
        if (keyPoints == null || actualCalcMode == CALC_MODE_PACED) {
            super.computeRefTimes();
            return;
        }

        // Check keyTimes is compatible with the animation specification.
        // 
        // a) keyTimes should be specified (i.e., not null)
        //
        // b) In all cases, the first keyTime must be zero.
        // 
        // c) For non-discrete animations, the last keyTime must be one.
        //
        // d) There should be as many keyTimes as there are keyPoints
        //
        if (/* a) */ keyTimes == null
            ||
            /* b) */ keyTimes.length < 1 || keyTimes[0] != 0
            ||
            /* c) */ (actualCalcMode != CALC_MODE_DISCRETE 
                      && 
                      keyTimes[keyTimes.length - 1] != 1)
            ||
            /* d) */ keyTimes.length != keyPoints.length) {
            throw animationError(
                    idRef,
                    traitNamespace,
                    traitName,
                    targetElement.getNamespaceURI(),
                    targetElement.getLocalName(),
                    getId(),
                    getNamespaceURI(),
                    getLocalName(),
                    Messages.formatMessage(
                        Messages.ERROR_INVALID_ANIMATION_KEY_TIMES, 
                        new Object[] {
                            getTrait(SVGConstants.SVG_KEY_TIMES_ATTRIBUTE)
                        }));
        }
        
        // If the calcMode is _not_ discrete, we trim the last '1' 
        // value.
        if (actualCalcMode != CALC_MODE_DISCRETE) {
            refTimes = new float[keyTimes.length - 1];
            System.arraycopy(keyTimes, 0, refTimes, 0, refTimes.length);
        } else {
            refTimes = keyTimes;
        }
    }

    /**
     * Validates the path and mpath values sources.
     *
     * @throws DOMException if there is a validation error, for example if the
     *         to value is incompatible with the target trait or if the target
     *         trait is not animatable.
     */
    final void validateValuesExtra() throws DOMException {
        // Validate the path attribute value
        pathRefValues = null;
        if (path != null) {
            pathRefValues = traitAnim.toRefValues(
                    this,
                    new String[] {path},
                    null,
                    SVGConstants.SVG_PATH_ATTRIBUTE);
        }

        // Now, validate the mpath child, if any.
        mpathRefValues = null;
        SVGElement c = (SVGElement) getFirstElementChild();
        SVGElement mpath = null;
        while (c != null) {
            if (SVGConstants.SVG_MPATH_TAG.equals(c.getLocalName())
                &&
                SVGConstants.SVG_NAMESPACE_URI.equals(c.getNamespaceURI())) {
                mpath = c;
                break;
            }
            c = (SVGElement) c.getNextElementSibling();
        }

        if (mpath != null) {
            String pathHref = ((ElementNode) mpath).getTraitNSImpl(
                    SVGConstants.XLINK_NAMESPACE_URI,
                    SVGConstants.SVG_HREF_ATTRIBUTE);
            if (pathHref != null) {
                boolean pathHrefError = false;
                if (pathHref.startsWith("#")) {
                    String pathId = pathHref.substring(1);
                    ElementNode path = 
                            (ElementNode) ownerDocument.getElementById(pathId);
                    if (path != null) {
                        mpathRefValues = traitAnim.toRefValues(
                                this,
                                new String[] {
                                    path.getTraitImpl(
                                        SVGConstants.SVG_D_ATTRIBUTE)
                                },
                                null,
                                SVGConstants.SVG_D_ATTRIBUTE);
                    } else {
                        pathHrefError = true;
                    }
                } else {
                    pathHrefError = true;
                }

                if (pathHrefError) {
                    throw animationError(idRef,
                                         traitNamespace,
                                         traitName,
                                         targetElement.getNamespaceURI(),
                                         targetElement.getLocalName(),
                                         getId(),
                                         getNamespaceURI(),
                                         getLocalName(),
                                         Messages.formatMessage
                                         (Messages.ERROR_INVALID_MPATH_HREF, 
                                          new Object[] {pathHref}));
                }
            } else {
                throw animationError(idRef,
                                     traitNamespace,
                                     traitName,
                                     targetElement.getNamespaceURI(),
                                     targetElement.getLocalName(),
                                     getId(),
                                     getNamespaceURI(),
                                     getLocalName(),
                                     Messages.formatMessage
                                     (Messages.ERROR_MISSING_MPATH_HREF, null));
            }
        }
    }

    /**
     * Computes the 'right' source for reference values, depending on the 
     * precedence rules for the different values sources. 
     *
     * @throws DOMException if there is no way to compute a set of reference
     *         values, for example if none of the values sources is specified.
     */
    final void selectRefValuesExtra() throws DOMException {
        if (mpathRefValues != null) {
            refValues = mpathRefValues;
        } else if (pathRefValues != null) {
            refValues = pathRefValues;
        }
    }

}