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 javax.microedition.lcdui;
028:
029: import com.sun.midp.i18n.Resource;
030: import com.sun.midp.i18n.ResourceConstants;
031: import com.sun.midp.util.ResourceHandler;
032:
033: import java.util.Timer;
034: import java.util.TimerTask;
035:
036: import com.sun.midp.security.SecurityToken;
037: import com.sun.midp.security.SecurityInitializer;
038: import com.sun.midp.security.ImplicitlyTrustedClass;
039:
040: /**
041: * Look & Feel implementation of <code>Alert</code> based on
042: * platform widget.
043: */
044: class AlertLFImpl extends DisplayableLFImpl implements AlertLF {
045:
046: /**
047: * Creates an <code>AlertLF</code> for the passed in <code>Alert</code>
048: * instance.
049: * @param a The <code>Alert</code> associated with this look & feel
050: */
051: AlertLFImpl(Alert a) {
052: super (a);
053: alert = a;
054: }
055:
056: // ************************************************************
057: // public methods - AlertLF interface implementation
058: // ************************************************************
059:
060: /**
061: * Determines if <code>Alert</code> associated with this view is modal.
062: *
063: * @return true if this <code>AlertLF</code> should be displayed as modal
064: */
065: public boolean lIsModal() {
066: if (alert.numCommands > 1) {
067: return true;
068: }
069:
070: if (isContentScroll < 0) {
071: layout();
072: }
073:
074: return (isContentScroll == 1);
075: }
076:
077: /**
078: * Gets default timeout for the <code>Alert</code> associated with
079: * this view.
080: *
081: * @return the default timeout
082: */
083: public int lGetDefaultTimeout() {
084: return DEFAULT_TIMEOUT;
085: }
086:
087: /**
088: * Return the command that should be mapped to
089: * <code>Alert.DISMISS_COMMAND</code>.
090: *
091: * @return command that maps to <code>Alert.DISMISS_COMMAND</code>
092: */
093: public Command lGetDismissCommand() {
094: return DISMISS_COMMAND;
095: }
096:
097: /**
098: * Notifies timeout change.
099: * Changing timeout on an already visible <code>Alert</code> will
100: * restart the timer, but has no effect on current layout.
101: *
102: * @param timeout the new timeout set in the corresponding
103: * <code>Alert</code>.
104: */
105: public void lSetTimeout(int timeout) {
106: if (timerTask != null) {
107: try {
108: timerTask.cancel();
109: if (timeout == Alert.FOREVER) {
110: timerTask = null;
111: } else {
112: timerTask = new TimeoutTask();
113: timeoutTimer.schedule(timerTask, timeout);
114: }
115: } catch (Throwable t) {
116: }
117: }
118: }
119:
120: /**
121: * Notifies <code>Alert</code> type change.
122: * Changing type on an already visible <code>Alert</code> will only
123: * update the default icon. No sound will be played.
124: *
125: * @param type the new <code>AlertType</code> set in the
126: * corresponding <code>Alert</code>.
127: */
128: public void lSetType(AlertType type) {
129: lRequestInvalidate();
130: }
131:
132: /**
133: * Notifies string change.
134: *
135: * @param oldString the old string set in the corresponding
136: * <code>Alert</code>.
137: * @param newString the new string set in the corresponding
138: * <code>Alert</code>.
139: */
140: public void lSetString(String oldString, String newString) {
141: lRequestInvalidate();
142: }
143:
144: /**
145: * Notifies image change.
146: *
147: * @param oldImg the old image set in the corresponding
148: * <code>Alert</code>.
149: * @param newImg the new image set in the corresponding
150: * <code>Alert</code>.
151: */
152: public void lSetImage(Image oldImg, Image newImg) {
153: lRequestInvalidate();
154: }
155:
156: /**
157: * Notifies indicator change.
158: *
159: * @param oldIndicator the old indicator set in the corresponding
160: * <code>Alert</code>.
161: * @param newIndicator the new indicator set in the corresponding
162: * <code>Alert</code>.
163: */
164: public void lSetIndicator(Gauge oldIndicator, Gauge newIndicator) {
165: lRequestInvalidate();
166: }
167:
168: /**
169: * Notify this <code>Alert</code> that it is being displayed.
170: * Override the version in <code>DisplayableLFImpl</code>.
171: */
172: void lCallShow() {
173:
174: // Create native resource with title and ticker
175: super .lCallShow();
176:
177: // Play sound
178: if (alert.type != null) {
179: currentDisplay.playAlertSound(alert.type);
180: }
181:
182: // Setup contained items and show them
183: showContents();
184:
185: // Show the Alert dialog window
186: showNativeResource0(nativeId);
187:
188: // Start Java timer
189: // If native dialog will cause VM to freeze, this timer
190: // needs to be moved to native.
191: if (alert.time != Alert.FOREVER && alert.numCommands == 1
192: && isContentScroll == 0) {
193:
194: if (timeoutTimer == null) {
195: timeoutTimer = new Timer();
196: }
197: timerTask = new TimeoutTask();
198: timeoutTimer.schedule(timerTask, alert.time);
199: }
200: }
201:
202: /**
203: * Notify this <code>Alert</code> that it will no longer be displayed.
204: * Override the version in <code>DisplayableLFImpl</code>.
205: */
206: void lCallHide() {
207:
208: // Stop the timer
209: if (timerTask != null) {
210: try {
211: timerTask.cancel();
212: timerTask = null;
213: } catch (Throwable t) {
214: }
215: }
216:
217: // Hide and delete gauge resource
218: if (alert.indicator != null) {
219: GaugeLFImpl gaugeLF = (GaugeLFImpl) alert.indicator.gaugeLF;
220:
221: gaugeLF.lHideNativeResource();
222:
223: gaugeLF.deleteNativeResource();
224:
225: if (gaugeLF.visibleInViewport) {
226: gaugeLF.lCallHideNotify();
227: }
228: }
229:
230: // Hide and delete alert dialog window including title and ticker
231: super .lCallHide();
232: }
233:
234: /**
235: * Called by the event handler to perform a re-layout
236: * on this <code>AlertLF</code>.
237: */
238: public void uCallInvalidate() {
239: synchronized (Display.LCDUILock) {
240: showContents();
241: }
242: }
243:
244: /**
245: * Notify return screen about screen size change
246: */
247: public void uCallSizeChanged(int w, int h) {
248: super .uCallSizeChanged(w, h);
249: Displayable returnScreen = alert.getReturnScreen();
250: if (returnScreen != null) {
251: (returnScreen.displayableLF).uCallSizeChanged(w, h);
252: }
253: }
254:
255: // *****************************************************
256: // Package private methods
257: // *****************************************************
258:
259: /**
260: * Called upon content change to schedule a request for relayout and
261: * repaint.
262: */
263: void lRequestInvalidate() {
264: super .lRequestInvalidate();
265: isContentScroll = -1; // Unknown scrolling state
266: }
267:
268: // *****************************************************
269: // Private methods
270: // *****************************************************
271:
272: /**
273: * Layout the content of this <code>Alert</code>.
274: * Query native resource for two informations:
275: * - Whether the content needs scrolling, in 'isContentScroll'
276: * - Location of the gauge indicator
277: *
278: * SYNC NOTE: Caller of this function should hold LCDUILock around
279: * this call.
280: */
281: private void layout() {
282:
283: boolean wasNoNative = (nativeId == INVALID_NATIVE_ID);
284:
285: // If no native resource yet, create it temporarily
286: if (wasNoNative) {
287: createNativeResource();
288: }
289:
290: Image img = alert.image;
291:
292: // If no image is specified, default icon for that type should be used
293: if (img == null && alert.type != null) {
294: img = getAlertImage(alert.type);
295: }
296:
297: // Bounds array of gauge
298: // The reason gauge bounds is passed back from native is to be
299: // consistent with Form's Java layout code.
300: int[] gaugeBounds;
301: GaugeLFImpl gaugeLF;
302:
303: if (alert.indicator == null) {
304: gaugeLF = null;
305: gaugeBounds = null;
306: } else {
307: // We temporarily use bounds array in gauge
308: // The real values will be set later by setSize() and setLocation()
309: gaugeLF = (GaugeLFImpl) alert.indicator.gaugeLF;
310: gaugeBounds = new int[4];
311:
312: // Pass gauge's preferred size to native layout code
313: gaugeBounds[WIDTH] = gaugeLF.lGetPreferredWidth(-1);
314: gaugeBounds[HEIGHT] = gaugeLF.lGetPreferredHeight(-1);
315: }
316:
317: ImageData imageData = null;
318:
319: if (img != null) {
320: imageData = img.getImageData();
321: }
322:
323: // Set content to native dialog and get layout information back
324: if (setNativeContents0(nativeId, imageData, gaugeBounds,
325: alert.text)) {
326: isContentScroll = 1; // scrolling needed
327: } else {
328: isContentScroll = 0; // no scrolling
329: }
330:
331: // Set gauge location and size based on return from native layout code
332: if (gaugeBounds != null) {
333: gaugeLF.lSetSize(gaugeBounds[WIDTH], gaugeBounds[HEIGHT]);
334: gaugeLF.lSetLocation(gaugeBounds[X], gaugeBounds[Y]);
335: }
336:
337: // Native resource should only be kept alive if it's visible
338: // Free temporarily created native resource here
339: if (wasNoNative) {
340: deleteNativeResource();
341: }
342: }
343:
344: /**
345: * Show or update contents on a visible <code>Alert</code>.
346: *
347: * SYNC NOTE: Caller must hold LCDUILock around this call.
348: */
349: private void showContents() {
350:
351: // Make sure gauge has native resource ready
352: GaugeLFImpl gaugeLF = (alert.indicator == null) ? null
353: : (GaugeLFImpl) alert.indicator.gaugeLF;
354:
355: if (gaugeLF != null && gaugeLF.nativeId == INVALID_NATIVE_ID) {
356: gaugeLF.createNativeResource(nativeId);
357: }
358:
359: // Re-populate the alert with updated contents
360: layout();
361:
362: // Make sure gauge is shown
363: if (gaugeLF != null) {
364: gaugeLF.lShowNativeResource();
365:
366: // SYNC NOTE: Since Gauge show and showNotify does not involve
367: // application code, we can call it while holding LCDUILock
368: gaugeLF.lCallShowNotify();
369:
370: // IMPLEMENTATION NOTE: when gauge is present in the Alert
371: // its visibleInViewport will always be set to true.
372: // If dynamic update of gauge's visibleInViewport flag is
373: // required in AlertLFImpl
374: // uViewportChanged() can be moved up from FormLFImpl to
375: // DisplayableLFImpl
376: }
377: }
378:
379: /**
380: * Create native resource for this <code>Alert</code>.
381: * <code>Gauge</code> resource will not be created.
382: */
383: void createNativeResource() {
384:
385: nativeId = createNativeResource0(alert.title,
386: alert.ticker == null ? null : alert.ticker.getString(),
387: alert.type == null ? 0 : alert.type.getType());
388: }
389:
390: /**
391: * Create native dialog with image and text widget for this
392: * <code>Alert</code>.
393: *
394: * @param title the title being passed to native
395: * @param tickerText text to be displayed on the <code>Ticker</code>
396: * @param type the type of <code>Alert</code>
397: * @return native resource id
398: */
399: private native int createNativeResource0(String title,
400: String tickerText, int type);
401:
402: /**
403: * (Re)Show native dialog with image and text widget for this
404: * <code>Alert<code>.
405: *
406: * @param nativeId native resource id
407: */
408: private native void showNativeResource0(int nativeId);
409:
410: /**
411: * Set content to native dialog.
412: *
413: * @param nativeId IN this alert's resource id (MidpDisplayable *)
414: * @param imgId IN icon image native id. 0 if no image.
415: * @param indicatorBounds a 4 integer array for indicator gauge
416: * [0] : OUT x coordinate in alert dialog
417: * [1] : OUT y coordinate in alert dialog
418: * [2] : IN/OUT width of the gauge, in pixels
419: * [3] : IN/OUT height of the gauge, in pixels
420: * null if no indicator gauge present.
421: * @param text IN alert text string
422: * @return <code>true</code> if content requires scrolling
423: */
424: private native boolean setNativeContents0(int nativeId,
425: ImageData imgId, int[] indicatorBounds, String text);
426:
427: /**
428: * Get the corresponding image for a given alert type.
429: *
430: * @param alertType type defined in <code>AlertType</code>
431: * @return image object to be displayed. Null if type is invalid.
432: */
433: private Image getAlertImage(AlertType alertType) {
434: if (alertType != null) {
435: if (alertType.equals(AlertType.INFO)) {
436: if (ALERT_INFO == null) {
437: ALERT_INFO = getSystemImage("alert.image_icon_info");
438: }
439: return ALERT_INFO;
440: } else if (alertType.equals(AlertType.WARNING)) {
441: if (ALERT_WARN == null) {
442: ALERT_WARN = getSystemImage("alert.image_icon_warn");
443: }
444: return ALERT_WARN;
445: } else if (alertType.equals(AlertType.ERROR)) {
446: if (ALERT_ERR == null) {
447: ALERT_ERR = getSystemImage("alert.image_icon_errr");
448: }
449: return ALERT_ERR;
450: } else if (alertType.equals(AlertType.ALARM)) {
451: if (ALERT_ALRM == null) {
452: ALERT_ALRM = getSystemImage("alert.image_icon_alrm");
453: }
454: return ALERT_ALRM;
455: } else if (alertType.equals(AlertType.CONFIRMATION)) {
456: if (ALERT_CFM == null) {
457: ALERT_CFM = getSystemImage("alert.image_icon_cnfm");
458: }
459: return ALERT_CFM;
460: }
461: }
462:
463: return null;
464: }
465:
466: /**
467: * Obtain system image resource and create Image object from it.
468: *
469: * @param imageName image name
470: * @return icon image
471: */
472: private Image getSystemImage(String imageName) {
473: byte[] imageData = ResourceHandler.getSystemImageResource(
474: classSecurityToken, imageName);
475: if (imageData != null) {
476: return Image.createImage(imageData, 0, imageData.length);
477: } else {
478: // Use a empty immutable image as placeholder
479: return Image.createImage(Image.createImage(16, 16));
480: }
481: }
482:
483: // *****************************************************
484: // Private members
485: // *****************************************************
486:
487: /**
488: * Inner class to request security token from SecurityInitializer.
489: * SecurityInitializer should be able to check this inner class name.
490: */
491: static private class SecurityTrusted implements
492: ImplicitlyTrustedClass {
493: };
494:
495: /** Security token to allow access to implementation APIs */
496: private static SecurityToken classSecurityToken = SecurityInitializer
497: .requestToken(new SecurityTrusted());
498:
499: /**
500: * Internal command used to visually represent
501: * <code>Alert.DISMISS_COMMAND</code>.
502: */
503: private static final Command DISMISS_COMMAND = new Command(Resource
504: .getString(ResourceConstants.DONE), Command.CANCEL, 0);
505:
506: /**
507: * The default timeout of all alerts.
508: */
509: private static final int DEFAULT_TIMEOUT = 2000;
510:
511: /**
512: * A <code>Timer</code> which serves all <code>Alert</code> objects
513: * to schedule their timeout tasks.
514: */
515: private static Timer timeoutTimer;
516:
517: /**
518: * <code>Alert</code> associated with this view.
519: */
520: private Alert alert;
521:
522: /**
523: * A <code>TimerTask</code> which will be set to expire this
524: * <code>Alert</code> after its timeout period has elapsed.
525: */
526: private TimerTask timerTask;
527:
528: /**
529: * A flag that indicates whether the content of the alert
530: * needs scrolling.
531: * Valid values are: -1: unknown, 0: no scrolling, 1: scrolling needed.
532: */
533: private int isContentScroll = -1; // Default is unknown
534:
535: /**
536: * An image to be drawn in <code>Alert</code> when it was
537: * created with AlertType ALARM.
538: */
539: private static Image ALERT_ALRM; // = null
540:
541: /**
542: * An image to be drawn in <code>Alert</code> when it was
543: * created with AlertType CONFIRMATION..
544: */
545: private static Image ALERT_CFM; // = null
546:
547: /**
548: * An image to be drawn in <code>Alert</code> when it was
549: * created with AlertType ERROR.
550: */
551: private static Image ALERT_ERR; // = null
552:
553: /**
554: * An image to be drawn in <code>Alert</code> when it was
555: * created with AlertType INFO.
556: */
557: private static Image ALERT_INFO; // = null
558:
559: /**
560: * An image to be drawn in <code>Alert</code> when it was
561: * created with AlertType WARNING.
562: */
563: private static Image ALERT_WARN; // = null
564:
565: // *****************************************************
566: // Inner Class for timed dismiss
567: // *****************************************************
568:
569: /**
570: * A <code>TimerTask</code> subclass which will notify the
571: * <code>Display</code> to make the 'returnScreen' of this
572: * <code>Alert</code> the new current screen.
573: */
574: private class TimeoutTask extends TimerTask {
575:
576: /**
577: * Create a new timeout task.
578: * This package protected constructor is just to enable creation
579: * of new TimerTask instance.
580: */
581: TimeoutTask() {
582: }
583:
584: /**
585: * Simply set the <code>Display</code>'s current screen to be this
586: * <code>Alert</code>'s return screen.
587: */
588: public void run() {
589: alert.uNotifyTimeout();
590: }
591: } // TimeoutTask
592: }
|