/********************************************************************************* * 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.io.*; import totalcross.sys.*; import totalcross.ui.event.*; import totalcross.ui.gfx.*; import totalcross.ui.image.*; import totalcross.util.*; /** * Creates a popup menu with a single line list and some radio buttons at right, like * in the Android combobox styles. * This is a sample of how to use it: * <pre> String[] items = { "Always", "Never", "Only in Silent mode", "Only when not in Silent mode", "Non of the answers above", }; PopupMenu pm = new PopupMenu("Vibrate",items); pm.popup(); * </pre> * A PRESSED event is sent when an item is selected. * * Note: the colors must be set before the control's bounds are defined using setRect or add. * */ public class PopupMenu extends Window { private Object []items; private int selected=-1; private Image off,ball; private ListContainer list; private Button cancel/*,ok - future*/; private ListContainer.Item []containers; private boolean multipleSelection; private int cursorColor=-1; private static final int UNSET = -9999; private int desiredSelectedIndex = UNSET; private IntHashtable htSearchKeys; private PushButtonGroup pbgSearch; /** The string of the button; defaults to "Cancel" */ public static String cancelString = "Cancel"; /** If the items is a String matrix (String[][]), this field sets the column that will be shown. */ public int dataCol; /** Sets the number of elements should be used from the items array passed in the constructor. Defaults to <code>items.length</code>. */ public int itemCount; /** The check color used to fill the radio button used in Android. Defaults to the fore color. * @since TotalCross 1.3 */ public int checkColor = -1; /** Set to true BEFORE popping up the window to enable search on the items of this PopupMenu. * Note that it only works if the items are ORDERED. * @since TotalCross 1.5 */ public boolean enableSearch; /** Set to false BEFORE popping up the window to disable the Cancel button. * @since TotalCross 1.65 */ public boolean enableCancel = true; /** Set to true to keep the selected index unchanged if user press the Cancel button * @since TotalCross 2.0 */ public boolean keepIndexOnCancel; /** Constructs a PopupMenu with the given parameters and without multiple selection support. */ public PopupMenu(String caption, Object []items) throws IOException,ImageException { this(caption,items,false); } /** Constructs a PopupMenu with the given parameters. */ public PopupMenu(String caption, Object []items, boolean multipleSelection) throws IOException,ImageException { super(caption,ROUND_BORDER); this.multipleSelection = multipleSelection; uiAdjustmentsBasedOnFontHeightIsSupported = false; titleColor = Color.WHITE; this.items = items; itemCount = items.length; if (multipleSelection) { off = new Image("totalcross/res/android/checkBkg.png"); ball = new Image("totalcross/res/android/checkSel.png"); } else { off = new Image("totalcross/res/android/radioBkg.png"); ball = new Image("totalcross/res/android/radioSel.png"); } } private Image getSelectedImage(int color) throws ImageException { // "off" image is a composite of two images: on + selection Image on = off.getFrameInstance(0); ball.applyColor2(color); // paint it on.getGraphics().drawImage(ball,0,0); return on; } public void initUI() { try { list = new ListContainer(); list.setFont(this.font); if (cursorColor != -1) list.highlightColor = cursorColor; ListContainer.Layout layout = list.getLayout(3,1); layout.insets.set(10,50,10,50); layout.defaultRightImage = off; layout.defaultRightImage2 = getSelectedImage(checkColor == -1 ? foreColor : checkColor); layout.imageGap = 50; layout.controlGap = 50; // 50% of font's height layout.centerVertically = true; layout.setup(); int cw=-1; containers = new ListContainer.Item[itemCount]; Vm.preallocateArray(new ListContainer.Item(layout), itemCount); Vm.preallocateArray(new String[3], itemCount); if (enableSearch && itemCount <= 10) enableSearch = false; htSearchKeys = new IntHashtable(40); char last = 0; for (int i = 0; i < itemCount; i++) { ListContainer.Item c = new ListContainer.Item(layout); containers[i] = c; String s = items[i] instanceof String ? (String)items[i] : (items[i] instanceof String[]) ? ((String[])items[i])[dataCol] : items[i].toString(); if (enableSearch && s.length() > 0) { char cc = s.charAt(0); if (cc != last) { last = cc; htSearchKeys.put(Convert.toUpperCase(cc), i); } } if (cw == -1) cw = getClientRect().width - Math.abs(c.getLeftControlX()) - Math.abs(c.getRightControlX()); int sw = fm.stringWidth(s); if (sw <= cw) c.items = new String[]{"",s,""}; else { String[] parts = Convert.tokenizeString(Convert.insertLineBreak(cw,fm,s),'\n'); c.items = new String[]{"","",""}; for (int j = 0, n = Math.min(parts.length, c.items.length); j < n; j++) c.items[j] = parts[j]; } c.appId = i; } if (htSearchKeys.size() <= 1) enableSearch = false; ScrollContainer sc2 = null; if (enableSearch) { IntVector v = htSearchKeys.getKeys(); v.qsort(); String[] caps = new String[v.size()]; for (int i = 0; i < caps.length; i++) caps[i] = Convert.toString((char)v.items[i]); pbgSearch = new PushButtonGroup(caps,false,-1,0,fmH,1,true,PushButtonGroup.BUTTON); add(sc2 = new ScrollContainer(true, false),LEFT,TOP,FILL,fmH*2); sc2.add(pbgSearch, LEFT,TOP,PREFERRED,FILL); } if (enableCancel) add(cancel = new Button(cancelString),CENTER,BOTTOM-fmH/2,PARENTSIZE+90,PREFERRED+fmH); add(list = new ListContainer(),LEFT,enableSearch ? AFTER : TOP,FILL,(enableCancel?FIT:FILL)-fmH/2, enableSearch ? sc2 : null); list.setBackColor(Color.WHITE); list.addContainers(containers); repositionOnSize(); } catch (Exception e) { if (Settings.onJavaSE) e.printStackTrace(); throw new RuntimeException(e.getClass().getName()+" "+e); } } private void repositionOnSize() { if (containers == null) return; int hh = containers[containers.length-1].getY2(); int hm = list.y+hh+(cancel==null?0:cancel.height)+fmH; if (this.height > hm) { list.height = hh+fmH/3; setRect(CENTER,CENTER,KEEP,hm); if (cancel != null) cancel.setRect(KEEP,BOTTOM-fmH/3,KEEP,KEEP); } } public void reposition() { super.reposition(); repositionOnSize(); } /** Selects the given index. */ public int setSelectedIndex(int index) { if (containers == null) { desiredSelectedIndex = index; return -1; } if (-1 <= index && index < containers.length) selected(index); if (0 <= selected && selected < containers.length) list.scrollToControl(containers[selected]); return selected; } /** Returns the selected index when this window was closed or -1 if non was selected */ public int getSelectedIndex() { return desiredSelectedIndex != UNSET ? desiredSelectedIndex : selected; } /** Setup some important variables */ protected void onPopup() { if (list == null) { int maxW = Math.max(!enableCancel ? 0 : fm.stringWidth(cancelString), title == null ? 0 : titleFont.fm.stringWidth(title))+fmH*4; for (int i = 0; i < itemCount; i++) { String s = items[i] instanceof String ? (String)items[i] : (items[i] instanceof String[]) ? ((String[])items[i])[dataCol] : items[i].toString(); int w = fm.stringWidth(s) + fmH*6; if (w > maxW) maxW = w; } setRect(CENTER,CENTER,maxW < Math.min(Settings.screenWidth,Settings.screenHeight)-fmH*2 ? maxW : SCREENSIZE+90,SCREENSIZE+90); } if (desiredSelectedIndex != UNSET) // change only if used wanted it setSelectedIndex(desiredSelectedIndex); desiredSelectedIndex = UNSET; } protected void postUnpop() { if (selected != -1) // guich@580_27 postPressedEvent(); } private void search(char c) { int pos = htSearchKeys.get(Convert.toUpperCase(c),-1); if (pos != -1) list.scrollToControl(containers[pos]); } public void onEvent(Event event) { switch (event.type) { case KeyEvent.KEY_PRESS: if (enableSearch) search((char)((KeyEvent)event).key); break; case ControlEvent.PRESSED: if (enableSearch && event.target == pbgSearch && pbgSearch.getSelectedIndex() != -1) search(pbgSearch.getSelectedItem().charAt(0)); else if (cancel != null && event.target == cancel) { if (!keepIndexOnCancel) selected = -1; unpop(); } break; case ListContainerEvent.ITEM_SELECTED_EVENT: { ListContainerEvent lce = (ListContainerEvent)event; selected(((Control)lce.source).appId); if (!multipleSelection) { Vm.sleep(100); unpop(); } break; } case ListContainerEvent.RIGHT_IMAGE_CLICKED_EVENT: { ListContainerEvent lce = (ListContainerEvent)event; //if (lce.isImage2) since tc 1.5, when this event is sent the image 2 was already replaced by image 1 { int idx; if (event.target instanceof Control) idx = ((Control)event.target).parent.appId; else idx = lce.source.appId; selected(idx); if (!multipleSelection) { Vm.sleep(100); unpop(); } } break; } } } private void selected(int newSel) { if (0 <= selected && selected < containers.length) containers[selected].setImage(false,true); selected = newSel; if (0 <= selected && selected < containers.length) containers[newSel].setImage(false,false); if (selected == -1) list.setSelectedIndex(-1); repaintNow(); } /** Sets the cursor color. By default, it is based in the background color */ public void setCursorColor(int c) { cursorColor = c; } }