blob: 690e89269c4f14a37c6612c82909f610652074f9 [file] [log] [blame]
/*
* 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.android.calculator2;
import com.android.calculator2.CalculatorDisplay.Scroll;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.widget.EditText;
import android.content.Context;
import java.util.Locale;
import org.javia.arity.Symbols;
import org.javia.arity.SyntaxException;
import org.javia.arity.Util;
class Logic {
private CalculatorDisplay mDisplay;
private Symbols mSymbols = new Symbols();
private History mHistory;
private String mResult = "";
private boolean mIsError = false;
private int mLineLength = 0;
private static final String INFINITY_UNICODE = "\u221e";
public static final String MARKER_EVALUATE_ON_RESUME = "?";
// the two strings below are the result of Double.toString() for Infinity & NaN
// they are not output to the user and don't require internationalization
private static final String INFINITY = "Infinity";
private static final String NAN = "NaN";
static final char MINUS = '\u2212';
private final String mErrorString;
public final static int DELETE_MODE_BACKSPACE = 0;
public final static int DELETE_MODE_CLEAR = 1;
private int mDeleteMode = DELETE_MODE_BACKSPACE;
public interface Listener {
void onDeleteModeChange();
}
private Listener mListener;
Logic(Context context, History history, CalculatorDisplay display) {
mErrorString = context.getResources().getString(R.string.error);
mHistory = history;
mDisplay = display;
mDisplay.setLogic(this);
}
public void setListener(Listener listener) {
this.mListener = listener;
}
public void setDeleteMode(int mode) {
if (mDeleteMode != mode) {
mDeleteMode = mode;
mListener.onDeleteModeChange();
}
}
public int getDeleteMode() {
return mDeleteMode;
}
void setLineLength(int nDigits) {
mLineLength = nDigits;
}
boolean eatHorizontalMove(boolean toLeft) {
EditText editText = mDisplay.getEditText();
int cursorPos = editText.getSelectionStart();
return toLeft ? cursorPos == 0 : cursorPos >= editText.length();
}
private String getText() {
return mDisplay.getText().toString();
}
void insert(String delta) {
mDisplay.insert(delta);
setDeleteMode(DELETE_MODE_BACKSPACE);
}
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.getText();
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) {
String text = getText();
return !mIsError &&
(!mResult.equals(text) ||
isOperator(delta) ||
mDisplay.getSelectionStart() != text.length());
}
void onDelete() {
if (getText().equals(mResult) || mIsError) {
clear(false);
} else {
mDisplay.dispatchKeyEvent(new KeyEvent(0, KeyEvent.KEYCODE_DEL));
mResult = "";
}
}
void onClear() {
clear(mDeleteMode == DELETE_MODE_CLEAR);
}
void onEnter() {
if (mDeleteMode == DELETE_MODE_CLEAR) {
clearWithHistory(false); // clear after an Enter on result
} else {
evaluateAndShowResult(getText(), CalculatorDisplay.Scroll.UP);
}
}
public void evaluateAndShowResult(String text, Scroll scroll) {
try {
String result = evaluate(text);
if (!text.equals(result)) {
mHistory.enter(text);
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() {
String text = getText();
if (!text.equals(mResult)) {
mHistory.update(text);
}
if (mHistory.moveToPrevious()) {
mDisplay.setText(mHistory.getText(), CalculatorDisplay.Scroll.DOWN);
}
}
void onDown() {
String text = getText();
if (!text.equals(mResult)) {
mHistory.update(text);
}
if (mHistory.moveToNext()) {
mDisplay.setText(mHistory.getText(), CalculatorDisplay.Scroll.UP);
}
}
void updateHistory() {
String text = getText();
// Don't set the ? marker for empty text or the error string.
// There is no need to evaluate those later.
if (!TextUtils.isEmpty(text) && !TextUtils.equals(text, mErrorString)
&& text.equals(mResult)) {
mHistory.update(MARKER_EVALUATE_ON_RESUME);
} else {
mHistory.update(getText());
}
}
private static final int ROUND_DIGITS = 1;
String evaluate(String input) throws SyntaxException {
if (input.trim().equals("")) {
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;
}
double value = mSymbols.eval(input);
String result = "";
for (int precision = mLineLength; precision > 6; precision--) {
result = tryFormattingWithPrecision(value, precision);
if (result.length() <= mLineLength) {
break;
}
}
return result.replace('-', MINUS).replace(INFINITY, INFINITY_UNICODE);
}
private 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
mIsError = true;
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));
} else {
mantissa = result;
}
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;
}
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;
}
}