blob: 9def2a7cf41c39bb76decd3a9a3fbeb7b130a0c7 [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";
Hans Boehm760a9dc2015-04-20 10:27:12 -0700171 private static final String KEY_UNPROCESSED_CHARS = NAME + "_unprocessed_chars";
Hans Boehm84614952014-11-25 18:46:17 -0800172 private static final String KEY_EVAL_STATE = NAME + "_eval_state";
173 // Associated value is a byte array holding both mCalculatorState
174 // and the (much more complex) evaluator state.
Justin Klaassen741471e2014-06-11 09:43:44 -0700175
Justin Klaassen4b3af052014-05-27 17:53:10 -0700176 private CalculatorState mCurrentState;
Hans Boehm84614952014-11-25 18:46:17 -0800177 private Evaluator mEvaluator;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700178
Justin Klaassen06360f92014-08-28 11:08:44 -0700179 private View mDisplayView;
Justin Klaassend48b7562015-04-16 16:51:38 -0700180 private TextView mModeView;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700181 private CalculatorEditText mFormulaEditText;
Hans Boehm84614952014-11-25 18:46:17 -0800182 private CalculatorResult mResult;
Justin Klaassend48b7562015-04-16 16:51:38 -0700183
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100184 private ViewPager mPadViewPager;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700185 private View mDeleteButton;
186 private View mClearButton;
Justin Klaassend48b7562015-04-16 16:51:38 -0700187 private View mEqualButton;
188 private TextView mModeButton;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700189
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700190 private View mCurrentButton;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700191 private Animator mCurrentAnimator;
192
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700193 private String mUnprocessedChars = null; // Characters that were recently entered
194 // at the end of the display that have not yet
195 // been added to the underlying expression.
196
Justin Klaassen4b3af052014-05-27 17:53:10 -0700197 @Override
198 protected void onCreate(Bundle savedInstanceState) {
199 super.onCreate(savedInstanceState);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700200 setContentView(R.layout.activity_calculator);
Justin Klaassend48b7562015-04-16 16:51:38 -0700201 setActionBar((Toolbar) findViewById(R.id.toolbar));
202
203 // Hide all default options in the ActionBar.
204 getActionBar().setDisplayOptions(0);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700205
Justin Klaassen06360f92014-08-28 11:08:44 -0700206 mDisplayView = findViewById(R.id.display);
Justin Klaassend48b7562015-04-16 16:51:38 -0700207 mModeView = (TextView) findViewById(R.id.deg_rad);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700208 mFormulaEditText = (CalculatorEditText) findViewById(R.id.formula);
Hans Boehm84614952014-11-25 18:46:17 -0800209 mResult = (CalculatorResult) findViewById(R.id.result);
Justin Klaassend48b7562015-04-16 16:51:38 -0700210
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100211 mPadViewPager = (ViewPager) findViewById(R.id.pad_pager);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700212 mDeleteButton = findViewById(R.id.del);
213 mClearButton = findViewById(R.id.clr);
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700214 mEqualButton = findViewById(R.id.pad_numeric).findViewById(R.id.eq);
215 if (mEqualButton == null || mEqualButton.getVisibility() != View.VISIBLE) {
216 mEqualButton = findViewById(R.id.pad_operator).findViewById(R.id.eq);
217 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700218 mModeButton = (TextView) findViewById(R.id.mode_deg_rad);
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700219
Hans Boehm84614952014-11-25 18:46:17 -0800220 mEvaluator = new Evaluator(this, mResult);
221 mResult.setEvaluator(mEvaluator);
Hans Boehm013969e2015-04-13 20:29:47 -0700222 KeyMaps.setActivity(this);
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700223
Hans Boehm84614952014-11-25 18:46:17 -0800224 if (savedInstanceState != null) {
225 setState(CalculatorState.values()[
226 savedInstanceState.getInt(KEY_DISPLAY_STATE,
227 CalculatorState.INPUT.ordinal())]);
Hans Boehm760a9dc2015-04-20 10:27:12 -0700228 CharSequence unprocessed = savedInstanceState.getCharSequence(KEY_UNPROCESSED_CHARS);
229 if (unprocessed != null) {
230 mUnprocessedChars = unprocessed.toString();
231 }
232 byte[] state = savedInstanceState.getByteArray(KEY_EVAL_STATE);
Hans Boehm84614952014-11-25 18:46:17 -0800233 if (state != null) {
234 try (ObjectInput in = new ObjectInputStream(new ByteArrayInputStream(state))) {
235 mEvaluator.restoreInstanceState(in);
236 } catch (Throwable ignored) {
237 // When in doubt, revert to clean state
238 mCurrentState = CalculatorState.INPUT;
239 mEvaluator.clear();
240 }
241 }
242 }
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700243 mFormulaEditText.setOnKeyListener(mFormulaOnKeyListener);
Justin Klaassenfed941a2014-06-09 18:42:40 +0100244 mFormulaEditText.setOnTextSizeChangeListener(this);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700245 mFormulaEditText.setPasteListener(this);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700246 mDeleteButton.setOnLongClickListener(this);
Hans Boehmbfe8c222015-04-02 16:26:07 -0700247 updateDegreeMode(mEvaluator.getDegreeMode());
Hans Boehm84614952014-11-25 18:46:17 -0800248 if (mCurrentState == CalculatorState.EVALUATE) {
249 // Odd case. Evaluation probably took a long time. Let user ask for it again
250 mCurrentState = CalculatorState.INPUT;
251 // TODO: This can happen if the user rotates the screen.
252 // Is this rotate-to-abort behavior correct? Revisit after experimentation.
253 }
254 if (mCurrentState != CalculatorState.INPUT) {
255 setState(CalculatorState.INIT);
Hans Boehm84614952014-11-25 18:46:17 -0800256 mEvaluator.requireResult();
257 } else {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700258 redisplayAfterFormulaChange();
Hans Boehm84614952014-11-25 18:46:17 -0800259 }
260 // TODO: We're currently not saving and restoring scroll position.
261 // We probably should. Details may require care to deal with:
262 // - new display size
263 // - slow recomputation if we've scrolled far.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700264 }
265
266 @Override
Justin Klaassenf79d6f62014-08-26 12:27:08 -0700267 protected void onSaveInstanceState(@NonNull Bundle outState) {
268 // If there's an animation in progress, cancel it first to ensure our state is up-to-date.
269 if (mCurrentAnimator != null) {
270 mCurrentAnimator.cancel();
271 }
272
Justin Klaassen4b3af052014-05-27 17:53:10 -0700273 super.onSaveInstanceState(outState);
Hans Boehm84614952014-11-25 18:46:17 -0800274 outState.putInt(KEY_DISPLAY_STATE, mCurrentState.ordinal());
Hans Boehm760a9dc2015-04-20 10:27:12 -0700275 outState.putCharSequence(KEY_UNPROCESSED_CHARS, mUnprocessedChars);
Hans Boehm84614952014-11-25 18:46:17 -0800276 ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
277 try (ObjectOutput out = new ObjectOutputStream(byteArrayStream)) {
278 mEvaluator.saveInstanceState(out);
279 } catch (IOException e) {
280 // Impossible; No IO involved.
281 throw new AssertionError("Impossible IO exception", e);
282 }
283 outState.putByteArray(KEY_EVAL_STATE, byteArrayStream.toByteArray());
Justin Klaassen4b3af052014-05-27 17:53:10 -0700284 }
285
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700286 // Set the state, updating delete label and display colors.
287 // This restores display positions on moving to INPUT.
Justin Klaassend48b7562015-04-16 16:51:38 -0700288 // But movement/animation for moving to RESULT has already been done.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700289 private void setState(CalculatorState state) {
290 if (mCurrentState != state) {
Hans Boehm84614952014-11-25 18:46:17 -0800291 if (state == CalculatorState.INPUT) {
292 restoreDisplayPositions();
293 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700294 mCurrentState = state;
295
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700296 if (mCurrentState == CalculatorState.RESULT) {
297 // No longer do this for ERROR; allow mistakes to be corrected.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700298 mDeleteButton.setVisibility(View.GONE);
299 mClearButton.setVisibility(View.VISIBLE);
300 } else {
301 mDeleteButton.setVisibility(View.VISIBLE);
302 mClearButton.setVisibility(View.GONE);
303 }
304
Hans Boehm84614952014-11-25 18:46:17 -0800305 if (mCurrentState == CalculatorState.ERROR) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700306 final int errorColor = getResources().getColor(R.color.calculator_error_color);
307 mFormulaEditText.setTextColor(errorColor);
Hans Boehm84614952014-11-25 18:46:17 -0800308 mResult.setTextColor(errorColor);
Justin Klaassen8fff1442014-06-19 10:43:29 -0700309 getWindow().setStatusBarColor(errorColor);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700310 } else {
311 mFormulaEditText.setTextColor(
312 getResources().getColor(R.color.display_formula_text_color));
Hans Boehm84614952014-11-25 18:46:17 -0800313 mResult.setTextColor(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700314 getResources().getColor(R.color.display_result_text_color));
Justin Klaassen8fff1442014-06-19 10:43:29 -0700315 getWindow().setStatusBarColor(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700316 getResources().getColor(R.color.calculator_accent_color));
317 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700318
319 invalidateOptionsMenu();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700320 }
321 }
322
323 @Override
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100324 public void onBackPressed() {
325 if (mPadViewPager == null || mPadViewPager.getCurrentItem() == 0) {
326 // If the user is currently looking at the first pad (or the pad is not paged),
327 // allow the system to handle the Back button.
328 super.onBackPressed();
329 } else {
330 // Otherwise, select the previous pad.
331 mPadViewPager.setCurrentItem(mPadViewPager.getCurrentItem() - 1);
332 }
333 }
334
335 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700336 public void onUserInteraction() {
337 super.onUserInteraction();
338
339 // If there's an animation in progress, cancel it so the user interaction can be handled
340 // immediately.
341 if (mCurrentAnimator != null) {
342 mCurrentAnimator.cancel();
343 }
344 }
345
Hans Boehmbfe8c222015-04-02 16:26:07 -0700346 // Update the top corner degree/radian display and mode button
347 // to reflect the indicated current degree mode (true = degrees)
348 // TODO: Hide the top corner display until the advanced panel is exposed.
349 private void updateDegreeMode(boolean dm) {
Hans Boehmbfe8c222015-04-02 16:26:07 -0700350 if (dm) {
Justin Klaassend48b7562015-04-16 16:51:38 -0700351 mModeView.setText(R.string.mode_deg);
352 mModeButton.setText(R.string.mode_rad);
353 mModeButton.setContentDescription(getString(R.string.desc_mode_rad));
Hans Boehmbfe8c222015-04-02 16:26:07 -0700354 } else {
Justin Klaassend48b7562015-04-16 16:51:38 -0700355 mModeView.setText(R.string.mode_rad);
356 mModeButton.setText(R.string.mode_deg);
357 mModeButton.setContentDescription(getString(R.string.desc_mode_deg));
Hans Boehmbfe8c222015-04-02 16:26:07 -0700358 }
359 }
Hans Boehm84614952014-11-25 18:46:17 -0800360
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700361 // Add the given button id to input expression.
362 // If appropriate, clear the expression before doing so.
363 private void addKeyToExpr(int id) {
364 if (mCurrentState == CalculatorState.ERROR) {
365 setState(CalculatorState.INPUT);
366 } else if (mCurrentState == CalculatorState.RESULT) {
367 if (KeyMaps.isBinary(id) || KeyMaps.isSuffix(id)) {
368 mEvaluator.collapse();
369 } else {
370 mEvaluator.clear();
371 }
372 setState(CalculatorState.INPUT);
373 }
374 if (!mEvaluator.append(id)) {
375 // TODO: Some user visible feedback?
376 }
377 }
378
379 private void redisplayAfterFormulaChange() {
380 // TODO: Could do this more incrementally.
381 redisplayFormula();
382 setState(CalculatorState.INPUT);
383 mResult.clear();
384 mEvaluator.evaluateAndShowResult();
385 }
386
Justin Klaassen4b3af052014-05-27 17:53:10 -0700387 public void onButtonClick(View view) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700388 mCurrentButton = view;
389
Hans Boehm84614952014-11-25 18:46:17 -0800390 // Always cancel in-progress evaluation.
391 // If we were waiting for the result, do nothing else.
392 mEvaluator.cancelAll();
Justin Klaassend48b7562015-04-16 16:51:38 -0700393
Hans Boehm84614952014-11-25 18:46:17 -0800394 if (mCurrentState == CalculatorState.EVALUATE
395 || mCurrentState == CalculatorState.ANIMATE) {
396 onCancelled();
397 return;
398 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700399
400
401 final int id = view.getId();
Hans Boehm84614952014-11-25 18:46:17 -0800402 switch (id) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700403 case R.id.eq:
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700404 onEquals();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700405 break;
406 case R.id.del:
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700407 onDelete();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700408 break;
409 case R.id.clr:
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700410 onClear();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700411 break;
Hans Boehmbfe8c222015-04-02 16:26:07 -0700412 case R.id.mode_deg_rad:
413 boolean mode = !mEvaluator.getDegreeMode();
414 updateDegreeMode(mode);
415 if (mCurrentState == CalculatorState.RESULT) {
416 mEvaluator.collapse(); // Capture result evaluated in old mode
417 redisplayFormula();
418 }
419 // In input mode, we reinterpret already entered trig functions.
420 mEvaluator.setDegreeMode(mode);
421 setState(CalculatorState.INPUT);
422 mResult.clear();
423 mEvaluator.evaluateAndShowResult();
424 break;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700425 default:
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700426 addKeyToExpr(id);
427 redisplayAfterFormulaChange();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700428 break;
429 }
430 }
431
Hans Boehm84614952014-11-25 18:46:17 -0800432 void redisplayFormula() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700433 String formula = mEvaluator.getExpr().toString(this);
434 if (mUnprocessedChars != null) {
435 // Add and highlight characters we couldn't process.
436 SpannableString formatted = new SpannableString(formula + mUnprocessedChars);
437 // TODO: should probably match this to the error color.
438 formatted.setSpan(new ForegroundColorSpan(Color.RED),
439 formula.length(), formatted.length(),
440 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
441 mFormulaEditText.setText(formatted);
442 } else {
443 mFormulaEditText.setText(formula);
444 }
Hans Boehm84614952014-11-25 18:46:17 -0800445 }
446
Justin Klaassen4b3af052014-05-27 17:53:10 -0700447 @Override
448 public boolean onLongClick(View view) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700449 mCurrentButton = view;
450
Justin Klaassen4b3af052014-05-27 17:53:10 -0700451 if (view.getId() == R.id.del) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700452 onClear();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700453 return true;
454 }
455 return false;
456 }
457
Hans Boehm84614952014-11-25 18:46:17 -0800458 // Initial evaluation completed successfully. Initiate display.
459 public void onEvaluate(int initDisplayPrec, String truncatedWholeNumber) {
Justin Klaassend48b7562015-04-16 16:51:38 -0700460 // Invalidate any options that may depend on the current result.
461 invalidateOptionsMenu();
462
Justin Klaassen4b3af052014-05-27 17:53:10 -0700463 if (mCurrentState == CalculatorState.INPUT) {
Hans Boehm84614952014-11-25 18:46:17 -0800464 // Just update small result display.
465 mResult.displayResult(initDisplayPrec, truncatedWholeNumber);
466 } else { // in EVALUATE or INIT state
467 mResult.displayResult(initDisplayPrec, truncatedWholeNumber);
468 onResult(mCurrentState != CalculatorState.INIT);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700469 }
Hans Boehm84614952014-11-25 18:46:17 -0800470 }
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700471
Hans Boehm84614952014-11-25 18:46:17 -0800472 public void onCancelled() {
473 // We should be in EVALUATE state.
474 // Display is still in input state.
475 setState(CalculatorState.INPUT);
476 }
477
478 // Reevaluation completed; ask result to redisplay current value.
479 public void onReevaluate()
480 {
481 mResult.redisplay();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700482 }
483
Justin Klaassenfed941a2014-06-09 18:42:40 +0100484 @Override
485 public void onTextSizeChanged(final TextView textView, float oldSize) {
486 if (mCurrentState != CalculatorState.INPUT) {
487 // Only animate text changes that occur from user input.
488 return;
489 }
490
491 // Calculate the values needed to perform the scale and translation animations,
492 // maintaining the same apparent baseline for the displayed text.
493 final float textScale = oldSize / textView.getTextSize();
494 final float translationX = (1.0f - textScale) *
495 (textView.getWidth() / 2.0f - textView.getPaddingEnd());
496 final float translationY = (1.0f - textScale) *
497 (textView.getHeight() / 2.0f - textView.getPaddingBottom());
498
499 final AnimatorSet animatorSet = new AnimatorSet();
500 animatorSet.playTogether(
501 ObjectAnimator.ofFloat(textView, View.SCALE_X, textScale, 1.0f),
502 ObjectAnimator.ofFloat(textView, View.SCALE_Y, textScale, 1.0f),
503 ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, translationX, 0.0f),
504 ObjectAnimator.ofFloat(textView, View.TRANSLATION_Y, translationY, 0.0f));
Justin Klaassen94db7202014-06-11 11:22:31 -0700505 animatorSet.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
Justin Klaassenfed941a2014-06-09 18:42:40 +0100506 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
507 animatorSet.start();
508 }
509
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700510 private void onEquals() {
511 if (mCurrentState == CalculatorState.INPUT) {
512 setState(CalculatorState.EVALUATE);
Hans Boehm84614952014-11-25 18:46:17 -0800513 mEvaluator.requireResult();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700514 }
515 }
516
517 private void onDelete() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700518 // Delete works like backspace; remove the last character or operator from the expression.
519 // Note that we handle keyboard delete exactly like the delete button. For
520 // example the delete button can be used to delete a character from an incomplete
521 // function name typed on a physical keyboard.
Hans Boehm84614952014-11-25 18:46:17 -0800522 mEvaluator.cancelAll();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700523 // This should be impossible in RESULT state.
524 setState(CalculatorState.INPUT);
525 if (mUnprocessedChars != null) {
526 int len = mUnprocessedChars.length();
527 if (len > 0) {
528 mUnprocessedChars = mUnprocessedChars.substring(0, len-1);
529 } else {
530 mEvaluator.getExpr().delete();
531 }
532 } else {
533 mEvaluator.getExpr().delete();
534 }
535 redisplayAfterFormulaChange();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700536 }
537
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700538 private void reveal(View sourceView, int colorRes, AnimatorListener listener) {
Justin Klaassen06360f92014-08-28 11:08:44 -0700539 final ViewGroupOverlay groupOverlay =
540 (ViewGroupOverlay) getWindow().getDecorView().getOverlay();
Justin Klaassen8fff1442014-06-19 10:43:29 -0700541
542 final Rect displayRect = new Rect();
Justin Klaassen06360f92014-08-28 11:08:44 -0700543 mDisplayView.getGlobalVisibleRect(displayRect);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700544
545 // Make reveal cover the display and status bar.
546 final View revealView = new View(this);
Justin Klaassen8fff1442014-06-19 10:43:29 -0700547 revealView.setBottom(displayRect.bottom);
548 revealView.setLeft(displayRect.left);
549 revealView.setRight(displayRect.right);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700550 revealView.setBackgroundColor(getResources().getColor(colorRes));
Justin Klaassen06360f92014-08-28 11:08:44 -0700551 groupOverlay.add(revealView);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700552
Justin Klaassen4b3af052014-05-27 17:53:10 -0700553 final int[] clearLocation = new int[2];
554 sourceView.getLocationInWindow(clearLocation);
555 clearLocation[0] += sourceView.getWidth() / 2;
556 clearLocation[1] += sourceView.getHeight() / 2;
557
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700558 final int revealCenterX = clearLocation[0] - revealView.getLeft();
559 final int revealCenterY = clearLocation[1] - revealView.getTop();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700560
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700561 final double x1_2 = Math.pow(revealView.getLeft() - revealCenterX, 2);
562 final double x2_2 = Math.pow(revealView.getRight() - revealCenterX, 2);
563 final double y_2 = Math.pow(revealView.getTop() - revealCenterY, 2);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700564 final float revealRadius = (float) Math.max(Math.sqrt(x1_2 + y_2), Math.sqrt(x2_2 + y_2));
565
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700566 final Animator revealAnimator =
567 ViewAnimationUtils.createCircularReveal(revealView,
ztenghui3d6ecaf2014-06-05 09:56:00 -0700568 revealCenterX, revealCenterY, 0.0f, revealRadius);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700569 revealAnimator.setDuration(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700570 getResources().getInteger(android.R.integer.config_longAnimTime));
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700571 revealAnimator.addListener(listener);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700572
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700573 final Animator alphaAnimator = ObjectAnimator.ofFloat(revealView, View.ALPHA, 0.0f);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700574 alphaAnimator.setDuration(
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700575 getResources().getInteger(android.R.integer.config_mediumAnimTime));
Justin Klaassen4b3af052014-05-27 17:53:10 -0700576
577 final AnimatorSet animatorSet = new AnimatorSet();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700578 animatorSet.play(revealAnimator).before(alphaAnimator);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700579 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
580 animatorSet.addListener(new AnimatorListenerAdapter() {
581 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700582 public void onAnimationEnd(Animator animator) {
Justin Klaassen8fff1442014-06-19 10:43:29 -0700583 groupOverlay.remove(revealView);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700584 mCurrentAnimator = null;
585 }
586 });
587
588 mCurrentAnimator = animatorSet;
589 animatorSet.start();
590 }
591
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700592 private void onClear() {
Hans Boehm84614952014-11-25 18:46:17 -0800593 if (mEvaluator.getExpr().isEmpty()) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700594 return;
595 }
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700596 reveal(mCurrentButton, R.color.calculator_accent_color, new AnimatorListenerAdapter() {
597 @Override
598 public void onAnimationEnd(Animator animation) {
Hans Boehm760a9dc2015-04-20 10:27:12 -0700599 mUnprocessedChars = null;
600 mResult.clear();
601 mEvaluator.clear();
602 setState(CalculatorState.INPUT);
Hans Boehm84614952014-11-25 18:46:17 -0800603 redisplayFormula();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700604 }
605 });
606 }
607
Hans Boehm84614952014-11-25 18:46:17 -0800608 // Evaluation encountered en error. Display the error.
609 void onError(final int errorResourceId) {
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700610 if (mCurrentState != CalculatorState.EVALUATE) {
611 // Only animate error on evaluate.
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700612 return;
613 }
614
Hans Boehm84614952014-11-25 18:46:17 -0800615 setState(CalculatorState.ANIMATE);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700616 reveal(mCurrentButton, R.color.calculator_error_color, new AnimatorListenerAdapter() {
617 @Override
618 public void onAnimationEnd(Animator animation) {
619 setState(CalculatorState.ERROR);
Hans Boehm84614952014-11-25 18:46:17 -0800620 mResult.displayError(errorResourceId);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700621 }
622 });
623 }
624
Hans Boehm84614952014-11-25 18:46:17 -0800625
626 // Animate movement of result into the top formula slot.
627 // Result window now remains translated in the top slot while the result is displayed.
628 // (We convert it back to formula use only when the user provides new input.)
629 // Historical note: In the Lollipop version, this invisibly and instantaeously moved
630 // formula and result displays back at the end of the animation. We no longer do that,
631 // so that we can continue to properly support scrolling of the result.
632 // We assume the result already contains the text to be expanded.
633 private void onResult(boolean animate) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700634 // Calculate the values needed to perform the scale and translation animations.
635 // We now fix the character size in the display to avoid weird effects
Hans Boehm84614952014-11-25 18:46:17 -0800636 // when we scroll.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700637 // Display.xml is designed to ensure exactly a 3/2 ratio between the formula
638 // slot and small result slot.
639 final float resultScale = 1.5f;
640 final float resultTranslationX = -mResult.getWidth() * (resultScale - 1)/2;
641 // mFormulaEditText is aligned with mResult on the right.
642 // When we enlarge it around its center, the right side
643 // moves to the right. This compensates.
644 float resultTranslationY = -mResult.getHeight();
645 // This is how much we want to move the bottom.
646 // Now compensate for the fact that we're
647 // simultaenously expanding it around its center by half its height
Hans Boehm760a9dc2015-04-20 10:27:12 -0700648 resultTranslationY += mResult.getHeight() * (resultScale - 1)/2;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700649 final float formulaTranslationY = -mFormulaEditText.getBottom();
650
Hans Boehm84614952014-11-25 18:46:17 -0800651 // TODO: Reintroduce textColorAnimator?
652 // The initial and final colors seemed to be the same in L.
653 // With the new model, the result logically changes back to a formula
654 // only when we switch back to INPUT state, so it's unclear that animating
655 // a color change here makes sense.
656 if (animate) {
657 final AnimatorSet animatorSet = new AnimatorSet();
658 animatorSet.playTogether(
659 ObjectAnimator.ofFloat(mResult, View.SCALE_X, resultScale),
660 ObjectAnimator.ofFloat(mResult, View.SCALE_Y, resultScale),
661 ObjectAnimator.ofFloat(mResult, View.TRANSLATION_X, resultTranslationX),
662 ObjectAnimator.ofFloat(mResult, View.TRANSLATION_Y, resultTranslationY),
663 ObjectAnimator.ofFloat(mFormulaEditText, View.TRANSLATION_Y,
664 formulaTranslationY));
665 animatorSet.setDuration(
666 getResources().getInteger(android.R.integer.config_longAnimTime));
667 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
668 animatorSet.addListener(new AnimatorListenerAdapter() {
669 @Override
670 public void onAnimationStart(Animator animation) {
671 // Result should already be displayed; no need to do anything.
672 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700673
Hans Boehm84614952014-11-25 18:46:17 -0800674 @Override
675 public void onAnimationEnd(Animator animation) {
676 setState(CalculatorState.RESULT);
677 mCurrentAnimator = null;
678 }
679 });
Justin Klaassen4b3af052014-05-27 17:53:10 -0700680
Hans Boehm84614952014-11-25 18:46:17 -0800681 mCurrentAnimator = animatorSet;
682 animatorSet.start();
683 } else /* No animation desired; get there fast, e.g. when restarting */ {
684 mResult.setScaleX(resultScale);
685 mResult.setScaleY(resultScale);
686 mResult.setTranslationX(resultTranslationX);
687 mResult.setTranslationY(resultTranslationY);
688 mFormulaEditText.setTranslationY(formulaTranslationY);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700689 setState(CalculatorState.RESULT);
Hans Boehm84614952014-11-25 18:46:17 -0800690 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700691 }
Hans Boehm84614952014-11-25 18:46:17 -0800692
693 // Restore positions of the formula and result displays back to their original,
694 // pre-animation state.
695 private void restoreDisplayPositions() {
696 // Clear result.
697 mResult.setText("");
698 // Reset all of the values modified during the animation.
699 mResult.setScaleX(1.0f);
700 mResult.setScaleY(1.0f);
701 mResult.setTranslationX(0.0f);
702 mResult.setTranslationY(0.0f);
703 mFormulaEditText.setTranslationY(0.0f);
704
705 mFormulaEditText.requestFocus();
706 }
707
Justin Klaassend48b7562015-04-16 16:51:38 -0700708 @Override
709 public boolean onCreateOptionsMenu(Menu menu) {
710 getMenuInflater().inflate(R.menu.overflow, menu);
711 return true;
712 }
713
714 @Override
715 public boolean onPrepareOptionsMenu(Menu menu) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700716 if (mCurrentState != CalculatorState.RESULT) {
717 menu.findItem(R.id.menu_fraction).setEnabled(false);
718 menu.findItem(R.id.menu_leading).setEnabled(false);
719 } else if (mEvaluator.getRational() == null) {
720 menu.findItem(R.id.menu_fraction).setEnabled(false);
721 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700722 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800723 }
724
725 @Override
Justin Klaassend48b7562015-04-16 16:51:38 -0700726 public boolean onOptionsItemSelected(MenuItem item) {
Hans Boehm84614952014-11-25 18:46:17 -0800727 switch (item.getItemId()) {
728 case R.id.menu_help:
729 displayHelpMessage();
730 return true;
731 case R.id.menu_about:
732 displayAboutPage();
733 return true;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700734 case R.id.menu_fraction:
735 displayFraction();
736 return true;
737 case R.id.menu_leading:
738 displayFull();
739 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800740 default:
741 return super.onOptionsItemSelected(item);
742 }
743 }
744
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700745 private void displayMessage(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800746 AlertDialog.Builder builder = new AlertDialog.Builder(this);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700747 builder.setMessage(s)
748 .setNegativeButton(R.string.dismiss,
Hans Boehm84614952014-11-25 18:46:17 -0800749 new DialogInterface.OnClickListener() {
750 public void onClick(DialogInterface d, int which) { }
751 })
752 .show();
753 }
754
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700755 private void displayHelpMessage() {
756 Resources res = getResources();
757 String msg = res.getString(R.string.help_message);
758 if (mPadViewPager != null) {
759 msg += res.getString(R.string.help_pager);
760 }
761 displayMessage(msg);
762 }
763
764 private void displayFraction() {
765 BoundedRational result = mEvaluator.getRational();
Hans Boehm013969e2015-04-13 20:29:47 -0700766 displayMessage(KeyMaps.translateResult(result.toNiceString()));
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700767 }
768
769 // Display full result to currently evaluated precision
770 private void displayFull() {
771 Resources res = getResources();
772 String msg = mResult.getFullText() + " ";
773 if (mResult.fullTextIsExact()) {
774 msg += res.getString(R.string.exact);
775 } else {
776 msg += res.getString(R.string.approximate);
777 }
778 displayMessage(msg);
779 }
780
Hans Boehm84614952014-11-25 18:46:17 -0800781 private void displayAboutPage() {
782 WebView wv = new WebView(this);
783 wv.loadUrl("file:///android_asset/about.txt");
784 new AlertDialog.Builder(this)
785 .setView(wv)
786 .setNegativeButton(R.string.dismiss,
787 new DialogInterface.OnClickListener() {
788 public void onClick(DialogInterface d, int which) { }
789 })
790 .show();
791 }
792
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700793 // Add input characters to the end of the expression by mapping them to
794 // the appropriate button pushes when possible. Leftover characters
795 // are added to mUnprocessedChars, which is presumed to immediately
796 // precede the newly added characters.
797 private void addChars(String moreChars) {
798 if (mUnprocessedChars != null) {
799 moreChars = mUnprocessedChars + moreChars;
800 }
801 int current = 0;
802 int len = moreChars.length();
803 while (current < len) {
804 char c = moreChars.charAt(current);
Hans Boehm013969e2015-04-13 20:29:47 -0700805 int k = KeyMaps.keyForChar(c);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700806 if (k != View.NO_ID) {
807 mCurrentButton = findViewById(k);
808 addKeyToExpr(k);
809 if (Character.isSurrogate(c)) {
810 current += 2;
811 } else {
812 ++current;
813 }
814 continue;
815 }
Hans Boehm013969e2015-04-13 20:29:47 -0700816 int f = KeyMaps.funForString(moreChars, current);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700817 if (f != View.NO_ID) {
818 mCurrentButton = findViewById(f);
819 addKeyToExpr(f);
820 if (f == R.id.op_sqrt) {
821 // Square root entered as function; don't lose the parenthesis.
822 addKeyToExpr(R.id.lparen);
823 }
824 current = moreChars.indexOf('(', current) + 1;
825 continue;
826 }
827 // There are characters left, but we can't convert them to button presses.
828 mUnprocessedChars = moreChars.substring(current);
829 redisplayAfterFormulaChange();
830 return;
831 }
832 mUnprocessedChars = null;
833 redisplayAfterFormulaChange();
834 return;
Hans Boehm84614952014-11-25 18:46:17 -0800835 }
836
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700837 @Override
838 public boolean paste(Uri uri) {
839 if (mEvaluator.isLastSaved(uri)) {
840 if (mCurrentState == CalculatorState.ERROR
841 || mCurrentState == CalculatorState.RESULT) {
842 setState(CalculatorState.INPUT);
843 mEvaluator.clear();
Hans Boehm84614952014-11-25 18:46:17 -0800844 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700845 mEvaluator.addSaved();
846 redisplayAfterFormulaChange();
847 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800848 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700849 return false;
Hans Boehm84614952014-11-25 18:46:17 -0800850 }
851
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700852 @Override
853 public void paste(String s) {
854 addChars(s);
Hans Boehm84614952014-11-25 18:46:17 -0800855 }
856
Justin Klaassen4b3af052014-05-27 17:53:10 -0700857}