/********************************************************************************* * 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.dialog; import totalcross.ui.*; import totalcross.ui.event.*; import totalcross.ui.font.*; import totalcross.ui.gfx.*; import totalcross.sys.*; /** This class is used by the Edit class when its mode is set to CURRENCY and displays * a calculator with six basic operations and a numeric pad. */ public class CalculatorBox extends Window { /** Implement this interface to check if the given value is a valid value to be returned. * Return null if the value is correct, or a message that will be displayed to the user if not. * * Note that the range check only occurs when the user do NOT press Cancel. * * <pre> * Edit ed = new Edit(); * ed.rangeCheck = new CalculatorBox.RangeCheck() * { * public String check(double value) * { * if (value > 0 && value < 10) * return null; * return "The value must be between 0 and 10"; * } * }; * ed.setMode(Edit.CURRENCY,true); * add(ed,LEFT,TOP); * </pre> * * @since TotalCross 2.0 */ public static interface RangeCheck { public String check(double value); } /** The RangeCheck instance that will be called before the user closes the box. */ public RangeCheck rangeCheck; /** The Edit used to show the number. */ public Edit edNumber; private PushButtonGroup pbgAction,numericPad,pbgArrows,pbgOp,pbgOp2,pbgEq; private String answer; private KeyEvent ke = new KeyEvent(),backspace; // guich@421_59 private boolean showOperations; private String ds; // calculator computations private String last = null; private boolean clearNext; private int lastSel=-1; private boolean callNext; /** Strings used to display the action messages. You can localize these strings if you wish. */ public static String []actions = {"Clear","Ok","Cancel"}; // guich@320_44: added reuse button /** The default title. */ public static String defaultTitle = "Numeric Pad"; /** Defines an optional character to be used in the NumericBox. Replaces the decimal separator / 00 char. * @since TotalCross 1.5 */ public String optionalValue; /** The maximum length for the edit that will be created. */ public int maxLength=-2; /** The default value of the edit. */ public String defaultValue; /** The control that had focus when this CalculatorBox was popped up. */ public Control cOrig; /** The desired control that will be the original one. */ public Control cOrigDefault; /** Set to true to don't replace the original value in the Edit if user pressed Ok. */ public boolean keepOriginalValue; /** Set to true to replace the "Clear" button by the "Next" button. This * button is equivalent to the "Ok" button, but it also changes the focus to * the next field. The user can still clean the edit by clicking the backspace << button. * * The default behaviour calls moveFocusToNextControl. You can change it by overriding the * method <code>gotoNext</code>. * * @since TotalCross 1.53 */ public static boolean showNextButtonInsteadOfClear; /** Constructs a CalculatorBox with the 6 basic operations visible. */ public CalculatorBox() { this(true); } /** Constructs a CalculatorBox with the 6 basic operations hidden. */ public CalculatorBox(boolean showOperations) { super(defaultTitle,uiAndroid ? ROUND_BORDER : RECT_BORDER); // with caption and borders fadeOtherWindows = Settings.fadeOtherWindows; transitionEffect = Settings.enableWindowTransitionEffects ? TRANSITION_OPEN : TRANSITION_NONE; started = true; uiAdjustmentsBasedOnFontHeightIsSupported = false; backspace = new KeyEvent(); backspace.type = KeyEvent.SPECIAL_KEY_PRESS; backspace.key = SpecialKeys.BACKSPACE; this.showOperations = showOperations; } private void setupUI(boolean isReposition) // guich@tc100b5_28 { setBackColor(showOperations ? UIColors.calculatorBack : UIColors.numericboxBack); // before control definitions! setRect(LEFT,TOP,WILL_RESIZE,WILL_RESIZE); int hh = fmH*2; if (!isReposition || edNumber == null) { if (edNumber != null) remove(edNumber); edNumber = cOrig != null && cOrig instanceof Edit ? ((Edit)cOrig).getCopy() : new Edit(); edNumber.setKeyboard(Edit.KBD_NONE); edNumber.autoSelect = true; if (cOrig != null && cOrig instanceof SpinList) { edNumber.setDecimalPlaces(0); edNumber.setMode(Edit.CURRENCY,true); } if (maxLength != -2) edNumber.setMaxLength(maxLength); backspace.target = edNumber; add(edNumber); } Font f = font.adjustedBy(3,false); edNumber.setFont(f); edNumber.setRect(LEFT+2,TOP+4, Math.min(hh*5,Settings.screenWidth-20),PREFERRED); // positioning arrows if (pbgArrows == null) { pbgArrows = new PushButtonGroup(new String[]{"<",">","<<"},false,-1,2,12,1,true,PushButtonGroup.BUTTON); pbgArrows.autoRepeat = true; pbgArrows.setFocusLess(true); pbgArrows.clearValueInt = -1; add(pbgArrows); } pbgArrows.setRect(SAME,AFTER+4,SAME,hh); // numeric pad if (numericPad == null) { add(numericPad=new PushButtonGroup(new String [] {"1","2","3","4","5","6","7","8","9","00","0","�"},false,-1,2,10,4,true,PushButtonGroup.BUTTON)); numericPad.setFont(font.adjustedBy(2)); numericPad.setFocusLess(true); // guich@320_32 numericPad.clearValueInt = -1; } numericPad.setRect(SAME, AFTER+4,SAME,Settings.screenHeight > hh*8 ? hh*6 : hh*4); // guich@571_9 String[] names = numericPad.names; ds = Convert.toString(Settings.decimalSeparator); names[9] = optionalValue != null ? optionalValue : edNumber.getMode() == Edit.CURRENCY && edNumber.getDecimalPlaces() > 0 ? "00" : ds; numericPad.setNames(names); if (pbgAction == null) { pbgAction = new PushButtonGroup(actions,false,-1,2,12,1,true,PushButtonGroup.BUTTON); pbgAction.setFocusLess(true); pbgAction.clearValueInt = -1; add(pbgAction); } pbgAction.setRect(SAME,AFTER+2,SAME,hh); if (showOperations && pbgOp == null) { Font ff = numericPad.getFont(); String []opers = {"+","-","*","�"}; pbgOp = new PushButtonGroup(opers,false,-1,2,12,opers.length,true,PushButtonGroup.NORMAL); pbgOp.setFont(ff); pbgOp.setFocusLess(true); pbgOp.clearValueInt = -1; pbgOp.setCursorColor(Color.ORANGE); add(pbgOp); pbgOp2 = new PushButtonGroup(new String[]{"%"},false,-1,2,12,1,true,PushButtonGroup.BUTTON); pbgOp2.setFont(ff); pbgOp2.setFocusLess(true); pbgOp2.setCursorColor(Color.ORANGE); pbgOp2.clearValueInt = -1; add(pbgOp2); pbgEq = new PushButtonGroup(new String[]{"="},false,-1,2,12,1,true,PushButtonGroup.BUTTON); pbgEq.setFont(ff); pbgEq.setFocusLess(true); pbgEq.clearValueInt = -1; add(pbgEq); } if (pbgOp != null) { pbgOp.setRect(AFTER+2,SAME,hh,SAME,numericPad); pbgOp2.setRect(AFTER+2,SAME,hh,SAME,pbgArrows); pbgEq.setRect(AFTER+2,SAME,hh,SAME,pbgAction); edNumber.setRect(KEEP,KEEP,edNumber.getWidth()+2+hh,KEEP); } setInsets(2,2,2,2); resize(); setRect(CENTER,CENTER,KEEP,KEEP); numericPad.setBackColor(showOperations ? UIColors.calculatorFore : UIColors.numericboxFore); pbgAction.setBackColor(showOperations ? UIColors.calculatorAction : UIColors.numericboxAction); pbgArrows.setBackColor(showOperations ? UIColors.calculatorAction : UIColors.numericboxAction); edNumber.setBackColor(backColor); if (pbgOp != null) pbgEq.setBackColor(showOperations ? UIColors.calculatorAction : UIColors.numericboxAction); } /** Gets the answer that the user selected to be pasted. * It can be the first operator, the total computed or null if the user canceled. */ public String getAnswer() // guich@200b4_193: get the 'pasted' answer { return answer; } public void clear() { super.clear(); last = answer = null; } public void onUnpop() { setFocus(this); } public void onPopup() { Control c = topMost.getFocus(); cOrig = cOrigDefault != null ? cOrigDefault : c instanceof Edit || c instanceof SpinList ? (Control)c : null; setupUI(false); clear(); if (cOrig != null) { String s = cOrig instanceof Edit ? ((Edit)cOrig).getTextWithoutMask() : ((SpinList)cOrig).getSelectedItem(); if (s.length() == 0 || "+-0123456789".indexOf(s.charAt(0)) != -1) // guich@401_16: added support for + and changed the routine edNumber.setText(s); } if (defaultValue != null) edNumber.setText(defaultValue); } public void postPopup() { setFocus(edNumber); } protected void postUnpop() { if (answer != null) // guich@580_27 postPressedEvent(); if (cOrig != null && callNext) { callNext = false; Control next = gotoNext(); if (next != null && next instanceof Edit) ((Edit)next).showKeyboardOnNextEvent = true; } } /** Returns the next control to be focused when Next is clicked. * By default, calls moveFocusToNextControl. * @since TotalCross 1.53 */ protected Control gotoNext() { return cOrig.getParent().moveFocusToNextControl(cOrig, true); } public void onEvent(Event event) { try { switch (event.type) { case KeyEvent.SPECIAL_KEY_PRESS: if (clearNext) { clearNext = false; edNumber.clear(); } int key = ((KeyEvent)event).key; if (key == SpecialKeys.CALC) unpop(); break; case ControlEvent.PRESSED: if (pbgEq != null && event.target == pbgEq && pbgEq.getSelectedIndex() != -1) compute(-2); else if (pbgOp != null && event.target == pbgOp && pbgOp.getSelectedIndex() != -1) compute(pbgOp.getSelectedIndex()); else if (pbgOp2 != null && event.target == pbgOp2 && pbgOp2.getSelectedIndex() != -1) compute(-3); else if (event.target == pbgArrows && pbgArrows.getSelectedIndex() != -1) { switch (pbgArrows.getSelectedIndex()) { case 0: { int p = edNumber.getCursorPos()[1] - 1; if (p >= 0) edNumber.setCursorPos(p,p); break; } case 1: { int p = edNumber.getCursorPos()[1] + 1; if (p <= edNumber.getLength()) edNumber.setCursorPos(p,p); break; } case 2: { edNumber.onEvent(backspace); break; } } } else if (event.target == pbgAction && pbgAction.getSelectedIndex() != -1) { switch (pbgAction.getSelectedIndex()) { case 0: if (showNextButtonInsteadOfClear) { callNext = true; ok(); } else { clear(); if (pbgOp != null) pbgOp.clear(); } break; case 1: ok(); break; case 2: clear(); unpop(); break; } } else if (event.target == numericPad) { String s = numericPad.getSelectedItem(); if (s != null) { if (s.equals(ds) && (clearNext || edNumber.getLength() == 0 || edNumber.getText().indexOf(ds) != -1)) return; if (s.equals("�")) { String t = unformat(edNumber.getTextWithoutMask()); if (t.length() > 0) { if (t.startsWith("-")) t = t.substring(1); else t = "-".concat(t); edNumber.setText(t); edNumber.setCursorPos(t.length(),t.length()); } } else { if (clearNext) { clearNext = false; edNumber.clear(); } for (int i =0, n = s.length(); i < n; i++) { ke.key = s.charAt(i); ke.target = edNumber; edNumber._onEvent(ke); } } } } break; } } catch (Exception ee) { MessageBox.showException(ee,true); } } private void ok() throws InvalidNumberException { answer = unformat(edNumber.getTextWithoutMask()); String msg; if (answer != null && answer.length() > 0 && rangeCheck != null && (msg = rangeCheck.check(Convert.toDouble(answer))) != null) new MessageBox(title,msg).popup(); else { if (cOrig != null && !keepOriginalValue) { if (cOrig instanceof Edit) ((Edit)cOrig).setText(answer,true); else ((SpinList)cOrig).setSelectedItem(answer); } unpop(); } } public void reposition() { setupUI(true); } private void compute(int selectedIndex) throws Exception { try { switch (selectedIndex) { case -3: // % if (edNumber.getLength() > 0) { double d2 = Convert.toDouble(unformat(edNumber.getTextWithoutMask())); if (last == null) // compute % of the visible number only last = showResult(d2/100); else // apply the % to the previous number { double d1 = Convert.toDouble(last); double res = d1 * d2 / 100; showResult(res); // keep last } } return; case -2: // = case 0: // + case 1: // - case 2: // * case 3: // / case 4: // ^ if (last == null && edNumber.getLength() == 0) { pbgOp.clear(); return; } if (last != null && lastSel != -1) { double d1 = Convert.toDouble(last); double d2 = Convert.toDouble(unformat(edNumber.getTextWithoutMask())); double res=0; switch (lastSel) { case 0: res = d1 + d2; break; case 1: res = d1 - d2; break; case 2: res = d1 * d2; break; case 4: res = Math.pow(d1, d2); break; case 3: if (d2 == 0) new MessageBox("Error","Division by 0").popup(); else res = d1 / d2; break; } showResult(res); if (selectedIndex == -2) pbgOp.clear(); } if (edNumber.getLength() == 0) { last = null; clearNext = false; } else { last = unformat(edNumber.getTextWithoutMask()); if (last.endsWith(".")) { last = last.substring(0,last.length()-1); edNumber.setText(last); } clearNext = true; } break; } lastSel = selectedIndex == -2 ? -1 : selectedIndex; } catch (InvalidNumberException ine) { new MessageBox("Message","Overflow or underflow error!"); } } private static String unformat(String s) { if (s.indexOf(',') >= 0) return Convert.replace(s,".","").replace(',','.'); return s; } private String showResult(double res) { int dc = res == (double)(int)res ? 0 : edNumber.getDecimalPlaces(); String s = Convert.toString(res,dc); int p = s.indexOf('.'); if (p != -1) while (s.length() > p+1 && s.endsWith("0")) s = s.substring(0,s.length()-1); if (edNumber.getMode() != Edit.CURRENCY && p != -1 && Settings.decimalSeparator != '.') s = s.replace('.',Settings.decimalSeparator); edNumber.setText(s); return s; } }