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     private static final int ROUND_DIGITS = 1;
    228     String evaluate(String input) throws SyntaxException {
    229         if (input.trim().equals("")) {
    230             return "";
    231         }
    232 
    233         // drop final infix operators (they can only result in error)
    234         int size = input.length();
    235         while (size > 0 && isOperator(input.charAt(size - 1))) {
    236             input = input.substring(0, size - 1);
    237             --size;
    238         }
    239         // Find and replace any translated mathematical functions.
    240         input = replaceTranslations(input);
    241         double value = mSymbols.eval(input);
    242 
    243         String result = "";
    244         for (int precision = mLineLength; precision > 6; precision--) {
    245             result = tryFormattingWithPrecision(value, precision);
    246             if (result.length() <= mLineLength) {
    247                 break;
    248             }
    249         }
    250         return result.replace('-', MINUS).replace(INFINITY, INFINITY_UNICODE);
    251     }
    252 
    253     private void addTranslation(HashMap<String, String> map, int t, int m) {
    254         Resources res = mContext.getResources();
    255         String translated = res.getString(t);
    256         String math = res.getString(m);
    257         if (!TextUtils.equals(translated, math)) {
    258             map.put(translated, math);
    259         }
    260     }
    261 
    262     private String replaceTranslations(String input) {
    263         if (mTranslationsSet == null) {
    264             HashMap<String, String> map = new HashMap<String, String>();
    265             addTranslation(map, R.string.sin, R.string.sin_mathematical_value);
    266             addTranslation(map, R.string.cos, R.string.cos_mathematical_value);
    267             addTranslation(map, R.string.tan, R.string.tan_mathematical_value);
    268             addTranslation(map, R.string.e, R.string.e_mathematical_value);
    269             addTranslation(map, R.string.ln, R.string.ln_mathematical_value);
    270             addTranslation(map, R.string.lg, R.string.lg_mathematical_value);
    271             mTranslationsSet = map.entrySet();
    272         }
    273         for (Entry<String, String> entry : mTranslationsSet) {
    274             input = input.replace(entry.getKey(), entry.getValue());
    275         }
    276         return input;
    277     }
    278 
    279     private String tryFormattingWithPrecision(double value, int precision) {
    280         // The standard scientific formatter is basically what we need. We will
    281         // start with what it produces and then massage it a bit.
    282         String result = String.format(Locale.US, "%" + mLineLength + "." + precision + "g", value);
    283         if (result.equals(NAN)) { // treat NaN as Error
    284             mIsError = true;
    285             return mErrorString;
    286         }
    287         String mantissa = result;
    288         String exponent = null;
    289         int e = result.indexOf('e');
    290         if (e != -1) {
    291             mantissa = result.substring(0, e);
    292 
    293             // Strip "+" and unnecessary 0's from the exponent
    294             exponent = result.substring(e + 1);
    295             if (exponent.startsWith("+")) {
    296                 exponent = exponent.substring(1);
    297             }
    298             exponent = String.valueOf(Integer.parseInt(exponent));
    299         } else {
    300             mantissa = result;
    301         }
    302 
    303         int period = mantissa.indexOf('.');
    304         if (period == -1) {
    305             period = mantissa.indexOf(',');
    306         }
    307         if (period != -1) {
    308             // Strip trailing 0's
    309             while (mantissa.length() > 0 && mantissa.endsWith("0")) {
    310                 mantissa = mantissa.substring(0, mantissa.length() - 1);
    311             }
    312             if (mantissa.length() == period + 1) {
    313                 mantissa = mantissa.substring(0, mantissa.length() - 1);
    314             }
    315         }
    316 
    317         if (exponent != null) {
    318             result = mantissa + 'e' + exponent;
    319         } else {
    320             result = mantissa;
    321         }
    322         return result;
    323     }
    324 
    325     static boolean isOperator(String text) {
    326         return text.length() == 1 && isOperator(text.charAt(0));
    327     }
    328 
    329     static boolean isOperator(char c) {
    330         //plus minus times div
    331         return "+\u2212\u00d7\u00f7/*".indexOf(c) != -1;
    332     }
    333 }
    334