blob: 2b06575a47bea1cb20285c07c17e0fd3f6ec2d4f [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
Hans Boehm08e8f322015-04-21 13:18:38 -070079import com.android.calculator2.CalculatorText.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
Hans Boehm08e8f322015-04-21 13:18:38 -070091 implements OnTextSizeChangeListener, OnLongClickListener, CalculatorText.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;
Hans Boehm08e8f322015-04-21 13:18:38 -0700181 private CalculatorText mFormulaText;
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);
Hans Boehm08e8f322015-04-21 13:18:38 -0700208 mFormulaText = (CalculatorText) 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 }
Hans Boehmfbcef702015-04-27 18:07:47 -0700242 } else {
243 mCurrentState = CalculatorState.INPUT;
244 mEvaluator.clear();
Hans Boehm84614952014-11-25 18:46:17 -0800245 }
Hans Boehm08e8f322015-04-21 13:18:38 -0700246 mFormulaText.setOnKeyListener(mFormulaOnKeyListener);
247 mFormulaText.setOnTextSizeChangeListener(this);
248 mFormulaText.setPasteListener(this);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700249 mDeleteButton.setOnLongClickListener(this);
Hans Boehmbfe8c222015-04-02 16:26:07 -0700250 updateDegreeMode(mEvaluator.getDegreeMode());
Hans Boehm84614952014-11-25 18:46:17 -0800251 if (mCurrentState != CalculatorState.INPUT) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700252 // Just reevaluate.
253 redisplayFormula();
Hans Boehm84614952014-11-25 18:46:17 -0800254 setState(CalculatorState.INIT);
Hans Boehm84614952014-11-25 18:46:17 -0800255 mEvaluator.requireResult();
256 } else {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700257 redisplayAfterFormulaChange();
Hans Boehm84614952014-11-25 18:46:17 -0800258 }
259 // TODO: We're currently not saving and restoring scroll position.
260 // We probably should. Details may require care to deal with:
261 // - new display size
262 // - slow recomputation if we've scrolled far.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700263 }
264
265 @Override
Justin Klaassenf79d6f62014-08-26 12:27:08 -0700266 protected void onSaveInstanceState(@NonNull Bundle outState) {
267 // If there's an animation in progress, cancel it first to ensure our state is up-to-date.
268 if (mCurrentAnimator != null) {
269 mCurrentAnimator.cancel();
270 }
271
Justin Klaassen4b3af052014-05-27 17:53:10 -0700272 super.onSaveInstanceState(outState);
Hans Boehm84614952014-11-25 18:46:17 -0800273 outState.putInt(KEY_DISPLAY_STATE, mCurrentState.ordinal());
Hans Boehm760a9dc2015-04-20 10:27:12 -0700274 outState.putCharSequence(KEY_UNPROCESSED_CHARS, mUnprocessedChars);
Hans Boehm84614952014-11-25 18:46:17 -0800275 ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
276 try (ObjectOutput out = new ObjectOutputStream(byteArrayStream)) {
277 mEvaluator.saveInstanceState(out);
278 } catch (IOException e) {
279 // Impossible; No IO involved.
280 throw new AssertionError("Impossible IO exception", e);
281 }
282 outState.putByteArray(KEY_EVAL_STATE, byteArrayStream.toByteArray());
Justin Klaassen4b3af052014-05-27 17:53:10 -0700283 }
284
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700285 // Set the state, updating delete label and display colors.
286 // This restores display positions on moving to INPUT.
Justin Klaassend48b7562015-04-16 16:51:38 -0700287 // But movement/animation for moving to RESULT has already been done.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700288 private void setState(CalculatorState state) {
289 if (mCurrentState != state) {
Hans Boehm84614952014-11-25 18:46:17 -0800290 if (state == CalculatorState.INPUT) {
291 restoreDisplayPositions();
292 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700293 mCurrentState = state;
294
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700295 if (mCurrentState == CalculatorState.RESULT) {
296 // No longer do this for ERROR; allow mistakes to be corrected.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700297 mDeleteButton.setVisibility(View.GONE);
298 mClearButton.setVisibility(View.VISIBLE);
299 } else {
300 mDeleteButton.setVisibility(View.VISIBLE);
301 mClearButton.setVisibility(View.GONE);
302 }
303
Hans Boehm84614952014-11-25 18:46:17 -0800304 if (mCurrentState == CalculatorState.ERROR) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700305 final int errorColor = getResources().getColor(R.color.calculator_error_color);
Hans Boehm08e8f322015-04-21 13:18:38 -0700306 mFormulaText.setTextColor(errorColor);
Hans Boehm84614952014-11-25 18:46:17 -0800307 mResult.setTextColor(errorColor);
Justin Klaassen8fff1442014-06-19 10:43:29 -0700308 getWindow().setStatusBarColor(errorColor);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700309 } else {
Hans Boehm08e8f322015-04-21 13:18:38 -0700310 mFormulaText.setTextColor(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700311 getResources().getColor(R.color.display_formula_text_color));
Hans Boehm84614952014-11-25 18:46:17 -0800312 mResult.setTextColor(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700313 getResources().getColor(R.color.display_result_text_color));
Justin Klaassen8fff1442014-06-19 10:43:29 -0700314 getWindow().setStatusBarColor(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700315 getResources().getColor(R.color.calculator_accent_color));
316 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700317
318 invalidateOptionsMenu();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700319 }
320 }
321
322 @Override
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100323 public void onBackPressed() {
324 if (mPadViewPager == null || mPadViewPager.getCurrentItem() == 0) {
325 // If the user is currently looking at the first pad (or the pad is not paged),
326 // allow the system to handle the Back button.
327 super.onBackPressed();
328 } else {
329 // Otherwise, select the previous pad.
330 mPadViewPager.setCurrentItem(mPadViewPager.getCurrentItem() - 1);
331 }
332 }
333
334 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700335 public void onUserInteraction() {
336 super.onUserInteraction();
337
338 // If there's an animation in progress, cancel it so the user interaction can be handled
339 // immediately.
340 if (mCurrentAnimator != null) {
341 mCurrentAnimator.cancel();
342 }
343 }
344
Hans Boehmbfe8c222015-04-02 16:26:07 -0700345 // Update the top corner degree/radian display and mode button
346 // to reflect the indicated current degree mode (true = degrees)
347 // TODO: Hide the top corner display until the advanced panel is exposed.
348 private void updateDegreeMode(boolean dm) {
Hans Boehmbfe8c222015-04-02 16:26:07 -0700349 if (dm) {
Justin Klaassend48b7562015-04-16 16:51:38 -0700350 mModeView.setText(R.string.mode_deg);
351 mModeButton.setText(R.string.mode_rad);
352 mModeButton.setContentDescription(getString(R.string.desc_mode_rad));
Hans Boehmbfe8c222015-04-02 16:26:07 -0700353 } else {
Justin Klaassend48b7562015-04-16 16:51:38 -0700354 mModeView.setText(R.string.mode_rad);
355 mModeButton.setText(R.string.mode_deg);
356 mModeButton.setContentDescription(getString(R.string.desc_mode_deg));
Hans Boehmbfe8c222015-04-02 16:26:07 -0700357 }
358 }
Hans Boehm84614952014-11-25 18:46:17 -0800359
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700360 // Add the given button id to input expression.
361 // If appropriate, clear the expression before doing so.
362 private void addKeyToExpr(int id) {
363 if (mCurrentState == CalculatorState.ERROR) {
364 setState(CalculatorState.INPUT);
365 } else if (mCurrentState == CalculatorState.RESULT) {
366 if (KeyMaps.isBinary(id) || KeyMaps.isSuffix(id)) {
367 mEvaluator.collapse();
368 } else {
369 mEvaluator.clear();
370 }
371 setState(CalculatorState.INPUT);
372 }
373 if (!mEvaluator.append(id)) {
374 // TODO: Some user visible feedback?
375 }
376 }
377
378 private void redisplayAfterFormulaChange() {
379 // TODO: Could do this more incrementally.
380 redisplayFormula();
381 setState(CalculatorState.INPUT);
Hans Boehmc023b732015-04-29 11:30:47 -0700382 if (mEvaluator.getExpr().hasInterestingOps()) {
383 mEvaluator.evaluateAndShowResult();
384 } else {
385 mResult.clear();
386 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700387 }
388
Justin Klaassen4b3af052014-05-27 17:53:10 -0700389 public void onButtonClick(View view) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700390 mCurrentButton = view;
391
Hans Boehm84614952014-11-25 18:46:17 -0800392 // Always cancel in-progress evaluation.
393 // If we were waiting for the result, do nothing else.
394 mEvaluator.cancelAll();
Justin Klaassend48b7562015-04-16 16:51:38 -0700395
Hans Boehm84614952014-11-25 18:46:17 -0800396 if (mCurrentState == CalculatorState.EVALUATE
397 || mCurrentState == CalculatorState.ANIMATE) {
398 onCancelled();
399 return;
400 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700401
Justin Klaassend48b7562015-04-16 16:51:38 -0700402 final int id = view.getId();
Hans Boehm84614952014-11-25 18:46:17 -0800403 switch (id) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700404 case R.id.eq:
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700405 onEquals();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700406 break;
407 case R.id.del:
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700408 onDelete();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700409 break;
410 case R.id.clr:
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700411 onClear();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700412 break;
Hans Boehmbfe8c222015-04-02 16:26:07 -0700413 case R.id.mode_deg_rad:
414 boolean mode = !mEvaluator.getDegreeMode();
415 updateDegreeMode(mode);
416 if (mCurrentState == CalculatorState.RESULT) {
417 mEvaluator.collapse(); // Capture result evaluated in old mode
418 redisplayFormula();
419 }
420 // In input mode, we reinterpret already entered trig functions.
421 mEvaluator.setDegreeMode(mode);
422 setState(CalculatorState.INPUT);
423 mResult.clear();
Hans Boehmc023b732015-04-29 11:30:47 -0700424 if (mEvaluator.getExpr().hasInterestingOps()) {
425 mEvaluator.evaluateAndShowResult();
426 }
Hans Boehmbfe8c222015-04-02 16:26:07 -0700427 break;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700428 default:
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700429 addKeyToExpr(id);
430 redisplayAfterFormulaChange();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700431 break;
432 }
433 }
434
Hans Boehm84614952014-11-25 18:46:17 -0800435 void redisplayFormula() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700436 String formula = mEvaluator.getExpr().toString(this);
437 if (mUnprocessedChars != null) {
438 // Add and highlight characters we couldn't process.
439 SpannableString formatted = new SpannableString(formula + mUnprocessedChars);
440 // TODO: should probably match this to the error color.
441 formatted.setSpan(new ForegroundColorSpan(Color.RED),
442 formula.length(), formatted.length(),
443 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Hans Boehm08e8f322015-04-21 13:18:38 -0700444 mFormulaText.setText(formatted);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700445 } else {
Hans Boehm08e8f322015-04-21 13:18:38 -0700446 mFormulaText.setText(formula);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700447 }
Hans Boehm84614952014-11-25 18:46:17 -0800448 }
449
Justin Klaassen4b3af052014-05-27 17:53:10 -0700450 @Override
451 public boolean onLongClick(View view) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700452 mCurrentButton = view;
453
Justin Klaassen4b3af052014-05-27 17:53:10 -0700454 if (view.getId() == R.id.del) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700455 onClear();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700456 return true;
457 }
458 return false;
459 }
460
Hans Boehm84614952014-11-25 18:46:17 -0800461 // Initial evaluation completed successfully. Initiate display.
462 public void onEvaluate(int initDisplayPrec, String truncatedWholeNumber) {
Justin Klaassend48b7562015-04-16 16:51:38 -0700463 // Invalidate any options that may depend on the current result.
464 invalidateOptionsMenu();
465
Justin Klaassen4b3af052014-05-27 17:53:10 -0700466 if (mCurrentState == CalculatorState.INPUT) {
Hans Boehm84614952014-11-25 18:46:17 -0800467 // Just update small result display.
468 mResult.displayResult(initDisplayPrec, truncatedWholeNumber);
469 } else { // in EVALUATE or INIT state
470 mResult.displayResult(initDisplayPrec, truncatedWholeNumber);
471 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.
642 // We now fix the character size in the display to avoid weird effects
Hans Boehm84614952014-11-25 18:46:17 -0800643 // when we scroll.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700644 // Display.xml is designed to ensure exactly a 3/2 ratio between the formula
645 // slot and small result slot.
646 final float resultScale = 1.5f;
647 final float resultTranslationX = -mResult.getWidth() * (resultScale - 1)/2;
Hans Boehm08e8f322015-04-21 13:18:38 -0700648 // mFormulaText is aligned with mResult on the right.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700649 // When we enlarge it around its center, the right side
650 // moves to the right. This compensates.
651 float resultTranslationY = -mResult.getHeight();
652 // This is how much we want to move the bottom.
653 // Now compensate for the fact that we're
654 // simultaenously expanding it around its center by half its height
Hans Boehm760a9dc2015-04-20 10:27:12 -0700655 resultTranslationY += mResult.getHeight() * (resultScale - 1)/2;
Hans Boehm08e8f322015-04-21 13:18:38 -0700656 final float formulaTranslationY = -mFormulaText.getBottom();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700657
Hans Boehm84614952014-11-25 18:46:17 -0800658 // TODO: Reintroduce textColorAnimator?
659 // The initial and final colors seemed to be the same in L.
660 // With the new model, the result logically changes back to a formula
661 // only when we switch back to INPUT state, so it's unclear that animating
662 // a color change here makes sense.
663 if (animate) {
664 final AnimatorSet animatorSet = new AnimatorSet();
665 animatorSet.playTogether(
666 ObjectAnimator.ofFloat(mResult, View.SCALE_X, resultScale),
667 ObjectAnimator.ofFloat(mResult, View.SCALE_Y, resultScale),
668 ObjectAnimator.ofFloat(mResult, View.TRANSLATION_X, resultTranslationX),
669 ObjectAnimator.ofFloat(mResult, View.TRANSLATION_Y, resultTranslationY),
Hans Boehm08e8f322015-04-21 13:18:38 -0700670 ObjectAnimator.ofFloat(mFormulaText, View.TRANSLATION_Y,
Hans Boehm84614952014-11-25 18:46:17 -0800671 formulaTranslationY));
672 animatorSet.setDuration(
673 getResources().getInteger(android.R.integer.config_longAnimTime));
674 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
675 animatorSet.addListener(new AnimatorListenerAdapter() {
676 @Override
677 public void onAnimationStart(Animator animation) {
678 // Result should already be displayed; no need to do anything.
679 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700680
Hans Boehm84614952014-11-25 18:46:17 -0800681 @Override
682 public void onAnimationEnd(Animator animation) {
683 setState(CalculatorState.RESULT);
684 mCurrentAnimator = null;
685 }
686 });
Justin Klaassen4b3af052014-05-27 17:53:10 -0700687
Hans Boehm84614952014-11-25 18:46:17 -0800688 mCurrentAnimator = animatorSet;
689 animatorSet.start();
690 } else /* No animation desired; get there fast, e.g. when restarting */ {
691 mResult.setScaleX(resultScale);
692 mResult.setScaleY(resultScale);
693 mResult.setTranslationX(resultTranslationX);
694 mResult.setTranslationY(resultTranslationY);
Hans Boehm08e8f322015-04-21 13:18:38 -0700695 mFormulaText.setTranslationY(formulaTranslationY);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700696 setState(CalculatorState.RESULT);
Hans Boehm84614952014-11-25 18:46:17 -0800697 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700698 }
Hans Boehm84614952014-11-25 18:46:17 -0800699
700 // Restore positions of the formula and result displays back to their original,
701 // pre-animation state.
702 private void restoreDisplayPositions() {
703 // Clear result.
704 mResult.setText("");
705 // Reset all of the values modified during the animation.
706 mResult.setScaleX(1.0f);
707 mResult.setScaleY(1.0f);
708 mResult.setTranslationX(0.0f);
709 mResult.setTranslationY(0.0f);
Hans Boehm08e8f322015-04-21 13:18:38 -0700710 mFormulaText.setTranslationY(0.0f);
Hans Boehm84614952014-11-25 18:46:17 -0800711
Hans Boehm08e8f322015-04-21 13:18:38 -0700712 mFormulaText.requestFocus();
Hans Boehm84614952014-11-25 18:46:17 -0800713 }
714
Justin Klaassend48b7562015-04-16 16:51:38 -0700715 @Override
716 public boolean onCreateOptionsMenu(Menu menu) {
717 getMenuInflater().inflate(R.menu.overflow, menu);
718 return true;
719 }
720
721 @Override
722 public boolean onPrepareOptionsMenu(Menu menu) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700723 if (mCurrentState != CalculatorState.RESULT) {
724 menu.findItem(R.id.menu_fraction).setEnabled(false);
725 menu.findItem(R.id.menu_leading).setEnabled(false);
726 } else if (mEvaluator.getRational() == null) {
727 menu.findItem(R.id.menu_fraction).setEnabled(false);
728 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700729 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800730 }
731
732 @Override
Justin Klaassend48b7562015-04-16 16:51:38 -0700733 public boolean onOptionsItemSelected(MenuItem item) {
Hans Boehm84614952014-11-25 18:46:17 -0800734 switch (item.getItemId()) {
735 case R.id.menu_help:
736 displayHelpMessage();
737 return true;
738 case R.id.menu_about:
739 displayAboutPage();
740 return true;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700741 case R.id.menu_fraction:
742 displayFraction();
743 return true;
744 case R.id.menu_leading:
745 displayFull();
746 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800747 default:
748 return super.onOptionsItemSelected(item);
749 }
750 }
751
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700752 private void displayMessage(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800753 AlertDialog.Builder builder = new AlertDialog.Builder(this);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700754 builder.setMessage(s)
755 .setNegativeButton(R.string.dismiss,
Hans Boehm84614952014-11-25 18:46:17 -0800756 new DialogInterface.OnClickListener() {
757 public void onClick(DialogInterface d, int which) { }
758 })
759 .show();
760 }
761
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700762 private void displayHelpMessage() {
763 Resources res = getResources();
764 String msg = res.getString(R.string.help_message);
765 if (mPadViewPager != null) {
766 msg += res.getString(R.string.help_pager);
767 }
768 displayMessage(msg);
769 }
770
771 private void displayFraction() {
772 BoundedRational result = mEvaluator.getRational();
Hans Boehm013969e2015-04-13 20:29:47 -0700773 displayMessage(KeyMaps.translateResult(result.toNiceString()));
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700774 }
775
776 // Display full result to currently evaluated precision
777 private void displayFull() {
778 Resources res = getResources();
779 String msg = mResult.getFullText() + " ";
780 if (mResult.fullTextIsExact()) {
781 msg += res.getString(R.string.exact);
782 } else {
783 msg += res.getString(R.string.approximate);
784 }
785 displayMessage(msg);
786 }
787
Hans Boehm84614952014-11-25 18:46:17 -0800788 private void displayAboutPage() {
789 WebView wv = new WebView(this);
790 wv.loadUrl("file:///android_asset/about.txt");
791 new AlertDialog.Builder(this)
792 .setView(wv)
793 .setNegativeButton(R.string.dismiss,
794 new DialogInterface.OnClickListener() {
795 public void onClick(DialogInterface d, int which) { }
796 })
797 .show();
798 }
799
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700800 // Add input characters to the end of the expression by mapping them to
801 // the appropriate button pushes when possible. Leftover characters
802 // are added to mUnprocessedChars, which is presumed to immediately
803 // precede the newly added characters.
804 private void addChars(String moreChars) {
805 if (mUnprocessedChars != null) {
806 moreChars = mUnprocessedChars + moreChars;
807 }
808 int current = 0;
809 int len = moreChars.length();
810 while (current < len) {
811 char c = moreChars.charAt(current);
Hans Boehm013969e2015-04-13 20:29:47 -0700812 int k = KeyMaps.keyForChar(c);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700813 if (k != View.NO_ID) {
814 mCurrentButton = findViewById(k);
815 addKeyToExpr(k);
816 if (Character.isSurrogate(c)) {
817 current += 2;
818 } else {
819 ++current;
820 }
821 continue;
822 }
Hans Boehm013969e2015-04-13 20:29:47 -0700823 int f = KeyMaps.funForString(moreChars, current);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700824 if (f != View.NO_ID) {
825 mCurrentButton = findViewById(f);
826 addKeyToExpr(f);
827 if (f == R.id.op_sqrt) {
828 // Square root entered as function; don't lose the parenthesis.
829 addKeyToExpr(R.id.lparen);
830 }
831 current = moreChars.indexOf('(', current) + 1;
832 continue;
833 }
834 // There are characters left, but we can't convert them to button presses.
835 mUnprocessedChars = moreChars.substring(current);
836 redisplayAfterFormulaChange();
837 return;
838 }
839 mUnprocessedChars = null;
840 redisplayAfterFormulaChange();
841 return;
Hans Boehm84614952014-11-25 18:46:17 -0800842 }
843
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700844 @Override
845 public boolean paste(Uri uri) {
846 if (mEvaluator.isLastSaved(uri)) {
847 if (mCurrentState == CalculatorState.ERROR
848 || mCurrentState == CalculatorState.RESULT) {
849 setState(CalculatorState.INPUT);
850 mEvaluator.clear();
Hans Boehm84614952014-11-25 18:46:17 -0800851 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700852 mEvaluator.addSaved();
853 redisplayAfterFormulaChange();
854 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800855 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700856 return false;
Hans Boehm84614952014-11-25 18:46:17 -0800857 }
858
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700859 @Override
860 public void paste(String s) {
861 addChars(s);
Hans Boehm84614952014-11-25 18:46:17 -0800862 }
863
Justin Klaassen4b3af052014-05-27 17:53:10 -0700864}