/*
*
*
* Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package com.sun.midp.chameleon.layers;
import com.sun.midp.chameleon.*;
import javax.microedition.lcdui.*;
import com.sun.midp.chameleon.skins.*;
import com.sun.midp.util.ResourceHandler;
import com.sun.midp.configurator.Constants;
import com.sun.midp.lcdui.EventConstants;
import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;
/**
* A special popup layer which implements a system
* menu. The system menu is a collection of commands,
* both screen (Back, Exit, etc) and item specific
* commands.
*/
public class MenuLayer extends ScrollablePopupLayer {
/** The list of Commands to display in the menu. */
protected Command[] menuCmds;
/** The currently selected index in the menu. */
protected int selI;
/**
* The number of commands which have been scrolled off the
* top of the menu, normally 0 unless there are more commands
* than can fit on the menu.
*/
protected int scrollIndex;
/**
* The SoftButtonLayer maintains the overall set of
* commands and their associated listeners.
*/
protected SoftButtonLayer btnLayer;
/**
* A cascading menu which holds commands for a SubMenuCommand.
*/
protected CascadeMenuLayer cascadeMenu;
/**
* A flag indicating if a cascading menu is visible.
*/
protected boolean cascadeMenuUp;
/** pointer pressed outside of the menuLayer's bounds */
private final static int PRESS_OUT_OF_BOUNDS = -1;
/** pointer pressed on the menuLayer's title area */
private final static int PRESS_ON_TITLE = -2;
/** variable used in pointerInput handling */
private int itemIndexWhenPressed = PRESS_OUT_OF_BOUNDS;
/**
* Construct a new system menu layer.
*/
public MenuLayer() {
super();
setBackground(MenuSkin.IMAGE_BG, MenuSkin.COLOR_BG);
cascadeMenu = new CascadeMenuLayer();
}
/**
* Called typically by the SoftButtonLayer to establish the
* set of Commands to display on this system menu. This method will
* create a new copy of the array of commands passed in.
*
* @param cmdList the set of commands to display in the menu
* (the commands should already be sorted by priority)
* @param btnLayer the SoftButtonLayer to notify of any command
* selections
* @param index the command index has to be highlighted. If index exceeds
* the number of commands the 1st command has to be highlighted.
*/
public void setMenuCommands(Command[] cmdList, SoftButtonLayer btnLayer,
int index) {
if (cmdList.length == 1 && cmdList[0] instanceof SubMenuCommand) {
cmdList = ((SubMenuCommand)cmdList[0]).getSubCommands();
}
this.menuCmds = new Command[cmdList.length];
System.arraycopy(cmdList, 0, this.menuCmds, 0, cmdList.length);
// If we have fewer commands than fill up the menu,
// we shorten the menu's height
if (menuCmds.length < MenuSkin.MAX_ITEMS) {
bounds[H] = MenuSkin.HEIGHT -
((MenuSkin.MAX_ITEMS - menuCmds.length)
* MenuSkin.ITEM_HEIGHT);
} else {
bounds[H] = MenuSkin.HEIGHT;
}
alignMenu();
requestRepaint();
this.btnLayer = btnLayer;
selI = index < cmdList.length ? index : 0;
}
/**
* Updates the scroll indicator.
*/
public void updateScrollIndicator() {
if (scrollInd != null) {
if (menuCmds.length > MenuSkin.MAX_ITEMS) {
scrollInd.setVerticalScroll(
(scrollIndex * 100) / (menuCmds.length - MenuSkin.MAX_ITEMS),
(MenuSkin.MAX_ITEMS * 100) / menuCmds.length);
} else {
scrollInd.setVerticalScroll(0, 100);
}
super.updateScrollIndicator();
}
}
/**
* Helper function to determine the itemIndex at the x,y position
*
* @param x,y pointer coordinates in menuLayer's space (0,0 means left-top
* corner) both value can be negative as menuLayer handles the pointer
* event outside its bounds
* @return menuItem's index since 0, or PRESS_OUT_OF_BOUNDS, PRESS_ON_TITLE
*
*/
private int itemIndexAtPointerPosition(int x, int y) {
int ret;
if (!containsPoint(x + bounds[X], y + bounds[Y])) {
ret = PRESS_OUT_OF_BOUNDS;
} else if (y < MenuSkin.ITEM_TOPOFFSET) {
ret = PRESS_ON_TITLE;
} else {
ret = (y - MenuSkin.ITEM_TOPOFFSET) / MenuSkin.ITEM_HEIGHT;
}
return ret;
}
/**
* Handle input from a pen tap. Parameters describe
* the type of pen event and the x,y location in the
* layer at which the event occurred. Important : the
* x,y location of the pen tap will already be translated
* into the coordinate space of the layer.
*
* @param type the type of pen event
* @param x the x coordinate of the event
* @param y the y coordinate of the event
*/
public boolean pointerInput(int type, int x, int y) {
switch (type) {
case EventConstants.PRESSED:
itemIndexWhenPressed = itemIndexAtPointerPosition(x, y);
// dismiss the menu layer if the user pressed outside the menu
if (itemIndexWhenPressed == PRESS_OUT_OF_BOUNDS) {
if (btnLayer != null) {
btnLayer.dismissMenu();
}
} else if (itemIndexWhenPressed >= 0) { // press on valid menu item
selI = scrollIndex + itemIndexWhenPressed;
requestRepaint();
// if (btnLayer != null) btnLayer.serviceRepaints();
}
break;
case EventConstants.RELEASED:
int itemIndexWhenReleased = itemIndexAtPointerPosition(x, y);
if (itemIndexWhenReleased == itemIndexWhenPressed) {
if (itemIndexWhenPressed >= 0) {
if (btnLayer != null && !showSubMenu(selI)) {
if (selI >= 0 && selI < menuCmds.length) {
btnLayer.commandSelected(menuCmds[selI]);
}
}
}
}
// remember to reset the variables
itemIndexWhenPressed = PRESS_OUT_OF_BOUNDS;
break;
}
// return true always as menuLayer will capture all of the pointer inputs
return true;
}
/**
* Handles key input from a keypad. Parameters describe
* the type of key event and the platform-specific
* code for the key. (Codes are translated using the
* lcdui.Canvas)
*
* @param type the type of key event
* @param keyCode the numeric code assigned to the key
* @return true if the input has been processed by this
* method, otherwise false (soft menu keys)
*/
public boolean keyInput(int type, int keyCode) {
// The system menu will absorb all key presses except
// for the soft menu keys - that is, it will always
// return 'true' indicating it has handled the key
// event except for the soft button keys for which it
// returns 'false'
if (keyCode == EventConstants.SOFT_BUTTON1 ||
keyCode == EventConstants.SOFT_BUTTON2) {
return false;
}
if (type != EventConstants.PRESSED && type != EventConstants.REPEATED) {
return true;
}
if (keyCode == Constants.KEYCODE_UP) {
if (selI > 0) {
selI--;
if (selI < scrollIndex && scrollIndex > 0) {
scrollIndex--;
}
updateScrollIndicator();
requestRepaint();
}
} else if (keyCode == Constants.KEYCODE_DOWN) {
if (selI < (menuCmds.length - 1)) {
selI++;
if (selI >= scrollIndex + MenuSkin.MAX_ITEMS &&
scrollIndex < (menuCmds.length - MenuSkin.MAX_ITEMS)) {
scrollIndex++;
}
updateScrollIndicator();
requestRepaint();
}
} else if (keyCode == Constants.KEYCODE_LEFT) {
// IMPL_NOTE : Need to add support for a "right popping"
// sub menu if the system menu is placed on the left
// side of the screen instead of the right
if (btnLayer != null) {
showSubMenu(selI);
}
} else if (keyCode == Constants.KEYCODE_SELECT) {
if (btnLayer != null && !showSubMenu(selI)) {
btnLayer.commandSelected(menuCmds[selI]);
}
} else {
int max = 0;
switch (keyCode) {
case Canvas.KEY_NUM1:
max = 1;
break;
case Canvas.KEY_NUM2:
max = 2;
break;
case Canvas.KEY_NUM3:
max = 3;
break;
case Canvas.KEY_NUM4:
max = 4;
break;
case Canvas.KEY_NUM5:
max = 5;
break;
case Canvas.KEY_NUM6:
max = 6;
break;
case Canvas.KEY_NUM7:
max = 7;
break;
case Canvas.KEY_NUM8:
max = 8;
break;
case Canvas.KEY_NUM9:
max = 9;
break;
}
if (max > 0 && menuCmds.length >= max) {
if (btnLayer != null && !showSubMenu(max - 1)) {
btnLayer.commandSelected(menuCmds[max - 1]);
}
}
}
return true;
}
/**
* Cleans up the display when the cascaded menu is dismissed.
* Removes the layer with the menu and requests the display to be
* repainted.
*/
public void dismissCascadeMenu() {
if (owner != null && cascadeMenuUp) {
cascadeMenuUp = false;
cascadeMenu.dismiss();
setScrollInd(ScrollIndLayer.getInstance(ScrollIndSkin.MODE));
owner.removeLayer(cascadeMenu);
requestRepaint();
}
}
/**
* Cleans up the display when the cascaded menu is dismissed.
* Removes the layer with the menu and requests the display to be
* repainted.
*/
public void dismiss() {
dismissCascadeMenu();
selI = scrollIndex = 0;
}
/**
* Notifies listener that a command has been selected.
* Dismisses the cascaded menu and the button layer.
* @param cmd the command that was selected
*/
public void subCommandSelected(Command cmd) {
Command c = menuCmds[selI];
if (c instanceof SubMenuCommand) {
btnLayer.dismissMenu();
((SubMenuCommand)c).notifyListener(cmd);
}
}
/**
* Initializes the menu parameters.
*/
protected void initialize() {
super.initialize();
bounds[X] = 0; // set in alignMenu()
bounds[Y] = 0; // set in alignMenu()
bounds[W] = MenuSkin.WIDTH;
bounds[H] = MenuSkin.HEIGHT;
}
/**
* Aligns the menu to the current screen.
*/
protected void alignMenu() {
bounds[W] = MenuSkin.WIDTH;
switch (MenuSkin.ALIGN_X) {
case Graphics.LEFT:
bounds[X] = 0;
break;
case Graphics.HCENTER:
bounds[X] = (ScreenSkin.WIDTH - bounds[W]) / 2;
break;
case Graphics.RIGHT:
default:
bounds[X] = ScreenSkin.WIDTH - bounds[W];
break;
}
switch (MenuSkin.ALIGN_Y) {
case Graphics.TOP:
bounds[Y] = 0;
break;
case Graphics.VCENTER:
bounds[Y] = (ScreenSkin.HEIGHT - SoftButtonSkin.HEIGHT -
bounds[H]) / 2;
break;
case Graphics.BOTTOM:
default:
bounds[Y] = ScreenSkin.HEIGHT - SoftButtonSkin.HEIGHT -
bounds[H];
break;
}
}
/**
* Renders the body of the menu.
* @param g the graphics context to be updated
*/
protected void paintBody(Graphics g) {
if (MenuSkin.TEXT_TITLE != null) {
// IMPL_NOTE enforce MenuSkin.TITLE_MAXWIDTH based on
// title value and font, add '...' to titles which
// are too long to show
g.setFont(MenuSkin.FONT_TITLE);
g.setColor(MenuSkin.COLOR_TITLE);
g.drawString(MenuSkin.TEXT_TITLE,
MenuSkin.TITLE_X,
MenuSkin.TITLE_Y,
Graphics.TOP | MenuSkin.TITLE_ALIGN);
}
if (menuCmds != null) {
int y = MenuSkin.ITEM_TOPOFFSET;
int x = 0;
Image arrow = null;
for (int cmdIndex = scrollIndex;
(cmdIndex < menuCmds.length)
&& (cmdIndex - scrollIndex < MenuSkin.MAX_ITEMS);
cmdIndex++)
{
if (menuCmds[cmdIndex] instanceof SubMenuCommand) {
arrow = MenuSkin.IMAGE_SUBMENU_ARROW;
if (cmdIndex == selI && !cascadeMenuUp) {
arrow = MenuSkin.IMAGE_SUBMENU_ARROW_HL;
}
if (arrow != null) {
x = arrow.getWidth() + 2;
}
}
if (cmdIndex == selI && !cascadeMenuUp) {
if (MenuSkin.IMAGE_ITEM_SEL_BG != null) {
// We want to draw the selected item background
CGraphicsUtil.draw3pcsBackground(g, 3,
((selI - scrollIndex) * MenuSkin.ITEM_HEIGHT) +
MenuSkin.IMAGE_BG[0].getHeight(),
bounds[W] - 3,
MenuSkin.IMAGE_ITEM_SEL_BG);
} else {
g.setColor(MenuSkin.COLOR_BG_SEL);
g.fillRoundRect(MenuSkin.ITEM_ANCHOR_X - 2,
((selI - scrollIndex) * MenuSkin.ITEM_HEIGHT) +
MenuSkin.ITEM_TOPOFFSET,
MenuSkin.FONT_ITEM_SEL.stringWidth(
menuCmds[cmdIndex].getLabel()) + 4 + x,
MenuSkin.ITEM_HEIGHT,
3, 3);
}
}
if (cmdIndex < 9) {
g.setFont((selI == cmdIndex) ?
MenuSkin.FONT_ITEM_SEL :
MenuSkin.FONT_ITEM);
g.setColor((selI == cmdIndex) ?
MenuSkin.COLOR_INDEX_SEL :
MenuSkin.COLOR_INDEX);
g.drawString("" + (cmdIndex + 1),
MenuSkin.ITEM_INDEX_ANCHOR_X,
y, Graphics.TOP | Graphics.LEFT);
}
g.setFont(MenuSkin.FONT_ITEM);
g.setColor((selI == cmdIndex) ? MenuSkin.COLOR_ITEM_SEL :
MenuSkin.COLOR_ITEM);
if (arrow != null) {
g.drawImage(arrow, MenuSkin.ITEM_ANCHOR_X, y + 2,
Graphics.TOP | Graphics.LEFT);
arrow = null;
}
g.drawString(menuCmds[cmdIndex].getLabel(),
MenuSkin.ITEM_ANCHOR_X + x,
y, Graphics.TOP | Graphics.LEFT);
x = 0;
y += MenuSkin.ITEM_HEIGHT;
}
}
}
/**
* Shows the sub menu.
* @param index the offset in the array of menu commands
* @return true if submenu is shown, false - otherwise
*/
private boolean showSubMenu(int index) {
boolean ret = false;
if (menuCmds[index] instanceof SubMenuCommand) {
SubMenuCommand subMenu = (SubMenuCommand)menuCmds[index];
cascadeMenu.setMenuCommands(subMenu.getSubCommands(), this);
cascadeMenu.setAnchorPoint(bounds[X],
bounds[Y] + MenuSkin.ITEM_TOPOFFSET +
((index - scrollIndex) *
MenuSkin.ITEM_HEIGHT));
cascadeMenuUp = true;
owner.addLayer(cascadeMenu);
setScrollInd(ScrollIndLayer.getInstance(ScrollIndSkin.MODE));
// IMPL_NOTE: fix layer inrteraction in removeLayer
btnLayer.requestRepaint();
selI = index;
addDirtyRegion();
ret = true;
}
return ret;
}
/**
* Gets index of highlighted command.
* @return highlighted index
*/
public int getIndex() {
return selI;
}
/**
* Update bounds of layer
* @param layers - current layer can be dependant on this parameter
*/
public void update(CLayer[] layers) {
alignMenu();
if (owner != null && cascadeMenuUp) {
cascadeMenu.update(layers);
if (btnLayer != null) {
showSubMenu(selI);
}
}
super.update(layers);
}
/**
* Scroll content inside of the Menu.
* @param scrollType scrollType. Scroll type can be one of the following
* @see ScrollBarLayer.SCROLL_NONE
* @see ScrollBarLayer.SCROLL_PAGEUP
* @see ScrollBarLayer.SCROLL_PAGEDOWN
* @see ScrollBarLayer.SCROLL_LINEUP
* @see ScrollBarLayer.SCROLL_LINEDOWN or
* @see ScrollBarLayer.SCROLL_THUMBTRACK
* @param thumbPosition
*/
public void scrollContent(int scrollType, int thumbPosition) {
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION,
LogChannels.LC_HIGHUI,
"MenuLayer.scrollContent scrollType=" + scrollType +
" thumbPosition=" + thumbPosition);
}
// keep old scrollIndex
int oldScrollIndex = scrollIndex;
switch (scrollType) {
case ScrollBarLayer.SCROLL_PAGEUP:
uScrollViewport(Canvas.UP);
break;
case ScrollBarLayer.SCROLL_PAGEDOWN:
uScrollViewport(Canvas.DOWN);
break;
case ScrollBarLayer.SCROLL_LINEUP:
uScrollByLine(Canvas.UP);
break;
case ScrollBarLayer.SCROLL_LINEDOWN:
uScrollByLine(Canvas.DOWN);
break;
case ScrollBarLayer.SCROLL_THUMBTRACK:
uScrollAt(thumbPosition);
break;
default:
break;
}
// only if scroll index has been changed do update
if (oldScrollIndex != scrollIndex && scrollIndex >= 0) {
// correct selI if required.
// The selected item always should be on the screen
if (selI < scrollIndex) {
selI = scrollIndex;
} else if (selI >= (scrollIndex + MenuSkin.MAX_ITEMS)) {
selI = scrollIndex + MenuSkin.MAX_ITEMS - 1;
}
updateScrollIndicator();
requestRepaint();
}
}
/**
* Perform a line scrolling in the given direction. This method will
* attempt to scroll the view to show next/previous line.
*
* @param dir the direction of the flip, either DOWN or UP
*/
private void uScrollByLine(int dir) {
switch(dir) {
case Canvas.UP:
if (scrollIndex > 0) {
scrollIndex--;
}
break;
case Canvas.DOWN:
if (scrollIndex < (menuCmds.length - MenuSkin.MAX_ITEMS)) {
scrollIndex++;
}
break;
}
}
/**
* Perform a page flip in the given direction. This method will
* attempt to scroll the view to show as much of the next page
* as possible. It uses the locations and bounds of the items on
* the page to best determine a new location - taking into account
* items which may lie on page boundaries as well as items which
* may span several pages.
*
* @param dir the direction of the flip, either DOWN or UP
*/
private void uScrollViewport(int dir) {
switch (dir) {
case Canvas.UP:
scrollIndex -= MenuSkin.MAX_ITEMS - 1;
if (scrollIndex < 0) {
scrollIndex = 0;
}
break;
case Canvas.DOWN:
scrollIndex += MenuSkin.MAX_ITEMS - 1;
if (scrollIndex > menuCmds.length - MenuSkin.MAX_ITEMS) {
scrollIndex = menuCmds.length - MenuSkin.MAX_ITEMS;
}
break;
}
}
/**
* Perform a scrolling at the given position.
* @param context position
*/
void uScrollAt(int position) {
int viewableH = MenuSkin.ITEM_HEIGHT * menuCmds.length;
int viewportH = MenuSkin.ITEM_HEIGHT * MenuSkin.MAX_ITEMS;
int newY = (viewableH - viewportH) * position / 100;
if (newY < 0) {
newY = 0;
} else if (newY > viewableH - viewportH) {
newY = viewableH - viewportH;
}
scrollIndex = newY / MenuSkin.ITEM_HEIGHT;
}
}
|