001: /*
002: *
003: * Copyright (c) 2007, Sun Microsystems, Inc.
004: *
005: * All rights reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * * Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: * * Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in the
015: * documentation and/or other materials provided with the distribution.
016: * * Neither the name of Sun Microsystems nor the names of its contributors
017: * may be used to endorse or promote products derived from this software
018: * without specific prior written permission.
019: *
020: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
023: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031: */
032: package com.sun.perseus.demo.picturedecorator;
033:
034: // Exceptions
035: import java.io.IOException;
036:
037: // MIDp packages
038: import java.util.Vector;
039:
040: import javax.microedition.lcdui.Canvas;
041: import javax.microedition.lcdui.Command;
042: import javax.microedition.lcdui.Graphics;
043: import javax.microedition.lcdui.Image;
044: import javax.microedition.m2g.SVGImage;
045:
046: // JSR 226 packages
047: import javax.microedition.m2g.ScalableGraphics;
048:
049: import org.w3c.dom.Document;
050: import org.w3c.dom.svg.SVGElement;
051: import org.w3c.dom.svg.SVGLocatableElement;
052: import org.w3c.dom.svg.SVGMatrix;
053: import org.w3c.dom.svg.SVGRect;
054: import org.w3c.dom.svg.SVGSVGElement;
055:
056: /**
057: * The photo frame represents a single photo with SVG overlays.
058: */
059: public class PhotoFrame extends Canvas {
060: /**
061: * The namespace for xlink:href
062: */
063: public static final String XLINK_NAMESPACE_URI = "http://www.w3.org/1999/xlink";
064:
065: /**
066: * The namespace for the svg document.
067: */
068: public final String SVG_NAMESPACE_URI = "http://www.w3.org/2000/svg";
069:
070: /** The movement step for the cursor. */
071: private final int CURSOR_STEP = 3;
072:
073: /**
074: * The anchor for the background image.
075: */
076: private final int ANCHOR = Graphics.VCENTER | Graphics.HCENTER;
077:
078: /**
079: * The scalable graphics instance used for rendering.
080: */
081: protected ScalableGraphics sg = ScalableGraphics.createInstance();
082:
083: /**
084: * The main application that can load images.
085: */
086: private final PictureDecorator picturedecorator;
087:
088: /**
089: * The current photo number.
090: */
091: int photoNumber = 0;
092:
093: /**
094: * The current background image.
095: */
096: Image currentImage = null;
097:
098: /**
099: * The current SVG image.
100: */
101: SVGImage svgImage = null;
102:
103: /**
104: * The SVG document.
105: */
106: Document doc = null;
107:
108: /**
109: * The <svg> element.
110: */
111: SVGSVGElement svg = null;
112:
113: /**
114: * The current prop.
115: */
116: SVGLocatableElement prop = null;
117:
118: /**
119: * The list of props that have been placed and can be picked up.
120: */
121: Vector visibleProps = new Vector();
122:
123: /**
124: * The crosshair cursor's horizontal position.
125: */
126: int cursorX = 0;
127:
128: /**
129: * The crosshair cursor's vertical position.
130: */
131: int cursorY = 0;
132:
133: /**
134: * The ItemPicker canvas.
135: */
136: ItemPicker itemPicker;
137:
138: /**
139: * The ItemPicker command.
140: */
141: Command showItemPicker;
142:
143: /**
144: * Constructs a new photo frame.
145: */
146: public PhotoFrame(final PictureDecorator main) {
147: super ();
148:
149: picturedecorator = main;
150:
151: // There is currently no background image or SVG overlays.
152: currentImage = null;
153: svgImage = null;
154: doc = null;
155:
156: centerCursor();
157: }
158:
159: /**
160: * Centers the cursor in the canvas.
161: */
162: private void centerCursor() {
163: cursorX = getWidth() / 2;
164: cursorY = getHeight() / 2;
165: }
166:
167: /**
168: * Establishes the current background photo.
169: * @param number The number of the photo in the JAD file.
170: */
171: public void setPhoto(final int number) {
172: if ((number < 1)
173: || (number > picturedecorator.getMaxPhotoNumber())) {
174: throw new IllegalArgumentException("Bad photo number.");
175: }
176:
177: photoNumber = number;
178:
179: final String property = "Attribute-photo" + number;
180:
181: //String title = picturedecorator.getImageName(property);
182: //setTitle(title);
183: currentImage = picturedecorator.getImage(property);
184: repaint();
185: }
186:
187: /**
188: * Establishes the current prop library, which holds definitions of props in
189: * its <defs> section(s). Each prop is located by its ID.
190: * <p>
191: * When a prop is used on the display, a <code><use></code> element is
192: * inserted into the library, effectively making the prop available for
193: * display.
194: *
195: * @param image The SVG image containing the prop definitions. The image may
196: * also contain elements that are statically positioned.
197: */
198: public void setPropsLibrary(final SVGImage image) {
199: // Establish or cancel the props library.
200: svgImage = image;
201: doc = null;
202: svg = null;
203: prop = null;
204:
205: if (svgImage != null) {
206: // Establish the new library.
207: doc = svgImage.getDocument();
208: svg = (SVGSVGElement) doc.getDocumentElement();
209:
210: SVGRect viewBox = svg.getRectTrait("viewBox");
211: viewBox.setWidth(getWidth());
212: viewBox.setHeight(getHeight());
213: svg.setRectTrait("viewBox", viewBox);
214:
215: /*
216: * The viewport is set to the full size of the canvas. The
217: * image will then be moved, scaled and rotated within this
218: * environment.
219: */
220: svgImage.setViewportWidth(getWidth());
221: svgImage.setViewportHeight(getHeight());
222: }
223:
224: // Re-build the item picker
225: itemPicker = new ItemPicker(this , svgImage,
226: picturedecorator.display);
227: showItemPicker = new Command("Show Picker", Command.SCREEN, 0);
228: addCommand(showItemPicker);
229:
230: repaint();
231: }
232:
233: /**
234: * Walk the current parent node downward to find all "hiddens" (The SVG
235: * elements within a <defs> section that have IDs) and all "visibles"
236: * (The SVG elements outside the <defs> sections).
237: *
238: * @param parent The relative parent node.
239: * @param level The nesting level (0..n).
240: * @param defs The <defs> nesting level.
241: * @param hiddens The list of elements with IDs within a <defs> section.
242: * @param visibles The list of elements outside a <defs> section.
243: */
244: static void locateProps(final SVGElement parent, int level,
245: int defs, Vector hiddens, Vector visibles) {
246: SVGElement elem = (SVGElement) parent.getFirstElementChild();
247:
248: while (elem != null) {
249: String name = elem.getLocalName();
250: String id = elem.getId();
251:
252: // If <defs> was found, go down one more <defs> level.
253: if (name.equals("defs")) {
254: defs++;
255: }
256:
257: if (defs > 0) {
258: /*
259: * Don't include <defs> tag. Only accept <g> tags with ID's
260: * at this time to keep the prop-locating simple.
261: */
262:
263: // If an ID was found within <defs>, record it as a usable prop.
264: if (!name.equals("defs") && name.equals("g")
265: && (id != null)) {
266: hiddens.addElement(elem);
267: }
268: } else {
269: /*
270: * If an element is <use> or an ID'ed element outside of <defs>,
271: * record it as a visible prop that can potentially be picked up
272: * and manipulated.
273: */
274: if (name.equals("use") || (id != null)) {
275: visibles.addElement(elem);
276: }
277: }
278:
279: // Recurse to try to keep walking the tree downward.
280: locateProps(elem, level + 1, defs, hiddens, visibles);
281:
282: // Walking up again. "Walk out" of a <defs> if necessary.
283: if (level < defs) {
284: defs--;
285: }
286:
287: // Pick up the next sibling and try walking down its subtree.
288: elem = (SVGElement) elem.getNextElementSibling();
289: }
290: }
291:
292: /**
293: * Adds the prop referenced by <code>id</code> in the library.
294: *
295: * @param id The identifier of the prop within a <code><defs></code>
296: * section of the library.
297: */
298: public void addProp(final String id) {
299: prop = (SVGLocatableElement) doc.createElementNS(
300: SVG_NAMESPACE_URI, "use");
301: prop.setTraitNS(XLINK_NAMESPACE_URI, "href", "#" + id);
302:
303: addProp(prop);
304: }
305:
306: /**
307: * Adds the prop to the SVG document.
308: *
309: * @param prop The prop to be added.
310: */
311: public void addProp(final SVGLocatableElement newProp) {
312: if (newProp != null) {
313: svg.appendChild(newProp);
314: visibleProps.addElement(newProp);
315:
316: // The prop is now part of the document. Pick up its bounding box.
317: SVGRect r = newProp.getBBox();
318: float rx = 0;
319: float ry = 0;
320: float rwidth = 0;
321: float rheight = 0;
322:
323: if (r != null) {
324: rx = r.getX();
325: ry = r.getY();
326: rwidth = r.getWidth() / 2;
327: rheight = r.getHeight() / 2;
328: }
329:
330: translateProp(newProp, cursorX - (rx + rwidth), cursorY
331: - (ry + rheight));
332:
333: repaint();
334: }
335: }
336:
337: /**
338: * Removes the current prop if it is displayed.
339: */
340: public void removeProp() {
341: if (prop != null) {
342: svg.removeChild(prop);
343: visibleProps.removeElement(prop);
344: prop = null;
345: repaint();
346: }
347: }
348:
349: /**
350: * Translate the prop by the given horizontal and vertical distances.
351: *
352: * @param dx The horizontal distance to move the prop.
353: * @param dy The vertical distance to move the prop.
354: */
355: public void translateProp(final SVGLocatableElement prop,
356: final float dx, final float dy) {
357: if (prop != null) {
358: SVGMatrix translateTxf = prop.getMatrixTrait("transform");
359:
360: SVGMatrix txf = prop.getScreenCTM();
361: translateTxf.mMultiply(txf.inverse());
362: translateTxf.mTranslate(dx, dy);
363: translateTxf.mMultiply(txf);
364:
365: prop.setMatrixTrait("transform", translateTxf);
366: repaint();
367: }
368: }
369:
370: /**
371: * Scales the prop by the given scale.
372: *
373: * @param scale The scaling factor to be applied to the prop.
374: */
375: public void scaleProp(final SVGLocatableElement prop,
376: final float scale) {
377: if (prop != null) {
378: SVGMatrix scaleTxf = prop.getMatrixTrait("transform");
379:
380: SVGMatrix txf = prop.getScreenCTM();
381: scaleTxf.mMultiply(txf.inverse());
382: scaleTxf.mTranslate(cursorX, cursorY);
383: scaleTxf.mScale(scale);
384: scaleTxf.mTranslate(-cursorX, -cursorY);
385: scaleTxf.mMultiply(txf);
386:
387: prop.setMatrixTrait("transform", scaleTxf);
388: repaint();
389: }
390: }
391:
392: /**
393: * Rotates the prop by the given angle.
394: *
395: * @param angle The angle by which the prop will be rotated.
396: */
397: public void rotateProp(final SVGLocatableElement prop,
398: final float angle) {
399: if (prop != null) {
400: SVGMatrix rotateTxf = prop.getMatrixTrait("transform");
401:
402: SVGMatrix txf = prop.getScreenCTM();
403: rotateTxf.mMultiply(txf.inverse());
404: rotateTxf.mTranslate(cursorX, cursorY);
405: rotateTxf.mRotate(angle);
406: rotateTxf.mTranslate(-cursorX, -cursorY);
407: rotateTxf.mMultiply(txf);
408:
409: prop.setMatrixTrait("transform", rotateTxf);
410: repaint();
411: }
412: }
413:
414: /**
415: * Mirrors the prop about the horizontal or vertical axis.
416: *
417: * @param angle The angle by which the image will be rotated.
418: * @param flipHorizontal <code>true</code> if mirroring will be about the
419: * vertical axis; <code>false</code> if mirroring will be about the
420: * horizontal axis.
421: */
422: public void mirrorProp(final SVGLocatableElement prop,
423: final boolean flipHorizontal) {
424: if (prop != null) {
425: SVGMatrix mirrorTxf = prop.getMatrixTrait("transform");
426:
427: SVGMatrix mirroringTxf = flipHorizontal ? svg
428: .createSVGMatrixComponents(-1, 0, 0, 1, 0, 0) : svg
429: .createSVGMatrixComponents(1, 0, 0, -1, 0, 0);
430:
431: SVGMatrix txf = prop.getScreenCTM();
432: mirrorTxf.mMultiply(txf.inverse());
433: mirrorTxf.mTranslate(cursorX, cursorY);
434: mirrorTxf.mMultiply(mirroringTxf);
435: mirrorTxf.mTranslate(-cursorX, -cursorY);
436: mirrorTxf.mMultiply(txf);
437:
438: prop.setMatrixTrait("transform", mirrorTxf);
439: repaint();
440: }
441: }
442:
443: /**
444: * Handle a keypress.
445: * @param keyCode The code for the key that was pressed.
446: */
447: public void keyPressed(int keyCode) {
448: handleKey(keyCode);
449: }
450:
451: /**
452: * Repeat a keypress.
453: * @param keyCode The code for the key that was pressed.
454: */
455: public void keyRepeated(int keyCode) {
456: // keyPressed(keyCode);
457: }
458:
459: /**
460: * Process a keypress.
461: * <p>
462: * Note: This routine has been made separate from keyPressed in case
463: * repeated events need to be handled.
464: *
465: * @param keyCode The code for the key that was pressed.
466: */
467: private void handleKey(final int keyCode) {
468: if (svgImage == null) {
469: // Ignore input until the props are available.
470: return;
471: }
472:
473: int gameAction = getGameAction(keyCode);
474:
475: if (gameAction == FIRE) {
476: int w = getWidth();
477: int h = getHeight();
478:
479: if (prop != null) {
480: // Detach the prop from the crosshair.
481: prop = null;
482: } else {
483: // Pick up a prop or nothing (Leave prop null).
484: prop = getPropContaining((float) cursorX,
485: (float) cursorY);
486: }
487:
488: // Center the cursor again if it went off the screen.
489: if ((cursorX < 0) || (cursorX >= w) || (cursorY < 0)
490: || (cursorY >= h)) {
491: centerCursor();
492: }
493:
494: // Make sure the cursor gets updated.
495: repaint();
496: } else if (gameAction == UP) {
497: cursorY -= CURSOR_STEP;
498:
499: if (svgImage != null) {
500: translateProp(prop, 0, -CURSOR_STEP);
501: }
502:
503: repaint();
504: } else if (gameAction == DOWN) {
505: cursorY += CURSOR_STEP;
506: translateProp(prop, 0, CURSOR_STEP);
507: repaint();
508: } else if (gameAction == LEFT) {
509: cursorX -= CURSOR_STEP;
510: translateProp(prop, -CURSOR_STEP, 0);
511: repaint();
512: } else if (gameAction == RIGHT) {
513: cursorX += CURSOR_STEP;
514: translateProp(prop, CURSOR_STEP, 0);
515: repaint();
516: } else if (keyCode == Canvas.KEY_NUM0) {
517: if (prop != null) {
518: removeProp();
519: }
520: } else if (keyCode == Canvas.KEY_NUM1) {
521: scaleProp(prop, 1.00F - 0.10F);
522: } else if (keyCode == Canvas.KEY_NUM2) {
523: try {
524: setPhoto(photoNumber + 1);
525: } catch (IllegalArgumentException iae) {
526: setPhoto(1);
527: }
528: } else if (keyCode == Canvas.KEY_NUM3) {
529: scaleProp(prop, 1.00F + 0.10F);
530: } else if (keyCode == Canvas.KEY_NUM4) {
531: picturedecorator.splashCanvas.showAndWait(
532: picturedecorator.display, this );
533: } else if (keyCode == Canvas.KEY_NUM5) {
534: mirrorProp(prop, true); // Horizontal flip
535: } else if (keyCode == Canvas.KEY_NUM6) {
536: mirrorProp(prop, false);
537: } else if (keyCode == Canvas.KEY_NUM7) {
538: rotateProp(prop, -5);
539: } else if (keyCode == Canvas.KEY_NUM8) {
540: try {
541: setPhoto(photoNumber - 1);
542: } catch (IllegalArgumentException iae) {
543: setPhoto(picturedecorator.getMaxPhotoNumber());
544: }
545: } else if (keyCode == Canvas.KEY_NUM9) {
546: rotateProp(prop, +5);
547: } else if (keyCode == Canvas.KEY_POUND) {
548: // Show item picker
549: picturedecorator.display.setCurrent(itemPicker);
550: }
551: }
552:
553: /**
554: * Returns the smallest prop containing the given coordinate.
555: * <p>
556: * Note: This isn't the most refined method for containing a point within
557: * a prop. If a prop is circular, for example, and the coordinate lies
558: * outside the visible part of the circle but within the bounding box,
559: * the prop will be returned.
560: *
561: * @param x The horizontal position to be tested.
562: * @param y The vertical position to be tested.
563: *
564: * @return The <code>SVGLocatableElement</code> that contains the
565: * coordinate; <code>null</code> if the coordinate isn't within the
566: * bounding box for any of the props on the screen.
567: */
568: private SVGLocatableElement getPropContaining(final float x,
569: final float y) {
570: // The last smallest rectangle.
571: float lastX1 = 0;
572: float lastY1 = 0;
573: float lastX2 = lastX1 + getWidth();
574: float lastY2 = lastY1 + getHeight();
575:
576: // The prop that was located (Initially, no prop located.).
577: SVGLocatableElement foundProp = null;
578:
579: int n = visibleProps.size();
580:
581: for (int i = n - 1; i >= 0; i--) {
582: SVGLocatableElement elem = (SVGLocatableElement) visibleProps
583: .elementAt(i);
584:
585: if (elem == null) {
586: // This shouldn't happen, but just in case, don't crash.
587: continue;
588: }
589:
590: SVGRect r = elem.getScreenBBox();
591:
592: if (r == null) {
593: // This shouldn't happen, but just in case, don't crash.
594: continue;
595: }
596:
597: float x1 = r.getX();
598: float y1 = r.getY();
599: float x2 = x1 + r.getWidth();
600: float y2 = y1 + r.getHeight();
601:
602: if ((x >= x1) && (x < x2) && (y >= y1) && (y < y2)) {
603: /*
604: * The cursor is within this prop. If this is the first prop
605: * that was found, make sure it the user can get to it (This
606: * can happen in a case where the prop has been enlarged such
607: * that its bounding box has gone beyond the dimensions of the
608: * screen.).
609: */
610: if (foundProp == null) {
611: foundProp = elem;
612: }
613:
614: /*
615: * If this prop has the smallest bounding box, choose this prop
616: * over a prop that was previously selected.
617: */
618: if ((x1 >= lastX1) || (x2 < lastX2) || (y1 >= lastY1)
619: || (y2 < lastY2)) {
620: // Use the smaller bounding box now.
621: lastX1 = x1;
622: lastY1 = y1;
623: lastX2 = x2;
624: lastY2 = y2;
625: foundProp = elem;
626: }
627: }
628: } // for
629:
630: return foundProp;
631: }
632:
633: /**
634: * Paint the photo in the back, then layer all SVG images on top.
635: *
636: * @param g The graphics context for this canvas.
637: */
638: public void paint(Graphics g) {
639: int width = getWidth();
640: int height = getHeight();
641:
642: // Fill the background when a photo isn't present.
643: g.setColor(0x00ffffff);
644: g.fillRect(0, 0, width, height);
645:
646: // Draw the photo only when the user has taken one.
647: if (currentImage != null) {
648: g.drawImage(currentImage, width / 2, height / 2, ANCHOR);
649: }
650:
651: // Paint the current SVG image on top of everything.
652: if (svgImage != null) {
653: sg.bindTarget(g);
654: sg.render(0, 0, svgImage);
655: sg.releaseTarget();
656:
657: /*
658: * Draw the cursor. When the cursor is just moving around and has no
659: * prop attached, use a green triangle. When a prop is attached to
660: * the cursor, represent the cursor with a crosshair.
661: *
662: * When the cursor is simply moving around, give the user a hint as
663: * to which prop can be picked up by highlighting the prop's
664: * bounding box.
665: */
666: if (prop == null) {
667: // First, try to locate the prop that contains the cursor.
668: SVGLocatableElement elem = getPropContaining(
669: (float) cursorX, (float) cursorY);
670:
671: // If the prop could be found, show its bounding box.
672: if (elem != null) {
673: SVGRect r = elem.getScreenBBox();
674: int rx = (int) r.getX();
675: int ry = (int) r.getY();
676: int rwidth = (int) r.getWidth();
677: int rheight = (int) r.getHeight();
678:
679: // Draw the bounding box in red.
680: g.setColor(0x00FF0000);
681: g.drawRect(rx, ry, rwidth, rheight);
682: }
683:
684: // No prop is being manipulated; Use a green triangle cursor.
685: g.setColor(0x0000CC00);
686: g.fillTriangle(cursorX, cursorY, cursorX + 4,
687: cursorY + 12, cursorX - 4, cursorY + 12);
688: } else {
689: // Prop is being manipulated. Use a red crosshair cursor.
690: g.setColor(0x00cc0000);
691: g.drawLine(cursorX - 5, cursorY, cursorX - 1, cursorY);
692: g.drawLine(cursorX + 1, cursorY, cursorX + 5, cursorY);
693: g.drawLine(cursorX, cursorY - 5, cursorX, cursorY - 1);
694: g.drawLine(cursorX, cursorY + 1, cursorX, cursorY + 5);
695: }
696: } // if there's an SVG props file available.
697: }
698: }
|