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 com.sun.midp.chameleon.layers;
028:
029: import com.sun.midp.chameleon.*;
030: import javax.microedition.lcdui.*;
031: import com.sun.midp.chameleon.skins.*;
032: import com.sun.midp.util.ResourceHandler;
033: import com.sun.midp.configurator.Constants;
034: import com.sun.midp.lcdui.EventConstants;
035: import com.sun.midp.log.Logging;
036: import com.sun.midp.log.LogChannels;
037:
038: /**
039: * A special popup layer which implements a system
040: * menu. The system menu is a collection of commands,
041: * both screen (Back, Exit, etc) and item specific
042: * commands.
043: */
044: public class MenuLayer extends ScrollablePopupLayer {
045:
046: /** The list of Commands to display in the menu. */
047: protected Command[] menuCmds;
048:
049: /** The currently selected index in the menu. */
050: protected int selI;
051:
052: /**
053: * The number of commands which have been scrolled off the
054: * top of the menu, normally 0 unless there are more commands
055: * than can fit on the menu.
056: */
057: protected int scrollIndex;
058:
059: /**
060: * The SoftButtonLayer maintains the overall set of
061: * commands and their associated listeners.
062: */
063: protected SoftButtonLayer btnLayer;
064:
065: /**
066: * A cascading menu which holds commands for a SubMenuCommand.
067: */
068: protected CascadeMenuLayer cascadeMenu;
069:
070: /**
071: * A flag indicating if a cascading menu is visible.
072: */
073: protected boolean cascadeMenuUp;
074:
075: /** pointer pressed outside of the menuLayer's bounds */
076: private final static int PRESS_OUT_OF_BOUNDS = -1;
077:
078: /** pointer pressed on the menuLayer's title area */
079: private final static int PRESS_ON_TITLE = -2;
080:
081: /** variable used in pointerInput handling */
082: private int itemIndexWhenPressed = PRESS_OUT_OF_BOUNDS;
083:
084: /**
085: * Construct a new system menu layer.
086: */
087: public MenuLayer() {
088: super ();
089: setBackground(MenuSkin.IMAGE_BG, MenuSkin.COLOR_BG);
090: cascadeMenu = new CascadeMenuLayer();
091: }
092:
093: /**
094: * Called typically by the SoftButtonLayer to establish the
095: * set of Commands to display on this system menu. This method will
096: * create a new copy of the array of commands passed in.
097: *
098: * @param cmdList the set of commands to display in the menu
099: * (the commands should already be sorted by priority)
100: * @param btnLayer the SoftButtonLayer to notify of any command
101: * selections
102: * @param index the command index has to be highlighted. If index exceeds
103: * the number of commands the 1st command has to be highlighted.
104: */
105: public void setMenuCommands(Command[] cmdList,
106: SoftButtonLayer btnLayer, int index) {
107: if (cmdList.length == 1 && cmdList[0] instanceof SubMenuCommand) {
108: cmdList = ((SubMenuCommand) cmdList[0]).getSubCommands();
109: }
110: this .menuCmds = new Command[cmdList.length];
111: System.arraycopy(cmdList, 0, this .menuCmds, 0, cmdList.length);
112: // If we have fewer commands than fill up the menu,
113: // we shorten the menu's height
114: if (menuCmds.length < MenuSkin.MAX_ITEMS) {
115: bounds[H] = MenuSkin.HEIGHT
116: - ((MenuSkin.MAX_ITEMS - menuCmds.length) * MenuSkin.ITEM_HEIGHT);
117: } else {
118: bounds[H] = MenuSkin.HEIGHT;
119: }
120: alignMenu();
121: requestRepaint();
122:
123: this .btnLayer = btnLayer;
124:
125: selI = index < cmdList.length ? index : 0;
126: }
127:
128: /**
129: * Updates the scroll indicator.
130: */
131: public void updateScrollIndicator() {
132: if (scrollInd != null) {
133: if (menuCmds.length > MenuSkin.MAX_ITEMS) {
134: scrollInd.setVerticalScroll((scrollIndex * 100)
135: / (menuCmds.length - MenuSkin.MAX_ITEMS),
136: (MenuSkin.MAX_ITEMS * 100) / menuCmds.length);
137: } else {
138: scrollInd.setVerticalScroll(0, 100);
139: }
140: super .updateScrollIndicator();
141: }
142: }
143:
144: /**
145: * Helper function to determine the itemIndex at the x,y position
146: *
147: * @param x,y pointer coordinates in menuLayer's space (0,0 means left-top
148: * corner) both value can be negative as menuLayer handles the pointer
149: * event outside its bounds
150: * @return menuItem's index since 0, or PRESS_OUT_OF_BOUNDS, PRESS_ON_TITLE
151: *
152: */
153: private int itemIndexAtPointerPosition(int x, int y) {
154: int ret;
155: if (!containsPoint(x + bounds[X], y + bounds[Y])) {
156: ret = PRESS_OUT_OF_BOUNDS;
157: } else if (y < MenuSkin.ITEM_TOPOFFSET) {
158: ret = PRESS_ON_TITLE;
159: } else {
160: ret = (y - MenuSkin.ITEM_TOPOFFSET) / MenuSkin.ITEM_HEIGHT;
161: }
162: return ret;
163: }
164:
165: /**
166: * Handle input from a pen tap. Parameters describe
167: * the type of pen event and the x,y location in the
168: * layer at which the event occurred. Important : the
169: * x,y location of the pen tap will already be translated
170: * into the coordinate space of the layer.
171: *
172: * @param type the type of pen event
173: * @param x the x coordinate of the event
174: * @param y the y coordinate of the event
175: */
176: public boolean pointerInput(int type, int x, int y) {
177: switch (type) {
178: case EventConstants.PRESSED:
179: itemIndexWhenPressed = itemIndexAtPointerPosition(x, y);
180:
181: // dismiss the menu layer if the user pressed outside the menu
182: if (itemIndexWhenPressed == PRESS_OUT_OF_BOUNDS) {
183: if (btnLayer != null) {
184: btnLayer.dismissMenu();
185: }
186: } else if (itemIndexWhenPressed >= 0) { // press on valid menu item
187: selI = scrollIndex + itemIndexWhenPressed;
188: requestRepaint();
189: // if (btnLayer != null) btnLayer.serviceRepaints();
190: }
191: break;
192: case EventConstants.RELEASED:
193: int itemIndexWhenReleased = itemIndexAtPointerPosition(x, y);
194:
195: if (itemIndexWhenReleased == itemIndexWhenPressed) {
196: if (itemIndexWhenPressed >= 0) {
197: if (btnLayer != null && !showSubMenu(selI)) {
198: if (selI >= 0 && selI < menuCmds.length) {
199: btnLayer.commandSelected(menuCmds[selI]);
200: }
201: }
202: }
203: }
204:
205: // remember to reset the variables
206: itemIndexWhenPressed = PRESS_OUT_OF_BOUNDS;
207: break;
208: }
209: // return true always as menuLayer will capture all of the pointer inputs
210: return true;
211: }
212:
213: /**
214: * Handles key input from a keypad. Parameters describe
215: * the type of key event and the platform-specific
216: * code for the key. (Codes are translated using the
217: * lcdui.Canvas)
218: *
219: * @param type the type of key event
220: * @param keyCode the numeric code assigned to the key
221: * @return true if the input has been processed by this
222: * method, otherwise false (soft menu keys)
223: */
224: public boolean keyInput(int type, int keyCode) {
225: // The system menu will absorb all key presses except
226: // for the soft menu keys - that is, it will always
227: // return 'true' indicating it has handled the key
228: // event except for the soft button keys for which it
229: // returns 'false'
230:
231: if (keyCode == EventConstants.SOFT_BUTTON1
232: || keyCode == EventConstants.SOFT_BUTTON2) {
233: return false;
234: }
235:
236: if (type != EventConstants.PRESSED
237: && type != EventConstants.REPEATED) {
238: return true;
239: }
240:
241: if (keyCode == Constants.KEYCODE_UP) {
242: if (selI > 0) {
243: selI--;
244: if (selI < scrollIndex && scrollIndex > 0) {
245: scrollIndex--;
246: }
247: updateScrollIndicator();
248: requestRepaint();
249: }
250: } else if (keyCode == Constants.KEYCODE_DOWN) {
251: if (selI < (menuCmds.length - 1)) {
252: selI++;
253: if (selI >= scrollIndex + MenuSkin.MAX_ITEMS
254: && scrollIndex < (menuCmds.length - MenuSkin.MAX_ITEMS)) {
255: scrollIndex++;
256: }
257: updateScrollIndicator();
258: requestRepaint();
259: }
260: } else if (keyCode == Constants.KEYCODE_LEFT) {
261: // IMPL_NOTE : Need to add support for a "right popping"
262: // sub menu if the system menu is placed on the left
263: // side of the screen instead of the right
264: if (btnLayer != null) {
265: showSubMenu(selI);
266: }
267: } else if (keyCode == Constants.KEYCODE_SELECT) {
268: if (btnLayer != null && !showSubMenu(selI)) {
269: btnLayer.commandSelected(menuCmds[selI]);
270: }
271: } else {
272: int max = 0;
273: switch (keyCode) {
274: case Canvas.KEY_NUM1:
275: max = 1;
276: break;
277: case Canvas.KEY_NUM2:
278: max = 2;
279: break;
280: case Canvas.KEY_NUM3:
281: max = 3;
282: break;
283: case Canvas.KEY_NUM4:
284: max = 4;
285: break;
286: case Canvas.KEY_NUM5:
287: max = 5;
288: break;
289: case Canvas.KEY_NUM6:
290: max = 6;
291: break;
292: case Canvas.KEY_NUM7:
293: max = 7;
294: break;
295: case Canvas.KEY_NUM8:
296: max = 8;
297: break;
298: case Canvas.KEY_NUM9:
299: max = 9;
300: break;
301: }
302: if (max > 0 && menuCmds.length >= max) {
303: if (btnLayer != null && !showSubMenu(max - 1)) {
304: btnLayer.commandSelected(menuCmds[max - 1]);
305: }
306: }
307: }
308: return true;
309: }
310:
311: /**
312: * Cleans up the display when the cascaded menu is dismissed.
313: * Removes the layer with the menu and requests the display to be
314: * repainted.
315: */
316: public void dismissCascadeMenu() {
317: if (owner != null && cascadeMenuUp) {
318: cascadeMenuUp = false;
319: cascadeMenu.dismiss();
320:
321: setScrollInd(ScrollIndLayer.getInstance(ScrollIndSkin.MODE));
322:
323: owner.removeLayer(cascadeMenu);
324: requestRepaint();
325: }
326: }
327:
328: /**
329: * Cleans up the display when the cascaded menu is dismissed.
330: * Removes the layer with the menu and requests the display to be
331: * repainted.
332: */
333: public void dismiss() {
334: dismissCascadeMenu();
335: selI = scrollIndex = 0;
336: }
337:
338: /**
339: * Notifies listener that a command has been selected.
340: * Dismisses the cascaded menu and the button layer.
341: * @param cmd the command that was selected
342: */
343: public void subCommandSelected(Command cmd) {
344: Command c = menuCmds[selI];
345: if (c instanceof SubMenuCommand) {
346: btnLayer.dismissMenu();
347: ((SubMenuCommand) c).notifyListener(cmd);
348: }
349: }
350:
351: /**
352: * Initializes the menu parameters.
353: */
354: protected void initialize() {
355: super .initialize();
356: bounds[X] = 0; // set in alignMenu()
357: bounds[Y] = 0; // set in alignMenu()
358: bounds[W] = MenuSkin.WIDTH;
359: bounds[H] = MenuSkin.HEIGHT;
360: }
361:
362: /**
363: * Aligns the menu to the current screen.
364: */
365: protected void alignMenu() {
366:
367: bounds[W] = MenuSkin.WIDTH;
368:
369: switch (MenuSkin.ALIGN_X) {
370: case Graphics.LEFT:
371: bounds[X] = 0;
372: break;
373: case Graphics.HCENTER:
374: bounds[X] = (ScreenSkin.WIDTH - bounds[W]) / 2;
375: break;
376: case Graphics.RIGHT:
377: default:
378: bounds[X] = ScreenSkin.WIDTH - bounds[W];
379: break;
380: }
381: switch (MenuSkin.ALIGN_Y) {
382: case Graphics.TOP:
383: bounds[Y] = 0;
384: break;
385: case Graphics.VCENTER:
386: bounds[Y] = (ScreenSkin.HEIGHT - SoftButtonSkin.HEIGHT - bounds[H]) / 2;
387: break;
388: case Graphics.BOTTOM:
389: default:
390: bounds[Y] = ScreenSkin.HEIGHT - SoftButtonSkin.HEIGHT
391: - bounds[H];
392: break;
393: }
394: }
395:
396: /**
397: * Renders the body of the menu.
398: * @param g the graphics context to be updated
399: */
400: protected void paintBody(Graphics g) {
401: if (MenuSkin.TEXT_TITLE != null) {
402: // IMPL_NOTE enforce MenuSkin.TITLE_MAXWIDTH based on
403: // title value and font, add '...' to titles which
404: // are too long to show
405: g.setFont(MenuSkin.FONT_TITLE);
406: g.setColor(MenuSkin.COLOR_TITLE);
407: g.drawString(MenuSkin.TEXT_TITLE, MenuSkin.TITLE_X,
408: MenuSkin.TITLE_Y, Graphics.TOP
409: | MenuSkin.TITLE_ALIGN);
410: }
411:
412: if (menuCmds != null) {
413:
414: int y = MenuSkin.ITEM_TOPOFFSET;
415: int x = 0;
416: Image arrow = null;
417:
418: for (int cmdIndex = scrollIndex; (cmdIndex < menuCmds.length)
419: && (cmdIndex - scrollIndex < MenuSkin.MAX_ITEMS); cmdIndex++) {
420:
421: if (menuCmds[cmdIndex] instanceof SubMenuCommand) {
422: arrow = MenuSkin.IMAGE_SUBMENU_ARROW;
423: if (cmdIndex == selI && !cascadeMenuUp) {
424: arrow = MenuSkin.IMAGE_SUBMENU_ARROW_HL;
425: }
426: if (arrow != null) {
427: x = arrow.getWidth() + 2;
428: }
429: }
430:
431: if (cmdIndex == selI && !cascadeMenuUp) {
432: if (MenuSkin.IMAGE_ITEM_SEL_BG != null) {
433: // We want to draw the selected item background
434: CGraphicsUtil
435: .draw3pcsBackground(
436: g,
437: 3,
438: ((selI - scrollIndex) * MenuSkin.ITEM_HEIGHT)
439: + MenuSkin.IMAGE_BG[0]
440: .getHeight(),
441: bounds[W] - 3,
442: MenuSkin.IMAGE_ITEM_SEL_BG);
443: } else {
444: g.setColor(MenuSkin.COLOR_BG_SEL);
445: g
446: .fillRoundRect(
447: MenuSkin.ITEM_ANCHOR_X - 2,
448: ((selI - scrollIndex) * MenuSkin.ITEM_HEIGHT)
449: + MenuSkin.ITEM_TOPOFFSET,
450: MenuSkin.FONT_ITEM_SEL
451: .stringWidth(menuCmds[cmdIndex]
452: .getLabel())
453: + 4 + x,
454: MenuSkin.ITEM_HEIGHT, 3, 3);
455: }
456: }
457:
458: if (cmdIndex < 9) {
459: g
460: .setFont((selI == cmdIndex) ? MenuSkin.FONT_ITEM_SEL
461: : MenuSkin.FONT_ITEM);
462: g
463: .setColor((selI == cmdIndex) ? MenuSkin.COLOR_INDEX_SEL
464: : MenuSkin.COLOR_INDEX);
465: g.drawString("" + (cmdIndex + 1),
466: MenuSkin.ITEM_INDEX_ANCHOR_X, y,
467: Graphics.TOP | Graphics.LEFT);
468: }
469:
470: g.setFont(MenuSkin.FONT_ITEM);
471: g.setColor((selI == cmdIndex) ? MenuSkin.COLOR_ITEM_SEL
472: : MenuSkin.COLOR_ITEM);
473:
474: if (arrow != null) {
475: g.drawImage(arrow, MenuSkin.ITEM_ANCHOR_X, y + 2,
476: Graphics.TOP | Graphics.LEFT);
477: arrow = null;
478: }
479: g.drawString(menuCmds[cmdIndex].getLabel(),
480: MenuSkin.ITEM_ANCHOR_X + x, y, Graphics.TOP
481: | Graphics.LEFT);
482:
483: x = 0;
484: y += MenuSkin.ITEM_HEIGHT;
485: }
486: }
487: }
488:
489: /**
490: * Shows the sub menu.
491: * @param index the offset in the array of menu commands
492: * @return true if submenu is shown, false - otherwise
493: */
494: private boolean showSubMenu(int index) {
495: boolean ret = false;
496: if (menuCmds[index] instanceof SubMenuCommand) {
497: SubMenuCommand subMenu = (SubMenuCommand) menuCmds[index];
498: cascadeMenu.setMenuCommands(subMenu.getSubCommands(), this );
499: cascadeMenu.setAnchorPoint(bounds[X], bounds[Y]
500: + MenuSkin.ITEM_TOPOFFSET
501: + ((index - scrollIndex) * MenuSkin.ITEM_HEIGHT));
502: cascadeMenuUp = true;
503: owner.addLayer(cascadeMenu);
504: setScrollInd(ScrollIndLayer.getInstance(ScrollIndSkin.MODE));
505: // IMPL_NOTE: fix layer inrteraction in removeLayer
506: btnLayer.requestRepaint();
507:
508: selI = index;
509: addDirtyRegion();
510: ret = true;
511: }
512: return ret;
513: }
514:
515: /**
516: * Gets index of highlighted command.
517: * @return highlighted index
518: */
519: public int getIndex() {
520: return selI;
521: }
522:
523: /**
524: * Update bounds of layer
525: * @param layers - current layer can be dependant on this parameter
526: */
527: public void update(CLayer[] layers) {
528: alignMenu();
529: if (owner != null && cascadeMenuUp) {
530: cascadeMenu.update(layers);
531: if (btnLayer != null) {
532: showSubMenu(selI);
533: }
534: }
535: super .update(layers);
536: }
537:
538: /**
539: * Scroll content inside of the Menu.
540: * @param scrollType scrollType. Scroll type can be one of the following
541: * @see ScrollBarLayer.SCROLL_NONE
542: * @see ScrollBarLayer.SCROLL_PAGEUP
543: * @see ScrollBarLayer.SCROLL_PAGEDOWN
544: * @see ScrollBarLayer.SCROLL_LINEUP
545: * @see ScrollBarLayer.SCROLL_LINEDOWN or
546: * @see ScrollBarLayer.SCROLL_THUMBTRACK
547: * @param thumbPosition
548: */
549: public void scrollContent(int scrollType, int thumbPosition) {
550: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
551: Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI,
552: "MenuLayer.scrollContent scrollType=" + scrollType
553: + " thumbPosition=" + thumbPosition);
554: }
555: // keep old scrollIndex
556: int oldScrollIndex = scrollIndex;
557:
558: switch (scrollType) {
559: case ScrollBarLayer.SCROLL_PAGEUP:
560: uScrollViewport(Canvas.UP);
561: break;
562: case ScrollBarLayer.SCROLL_PAGEDOWN:
563: uScrollViewport(Canvas.DOWN);
564: break;
565: case ScrollBarLayer.SCROLL_LINEUP:
566: uScrollByLine(Canvas.UP);
567: break;
568: case ScrollBarLayer.SCROLL_LINEDOWN:
569: uScrollByLine(Canvas.DOWN);
570: break;
571: case ScrollBarLayer.SCROLL_THUMBTRACK:
572: uScrollAt(thumbPosition);
573: break;
574: default:
575: break;
576: }
577: // only if scroll index has been changed do update
578: if (oldScrollIndex != scrollIndex && scrollIndex >= 0) {
579:
580: // correct selI if required.
581: // The selected item always should be on the screen
582: if (selI < scrollIndex) {
583: selI = scrollIndex;
584: } else if (selI >= (scrollIndex + MenuSkin.MAX_ITEMS)) {
585: selI = scrollIndex + MenuSkin.MAX_ITEMS - 1;
586: }
587:
588: updateScrollIndicator();
589: requestRepaint();
590: }
591: }
592:
593: /**
594: * Perform a line scrolling in the given direction. This method will
595: * attempt to scroll the view to show next/previous line.
596: *
597: * @param dir the direction of the flip, either DOWN or UP
598: */
599: private void uScrollByLine(int dir) {
600: switch (dir) {
601: case Canvas.UP:
602: if (scrollIndex > 0) {
603: scrollIndex--;
604: }
605: break;
606: case Canvas.DOWN:
607: if (scrollIndex < (menuCmds.length - MenuSkin.MAX_ITEMS)) {
608: scrollIndex++;
609: }
610: break;
611: }
612: }
613:
614: /**
615: * Perform a page flip in the given direction. This method will
616: * attempt to scroll the view to show as much of the next page
617: * as possible. It uses the locations and bounds of the items on
618: * the page to best determine a new location - taking into account
619: * items which may lie on page boundaries as well as items which
620: * may span several pages.
621: *
622: * @param dir the direction of the flip, either DOWN or UP
623: */
624: private void uScrollViewport(int dir) {
625: switch (dir) {
626: case Canvas.UP:
627: scrollIndex -= MenuSkin.MAX_ITEMS - 1;
628: if (scrollIndex < 0) {
629: scrollIndex = 0;
630: }
631: break;
632: case Canvas.DOWN:
633: scrollIndex += MenuSkin.MAX_ITEMS - 1;
634: if (scrollIndex > menuCmds.length - MenuSkin.MAX_ITEMS) {
635: scrollIndex = menuCmds.length - MenuSkin.MAX_ITEMS;
636: }
637: break;
638: }
639: }
640:
641: /**
642: * Perform a scrolling at the given position.
643: * @param context position
644: */
645: void uScrollAt(int position) {
646: int viewableH = MenuSkin.ITEM_HEIGHT * menuCmds.length;
647: int viewportH = MenuSkin.ITEM_HEIGHT * MenuSkin.MAX_ITEMS;
648:
649: int newY = (viewableH - viewportH) * position / 100;
650: if (newY < 0) {
651: newY = 0;
652: } else if (newY > viewableH - viewportH) {
653: newY = viewableH - viewportH;
654: }
655: scrollIndex = newY / MenuSkin.ITEM_HEIGHT;
656: }
657: }
|