blob: b2cf47745e376c1a59291c0bae8287214f98deb6 [file] [log] [blame]
Justin Klaassen4b3af052014-05-27 17:53:10 -07001/*
Justin Klaassen44595162015-05-28 17:55:20 -07002 * Copyright (C) 2015 The Android Open Source Project
Justin Klaassen4b3af052014-05-27 17:53:10 -07003 *
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.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070020// TODO: Better indication of when the result is known to be exact.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070021// TODO: Check and possibly fix accessability issues.
Hans Boehm013969e2015-04-13 20:29:47 -070022// TODO: Copy & more general paste in formula? Note that this requires
23// great care: Currently the text version of a displayed formula
24// is not directly useful for re-evaluating the formula later, since
25// it contains ellipses representing subexpressions evaluated with
26// a different degree mode. Rather than supporting copy from the
27// formula window, we may eventually want to support generation of a
28// more useful text version in a separate window. It's not clear
29// this is worth the added (code and user) complexity.
Hans Boehm84614952014-11-25 18:46:17 -080030
Justin Klaassen4b3af052014-05-27 17:53:10 -070031package com.android.calculator2;
32
33import android.animation.Animator;
Justin Klaassen5f2a3342014-06-11 17:40:22 -070034import android.animation.Animator.AnimatorListener;
Justin Klaassen4b3af052014-05-27 17:53:10 -070035import android.animation.AnimatorListenerAdapter;
36import android.animation.AnimatorSet;
Justin Klaassen4b3af052014-05-27 17:53:10 -070037import android.animation.ObjectAnimator;
Justin Klaassen44595162015-05-28 17:55:20 -070038import android.animation.PropertyValuesHolder;
Justin Klaassen4b3af052014-05-27 17:53:10 -070039import android.app.Activity;
Hans Boehm84614952014-11-25 18:46:17 -080040import android.app.AlertDialog;
Hans Boehm84614952014-11-25 18:46:17 -080041import android.content.DialogInterface;
Justin Klaassend36d63e2015-05-05 12:59:36 -070042import android.content.Intent;
Hans Boehmbfe8c222015-04-02 16:26:07 -070043import android.content.res.Resources;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070044import android.graphics.Color;
Justin Klaassen8fff1442014-06-19 10:43:29 -070045import android.graphics.Rect;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070046import android.net.Uri;
Justin Klaassen4b3af052014-05-27 17:53:10 -070047import android.os.Bundle;
Justin Klaassenf79d6f62014-08-26 12:27:08 -070048import android.support.annotation.NonNull;
Justin Klaassen3b4d13d2014-06-06 18:18:37 +010049import android.support.v4.view.ViewPager;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070050import android.text.SpannableString;
51import android.text.Spanned;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070052import android.text.style.ForegroundColorSpan;
Justin Klaassen44595162015-05-28 17:55:20 -070053import android.util.Property;
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;
Hans Boehm84614952014-11-25 18:46:17 -080070import java.io.ByteArrayOutputStream;
Hans Boehm84614952014-11-25 18:46:17 -080071import java.io.IOException;
Justin Klaassen721ec842015-05-28 14:30:08 -070072import java.io.ObjectInput;
73import java.io.ObjectInputStream;
74import java.io.ObjectOutput;
75import java.io.ObjectOutputStream;
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
Justin Klaassen44595162015-05-28 17:55:20 -0700113 private final Property<TextView, Integer> TEXT_COLOR =
114 new Property<TextView, Integer>(Integer.class, "textColor") {
115 @Override
116 public Integer get(TextView textView) {
117 return textView.getCurrentTextColor();
118 }
119
120 @Override
121 public void set(TextView textView, Integer textColor) {
122 textView.setTextColor(textColor);
123 }
124 };
125
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.
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700128 private final OnKeyListener mFormulaOnKeyListener = new OnKeyListener() {
129 @Override
130 public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700131 if (keyEvent.getAction() != KeyEvent.ACTION_UP) return true;
Hans Boehm1176f232015-05-11 16:26:03 -0700132 stopActionMode();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700133 switch (keyCode) {
134 case KeyEvent.KEYCODE_NUMPAD_ENTER:
135 case KeyEvent.KEYCODE_ENTER:
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700136 case KeyEvent.KEYCODE_DPAD_CENTER:
137 mCurrentButton = mEqualButton;
138 onEquals();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700139 return true;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700140 case KeyEvent.KEYCODE_DEL:
141 mCurrentButton = mDeleteButton;
142 onDelete();
143 return true;
144 default:
145 final int raw = keyEvent.getKeyCharacterMap()
Justin Klaassen44595162015-05-28 17:55:20 -0700146 .get(keyCode, keyEvent.getMetaState());
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700147 if ((raw & KeyCharacterMap.COMBINING_ACCENT) != 0) {
148 return true; // discard
149 }
150 // Try to discard non-printing characters and the like.
151 // The user will have to explicitly delete other junk that gets past us.
152 if (Character.isIdentifierIgnorable(raw)
Justin Klaassen44595162015-05-28 17:55:20 -0700153 || Character.isWhitespace(raw)) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700154 return true;
155 }
Justin Klaassen44595162015-05-28 17:55:20 -0700156 char c = (char) raw;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700157 if (c == '=') {
Hans Boehme57fb012015-05-07 19:52:32 -0700158 mCurrentButton = mEqualButton;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700159 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;
Justin Klaassen44595162015-05-28 17:55:20 -0700182 private CalculatorResult mResultText;
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;
Justin Klaassene2711cb2015-05-28 11:13:17 -0700188
189 private TextView mInverseToggle;
190 private TextView mModeToggle;
191
Justin Klaassen721ec842015-05-28 14:30:08 -0700192 private View[] mInvertibleButtons;
Justin Klaassene2711cb2015-05-28 11:13:17 -0700193 private View[] mInverseButtons;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700194
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700195 private View mCurrentButton;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700196 private Animator mCurrentAnimator;
197
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700198 private String mUnprocessedChars = null; // Characters that were recently entered
199 // at the end of the display that have not yet
200 // been added to the underlying expression.
201
Justin Klaassen4b3af052014-05-27 17:53:10 -0700202 @Override
203 protected void onCreate(Bundle savedInstanceState) {
204 super.onCreate(savedInstanceState);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700205 setContentView(R.layout.activity_calculator);
Justin Klaassend48b7562015-04-16 16:51:38 -0700206 setActionBar((Toolbar) findViewById(R.id.toolbar));
207
208 // Hide all default options in the ActionBar.
209 getActionBar().setDisplayOptions(0);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700210
Justin Klaassen06360f92014-08-28 11:08:44 -0700211 mDisplayView = findViewById(R.id.display);
Justin Klaassene2711cb2015-05-28 11:13:17 -0700212 mModeView = (TextView) findViewById(R.id.mode);
Hans Boehm08e8f322015-04-21 13:18:38 -0700213 mFormulaText = (CalculatorText) findViewById(R.id.formula);
Justin Klaassen44595162015-05-28 17:55:20 -0700214 mResultText = (CalculatorResult) findViewById(R.id.result);
Justin Klaassend48b7562015-04-16 16:51:38 -0700215
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100216 mPadViewPager = (ViewPager) findViewById(R.id.pad_pager);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700217 mDeleteButton = findViewById(R.id.del);
218 mClearButton = findViewById(R.id.clr);
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700219 mEqualButton = findViewById(R.id.pad_numeric).findViewById(R.id.eq);
220 if (mEqualButton == null || mEqualButton.getVisibility() != View.VISIBLE) {
221 mEqualButton = findViewById(R.id.pad_operator).findViewById(R.id.eq);
222 }
Justin Klaassene2711cb2015-05-28 11:13:17 -0700223
224 mInverseToggle = (TextView) findViewById(R.id.toggle_inv);
225 mModeToggle = (TextView) findViewById(R.id.toggle_mode);
226
Justin Klaassen721ec842015-05-28 14:30:08 -0700227 mInvertibleButtons = new View[] {
228 findViewById(R.id.fun_sin),
229 findViewById(R.id.fun_cos),
Hans Boehm4db31b42015-05-31 12:19:05 -0700230 findViewById(R.id.fun_tan),
231 findViewById(R.id.fun_ln),
232 findViewById(R.id.fun_log),
233 findViewById(R.id.op_sqrt)
Justin Klaassene2711cb2015-05-28 11:13:17 -0700234 };
235 mInverseButtons = new View[] {
236 findViewById(R.id.fun_arcsin),
237 findViewById(R.id.fun_arccos),
Hans Boehm4db31b42015-05-31 12:19:05 -0700238 findViewById(R.id.fun_arctan),
239 findViewById(R.id.fun_exp),
240 findViewById(R.id.fun_10pow),
241 findViewById(R.id.op_sqr)
Justin Klaassene2711cb2015-05-28 11:13:17 -0700242 };
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700243
Justin Klaassen44595162015-05-28 17:55:20 -0700244 mEvaluator = new Evaluator(this, mResultText);
245 mResultText.setEvaluator(mEvaluator);
Hans Boehm013969e2015-04-13 20:29:47 -0700246 KeyMaps.setActivity(this);
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700247
Hans Boehm84614952014-11-25 18:46:17 -0800248 if (savedInstanceState != null) {
249 setState(CalculatorState.values()[
250 savedInstanceState.getInt(KEY_DISPLAY_STATE,
251 CalculatorState.INPUT.ordinal())]);
Hans Boehm760a9dc2015-04-20 10:27:12 -0700252 CharSequence unprocessed = savedInstanceState.getCharSequence(KEY_UNPROCESSED_CHARS);
253 if (unprocessed != null) {
254 mUnprocessedChars = unprocessed.toString();
255 }
256 byte[] state = savedInstanceState.getByteArray(KEY_EVAL_STATE);
Hans Boehm84614952014-11-25 18:46:17 -0800257 if (state != null) {
258 try (ObjectInput in = new ObjectInputStream(new ByteArrayInputStream(state))) {
259 mEvaluator.restoreInstanceState(in);
260 } catch (Throwable ignored) {
261 // When in doubt, revert to clean state
262 mCurrentState = CalculatorState.INPUT;
263 mEvaluator.clear();
264 }
265 }
Hans Boehmfbcef702015-04-27 18:07:47 -0700266 } else {
267 mCurrentState = CalculatorState.INPUT;
268 mEvaluator.clear();
Hans Boehm84614952014-11-25 18:46:17 -0800269 }
Justin Klaassene2711cb2015-05-28 11:13:17 -0700270
Hans Boehm08e8f322015-04-21 13:18:38 -0700271 mFormulaText.setOnKeyListener(mFormulaOnKeyListener);
272 mFormulaText.setOnTextSizeChangeListener(this);
273 mFormulaText.setPasteListener(this);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700274 mDeleteButton.setOnLongClickListener(this);
Justin Klaassene2711cb2015-05-28 11:13:17 -0700275
276 onInverseToggled(mInverseToggle.isSelected());
277 onModeChanged(mEvaluator.getDegreeMode());
278
Hans Boehm84614952014-11-25 18:46:17 -0800279 if (mCurrentState != CalculatorState.INPUT) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700280 // Just reevaluate.
281 redisplayFormula();
Hans Boehm84614952014-11-25 18:46:17 -0800282 setState(CalculatorState.INIT);
Hans Boehm84614952014-11-25 18:46:17 -0800283 mEvaluator.requireResult();
284 } else {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700285 redisplayAfterFormulaChange();
Hans Boehm84614952014-11-25 18:46:17 -0800286 }
287 // TODO: We're currently not saving and restoring scroll position.
288 // We probably should. Details may require care to deal with:
289 // - new display size
290 // - slow recomputation if we've scrolled far.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700291 }
292
293 @Override
Justin Klaassenf79d6f62014-08-26 12:27:08 -0700294 protected void onSaveInstanceState(@NonNull Bundle outState) {
295 // If there's an animation in progress, cancel it first to ensure our state is up-to-date.
296 if (mCurrentAnimator != null) {
297 mCurrentAnimator.cancel();
298 }
299
Justin Klaassen4b3af052014-05-27 17:53:10 -0700300 super.onSaveInstanceState(outState);
Hans Boehm84614952014-11-25 18:46:17 -0800301 outState.putInt(KEY_DISPLAY_STATE, mCurrentState.ordinal());
Hans Boehm760a9dc2015-04-20 10:27:12 -0700302 outState.putCharSequence(KEY_UNPROCESSED_CHARS, mUnprocessedChars);
Hans Boehm84614952014-11-25 18:46:17 -0800303 ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
304 try (ObjectOutput out = new ObjectOutputStream(byteArrayStream)) {
305 mEvaluator.saveInstanceState(out);
306 } catch (IOException e) {
307 // Impossible; No IO involved.
308 throw new AssertionError("Impossible IO exception", e);
309 }
310 outState.putByteArray(KEY_EVAL_STATE, byteArrayStream.toByteArray());
Justin Klaassen4b3af052014-05-27 17:53:10 -0700311 }
312
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700313 // Set the state, updating delete label and display colors.
314 // This restores display positions on moving to INPUT.
Justin Klaassend48b7562015-04-16 16:51:38 -0700315 // But movement/animation for moving to RESULT has already been done.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700316 private void setState(CalculatorState state) {
317 if (mCurrentState != state) {
Hans Boehm84614952014-11-25 18:46:17 -0800318 if (state == CalculatorState.INPUT) {
319 restoreDisplayPositions();
320 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700321 mCurrentState = state;
322
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700323 if (mCurrentState == CalculatorState.RESULT) {
324 // No longer do this for ERROR; allow mistakes to be corrected.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700325 mDeleteButton.setVisibility(View.GONE);
326 mClearButton.setVisibility(View.VISIBLE);
327 } else {
328 mDeleteButton.setVisibility(View.VISIBLE);
329 mClearButton.setVisibility(View.GONE);
330 }
331
Hans Boehm84614952014-11-25 18:46:17 -0800332 if (mCurrentState == CalculatorState.ERROR) {
Justin Klaassen44595162015-05-28 17:55:20 -0700333 final int errorColor = getColor(R.color.calculator_error_color);
Hans Boehm08e8f322015-04-21 13:18:38 -0700334 mFormulaText.setTextColor(errorColor);
Justin Klaassen44595162015-05-28 17:55:20 -0700335 mResultText.setTextColor(errorColor);
Justin Klaassen8fff1442014-06-19 10:43:29 -0700336 getWindow().setStatusBarColor(errorColor);
Justin Klaassen44595162015-05-28 17:55:20 -0700337 } else if (mCurrentState != CalculatorState.RESULT) {
338 mFormulaText.setTextColor(getColor(R.color.display_formula_text_color));
339 mResultText.setTextColor(getColor(R.color.display_result_text_color));
340 getWindow().setStatusBarColor(getColor(R.color.calculator_accent_color));
Justin Klaassen4b3af052014-05-27 17:53:10 -0700341 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700342
343 invalidateOptionsMenu();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700344 }
345 }
346
Hans Boehm1176f232015-05-11 16:26:03 -0700347 // Stop any active ActionMode. Return true if there was one.
348 private boolean stopActionMode() {
Justin Klaassen44595162015-05-28 17:55:20 -0700349 if (mResultText.stopActionMode()) {
Hans Boehm1176f232015-05-11 16:26:03 -0700350 return true;
351 }
352 if (mFormulaText.stopActionMode()) {
353 return true;
354 }
355 return false;
356 }
357
Justin Klaassen4b3af052014-05-27 17:53:10 -0700358 @Override
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100359 public void onBackPressed() {
Hans Boehm1176f232015-05-11 16:26:03 -0700360 if (!stopActionMode()) {
361 if (mPadViewPager != null && mPadViewPager.getCurrentItem() != 0) {
362 // Select the previous pad.
363 mPadViewPager.setCurrentItem(mPadViewPager.getCurrentItem() - 1);
364 } else {
365 // If the user is currently looking at the first pad (or the pad is not paged),
366 // allow the system to handle the Back button.
367 super.onBackPressed();
368 }
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100369 }
370 }
371
372 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700373 public void onUserInteraction() {
374 super.onUserInteraction();
375
376 // If there's an animation in progress, cancel it so the user interaction can be handled
377 // immediately.
378 if (mCurrentAnimator != null) {
379 mCurrentAnimator.cancel();
380 }
381 }
382
Justin Klaassene2711cb2015-05-28 11:13:17 -0700383 /**
384 * Invoked whenever the inverse button is toggled to update the UI.
385 *
386 * @param showInverse {@code true} if inverse functions should be shown
387 */
388 private void onInverseToggled(boolean showInverse) {
389 if (showInverse) {
390 mInverseToggle.setContentDescription(getString(R.string.desc_inv_on));
Justin Klaassen721ec842015-05-28 14:30:08 -0700391 for (View invertibleButton : mInvertibleButtons) {
392 invertibleButton.setVisibility(View.GONE);
Justin Klaassene2711cb2015-05-28 11:13:17 -0700393 }
394 for (View inverseButton : mInverseButtons) {
395 inverseButton.setVisibility(View.VISIBLE);
396 }
397 } else {
398 mInverseToggle.setContentDescription(getString(R.string.desc_inv_off));
Justin Klaassen721ec842015-05-28 14:30:08 -0700399 for (View invertibleButton : mInvertibleButtons) {
400 invertibleButton.setVisibility(View.VISIBLE);
Justin Klaassene2711cb2015-05-28 11:13:17 -0700401 }
402 for (View inverseButton : mInverseButtons) {
403 inverseButton.setVisibility(View.GONE);
404 }
405 }
406 }
407
408 /**
409 * Invoked whenever the deg/rad mode may have changed to update the UI.
410 *
411 * @param degreeMode {@code true} if in degree mode
412 */
413 private void onModeChanged(boolean degreeMode) {
414 if (degreeMode) {
Justin Klaassend48b7562015-04-16 16:51:38 -0700415 mModeView.setText(R.string.mode_deg);
Justin Klaassene2711cb2015-05-28 11:13:17 -0700416 mModeView.setContentDescription(getString(R.string.desc_mode_deg));
417
418 mModeToggle.setText(R.string.mode_rad);
419 mModeToggle.setContentDescription(getString(R.string.desc_switch_rad));
Hans Boehmbfe8c222015-04-02 16:26:07 -0700420 } else {
Justin Klaassend48b7562015-04-16 16:51:38 -0700421 mModeView.setText(R.string.mode_rad);
Justin Klaassene2711cb2015-05-28 11:13:17 -0700422 mModeView.setContentDescription(getString(R.string.desc_mode_rad));
423
424 mModeToggle.setText(R.string.mode_deg);
425 mModeToggle.setContentDescription(getString(R.string.desc_switch_deg));
Hans Boehmbfe8c222015-04-02 16:26:07 -0700426 }
427 }
Hans Boehm84614952014-11-25 18:46:17 -0800428
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700429 // Add the given button id to input expression.
430 // If appropriate, clear the expression before doing so.
431 private void addKeyToExpr(int id) {
432 if (mCurrentState == CalculatorState.ERROR) {
433 setState(CalculatorState.INPUT);
434 } else if (mCurrentState == CalculatorState.RESULT) {
435 if (KeyMaps.isBinary(id) || KeyMaps.isSuffix(id)) {
436 mEvaluator.collapse();
437 } else {
438 mEvaluator.clear();
439 }
440 setState(CalculatorState.INPUT);
441 }
442 if (!mEvaluator.append(id)) {
443 // TODO: Some user visible feedback?
444 }
445 }
446
447 private void redisplayAfterFormulaChange() {
448 // TODO: Could do this more incrementally.
449 redisplayFormula();
450 setState(CalculatorState.INPUT);
Hans Boehmc023b732015-04-29 11:30:47 -0700451 if (mEvaluator.getExpr().hasInterestingOps()) {
452 mEvaluator.evaluateAndShowResult();
453 } else {
Justin Klaassen44595162015-05-28 17:55:20 -0700454 mResultText.clear();
Hans Boehmc023b732015-04-29 11:30:47 -0700455 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700456 }
457
Justin Klaassen4b3af052014-05-27 17:53:10 -0700458 public void onButtonClick(View view) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700459 mCurrentButton = view;
Hans Boehm1176f232015-05-11 16:26:03 -0700460 stopActionMode();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700461
Hans Boehm84614952014-11-25 18:46:17 -0800462 // Always cancel in-progress evaluation.
463 // If we were waiting for the result, do nothing else.
464 mEvaluator.cancelAll();
Justin Klaassend48b7562015-04-16 16:51:38 -0700465
Hans Boehm84614952014-11-25 18:46:17 -0800466 if (mCurrentState == CalculatorState.EVALUATE
467 || mCurrentState == CalculatorState.ANIMATE) {
468 onCancelled();
469 return;
470 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700471
Justin Klaassend48b7562015-04-16 16:51:38 -0700472 final int id = view.getId();
Hans Boehm84614952014-11-25 18:46:17 -0800473 switch (id) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700474 case R.id.eq:
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700475 onEquals();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700476 break;
477 case R.id.del:
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700478 onDelete();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700479 break;
480 case R.id.clr:
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700481 onClear();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700482 break;
Justin Klaassene2711cb2015-05-28 11:13:17 -0700483 case R.id.toggle_inv:
484 final boolean selected = !mInverseToggle.isSelected();
485 mInverseToggle.setSelected(selected);
486 onInverseToggled(selected);
487 break;
488 case R.id.toggle_mode:
489 final boolean mode = !mEvaluator.getDegreeMode();
Hans Boehmbfe8c222015-04-02 16:26:07 -0700490 if (mCurrentState == CalculatorState.RESULT) {
491 mEvaluator.collapse(); // Capture result evaluated in old mode
492 redisplayFormula();
493 }
494 // In input mode, we reinterpret already entered trig functions.
495 mEvaluator.setDegreeMode(mode);
Justin Klaassene2711cb2015-05-28 11:13:17 -0700496 onModeChanged(mode);
497
Hans Boehmbfe8c222015-04-02 16:26:07 -0700498 setState(CalculatorState.INPUT);
Justin Klaassen44595162015-05-28 17:55:20 -0700499 mResultText.clear();
Hans Boehmc023b732015-04-29 11:30:47 -0700500 if (mEvaluator.getExpr().hasInterestingOps()) {
501 mEvaluator.evaluateAndShowResult();
502 }
Hans Boehmbfe8c222015-04-02 16:26:07 -0700503 break;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700504 default:
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700505 addKeyToExpr(id);
506 redisplayAfterFormulaChange();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700507 break;
508 }
509 }
510
Hans Boehm84614952014-11-25 18:46:17 -0800511 void redisplayFormula() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700512 String formula = mEvaluator.getExpr().toString(this);
513 if (mUnprocessedChars != null) {
514 // Add and highlight characters we couldn't process.
515 SpannableString formatted = new SpannableString(formula + mUnprocessedChars);
516 // TODO: should probably match this to the error color.
517 formatted.setSpan(new ForegroundColorSpan(Color.RED),
518 formula.length(), formatted.length(),
519 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Hans Boehm08e8f322015-04-21 13:18:38 -0700520 mFormulaText.setText(formatted);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700521 } else {
Hans Boehm08e8f322015-04-21 13:18:38 -0700522 mFormulaText.setText(formula);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700523 }
Hans Boehm84614952014-11-25 18:46:17 -0800524 }
525
Justin Klaassen4b3af052014-05-27 17:53:10 -0700526 @Override
527 public boolean onLongClick(View view) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700528 mCurrentButton = view;
529
Justin Klaassen4b3af052014-05-27 17:53:10 -0700530 if (view.getId() == R.id.del) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700531 onClear();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700532 return true;
533 }
534 return false;
535 }
536
Hans Boehm84614952014-11-25 18:46:17 -0800537 // Initial evaluation completed successfully. Initiate display.
Hans Boehm61568a12015-05-18 18:25:41 -0700538 public void onEvaluate(int initDisplayPrec, int leastDigPos, String truncatedWholeNumber) {
Justin Klaassend48b7562015-04-16 16:51:38 -0700539 // Invalidate any options that may depend on the current result.
540 invalidateOptionsMenu();
541
Justin Klaassen44595162015-05-28 17:55:20 -0700542 mResultText.displayResult(initDisplayPrec, leastDigPos, truncatedWholeNumber);
Hans Boehm61568a12015-05-18 18:25:41 -0700543 if (mCurrentState != CalculatorState.INPUT) { // in EVALUATE or INIT state
Hans Boehm84614952014-11-25 18:46:17 -0800544 onResult(mCurrentState != CalculatorState.INIT);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700545 }
Hans Boehm84614952014-11-25 18:46:17 -0800546 }
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700547
Hans Boehm84614952014-11-25 18:46:17 -0800548 public void onCancelled() {
549 // We should be in EVALUATE state.
550 // Display is still in input state.
551 setState(CalculatorState.INPUT);
Justin Klaassen44595162015-05-28 17:55:20 -0700552 mResultText.clear();
Hans Boehm84614952014-11-25 18:46:17 -0800553 }
554
555 // Reevaluation completed; ask result to redisplay current value.
556 public void onReevaluate()
557 {
Justin Klaassen44595162015-05-28 17:55:20 -0700558 mResultText.redisplay();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700559 }
560
Justin Klaassenfed941a2014-06-09 18:42:40 +0100561 @Override
562 public void onTextSizeChanged(final TextView textView, float oldSize) {
563 if (mCurrentState != CalculatorState.INPUT) {
564 // Only animate text changes that occur from user input.
565 return;
566 }
567
568 // Calculate the values needed to perform the scale and translation animations,
569 // maintaining the same apparent baseline for the displayed text.
570 final float textScale = oldSize / textView.getTextSize();
571 final float translationX = (1.0f - textScale) *
572 (textView.getWidth() / 2.0f - textView.getPaddingEnd());
573 final float translationY = (1.0f - textScale) *
574 (textView.getHeight() / 2.0f - textView.getPaddingBottom());
575
576 final AnimatorSet animatorSet = new AnimatorSet();
577 animatorSet.playTogether(
578 ObjectAnimator.ofFloat(textView, View.SCALE_X, textScale, 1.0f),
579 ObjectAnimator.ofFloat(textView, View.SCALE_Y, textScale, 1.0f),
580 ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, translationX, 0.0f),
581 ObjectAnimator.ofFloat(textView, View.TRANSLATION_Y, translationY, 0.0f));
Justin Klaassen94db7202014-06-11 11:22:31 -0700582 animatorSet.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
Justin Klaassenfed941a2014-06-09 18:42:40 +0100583 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
584 animatorSet.start();
585 }
586
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700587 private void onEquals() {
Hans Boehmc023b732015-04-29 11:30:47 -0700588 if (mCurrentState == CalculatorState.INPUT && !mEvaluator.getExpr().isEmpty()) {
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700589 setState(CalculatorState.EVALUATE);
Hans Boehm84614952014-11-25 18:46:17 -0800590 mEvaluator.requireResult();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700591 }
592 }
593
594 private void onDelete() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700595 // Delete works like backspace; remove the last character or operator from the expression.
596 // Note that we handle keyboard delete exactly like the delete button. For
597 // example the delete button can be used to delete a character from an incomplete
598 // function name typed on a physical keyboard.
Hans Boehm84614952014-11-25 18:46:17 -0800599 mEvaluator.cancelAll();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700600 // This should be impossible in RESULT state.
601 setState(CalculatorState.INPUT);
602 if (mUnprocessedChars != null) {
603 int len = mUnprocessedChars.length();
604 if (len > 0) {
605 mUnprocessedChars = mUnprocessedChars.substring(0, len-1);
606 } else {
Hans Boehmc023b732015-04-29 11:30:47 -0700607 mEvaluator.delete();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700608 }
609 } else {
Hans Boehmc023b732015-04-29 11:30:47 -0700610 mEvaluator.delete();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700611 }
612 redisplayAfterFormulaChange();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700613 }
614
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700615 private void reveal(View sourceView, int colorRes, AnimatorListener listener) {
Justin Klaassen06360f92014-08-28 11:08:44 -0700616 final ViewGroupOverlay groupOverlay =
617 (ViewGroupOverlay) getWindow().getDecorView().getOverlay();
Justin Klaassen8fff1442014-06-19 10:43:29 -0700618
619 final Rect displayRect = new Rect();
Justin Klaassen06360f92014-08-28 11:08:44 -0700620 mDisplayView.getGlobalVisibleRect(displayRect);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700621
622 // Make reveal cover the display and status bar.
623 final View revealView = new View(this);
Justin Klaassen8fff1442014-06-19 10:43:29 -0700624 revealView.setBottom(displayRect.bottom);
625 revealView.setLeft(displayRect.left);
626 revealView.setRight(displayRect.right);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700627 revealView.setBackgroundColor(getResources().getColor(colorRes));
Justin Klaassen06360f92014-08-28 11:08:44 -0700628 groupOverlay.add(revealView);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700629
Justin Klaassen4b3af052014-05-27 17:53:10 -0700630 final int[] clearLocation = new int[2];
631 sourceView.getLocationInWindow(clearLocation);
632 clearLocation[0] += sourceView.getWidth() / 2;
633 clearLocation[1] += sourceView.getHeight() / 2;
634
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700635 final int revealCenterX = clearLocation[0] - revealView.getLeft();
636 final int revealCenterY = clearLocation[1] - revealView.getTop();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700637
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700638 final double x1_2 = Math.pow(revealView.getLeft() - revealCenterX, 2);
639 final double x2_2 = Math.pow(revealView.getRight() - revealCenterX, 2);
640 final double y_2 = Math.pow(revealView.getTop() - revealCenterY, 2);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700641 final float revealRadius = (float) Math.max(Math.sqrt(x1_2 + y_2), Math.sqrt(x2_2 + y_2));
642
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700643 final Animator revealAnimator =
644 ViewAnimationUtils.createCircularReveal(revealView,
ztenghui3d6ecaf2014-06-05 09:56:00 -0700645 revealCenterX, revealCenterY, 0.0f, revealRadius);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700646 revealAnimator.setDuration(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700647 getResources().getInteger(android.R.integer.config_longAnimTime));
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700648 revealAnimator.addListener(listener);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700649
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700650 final Animator alphaAnimator = ObjectAnimator.ofFloat(revealView, View.ALPHA, 0.0f);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700651 alphaAnimator.setDuration(
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700652 getResources().getInteger(android.R.integer.config_mediumAnimTime));
Justin Klaassen4b3af052014-05-27 17:53:10 -0700653
654 final AnimatorSet animatorSet = new AnimatorSet();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700655 animatorSet.play(revealAnimator).before(alphaAnimator);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700656 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
657 animatorSet.addListener(new AnimatorListenerAdapter() {
658 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700659 public void onAnimationEnd(Animator animator) {
Justin Klaassen8fff1442014-06-19 10:43:29 -0700660 groupOverlay.remove(revealView);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700661 mCurrentAnimator = null;
662 }
663 });
664
665 mCurrentAnimator = animatorSet;
666 animatorSet.start();
667 }
668
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700669 private void onClear() {
Hans Boehm84614952014-11-25 18:46:17 -0800670 if (mEvaluator.getExpr().isEmpty()) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700671 return;
672 }
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700673 reveal(mCurrentButton, R.color.calculator_accent_color, new AnimatorListenerAdapter() {
674 @Override
675 public void onAnimationEnd(Animator animation) {
Hans Boehm760a9dc2015-04-20 10:27:12 -0700676 mUnprocessedChars = null;
Justin Klaassen44595162015-05-28 17:55:20 -0700677 mResultText.clear();
Hans Boehm760a9dc2015-04-20 10:27:12 -0700678 mEvaluator.clear();
679 setState(CalculatorState.INPUT);
Hans Boehm84614952014-11-25 18:46:17 -0800680 redisplayFormula();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700681 }
682 });
683 }
684
Hans Boehm84614952014-11-25 18:46:17 -0800685 // Evaluation encountered en error. Display the error.
686 void onError(final int errorResourceId) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700687 if (mCurrentState == CalculatorState.EVALUATE) {
688 setState(CalculatorState.ANIMATE);
689 reveal(mCurrentButton, R.color.calculator_error_color,
690 new AnimatorListenerAdapter() {
691 @Override
692 public void onAnimationEnd(Animator animation) {
693 setState(CalculatorState.ERROR);
Justin Klaassen44595162015-05-28 17:55:20 -0700694 mResultText.displayError(errorResourceId);
Hans Boehmfbcef702015-04-27 18:07:47 -0700695 }
696 });
697 } else if (mCurrentState == CalculatorState.INIT) {
698 setState(CalculatorState.ERROR);
Justin Klaassen44595162015-05-28 17:55:20 -0700699 mResultText.displayError(errorResourceId);
Hans Boehmc023b732015-04-29 11:30:47 -0700700 } else {
Justin Klaassen44595162015-05-28 17:55:20 -0700701 mResultText.clear();
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700702 }
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700703 }
704
Hans Boehm84614952014-11-25 18:46:17 -0800705
706 // Animate movement of result into the top formula slot.
707 // Result window now remains translated in the top slot while the result is displayed.
708 // (We convert it back to formula use only when the user provides new input.)
Justin Klaassen44595162015-05-28 17:55:20 -0700709 // Historical note: In the Lollipop version, this invisibly and instantaneously moved
Hans Boehm84614952014-11-25 18:46:17 -0800710 // formula and result displays back at the end of the animation. We no longer do that,
711 // so that we can continue to properly support scrolling of the result.
712 // We assume the result already contains the text to be expanded.
713 private void onResult(boolean animate) {
Justin Klaassen44595162015-05-28 17:55:20 -0700714 // Calculate the textSize that would be used to display the result in the formula.
715 // For scrollable results just use the minimum textSize to maximize the number of digits
716 // that are visible on screen.
717 float textSize = mFormulaText.getMinimumTextSize();
718 if (!mResultText.isScrollable()) {
719 textSize = mFormulaText.getVariableTextSize(mResultText.getText().toString());
720 }
721
722 // Scale the result to match the calculated textSize, minimizing the jump-cut transition
723 // when a result is reused in a subsequent expression.
724 final float resultScale = textSize / mResultText.getTextSize();
725
726 // Set the result's pivot to match its gravity.
727 mResultText.setPivotX(mResultText.getWidth() - mResultText.getPaddingRight());
728 mResultText.setPivotY(mResultText.getHeight() - mResultText.getPaddingBottom());
729
730 // Calculate the necessary translations so the result takes the place of the formula and
731 // the formula moves off the top of the screen.
732 final float resultTranslationY = (mFormulaText.getBottom() - mResultText.getBottom())
733 - (mFormulaText.getPaddingBottom() - mResultText.getPaddingBottom());
Hans Boehm08e8f322015-04-21 13:18:38 -0700734 final float formulaTranslationY = -mFormulaText.getBottom();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700735
Justin Klaassen44595162015-05-28 17:55:20 -0700736 // Change the result's textColor to match the formula.
737 final int formulaTextColor = mFormulaText.getCurrentTextColor();
738
Hans Boehm84614952014-11-25 18:46:17 -0800739 if (animate) {
740 final AnimatorSet animatorSet = new AnimatorSet();
741 animatorSet.playTogether(
Justin Klaassen44595162015-05-28 17:55:20 -0700742 ObjectAnimator.ofPropertyValuesHolder(mResultText,
743 PropertyValuesHolder.ofFloat(View.SCALE_X, resultScale),
744 PropertyValuesHolder.ofFloat(View.SCALE_Y, resultScale),
745 PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, resultTranslationY)),
746 ObjectAnimator.ofArgb(mResultText, TEXT_COLOR, formulaTextColor),
747 ObjectAnimator.ofFloat(mFormulaText, View.TRANSLATION_Y, formulaTranslationY));
748 animatorSet.setDuration(getResources().getInteger(
749 android.R.integer.config_longAnimTime));
Hans Boehm84614952014-11-25 18:46:17 -0800750 animatorSet.addListener(new AnimatorListenerAdapter() {
751 @Override
Hans Boehm84614952014-11-25 18:46:17 -0800752 public void onAnimationEnd(Animator animation) {
753 setState(CalculatorState.RESULT);
754 mCurrentAnimator = null;
755 }
756 });
Justin Klaassen4b3af052014-05-27 17:53:10 -0700757
Hans Boehm84614952014-11-25 18:46:17 -0800758 mCurrentAnimator = animatorSet;
759 animatorSet.start();
760 } else /* No animation desired; get there fast, e.g. when restarting */ {
Justin Klaassen44595162015-05-28 17:55:20 -0700761 mResultText.setScaleX(resultScale);
762 mResultText.setScaleY(resultScale);
763 mResultText.setTranslationY(resultTranslationY);
764 mResultText.setTextColor(formulaTextColor);
Hans Boehm08e8f322015-04-21 13:18:38 -0700765 mFormulaText.setTranslationY(formulaTranslationY);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700766 setState(CalculatorState.RESULT);
Hans Boehm84614952014-11-25 18:46:17 -0800767 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700768 }
Hans Boehm84614952014-11-25 18:46:17 -0800769
770 // Restore positions of the formula and result displays back to their original,
771 // pre-animation state.
772 private void restoreDisplayPositions() {
773 // Clear result.
Justin Klaassen44595162015-05-28 17:55:20 -0700774 mResultText.setText("");
Hans Boehm84614952014-11-25 18:46:17 -0800775 // Reset all of the values modified during the animation.
Justin Klaassen44595162015-05-28 17:55:20 -0700776 mResultText.setScaleX(1.0f);
777 mResultText.setScaleY(1.0f);
778 mResultText.setTranslationX(0.0f);
779 mResultText.setTranslationY(0.0f);
Hans Boehm08e8f322015-04-21 13:18:38 -0700780 mFormulaText.setTranslationY(0.0f);
Hans Boehm84614952014-11-25 18:46:17 -0800781
Hans Boehm08e8f322015-04-21 13:18:38 -0700782 mFormulaText.requestFocus();
Hans Boehm84614952014-11-25 18:46:17 -0800783 }
784
Justin Klaassend48b7562015-04-16 16:51:38 -0700785 @Override
786 public boolean onCreateOptionsMenu(Menu menu) {
Justin Klaassend36d63e2015-05-05 12:59:36 -0700787 super.onCreateOptionsMenu(menu);
788
789 getMenuInflater().inflate(R.menu.activity_calculator, menu);
Justin Klaassend48b7562015-04-16 16:51:38 -0700790 return true;
791 }
792
793 @Override
794 public boolean onPrepareOptionsMenu(Menu menu) {
Justin Klaassend36d63e2015-05-05 12:59:36 -0700795 super.onPrepareOptionsMenu(menu);
796
797 // Show the leading option when displaying a result.
798 menu.findItem(R.id.menu_leading).setVisible(mCurrentState == CalculatorState.RESULT);
799
800 // Show the fraction option when displaying a rational result.
801 menu.findItem(R.id.menu_fraction).setVisible(mCurrentState == CalculatorState.RESULT
802 && mEvaluator.getRational() != null);
803
Justin Klaassend48b7562015-04-16 16:51:38 -0700804 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800805 }
806
807 @Override
Justin Klaassend48b7562015-04-16 16:51:38 -0700808 public boolean onOptionsItemSelected(MenuItem item) {
Hans Boehm84614952014-11-25 18:46:17 -0800809 switch (item.getItemId()) {
Justin Klaassend36d63e2015-05-05 12:59:36 -0700810 case R.id.menu_leading:
811 displayFull();
Hans Boehm84614952014-11-25 18:46:17 -0800812 return true;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700813 case R.id.menu_fraction:
814 displayFraction();
815 return true;
Justin Klaassend36d63e2015-05-05 12:59:36 -0700816 case R.id.menu_licenses:
817 startActivity(new Intent(this, Licenses.class));
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700818 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800819 default:
820 return super.onOptionsItemSelected(item);
821 }
822 }
823
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700824 private void displayMessage(String s) {
Justin Klaassen44595162015-05-28 17:55:20 -0700825 new AlertDialog.Builder(this)
826 .setMessage(s)
827 .setNegativeButton(R.string.dismiss, null /* listener */)
828 .show();
Hans Boehm84614952014-11-25 18:46:17 -0800829 }
830
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700831 private void displayFraction() {
832 BoundedRational result = mEvaluator.getRational();
Hans Boehm013969e2015-04-13 20:29:47 -0700833 displayMessage(KeyMaps.translateResult(result.toNiceString()));
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700834 }
835
836 // Display full result to currently evaluated precision
837 private void displayFull() {
838 Resources res = getResources();
Justin Klaassen44595162015-05-28 17:55:20 -0700839 String msg = mResultText.getFullText() + " ";
840 if (mResultText.fullTextIsExact()) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700841 msg += res.getString(R.string.exact);
842 } else {
843 msg += res.getString(R.string.approximate);
844 }
845 displayMessage(msg);
846 }
847
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700848 // Add input characters to the end of the expression by mapping them to
849 // the appropriate button pushes when possible. Leftover characters
850 // are added to mUnprocessedChars, which is presumed to immediately
851 // precede the newly added characters.
852 private void addChars(String moreChars) {
853 if (mUnprocessedChars != null) {
854 moreChars = mUnprocessedChars + moreChars;
855 }
856 int current = 0;
857 int len = moreChars.length();
858 while (current < len) {
859 char c = moreChars.charAt(current);
Hans Boehm013969e2015-04-13 20:29:47 -0700860 int k = KeyMaps.keyForChar(c);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700861 if (k != View.NO_ID) {
862 mCurrentButton = findViewById(k);
863 addKeyToExpr(k);
864 if (Character.isSurrogate(c)) {
865 current += 2;
866 } else {
867 ++current;
868 }
869 continue;
870 }
Hans Boehm013969e2015-04-13 20:29:47 -0700871 int f = KeyMaps.funForString(moreChars, current);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700872 if (f != View.NO_ID) {
873 mCurrentButton = findViewById(f);
874 addKeyToExpr(f);
875 if (f == R.id.op_sqrt) {
876 // Square root entered as function; don't lose the parenthesis.
877 addKeyToExpr(R.id.lparen);
878 }
879 current = moreChars.indexOf('(', current) + 1;
880 continue;
881 }
882 // There are characters left, but we can't convert them to button presses.
883 mUnprocessedChars = moreChars.substring(current);
884 redisplayAfterFormulaChange();
885 return;
886 }
887 mUnprocessedChars = null;
888 redisplayAfterFormulaChange();
889 return;
Hans Boehm84614952014-11-25 18:46:17 -0800890 }
891
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700892 @Override
893 public boolean paste(Uri uri) {
894 if (mEvaluator.isLastSaved(uri)) {
895 if (mCurrentState == CalculatorState.ERROR
896 || mCurrentState == CalculatorState.RESULT) {
897 setState(CalculatorState.INPUT);
898 mEvaluator.clear();
Hans Boehm84614952014-11-25 18:46:17 -0800899 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700900 mEvaluator.addSaved();
901 redisplayAfterFormulaChange();
902 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800903 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700904 return false;
Hans Boehm84614952014-11-25 18:46:17 -0800905 }
906
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700907 @Override
908 public void paste(String s) {
909 addChars(s);
Hans Boehm84614952014-11-25 18:46:17 -0800910 }
911
Justin Klaassen4b3af052014-05-27 17:53:10 -0700912}