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 example.photoalbum;
033:
034: import java.io.DataInputStream;
035: import java.io.IOException;
036:
037: import java.util.Vector;
038:
039: import javax.microedition.io.Connector;
040: import javax.microedition.io.ContentConnection;
041: import javax.microedition.io.HttpConnection;
042: import javax.microedition.lcdui.*;
043: import javax.microedition.midlet.*;
044: import javax.microedition.rms.*;
045:
046: /**
047: * The PhotoAlbum MIDlet provides the commands and screens
048: * that implement a simple photograph and animation album.
049: * The images and animations to be displayed are configured
050: * in the descriptor file with attributes.
051: * <p>
052: * Options are provided to to vary the speed of display
053: * and the frame style.
054: *
055: */
056: public class PhotoAlbum extends MIDlet implements CommandListener,
057: ItemStateListener, Runnable {
058: /** The Command object for the Exit command */
059: private Command exitCommand;
060:
061: /** The Command object for the Ok command */
062: private Command okCommand;
063:
064: /** The Command object for the Options command */
065: private Command optionsCommand;
066:
067: /** The Command object for the Back command */
068: private Command backCommand;
069:
070: /** The Command object for the Cancel command */
071: private Command cancelCommand;
072:
073: /** The Form object for the Progress form */
074: private Form progressForm;
075:
076: /** The Gauge object for the Progress gauge */
077: private Gauge progressGauge;
078:
079: /** The Form object for the Options command */
080: private Form optionsForm;
081:
082: /** Set of choices for the border styles */
083: private ChoiceGroup borderChoice;
084:
085: /** Set of choices for the speeds */
086: private ChoiceGroup speedChoice;
087:
088: /** The current display for this MIDlet */
089: private Display display;
090:
091: /** The PhotoFrame that displays images */
092: private PhotoFrame frame;
093:
094: /** The Alert for messages */
095: private Alert alert;
096:
097: /** Contains Strings with the image names */
098: private Vector imageNames;
099:
100: /** List of Image titles for user to select */
101: private List imageList;
102:
103: /** Name of current image, may be null */
104: private String imageName;
105:
106: /** Current thread loading images, may be null */
107: private Thread thread;
108:
109: /** Name of persistent storage */
110: private final String optionsName = "PhotoAlbum";
111:
112: /** Persistent storage for options */
113: private RecordStore optionsStore;
114: private boolean firstTime = true;
115:
116: /**
117: * Construct a new PhotoAlbum MIDlet and initialize the base options
118: * and main PhotoFrame to be used when the MIDlet is started.
119: */
120: public PhotoAlbum() {
121: display = Display.getDisplay(this );
122: exitCommand = new Command("Exit", Command.EXIT, 1);
123: optionsCommand = new Command("Options", Command.SCREEN, 1);
124: okCommand = new Command("Ok", Command.OK, 3);
125: backCommand = new Command("Back", Command.BACK, 3);
126: cancelCommand = new Command("Cancel", Command.CANCEL, 1);
127:
128: frame = new PhotoFrame();
129: frame.setStyle(2);
130: frame.setSpeed(2);
131: frame.addCommand(optionsCommand);
132: frame.addCommand(backCommand);
133: frame.setCommandListener(this );
134: alert = new Alert("Warning");
135: setupImageList();
136: firstTime = true;
137: }
138:
139: /**
140: * Start up the Hello MIDlet by setting the PhotoFrame
141: * and loading the initial images.
142: */
143: protected void startApp() {
144: if (firstTime) {
145: if (imageList.size() > 0) {
146: display.setCurrent(imageList);
147: openOptions();
148: restoreOptions();
149: } else {
150: alert.setString("No images configured.");
151: display.setCurrent(alert, imageList);
152: }
153:
154: firstTime = false;
155: }
156:
157: openOptions();
158: restoreOptions();
159: }
160:
161: /**
162: * Pause is used to release the memory used by Image.
163: * When restarted the images will be re-created.
164: * Save the options for the next restart.
165: */
166: protected void pauseApp() {
167: saveOptions();
168: }
169:
170: /**
171: * Destroy must cleanup everything not handled by the garbage collector.
172: * In this case there is nothing to cleanup.
173: * Save the options for the next restart.
174: * @param unconditional true if this MIDlet should always cleanup
175: */
176: protected void destroyApp(boolean unconditional) {
177: saveOptions();
178: frame.reset(); // Discard images cached in the frame.
179: saveOptions();
180: closeOptions();
181: }
182:
183: /**
184: * Respond to commands. Commands are added to each screen as
185: * they are created. Each screen uses the PhotoAlbum MIDlet as the
186: * CommandListener. All commands are handled here:
187: * <UL>
188: * <LI>Select on Image List - display the progress form and start the thread
189: * to read in the images.
190: * <LI>Options - display the options form.
191: * <LI>Ok on the Options form - returns to the PhotoFrame.
192: * <LI>Back - display the Image List, deactivating the PhotoFrame.
193: * <LI>Cancel - display the image List and stop the thread loading images.
194: * <LI>Exit - images are released and notification is given that the MIDlet
195: * has exited.
196: * </UL>
197: * @param c the command that triggered this callback
198: * @param s the screen that contained the command
199: */
200: public void commandAction(Command c, Displayable s) {
201: if (c == exitCommand) {
202: // Cleanup and notify that the MIDlet has exited
203: destroyApp(false);
204: notifyDestroyed();
205: } else if (c == optionsCommand) {
206: // Display the options form
207: display.setCurrent(genOptions());
208: } else if ((c == okCommand) && (s == optionsForm)) {
209: // Return to the PhotoFrame, the option values have already
210: // been saved by the item state listener
211: display.setCurrent(frame);
212: } else if (c == List.SELECT_COMMAND) {
213: // Display the progress screen and
214: // start the thread to read the images
215: int i = imageList.getSelectedIndex();
216: imageName = (String) imageNames.elementAt(i);
217: display.setCurrent(genProgress(imageList.getString(i)));
218: thread = new Thread(this );
219: thread.start();
220: } else if (c == backCommand) {
221: // Display the list of images.
222: display.setCurrent(imageList);
223: } else if (c == cancelCommand) {
224: // Signal thread to stop and put an alert.
225: thread = null;
226: alert.setString("Loading images cancelled.");
227: display.setCurrent(alert, imageList);
228: }
229: }
230:
231: /**
232: * Listener for changes to options.
233: * The new values are set in the PhotoFrame.
234: * @param item - the item whose value has changed.
235: */
236: public void itemStateChanged(Item item) {
237: if (item == borderChoice) {
238: frame.setStyle(borderChoice.getSelectedIndex());
239: } else if (item == speedChoice) {
240: frame.setSpeed(speedChoice.getSelectedIndex());
241: }
242: }
243:
244: /**
245: * Generate the options form with speed and style choices.
246: * Speed choices are stop, slow, medium, and fast.
247: * Style choices for borders are none, plain, fancy.
248: * @return the generated options Screen
249: */
250: private Screen genOptions() {
251: if (optionsForm == null) {
252: optionsForm = new Form("Options");
253: optionsForm.addCommand(okCommand);
254: optionsForm.setCommandListener(this );
255: optionsForm.setItemStateListener(this );
256:
257: speedChoice = new ChoiceGroup("Speed", Choice.EXCLUSIVE);
258: speedChoice.append("Stop", null);
259: speedChoice.append("Slow", null);
260: speedChoice.append("Medium", null);
261: speedChoice.append("Fast", null);
262: speedChoice.append("Unlimited", null);
263: speedChoice.setSelectedIndex(frame.getSpeed(), true);
264: optionsForm.append(speedChoice);
265:
266: borderChoice = new ChoiceGroup("Borders", Choice.EXCLUSIVE);
267: borderChoice.append("None", null);
268: borderChoice.append("Plain", null);
269: borderChoice.append("Fancy", null);
270: borderChoice.setSelectedIndex(frame.getStyle(), true);
271: optionsForm.append(borderChoice);
272: }
273:
274: return optionsForm;
275: }
276:
277: /**
278: * Generate the options form with image title and progress gauge.
279: * @param name the title of the Image to be loaded.
280: * @return the generated progress screen
281: */
282: private Screen genProgress(String name) {
283: if (progressForm == null) {
284: progressForm = new Form(name);
285: progressForm.addCommand(cancelCommand);
286: progressForm.setCommandListener(this );
287:
288: progressGauge = new javax.microedition.lcdui.Gauge(
289: "Loading images...", false, 9, 0);
290:
291: progressForm.append(progressGauge);
292: } else {
293: progressGauge.setValue(0);
294: progressForm.setTitle(name);
295: }
296:
297: return progressForm;
298: }
299:
300: /**
301: * Check the attributes in the descriptor that identify
302: * images and titles and initialize the lists of imageNames
303: * and imageList.
304: * <P>
305: * The attributes are named "PhotoTitle-n" and "PhotoImage-n".
306: * The value "n" must start at "1" and increment by 1.
307: */
308: private void setupImageList() {
309: imageNames = new Vector();
310: imageList = new List("Images", List.IMPLICIT);
311: imageList.addCommand(exitCommand);
312: imageList.setCommandListener(this );
313:
314: for (int n = 1; n < 100; n++) {
315: String nthImage = "PhotoImage-" + n;
316: String image = getAppProperty(nthImage);
317:
318: if ((image == null) || (image.length() == 0)) {
319: break;
320: }
321:
322: String nthTitle = "PhotoTitle-" + n;
323: String title = getAppProperty(nthTitle);
324:
325: if ((title == null) || (title.length() == 0)) {
326: title = image;
327: }
328:
329: imageNames.addElement(image);
330: imageList.append(title, null);
331: }
332:
333: imageNames.addElement("testchart:");
334: imageList.append("Test Chart", null);
335: }
336:
337: /**
338: * The Run method is used to load the images.
339: * A form is used to report the progress of loading images
340: * and when the loading is complete they are displayed.
341: * Any errors that occur are reported using an Alert.
342: * Images previously loaded into the PhotoFrame are discarded
343: * before loading.
344: * <P>
345: * Load images from resource files using <code>Image.createImage</code>.
346: * Images may be in resource files or accessed using http:
347: * The first image is loaded to determine whether it is a
348: * single image or a sequence of images and to make sure it exists.
349: * If the name given is the complete name of the image then
350: * it is a singleton.
351: * Otherwise it is assumed to be a sequence of images
352: * with the name as a prefix. Sequence numbers (n) are
353: * 0, 1, 2, 3, .... The full resource name is the concatenation
354: * of name + n + ".png".
355: * <p>
356: * If an OutOfMemoryError occurs the sequence of images is truncated
357: * and an alert is used to inform the user. The images loaded are
358: * displayed.
359: * @see createImage
360: */
361: public void run() {
362: Thread mythread = Thread.currentThread();
363: Vector images = new Vector(5);
364: /* Free images and resources used by current frame. */
365: frame.reset();
366:
367: try { // Catch OutOfMemory Errors
368:
369: try {
370: if (imageName.startsWith("testchart:")) {
371: TestChart t = new TestChart(frame.getWidth(), frame
372: .getHeight());
373: images = t.generateImages();
374: } else {
375: // Try the name supplied for the single image case.
376: images.addElement(createImage(imageName));
377: }
378: } catch (IOException ex) {
379: try {
380: int namelen = imageName.length();
381: StringBuffer buf = new StringBuffer(namelen + 8);
382: buf.append(imageName);
383:
384: Runtime rt = Runtime.getRuntime();
385:
386: // Try for a sequence of images.
387: for (int i = 0;; i++) {
388: progressGauge.setValue(i % 10);
389:
390: // If cancelled, discard images and return immediately
391: if (thread != mythread) {
392: break;
393: }
394:
395: // locate the next in the series of images.
396: buf.setLength(namelen);
397: buf.append(i);
398: buf.append(".png");
399:
400: String name = buf.toString();
401: images.addElement(createImage(name));
402: }
403: } catch (IOException io_ex) {
404: }
405: } catch (SecurityException se_ex) {
406: // no-retry, just put up the alert
407: }
408:
409: // If cancelled, discard images and return immediately
410: if (thread != mythread) {
411: return;
412: }
413:
414: // If any images, setup the images and display them.
415: if (images.size() > 0) {
416: frame.setImages(images);
417: display.setCurrent(frame);
418: } else {
419: // Put up an alert saying image cannot be loaded
420: alert.setString("Images could not be loaded.");
421: display.setCurrent(alert, imageList);
422: }
423: } catch (OutOfMemoryError err) {
424: int size = images.size();
425:
426: if (size > 0) {
427: images.setSize(size - 1);
428: }
429:
430: // If cancelled, discard images and return immediately
431: if (thread != mythread) {
432: return;
433: }
434:
435: alert.setString("Not enough memory for all images.");
436:
437: // If no images are loaded, Alert and return to the list
438: // Otherwise, Alert and display the ones that were loaded.
439: if (images.size() <= 0) {
440: display.setCurrent(alert, imageList);
441: } else {
442: frame.setImages(images);
443: display.setCurrent(alert, frame);
444: }
445: }
446: }
447:
448: /**
449: * Fetch the image. If the name begins with "http:"
450: * fetch it with connector.open and http.
451: * If it starts with "/" then load it from the
452: * resource file.
453: * @param name of the image to load
454: * @return image created
455: * @exception IOException if errors occur during image loading
456: */
457: private Image createImage(String name) throws IOException {
458: if (name.startsWith("/")) {
459: // Load as a resource with Image.createImage
460: return Image.createImage(name);
461: } else if (name.startsWith("http:")) {
462: // Load from a ContentConnection
463: HttpConnection c = null;
464: DataInputStream is = null;
465:
466: try {
467: c = (HttpConnection) Connector.open(name);
468:
469: int status = c.getResponseCode();
470:
471: if (status != 200) {
472: throw new IOException("HTTP Response Code = "
473: + status);
474: }
475:
476: int len = (int) c.getLength();
477: String type = c.getType();
478:
479: if (!type.equals("image/png")
480: && !type.equals("image/jpeg")) {
481: throw new IOException("Expecting image, received "
482: + type);
483: }
484:
485: if (len > 0) {
486: is = c.openDataInputStream();
487:
488: byte[] data = new byte[len];
489: is.readFully(data);
490:
491: return Image.createImage(data, 0, len);
492: } else {
493: throw new IOException("Content length is missing");
494: }
495: } finally {
496: if (is != null) {
497: is.close();
498: }
499:
500: if (c != null) {
501: c.close();
502: }
503: }
504: } else {
505: throw new IOException("Unsupported media");
506: }
507: }
508:
509: /**
510: * Open the store that holds the saved options.
511: * If an error occurs, put up an Alert.
512: */
513: void openOptions() {
514: try {
515: optionsStore = RecordStore.openRecordStore(optionsName,
516: true);
517: } catch (RecordStoreException ex) {
518: alert.setString("Could not access options storage");
519: display.setCurrent(alert);
520: optionsStore = null;
521: }
522: }
523:
524: /**
525: * Save the options to persistent storage.
526: * The options are retrieved ChoiceGroups and stored
527: * in Record 1 of the store which is reserved for it.
528: * The two options are stored in bytes 0 and 1 of the record.
529: */
530: void saveOptions() {
531: if (optionsStore != null) {
532: byte[] options = new byte[2];
533: options[0] = (byte) frame.getStyle();
534: options[1] = (byte) frame.getSpeed();
535:
536: try {
537: optionsStore.setRecord(1, options, 0, options.length);
538: } catch (InvalidRecordIDException ridex) {
539: // Record 1 did not exist, create a new record (Should be 1)
540: try {
541: int rec = optionsStore.addRecord(options, 0,
542: options.length);
543: } catch (RecordStoreException ex) {
544: alert.setString("Could not add options record");
545: display.setCurrent(alert);
546: }
547: } catch (RecordStoreException ex) {
548: alert.setString("Could not save options");
549: display.setCurrent(alert);
550: }
551: }
552: }
553:
554: /**
555: * Restore the options from persistent storage.
556: * The options are read from record 1 and set in
557: * the frame and if the optionsForm has been created
558: * in the respective ChoiceGroups.
559: */
560: void restoreOptions() {
561: if (optionsStore != null) {
562: try {
563: byte[] options = optionsStore.getRecord(1);
564:
565: if (options.length == 2) {
566: frame.setStyle(options[0]);
567: frame.setSpeed(options[1]);
568:
569: if (optionsForm != null) {
570: borderChoice.setSelectedIndex(options[0], true);
571: speedChoice.setSelectedIndex(options[1], true);
572: }
573:
574: return; // Return all set
575: }
576: } catch (RecordStoreException ex) {
577: // Ignore, use normal defaults
578: }
579: }
580: }
581:
582: /**
583: * Close the options store.
584: */
585: void closeOptions() {
586: if (optionsStore != null) {
587: try {
588: optionsStore.closeRecordStore();
589: optionsStore = null;
590: } catch (RecordStoreException ex) {
591: alert.setString("Could not close options storage");
592: display.setCurrent(alert);
593: }
594: }
595: }
596: }
|