Home | History | Annotate | Download | only in calculator2
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.calculator2;
     18 
     19 import com.android.calculator2.CalculatorDisplay.Scroll;
     20 
     21 import android.text.TextUtils;
     22 import android.view.KeyEvent;
     23 import android.widget.EditText;
     24 import android.content.Context;
     25 import android.content.res.Resources;
     26 
     27 import java.util.HashMap;
     28 import java.util.Locale;
     29 import java.util.Map.Entry;
     30 import java.util.Set;
     31 
     32 import org.javia.arity.Symbols;
     33 import org.javia.arity.SyntaxException;
     34 
     35 class Logic {
     36     private CalculatorDisplay mDisplay;
     37     private Symbols mSymbols = new Symbols();
     38     private History mHistory;
     39     private String  mResult = "";
     40     private boolean mIsError = false;
     41     private int mLineLength = 0;
     42 
     43     private static final String INFINITY_UNICODE = "\u221e";
     44 
     45     public static final String MARKER_EVALUATE_ON_RESUME = "?";
     46 
     47     // the two strings below are the result of Double.toString() for Infinity & NaN
     48     // they are not output to the user and don't require internationalization
     49     private static final String INFINITY = "Infinity";
     50     private static final String NAN      = "NaN";
     51 
     52     static final char MINUS = '\u2212';
     53 
     54     private final String mErrorString;
     55 
     56     public final static int DELETE_MODE_BACKSPACE = 0;
     57     public final static int DELETE_MODE_CLEAR = 1;
     58 
     59     private int mDeleteMode = DELETE_MODE_BACKSPACE;
     60 
     61     public interface Listener {
     62         void onDeleteModeChange();
     63     }
     64 
     65     private Listener mListener;
     66     private Context mContext;
     67     private Set<Entry<String, String>> mTranslationsSet;
     68 
     69     Logic(Context context, History history, CalculatorDisplay display) {
     70         mContext = context;
     71         mErrorString = mContext.getResources().getString(R.string.error);
     72         mHistory = history;
     73         mDisplay = display;
     74         mDisplay.setLogic(this);
     75     }
     76 
     77     public void setListener(Listener listener) {
     78         this.mListener = listener;
     79     }
     80 
     81     public void setDeleteMode(int mode) {
     82         if (mDeleteMode != mode) {
     83             mDeleteMode = mode;
     84             mListener.onDeleteModeChange();
     85         }
     86     }
     87 
     88     public int getDeleteMode() {
     89         return mDeleteMode;
     90     }
     91 
     92     void setLineLength(int nDigits) {
     93         mLineLength = nDigits;
     94     }
     95 
     96     boolean eatHorizontalMove(boolean toLeft) {
     97         EditText editText = mDisplay.getEditText();
     98         int cursorPos = editText.getSelectionStart();
     99         return toLeft ? cursorPos == 0 : cursorPos >= editText.length();
    100     }
    101 
    102     private String getText() {
    103         return mDisplay.getText().toString();
    104     }
    105 
    106     void insert(String delta) {
    107         mDisplay.insert(delta);
    108         setDeleteMode(DELETE_MODE_BACKSPACE);
    109     }
    110 
    111     public void onTextChanged() {
    112         setDeleteMode(DELETE_MODE_BACKSPACE);
    113     }
    114 
    115     public void resumeWithHistory() {
    116         clearWithHistory(false);
    117     }
    118 
    119     private void clearWithHistory(boolean scroll) {
    120         String text = mHistory.getText();
    121         if (MARKER_EVALUATE_ON_RESUME.equals(text)) {
    122             if (!mHistory.moveToPrevious()) {
    123                 text = "";
    124             }
    125             text = mHistory.getText();
    126             evaluateAndShowResult(text, CalculatorDisplay.Scroll.NONE);
    127         } else {
    128             mResult = "";
    129             mDisplay.setText(
    130                     text, scroll ? CalculatorDisplay.Scroll.UP : CalculatorDisplay.Scroll.NONE);
    131             mIsError = false;
    132         }
    133     }
    134 
    135     private void clear(boolean scroll) {
    136         mHistory.enter("");
    137         mDisplay.setText("", scroll ? CalculatorDisplay.Scroll.UP : CalculatorDisplay.Scroll.NONE);
    138         cleared();
    139     }
    140 
    141     void cleared() {
    142         mResult = "";
    143         mIsError = false;
    144         updateHistory();
    145 
    146         setDeleteMode(DELETE_MODE_BACKSPACE);
    147     }
    148 
    149     boolean acceptInsert(String delta) {
    150         String text = getText();
    151         return !mIsError &&
    152             (!mResult.equals(text) ||
    153              isOperator(delta) ||
    154              mDisplay.getSelectionStart() != text.length());
    155     }
    156 
    157     void onDelete() {
    158         if (getText().equals(mResult) || mIsError) {
    159             clear(false);
    160         } else {
    161             mDisplay.dispatchKeyEvent(new KeyEvent(0, KeyEvent.KEYCODE_DEL));
    162             mResult = "";
    163         }
    164     }
    165 
    166     void onClear() {
    167         clear(mDeleteMode == DELETE_MODE_CLEAR);
    168     }
    169 
    170     void onEnter() {
    171         if (mDeleteMode == DELETE_MODE_CLEAR) {
    172             clearWithHistory(false); // clear after an Enter on result
    173         } else {
    174             evaluateAndShowResult(getText(), CalculatorDisplay.Scroll.UP);
    175         }
    176     }
    177 
    178     public void evaluateAndShowResult(String text, Scroll scroll) {
    179         try {
    180             String result = evaluate(text);
    181             if (!text.equals(result)) {
    182                 mHistory.enter(text);
    183                 mResult = result;
    184                 mDisplay.setText(mResult, scroll);
    185                 setDeleteMode(DELETE_MODE_CLEAR);
    186             }
    187         } catch (SyntaxException e) {
    188             mIsError = true;
    189             mResult = mErrorString;
    190             mDisplay.setText(mResult, scroll);
    191             setDeleteMode(DELETE_MODE_CLEAR);
    192         }
    193     }
    194 
    195     void onUp() {
    196         String text = getText();
    197         if (!text.equals(mResult)) {
    198             mHistory.update(text);
    199         }
    200         if (mHistory.moveToPrevious()) {
    201             mDisplay.setText(mHistory.getText(), CalculatorDisplay.Scroll.DOWN);
    202         }
    203     }
    204 
    205     void onDown() {
    206         String text = getText();
    207         if (!text.equals(mResult)) {
    208             mHistory.update(text);
    209         }
    210         if (mHistory.moveToNext()) {
    211             mDisplay.setText(mHistory.getText(), CalculatorDisplay.Scroll.UP);
    212         }
    213     }
    214 
    215     void updateHistory() {
    216         String text = getText();
    217         // Don't set the ? marker for empty text or the error string.
    218         // There is no need to evaluate those later.
    219         if (!TextUtils.isEmpty(text) && !TextUtils.equals(text, mErrorString)
    220                 && text.equals(mResult)) {
    221             mHistory.update(MARKER_EVALUATE_ON_RESUME);
    222         } else {
    223             mHistory.update(getText());
    224         }
    225     }
    226 
    227     String evaluate(String input) throws SyntaxException {
    228         if (input.trim().equals("")) {
    229             return "";
    230         }
    231 
    232         // drop final infix operators (they can only result in error)
    233         int size = input.length();
    234         while (size > 0 && isOperator(input.charAt(size - 1))) {
    235             input = input.substring(0, size - 1);
    236             --size;
    237         }
    238         // Find and replace any translated mathematical functions.
    239         input = replaceTranslations(input);
    240         double value = mSymbols.eval(input);
    241 
    242         String result = "";
    243         for (int precision = mLineLength; precision > 6; precision--) {
    244             result = tryFormattingWithPrecision(value, precision);
    245             if (result.length() <= mLineLength) {
    246                 break;
    247             }
    248         }
    249         return result.replace('-', MINUS).replace(INFINITY, INFINITY_UNICODE);
    250     }
    251 
    252     private void addTranslation(HashMap<String, String> map, int t, int m) {
    253         Resources res = mContext.getResources();
    254         String translated = res.getString(t);
    255         String math = res.getString(m);
    256         if (!TextUtils.equals(translated, math)) {
    257             map.put(translated, math);
    258         }
    259     }
    260 
    261     private String replaceTranslations(String input) {
    262         if (mTranslationsSet == null) {
    263             HashMap<String, String> map = new HashMap<String, String>();
    264             addTranslation(map, R.string.sin, R.string.sin_mathematical_value);
    265             addTranslation(map, R.string.cos, R.string.cos_mathematical_value);
    266             addTranslation(map, R.string.tan, R.string.tan_mathematical_value);
    267             addTranslation(map, R.string.e, R.string.e_mathematical_value);
    268             addTranslation(map, R.string.ln, R.string.ln_mathematical_value);
    269             addTranslation(map, R.string.lg, R.string.lg_mathematical_value);
    270             mTranslationsSet = map.entrySet();
    271         }
    272         for (Entry<String, String> entry : mTranslationsSet) {
    273             input = input.replace(entry.getKey(), entry.getValue());
    274         }
    275         return input;
    276     }
    277 
    278     private String tryFormattingWithPrecision(double value, int precision) {
    279         // The standard scientific formatter is basically what we need. We will
    280         // start with what it produces and then massage it a bit.
    281         String result = String.format(Locale.US, "%" + mLineLength + "." + precision + "g", value);
    282         if (result.equals(NAN)) { // treat NaN as Error
    283             mIsError = true;
    284             return mErrorString;
    285         }
    286         String mantissa = result;
    287         String exponent = null;
    288         int e = result.indexOf('e');
    289         if (e != -1) {
    290             mantissa = result.substring(0, e);
    291 
    292             // Strip "+" and unnecessary 0's from the exponent
    293             exponent = result.substring(e + 1);
    294             if (exponent.startsWith("+")) {
    295                 exponent = exponent.substring(1);
    296             }
    297             exponent = String.valueOf(Integer.parseInt(exponent));
    298         } else {
    299             mantissa = result;
    300         }
    301 
    302         int period = mantissa.indexOf('.');
    303         if (period == -1) {
    304             period = mantissa.indexOf(',');
    305         }
    306         if (period != -1) {
    307             // Strip trailing 0's
    308             while (mantissa.length() > 0 && mantissa.endsWith("0")) {
    309                 mantissa = mantissa.substring(0, mantissa.length() - 1);
    310             }
    311             if (mantissa.length() == period + 1) {
    312                 mantissa = mantissa.substring(0, mantissa.length() - 1);
    313             }
    314         }
    315 
    316         if (exponent != null) {
    317             result = mantissa + 'e' + exponent;
    318         } else {
    319             result = mantissa;
    320         }
    321         return result;
    322     }
    323 
    324     static boolean isOperator(String text) {
    325         return text.length() == 1 && isOperator(text.charAt(0));
    326     }
    327 
    328     static boolean isOperator(char c) {
    329         //plus minus times div
    330         return "+\u2212\u00d7\u00f7/*".indexOf(c) != -1;
    331     }
    332 }
    333