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 
     26 import java.util.Locale;
     27 
     28 import org.javia.arity.Symbols;
     29 import org.javia.arity.SyntaxException;
     30 import org.javia.arity.Util;
     31 
     32 class Logic {
     33     private CalculatorDisplay mDisplay;
     34     private Symbols mSymbols = new Symbols();
     35     private History mHistory;
     36     private String  mResult = "";
     37     private boolean mIsError = false;
     38     private int mLineLength = 0;
     39 
     40     private static final String INFINITY_UNICODE = "\u221e";
     41 
     42     public static final String MARKER_EVALUATE_ON_RESUME = "?";
     43 
     44     // the two strings below are the result of Double.toString() for Infinity & NaN
     45     // they are not output to the user and don't require internationalization
     46     private static final String INFINITY = "Infinity";
     47     private static final String NAN      = "NaN";
     48 
     49     static final char MINUS = '\u2212';
     50 
     51     private final String mErrorString;
     52 
     53     public final static int DELETE_MODE_BACKSPACE = 0;
     54     public final static int DELETE_MODE_CLEAR = 1;
     55 
     56     private int mDeleteMode = DELETE_MODE_BACKSPACE;
     57 
     58     public interface Listener {
     59         void onDeleteModeChange();
     60     }
     61 
     62     private Listener mListener;
     63 
     64     Logic(Context context, History history, CalculatorDisplay display) {
     65         mErrorString = context.getResources().getString(R.string.error);
     66         mHistory = history;
     67         mDisplay = display;
     68         mDisplay.setLogic(this);
     69     }
     70 
     71     public void setListener(Listener listener) {
     72         this.mListener = listener;
     73     }
     74 
     75     public void setDeleteMode(int mode) {
     76         if (mDeleteMode != mode) {
     77             mDeleteMode = mode;
     78             mListener.onDeleteModeChange();
     79         }
     80     }
     81 
     82     public int getDeleteMode() {
     83         return mDeleteMode;
     84     }
     85 
     86     void setLineLength(int nDigits) {
     87         mLineLength = nDigits;
     88     }
     89 
     90     boolean eatHorizontalMove(boolean toLeft) {
     91         EditText editText = mDisplay.getEditText();
     92         int cursorPos = editText.getSelectionStart();
     93         return toLeft ? cursorPos == 0 : cursorPos >= editText.length();
     94     }
     95 
     96     private String getText() {
     97         return mDisplay.getText().toString();
     98     }
     99 
    100     void insert(String delta) {
    101         mDisplay.insert(delta);
    102         setDeleteMode(DELETE_MODE_BACKSPACE);
    103     }
    104 
    105     public void resumeWithHistory() {
    106         clearWithHistory(false);
    107     }
    108 
    109     private void clearWithHistory(boolean scroll) {
    110         String text = mHistory.getText();
    111         if (MARKER_EVALUATE_ON_RESUME.equals(text)) {
    112             if (!mHistory.moveToPrevious()) {
    113                 text = "";
    114             }
    115             text = mHistory.getText();
    116             evaluateAndShowResult(text, CalculatorDisplay.Scroll.NONE);
    117         } else {
    118             mResult = "";
    119             mDisplay.setText(
    120                     text, scroll ? CalculatorDisplay.Scroll.UP : CalculatorDisplay.Scroll.NONE);
    121             mIsError = false;
    122         }
    123     }
    124 
    125     private void clear(boolean scroll) {
    126         mHistory.enter("");
    127         mDisplay.setText("", scroll ? CalculatorDisplay.Scroll.UP : CalculatorDisplay.Scroll.NONE);
    128         cleared();
    129     }
    130 
    131     void cleared() {
    132         mResult = "";
    133         mIsError = false;
    134         updateHistory();
    135 
    136         setDeleteMode(DELETE_MODE_BACKSPACE);
    137     }
    138 
    139     boolean acceptInsert(String delta) {
    140         String text = getText();
    141         return !mIsError &&
    142             (!mResult.equals(text) ||
    143              isOperator(delta) ||
    144              mDisplay.getSelectionStart() != text.length());
    145     }
    146 
    147     void onDelete() {
    148         if (getText().equals(mResult) || mIsError) {
    149             clear(false);
    150         } else {
    151             mDisplay.dispatchKeyEvent(new KeyEvent(0, KeyEvent.KEYCODE_DEL));
    152             mResult = "";
    153         }
    154     }
    155 
    156     void onClear() {
    157         clear(mDeleteMode == DELETE_MODE_CLEAR);
    158     }
    159 
    160     void onEnter() {
    161         if (mDeleteMode == DELETE_MODE_CLEAR) {
    162             clearWithHistory(false); // clear after an Enter on result
    163         } else {
    164             evaluateAndShowResult(getText(), CalculatorDisplay.Scroll.UP);
    165         }
    166     }
    167 
    168     public void evaluateAndShowResult(String text, Scroll scroll) {
    169         try {
    170             String result = evaluate(text);
    171             if (!text.equals(result)) {
    172                 mHistory.enter(text);
    173                 mResult = result;
    174                 mDisplay.setText(mResult, scroll);
    175                 setDeleteMode(DELETE_MODE_CLEAR);
    176             }
    177         } catch (SyntaxException e) {
    178             mIsError = true;
    179             mResult = mErrorString;
    180             mDisplay.setText(mResult, scroll);
    181             setDeleteMode(DELETE_MODE_CLEAR);
    182         }
    183     }
    184 
    185     void onUp() {
    186         String text = getText();
    187         if (!text.equals(mResult)) {
    188             mHistory.update(text);
    189         }
    190         if (mHistory.moveToPrevious()) {
    191             mDisplay.setText(mHistory.getText(), CalculatorDisplay.Scroll.DOWN);
    192         }
    193     }
    194 
    195     void onDown() {
    196         String text = getText();
    197         if (!text.equals(mResult)) {
    198             mHistory.update(text);
    199         }
    200         if (mHistory.moveToNext()) {
    201             mDisplay.setText(mHistory.getText(), CalculatorDisplay.Scroll.UP);
    202         }
    203     }
    204 
    205     void updateHistory() {
    206         String text = getText();
    207         // Don't set the ? marker for empty text or the error string.
    208         // There is no need to evaluate those later.
    209         if (!TextUtils.isEmpty(text) && !TextUtils.equals(text, mErrorString)
    210                 && text.equals(mResult)) {
    211             mHistory.update(MARKER_EVALUATE_ON_RESUME);
    212         } else {
    213             mHistory.update(getText());
    214         }
    215     }
    216 
    217     private static final int ROUND_DIGITS = 1;
    218     String evaluate(String input) throws SyntaxException {
    219         if (input.trim().equals("")) {
    220             return "";
    221         }
    222 
    223         // drop final infix operators (they can only result in error)
    224         int size = input.length();
    225         while (size > 0 && isOperator(input.charAt(size - 1))) {
    226             input = input.substring(0, size - 1);
    227             --size;
    228         }
    229 
    230         double value = mSymbols.eval(input);
    231 
    232         String result = "";
    233         for (int precision = mLineLength; precision > 6; precision--) {
    234             result = tryFormattingWithPrecision(value, precision);
    235             if (result.length() <= mLineLength) {
    236                 break;
    237             }
    238         }
    239         return result.replace('-', MINUS).replace(INFINITY, INFINITY_UNICODE);
    240     }
    241 
    242     private String tryFormattingWithPrecision(double value, int precision) {
    243         // The standard scientific formatter is basically what we need. We will
    244         // start with what it produces and then massage it a bit.
    245         String result = String.format(Locale.US, "%" + mLineLength + "." + precision + "g", value);
    246         if (result.equals(NAN)) { // treat NaN as Error
    247             mIsError = true;
    248             return mErrorString;
    249         }
    250         String mantissa = result;
    251         String exponent = null;
    252         int e = result.indexOf('e');
    253         if (e != -1) {
    254             mantissa = result.substring(0, e);
    255 
    256             // Strip "+" and unnecessary 0's from the exponent
    257             exponent = result.substring(e + 1);
    258             if (exponent.startsWith("+")) {
    259                 exponent = exponent.substring(1);
    260             }
    261             exponent = String.valueOf(Integer.parseInt(exponent));
    262         } else {
    263             mantissa = result;
    264         }
    265 
    266         int period = mantissa.indexOf('.');
    267         if (period == -1) {
    268             period = mantissa.indexOf(',');
    269         }
    270         if (period != -1) {
    271             // Strip trailing 0's
    272             while (mantissa.length() > 0 && mantissa.endsWith("0")) {
    273                 mantissa = mantissa.substring(0, mantissa.length() - 1);
    274             }
    275             if (mantissa.length() == period + 1) {
    276                 mantissa = mantissa.substring(0, mantissa.length() - 1);
    277             }
    278         }
    279 
    280         if (exponent != null) {
    281             result = mantissa + 'e' + exponent;
    282         } else {
    283             result = mantissa;
    284         }
    285         return result;
    286     }
    287 
    288     static boolean isOperator(String text) {
    289         return text.length() == 1 && isOperator(text.charAt(0));
    290     }
    291 
    292     static boolean isOperator(char c) {
    293         //plus minus times div
    294         return "+\u2212\u00d7\u00f7/*".indexOf(c) != -1;
    295     }
    296 }
    297