001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package com.sun.perseus.model;
028:
029: import com.sun.perseus.platform.MathSupport;
030:
031: import com.sun.perseus.util.SVGConstants;
032:
033: import org.w3c.dom.DOMException;
034:
035: import org.w3c.dom.svg.SVGElement;
036:
037: import java.util.Vector;
038:
039: /**
040: * <code>AnimateMotion</code> represents an SVG Tiny
041: * <code><animateMotion></code> element.
042: *
043: * @version $Id: AnimateMotion.java,v 1.5 2006/06/29 10:47:29 ln156897 Exp $
044: */
045: public class AnimateMotion extends AbstractAnimate {
046: /**
047: * Rotate type when an angle is specified in the rotate attribute.
048: */
049: static final int ROTATE_ANGLE = 1;
050:
051: /**
052: * Rotate type when the rotate attribute is set to auto.
053: */
054: static final int ROTATE_AUTO = 2;
055:
056: /**
057: * Rotate type when the rotate attribute is set to auto-reverse
058: */
059: static final int ROTATE_AUTO_REVERSE = 3;
060:
061: /**
062: * The path attribute value.
063: */
064: String path;
065:
066: /**
067: * The keyPoints attribute value
068: */
069: float[] keyPoints;
070:
071: /**
072: * The rotate angle. Used if rotateType is ROTATE_ANGLE
073: */
074: float rotate;
075:
076: /**
077: * The rotation's cos value
078: */
079: float cosRotate = 1;
080:
081: /**
082: * The rotation angle's sin value.
083: */
084: float sinRotate;
085:
086: /**
087: * The rotate type. One of ROTATE_ANGLE (an angle was specified) or
088: * ROTATE_AUTO, ROTATE_AUTO_REVERSE
089: */
090: int rotateType = ROTATE_ANGLE;
091:
092: /**
093: * Used, temporarily, to hold the refValues for the path attribute.
094: */
095: RefValues pathRefValues;
096:
097: /**
098: * Used, temporarily, to hold the refValues for mpath child.
099: */
100: RefValues mpathRefValues;
101:
102: /**
103: * Builds a new AnimateMotion element that belongs to the given
104: * document. This <code>AnimateMotion</code> will belong
105: * to the <code>DocumentNode</code>'s time container.
106: *
107: * @param ownerDocument the document this node belongs to.
108: * @throws IllegalArgumentException if the input ownerDocument is null
109: */
110: public AnimateMotion(final DocumentNode ownerDocument) {
111: super (ownerDocument, SVGConstants.SVG_ANIMATE_MOTION_TAG);
112:
113: // Default calcMode for animateMotion is paced.
114: calcMode = CALC_MODE_PACED;
115:
116: // AnimateMotion operates on a fixed trait name
117: traitName = SVGConstants.SVG_MOTION_PSEUDO_ATTRIBUTE;
118: }
119:
120: /**
121: * Used by <code>DocumentNode</code> to create a new instance from
122: * a prototype <code>TimedElementNode</code>.
123: *
124: * @param doc the <code>DocumentNode</code> for which a new node is
125: * should be created.
126: * @return a new <code>TimedElementNode</code> for the requested document.
127: */
128: public ElementNode newInstance(final DocumentNode doc) {
129: return new AnimateMotion(doc);
130: }
131:
132: /**
133: * @param traitName the name of the trait which the element may support.
134: * @return true if this element supports the given trait in one of the
135: * trait accessor methods.
136: */
137: boolean supportsTrait(final String traitName) {
138: if (SVGConstants.SVG_PATH_ATTRIBUTE == traitName
139: || SVGConstants.SVG_KEY_POINTS_ATTRIBUTE == traitName
140: || SVGConstants.SVG_ROTATE_ATTRIBUTE == traitName) {
141: return true;
142: } else {
143: return super .supportsTrait(traitName);
144: }
145: }
146:
147: // JAVADOC COMMENT ELIDED
148: public String getTraitImpl(final String name) throws DOMException {
149: if (SVGConstants.SVG_PATH_ATTRIBUTE == name) {
150: return path;
151: } else if (SVGConstants.SVG_KEY_POINTS_ATTRIBUTE == name) {
152: return toStringTrait(keyPoints);
153: } else if (SVGConstants.SVG_ROTATE_ATTRIBUTE == name) {
154: switch (rotateType) {
155: case ROTATE_ANGLE:
156: return Float.toString(rotate);
157: case ROTATE_AUTO:
158: return SVGConstants.SVG_AUTO_VALUE;
159: case ROTATE_AUTO_REVERSE:
160: default:
161: return SVGConstants.SVG_AUTO_REVERSE_VALUE;
162: }
163: } else {
164: return super .getTraitImpl(name);
165: }
166: }
167:
168: /**
169: * The following call lets the animate implementation map
170: * the time segment indices and the time segment penetration
171: * into refValues indices and penetration, in case these are
172: * different. Typically, these are the same, but they may be
173: * different, for example in the case of animateMotion with
174: * keyPoints.
175: *
176: * @param si the time segment index.
177: * @param sp the segment penetration.
178: * @param sisp an array where the mapped si and sp value should
179: * be stored. si is at index 0 and sp at index 1.
180: */
181: protected void mapToSegmentProgress(final int si, final float sp,
182: final float[] sisp) {
183: // animateMotion also maps to segments directly unless there
184: // is a keyPoints attribute specified, in which case it
185: // overrides the default behavior.
186: if (keyPoints == null || actualCalcMode == CALC_MODE_PACED) {
187: super .mapToSegmentProgress(si, sp, sisp);
188: return;
189: }
190:
191: // We are dealing with a keyPoints animateMotion.
192: // We only do the computation on the first component, because
193: // the input and output indices for animateMotion with keyTimes
194: // should be the same on all components (by definition).
195: float startDist = keyPoints[si];
196: float endDist = keyPoints[si + 1];
197:
198: float dist = sp * endDist + (1 - sp) * startDist;
199:
200: // Now that we know how far along we should be on the path,
201: // find the corresponding segment.
202: ((MotionRefValues) refValues).getSegmentAtDist(sisp, dist);
203: }
204:
205: /**
206: * @param name the trait's name.
207: * @param value the trait's value.
208: *
209: * @throws DOMException with error code NOT_SUPPORTED_ERR if the requested
210: * trait is not supported on this element or null.
211: * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested
212: * trait's value cannot be specified as a String
213: * @throws DOMException with error code INVALID_ACCESS_ERR if the input
214: * value is an invalid value for the given trait or null.
215: * @throws DOMException with error code NO_MODIFICATION_ALLOWED_ERR: if
216: * attempt is made to change readonly trait.
217: */
218: public void setTraitImpl(final String name, final String value)
219: throws DOMException {
220: if (SVGConstants.SVG_PATH_ATTRIBUTE == name) {
221: checkWriteLoading(name);
222: // path values are validated in the validate() method,
223: // just like to/from/by and values are
224: path = value;
225: } else if (SVGConstants.SVG_KEY_POINTS_ATTRIBUTE == name) {
226: checkWriteLoading(name);
227: keyPoints = parseFloatArrayTrait(name, value, ';');
228: } else if (SVGConstants.SVG_ROTATE_ATTRIBUTE == name) {
229: checkWriteLoading(name);
230:
231: if (SVGConstants.SVG_AUTO_VALUE.equals(value)) {
232: rotate = 0;
233: rotateType = ROTATE_AUTO;
234: } else if (SVGConstants.SVG_AUTO_REVERSE_VALUE
235: .equals(value)) {
236: rotate = 0;
237: rotateType = ROTATE_AUTO_REVERSE;
238: } else {
239: // The value is neither 'auto' nor 'auto-reverse'. It
240: // must be an angle value.
241: rotate = parseFloatTrait(name, value);
242: cosRotate = MathSupport.cos(MathSupport
243: .toRadians(rotate));
244: sinRotate = MathSupport.sin(MathSupport
245: .toRadians(rotate));
246: rotateType = ROTATE_ANGLE;
247: }
248: } else {
249: super .setTraitImpl(name, value);
250: }
251: }
252:
253: /**
254: * Computes refTimes from the calcMode and keyTimes attributes. Validates
255: * that the keyTimes attribute is compatible with the animate set up. This
256: * may be overridden by subclasses (e.g., animateMotion), when there are
257: * special rules for checking keyTimes compatiblity.
258: */
259: protected void computeRefTimes() throws DOMException {
260: // if there is no keyPoints defined or if the calcMode is
261: // paced, we use the default behavior.
262: if (keyPoints == null || actualCalcMode == CALC_MODE_PACED) {
263: super .computeRefTimes();
264: return;
265: }
266:
267: // Check keyTimes is compatible with the animation specification.
268: //
269: // a) keyTimes should be specified (i.e., not null)
270: //
271: // b) In all cases, the first keyTime must be zero.
272: //
273: // c) For non-discrete animations, the last keyTime must be one.
274: //
275: // d) There should be as many keyTimes as there are keyPoints
276: //
277: if (/* a) */keyTimes == null
278: ||
279: /* b) */keyTimes.length < 1
280: || keyTimes[0] != 0
281: ||
282: /* c) */(actualCalcMode != CALC_MODE_DISCRETE && keyTimes[keyTimes.length - 1] != 1)
283: ||
284: /* d) */keyTimes.length != keyPoints.length) {
285: throw animationError(
286: idRef,
287: traitNamespace,
288: traitName,
289: targetElement.getNamespaceURI(),
290: targetElement.getLocalName(),
291: getId(),
292: getNamespaceURI(),
293: getLocalName(),
294: Messages
295: .formatMessage(
296: Messages.ERROR_INVALID_ANIMATION_KEY_TIMES,
297: new Object[] { getTrait(SVGConstants.SVG_KEY_TIMES_ATTRIBUTE) }));
298: }
299:
300: // If the calcMode is _not_ discrete, we trim the last '1'
301: // value.
302: if (actualCalcMode != CALC_MODE_DISCRETE) {
303: refTimes = new float[keyTimes.length - 1];
304: System.arraycopy(keyTimes, 0, refTimes, 0, refTimes.length);
305: } else {
306: refTimes = keyTimes;
307: }
308: }
309:
310: /**
311: * Validates the path and mpath values sources.
312: *
313: * @throws DOMException if there is a validation error, for example if the
314: * to value is incompatible with the target trait or if the target
315: * trait is not animatable.
316: */
317: final void validateValuesExtra() throws DOMException {
318: // Validate the path attribute value
319: pathRefValues = null;
320: if (path != null) {
321: pathRefValues = traitAnim.toRefValues(this ,
322: new String[] { path }, null,
323: SVGConstants.SVG_PATH_ATTRIBUTE);
324: }
325:
326: // Now, validate the mpath child, if any.
327: mpathRefValues = null;
328: SVGElement c = (SVGElement) getFirstElementChild();
329: SVGElement mpath = null;
330: while (c != null) {
331: if (SVGConstants.SVG_MPATH_TAG.equals(c.getLocalName())
332: && SVGConstants.SVG_NAMESPACE_URI.equals(c
333: .getNamespaceURI())) {
334: mpath = c;
335: break;
336: }
337: c = (SVGElement) c.getNextElementSibling();
338: }
339:
340: if (mpath != null) {
341: String pathHref = ((ElementNode) mpath).getTraitNSImpl(
342: SVGConstants.XLINK_NAMESPACE_URI,
343: SVGConstants.SVG_HREF_ATTRIBUTE);
344: if (pathHref != null) {
345: boolean pathHrefError = false;
346: if (pathHref.startsWith("#")) {
347: String pathId = pathHref.substring(1);
348: ElementNode path = (ElementNode) ownerDocument
349: .getElementById(pathId);
350: if (path != null) {
351: mpathRefValues = traitAnim
352: .toRefValues(
353: this ,
354: new String[] { path
355: .getTraitImpl(SVGConstants.SVG_D_ATTRIBUTE) },
356: null,
357: SVGConstants.SVG_D_ATTRIBUTE);
358: } else {
359: pathHrefError = true;
360: }
361: } else {
362: pathHrefError = true;
363: }
364:
365: if (pathHrefError) {
366: throw animationError(idRef, traitNamespace,
367: traitName, targetElement.getNamespaceURI(),
368: targetElement.getLocalName(), getId(),
369: getNamespaceURI(), getLocalName(),
370: Messages.formatMessage(
371: Messages.ERROR_INVALID_MPATH_HREF,
372: new Object[] { pathHref }));
373: }
374: } else {
375: throw animationError(
376: idRef,
377: traitNamespace,
378: traitName,
379: targetElement.getNamespaceURI(),
380: targetElement.getLocalName(),
381: getId(),
382: getNamespaceURI(),
383: getLocalName(),
384: Messages
385: .formatMessage(
386: Messages.ERROR_MISSING_MPATH_HREF,
387: null));
388: }
389: }
390: }
391:
392: /**
393: * Computes the 'right' source for reference values, depending on the
394: * precedence rules for the different values sources.
395: *
396: * @throws DOMException if there is no way to compute a set of reference
397: * values, for example if none of the values sources is specified.
398: */
399: final void selectRefValuesExtra() throws DOMException {
400: if (mpathRefValues != null) {
401: refValues = mpathRefValues;
402: } else if (pathRefValues != null) {
403: refValues = pathRefValues;
404: }
405: }
406:
407: }
|