/********************************************************************************* * TotalCross Software Development Kit * * Copyright (C) 2000-2012 SuperWaba Ltda. * * All Rights Reserved * * * * This library and virtual machine 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. * * * * This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 * * A copy of this license is located in file license.txt at the root of this * * SDK or can be downloaded here: * * http://www.gnu.org/licenses/lgpl-3.0.txt * * * *********************************************************************************/ package totalcross.ui; import totalcross.ui.event.*; import totalcross.ui.gfx.*; import totalcross.sys.*; /** Constructs a Menu with the given items. * A menu can be opened by the user in a couple of ways: * <ul> * <li> By clicking in the Menu button * <li> By clicking in the title of a Window * </ul> * The Menu supports disabled and checked items. * Here is an example of how to build the menu:<br> * <pre> * MenuItem miBeamEvent,miNewEvent,miDeleteEvent; * MenuItem col0[] = * { * new MenuItem("Record"), // caption for the MenuBar * miNewEvent = new MenuItem("NewEvent",false), // checked item, starting unchecked * miDeleteEvent = new MenuItem("Delete Event...",true), // checked item, starting checked * new MenuItem("Attach Note"), * new MenuItem("Delete Note..."), * new MenuItem("Purge..."), * beamEvent = new MenuItem("Beam Event"), * }; * MenuItem col1[] = * { * new MenuItem("Edit"), // caption for the MenuBar * new MenuItem("Undo"), * new MenuItem("Cut"), * new MenuItem("Copy"), * new MenuItem("Paste"), * new MenuItem("Select All"), * new MenuItem(), // a separator * new MenuItem("Keyboard"), * new MenuItem("Graffiti Help"), * }; * MenuItem col2[] = * { * new MenuItem("Options"), // caption for the MenuBar * new MenuItem("Font..."), * new MenuItem("Preferences..."), * new MenuItem("Display Options..."), * new MenuItem("Phone Lookup"), * new MenuItem("About Date Book"), * }; * setMenuBar(new MenuBar(new MenuItem[][]{col0,col1,col2})); * beamEvent.isEnabled = false; * ... * // at later time, disable the "new event" and enable the "delete event" * miNewEvent.isChecked = true; * miDeleteEvent.isChecked = false; * </pre> * The menu can be closed by a click on a valid item or clicking outside of its bounds. * A PRESSED event will be thrown when the menu is closed and a menu item was selected. * To discover which item was selected, see method getSelectedIndex, which returns -1 if none, * or the matrix index otherwise. * <p> * Note that the separator dotted line doesn't generate events and can't be selected. * <p> * To convert the old menu form (using a string matrix) into the new form using a MenuItem matrix, * you can use this idea: * <br>Old format: * <pre> * // the string arrays menuArq, menuUtil and menuSobre are initialized somewhere else * setMenuBar(mbar = new MenuBar(new String[][]{menuArq,menuUtil,menuSobre})); * mbar.setChecked(106,true); * mbar.setEnabled(106,false); * mbar.setChecked(4, true); * </pre> * New format: * <pre> * // declare these as global variables: * MenuItem mi4, mi106; * * // at initUI * setMenuBar(mbar = new MenuBar(MenuBar.strings2items(new String[][]{menuArq,menuUtil,menuSobre}))); * MenuItem[][] mis = mbar.getMenuItems(); * mi4 = mis[0][4]; * mi106 = mis[1][6]; * mi106.isChecked = true; * mi106.isEnabled = false; * mi4.isChecked = true; * </pre> * Note that, after changing the isChecked and isEnabled states, there's no need to call repaint, * bacause they will show up only the next time the menu bar opens. */ public class MenuBar extends Window { protected MenuItem [][]items; private int []xpos; private int xmin,xmax; /** Note: if you want to change the spacement between the menu items (maybe to make more items fit in the row), change this gap value. Default is 3 (3 pixels at left and 3 at right). */ public int gap = 3; // guich@200b4_89 private int selected=0; private int menuItemSelected; private boolean switching; private MenuBarDropDown pop; private int eColor,dColor,bColor,cursorColor=-1; private int fourColors[] = new int[4]; private int popFore=-1,popBack=-1,popCursor=-1; /** Converts a String matrix into a MenuItem matrix. */ public static MenuItem[][] strings2items(String[][] items) { MenuItem[][] its = new MenuItem[items.length][]; for (int i =0; i < its.length; i++) its[i] = MenuBarDropDown.strings2items(items[i]); return its; } /** Create a MenuBar with the given menu items. */ public MenuBar(MenuItem [][]items) { started = true; // avoid calling the initUI method canDrag = false; setBackColor(Color.WHITE); borderStyle = -1; // guich@220_48 this.items = items; setFont(getFont().asBold()); // use a bold font. } /** Returns the matrix of Menuitems passed in the constructor. */ public MenuItem[][] getMenuItems() { return this.items; } /** Returns the MenuItem at the given index. E.g.: * <pre> * MenuItem file = mbar.getMenuItem(102); * </pre> */ public MenuItem getMenuItem(int index) { return items[index / 100][index % 100]; } /** Set the colors for the popup windows. You can pass <code>-1</code> * to any parameter to keep the current settings. */ public void setPopColors(int back, int fore, int cursor) { this.popFore = fore; this.popBack = back; this.popCursor = cursor; } /** Sets the cursor color. By default, it is based in the background color */ public void setCursorColor(int c) // guich@220_49 { cursorColor = c; } /** Called by the Window class to popup this MenuBar */ public void setVisible(boolean b) { if (b) { setRect(0,0,FILL,fmH+4+titleGap); // update the bounds, because the screen may have been changed popupNonBlocking(); } } /** Change the font and recompute some parameters */ protected void onFontChanged() // guich@200b4_153 { // all this is recalculated because the font had changed int n = items.length; xpos = new int[n+1]; xpos[0] = xmax = xmin = 4; int temp = (gap<<1)-1; for (int i=0; i < n; i++) { xmax += fm.stringWidth(items[i][0].caption)+temp; xpos[i+1] = xmax; } xmax--; } /** Returns the current menu item selected. * @return -1 if no item was selected, or a number with format <i>xyy</i>, where x is the index of the item * selected at the MenuBar and yy is the index+1 ( = the index of the String array that defines the items) * of the item selected in the MenuBarDropDown. * EG: suppose that the col1/"Cut" of the * example at the top of this page was clicked, then 102 is returned. */ public int getSelectedIndex() { return menuItemSelected; } /** Setup some important variables */ protected void onPopup() { menuItemSelected = -1; enableUpdateScreen = false; // avoid flicker } /** Close the popup list with a click outside its bounds */ protected boolean onClickedOutside(PenEvent event) { close(); return true; } void close() { if (pop != null) pop.unpop(); unpop(); } private int getItemAt(int x) { int n = xpos.length-1; for (int i=0; i < n; i++) if (xpos[i] <= x && x < xpos[i+1] && items[i][0].isEnabled) return i; return -1; } public void onEvent(Event event) { switch (event.type) { case KeyEvent.SPECIAL_KEY_PRESS: if (((KeyEvent)event).key == SpecialKeys.MENU) close(); break; case ControlEvent.WINDOW_CLOSED: if (!switching && event.target == pop) { int row = pop.getSelectedIndex(); pop = null; if (row != -1) // closed bc a item was clicked? { MenuItem mi = items[selected][row]; menuItemSelected = selected * 100 + row; if (mi.isCheckable) mi.isChecked = !mi.isChecked; close(); } else menuItemSelected = -1; } break; case PenEvent.PEN_DOWN: case PenEvent.PEN_DRAG: PenEvent pe = (PenEvent)event; if (xmin <= pe.x && pe.x <= xmax) { int newSelected = getItemAt(pe.x); if (newSelected != selected) { switchTo(newSelected); } } else if (selected != -1) // outside valid area? { selected = -1; switchTo(selected); } break; } } protected void postUnpop() { if (menuItemSelected != -1) // guich@580_27 postPressedEvent(); } protected void postPopup() { if ( selected != -1 ) switchTo(selected); } protected void switchTo(int index) { enableUpdateScreen = false; // avoid flicker selected = index; switching = true; if (pop != null) { pop.unpop(); MainWindow.repaintActiveWindows(); } if (index != -1) { pop = new MenuBarDropDown(xpos[index],height-2,items[index]); pop.titleGap = this.titleGap; // propagate to the drop down since user may change our's pop.setTextShadowColor(textShadowColor); pop.setFont(this.font); // guich@350_8: added a fix when the user changes the font if (borderStyle == NO_BORDER) // guich@220_48 pop.setBorderStyle(NO_BORDER); pop.setBackForeColors(popBack!=-1?popBack:backColor, popFore!=-1?popFore:foreColor); if (popCursor != -1) pop.setCursorColor(popCursor); // guich@220_49 pop.popupNonBlocking(); } else pop = null; enableUpdateScreen = true; switching = false; } protected void onColorsChanged(boolean colorsChanged) { eColor = getForeColor(); dColor = Color.getCursorColor(eColor); bColor = getBackColor(); if (colorsChanged) { Graphics.compute3dColors(true,backColor,foreColor,fourColors); if (cursorColor == -1) cursorColor = 0x0000E0; } } public void onPaint(Graphics g) { // changed drawing of menu border to look more native // PMD 25Oct2001 g.foreColor = eColor; g.backColor = bColor; switch (borderStyle) { case -1: g.draw3dRect(0,0,width,height,Graphics.R3D_SHADED,false,false,fourColors); // guich@220_48 break; case RECT_BORDER: g.drawRect(0,0,width,height); // guich@402_60 break; case BORDER_NONE: if (uiVista) g.fillVistaRect(0,0,width,height,backColor, false,false); break; } // paint border g.foreColor = eColor; g.setFont(font); if (selected != -1) { if (!uiAndroid && uiVista && borderStyle == BORDER_NONE) { g.backColor = popCursor != -1 ? popCursor : popBack; g.fillRect(xpos[selected],1,xpos[selected+1]-xpos[selected],height-2); } else { g.backColor = cursorColor != -1 ? cursorColor : Color.getCursorColor(bColor); // guich@220_49 g.fillRect(xpos[selected],1,xpos[selected+1]-xpos[selected],height-2); } } // paint captions int yy = (height-fmH)/2; for (int i =0; i < items.length; i++) { MenuItem mi = items[i][0]; if (mi.isEnabled) g.drawText(mi.caption,xpos[i]+gap,yy, textShadowColor != -1, textShadowColor); else { g.foreColor = dColor; g.drawText(mi.caption,xpos[i]+gap,yy, textShadowColor != -1, textShadowColor); g.foreColor = eColor; } } } /** Moves to the MenuItem array at left or right depending on the passed value (-1 or +1). */ public void moveBy(int i) { int newSelected; if (i > 0) newSelected = Settings.circularNavigation ? (selected+1) % items.length : Math.min(selected+1,items.length-1); else newSelected = Settings.circularNavigation ? (selected<=0 ? items.length-1 : selected-1) : Math.max(selected-1,0); if (selected != newSelected) switchTo(newSelected); } protected boolean handleFocusChangeKeys(KeyEvent ke) // guich@512_1: transfer focus on tab keys - fdie@550_15 : transfer also on arrow keys { if (ke.isActionKey() || ke.isUpKey() || ke.isDownKey()) close(); else if (ke.key == SpecialKeys.LEFT) moveBy(-1); else if (ke.key == SpecialKeys.RIGHT || ke.key == SpecialKeys.TAB) moveBy(1); else return false; return true; } /** Sets the style of this menubar to an alternative style (no borders, rectangular appearance). * Here's a sample, taken from the UIGadgets sample: * <pre> * mbar.setAlternativeStyle(Color.BLUE,Color.WHITE); * </pre> * The colors are set using a combination of the given back and fore colors. You can have a more * flexible color selection by using the code below, instead of calling this method. Under most * situations, tho, this method is enough. * <pre> mbar.setBackForeColors(Color.BLUE, Color.WHITE); mbar.setCursorColor(0x6464FF); mbar.setBorderStyle(NO_BORDER); mbar.setPopColors(0x0078FF, Color.CYAN, -1); // use the default cursor color for the popup menu (last -1 param) * </pre> * @since TotalCross 1.0 beta 4 */ public void setAlternativeStyle(int back, int fore) { setBackForeColors(back, fore); int c1,c2; setCursorColor(Color.interpolate(back,fore)); setBorderStyle(NO_BORDER); setPopColors(c1=Color.brighter(back,32), c2=Color.darker(fore,32), Color.interpolate(c1,c2)); // use the default cursor color for the popup menu (last null param) } }