/********************************************************************************* * 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 tc.samples.app.calc; import totalcross.io.*; import totalcross.res.*; import totalcross.sys.*; import totalcross.ui.*; import totalcross.ui.dialog.*; import totalcross.ui.event.*; import totalcross.ui.font.*; import totalcross.ui.gfx.*; import totalcross.util.*; /** * Full calculator program to serve as a SuperWaba example. This software is * released as freeware at PalmGear */ public class Calculator extends MainWindow { // variables related to the user interface private Edit edNum; private TabbedContainer tpOpers; private ListBox lbHist; private PushButtonGroup pbgOpers1, pbgOpers2; private Radio rbDec, rbHex, rbBin, rbRad, rbDeg; private MenuBar mbar; private String catName = "History." + Settings.applicationId + ".DATA"; private MenuItem miSaveHist; private String opers1[] = {"7", "8", "9", "/", "A", "B", "4", "5", "6", "*", "C", "D", "1", "2", "3", "-", "E", "F", "0", " +-", ".", "+", "=", "clr"}; private String opers2[] = {"e^x", "Ln", "Log", "X^2", "X^3", "X^Y", "1/x", "Sqr", "N!", "Mod", "Rand", "Sin", "Cos", "Tan", "Int", "Shl", "Shr", "Rol", "Ror", "clr", "And", "Or", "Xor", "Not", "="}; public Calculator() { super("Calculator", BORDER_NONE); setUIStyle(Settings.Android); setTitle(""); } private static final int BACK = 0x50A0FF; public void initUI() { setBackColor(Color.WHITE); final Bar bar = new Bar("Calculator"); bar.setBackForeColors(BACK,Color.WHITE); bar.addButton(Resources.menu); bar.addButton(Resources.exit); bar.addPressListener(new PressListener() { public void controlPressed(ControlEvent e) { switch (bar.getSelectedIndex()) { case 0: exit(0); break; case 1: popupMenuBar(); break; } } }); add(bar,LEFT,0,FILL,fmH*3/2); // add a menubar MenuItem col0[] = { new MenuItem(" File "), new MenuItem("Set precision"), new MenuItem(), miSaveHist = new MenuItem("Save history", false), new MenuItem("Clear history"), new MenuItem(), new MenuItem("Exit"), }; MenuItem col1[] = { new MenuItem(" Edit "), new MenuItem("Copy"), new MenuItem("Paste"), }; MenuItem col2[] = { new MenuItem(" ? "), new MenuItem("Instructions"), new MenuItem("About"), }; setMenuBar(mbar = new MenuBar(new MenuItem[][] {col0, col1, col2})); mbar.setBackForeColors(BACK, Color.WHITE); mbar.setCursorColor(BACK); mbar.setBorderStyle(NO_BORDER); mbar.setPopColors(0x0078FF, Color.CYAN, -1); // use the default cursor color for the popup menu (last null param) // restore app settings String[] history = null; if (Settings.appSettings != null) { try { miSaveHist.isChecked = Settings.appSettings.charAt(0) == '1'; places = Convert.toInt(Settings.appSettings.substring(1)); } catch (Exception e) { Settings.appSettings = null; } // corrupted appSettings... if (miSaveHist.isChecked) { PDBFile cat; try { cat = new PDBFile(catName, PDBFile.READ_WRITE); DataStream ds = new DataStream(cat); history = ds.readStringArray(); cat.close(); } catch (totalcross.io.IOException e) { e.printStackTrace(); } // corrupted or not found catalog } } dotZero = '.' + Convert.zeroPad("", places); String tits[] = {"Basic", "Advanced", "History"}; int x; // load the right font and check if it was successfully loaded Font tinyFont = Settings.screenWidth < 200 ? Font.getFont(false, Font.getDefaultFontSize()-3) : this.font; // add the numbers Edit filling until end and with preferred height edNum = new Edit(); // here we must use the tiny font so a 32bit binary number can be entirely shown edNum.setFont(tinyFont); edNum.setMode(Edit.CURRENCY); edNum.setText("0"); edNum.setEditable(false); edNum.setKeyboard(Edit.KBD_NONE); add(edNum); edNum.setRect(4, AFTER + 2, FILL - 4, PREFERRED); // The operations TabPanel will occupy the rest of the available space add(tpOpers = new TabbedContainer(tits)); tpOpers.setRect(0, AFTER + 10, FILL, FILL); tpOpers.getContainer(0).setBackColor(Color.WHITE); tpOpers.getContainer(1).setBackColor(Color.WHITE); tpOpers.getContainer(2).setBackColor(Color.WHITE); tpOpers.setBackForeColors(BACK,Color.WHITE); // panel 0: Basic operations // add the basic operation PushButtonGroup Container panel = tpOpers.getContainer(0); panel.add(pbgOpers1 = new PushButtonGroup(opers1, false, -1, 5, 8, 4, true, PushButtonGroup.BUTTON)); // add the base radios RadioGroupController rg = new RadioGroupController(); panel.add(rbDec = new Radio("Dec", rg)); panel.add(rbHex = new Radio("Hex", rg)); panel.add(rbBin = new Radio("Bin", rg)); // appId can be used to store any application data // Here we store the base that the radio corresponds to rbDec.appId = 10; rbHex.appId = 16; rbBin.appId = 2; // calculate the ideal position to center the three radios x = (getSize().x - (rbDec.getPreferredWidth() + rbHex.getPreferredWidth() + rbBin.getPreferredWidth() + 20)) / 2; rbDec.setRect(x, 3, PREFERRED, PREFERRED); rbHex.setRect(AFTER + 10, 3, PREFERRED, PREFERRED); rbBin.setRect(AFTER + 10, 3, PREFERRED, PREFERRED); rbDec.setChecked(true); int s = Math.min(Settings.screenWidth, Settings.screenHeight) * 8 / 10; pbgOpers1.setRect(CENTER, CENTER, s, s); // panel 1: Advanced operations panel = tpOpers.getContainer(1); // add the radians/degrees radios rg = new RadioGroupController(); // criamos outro radio group panel.add(rbDeg = new Radio("Deg", rg)); panel.add(rbRad = new Radio("Rad", rg)); // calculate the ideal position to center the two radios x = (getSize().x - (rbRad.getPreferredWidth() + rbDeg.getPreferredWidth() + 10)) / 2; rbDeg.setRect(x, 2, PREFERRED, PREFERRED); rbRad.setRect(AFTER + 10, 2, PREFERRED, PREFERRED); rbDeg.setChecked(true); // add the PushButtonGroup with the math operations panel.add(pbgOpers2 = new PushButtonGroup(opers2, false, -1, 3, 6, 5, true, PushButtonGroup.BUTTON)); pbgOpers2.setRect(CENTER, CENTER, s,s); // panel 2: History tpOpers.getContainer(2).add(lbHist = new ListBox(history)); lbHist.enableHorizontalScroll(); lbHist.setFont(tinyFont); // make the history occupy all the available area lbHist.setRect(-2, -2, FILL + 2, FILL + 2); pbgOpers1.setBackForeColors(BACK,Color.WHITE); pbgOpers2.setBackForeColors(BACK,Color.WHITE); } // /////////////////////////////////////////////////////////////////////////////////// public void onExit() { // Save the current settings Settings.appSettings = "" + (miSaveHist.isChecked ? '1' : '0') + places; // Save the current history (if wanted) PDBFile cat; try { cat = new PDBFile(catName, PDBFile.CREATE_EMPTY); if (miSaveHist.isChecked && lbHist.size() > 0) { ResizeRecord rs = new ResizeRecord(cat, 2048); DataStream ds = new DataStream(rs); rs.startRecord(); ds.writeStringArray((String[]) lbHist.getItems()); rs.endRecord(); ds.close(); } } catch (IOException e1) { e1.printStackTrace(); } } // /////////////////////////////////////////////////////////////////////////////////// // application related variables private int DEC = 10; private int HEX = 16; private int BIN = 2; private boolean radians; private Random rand = new Random(); private int places = 6; private String dotZero; private boolean startOver; /** current base */ private int base = DEC; /** current angle */ // private int angle = GRA; /** current operation */ private char op = (char) -1; /** operators */ private String oper1; /** if true the number is replaced by a new one */ /** Process the application's events */ public void onEvent(Event event) { if (edNum == null) return; int ind; String text = edNum.getText(); switch (event.type) // always good put event.type in a switch instead of various if's { case ControlEvent.PRESSED: // anything pressed? try { if (event.target == mbar) handleMenuEvent(mbar.getSelectedIndex()); else // changed tabs? if (event.target == tpOpers && tpOpers.getActiveTab() == 2) lbHist.requestFocus(); // let the user use the page up/down key to scroll the history else // basic/advanced operation? if (event.target instanceof PushButtonGroup && (ind = ((PushButtonGroup) event.target).getSelectedIndex()) != -1) { char c; if (event.target == pbgOpers2) c = (char) ind; else c = opers1[ind].charAt(0); switch (c) { // was a decimal number? case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { if (startOver) { text = ""; startOver = false; } if (base != BIN || c <= '1') // is the char valid in this base? setNum(text + c); break; } // was a hexadecimal number? case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': { if (startOver) { text = ""; startOver = false; } if (base == HEX) setNum(text + c); break; } case ' ': // +- { try { double value = Convert.toDouble(text); if (value < 0) setNum(text.substring(1)); // remove the minus sign else setNum('-' + text); // put the minus sign } catch (InvalidNumberException ine) {} break; } case 19: // Clr - opers2 case 'c': // clr - opers1 { setNum("0"); oper1 = null; break; } case '.': { if (startOver) { text = ""; startOver = false; } if (base == DEC && text.indexOf('.') == -1) // base other than decimal can't have a decimal point setNum(text + '.'); break; } case 24: c = '='; // make easier default: // =+-/* and advanced operations preCompute(c); } } else // base conversion ? if ((event.target == rbDec || event.target == rbHex || event.target == rbBin) && base != ((Control) event.target).appId) // appId stores the base that is represented by the control { if (!text.equals("0")) // if not empty... { String hist = (base == 2 ? trimZeros(text) : text) + '(' + base + ") -> "; // if is base 10 and just finished an operation, maybe ends with .0000; strip it if (base == 10 && text.indexOf('.') >= 0) text = text.substring(0, text.indexOf('.')); // convert from the preceding base to base 10 long l = Convert.toLong(text, base); // store the new base base = ((Control) event.target).appId; // and convert to the new one String s = Convert.toString(l, base).toUpperCase(); // show it hist += (base == 2 ? trimZeros(s) : s) + '(' + base + ')'; addHist(hist); setNum(s); } else { // just inform the base was changed base = ((Control) event.target).appId; addHist("-> " + base); } } else // grade convertion (radians)? if (event.target == rbRad) { double d = Convert.toDouble(text); if (d != 0) { d = Math.toDegrees(d); String s = Convert.toString(d, places); setNum(s); addHist("Rad(" + text + ") -> Deg(" + s + ")"); } else addHist("-> Rad"); radians = true; } else // grade convertion (degrees)? if (event.target == rbDeg) { double d = Convert.toDouble(text); if (d != 0) { d = Math.toRadians(d); String s = Convert.toString(d, places); setNum(s); addHist("Deg(" + text + ") -> Rad(" + s + ")"); } else addHist("-> Deg"); radians = false; } else // any history item was clicked? if (event.target == lbHist) { String s = (String) lbHist.getSelectedItem(); if ((ind = s.indexOf('=')) != -1) setNum(s.substring(ind + 2)); } } catch (InvalidNumberException ine) {addHist(ine.getMessage());} break; // case ControlEvent.PRESSED } } private void handleMenuEvent(int item) { switch (item) { case 1: // set precision InputBox id = new InputBox("Set Precision", "Please enter the number\nof decimal places to be used\n(2 to 12):", "" + places); id.getEdit().setMode(Edit.CURRENCY); id.popup(); if (id.getPressedButtonIndex() == 0) try { int n = Convert.toInt(id.getValue()); if (n < 2) n = 2; else if (n > 12) n = 12; places = n; dotZero = '.' + Convert.zeroPad("", places); } catch (InvalidNumberException ine) {} break; case 4: // clear history lbHist.removeAll(); break; case 6: // exit exit(0); break; case 101: // copy Vm.clipboardCopy(edNum.getText()); break; case 102: // paste String temp = Vm.clipboardPaste(); if (temp.length() > 0) { oper1 = temp; setNum(edNum.getText()); } break; case 201: // instructions new MessageBox( "Instructions", "You must type the operator 1,\nthe operation, and, in some\ncases, the operator 2 and\nthen type = to compute\nthe result. Some operations\nrequire two or one operators.\nThere's no operator precedence.\nAfter computing the value,\nthe result is assigned to\noperator 1. Some operations\nin the advanced tab require\nan integer; if there is\na floating point value, it\nwill be truncated (E.g.:\n3.67! = 3! = 6). Clicking\non the history places the\nresult in the operator 1.\nSelecting File/Save History\nstores and retrieves the history\nfrom a database.").popupNonBlocking(); break; case 202: // about new MessageBox( "About", "TotalCross Calculator 3.0\nExample program for the\nTotalCross SDK. This software\nis freeware.\nCreated by Guilherme C. Hazan\nwww.totalcross.com").popupNonBlocking(); break; } } private String format(String s) { if (base == BIN) { // remove the minus sign if base=2 if (s.length() > 1 && s.charAt(0) == '-') s = s.substring(1); // the result must have exactly 32 chars int d = s.length() - 32; if (d < 0) // add zeros s = Convert.zeroPad(s, 32); else if (d > 0) // remove zeros s = s.substring(d); } else { // remove starting zeroes if (s.length() > 1) s = trimZeros(s); // change .00 to 0.00 if (s.charAt(0) == '.') s = '0' + s; // strip .00 if (s.endsWith(dotZero)) s = s.substring(0, s.length() - places - 1); // if hex mode, convert to upper case if (base == HEX) s = s.toUpperCase(); } return s; } // format the output and display it. private void setNum(String s) { edNum.setText(format(s)); } // remove zeroes at the start of the string private String trimZeros(String s) { if (s.length() == 0) return "0"; char[] ac = s.toCharArray(); int i = 0; while (i < ac.length && ac[i] == '0') i++; return i == 0 ? s : new String(ac, i, ac.length - i); } // add to the history private void addHist(String s) { // add and select the item lbHist.add(s); lbHist.setSelectedIndex(lbHist.size() - 1); // warn the user if the history gets too big if (lbHist.size() % 1000 == 0) { MessageBox mb = new MessageBox("Attention!", "The history is getting\ntoo large. Do you\nwant to erase it?", new String[] {"Yes", "No"}); mb.popup(); if (mb.getPressedButtonIndex() == 0) lbHist.removeAll(); } } private void preCompute(char newOp) { String text = edNum.getText(); boolean isEqual = newOp == '='; if (!isEqual && requiresOneOperator(newOp)) { String res; setNum(res = compute(text, null, newOp)); addHist(format(text) + ' ' + getOpText(newOp) + " = " + format(res)); oper1 = null; } else { if (oper1 == null) oper1 = text; else { String s = format(oper1) + ' ' + getOpText(op) + ' ' + format(text) + " = "; setNum(oper1 = compute(oper1, text, op)); addHist(s + format(oper1)); } if (isEqual) oper1 = null; else op = newOp; } startOver = true; } private String getOpText(char op) { return op >= '*' ? Convert.toString(op) : opers2[op]; } private String compute(String oper1, String oper2, char op) { // Vm.debug(oper1+" "+(op>='*'?((char)op+"") : opers2[op])+" "+oper2); try { if (op >= '*') // basic operations? { // in base 10 we can make operations using floating point if (base == DEC) { double r = 0; double op1 = Convert.toDouble(oper1); double op2 = Convert.toDouble(oper2); switch (op) { case '+': r = op1 + op2; break; case '-': r = op1 - op2; break; case '*': r = op1 * op2; break; case '/': r = op1 / op2; break; } return Convert.toString(r, places); // trunc with the desired places } else // else, we must use the long data type { long r = 0; long op1 = Convert.toLong(oper1, base); long op2 = Convert.toLong(oper2, base); switch (op) { case '+': r = op1 + op2; break; case '-': r = op1 - op2; break; case '*': r = op1 * op2; break; case '/': r = op1 / op2; break; } return Convert.toString(r, base); // convert back to the desired base } } else // advanced if (op <= 14 && op != 8) { double r = 0; double op1 = Convert.toDouble(oper1); double op2 = oper2 == null ? 0 : Convert.toDouble(oper2); switch ((int) op) { // double case 0: // exp r = Math.exp(op1); break; case 1: // ln r = Math.log(op1); break; case 2: // log r = Math.log(op1) / Math.log(10); break; case 3: // x^2 r = op1 * op1; break; case 4: // x^3 r = op1 * op1 * op1; break; case 5: // x^y r = Math.pow(op1, op2); break; case 6: // 1/x r = 1 / op1; break; case 7: // sqr r = Math.sqrt(op1); break; case 9: // Mod r = op1 % op2; break; case 10: // Rnd r = rand.nextDouble(); break; case 11: // Sin if (!radians) op1 = Math.toRadians(op1); r = Math.sin(op1); // if (!radians) r = Math.toDegrees(r); break; case 12: // Cos if (!radians) op1 = Math.toRadians(op1); r = Math.cos(op1); // if (!radians) r = Math.toDegrees(r); break; case 13: // Tan if (!radians) op1 = Math.toRadians(op1); r = Math.tan(op1); // if (!radians) r = Math.toDegrees(r); break; case 14: // Int return (long) op1 + ""; } return Convert.toString(r, places); } else { long r = 0; long op1 = oper1.indexOf('.') != -1 ? (long) Convert.toDouble(oper1) : Convert.toLong(oper1, base); long op2 = oper2 == null ? 0 : oper2.indexOf('.') != -1 ? (long) Convert.toDouble(oper2) : Convert.toLong(oper2, base); switch ((int) op) { case 15: // Shl r = op1 << 1; break; case 16: // Shr r = op1 >> 1; break; case 17: // Rol r = rol(op1); break; case 18: // Ror r = ror(op1); break; case 20: // And r = op1 & op2; break; case 21: // Or r = op1 | op2; break; case 22: // Xor r = op1 ^ op2; break; case 23: // Not r = ~op1; break; case 8: // N! r = factorial((long) Convert.toDouble(oper1)); if (r == 0) return "Number too big or invalid"; break; } return Convert.toString(r, base); } } catch (Exception ae) { return "Err"; } } private boolean requiresOneOperator(char op) { switch (op) { case '+': case '-': case '*': case '/': case 20: case 21: case 22: case 5: case 9: return false; } return true; } private long factorial(long n) { if (n == 0) return 1; if (n > 25) return 0; long r = n; while (--n > 1) r *= n; return r; } private long rol(long i) { return ((i << 1) | (((int) i < 0) ? 1 : 0)) & 0xFFFFFFFFL; } private long ror(long i) { return (i >> 1) | ((i & 1) != 0 ? 0x80000000L : 0L); } }