• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
onDeleteModeChange()62         void onDeleteModeChange();
63     }
64 
65     private Listener mListener;
66     private Context mContext;
67     private Set<Entry<String, String>> mTranslationsSet;
68 
Logic(Context context, History history, CalculatorDisplay display)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 
setListener(Listener listener)77     public void setListener(Listener listener) {
78         this.mListener = listener;
79     }
80 
setDeleteMode(int mode)81     public void setDeleteMode(int mode) {
82         if (mDeleteMode != mode) {
83             mDeleteMode = mode;
84             mListener.onDeleteModeChange();
85         }
86     }
87 
getDeleteMode()88     public int getDeleteMode() {
89         return mDeleteMode;
90     }
91 
setLineLength(int nDigits)92     void setLineLength(int nDigits) {
93         mLineLength = nDigits;
94     }
95 
eatHorizontalMove(boolean toLeft)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 
getText()102     private String getText() {
103         return mDisplay.getText().toString();
104     }
105 
insert(String delta)106     void insert(String delta) {
107         mDisplay.insert(delta);
108         setDeleteMode(DELETE_MODE_BACKSPACE);
109     }
110 
onTextChanged()111     public void onTextChanged() {
112         setDeleteMode(DELETE_MODE_BACKSPACE);
113     }
114 
resumeWithHistory()115     public void resumeWithHistory() {
116         clearWithHistory(false);
117     }
118 
clearWithHistory(boolean scroll)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 
clear(boolean scroll)135     private void clear(boolean scroll) {
136         mHistory.enter("");
137         mDisplay.setText("", scroll ? CalculatorDisplay.Scroll.UP : CalculatorDisplay.Scroll.NONE);
138         cleared();
139     }
140 
cleared()141     void cleared() {
142         mResult = "";
143         mIsError = false;
144         updateHistory();
145 
146         setDeleteMode(DELETE_MODE_BACKSPACE);
147     }
148 
acceptInsert(String delta)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 
onDelete()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 
onClear()166     void onClear() {
167         clear(mDeleteMode == DELETE_MODE_CLEAR);
168     }
169 
onEnter()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 
evaluateAndShowResult(String text, Scroll scroll)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 
onUp()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 
onDown()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 
updateHistory()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;
evaluate(String input)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 
addTranslation(HashMap<String, String> map, int t, int m)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 
replaceTranslations(String input)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 
tryFormattingWithPrecision(double value, int precision)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 
isOperator(String text)325     static boolean isOperator(String text) {
326         return text.length() == 1 && isOperator(text.charAt(0));
327     }
328 
isOperator(char c)329     static boolean isOperator(char c) {
330         //plus minus times div
331         return "+\u2212\u00d7\u00f7/*".indexOf(c) != -1;
332     }
333 }
334