blob: 0d4ab2d4cd9ad30742d94c70dc0a45f6915e4eac [file] [log] [blame]
Justin Klaassen4b3af052014-05-27 17:53:10 -07001/*
2 * Copyright (C) 2014 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
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070017// FIXME: Menu handling, particularly for cut/paste, is very ugly
18// and not the way it was intended.
19// Other menus are not handled brilliantly either.
20// TODO: Revisit handling of "Help" menu, so that it's more consistent
21// with our conventions.
22// TODO: See if we can make scrolling look better, especially on small
23// displays. Fix evaluation interface so the evaluator returns entire
24// result, and formatting of exponent etc. is done separately.
25// TODO: Better indication of when the result is known to be exact.
Hans Boehm84614952014-11-25 18:46:17 -080026// TODO: Fix placement of inverse trig buttons.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070027// TODO: Check and possibly fix accessability issues.
Hans Boehm013969e2015-04-13 20:29:47 -070028// TODO: Copy & more general paste in formula? Note that this requires
29// great care: Currently the text version of a displayed formula
30// is not directly useful for re-evaluating the formula later, since
31// it contains ellipses representing subexpressions evaluated with
32// a different degree mode. Rather than supporting copy from the
33// formula window, we may eventually want to support generation of a
34// more useful text version in a separate window. It's not clear
35// this is worth the added (code and user) complexity.
Hans Boehm84614952014-11-25 18:46:17 -080036
Justin Klaassen4b3af052014-05-27 17:53:10 -070037package com.android.calculator2;
38
39import android.animation.Animator;
Justin Klaassen5f2a3342014-06-11 17:40:22 -070040import android.animation.Animator.AnimatorListener;
Justin Klaassen4b3af052014-05-27 17:53:10 -070041import android.animation.AnimatorListenerAdapter;
42import android.animation.AnimatorSet;
43import android.animation.ArgbEvaluator;
44import android.animation.ObjectAnimator;
45import android.animation.ValueAnimator;
46import android.animation.ValueAnimator.AnimatorUpdateListener;
47import android.app.Activity;
Hans Boehm84614952014-11-25 18:46:17 -080048import android.app.AlertDialog;
49import android.content.Context;
50import android.content.DialogInterface;
Hans Boehmbfe8c222015-04-02 16:26:07 -070051import android.content.res.Resources;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070052import android.graphics.Color;
Justin Klaassen8fff1442014-06-19 10:43:29 -070053import android.graphics.Rect;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070054import android.net.Uri;
Justin Klaassen4b3af052014-05-27 17:53:10 -070055import android.os.Bundle;
Justin Klaassenf79d6f62014-08-26 12:27:08 -070056import android.support.annotation.NonNull;
Justin Klaassen3b4d13d2014-06-06 18:18:37 +010057import android.support.v4.view.ViewPager;
Justin Klaassen4b3af052014-05-27 17:53:10 -070058import android.text.Editable;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070059import android.text.SpannableString;
60import android.text.Spanned;
Justin Klaassen4b3af052014-05-27 17:53:10 -070061import android.text.TextUtils;
62import android.text.TextWatcher;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070063import android.text.style.ForegroundColorSpan;
Hans Boehm84614952014-11-25 18:46:17 -080064import android.util.Log;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070065import android.view.KeyCharacterMap;
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -070066import android.view.KeyEvent;
Hans Boehm84614952014-11-25 18:46:17 -080067import android.view.Menu;
68import android.view.MenuItem;
Justin Klaassen4b3af052014-05-27 17:53:10 -070069import android.view.View;
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -070070import android.view.View.OnKeyListener;
Justin Klaassen4b3af052014-05-27 17:53:10 -070071import android.view.View.OnLongClickListener;
Justin Klaassen5f2a3342014-06-11 17:40:22 -070072import android.view.ViewAnimationUtils;
Justin Klaassen8fff1442014-06-19 10:43:29 -070073import android.view.ViewGroupOverlay;
Justin Klaassen4b3af052014-05-27 17:53:10 -070074import android.view.animation.AccelerateDecelerateInterpolator;
Hans Boehm84614952014-11-25 18:46:17 -080075import android.webkit.WebView;
Justin Klaassenfed941a2014-06-09 18:42:40 +010076import android.widget.TextView;
Justin Klaassend48b7562015-04-16 16:51:38 -070077import android.widget.Toolbar;
Justin Klaassenfed941a2014-06-09 18:42:40 +010078
79import com.android.calculator2.CalculatorEditText.OnTextSizeChangeListener;
Hans Boehm84614952014-11-25 18:46:17 -080080
81import java.io.ByteArrayInputStream;
82import java.io.ObjectInputStream;
83import java.io.ByteArrayOutputStream;
84import java.io.ObjectOutputStream;
85import java.io.ObjectInput;
86import java.io.ObjectOutput;
87import java.io.IOException;
88import java.text.DecimalFormatSymbols; // TODO: May eventually not need this here.
Justin Klaassen4b3af052014-05-27 17:53:10 -070089
Justin Klaassen04f79c72014-06-27 17:25:35 -070090public class Calculator extends Activity
Justin Klaassend48b7562015-04-16 16:51:38 -070091 implements OnTextSizeChangeListener, OnLongClickListener, CalculatorEditText.PasteListener {
Justin Klaassen2be4fdb2014-08-06 19:54:09 -070092
93 /**
94 * Constant for an invalid resource id.
95 */
96 public static final int INVALID_RES_ID = -1;
Justin Klaassen4b3af052014-05-27 17:53:10 -070097
98 private enum CalculatorState {
Hans Boehm84614952014-11-25 18:46:17 -080099 INPUT, // Result and formula both visible, no evaluation requested,
100 // Though result may be visible on bottom line.
101 EVALUATE, // Both visible, evaluation requested, evaluation/animation incomplete.
102 INIT, // Very temporary state used as alternative to EVALUATE
103 // during reinitialization. Do not animate on completion.
104 ANIMATE, // Result computed, animation to enlarge result window in progress.
105 RESULT, // Result displayed, formula invisible.
106 // If we are in RESULT state, the formula was evaluated without
107 // error to initial precision.
108 ERROR // Error displayed: Formula visible, result shows error message.
109 // Display similar to INPUT state.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700110 }
Hans Boehm84614952014-11-25 18:46:17 -0800111 // Normal transition sequence is
112 // INPUT -> EVALUATE -> ANIMATE -> RESULT (or ERROR) -> INPUT
113 // A RESULT -> ERROR transition is possible in rare corner cases, in which
114 // a higher precision evaluation exposes an error. This is possible, since we
115 // initially evaluate assuming we were given a well-defined problem. If we
116 // were actually asked to compute sqrt(<extremely tiny negative number>) we produce 0
117 // unless we are asked for enough precision that we can distinguish the argument from zero.
118 // TODO: Consider further heuristics to reduce the chance of observing this?
119 // It already seems to be observable only in contrived cases.
120 // ANIMATE, ERROR, and RESULT are translated to an INIT state if the application
121 // is restarted in that state. This leads us to recompute and redisplay the result
122 // ASAP.
123 // TODO: Possibly save a bit more information, e.g. its initial display string
124 // or most significant digit position, to speed up restart.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700125
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700126 // We currently assume that the formula does not change out from under us in
127 // any way. We explicitly handle all input to the formula here.
128 // TODO: Perhaps the formula should not be editable at all?
Justin Klaassen4b3af052014-05-27 17:53:10 -0700129
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700130 private final OnKeyListener mFormulaOnKeyListener = new OnKeyListener() {
131 @Override
132 public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700133 if (keyEvent.getAction() != KeyEvent.ACTION_UP) return true;
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700134 switch (keyCode) {
135 case KeyEvent.KEYCODE_NUMPAD_ENTER:
136 case KeyEvent.KEYCODE_ENTER:
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700137 case KeyEvent.KEYCODE_DPAD_CENTER:
138 mCurrentButton = mEqualButton;
139 onEquals();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700140 return true;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700141 case KeyEvent.KEYCODE_DEL:
142 mCurrentButton = mDeleteButton;
143 onDelete();
144 return true;
145 default:
146 final int raw = keyEvent.getKeyCharacterMap()
147 .get(keyCode, keyEvent.getMetaState());
148 if ((raw & KeyCharacterMap.COMBINING_ACCENT) != 0) {
149 return true; // discard
150 }
151 // Try to discard non-printing characters and the like.
152 // The user will have to explicitly delete other junk that gets past us.
153 if (Character.isIdentifierIgnorable(raw)
154 || Character.isWhitespace(raw)) {
155 return true;
156 }
157 char c = (char)raw;
158 if (c == '=') {
159 onEquals();
160 } else {
161 addChars(String.valueOf(c));
162 redisplayAfterFormulaChange();
163 }
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700164 }
165 return false;
166 }
167 };
168
Hans Boehm84614952014-11-25 18:46:17 -0800169 private static final String NAME = Calculator.class.getName();
170 private static final String KEY_DISPLAY_STATE = NAME + "_display_state";
171 private static final String KEY_EVAL_STATE = NAME + "_eval_state";
172 // Associated value is a byte array holding both mCalculatorState
173 // and the (much more complex) evaluator state.
Justin Klaassen741471e2014-06-11 09:43:44 -0700174
Justin Klaassen4b3af052014-05-27 17:53:10 -0700175 private CalculatorState mCurrentState;
Hans Boehm84614952014-11-25 18:46:17 -0800176 private Evaluator mEvaluator;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700177
Justin Klaassen06360f92014-08-28 11:08:44 -0700178 private View mDisplayView;
Justin Klaassend48b7562015-04-16 16:51:38 -0700179 private TextView mModeView;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700180 private CalculatorEditText mFormulaEditText;
Hans Boehm84614952014-11-25 18:46:17 -0800181 private CalculatorResult mResult;
Justin Klaassend48b7562015-04-16 16:51:38 -0700182
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100183 private ViewPager mPadViewPager;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700184 private View mDeleteButton;
185 private View mClearButton;
Justin Klaassend48b7562015-04-16 16:51:38 -0700186 private View mEqualButton;
187 private TextView mModeButton;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700188
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700189 private View mCurrentButton;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700190 private Animator mCurrentAnimator;
191
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700192 private String mUnprocessedChars = null; // Characters that were recently entered
193 // at the end of the display that have not yet
194 // been added to the underlying expression.
195
Justin Klaassen4b3af052014-05-27 17:53:10 -0700196 @Override
197 protected void onCreate(Bundle savedInstanceState) {
198 super.onCreate(savedInstanceState);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700199 setContentView(R.layout.activity_calculator);
Justin Klaassend48b7562015-04-16 16:51:38 -0700200 setActionBar((Toolbar) findViewById(R.id.toolbar));
201
202 // Hide all default options in the ActionBar.
203 getActionBar().setDisplayOptions(0);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700204
Justin Klaassen06360f92014-08-28 11:08:44 -0700205 mDisplayView = findViewById(R.id.display);
Justin Klaassend48b7562015-04-16 16:51:38 -0700206 mModeView = (TextView) findViewById(R.id.deg_rad);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700207 mFormulaEditText = (CalculatorEditText) findViewById(R.id.formula);
Hans Boehm84614952014-11-25 18:46:17 -0800208 mResult = (CalculatorResult) findViewById(R.id.result);
Justin Klaassend48b7562015-04-16 16:51:38 -0700209
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100210 mPadViewPager = (ViewPager) findViewById(R.id.pad_pager);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700211 mDeleteButton = findViewById(R.id.del);
212 mClearButton = findViewById(R.id.clr);
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700213 mEqualButton = findViewById(R.id.pad_numeric).findViewById(R.id.eq);
214 if (mEqualButton == null || mEqualButton.getVisibility() != View.VISIBLE) {
215 mEqualButton = findViewById(R.id.pad_operator).findViewById(R.id.eq);
216 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700217 mModeButton = (TextView) findViewById(R.id.mode_deg_rad);
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700218
Hans Boehm84614952014-11-25 18:46:17 -0800219 mEvaluator = new Evaluator(this, mResult);
220 mResult.setEvaluator(mEvaluator);
Hans Boehm013969e2015-04-13 20:29:47 -0700221 KeyMaps.setActivity(this);
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700222
Hans Boehm84614952014-11-25 18:46:17 -0800223 if (savedInstanceState != null) {
224 setState(CalculatorState.values()[
225 savedInstanceState.getInt(KEY_DISPLAY_STATE,
226 CalculatorState.INPUT.ordinal())]);
227 byte[] state =
228 savedInstanceState.getByteArray(KEY_EVAL_STATE);
229 if (state != null) {
230 try (ObjectInput in = new ObjectInputStream(new ByteArrayInputStream(state))) {
231 mEvaluator.restoreInstanceState(in);
232 } catch (Throwable ignored) {
233 // When in doubt, revert to clean state
234 mCurrentState = CalculatorState.INPUT;
235 mEvaluator.clear();
236 }
237 }
238 }
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700239 mFormulaEditText.setOnKeyListener(mFormulaOnKeyListener);
Justin Klaassenfed941a2014-06-09 18:42:40 +0100240 mFormulaEditText.setOnTextSizeChangeListener(this);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700241 mFormulaEditText.setPasteListener(this);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700242 mDeleteButton.setOnLongClickListener(this);
Hans Boehmbfe8c222015-04-02 16:26:07 -0700243 updateDegreeMode(mEvaluator.getDegreeMode());
Hans Boehm84614952014-11-25 18:46:17 -0800244 if (mCurrentState == CalculatorState.EVALUATE) {
245 // Odd case. Evaluation probably took a long time. Let user ask for it again
246 mCurrentState = CalculatorState.INPUT;
247 // TODO: This can happen if the user rotates the screen.
248 // Is this rotate-to-abort behavior correct? Revisit after experimentation.
249 }
250 if (mCurrentState != CalculatorState.INPUT) {
251 setState(CalculatorState.INIT);
Hans Boehm84614952014-11-25 18:46:17 -0800252 mEvaluator.requireResult();
253 } else {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700254 redisplayAfterFormulaChange();
Hans Boehm84614952014-11-25 18:46:17 -0800255 }
256 // TODO: We're currently not saving and restoring scroll position.
257 // We probably should. Details may require care to deal with:
258 // - new display size
259 // - slow recomputation if we've scrolled far.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700260 }
261
262 @Override
Justin Klaassenf79d6f62014-08-26 12:27:08 -0700263 protected void onSaveInstanceState(@NonNull Bundle outState) {
264 // If there's an animation in progress, cancel it first to ensure our state is up-to-date.
265 if (mCurrentAnimator != null) {
266 mCurrentAnimator.cancel();
267 }
268
Justin Klaassen4b3af052014-05-27 17:53:10 -0700269 super.onSaveInstanceState(outState);
Hans Boehm84614952014-11-25 18:46:17 -0800270 outState.putInt(KEY_DISPLAY_STATE, mCurrentState.ordinal());
271 ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
272 try (ObjectOutput out = new ObjectOutputStream(byteArrayStream)) {
273 mEvaluator.saveInstanceState(out);
274 } catch (IOException e) {
275 // Impossible; No IO involved.
276 throw new AssertionError("Impossible IO exception", e);
277 }
278 outState.putByteArray(KEY_EVAL_STATE, byteArrayStream.toByteArray());
Justin Klaassen4b3af052014-05-27 17:53:10 -0700279 }
280
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700281 // Set the state, updating delete label and display colors.
282 // This restores display positions on moving to INPUT.
Justin Klaassend48b7562015-04-16 16:51:38 -0700283 // But movement/animation for moving to RESULT has already been done.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700284 private void setState(CalculatorState state) {
285 if (mCurrentState != state) {
Hans Boehm84614952014-11-25 18:46:17 -0800286 if (state == CalculatorState.INPUT) {
287 restoreDisplayPositions();
288 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700289 mCurrentState = state;
290
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700291 if (mCurrentState == CalculatorState.RESULT) {
292 // No longer do this for ERROR; allow mistakes to be corrected.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700293 mDeleteButton.setVisibility(View.GONE);
294 mClearButton.setVisibility(View.VISIBLE);
295 } else {
296 mDeleteButton.setVisibility(View.VISIBLE);
297 mClearButton.setVisibility(View.GONE);
298 }
299
Hans Boehm84614952014-11-25 18:46:17 -0800300 if (mCurrentState == CalculatorState.ERROR) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700301 final int errorColor = getResources().getColor(R.color.calculator_error_color);
302 mFormulaEditText.setTextColor(errorColor);
Hans Boehm84614952014-11-25 18:46:17 -0800303 mResult.setTextColor(errorColor);
Justin Klaassen8fff1442014-06-19 10:43:29 -0700304 getWindow().setStatusBarColor(errorColor);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700305 } else {
306 mFormulaEditText.setTextColor(
307 getResources().getColor(R.color.display_formula_text_color));
Hans Boehm84614952014-11-25 18:46:17 -0800308 mResult.setTextColor(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700309 getResources().getColor(R.color.display_result_text_color));
Justin Klaassen8fff1442014-06-19 10:43:29 -0700310 getWindow().setStatusBarColor(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700311 getResources().getColor(R.color.calculator_accent_color));
312 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700313
314 invalidateOptionsMenu();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700315 }
316 }
317
318 @Override
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100319 public void onBackPressed() {
320 if (mPadViewPager == null || mPadViewPager.getCurrentItem() == 0) {
321 // If the user is currently looking at the first pad (or the pad is not paged),
322 // allow the system to handle the Back button.
323 super.onBackPressed();
324 } else {
325 // Otherwise, select the previous pad.
326 mPadViewPager.setCurrentItem(mPadViewPager.getCurrentItem() - 1);
327 }
328 }
329
330 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700331 public void onUserInteraction() {
332 super.onUserInteraction();
333
334 // If there's an animation in progress, cancel it so the user interaction can be handled
335 // immediately.
336 if (mCurrentAnimator != null) {
337 mCurrentAnimator.cancel();
338 }
339 }
340
Hans Boehmbfe8c222015-04-02 16:26:07 -0700341 // Update the top corner degree/radian display and mode button
342 // to reflect the indicated current degree mode (true = degrees)
343 // TODO: Hide the top corner display until the advanced panel is exposed.
344 private void updateDegreeMode(boolean dm) {
Hans Boehmbfe8c222015-04-02 16:26:07 -0700345 if (dm) {
Justin Klaassend48b7562015-04-16 16:51:38 -0700346 mModeView.setText(R.string.mode_deg);
347 mModeButton.setText(R.string.mode_rad);
348 mModeButton.setContentDescription(getString(R.string.desc_mode_rad));
Hans Boehmbfe8c222015-04-02 16:26:07 -0700349 } else {
Justin Klaassend48b7562015-04-16 16:51:38 -0700350 mModeView.setText(R.string.mode_rad);
351 mModeButton.setText(R.string.mode_deg);
352 mModeButton.setContentDescription(getString(R.string.desc_mode_deg));
Hans Boehmbfe8c222015-04-02 16:26:07 -0700353 }
354 }
Hans Boehm84614952014-11-25 18:46:17 -0800355
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700356 // Add the given button id to input expression.
357 // If appropriate, clear the expression before doing so.
358 private void addKeyToExpr(int id) {
359 if (mCurrentState == CalculatorState.ERROR) {
360 setState(CalculatorState.INPUT);
361 } else if (mCurrentState == CalculatorState.RESULT) {
362 if (KeyMaps.isBinary(id) || KeyMaps.isSuffix(id)) {
363 mEvaluator.collapse();
364 } else {
365 mEvaluator.clear();
366 }
367 setState(CalculatorState.INPUT);
368 }
369 if (!mEvaluator.append(id)) {
370 // TODO: Some user visible feedback?
371 }
372 }
373
374 private void redisplayAfterFormulaChange() {
375 // TODO: Could do this more incrementally.
376 redisplayFormula();
377 setState(CalculatorState.INPUT);
378 mResult.clear();
379 mEvaluator.evaluateAndShowResult();
380 }
381
Justin Klaassen4b3af052014-05-27 17:53:10 -0700382 public void onButtonClick(View view) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700383 mCurrentButton = view;
384
Hans Boehm84614952014-11-25 18:46:17 -0800385 // Always cancel in-progress evaluation.
386 // If we were waiting for the result, do nothing else.
387 mEvaluator.cancelAll();
Justin Klaassend48b7562015-04-16 16:51:38 -0700388
Hans Boehm84614952014-11-25 18:46:17 -0800389 if (mCurrentState == CalculatorState.EVALUATE
390 || mCurrentState == CalculatorState.ANIMATE) {
391 onCancelled();
392 return;
393 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700394
395
396 final int id = view.getId();
Hans Boehm84614952014-11-25 18:46:17 -0800397 switch (id) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700398 case R.id.eq:
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700399 onEquals();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700400 break;
401 case R.id.del:
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700402 onDelete();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700403 break;
404 case R.id.clr:
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700405 onClear();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700406 break;
Hans Boehmbfe8c222015-04-02 16:26:07 -0700407 case R.id.mode_deg_rad:
408 boolean mode = !mEvaluator.getDegreeMode();
409 updateDegreeMode(mode);
410 if (mCurrentState == CalculatorState.RESULT) {
411 mEvaluator.collapse(); // Capture result evaluated in old mode
412 redisplayFormula();
413 }
414 // In input mode, we reinterpret already entered trig functions.
415 mEvaluator.setDegreeMode(mode);
416 setState(CalculatorState.INPUT);
417 mResult.clear();
418 mEvaluator.evaluateAndShowResult();
419 break;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700420 default:
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700421 addKeyToExpr(id);
422 redisplayAfterFormulaChange();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700423 break;
424 }
425 }
426
Hans Boehm84614952014-11-25 18:46:17 -0800427 void redisplayFormula() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700428 String formula = mEvaluator.getExpr().toString(this);
429 if (mUnprocessedChars != null) {
430 // Add and highlight characters we couldn't process.
431 SpannableString formatted = new SpannableString(formula + mUnprocessedChars);
432 // TODO: should probably match this to the error color.
433 formatted.setSpan(new ForegroundColorSpan(Color.RED),
434 formula.length(), formatted.length(),
435 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
436 mFormulaEditText.setText(formatted);
437 } else {
438 mFormulaEditText.setText(formula);
439 }
Hans Boehm84614952014-11-25 18:46:17 -0800440 }
441
Justin Klaassen4b3af052014-05-27 17:53:10 -0700442 @Override
443 public boolean onLongClick(View view) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700444 mCurrentButton = view;
445
Justin Klaassen4b3af052014-05-27 17:53:10 -0700446 if (view.getId() == R.id.del) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700447 onClear();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700448 return true;
449 }
450 return false;
451 }
452
Hans Boehm84614952014-11-25 18:46:17 -0800453 // Initial evaluation completed successfully. Initiate display.
454 public void onEvaluate(int initDisplayPrec, String truncatedWholeNumber) {
Justin Klaassend48b7562015-04-16 16:51:38 -0700455 // Invalidate any options that may depend on the current result.
456 invalidateOptionsMenu();
457
Justin Klaassen4b3af052014-05-27 17:53:10 -0700458 if (mCurrentState == CalculatorState.INPUT) {
Hans Boehm84614952014-11-25 18:46:17 -0800459 // Just update small result display.
460 mResult.displayResult(initDisplayPrec, truncatedWholeNumber);
461 } else { // in EVALUATE or INIT state
462 mResult.displayResult(initDisplayPrec, truncatedWholeNumber);
463 onResult(mCurrentState != CalculatorState.INIT);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700464 }
Hans Boehm84614952014-11-25 18:46:17 -0800465 }
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700466
Hans Boehm84614952014-11-25 18:46:17 -0800467 public void onCancelled() {
468 // We should be in EVALUATE state.
469 // Display is still in input state.
470 setState(CalculatorState.INPUT);
471 }
472
473 // Reevaluation completed; ask result to redisplay current value.
474 public void onReevaluate()
475 {
476 mResult.redisplay();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700477 }
478
Justin Klaassenfed941a2014-06-09 18:42:40 +0100479 @Override
480 public void onTextSizeChanged(final TextView textView, float oldSize) {
481 if (mCurrentState != CalculatorState.INPUT) {
482 // Only animate text changes that occur from user input.
483 return;
484 }
485
486 // Calculate the values needed to perform the scale and translation animations,
487 // maintaining the same apparent baseline for the displayed text.
488 final float textScale = oldSize / textView.getTextSize();
489 final float translationX = (1.0f - textScale) *
490 (textView.getWidth() / 2.0f - textView.getPaddingEnd());
491 final float translationY = (1.0f - textScale) *
492 (textView.getHeight() / 2.0f - textView.getPaddingBottom());
493
494 final AnimatorSet animatorSet = new AnimatorSet();
495 animatorSet.playTogether(
496 ObjectAnimator.ofFloat(textView, View.SCALE_X, textScale, 1.0f),
497 ObjectAnimator.ofFloat(textView, View.SCALE_Y, textScale, 1.0f),
498 ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, translationX, 0.0f),
499 ObjectAnimator.ofFloat(textView, View.TRANSLATION_Y, translationY, 0.0f));
Justin Klaassen94db7202014-06-11 11:22:31 -0700500 animatorSet.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
Justin Klaassenfed941a2014-06-09 18:42:40 +0100501 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
502 animatorSet.start();
503 }
504
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700505 private void onEquals() {
506 if (mCurrentState == CalculatorState.INPUT) {
507 setState(CalculatorState.EVALUATE);
Hans Boehm84614952014-11-25 18:46:17 -0800508 mEvaluator.requireResult();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700509 }
510 }
511
512 private void onDelete() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700513 // Delete works like backspace; remove the last character or operator from the expression.
514 // Note that we handle keyboard delete exactly like the delete button. For
515 // example the delete button can be used to delete a character from an incomplete
516 // function name typed on a physical keyboard.
Hans Boehm84614952014-11-25 18:46:17 -0800517 mEvaluator.cancelAll();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700518 // This should be impossible in RESULT state.
519 setState(CalculatorState.INPUT);
520 if (mUnprocessedChars != null) {
521 int len = mUnprocessedChars.length();
522 if (len > 0) {
523 mUnprocessedChars = mUnprocessedChars.substring(0, len-1);
524 } else {
525 mEvaluator.getExpr().delete();
526 }
527 } else {
528 mEvaluator.getExpr().delete();
529 }
530 redisplayAfterFormulaChange();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700531 }
532
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700533 private void reveal(View sourceView, int colorRes, AnimatorListener listener) {
Justin Klaassen06360f92014-08-28 11:08:44 -0700534 final ViewGroupOverlay groupOverlay =
535 (ViewGroupOverlay) getWindow().getDecorView().getOverlay();
Justin Klaassen8fff1442014-06-19 10:43:29 -0700536
537 final Rect displayRect = new Rect();
Justin Klaassen06360f92014-08-28 11:08:44 -0700538 mDisplayView.getGlobalVisibleRect(displayRect);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700539
540 // Make reveal cover the display and status bar.
541 final View revealView = new View(this);
Justin Klaassen8fff1442014-06-19 10:43:29 -0700542 revealView.setBottom(displayRect.bottom);
543 revealView.setLeft(displayRect.left);
544 revealView.setRight(displayRect.right);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700545 revealView.setBackgroundColor(getResources().getColor(colorRes));
Justin Klaassen06360f92014-08-28 11:08:44 -0700546 groupOverlay.add(revealView);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700547
Justin Klaassen4b3af052014-05-27 17:53:10 -0700548 final int[] clearLocation = new int[2];
549 sourceView.getLocationInWindow(clearLocation);
550 clearLocation[0] += sourceView.getWidth() / 2;
551 clearLocation[1] += sourceView.getHeight() / 2;
552
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700553 final int revealCenterX = clearLocation[0] - revealView.getLeft();
554 final int revealCenterY = clearLocation[1] - revealView.getTop();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700555
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700556 final double x1_2 = Math.pow(revealView.getLeft() - revealCenterX, 2);
557 final double x2_2 = Math.pow(revealView.getRight() - revealCenterX, 2);
558 final double y_2 = Math.pow(revealView.getTop() - revealCenterY, 2);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700559 final float revealRadius = (float) Math.max(Math.sqrt(x1_2 + y_2), Math.sqrt(x2_2 + y_2));
560
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700561 final Animator revealAnimator =
562 ViewAnimationUtils.createCircularReveal(revealView,
ztenghui3d6ecaf2014-06-05 09:56:00 -0700563 revealCenterX, revealCenterY, 0.0f, revealRadius);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700564 revealAnimator.setDuration(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700565 getResources().getInteger(android.R.integer.config_longAnimTime));
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700566 revealAnimator.addListener(listener);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700567
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700568 final Animator alphaAnimator = ObjectAnimator.ofFloat(revealView, View.ALPHA, 0.0f);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700569 alphaAnimator.setDuration(
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700570 getResources().getInteger(android.R.integer.config_mediumAnimTime));
Justin Klaassen4b3af052014-05-27 17:53:10 -0700571
572 final AnimatorSet animatorSet = new AnimatorSet();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700573 animatorSet.play(revealAnimator).before(alphaAnimator);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700574 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
575 animatorSet.addListener(new AnimatorListenerAdapter() {
576 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700577 public void onAnimationEnd(Animator animator) {
Justin Klaassen8fff1442014-06-19 10:43:29 -0700578 groupOverlay.remove(revealView);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700579 mCurrentAnimator = null;
580 }
581 });
582
583 mCurrentAnimator = animatorSet;
584 animatorSet.start();
585 }
586
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700587 private void onClear() {
Hans Boehm84614952014-11-25 18:46:17 -0800588 if (mEvaluator.getExpr().isEmpty()) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700589 return;
590 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700591 mUnprocessedChars = null;
Hans Boehm84614952014-11-25 18:46:17 -0800592 mResult.clear();
593 mEvaluator.clear();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700594 reveal(mCurrentButton, R.color.calculator_accent_color, new AnimatorListenerAdapter() {
595 @Override
596 public void onAnimationEnd(Animator animation) {
Hans Boehm84614952014-11-25 18:46:17 -0800597 redisplayFormula();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700598 }
599 });
600 }
601
Hans Boehm84614952014-11-25 18:46:17 -0800602 // Evaluation encountered en error. Display the error.
603 void onError(final int errorResourceId) {
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700604 if (mCurrentState != CalculatorState.EVALUATE) {
605 // Only animate error on evaluate.
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700606 return;
607 }
608
Hans Boehm84614952014-11-25 18:46:17 -0800609 setState(CalculatorState.ANIMATE);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700610 reveal(mCurrentButton, R.color.calculator_error_color, new AnimatorListenerAdapter() {
611 @Override
612 public void onAnimationEnd(Animator animation) {
613 setState(CalculatorState.ERROR);
Hans Boehm84614952014-11-25 18:46:17 -0800614 mResult.displayError(errorResourceId);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700615 }
616 });
617 }
618
Hans Boehm84614952014-11-25 18:46:17 -0800619
620 // Animate movement of result into the top formula slot.
621 // Result window now remains translated in the top slot while the result is displayed.
622 // (We convert it back to formula use only when the user provides new input.)
623 // Historical note: In the Lollipop version, this invisibly and instantaeously moved
624 // formula and result displays back at the end of the animation. We no longer do that,
625 // so that we can continue to properly support scrolling of the result.
626 // We assume the result already contains the text to be expanded.
627 private void onResult(boolean animate) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700628 // Calculate the values needed to perform the scale and translation animations.
629 // We now fix the character size in the display to avoid weird effects
Hans Boehm84614952014-11-25 18:46:17 -0800630 // when we scroll.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700631 // Display.xml is designed to ensure exactly a 3/2 ratio between the formula
632 // slot and small result slot.
633 final float resultScale = 1.5f;
634 final float resultTranslationX = -mResult.getWidth() * (resultScale - 1)/2;
635 // mFormulaEditText is aligned with mResult on the right.
636 // When we enlarge it around its center, the right side
637 // moves to the right. This compensates.
638 float resultTranslationY = -mResult.getHeight();
639 // This is how much we want to move the bottom.
640 // Now compensate for the fact that we're
641 // simultaenously expanding it around its center by half its height
642 resultTranslationY += mResult.getHeight() * (resultScale-1)/2;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700643 final float formulaTranslationY = -mFormulaEditText.getBottom();
644
Hans Boehm84614952014-11-25 18:46:17 -0800645 // TODO: Reintroduce textColorAnimator?
646 // The initial and final colors seemed to be the same in L.
647 // With the new model, the result logically changes back to a formula
648 // only when we switch back to INPUT state, so it's unclear that animating
649 // a color change here makes sense.
650 if (animate) {
651 final AnimatorSet animatorSet = new AnimatorSet();
652 animatorSet.playTogether(
653 ObjectAnimator.ofFloat(mResult, View.SCALE_X, resultScale),
654 ObjectAnimator.ofFloat(mResult, View.SCALE_Y, resultScale),
655 ObjectAnimator.ofFloat(mResult, View.TRANSLATION_X, resultTranslationX),
656 ObjectAnimator.ofFloat(mResult, View.TRANSLATION_Y, resultTranslationY),
657 ObjectAnimator.ofFloat(mFormulaEditText, View.TRANSLATION_Y,
658 formulaTranslationY));
659 animatorSet.setDuration(
660 getResources().getInteger(android.R.integer.config_longAnimTime));
661 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
662 animatorSet.addListener(new AnimatorListenerAdapter() {
663 @Override
664 public void onAnimationStart(Animator animation) {
665 // Result should already be displayed; no need to do anything.
666 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700667
Hans Boehm84614952014-11-25 18:46:17 -0800668 @Override
669 public void onAnimationEnd(Animator animation) {
670 setState(CalculatorState.RESULT);
671 mCurrentAnimator = null;
672 }
673 });
Justin Klaassen4b3af052014-05-27 17:53:10 -0700674
Hans Boehm84614952014-11-25 18:46:17 -0800675 mCurrentAnimator = animatorSet;
676 animatorSet.start();
677 } else /* No animation desired; get there fast, e.g. when restarting */ {
678 mResult.setScaleX(resultScale);
679 mResult.setScaleY(resultScale);
680 mResult.setTranslationX(resultTranslationX);
681 mResult.setTranslationY(resultTranslationY);
682 mFormulaEditText.setTranslationY(formulaTranslationY);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700683 setState(CalculatorState.RESULT);
Hans Boehm84614952014-11-25 18:46:17 -0800684 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700685 }
Hans Boehm84614952014-11-25 18:46:17 -0800686
687 // Restore positions of the formula and result displays back to their original,
688 // pre-animation state.
689 private void restoreDisplayPositions() {
690 // Clear result.
691 mResult.setText("");
692 // Reset all of the values modified during the animation.
693 mResult.setScaleX(1.0f);
694 mResult.setScaleY(1.0f);
695 mResult.setTranslationX(0.0f);
696 mResult.setTranslationY(0.0f);
697 mFormulaEditText.setTranslationY(0.0f);
698
699 mFormulaEditText.requestFocus();
700 }
701
Justin Klaassend48b7562015-04-16 16:51:38 -0700702 @Override
703 public boolean onCreateOptionsMenu(Menu menu) {
704 getMenuInflater().inflate(R.menu.overflow, menu);
705 return true;
706 }
707
708 @Override
709 public boolean onPrepareOptionsMenu(Menu menu) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700710 if (mCurrentState != CalculatorState.RESULT) {
711 menu.findItem(R.id.menu_fraction).setEnabled(false);
712 menu.findItem(R.id.menu_leading).setEnabled(false);
713 } else if (mEvaluator.getRational() == null) {
714 menu.findItem(R.id.menu_fraction).setEnabled(false);
715 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700716 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800717 }
718
719 @Override
Justin Klaassend48b7562015-04-16 16:51:38 -0700720 public boolean onOptionsItemSelected(MenuItem item) {
Hans Boehm84614952014-11-25 18:46:17 -0800721 switch (item.getItemId()) {
722 case R.id.menu_help:
723 displayHelpMessage();
724 return true;
725 case R.id.menu_about:
726 displayAboutPage();
727 return true;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700728 case R.id.menu_fraction:
729 displayFraction();
730 return true;
731 case R.id.menu_leading:
732 displayFull();
733 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800734 default:
735 return super.onOptionsItemSelected(item);
736 }
737 }
738
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700739 private void displayMessage(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800740 AlertDialog.Builder builder = new AlertDialog.Builder(this);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700741 builder.setMessage(s)
742 .setNegativeButton(R.string.dismiss,
Hans Boehm84614952014-11-25 18:46:17 -0800743 new DialogInterface.OnClickListener() {
744 public void onClick(DialogInterface d, int which) { }
745 })
746 .show();
747 }
748
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700749 private void displayHelpMessage() {
750 Resources res = getResources();
751 String msg = res.getString(R.string.help_message);
752 if (mPadViewPager != null) {
753 msg += res.getString(R.string.help_pager);
754 }
755 displayMessage(msg);
756 }
757
758 private void displayFraction() {
759 BoundedRational result = mEvaluator.getRational();
Hans Boehm013969e2015-04-13 20:29:47 -0700760 displayMessage(KeyMaps.translateResult(result.toNiceString()));
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700761 }
762
763 // Display full result to currently evaluated precision
764 private void displayFull() {
765 Resources res = getResources();
766 String msg = mResult.getFullText() + " ";
767 if (mResult.fullTextIsExact()) {
768 msg += res.getString(R.string.exact);
769 } else {
770 msg += res.getString(R.string.approximate);
771 }
772 displayMessage(msg);
773 }
774
Hans Boehm84614952014-11-25 18:46:17 -0800775 private void displayAboutPage() {
776 WebView wv = new WebView(this);
777 wv.loadUrl("file:///android_asset/about.txt");
778 new AlertDialog.Builder(this)
779 .setView(wv)
780 .setNegativeButton(R.string.dismiss,
781 new DialogInterface.OnClickListener() {
782 public void onClick(DialogInterface d, int which) { }
783 })
784 .show();
785 }
786
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700787 // Add input characters to the end of the expression by mapping them to
788 // the appropriate button pushes when possible. Leftover characters
789 // are added to mUnprocessedChars, which is presumed to immediately
790 // precede the newly added characters.
791 private void addChars(String moreChars) {
792 if (mUnprocessedChars != null) {
793 moreChars = mUnprocessedChars + moreChars;
794 }
795 int current = 0;
796 int len = moreChars.length();
797 while (current < len) {
798 char c = moreChars.charAt(current);
Hans Boehm013969e2015-04-13 20:29:47 -0700799 int k = KeyMaps.keyForChar(c);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700800 if (k != View.NO_ID) {
801 mCurrentButton = findViewById(k);
802 addKeyToExpr(k);
803 if (Character.isSurrogate(c)) {
804 current += 2;
805 } else {
806 ++current;
807 }
808 continue;
809 }
Hans Boehm013969e2015-04-13 20:29:47 -0700810 int f = KeyMaps.funForString(moreChars, current);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700811 if (f != View.NO_ID) {
812 mCurrentButton = findViewById(f);
813 addKeyToExpr(f);
814 if (f == R.id.op_sqrt) {
815 // Square root entered as function; don't lose the parenthesis.
816 addKeyToExpr(R.id.lparen);
817 }
818 current = moreChars.indexOf('(', current) + 1;
819 continue;
820 }
821 // There are characters left, but we can't convert them to button presses.
822 mUnprocessedChars = moreChars.substring(current);
823 redisplayAfterFormulaChange();
824 return;
825 }
826 mUnprocessedChars = null;
827 redisplayAfterFormulaChange();
828 return;
Hans Boehm84614952014-11-25 18:46:17 -0800829 }
830
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700831 @Override
832 public boolean paste(Uri uri) {
833 if (mEvaluator.isLastSaved(uri)) {
834 if (mCurrentState == CalculatorState.ERROR
835 || mCurrentState == CalculatorState.RESULT) {
836 setState(CalculatorState.INPUT);
837 mEvaluator.clear();
Hans Boehm84614952014-11-25 18:46:17 -0800838 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700839 mEvaluator.addSaved();
840 redisplayAfterFormulaChange();
841 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800842 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700843 return false;
Hans Boehm84614952014-11-25 18:46:17 -0800844 }
845
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700846 @Override
847 public void paste(String s) {
848 addChars(s);
Hans Boehm84614952014-11-25 18:46:17 -0800849 }
850
Justin Klaassen4b3af052014-05-27 17:53:10 -0700851}