blob: e2c006c5ae97ccb63f1dabae492e10742914a592 [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.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070022// TODO: Better indication of when the result is known to be exact.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070023// TODO: Check and possibly fix accessability issues.
Hans Boehm013969e2015-04-13 20:29:47 -070024// TODO: Copy & more general paste in formula? Note that this requires
25// great care: Currently the text version of a displayed formula
26// is not directly useful for re-evaluating the formula later, since
27// it contains ellipses representing subexpressions evaluated with
28// a different degree mode. Rather than supporting copy from the
29// formula window, we may eventually want to support generation of a
30// more useful text version in a separate window. It's not clear
31// this is worth the added (code and user) complexity.
Hans Boehm84614952014-11-25 18:46:17 -080032
Justin Klaassen4b3af052014-05-27 17:53:10 -070033package com.android.calculator2;
34
35import android.animation.Animator;
Justin Klaassen5f2a3342014-06-11 17:40:22 -070036import android.animation.Animator.AnimatorListener;
Justin Klaassen4b3af052014-05-27 17:53:10 -070037import android.animation.AnimatorListenerAdapter;
38import android.animation.AnimatorSet;
Justin Klaassen4b3af052014-05-27 17:53:10 -070039import android.animation.ObjectAnimator;
Justin Klaassen4b3af052014-05-27 17:53:10 -070040import android.app.Activity;
Hans Boehm84614952014-11-25 18:46:17 -080041import android.app.AlertDialog;
Hans Boehm84614952014-11-25 18:46:17 -080042import android.content.DialogInterface;
Justin Klaassend36d63e2015-05-05 12:59:36 -070043import android.content.Intent;
Hans Boehmbfe8c222015-04-02 16:26:07 -070044import android.content.res.Resources;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070045import android.graphics.Color;
Justin Klaassen8fff1442014-06-19 10:43:29 -070046import android.graphics.Rect;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070047import android.net.Uri;
Justin Klaassen4b3af052014-05-27 17:53:10 -070048import android.os.Bundle;
Justin Klaassenf79d6f62014-08-26 12:27:08 -070049import android.support.annotation.NonNull;
Justin Klaassen3b4d13d2014-06-06 18:18:37 +010050import android.support.v4.view.ViewPager;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070051import android.text.SpannableString;
52import android.text.Spanned;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070053import android.text.style.ForegroundColorSpan;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070054import android.view.KeyCharacterMap;
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -070055import android.view.KeyEvent;
Hans Boehm84614952014-11-25 18:46:17 -080056import android.view.Menu;
57import android.view.MenuItem;
Justin Klaassen4b3af052014-05-27 17:53:10 -070058import android.view.View;
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -070059import android.view.View.OnKeyListener;
Justin Klaassen4b3af052014-05-27 17:53:10 -070060import android.view.View.OnLongClickListener;
Justin Klaassen5f2a3342014-06-11 17:40:22 -070061import android.view.ViewAnimationUtils;
Justin Klaassen8fff1442014-06-19 10:43:29 -070062import android.view.ViewGroupOverlay;
Justin Klaassen4b3af052014-05-27 17:53:10 -070063import android.view.animation.AccelerateDecelerateInterpolator;
Justin Klaassenfed941a2014-06-09 18:42:40 +010064import android.widget.TextView;
Justin Klaassend48b7562015-04-16 16:51:38 -070065import android.widget.Toolbar;
Justin Klaassenfed941a2014-06-09 18:42:40 +010066
Hans Boehm08e8f322015-04-21 13:18:38 -070067import com.android.calculator2.CalculatorText.OnTextSizeChangeListener;
Hans Boehm84614952014-11-25 18:46:17 -080068
69import java.io.ByteArrayInputStream;
70import java.io.ObjectInputStream;
71import java.io.ByteArrayOutputStream;
72import java.io.ObjectOutputStream;
73import java.io.ObjectInput;
74import java.io.ObjectOutput;
75import java.io.IOException;
Justin Klaassen4b3af052014-05-27 17:53:10 -070076
Justin Klaassen04f79c72014-06-27 17:25:35 -070077public class Calculator extends Activity
Hans Boehm08e8f322015-04-21 13:18:38 -070078 implements OnTextSizeChangeListener, OnLongClickListener, CalculatorText.PasteListener {
Justin Klaassen2be4fdb2014-08-06 19:54:09 -070079
80 /**
81 * Constant for an invalid resource id.
82 */
83 public static final int INVALID_RES_ID = -1;
Justin Klaassen4b3af052014-05-27 17:53:10 -070084
85 private enum CalculatorState {
Hans Boehm84614952014-11-25 18:46:17 -080086 INPUT, // Result and formula both visible, no evaluation requested,
87 // Though result may be visible on bottom line.
88 EVALUATE, // Both visible, evaluation requested, evaluation/animation incomplete.
89 INIT, // Very temporary state used as alternative to EVALUATE
90 // during reinitialization. Do not animate on completion.
91 ANIMATE, // Result computed, animation to enlarge result window in progress.
92 RESULT, // Result displayed, formula invisible.
93 // If we are in RESULT state, the formula was evaluated without
94 // error to initial precision.
95 ERROR // Error displayed: Formula visible, result shows error message.
96 // Display similar to INPUT state.
Justin Klaassen4b3af052014-05-27 17:53:10 -070097 }
Hans Boehm84614952014-11-25 18:46:17 -080098 // Normal transition sequence is
99 // INPUT -> EVALUATE -> ANIMATE -> RESULT (or ERROR) -> INPUT
100 // A RESULT -> ERROR transition is possible in rare corner cases, in which
101 // a higher precision evaluation exposes an error. This is possible, since we
102 // initially evaluate assuming we were given a well-defined problem. If we
103 // were actually asked to compute sqrt(<extremely tiny negative number>) we produce 0
104 // unless we are asked for enough precision that we can distinguish the argument from zero.
105 // TODO: Consider further heuristics to reduce the chance of observing this?
106 // It already seems to be observable only in contrived cases.
107 // ANIMATE, ERROR, and RESULT are translated to an INIT state if the application
108 // is restarted in that state. This leads us to recompute and redisplay the result
109 // ASAP.
110 // TODO: Possibly save a bit more information, e.g. its initial display string
111 // or most significant digit position, to speed up restart.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700112
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700113 // We currently assume that the formula does not change out from under us in
114 // any way. We explicitly handle all input to the formula here.
115 // TODO: Perhaps the formula should not be editable at all?
Justin Klaassen4b3af052014-05-27 17:53:10 -0700116
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700117 private final OnKeyListener mFormulaOnKeyListener = new OnKeyListener() {
118 @Override
119 public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700120 if (keyEvent.getAction() != KeyEvent.ACTION_UP) return true;
Hans Boehm1176f232015-05-11 16:26:03 -0700121 stopActionMode();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700122 switch (keyCode) {
123 case KeyEvent.KEYCODE_NUMPAD_ENTER:
124 case KeyEvent.KEYCODE_ENTER:
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700125 case KeyEvent.KEYCODE_DPAD_CENTER:
126 mCurrentButton = mEqualButton;
127 onEquals();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700128 return true;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700129 case KeyEvent.KEYCODE_DEL:
130 mCurrentButton = mDeleteButton;
131 onDelete();
132 return true;
133 default:
134 final int raw = keyEvent.getKeyCharacterMap()
135 .get(keyCode, keyEvent.getMetaState());
136 if ((raw & KeyCharacterMap.COMBINING_ACCENT) != 0) {
137 return true; // discard
138 }
139 // Try to discard non-printing characters and the like.
140 // The user will have to explicitly delete other junk that gets past us.
141 if (Character.isIdentifierIgnorable(raw)
142 || Character.isWhitespace(raw)) {
143 return true;
144 }
145 char c = (char)raw;
146 if (c == '=') {
Hans Boehme57fb012015-05-07 19:52:32 -0700147 mCurrentButton = mEqualButton;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700148 onEquals();
149 } else {
150 addChars(String.valueOf(c));
151 redisplayAfterFormulaChange();
152 }
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700153 }
154 return false;
155 }
156 };
157
Hans Boehm84614952014-11-25 18:46:17 -0800158 private static final String NAME = Calculator.class.getName();
159 private static final String KEY_DISPLAY_STATE = NAME + "_display_state";
Hans Boehm760a9dc2015-04-20 10:27:12 -0700160 private static final String KEY_UNPROCESSED_CHARS = NAME + "_unprocessed_chars";
Hans Boehm84614952014-11-25 18:46:17 -0800161 private static final String KEY_EVAL_STATE = NAME + "_eval_state";
162 // Associated value is a byte array holding both mCalculatorState
163 // and the (much more complex) evaluator state.
Justin Klaassen741471e2014-06-11 09:43:44 -0700164
Justin Klaassen4b3af052014-05-27 17:53:10 -0700165 private CalculatorState mCurrentState;
Hans Boehm84614952014-11-25 18:46:17 -0800166 private Evaluator mEvaluator;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700167
Justin Klaassen06360f92014-08-28 11:08:44 -0700168 private View mDisplayView;
Justin Klaassend48b7562015-04-16 16:51:38 -0700169 private TextView mModeView;
Hans Boehm08e8f322015-04-21 13:18:38 -0700170 private CalculatorText mFormulaText;
Hans Boehm84614952014-11-25 18:46:17 -0800171 private CalculatorResult mResult;
Justin Klaassend48b7562015-04-16 16:51:38 -0700172
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100173 private ViewPager mPadViewPager;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700174 private View mDeleteButton;
175 private View mClearButton;
Justin Klaassend48b7562015-04-16 16:51:38 -0700176 private View mEqualButton;
177 private TextView mModeButton;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700178
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700179 private View mCurrentButton;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700180 private Animator mCurrentAnimator;
181
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700182 private String mUnprocessedChars = null; // Characters that were recently entered
183 // at the end of the display that have not yet
184 // been added to the underlying expression.
185
Justin Klaassen4b3af052014-05-27 17:53:10 -0700186 @Override
187 protected void onCreate(Bundle savedInstanceState) {
188 super.onCreate(savedInstanceState);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700189 setContentView(R.layout.activity_calculator);
Justin Klaassend48b7562015-04-16 16:51:38 -0700190 setActionBar((Toolbar) findViewById(R.id.toolbar));
191
192 // Hide all default options in the ActionBar.
193 getActionBar().setDisplayOptions(0);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700194
Justin Klaassen06360f92014-08-28 11:08:44 -0700195 mDisplayView = findViewById(R.id.display);
Justin Klaassend48b7562015-04-16 16:51:38 -0700196 mModeView = (TextView) findViewById(R.id.deg_rad);
Hans Boehm08e8f322015-04-21 13:18:38 -0700197 mFormulaText = (CalculatorText) findViewById(R.id.formula);
Hans Boehm84614952014-11-25 18:46:17 -0800198 mResult = (CalculatorResult) findViewById(R.id.result);
Justin Klaassend48b7562015-04-16 16:51:38 -0700199
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100200 mPadViewPager = (ViewPager) findViewById(R.id.pad_pager);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700201 mDeleteButton = findViewById(R.id.del);
202 mClearButton = findViewById(R.id.clr);
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700203 mEqualButton = findViewById(R.id.pad_numeric).findViewById(R.id.eq);
204 if (mEqualButton == null || mEqualButton.getVisibility() != View.VISIBLE) {
205 mEqualButton = findViewById(R.id.pad_operator).findViewById(R.id.eq);
206 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700207 mModeButton = (TextView) findViewById(R.id.mode_deg_rad);
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700208
Hans Boehm84614952014-11-25 18:46:17 -0800209 mEvaluator = new Evaluator(this, mResult);
210 mResult.setEvaluator(mEvaluator);
Hans Boehm013969e2015-04-13 20:29:47 -0700211 KeyMaps.setActivity(this);
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700212
Hans Boehm84614952014-11-25 18:46:17 -0800213 if (savedInstanceState != null) {
214 setState(CalculatorState.values()[
215 savedInstanceState.getInt(KEY_DISPLAY_STATE,
216 CalculatorState.INPUT.ordinal())]);
Hans Boehm760a9dc2015-04-20 10:27:12 -0700217 CharSequence unprocessed = savedInstanceState.getCharSequence(KEY_UNPROCESSED_CHARS);
218 if (unprocessed != null) {
219 mUnprocessedChars = unprocessed.toString();
220 }
221 byte[] state = savedInstanceState.getByteArray(KEY_EVAL_STATE);
Hans Boehm84614952014-11-25 18:46:17 -0800222 if (state != null) {
223 try (ObjectInput in = new ObjectInputStream(new ByteArrayInputStream(state))) {
224 mEvaluator.restoreInstanceState(in);
225 } catch (Throwable ignored) {
226 // When in doubt, revert to clean state
227 mCurrentState = CalculatorState.INPUT;
228 mEvaluator.clear();
229 }
230 }
Hans Boehmfbcef702015-04-27 18:07:47 -0700231 } else {
232 mCurrentState = CalculatorState.INPUT;
233 mEvaluator.clear();
Hans Boehm84614952014-11-25 18:46:17 -0800234 }
Hans Boehm08e8f322015-04-21 13:18:38 -0700235 mFormulaText.setOnKeyListener(mFormulaOnKeyListener);
236 mFormulaText.setOnTextSizeChangeListener(this);
237 mFormulaText.setPasteListener(this);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700238 mDeleteButton.setOnLongClickListener(this);
Hans Boehmbfe8c222015-04-02 16:26:07 -0700239 updateDegreeMode(mEvaluator.getDegreeMode());
Hans Boehm84614952014-11-25 18:46:17 -0800240 if (mCurrentState != CalculatorState.INPUT) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700241 // Just reevaluate.
242 redisplayFormula();
Hans Boehm84614952014-11-25 18:46:17 -0800243 setState(CalculatorState.INIT);
Hans Boehm84614952014-11-25 18:46:17 -0800244 mEvaluator.requireResult();
245 } else {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700246 redisplayAfterFormulaChange();
Hans Boehm84614952014-11-25 18:46:17 -0800247 }
248 // TODO: We're currently not saving and restoring scroll position.
249 // We probably should. Details may require care to deal with:
250 // - new display size
251 // - slow recomputation if we've scrolled far.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700252 }
253
254 @Override
Justin Klaassenf79d6f62014-08-26 12:27:08 -0700255 protected void onSaveInstanceState(@NonNull Bundle outState) {
256 // If there's an animation in progress, cancel it first to ensure our state is up-to-date.
257 if (mCurrentAnimator != null) {
258 mCurrentAnimator.cancel();
259 }
260
Justin Klaassen4b3af052014-05-27 17:53:10 -0700261 super.onSaveInstanceState(outState);
Hans Boehm84614952014-11-25 18:46:17 -0800262 outState.putInt(KEY_DISPLAY_STATE, mCurrentState.ordinal());
Hans Boehm760a9dc2015-04-20 10:27:12 -0700263 outState.putCharSequence(KEY_UNPROCESSED_CHARS, mUnprocessedChars);
Hans Boehm84614952014-11-25 18:46:17 -0800264 ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
265 try (ObjectOutput out = new ObjectOutputStream(byteArrayStream)) {
266 mEvaluator.saveInstanceState(out);
267 } catch (IOException e) {
268 // Impossible; No IO involved.
269 throw new AssertionError("Impossible IO exception", e);
270 }
271 outState.putByteArray(KEY_EVAL_STATE, byteArrayStream.toByteArray());
Justin Klaassen4b3af052014-05-27 17:53:10 -0700272 }
273
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700274 // Set the state, updating delete label and display colors.
275 // This restores display positions on moving to INPUT.
Justin Klaassend48b7562015-04-16 16:51:38 -0700276 // But movement/animation for moving to RESULT has already been done.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700277 private void setState(CalculatorState state) {
278 if (mCurrentState != state) {
Hans Boehm84614952014-11-25 18:46:17 -0800279 if (state == CalculatorState.INPUT) {
280 restoreDisplayPositions();
281 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700282 mCurrentState = state;
283
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700284 if (mCurrentState == CalculatorState.RESULT) {
285 // No longer do this for ERROR; allow mistakes to be corrected.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700286 mDeleteButton.setVisibility(View.GONE);
287 mClearButton.setVisibility(View.VISIBLE);
288 } else {
289 mDeleteButton.setVisibility(View.VISIBLE);
290 mClearButton.setVisibility(View.GONE);
291 }
292
Hans Boehm84614952014-11-25 18:46:17 -0800293 if (mCurrentState == CalculatorState.ERROR) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700294 final int errorColor = getResources().getColor(R.color.calculator_error_color);
Hans Boehm08e8f322015-04-21 13:18:38 -0700295 mFormulaText.setTextColor(errorColor);
Hans Boehm84614952014-11-25 18:46:17 -0800296 mResult.setTextColor(errorColor);
Justin Klaassen8fff1442014-06-19 10:43:29 -0700297 getWindow().setStatusBarColor(errorColor);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700298 } else {
Hans Boehm08e8f322015-04-21 13:18:38 -0700299 mFormulaText.setTextColor(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700300 getResources().getColor(R.color.display_formula_text_color));
Hans Boehm84614952014-11-25 18:46:17 -0800301 mResult.setTextColor(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700302 getResources().getColor(R.color.display_result_text_color));
Justin Klaassen8fff1442014-06-19 10:43:29 -0700303 getWindow().setStatusBarColor(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700304 getResources().getColor(R.color.calculator_accent_color));
305 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700306
307 invalidateOptionsMenu();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700308 }
309 }
310
Hans Boehm1176f232015-05-11 16:26:03 -0700311 // Stop any active ActionMode. Return true if there was one.
312 private boolean stopActionMode() {
313 if (mResult.stopActionMode()) {
314 return true;
315 }
316 if (mFormulaText.stopActionMode()) {
317 return true;
318 }
319 return false;
320 }
321
Justin Klaassen4b3af052014-05-27 17:53:10 -0700322 @Override
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100323 public void onBackPressed() {
Hans Boehm1176f232015-05-11 16:26:03 -0700324 if (!stopActionMode()) {
325 if (mPadViewPager != null && mPadViewPager.getCurrentItem() != 0) {
326 // Select the previous pad.
327 mPadViewPager.setCurrentItem(mPadViewPager.getCurrentItem() - 1);
328 } else {
329 // If the user is currently looking at the first pad (or the pad is not paged),
330 // allow the system to handle the Back button.
331 super.onBackPressed();
332 }
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100333 }
334 }
335
336 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700337 public void onUserInteraction() {
338 super.onUserInteraction();
339
340 // If there's an animation in progress, cancel it so the user interaction can be handled
341 // immediately.
342 if (mCurrentAnimator != null) {
343 mCurrentAnimator.cancel();
344 }
345 }
346
Hans Boehmbfe8c222015-04-02 16:26:07 -0700347 // Update the top corner degree/radian display and mode button
348 // to reflect the indicated current degree mode (true = degrees)
349 // TODO: Hide the top corner display until the advanced panel is exposed.
350 private void updateDegreeMode(boolean dm) {
Hans Boehmbfe8c222015-04-02 16:26:07 -0700351 if (dm) {
Justin Klaassend48b7562015-04-16 16:51:38 -0700352 mModeView.setText(R.string.mode_deg);
353 mModeButton.setText(R.string.mode_rad);
354 mModeButton.setContentDescription(getString(R.string.desc_mode_rad));
Hans Boehmbfe8c222015-04-02 16:26:07 -0700355 } else {
Justin Klaassend48b7562015-04-16 16:51:38 -0700356 mModeView.setText(R.string.mode_rad);
357 mModeButton.setText(R.string.mode_deg);
358 mModeButton.setContentDescription(getString(R.string.desc_mode_deg));
Hans Boehmbfe8c222015-04-02 16:26:07 -0700359 }
360 }
Hans Boehm84614952014-11-25 18:46:17 -0800361
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700362 // Add the given button id to input expression.
363 // If appropriate, clear the expression before doing so.
364 private void addKeyToExpr(int id) {
365 if (mCurrentState == CalculatorState.ERROR) {
366 setState(CalculatorState.INPUT);
367 } else if (mCurrentState == CalculatorState.RESULT) {
368 if (KeyMaps.isBinary(id) || KeyMaps.isSuffix(id)) {
369 mEvaluator.collapse();
370 } else {
371 mEvaluator.clear();
372 }
373 setState(CalculatorState.INPUT);
374 }
375 if (!mEvaluator.append(id)) {
376 // TODO: Some user visible feedback?
377 }
378 }
379
380 private void redisplayAfterFormulaChange() {
381 // TODO: Could do this more incrementally.
382 redisplayFormula();
383 setState(CalculatorState.INPUT);
Hans Boehmc023b732015-04-29 11:30:47 -0700384 if (mEvaluator.getExpr().hasInterestingOps()) {
385 mEvaluator.evaluateAndShowResult();
386 } else {
387 mResult.clear();
388 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700389 }
390
Justin Klaassen4b3af052014-05-27 17:53:10 -0700391 public void onButtonClick(View view) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700392 mCurrentButton = view;
Hans Boehm1176f232015-05-11 16:26:03 -0700393 stopActionMode();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700394
Hans Boehm84614952014-11-25 18:46:17 -0800395 // Always cancel in-progress evaluation.
396 // If we were waiting for the result, do nothing else.
397 mEvaluator.cancelAll();
Justin Klaassend48b7562015-04-16 16:51:38 -0700398
Hans Boehm84614952014-11-25 18:46:17 -0800399 if (mCurrentState == CalculatorState.EVALUATE
400 || mCurrentState == CalculatorState.ANIMATE) {
401 onCancelled();
402 return;
403 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700404
Justin Klaassend48b7562015-04-16 16:51:38 -0700405 final int id = view.getId();
Hans Boehm84614952014-11-25 18:46:17 -0800406 switch (id) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700407 case R.id.eq:
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700408 onEquals();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700409 break;
410 case R.id.del:
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700411 onDelete();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700412 break;
413 case R.id.clr:
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700414 onClear();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700415 break;
Hans Boehmbfe8c222015-04-02 16:26:07 -0700416 case R.id.mode_deg_rad:
417 boolean mode = !mEvaluator.getDegreeMode();
418 updateDegreeMode(mode);
419 if (mCurrentState == CalculatorState.RESULT) {
420 mEvaluator.collapse(); // Capture result evaluated in old mode
421 redisplayFormula();
422 }
423 // In input mode, we reinterpret already entered trig functions.
424 mEvaluator.setDegreeMode(mode);
425 setState(CalculatorState.INPUT);
426 mResult.clear();
Hans Boehmc023b732015-04-29 11:30:47 -0700427 if (mEvaluator.getExpr().hasInterestingOps()) {
428 mEvaluator.evaluateAndShowResult();
429 }
Hans Boehmbfe8c222015-04-02 16:26:07 -0700430 break;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700431 default:
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700432 addKeyToExpr(id);
433 redisplayAfterFormulaChange();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700434 break;
435 }
436 }
437
Hans Boehm84614952014-11-25 18:46:17 -0800438 void redisplayFormula() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700439 String formula = mEvaluator.getExpr().toString(this);
440 if (mUnprocessedChars != null) {
441 // Add and highlight characters we couldn't process.
442 SpannableString formatted = new SpannableString(formula + mUnprocessedChars);
443 // TODO: should probably match this to the error color.
444 formatted.setSpan(new ForegroundColorSpan(Color.RED),
445 formula.length(), formatted.length(),
446 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Hans Boehm08e8f322015-04-21 13:18:38 -0700447 mFormulaText.setText(formatted);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700448 } else {
Hans Boehm08e8f322015-04-21 13:18:38 -0700449 mFormulaText.setText(formula);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700450 }
Hans Boehm84614952014-11-25 18:46:17 -0800451 }
452
Justin Klaassen4b3af052014-05-27 17:53:10 -0700453 @Override
454 public boolean onLongClick(View view) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700455 mCurrentButton = view;
456
Justin Klaassen4b3af052014-05-27 17:53:10 -0700457 if (view.getId() == R.id.del) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700458 onClear();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700459 return true;
460 }
461 return false;
462 }
463
Hans Boehm84614952014-11-25 18:46:17 -0800464 // Initial evaluation completed successfully. Initiate display.
Hans Boehm61568a12015-05-18 18:25:41 -0700465 public void onEvaluate(int initDisplayPrec, int leastDigPos, String truncatedWholeNumber) {
Justin Klaassend48b7562015-04-16 16:51:38 -0700466 // Invalidate any options that may depend on the current result.
467 invalidateOptionsMenu();
468
Hans Boehm61568a12015-05-18 18:25:41 -0700469 mResult.displayResult(initDisplayPrec, leastDigPos, truncatedWholeNumber);
470 if (mCurrentState != CalculatorState.INPUT) { // in EVALUATE or INIT state
Hans Boehm84614952014-11-25 18:46:17 -0800471 onResult(mCurrentState != CalculatorState.INIT);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700472 }
Hans Boehm84614952014-11-25 18:46:17 -0800473 }
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700474
Hans Boehm84614952014-11-25 18:46:17 -0800475 public void onCancelled() {
476 // We should be in EVALUATE state.
477 // Display is still in input state.
478 setState(CalculatorState.INPUT);
Hans Boehmc023b732015-04-29 11:30:47 -0700479 mResult.clear();
Hans Boehm84614952014-11-25 18:46:17 -0800480 }
481
482 // Reevaluation completed; ask result to redisplay current value.
483 public void onReevaluate()
484 {
485 mResult.redisplay();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700486 }
487
Justin Klaassenfed941a2014-06-09 18:42:40 +0100488 @Override
489 public void onTextSizeChanged(final TextView textView, float oldSize) {
490 if (mCurrentState != CalculatorState.INPUT) {
491 // Only animate text changes that occur from user input.
492 return;
493 }
494
495 // Calculate the values needed to perform the scale and translation animations,
496 // maintaining the same apparent baseline for the displayed text.
497 final float textScale = oldSize / textView.getTextSize();
498 final float translationX = (1.0f - textScale) *
499 (textView.getWidth() / 2.0f - textView.getPaddingEnd());
500 final float translationY = (1.0f - textScale) *
501 (textView.getHeight() / 2.0f - textView.getPaddingBottom());
502
503 final AnimatorSet animatorSet = new AnimatorSet();
504 animatorSet.playTogether(
505 ObjectAnimator.ofFloat(textView, View.SCALE_X, textScale, 1.0f),
506 ObjectAnimator.ofFloat(textView, View.SCALE_Y, textScale, 1.0f),
507 ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, translationX, 0.0f),
508 ObjectAnimator.ofFloat(textView, View.TRANSLATION_Y, translationY, 0.0f));
Justin Klaassen94db7202014-06-11 11:22:31 -0700509 animatorSet.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
Justin Klaassenfed941a2014-06-09 18:42:40 +0100510 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
511 animatorSet.start();
512 }
513
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700514 private void onEquals() {
Hans Boehmc023b732015-04-29 11:30:47 -0700515 if (mCurrentState == CalculatorState.INPUT && !mEvaluator.getExpr().isEmpty()) {
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700516 setState(CalculatorState.EVALUATE);
Hans Boehm84614952014-11-25 18:46:17 -0800517 mEvaluator.requireResult();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700518 }
519 }
520
521 private void onDelete() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700522 // Delete works like backspace; remove the last character or operator from the expression.
523 // Note that we handle keyboard delete exactly like the delete button. For
524 // example the delete button can be used to delete a character from an incomplete
525 // function name typed on a physical keyboard.
Hans Boehm84614952014-11-25 18:46:17 -0800526 mEvaluator.cancelAll();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700527 // This should be impossible in RESULT state.
528 setState(CalculatorState.INPUT);
529 if (mUnprocessedChars != null) {
530 int len = mUnprocessedChars.length();
531 if (len > 0) {
532 mUnprocessedChars = mUnprocessedChars.substring(0, len-1);
533 } else {
Hans Boehmc023b732015-04-29 11:30:47 -0700534 mEvaluator.delete();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700535 }
536 } else {
Hans Boehmc023b732015-04-29 11:30:47 -0700537 mEvaluator.delete();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700538 }
539 redisplayAfterFormulaChange();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700540 }
541
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700542 private void reveal(View sourceView, int colorRes, AnimatorListener listener) {
Justin Klaassen06360f92014-08-28 11:08:44 -0700543 final ViewGroupOverlay groupOverlay =
544 (ViewGroupOverlay) getWindow().getDecorView().getOverlay();
Justin Klaassen8fff1442014-06-19 10:43:29 -0700545
546 final Rect displayRect = new Rect();
Justin Klaassen06360f92014-08-28 11:08:44 -0700547 mDisplayView.getGlobalVisibleRect(displayRect);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700548
549 // Make reveal cover the display and status bar.
550 final View revealView = new View(this);
Justin Klaassen8fff1442014-06-19 10:43:29 -0700551 revealView.setBottom(displayRect.bottom);
552 revealView.setLeft(displayRect.left);
553 revealView.setRight(displayRect.right);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700554 revealView.setBackgroundColor(getResources().getColor(colorRes));
Justin Klaassen06360f92014-08-28 11:08:44 -0700555 groupOverlay.add(revealView);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700556
Justin Klaassen4b3af052014-05-27 17:53:10 -0700557 final int[] clearLocation = new int[2];
558 sourceView.getLocationInWindow(clearLocation);
559 clearLocation[0] += sourceView.getWidth() / 2;
560 clearLocation[1] += sourceView.getHeight() / 2;
561
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700562 final int revealCenterX = clearLocation[0] - revealView.getLeft();
563 final int revealCenterY = clearLocation[1] - revealView.getTop();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700564
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700565 final double x1_2 = Math.pow(revealView.getLeft() - revealCenterX, 2);
566 final double x2_2 = Math.pow(revealView.getRight() - revealCenterX, 2);
567 final double y_2 = Math.pow(revealView.getTop() - revealCenterY, 2);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700568 final float revealRadius = (float) Math.max(Math.sqrt(x1_2 + y_2), Math.sqrt(x2_2 + y_2));
569
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700570 final Animator revealAnimator =
571 ViewAnimationUtils.createCircularReveal(revealView,
ztenghui3d6ecaf2014-06-05 09:56:00 -0700572 revealCenterX, revealCenterY, 0.0f, revealRadius);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700573 revealAnimator.setDuration(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700574 getResources().getInteger(android.R.integer.config_longAnimTime));
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700575 revealAnimator.addListener(listener);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700576
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700577 final Animator alphaAnimator = ObjectAnimator.ofFloat(revealView, View.ALPHA, 0.0f);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700578 alphaAnimator.setDuration(
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700579 getResources().getInteger(android.R.integer.config_mediumAnimTime));
Justin Klaassen4b3af052014-05-27 17:53:10 -0700580
581 final AnimatorSet animatorSet = new AnimatorSet();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700582 animatorSet.play(revealAnimator).before(alphaAnimator);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700583 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
584 animatorSet.addListener(new AnimatorListenerAdapter() {
585 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700586 public void onAnimationEnd(Animator animator) {
Justin Klaassen8fff1442014-06-19 10:43:29 -0700587 groupOverlay.remove(revealView);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700588 mCurrentAnimator = null;
589 }
590 });
591
592 mCurrentAnimator = animatorSet;
593 animatorSet.start();
594 }
595
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700596 private void onClear() {
Hans Boehm84614952014-11-25 18:46:17 -0800597 if (mEvaluator.getExpr().isEmpty()) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700598 return;
599 }
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700600 reveal(mCurrentButton, R.color.calculator_accent_color, new AnimatorListenerAdapter() {
601 @Override
602 public void onAnimationEnd(Animator animation) {
Hans Boehm760a9dc2015-04-20 10:27:12 -0700603 mUnprocessedChars = null;
604 mResult.clear();
605 mEvaluator.clear();
606 setState(CalculatorState.INPUT);
Hans Boehm84614952014-11-25 18:46:17 -0800607 redisplayFormula();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700608 }
609 });
610 }
611
Hans Boehm84614952014-11-25 18:46:17 -0800612 // Evaluation encountered en error. Display the error.
613 void onError(final int errorResourceId) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700614 if (mCurrentState == CalculatorState.EVALUATE) {
615 setState(CalculatorState.ANIMATE);
616 reveal(mCurrentButton, R.color.calculator_error_color,
617 new AnimatorListenerAdapter() {
618 @Override
619 public void onAnimationEnd(Animator animation) {
620 setState(CalculatorState.ERROR);
621 mResult.displayError(errorResourceId);
622 }
623 });
624 } else if (mCurrentState == CalculatorState.INIT) {
625 setState(CalculatorState.ERROR);
626 mResult.displayError(errorResourceId);
Hans Boehmc023b732015-04-29 11:30:47 -0700627 } else {
628 mResult.clear();
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700629 }
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700630 }
631
Hans Boehm84614952014-11-25 18:46:17 -0800632
633 // Animate movement of result into the top formula slot.
634 // Result window now remains translated in the top slot while the result is displayed.
635 // (We convert it back to formula use only when the user provides new input.)
636 // Historical note: In the Lollipop version, this invisibly and instantaeously moved
637 // formula and result displays back at the end of the animation. We no longer do that,
638 // so that we can continue to properly support scrolling of the result.
639 // We assume the result already contains the text to be expanded.
640 private void onResult(boolean animate) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700641 // Calculate the values needed to perform the scale and translation animations.
Hans Boehm61568a12015-05-18 18:25:41 -0700642 // The nominal font size in the result display is fixed. But the magnification we
643 // use when the user hits "=" is variable, with a scrollable result always getting
644 // minimum magnification.
645 // Display.xml is designed to ensure that a 5/4 increase is always possible.
646 // More is possible if the display is not fully occupied.
647 // Pivot the result around the bottom of the text.
648 final float resultScale = (float)Math.min(1.25f / mResult.getOccupancy(), 2.0);
649 // Keep the right end of text fixed as we scale.
650 mResult.setPivotX(mResult.getWidth() - mResult.getPaddingRight());
651 // Move result up to take place of formula. Scale around top of formula.
652 mResult.setPivotY(mResult.getPaddingTop());
653 float resultTranslationY = -mFormulaText.getHeight();
654 // Move formula off screen.
Hans Boehm08e8f322015-04-21 13:18:38 -0700655 final float formulaTranslationY = -mFormulaText.getBottom();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700656
Hans Boehm84614952014-11-25 18:46:17 -0800657 // TODO: Reintroduce textColorAnimator?
658 // The initial and final colors seemed to be the same in L.
659 // With the new model, the result logically changes back to a formula
660 // only when we switch back to INPUT state, so it's unclear that animating
661 // a color change here makes sense.
662 if (animate) {
663 final AnimatorSet animatorSet = new AnimatorSet();
664 animatorSet.playTogether(
665 ObjectAnimator.ofFloat(mResult, View.SCALE_X, resultScale),
666 ObjectAnimator.ofFloat(mResult, View.SCALE_Y, resultScale),
Hans Boehm84614952014-11-25 18:46:17 -0800667 ObjectAnimator.ofFloat(mResult, View.TRANSLATION_Y, resultTranslationY),
Hans Boehm08e8f322015-04-21 13:18:38 -0700668 ObjectAnimator.ofFloat(mFormulaText, View.TRANSLATION_Y,
Hans Boehm84614952014-11-25 18:46:17 -0800669 formulaTranslationY));
670 animatorSet.setDuration(
671 getResources().getInteger(android.R.integer.config_longAnimTime));
672 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
673 animatorSet.addListener(new AnimatorListenerAdapter() {
674 @Override
675 public void onAnimationStart(Animator animation) {
676 // Result should already be displayed; no need to do anything.
677 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700678
Hans Boehm84614952014-11-25 18:46:17 -0800679 @Override
680 public void onAnimationEnd(Animator animation) {
681 setState(CalculatorState.RESULT);
682 mCurrentAnimator = null;
683 }
684 });
Justin Klaassen4b3af052014-05-27 17:53:10 -0700685
Hans Boehm84614952014-11-25 18:46:17 -0800686 mCurrentAnimator = animatorSet;
687 animatorSet.start();
688 } else /* No animation desired; get there fast, e.g. when restarting */ {
689 mResult.setScaleX(resultScale);
690 mResult.setScaleY(resultScale);
Hans Boehm84614952014-11-25 18:46:17 -0800691 mResult.setTranslationY(resultTranslationY);
Hans Boehm08e8f322015-04-21 13:18:38 -0700692 mFormulaText.setTranslationY(formulaTranslationY);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700693 setState(CalculatorState.RESULT);
Hans Boehm84614952014-11-25 18:46:17 -0800694 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700695 }
Hans Boehm84614952014-11-25 18:46:17 -0800696
697 // Restore positions of the formula and result displays back to their original,
698 // pre-animation state.
699 private void restoreDisplayPositions() {
700 // Clear result.
701 mResult.setText("");
702 // Reset all of the values modified during the animation.
703 mResult.setScaleX(1.0f);
704 mResult.setScaleY(1.0f);
705 mResult.setTranslationX(0.0f);
706 mResult.setTranslationY(0.0f);
Hans Boehm08e8f322015-04-21 13:18:38 -0700707 mFormulaText.setTranslationY(0.0f);
Hans Boehm84614952014-11-25 18:46:17 -0800708
Hans Boehm08e8f322015-04-21 13:18:38 -0700709 mFormulaText.requestFocus();
Hans Boehm84614952014-11-25 18:46:17 -0800710 }
711
Justin Klaassend48b7562015-04-16 16:51:38 -0700712 @Override
713 public boolean onCreateOptionsMenu(Menu menu) {
Justin Klaassend36d63e2015-05-05 12:59:36 -0700714 super.onCreateOptionsMenu(menu);
715
716 getMenuInflater().inflate(R.menu.activity_calculator, menu);
Justin Klaassend48b7562015-04-16 16:51:38 -0700717 return true;
718 }
719
720 @Override
721 public boolean onPrepareOptionsMenu(Menu menu) {
Justin Klaassend36d63e2015-05-05 12:59:36 -0700722 super.onPrepareOptionsMenu(menu);
723
724 // Show the leading option when displaying a result.
725 menu.findItem(R.id.menu_leading).setVisible(mCurrentState == CalculatorState.RESULT);
726
727 // Show the fraction option when displaying a rational result.
728 menu.findItem(R.id.menu_fraction).setVisible(mCurrentState == CalculatorState.RESULT
729 && mEvaluator.getRational() != null);
730
Justin Klaassend48b7562015-04-16 16:51:38 -0700731 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800732 }
733
734 @Override
Justin Klaassend48b7562015-04-16 16:51:38 -0700735 public boolean onOptionsItemSelected(MenuItem item) {
Hans Boehm84614952014-11-25 18:46:17 -0800736 switch (item.getItemId()) {
Justin Klaassend36d63e2015-05-05 12:59:36 -0700737 case R.id.menu_leading:
738 displayFull();
Hans Boehm84614952014-11-25 18:46:17 -0800739 return true;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700740 case R.id.menu_fraction:
741 displayFraction();
742 return true;
Justin Klaassend36d63e2015-05-05 12:59:36 -0700743 case R.id.menu_licenses:
744 startActivity(new Intent(this, Licenses.class));
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700745 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800746 default:
747 return super.onOptionsItemSelected(item);
748 }
749 }
750
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700751 private void displayMessage(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800752 AlertDialog.Builder builder = new AlertDialog.Builder(this);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700753 builder.setMessage(s)
754 .setNegativeButton(R.string.dismiss,
Hans Boehm84614952014-11-25 18:46:17 -0800755 new DialogInterface.OnClickListener() {
756 public void onClick(DialogInterface d, int which) { }
757 })
758 .show();
759 }
760
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700761 private void displayFraction() {
762 BoundedRational result = mEvaluator.getRational();
Hans Boehm013969e2015-04-13 20:29:47 -0700763 displayMessage(KeyMaps.translateResult(result.toNiceString()));
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700764 }
765
766 // Display full result to currently evaluated precision
767 private void displayFull() {
768 Resources res = getResources();
769 String msg = mResult.getFullText() + " ";
770 if (mResult.fullTextIsExact()) {
771 msg += res.getString(R.string.exact);
772 } else {
773 msg += res.getString(R.string.approximate);
774 }
775 displayMessage(msg);
776 }
777
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700778 // Add input characters to the end of the expression by mapping them to
779 // the appropriate button pushes when possible. Leftover characters
780 // are added to mUnprocessedChars, which is presumed to immediately
781 // precede the newly added characters.
782 private void addChars(String moreChars) {
783 if (mUnprocessedChars != null) {
784 moreChars = mUnprocessedChars + moreChars;
785 }
786 int current = 0;
787 int len = moreChars.length();
788 while (current < len) {
789 char c = moreChars.charAt(current);
Hans Boehm013969e2015-04-13 20:29:47 -0700790 int k = KeyMaps.keyForChar(c);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700791 if (k != View.NO_ID) {
792 mCurrentButton = findViewById(k);
793 addKeyToExpr(k);
794 if (Character.isSurrogate(c)) {
795 current += 2;
796 } else {
797 ++current;
798 }
799 continue;
800 }
Hans Boehm013969e2015-04-13 20:29:47 -0700801 int f = KeyMaps.funForString(moreChars, current);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700802 if (f != View.NO_ID) {
803 mCurrentButton = findViewById(f);
804 addKeyToExpr(f);
805 if (f == R.id.op_sqrt) {
806 // Square root entered as function; don't lose the parenthesis.
807 addKeyToExpr(R.id.lparen);
808 }
809 current = moreChars.indexOf('(', current) + 1;
810 continue;
811 }
812 // There are characters left, but we can't convert them to button presses.
813 mUnprocessedChars = moreChars.substring(current);
814 redisplayAfterFormulaChange();
815 return;
816 }
817 mUnprocessedChars = null;
818 redisplayAfterFormulaChange();
819 return;
Hans Boehm84614952014-11-25 18:46:17 -0800820 }
821
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700822 @Override
823 public boolean paste(Uri uri) {
824 if (mEvaluator.isLastSaved(uri)) {
825 if (mCurrentState == CalculatorState.ERROR
826 || mCurrentState == CalculatorState.RESULT) {
827 setState(CalculatorState.INPUT);
828 mEvaluator.clear();
Hans Boehm84614952014-11-25 18:46:17 -0800829 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700830 mEvaluator.addSaved();
831 redisplayAfterFormulaChange();
832 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800833 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700834 return false;
Hans Boehm84614952014-11-25 18:46:17 -0800835 }
836
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700837 @Override
838 public void paste(String s) {
839 addChars(s);
Hans Boehm84614952014-11-25 18:46:17 -0800840 }
841
Justin Klaassen4b3af052014-05-27 17:53:10 -0700842}