/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.numix.calculator; import java.util.Locale; import org.achartengine.GraphicalView; import org.javia.arity.Complex; import org.javia.arity.Symbols; import org.javia.arity.SyntaxException; import android.content.Context; import android.content.res.Resources; import android.view.KeyEvent; import android.widget.EditText; import com.numix.calculator.BaseModule.Mode; import com.numix.calculator.view.CalculatorDisplay; import com.numix.calculator.view.CalculatorDisplay.Scroll; import com.numix.calculator.view.MatrixInverseView; import com.numix.calculator.view.MatrixTransposeView; import com.numix.calculator.view.MatrixView; public class Logic { public static final String NUMBER = "[" + Logic.MINUS + "-]?[A-F0-9]+(\\.[A-F0-9]*)?"; public static final String INFINITY_UNICODE = "\u221e"; // Double.toString() for Infinity public static final String INFINITY = "Infinity"; // Double.toString() for NaN public static final String NAN = "NaN"; public static final char MINUS = '\u2212'; static final char MUL = '\u00d7'; static final char PLUS = '+'; static final char DIV = '\u00f7'; static final char POW = '^'; public static final String MARKER_EVALUATE_ON_RESUME = "?"; public static final int DELETE_MODE_BACKSPACE = 0; public static final int DELETE_MODE_CLEAR = 1; CalculatorDisplay mDisplay; GraphicalView mGraphDisplay; Symbols mSymbols = new Symbols(); private final History mHistory; String mResult = ""; boolean mIsError = false; int mLineLength = 0; private Graph mGraph; EquationFormatter mEquationFormatter; public GraphModule mGraphModule; public BaseModule mBaseModule; public MatrixModule mMatrixModule; private final boolean mUseRadians; final String mErrorString; private final String mSinString; private final String mCosString; private final String mTanString; private final String mArcsinString; private final String mArccosString; private final String mArctanString; private final String mLogString; private final String mLnString; private final String mDetString; final String mDecSeparator; final String mBinSeparator; final String mHexSeparator; final String mDecimalPoint; final String mMatrixSeparator; final int mDecSeparatorDistance; final int mBinSeparatorDistance; final int mHexSeparatorDistance; final String mX; final String mY; int mDeleteMode = DELETE_MODE_BACKSPACE; public interface Listener { void onDeleteModeChange(); } private Listener mListener; Logic(Context context, History history, CalculatorDisplay display) { final Resources r = context.getResources(); mErrorString = r.getString(R.string.error); mSinString = r.getString(R.string.sin); mCosString = r.getString(R.string.cos); mTanString = r.getString(R.string.tan); mArcsinString = r.getString(R.string.arcsin); mArccosString = r.getString(R.string.arccos); mArctanString = r.getString(R.string.arctan); mLogString = r.getString(R.string.lg); mLnString = r.getString(R.string.ln); mDetString = r.getString(R.string.det); mDecSeparator = r.getString(R.string.dec_separator); mBinSeparator = r.getString(R.string.bin_separator); mHexSeparator = r.getString(R.string.hex_separator); mDecSeparatorDistance = r.getInteger(R.integer.dec_separator_distance); mBinSeparatorDistance = r.getInteger(R.integer.bin_separator_distance); mHexSeparatorDistance = r.getInteger(R.integer.hex_separator_distance); mDecimalPoint = r.getString(R.string.dot); mMatrixSeparator = r.getString(R.string.matrix_separator); mX = r.getString(R.string.X); mY = r.getString(R.string.Y); mUseRadians = CalculatorSettings.useRadians(context); mEquationFormatter = new EquationFormatter(); mHistory = history; mDisplay = display; if(mDisplay != null) mDisplay.setLogic(this); mGraphModule = new GraphModule(this); mBaseModule = new BaseModule(this); mMatrixModule = new MatrixModule(this); } public void setGraphDisplay(GraphicalView graphDisplay) { mGraphDisplay = graphDisplay; } public void setGraph(Graph graph) { mGraph = graph; } public void setListener(Listener listener) { this.mListener = listener; } public void setDeleteMode(int mode) { if(mDeleteMode != mode) { mDeleteMode = mode; } } public int getDeleteMode() { return mDeleteMode; } void setLineLength(int nDigits) { mLineLength = nDigits; } public String getText() { return mDisplay.getText(); } void setText(String text) { clear(false); mDisplay.insert(text); if(text.equals(mErrorString)) setDeleteMode(DELETE_MODE_CLEAR); } void insert(String delta) { if(!acceptInsert(delta)) { clear(true); } mDisplay.insert(delta); setDeleteMode(DELETE_MODE_BACKSPACE); mGraphModule.updateGraphCatchErrors(mGraph); } public void onTextChanged() { setDeleteMode(DELETE_MODE_BACKSPACE); } public void resumeWithHistory() { clearWithHistory(false); } private void clearWithHistory(boolean scroll) { String text = mHistory.getText(); if(MARKER_EVALUATE_ON_RESUME.equals(text)) { if(!mHistory.moveToPrevious()) { text = ""; } text = mHistory.getBase(); evaluateAndShowResult(text, CalculatorDisplay.Scroll.NONE); } else { mResult = ""; mDisplay.setText(text, scroll ? CalculatorDisplay.Scroll.UP : CalculatorDisplay.Scroll.NONE); mIsError = false; } } private void clear(boolean scroll) { mHistory.enter("", ""); mDisplay.setText("", scroll ? CalculatorDisplay.Scroll.UP : CalculatorDisplay.Scroll.NONE); cleared(); } void cleared() { mResult = ""; mIsError = false; updateHistory(); setDeleteMode(DELETE_MODE_BACKSPACE); } boolean acceptInsert(String delta) { if (mIsError || getText().equals(mErrorString)) { return false; } if (getDeleteMode() == DELETE_MODE_BACKSPACE || isOperator(delta) || isPostFunction(delta)) { return true; } EditText editText = mDisplay.getActiveEditText(); int editLength = editText == null ? 0 : editText.getText().length(); return mDisplay.getSelectionStart() != editLength; } void onDelete() { if(getText().equals(mResult) || mIsError) { clear(false); } else { mDisplay.dispatchKeyEvent(new KeyEvent(0, KeyEvent.KEYCODE_DEL)); mResult = ""; } mGraphModule.updateGraphCatchErrors(mGraph); } void onClear() { clear(mDeleteMode == DELETE_MODE_CLEAR); mGraphModule.updateGraphCatchErrors(mGraph); } public void onEnter() { if(mDeleteMode == DELETE_MODE_CLEAR) { clearWithHistory(false); // clear after an Enter on result } else { evaluateAndShowResult(getText(), CalculatorDisplay.Scroll.UP); } } boolean displayContainsMatrices() { boolean containsMatrices = false; for(int i = 0; i < mDisplay.getAdvancedDisplay().getChildCount(); i++) { if(mDisplay.getAdvancedDisplay().getChildAt(i) instanceof MatrixView) containsMatrices = true; if(mDisplay.getAdvancedDisplay().getChildAt(i) instanceof MatrixInverseView) containsMatrices = true; if(mDisplay.getAdvancedDisplay().getChildAt(i) instanceof MatrixTransposeView) containsMatrices = true; } return containsMatrices; } public void evaluateAndShowResult(String text, Scroll scroll) { boolean containsMatrices = displayContainsMatrices(); try { String result = containsMatrices ? mMatrixModule.evaluateMatrices(mDisplay.getAdvancedDisplay()) : evaluate(text); if(!text.equals(result)) { mHistory.enter(mEquationFormatter.appendParenthesis(text), result); mResult = result; mDisplay.setText(mResult, scroll); setDeleteMode(DELETE_MODE_CLEAR); } } catch(SyntaxException e) { mIsError = true; mResult = mErrorString; mDisplay.setText(mResult, scroll); setDeleteMode(DELETE_MODE_CLEAR); } } void onUp() { if(mHistory.moveToPrevious()) { mDisplay.setText(mHistory.getText(), CalculatorDisplay.Scroll.DOWN); } } void onDown() { if(mHistory.moveToNext()) { mDisplay.setText(mHistory.getText(), CalculatorDisplay.Scroll.UP); } } void updateHistory() { String text = getText(); mHistory.update(text); } public static final int ROUND_DIGITS = 1; public String evaluate(String input) throws SyntaxException { if(input.trim().isEmpty()) { return ""; } // Drop final infix operators (they can only result in error) int size = input.length(); while(size > 0 && isOperator(input.charAt(size - 1))) { input = input.substring(0, size - 1); --size; } input = localize(input); // Convert to decimal String decimalInput = convertToDecimal(input); Complex value = mSymbols.evalComplex(decimalInput); String real = ""; for(int precision = mLineLength; precision > 6; precision--) { real = tryFormattingWithPrecision(value.re, precision); if(real.length() <= mLineLength) { break; } } String imaginary = ""; for(int precision = mLineLength; precision > 6; precision--) { imaginary = tryFormattingWithPrecision(value.im, precision); if(imaginary.length() <= mLineLength) { break; } } real = mBaseModule.updateTextToNewMode(real, Mode.DECIMAL, mBaseModule.getMode()).replace('-', MINUS).replace(INFINITY, INFINITY_UNICODE); imaginary = mBaseModule.updateTextToNewMode(imaginary, Mode.DECIMAL, mBaseModule.getMode()).replace('-', MINUS).replace(INFINITY, INFINITY_UNICODE); String result = ""; if(value.re != 0 && value.im > 0) result = real + "+" + imaginary + "i"; else if(value.re != 0 && value.im < 0) result = real + imaginary + "i"; // Implicit // - else if(value.re != 0 && value.im == 0) result = real; else if(value.re == 0 && value.im != 0) result = imaginary + "i"; else if(value.re == 0 && value.im == 0) result = "0"; result = relocalize(result); return result; } public String convertToDecimal(String input) { return mBaseModule.updateTextToNewMode(input, mBaseModule.getMode(), Mode.DECIMAL); } String localize(String input) { // Delocalize functions (e.g. Spanish localizes "sin" as "sen"). Order // matters for arc functions // ot input = input.replace(mArcsinString, "asin"); input = input.replace(mArccosString, "acos"); input = input.replace(mArctanString, "atan"); input = input.replace(mSinString, "sin"); input = input.replace(mCosString, "cos"); input = input.replace(mTanString, "tan"); if(!mUseRadians) { input = input.replace("sin", "sind"); input = input.replace("cos", "cosd"); input = input.replace("tan", "tand"); } input = input.replace(mLogString, "log"); input = input.replace(mLnString, "ln"); input = input.replace(mDetString, "det"); input = input.replace(mDecimalPoint, "."); input = input.replace(mMatrixSeparator, ","); return input; } String relocalize(String input) { input = input.replace(",", mMatrixSeparator); input = input.replace(".", mDecimalPoint); return input; } String tryFormattingWithPrecision(double value, int precision) { // The standard scientific formatter is basically what we need. We will // start with what it produces and then massage it a bit. String result = String.format(Locale.US, "%" + mLineLength + "." + precision + "g", value); if(result.equals(NAN)) { // treat NaN as Error return mErrorString; } String mantissa = result; String exponent = null; int e = result.indexOf('e'); if(e != -1) { mantissa = result.substring(0, e); // Strip "+" and unnecessary 0's from the exponent exponent = result.substring(e + 1); if(exponent.startsWith("+")) { exponent = exponent.substring(1); } exponent = String.valueOf(Integer.parseInt(exponent)); } int period = mantissa.indexOf('.'); if(period == -1) { period = mantissa.indexOf(','); } if(period != -1) { // Strip trailing 0's while(mantissa.length() > 0 && mantissa.endsWith("0")) { mantissa = mantissa.substring(0, mantissa.length() - 1); } if(mantissa.length() == period + 1) { mantissa = mantissa.substring(0, mantissa.length() - 1); } } if(exponent != null) { result = mantissa + 'e' + exponent; } else { result = mantissa; } return result; } public static boolean isOperator(String text) { return text.length() == 1 && isOperator(text.charAt(0)); } static boolean isOperator(char c) { // plus minus times div return "+\u2212\u00d7\u00f7/*".indexOf(c) != -1; } static boolean isPostFunction(String text) { return text.length() == 1 && isPostFunction(text.charAt(0)); } static boolean isPostFunction(char c) { // exponent, factorial, percent return "^!%".indexOf(c) != -1; } }