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.svgbrowser;
033:
034: import java.io.IOException;
035: import java.io.InputStream;
036:
037: import java.util.Enumeration;
038: import java.util.Vector;
039:
040: import javax.microedition.io.Connector;
041: import javax.microedition.io.file.FileConnection;
042: import javax.microedition.io.file.FileSystemRegistry;
043: import javax.microedition.lcdui.Alert;
044: import javax.microedition.lcdui.AlertType;
045: import javax.microedition.lcdui.Canvas;
046: import javax.microedition.lcdui.Choice;
047: import javax.microedition.lcdui.Command;
048: import javax.microedition.lcdui.CommandListener;
049: import javax.microedition.lcdui.Display;
050: import javax.microedition.lcdui.Displayable;
051: import javax.microedition.lcdui.List;
052: import javax.microedition.m2g.SVGAnimator;
053: import javax.microedition.m2g.SVGEventListener;
054: import javax.microedition.m2g.SVGImage;
055: import javax.microedition.m2g.ScalableImage;
056: import javax.microedition.midlet.MIDlet;
057:
058: import org.w3c.dom.Document;
059: import org.w3c.dom.svg.SVGSVGElement;
060:
061: /**
062: * This demo implements a simple file browser which allows opening SVG images
063: * and animations.
064: */
065: public final class SVGBrowser extends MIDlet implements
066: CommandListener, SVGEventListener {
067: /*
068: * Animation state constants.
069: */
070:
071: /** State representing stopped animation. */
072: private static final int STATE_STOPPED = 0;
073:
074: /** State representing paused animation. */
075: private static final int STATE_PAUSED = 1;
076:
077: /** State representing running animation. */
078: private static final int STATE_PLAYING = 2;
079:
080: /** Command for exiting the MIDlet. */
081: private final Command exitCommand;
082:
083: /** Command for returning from a displayed SVG image. */
084: private final Command backCommand;
085:
086: /** Command for entering directory or opening SVG file. */
087: private final Command openCommand;
088:
089: /** Command for starting a SVG animation. */
090: private final Command playCommand;
091:
092: /** Command for pausing a SVG animation. */
093: private final Command pauseCommand;
094:
095: /** Command for stopping a SVG animation. */
096: private final Command stopCommand;
097:
098: /** Screen with directory / SVG file listing. */
099: private final List browserList;
100:
101: /** The loaded SVG image. */
102: private SVGImage svgImage;
103:
104: /** The root <svg> element of the image. */
105: private SVGSVGElement svgRoot;
106:
107: /** The animator created for the image. */
108: private SVGAnimator svgAnimator;
109:
110: /** The canvas from the animator. */
111: private Canvas svgCanvas;
112:
113: /** The current animation state. */
114: private int animationState = STATE_STOPPED;
115:
116: /** The next animation state. */
117: private int animationNextState = STATE_STOPPED;
118:
119: /** The display assigned for the MIDlet. */
120: private final Display display;
121:
122: /** The parent path of the current path. */
123: private String parentPath;
124:
125: /** The browsing depth. Value 0 means a virtual root of all filesystems. */
126: private int browsingDepth;
127:
128: /** The current path. */
129: private String currentPath;
130:
131: /** Creates a new instance of SVGBrowser. */
132: public SVGBrowser() {
133: browserList = new List(null, Choice.IMPLICIT);
134:
135: exitCommand = new Command("Exit", Command.EXIT, 1);
136: backCommand = new Command("Back", Command.BACK, 1);
137: openCommand = new Command("Open", Command.OK, 1);
138:
139: playCommand = new Command("Play", Command.SCREEN, 1);
140: pauseCommand = new Command("Pause", Command.SCREEN, 1);
141: stopCommand = new Command("Stop", Command.SCREEN, 2);
142:
143: browserList.addCommand(exitCommand);
144: browserList.addCommand(openCommand);
145: browserList.setSelectCommand(openCommand);
146: browserList.setCommandListener(this );
147:
148: display = Display.getDisplay(this );
149: new BrowseAction("file:///", 0, null).start();
150: }
151:
152: protected void startApp() {
153: }
154:
155: protected void pauseApp() {
156: }
157:
158: protected synchronized void destroyApp(boolean unconditional) {
159: if ((svgAnimator != null) && (animationState != STATE_STOPPED)) {
160: svgAnimator.stop();
161: animationState = STATE_STOPPED;
162: animationNextState = STATE_STOPPED;
163: }
164: }
165:
166: public synchronized void commandAction(Command c, Displayable d) {
167: if ((c == exitCommand) || (c == Alert.DISMISS_COMMAND)) {
168: destroyApp(false);
169: notifyDestroyed();
170: } else if (c == openCommand) {
171: int selectedIdx = browserList.getSelectedIndex();
172:
173: if ((selectedIdx == 0) && (browsingDepth > 0)) {
174: new BrowseAction(parentPath, browsingDepth - 1,
175: browserList).start();
176: } else {
177: String selectedString = browserList
178: .getString(selectedIdx);
179:
180: if (selectedString.endsWith("/")) {
181: // open the selected directory
182: new BrowseAction(currentPath + selectedString,
183: browsingDepth + 1, browserList).start();
184: } else {
185: // open the selected svg file
186: new OpenAction(currentPath + selectedString,
187: browserList).start();
188: }
189: }
190: } else if (c == backCommand) {
191: display.setCurrent(browserList);
192: } else if (c == playCommand) {
193: play();
194: } else if (c == pauseCommand) {
195: pause();
196: } else if (c == stopCommand) {
197: stop();
198: }
199: }
200:
201: /**
202: * Opens browser for the given path. If an error happens while reading
203: * the directory content, an alert with the error message is displayed.
204: * After timeout this alert is dismissed automatically and the
205: * prevDisplayable is shown. If prevDisplayable is null and an error happens
206: * the MIDlet is ended after the alert timeouts.
207: *
208: * @param newPath the path to browse
209: * @param newDepth the browsing depth, 0 means "root"
210: * @param prevDisplayable the Displayable to be shown in the case of error
211: */
212: private void browse(String newPath, int newDepth,
213: Displayable prevDisplayable) {
214: try {
215: String newParentPath;
216: Vector directories = new Vector();
217: Vector svgFiles = new Vector();
218:
219: if (newDepth == 0) {
220: newParentPath = null;
221:
222: // get filesystems listing
223: Enumeration roots = FileSystemRegistry.listRoots();
224: if (!roots.hasMoreElements()) {
225: // this is fatal, no filesystems enumerated
226: handleError("No filesystems found", prevDisplayable);
227: return;
228: }
229:
230: do {
231: directories.addElement(roots.nextElement());
232: } while (roots.hasMoreElements());
233:
234: } else {
235: int slashIndex = newPath.lastIndexOf('/', newPath
236: .length() - 2);
237: // slashIndex != -1
238: newParentPath = newPath.substring(0, slashIndex + 1);
239:
240: // get directory listing
241: FileConnection fc = (FileConnection) Connector.open(
242: newPath, Connector.READ);
243: try {
244: Enumeration files = fc.list();
245:
246: while (files.hasMoreElements()) {
247: String fileName = (String) files.nextElement();
248:
249: if (fileName.endsWith("/")) {
250: directories.addElement(fileName);
251:
252: continue;
253: }
254:
255: int dotIndex = fileName.lastIndexOf('.');
256:
257: if (dotIndex != -1) {
258: String extension = fileName
259: .substring(dotIndex + 1);
260:
261: if ("svg".equalsIgnoreCase(extension)) {
262: svgFiles.addElement(fileName);
263: }
264: }
265: }
266: } finally {
267: fc.close();
268: }
269: }
270:
271: synchronized (this ) {
272: browserList.setTitle(newPath);
273: browserList.deleteAll();
274:
275: if (newDepth > 0) {
276: browserList.append("..", null);
277: }
278:
279: Enumeration e;
280: // add directories
281: e = directories.elements();
282:
283: while (e.hasMoreElements()) {
284: browserList.append((String) e.nextElement(), null);
285: }
286:
287: // add svg files
288: e = svgFiles.elements();
289:
290: while (e.hasMoreElements()) {
291: browserList.append((String) e.nextElement(), null);
292: }
293:
294: parentPath = newParentPath;
295: browsingDepth = newDepth;
296: currentPath = newPath;
297:
298: display.setCurrent(browserList);
299: }
300: } catch (IOException e) {
301: handleError("Failed to open " + newPath, prevDisplayable);
302: }
303: }
304:
305: /**
306: * Opens an SVG file and shows on the screen. If an error happens while
307: * opening the file, an alert with the error message is displayed.
308: * After timeout this alert is dismissed automatically and the
309: * prevDisplayable is shown. If prevDisplayable is null and an error happens
310: * the MIDlet is ended after the alert timeouts.
311: *
312: * @param svgPath the path to the SVG file
313: * @param prevDisplayable the Displayable to be shown in the case of error
314: */
315: private void open(String svgPath, Displayable prevDisplayable) {
316: try {
317: SVGImage newSVGImage;
318: FileConnection fc = (FileConnection) Connector.open(
319: svgPath, Connector.READ);
320:
321: try {
322: InputStream is = fc.openInputStream();
323:
324: try {
325: newSVGImage = (SVGImage) ScalableImage.createImage(
326: is, null);
327: } finally {
328: try {
329: is.close();
330: } catch (IOException e) {
331: // ignore
332: }
333: }
334: } finally {
335: fc.close();
336: }
337:
338: Document newSVGDocument = newSVGImage.getDocument();
339: SVGSVGElement newSVGRoot = (SVGSVGElement) newSVGDocument
340: .getDocumentElement();
341: SVGAnimator newSVGAnimator = SVGAnimator
342: .createAnimator(newSVGImage);
343:
344: synchronized (this ) {
345: if ((svgAnimator != null)
346: && (animationState != STATE_STOPPED)) {
347: svgAnimator.stop();
348: }
349:
350: svgImage = newSVGImage;
351: svgRoot = newSVGRoot;
352:
353: svgAnimator = newSVGAnimator;
354: // Set to 10 fps (frames per second)
355: svgAnimator.setTimeIncrement(0.01f);
356: svgAnimator.setSVGEventListener(this );
357:
358: svgCanvas = (Canvas) svgAnimator.getTargetComponent();
359: svgCanvas.setTitle(svgPath);
360: svgCanvas.addCommand(backCommand);
361: svgCanvas.setCommandListener(this );
362:
363: animationState = STATE_STOPPED;
364: animationNextState = STATE_PLAYING;
365:
366: display.setCurrent(svgCanvas);
367: }
368: } catch (IOException e) {
369: handleError("Failed to open " + svgPath, prevDisplayable);
370: }
371: }
372:
373: /**
374: * Handles error conditions. It shows an Alert with the error message.
375: * After the Alert timeouts the behaviour depends on the value of
376: * prevDisplayable. If prevDisplayable is not null it is displayed,
377: * otherwise the MIDlet exits.
378: *
379: * @param message the error message
380: * @param prevDisplayable the Displayable to be shown after the timeout
381: */
382: private void handleError(String message, Displayable prevDisplayable) {
383: if (prevDisplayable == null) {
384: errorExit(message);
385: } else {
386: warningMessage(message, prevDisplayable);
387: }
388: }
389:
390: /**
391: * Displays an alert with the given error message. When the alert timeouts
392: * the current MIDlet exits.
393: *
394: * @param message the error message
395: */
396: private void errorExit(String message) {
397: Alert errorAlert = new Alert("Fatal error", message, null,
398: AlertType.ERROR);
399: errorAlert.setCommandListener(this );
400:
401: synchronized (this ) {
402: display.setCurrent(errorAlert);
403: }
404: }
405:
406: /**
407: * Displays an alert with the given warning message. When the alert timeouts
408: * the given Displayable is displayed.
409: *
410: * @param message the warning message
411: * @param nextDisplayable the next Displayable to display
412: */
413: private void warningMessage(String message,
414: Displayable nextDisplayable) {
415: Alert warningAlert = new Alert("Warning", message, null,
416: AlertType.WARNING);
417:
418: synchronized (this ) {
419: display.setCurrent(warningAlert, nextDisplayable);
420: }
421: }
422:
423: public void keyPressed(int keyCode) {
424: }
425:
426: public void keyReleased(int keyCode) {
427: }
428:
429: public void pointerPressed(int x, int y) {
430: }
431:
432: public void pointerReleased(int x, int y) {
433: }
434:
435: public synchronized void hideNotify() {
436: if (animationState == STATE_PLAYING) {
437: svgAnimator.pause();
438: animationState = STATE_PAUSED;
439: animationNextState = STATE_PLAYING;
440: }
441: }
442:
443: public synchronized void showNotify() {
444: svgImage.setViewportWidth(svgCanvas.getWidth());
445: svgImage.setViewportHeight(svgCanvas.getHeight());
446:
447: if (animationState != animationNextState) {
448: switch (animationNextState) {
449: case STATE_PLAYING:
450: svgAnimator.play();
451:
452: break;
453:
454: case STATE_PAUSED:
455: svgAnimator.pause();
456:
457: break;
458:
459: case STATE_STOPPED:
460: svgAnimator.stop();
461:
462: break;
463: }
464:
465: animationState = animationNextState;
466: updateAnimatorCommands();
467: }
468: }
469:
470: /**
471: * Updates the SVG image viewport size when the canvas size changes.
472: */
473: public synchronized void sizeChanged(int width, int height) {
474: svgImage.setViewportWidth(width);
475: svgImage.setViewportHeight(height);
476: }
477:
478: /**
479: * Updates the available commands in the menu according to the current
480: * animation state.
481: */
482: private void updateAnimatorCommands() {
483: svgCanvas.removeCommand(playCommand);
484: svgCanvas.removeCommand(pauseCommand);
485: svgCanvas.removeCommand(stopCommand);
486:
487: switch (animationState) {
488: case STATE_PLAYING:
489: svgCanvas.addCommand(pauseCommand);
490: svgCanvas.addCommand(stopCommand);
491:
492: break;
493:
494: case STATE_PAUSED:
495: svgCanvas.addCommand(playCommand);
496: svgCanvas.addCommand(stopCommand);
497:
498: break;
499:
500: case STATE_STOPPED:
501: svgCanvas.addCommand(playCommand);
502:
503: break;
504: }
505: }
506:
507: /**
508: * This methods starts the current animation.
509: */
510: private void play() {
511: if ((animationState == animationNextState)
512: && (animationState != STATE_PLAYING)) {
513: svgAnimator.play();
514: animationState = STATE_PLAYING;
515: updateAnimatorCommands();
516: }
517:
518: animationNextState = STATE_PLAYING;
519: }
520:
521: /**
522: * This methods pauses the current animation.
523: */
524: private void pause() {
525: if ((animationState == animationNextState)
526: && (animationState != STATE_PAUSED)) {
527: svgAnimator.pause();
528: animationState = STATE_PAUSED;
529: updateAnimatorCommands();
530: }
531:
532: animationNextState = STATE_PAUSED;
533: }
534:
535: /**
536: * This methods stops the current animation.
537: */
538: private void stop() {
539: if ((animationState == animationNextState)
540: && (animationState != STATE_STOPPED)) {
541: svgAnimator.stop();
542: svgRoot.setCurrentTime(0);
543: animationState = STATE_STOPPED;
544: updateAnimatorCommands();
545: }
546:
547: animationNextState = STATE_STOPPED;
548: }
549:
550: private final class BrowseAction extends Thread {
551: private final String directory;
552: private final int depth;
553: private final Displayable prevDisplayable;
554:
555: public BrowseAction(String directory, int depth,
556: Displayable prevDisplayable) {
557: this .directory = directory;
558: this .depth = depth;
559: this .prevDisplayable = prevDisplayable;
560: }
561:
562: public void run() {
563: browse(directory, depth, prevDisplayable);
564: }
565: }
566:
567: private final class OpenAction extends Thread {
568: private final String svgFile;
569: private final Displayable prevDisplayable;
570:
571: public OpenAction(String svgFile, Displayable prevDisplayable) {
572: this .svgFile = svgFile;
573: this .prevDisplayable = prevDisplayable;
574: }
575:
576: public void run() {
577: open(svgFile, prevDisplayable);
578: }
579: }
580: }
|