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;
033:
034: import com.sun.svg.component.SVGHorizontalScrollBar;
035: import com.sun.svg.component.SVGList;
036:
037: import java.util.Vector;
038: import javax.microedition.lcdui.Canvas;
039: import javax.microedition.m2g.SVGEventListener;
040: import javax.microedition.m2g.SVGImage;
041: import javax.microedition.m2g.SVGAnimator;
042: import org.w3c.dom.Document;
043: import org.w3c.dom.svg.SVGAnimationElement;
044: import org.w3c.dom.svg.SVGElement;
045:
046: public class ContactListScreen implements SVGEventListener {
047: /**
048: * Prefix used to recognize the list element and its children.
049: */
050: private static final String LIST_PREFIX = "contactList";
051:
052: /**
053: * The maximum amount of time allowed between to rapid key presses. In milli seconds.
054: */
055: private static final long RAPID_KEY_PRESS_MAX_INTERVAL = 500;
056:
057: /**
058: * The associated SVGAnimator
059: */
060: private SVGAnimator contactListAnimator;
061:
062: /**
063: * The SVG Canvas
064: */
065: private Canvas contactListCanvas;
066:
067: /**
068: * The associated SVGImage
069: */
070: private SVGImage contactListImage;
071:
072: /**
073: * Horizontal ScrollBar
074: */
075: protected SVGHorizontalScrollBar scrollBar;
076:
077: /**
078: * The list where the contact items are displayed.
079: */
080: protected SVGList svgList;
081:
082: /**
083: * The animation triggered when a list item is selected.
084: */
085: private SVGAnimationElement selectItemAnim;
086:
087: /**
088: * The animation triggered when the user moves back to the contact list.
089: */
090: private SVGAnimationElement backToListAnim;
091:
092: /**
093: * Used to track when the contact details are shown.
094: */
095: private boolean showingItem = false;
096:
097: /**
098: * Helper class easing the transfer of data from the contact data source
099: * to the XML UI elements.
100: */
101: private ContactDetailsForm contactDetails;
102:
103: /**
104: * Abstracts access to the contact list information. Allows a fake
105: * implementation or a real one over the PIM APIs.
106: */
107: private ContactListSource contactListSource;
108:
109: /**
110: * Used to hold temporary contact details data.
111: */
112: private ContactDetails contactDetailsData;
113:
114: /**
115: * Used to track rapid key pressed succession. This is reset to zero if
116: * more than RAPID_KEY_PRESS_MAX_INTERVAL is exceeded.
117: */
118: private int keyLevel = 0;
119:
120: /**
121: * Time of the last keyPress.
122: */
123: private long lastKeyPress;
124:
125: /**
126: * Runnables are used for synchronized access to the DOM managed in the
127: * SVGAnimator's update thread.
128: */
129: private Runnable scrollDownRunnable = new Runnable() {
130: public void run() {
131: scrollDown();
132: }
133: };
134:
135: private Runnable scrollUpRunnable = new Runnable() {
136: public void run() {
137: scrollUp();
138: }
139: };
140:
141: private Runnable showContactRunnable = new Runnable() {
142: public void run() {
143: showContact();
144: }
145: };
146:
147: private Runnable backToListRunnable = new Runnable() {
148: public void run() {
149: backToList();
150: }
151: };
152:
153: private Runnable nextContactDetailsRunnable = new Runnable() {
154: public void run() {
155: nextContactDetails();
156: }
157: };
158:
159: private Runnable prevContactDetailsRunnable = new Runnable() {
160: public void run() {
161: prevContactDetails();
162: }
163: };
164:
165: class JumpToRunnable implements Runnable {
166: int listIndex;
167:
168: public void run() {
169: jumpTo(listIndex);
170: }
171: };
172:
173: private JumpToRunnable jumpToRunnable = new JumpToRunnable();
174:
175: /**
176: * Creates a new instance of SVGContactListScreen
177: *
178: * @param contactListAnimator - the associated SVGAnimator
179: * @param contactListImage - the associated SVGImage
180: * @param contactListSource - the contact list data source
181: */
182: public ContactListScreen(SVGAnimator contactListAnimator,
183: SVGImage contactListImage,
184: ContactListSource contactListSource) {
185: this .contactListAnimator = contactListAnimator;
186: this .contactListCanvas = (Canvas) contactListAnimator
187: .getTargetComponent();
188: this .contactListImage = contactListImage;
189: this .contactListSource = contactListSource;
190:
191: svgList = new SVGList(contactListSource, LIST_PREFIX);
192:
193: svgList.setCommonItemBinder(new CommonContactItemBinder());
194: svgList.setSelectedItemBinder(new SelectedContactItemBinder());
195:
196: // Initialize the contact details
197: contactDetails = new ContactDetailsForm();
198:
199: // Create the scrollbar used to position the currently selected
200: // contact in the application.
201: scrollBar = new SVGHorizontalScrollBar(LIST_PREFIX
202: + "_scrollBar");
203:
204: hookSkin(contactListImage.getDocument());
205:
206: // Initialize the XML UI Data
207: showingItem = false;
208: scrollBar.setThumbPosition(svgList.getPosition());
209: setContactDetails();
210:
211: // Start listening to User Interface events.
212: contactListAnimator.setSVGEventListener(this );
213:
214: }
215:
216: /**
217: * Hooks the specified sking to the application.
218: *
219: * @param doc the new Document to hook into the application
220: */
221: private void hookSkin(final Document doc) {
222: selectItemAnim = (SVGAnimationElement) doc
223: .getElementById("selectItem");
224: backToListAnim = (SVGAnimationElement) doc
225: .getElementById("backToList");
226:
227: scrollBar.hookSkin(doc);
228: svgList.hookSkin(doc);
229: contactDetails.hookSkin(doc);
230: }
231:
232: /**
233: * Fills in the selected contact information and triggers the animation
234: * to move to the contact display.
235: */
236: private void showContact() {
237: setContactDetails();
238: selectItemAnim.beginElementAt(0);
239: showingItem = true;
240: }
241:
242: /**
243: * Displays the next contact details information.
244: */
245: private void nextContactDetails() {
246: svgList.next();
247: setContactDetails();
248: scrollBar.setThumbPosition(svgList.getPosition());
249:
250: }
251:
252: /**
253: * Displays the previous contact details information.
254: */
255: private void prevContactDetails() {
256: svgList.prev();
257: setContactDetails();
258: scrollBar.setThumbPosition(svgList.getPosition());
259:
260: }
261:
262: /**
263: * Sets the current contact details information.
264: */
265: private void setContactDetails() {
266: ContactDetails contactDetailsData = (ContactDetails) contactListSource
267: .getElementAt(svgList.getFocusedIndex());
268: contactDetails.setContactDetails(contactDetailsData);
269: }
270:
271: /**
272: * Triggers the animation to move back to the list.
273: */
274: private void backToList() {
275: svgList.setDataItems();
276: backToListAnim.beginElementAt(0);
277: selectItemAnim.endElementAt(0);
278: showingItem = false;
279: }
280:
281: void scrollDown() {
282: svgList.scrollDown();
283: scrollBar.setThumbPosition(svgList.getPosition());
284:
285: }
286:
287: void scrollUp() {
288: svgList.scrollUp();
289: scrollBar.setThumbPosition(svgList.getPosition());
290:
291: }
292:
293: public void keyPressed(int i) {
294: char c = (char) i;
295:
296: if (c >= '2' && c <= '9') {
297: onTextEvent(c);
298: return;
299: }
300:
301: if (!showingItem) {
302: if (contactListCanvas.getGameAction(i) == Canvas.DOWN) {
303: contactListAnimator.invokeLater(scrollDownRunnable);
304: } else if (contactListCanvas.getGameAction(i) == Canvas.UP) {
305: contactListAnimator.invokeLater(scrollUpRunnable);
306: } else if (contactListCanvas.getGameAction(i) == Canvas.FIRE
307: || contactListCanvas.getGameAction(i) == Canvas.RIGHT) {
308: contactListAnimator.invokeLater(showContactRunnable);
309: }
310: } else {
311: if (contactListCanvas.getGameAction(i) == Canvas.DOWN) {
312: contactListAnimator
313: .invokeLater(nextContactDetailsRunnable);
314: } else if (contactListCanvas.getGameAction(i) == Canvas.UP) {
315: contactListAnimator
316: .invokeLater(prevContactDetailsRunnable);
317: } else if (contactListCanvas.getGameAction(i) == Canvas.FIRE
318: || contactListCanvas.getGameAction(i) == Canvas.LEFT) {
319: contactListAnimator.invokeLater(backToListRunnable);
320: }
321: }
322: }
323:
324: /**
325: * @param c the new character which was typed.
326: */
327: public void onTextEvent(char c) {
328: char a = toAlphabet(c);
329: jumpTo(a);
330: }
331:
332: /**
333: * @return true if less that RAPID_KEY_PRESS_MAX_INTERVAL has ellapsed since
334: * the lastKeyPress.
335: */
336: private boolean rapidKeyPress() {
337: long t = System.currentTimeMillis();
338: if ((t - lastKeyPress) < RAPID_KEY_PRESS_MAX_INTERVAL) {
339: lastKeyPress = t;
340: return true;
341: }
342: lastKeyPress = t;
343: return false;
344: }
345:
346: /**
347: * @param c the keyboard character to convert to an alphabetical value.
348: */
349: public char toAlphabet(char c) {
350: if (rapidKeyPress()) {
351: keyLevel++;
352: } else {
353: keyLevel = 0;
354: }
355:
356: switch (keyLevel) {
357: case 0:
358: switch (c) {
359: case '2':
360: return 'a';
361: case '3':
362: return 'd';
363: case '4':
364: return 'g';
365: case '5':
366: return 'j';
367: case '6':
368: return 'm';
369: case '7':
370: return 'p';
371: case '8':
372: return 't';
373: case '9':
374: default:
375: return 'w';
376:
377: }
378: case 1:
379: switch (c) {
380: case '2':
381: return 'b';
382: case '3':
383: return 'e';
384: case '4':
385: return 'h';
386: case '5':
387: return 'k';
388: case '6':
389: return 'n';
390: case '7':
391: return 'q';
392: case '8':
393: return 'u';
394: case '9':
395: default:
396: return 'x';
397:
398: }
399: case 2:
400: switch (c) {
401: case '2':
402: return 'c';
403: case '3':
404: return 'f';
405: case '4':
406: return 'i';
407: case '5':
408: return 'l';
409: case '6':
410: return 'o';
411: case '7':
412: return 'r';
413: case '8':
414: return 'v';
415: case '9':
416: default:
417: return 'y';
418:
419: }
420: default:
421: switch (c) {
422: case '2':
423: return 'c';
424: case '3':
425: return 'f';
426: case '4':
427: return 'i';
428: case '5':
429: return 'l';
430: case '6':
431: return 'o';
432: case '7':
433: return 's';
434: case '8':
435: return 'v';
436: case '9':
437: default:
438: return 'z';
439:
440: }
441: }
442: }
443:
444: /**
445: * Scrolls to the first item starting with the specified character, if any.
446: *
447: * @param c alpha
448: */
449: public void jumpTo(char c) {
450: int i = contactListSource.firstIndexFor(c);
451: if (i == -1) {
452: return;
453: }
454:
455: // There is an entry with the given character.
456: jumpToRunnable.listIndex = i;
457: contactListAnimator.invokeLater(jumpToRunnable);
458: }
459:
460: /**
461: * Should be invoked in the update thread.
462: *
463: * @param listIndex - the list index to jump to.
464: */
465: protected void jumpTo(int listIndex) {
466: svgList.setSelectedIndex(listIndex);
467:
468: if (showingItem) {
469: setContactDetails();
470: } else {
471: svgList.setDataItems();
472: }
473:
474: scrollBar.setThumbPosition(svgList.getPosition());
475: }
476:
477: public void keyReleased(int i) {
478: }
479:
480: public void pointerPressed(int i, int i0) {
481: }
482:
483: public void pointerReleased(int i, int i0) {
484: }
485:
486: public void hideNotify() {
487: }
488:
489: public void showNotify() {
490: }
491:
492: public void sizeChanged(int i, int i0) {
493: }
494:
495: /**
496: * In the list displayed by this application, we display the contact
497: * name and the contact cell phone in the selected item. The name is displayed
498: * in the item's first child (a <text> element) and the phone number in the
499: * item's second element child (a <text> element also).
500: */
501: static class SelectedContactItemBinder implements
502: SVGList.ListItemBinder {
503: /**
504: * @param itemValue - the item value to transfer to the SVG element.
505: * @param itemElement - the item element where the item value is
506: * displayed.
507: */
508: public void bindItem(Object itemValue, SVGElement itemElement) {
509: SVGElement text = (SVGElement) itemElement
510: .getFirstElementChild();
511: if (text == null) {
512: throw new IllegalArgumentException(
513: "SelectedContactItemBinder : "
514: + " could not find expected text element under "
515: + "element with id "
516: + itemElement.getId());
517: }
518: text.setTrait("#text", ((ContactDetails) itemValue)
519: .getName());
520: text = (SVGElement) text.getNextElementSibling();
521: if (text == null) {
522: throw new IllegalArgumentException(
523: "SelectedContactItemBinder : "
524: + " could not find expected second text element under "
525: + "element with id "
526: + itemElement.getId());
527: }
528: text.setTrait("#text", ((ContactDetails) itemValue)
529: .getCellPhone());
530: }
531: }
532:
533: /**
534: * In the list displayed by this application, we display the contact
535: * name in the list item which must be a <text> element.
536: */
537: static class CommonContactItemBinder implements
538: SVGList.ListItemBinder {
539: /**
540: * @param itemValue - the item value to transfer to the SVG element.
541: * @param itemElement - the item element where the item value is
542: * displayed.
543: */
544: public void bindItem(Object itemValue, SVGElement itemElement) {
545: itemElement.setTrait("#text", ((ContactDetails) itemValue)
546: .getName());
547: }
548: }
549: }
|