blob: d5242faee5911a22664d296714d26595185f7b9e [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;
Justin Klaassen4b3af052014-05-27 17:53:10 -070043import android.animation.ObjectAnimator;
Justin Klaassen4b3af052014-05-27 17:53:10 -070044import android.app.Activity;
Hans Boehm84614952014-11-25 18:46:17 -080045import android.app.AlertDialog;
Hans Boehm84614952014-11-25 18:46:17 -080046import android.content.DialogInterface;
Justin Klaassend36d63e2015-05-05 12:59:36 -070047import android.content.Intent;
Hans Boehmbfe8c222015-04-02 16:26:07 -070048import android.content.res.Resources;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070049import android.graphics.Color;
Justin Klaassen8fff1442014-06-19 10:43:29 -070050import android.graphics.Rect;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070051import android.net.Uri;
Justin Klaassen4b3af052014-05-27 17:53:10 -070052import android.os.Bundle;
Justin Klaassenf79d6f62014-08-26 12:27:08 -070053import android.support.annotation.NonNull;
Justin Klaassen3b4d13d2014-06-06 18:18:37 +010054import android.support.v4.view.ViewPager;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070055import android.text.SpannableString;
56import android.text.Spanned;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070057import android.text.style.ForegroundColorSpan;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070058import android.view.KeyCharacterMap;
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -070059import android.view.KeyEvent;
Hans Boehm84614952014-11-25 18:46:17 -080060import android.view.Menu;
61import android.view.MenuItem;
Justin Klaassen4b3af052014-05-27 17:53:10 -070062import android.view.View;
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -070063import android.view.View.OnKeyListener;
Justin Klaassen4b3af052014-05-27 17:53:10 -070064import android.view.View.OnLongClickListener;
Justin Klaassen5f2a3342014-06-11 17:40:22 -070065import android.view.ViewAnimationUtils;
Justin Klaassen8fff1442014-06-19 10:43:29 -070066import android.view.ViewGroupOverlay;
Justin Klaassen4b3af052014-05-27 17:53:10 -070067import android.view.animation.AccelerateDecelerateInterpolator;
Justin Klaassenfed941a2014-06-09 18:42:40 +010068import android.widget.TextView;
Justin Klaassend48b7562015-04-16 16:51:38 -070069import android.widget.Toolbar;
Justin Klaassenfed941a2014-06-09 18:42:40 +010070
Hans Boehm08e8f322015-04-21 13:18:38 -070071import com.android.calculator2.CalculatorText.OnTextSizeChangeListener;
Hans Boehm84614952014-11-25 18:46:17 -080072
73import java.io.ByteArrayInputStream;
74import java.io.ObjectInputStream;
75import java.io.ByteArrayOutputStream;
76import java.io.ObjectOutputStream;
77import java.io.ObjectInput;
78import java.io.ObjectOutput;
79import java.io.IOException;
Justin Klaassen4b3af052014-05-27 17:53:10 -070080
Justin Klaassen04f79c72014-06-27 17:25:35 -070081public class Calculator extends Activity
Hans Boehm08e8f322015-04-21 13:18:38 -070082 implements OnTextSizeChangeListener, OnLongClickListener, CalculatorText.PasteListener {
Justin Klaassen2be4fdb2014-08-06 19:54:09 -070083
84 /**
85 * Constant for an invalid resource id.
86 */
87 public static final int INVALID_RES_ID = -1;
Justin Klaassen4b3af052014-05-27 17:53:10 -070088
89 private enum CalculatorState {
Hans Boehm84614952014-11-25 18:46:17 -080090 INPUT, // Result and formula both visible, no evaluation requested,
91 // Though result may be visible on bottom line.
92 EVALUATE, // Both visible, evaluation requested, evaluation/animation incomplete.
93 INIT, // Very temporary state used as alternative to EVALUATE
94 // during reinitialization. Do not animate on completion.
95 ANIMATE, // Result computed, animation to enlarge result window in progress.
96 RESULT, // Result displayed, formula invisible.
97 // If we are in RESULT state, the formula was evaluated without
98 // error to initial precision.
99 ERROR // Error displayed: Formula visible, result shows error message.
100 // Display similar to INPUT state.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700101 }
Hans Boehm84614952014-11-25 18:46:17 -0800102 // Normal transition sequence is
103 // INPUT -> EVALUATE -> ANIMATE -> RESULT (or ERROR) -> INPUT
104 // A RESULT -> ERROR transition is possible in rare corner cases, in which
105 // a higher precision evaluation exposes an error. This is possible, since we
106 // initially evaluate assuming we were given a well-defined problem. If we
107 // were actually asked to compute sqrt(<extremely tiny negative number>) we produce 0
108 // unless we are asked for enough precision that we can distinguish the argument from zero.
109 // TODO: Consider further heuristics to reduce the chance of observing this?
110 // It already seems to be observable only in contrived cases.
111 // ANIMATE, ERROR, and RESULT are translated to an INIT state if the application
112 // is restarted in that state. This leads us to recompute and redisplay the result
113 // ASAP.
114 // TODO: Possibly save a bit more information, e.g. its initial display string
115 // or most significant digit position, to speed up restart.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700116
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700117 // We currently assume that the formula does not change out from under us in
118 // any way. We explicitly handle all input to the formula here.
119 // TODO: Perhaps the formula should not be editable at all?
Justin Klaassen4b3af052014-05-27 17:53:10 -0700120
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700121 private final OnKeyListener mFormulaOnKeyListener = new OnKeyListener() {
122 @Override
123 public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700124 if (keyEvent.getAction() != KeyEvent.ACTION_UP) return true;
Hans Boehm1176f232015-05-11 16:26:03 -0700125 stopActionMode();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700126 switch (keyCode) {
127 case KeyEvent.KEYCODE_NUMPAD_ENTER:
128 case KeyEvent.KEYCODE_ENTER:
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700129 case KeyEvent.KEYCODE_DPAD_CENTER:
130 mCurrentButton = mEqualButton;
131 onEquals();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700132 return true;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700133 case KeyEvent.KEYCODE_DEL:
134 mCurrentButton = mDeleteButton;
135 onDelete();
136 return true;
137 default:
138 final int raw = keyEvent.getKeyCharacterMap()
139 .get(keyCode, keyEvent.getMetaState());
140 if ((raw & KeyCharacterMap.COMBINING_ACCENT) != 0) {
141 return true; // discard
142 }
143 // Try to discard non-printing characters and the like.
144 // The user will have to explicitly delete other junk that gets past us.
145 if (Character.isIdentifierIgnorable(raw)
146 || Character.isWhitespace(raw)) {
147 return true;
148 }
149 char c = (char)raw;
150 if (c == '=') {
Hans Boehme57fb012015-05-07 19:52:32 -0700151 mCurrentButton = mEqualButton;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700152 onEquals();
153 } else {
154 addChars(String.valueOf(c));
155 redisplayAfterFormulaChange();
156 }
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700157 }
158 return false;
159 }
160 };
161
Hans Boehm84614952014-11-25 18:46:17 -0800162 private static final String NAME = Calculator.class.getName();
163 private static final String KEY_DISPLAY_STATE = NAME + "_display_state";
Hans Boehm760a9dc2015-04-20 10:27:12 -0700164 private static final String KEY_UNPROCESSED_CHARS = NAME + "_unprocessed_chars";
Hans Boehm84614952014-11-25 18:46:17 -0800165 private static final String KEY_EVAL_STATE = NAME + "_eval_state";
166 // Associated value is a byte array holding both mCalculatorState
167 // and the (much more complex) evaluator state.
Justin Klaassen741471e2014-06-11 09:43:44 -0700168
Justin Klaassen4b3af052014-05-27 17:53:10 -0700169 private CalculatorState mCurrentState;
Hans Boehm84614952014-11-25 18:46:17 -0800170 private Evaluator mEvaluator;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700171
Justin Klaassen06360f92014-08-28 11:08:44 -0700172 private View mDisplayView;
Justin Klaassend48b7562015-04-16 16:51:38 -0700173 private TextView mModeView;
Hans Boehm08e8f322015-04-21 13:18:38 -0700174 private CalculatorText mFormulaText;
Hans Boehm84614952014-11-25 18:46:17 -0800175 private CalculatorResult mResult;
Justin Klaassend48b7562015-04-16 16:51:38 -0700176
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100177 private ViewPager mPadViewPager;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700178 private View mDeleteButton;
179 private View mClearButton;
Justin Klaassend48b7562015-04-16 16:51:38 -0700180 private View mEqualButton;
181 private TextView mModeButton;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700182
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700183 private View mCurrentButton;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700184 private Animator mCurrentAnimator;
185
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700186 private String mUnprocessedChars = null; // Characters that were recently entered
187 // at the end of the display that have not yet
188 // been added to the underlying expression.
189
Justin Klaassen4b3af052014-05-27 17:53:10 -0700190 @Override
191 protected void onCreate(Bundle savedInstanceState) {
192 super.onCreate(savedInstanceState);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700193 setContentView(R.layout.activity_calculator);
Justin Klaassend48b7562015-04-16 16:51:38 -0700194 setActionBar((Toolbar) findViewById(R.id.toolbar));
195
196 // Hide all default options in the ActionBar.
197 getActionBar().setDisplayOptions(0);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700198
Justin Klaassen06360f92014-08-28 11:08:44 -0700199 mDisplayView = findViewById(R.id.display);
Justin Klaassend48b7562015-04-16 16:51:38 -0700200 mModeView = (TextView) findViewById(R.id.deg_rad);
Hans Boehm08e8f322015-04-21 13:18:38 -0700201 mFormulaText = (CalculatorText) findViewById(R.id.formula);
Hans Boehm84614952014-11-25 18:46:17 -0800202 mResult = (CalculatorResult) findViewById(R.id.result);
Justin Klaassend48b7562015-04-16 16:51:38 -0700203
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100204 mPadViewPager = (ViewPager) findViewById(R.id.pad_pager);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700205 mDeleteButton = findViewById(R.id.del);
206 mClearButton = findViewById(R.id.clr);
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700207 mEqualButton = findViewById(R.id.pad_numeric).findViewById(R.id.eq);
208 if (mEqualButton == null || mEqualButton.getVisibility() != View.VISIBLE) {
209 mEqualButton = findViewById(R.id.pad_operator).findViewById(R.id.eq);
210 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700211 mModeButton = (TextView) findViewById(R.id.mode_deg_rad);
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700212
Hans Boehm84614952014-11-25 18:46:17 -0800213 mEvaluator = new Evaluator(this, mResult);
214 mResult.setEvaluator(mEvaluator);
Hans Boehm013969e2015-04-13 20:29:47 -0700215 KeyMaps.setActivity(this);
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700216
Hans Boehm84614952014-11-25 18:46:17 -0800217 if (savedInstanceState != null) {
218 setState(CalculatorState.values()[
219 savedInstanceState.getInt(KEY_DISPLAY_STATE,
220 CalculatorState.INPUT.ordinal())]);
Hans Boehm760a9dc2015-04-20 10:27:12 -0700221 CharSequence unprocessed = savedInstanceState.getCharSequence(KEY_UNPROCESSED_CHARS);
222 if (unprocessed != null) {
223 mUnprocessedChars = unprocessed.toString();
224 }
225 byte[] state = savedInstanceState.getByteArray(KEY_EVAL_STATE);
Hans Boehm84614952014-11-25 18:46:17 -0800226 if (state != null) {
227 try (ObjectInput in = new ObjectInputStream(new ByteArrayInputStream(state))) {
228 mEvaluator.restoreInstanceState(in);
229 } catch (Throwable ignored) {
230 // When in doubt, revert to clean state
231 mCurrentState = CalculatorState.INPUT;
232 mEvaluator.clear();
233 }
234 }
Hans Boehmfbcef702015-04-27 18:07:47 -0700235 } else {
236 mCurrentState = CalculatorState.INPUT;
237 mEvaluator.clear();
Hans Boehm84614952014-11-25 18:46:17 -0800238 }
Hans Boehm08e8f322015-04-21 13:18:38 -0700239 mFormulaText.setOnKeyListener(mFormulaOnKeyListener);
240 mFormulaText.setOnTextSizeChangeListener(this);
241 mFormulaText.setPasteListener(this);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700242 mDeleteButton.setOnLongClickListener(this);
Hans Boehmbfe8c222015-04-02 16:26:07 -0700243 updateDegreeMode(mEvaluator.getDegreeMode());
Hans Boehm84614952014-11-25 18:46:17 -0800244 if (mCurrentState != CalculatorState.INPUT) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700245 // Just reevaluate.
246 redisplayFormula();
Hans Boehm84614952014-11-25 18:46:17 -0800247 setState(CalculatorState.INIT);
Hans Boehm84614952014-11-25 18:46:17 -0800248 mEvaluator.requireResult();
249 } else {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700250 redisplayAfterFormulaChange();
Hans Boehm84614952014-11-25 18:46:17 -0800251 }
252 // TODO: We're currently not saving and restoring scroll position.
253 // We probably should. Details may require care to deal with:
254 // - new display size
255 // - slow recomputation if we've scrolled far.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700256 }
257
258 @Override
Justin Klaassenf79d6f62014-08-26 12:27:08 -0700259 protected void onSaveInstanceState(@NonNull Bundle outState) {
260 // If there's an animation in progress, cancel it first to ensure our state is up-to-date.
261 if (mCurrentAnimator != null) {
262 mCurrentAnimator.cancel();
263 }
264
Justin Klaassen4b3af052014-05-27 17:53:10 -0700265 super.onSaveInstanceState(outState);
Hans Boehm84614952014-11-25 18:46:17 -0800266 outState.putInt(KEY_DISPLAY_STATE, mCurrentState.ordinal());
Hans Boehm760a9dc2015-04-20 10:27:12 -0700267 outState.putCharSequence(KEY_UNPROCESSED_CHARS, mUnprocessedChars);
Hans Boehm84614952014-11-25 18:46:17 -0800268 ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
269 try (ObjectOutput out = new ObjectOutputStream(byteArrayStream)) {
270 mEvaluator.saveInstanceState(out);
271 } catch (IOException e) {
272 // Impossible; No IO involved.
273 throw new AssertionError("Impossible IO exception", e);
274 }
275 outState.putByteArray(KEY_EVAL_STATE, byteArrayStream.toByteArray());
Justin Klaassen4b3af052014-05-27 17:53:10 -0700276 }
277
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700278 // Set the state, updating delete label and display colors.
279 // This restores display positions on moving to INPUT.
Justin Klaassend48b7562015-04-16 16:51:38 -0700280 // But movement/animation for moving to RESULT has already been done.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700281 private void setState(CalculatorState state) {
282 if (mCurrentState != state) {
Hans Boehm84614952014-11-25 18:46:17 -0800283 if (state == CalculatorState.INPUT) {
284 restoreDisplayPositions();
285 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700286 mCurrentState = state;
287
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700288 if (mCurrentState == CalculatorState.RESULT) {
289 // No longer do this for ERROR; allow mistakes to be corrected.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700290 mDeleteButton.setVisibility(View.GONE);
291 mClearButton.setVisibility(View.VISIBLE);
292 } else {
293 mDeleteButton.setVisibility(View.VISIBLE);
294 mClearButton.setVisibility(View.GONE);
295 }
296
Hans Boehm84614952014-11-25 18:46:17 -0800297 if (mCurrentState == CalculatorState.ERROR) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700298 final int errorColor = getResources().getColor(R.color.calculator_error_color);
Hans Boehm08e8f322015-04-21 13:18:38 -0700299 mFormulaText.setTextColor(errorColor);
Hans Boehm84614952014-11-25 18:46:17 -0800300 mResult.setTextColor(errorColor);
Justin Klaassen8fff1442014-06-19 10:43:29 -0700301 getWindow().setStatusBarColor(errorColor);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700302 } else {
Hans Boehm08e8f322015-04-21 13:18:38 -0700303 mFormulaText.setTextColor(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700304 getResources().getColor(R.color.display_formula_text_color));
Hans Boehm84614952014-11-25 18:46:17 -0800305 mResult.setTextColor(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700306 getResources().getColor(R.color.display_result_text_color));
Justin Klaassen8fff1442014-06-19 10:43:29 -0700307 getWindow().setStatusBarColor(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700308 getResources().getColor(R.color.calculator_accent_color));
309 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700310
311 invalidateOptionsMenu();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700312 }
313 }
314
Hans Boehm1176f232015-05-11 16:26:03 -0700315 // Stop any active ActionMode. Return true if there was one.
316 private boolean stopActionMode() {
317 if (mResult.stopActionMode()) {
318 return true;
319 }
320 if (mFormulaText.stopActionMode()) {
321 return true;
322 }
323 return false;
324 }
325
Justin Klaassen4b3af052014-05-27 17:53:10 -0700326 @Override
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100327 public void onBackPressed() {
Hans Boehm1176f232015-05-11 16:26:03 -0700328 if (!stopActionMode()) {
329 if (mPadViewPager != null && mPadViewPager.getCurrentItem() != 0) {
330 // Select the previous pad.
331 mPadViewPager.setCurrentItem(mPadViewPager.getCurrentItem() - 1);
332 } else {
333 // If the user is currently looking at the first pad (or the pad is not paged),
334 // allow the system to handle the Back button.
335 super.onBackPressed();
336 }
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100337 }
338 }
339
340 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700341 public void onUserInteraction() {
342 super.onUserInteraction();
343
344 // If there's an animation in progress, cancel it so the user interaction can be handled
345 // immediately.
346 if (mCurrentAnimator != null) {
347 mCurrentAnimator.cancel();
348 }
349 }
350
Hans Boehmbfe8c222015-04-02 16:26:07 -0700351 // Update the top corner degree/radian display and mode button
352 // to reflect the indicated current degree mode (true = degrees)
353 // TODO: Hide the top corner display until the advanced panel is exposed.
354 private void updateDegreeMode(boolean dm) {
Hans Boehmbfe8c222015-04-02 16:26:07 -0700355 if (dm) {
Justin Klaassend48b7562015-04-16 16:51:38 -0700356 mModeView.setText(R.string.mode_deg);
357 mModeButton.setText(R.string.mode_rad);
358 mModeButton.setContentDescription(getString(R.string.desc_mode_rad));
Hans Boehmbfe8c222015-04-02 16:26:07 -0700359 } else {
Justin Klaassend48b7562015-04-16 16:51:38 -0700360 mModeView.setText(R.string.mode_rad);
361 mModeButton.setText(R.string.mode_deg);
362 mModeButton.setContentDescription(getString(R.string.desc_mode_deg));
Hans Boehmbfe8c222015-04-02 16:26:07 -0700363 }
364 }
Hans Boehm84614952014-11-25 18:46:17 -0800365
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700366 // Add the given button id to input expression.
367 // If appropriate, clear the expression before doing so.
368 private void addKeyToExpr(int id) {
369 if (mCurrentState == CalculatorState.ERROR) {
370 setState(CalculatorState.INPUT);
371 } else if (mCurrentState == CalculatorState.RESULT) {
372 if (KeyMaps.isBinary(id) || KeyMaps.isSuffix(id)) {
373 mEvaluator.collapse();
374 } else {
375 mEvaluator.clear();
376 }
377 setState(CalculatorState.INPUT);
378 }
379 if (!mEvaluator.append(id)) {
380 // TODO: Some user visible feedback?
381 }
382 }
383
384 private void redisplayAfterFormulaChange() {
385 // TODO: Could do this more incrementally.
386 redisplayFormula();
387 setState(CalculatorState.INPUT);
Hans Boehmc023b732015-04-29 11:30:47 -0700388 if (mEvaluator.getExpr().hasInterestingOps()) {
389 mEvaluator.evaluateAndShowResult();
390 } else {
391 mResult.clear();
392 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700393 }
394
Justin Klaassen4b3af052014-05-27 17:53:10 -0700395 public void onButtonClick(View view) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700396 mCurrentButton = view;
Hans Boehm1176f232015-05-11 16:26:03 -0700397 stopActionMode();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700398
Hans Boehm84614952014-11-25 18:46:17 -0800399 // Always cancel in-progress evaluation.
400 // If we were waiting for the result, do nothing else.
401 mEvaluator.cancelAll();
Justin Klaassend48b7562015-04-16 16:51:38 -0700402
Hans Boehm84614952014-11-25 18:46:17 -0800403 if (mCurrentState == CalculatorState.EVALUATE
404 || mCurrentState == CalculatorState.ANIMATE) {
405 onCancelled();
406 return;
407 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700408
Justin Klaassend48b7562015-04-16 16:51:38 -0700409 final int id = view.getId();
Hans Boehm84614952014-11-25 18:46:17 -0800410 switch (id) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700411 case R.id.eq:
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700412 onEquals();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700413 break;
414 case R.id.del:
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700415 onDelete();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700416 break;
417 case R.id.clr:
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700418 onClear();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700419 break;
Hans Boehmbfe8c222015-04-02 16:26:07 -0700420 case R.id.mode_deg_rad:
421 boolean mode = !mEvaluator.getDegreeMode();
422 updateDegreeMode(mode);
423 if (mCurrentState == CalculatorState.RESULT) {
424 mEvaluator.collapse(); // Capture result evaluated in old mode
425 redisplayFormula();
426 }
427 // In input mode, we reinterpret already entered trig functions.
428 mEvaluator.setDegreeMode(mode);
429 setState(CalculatorState.INPUT);
430 mResult.clear();
Hans Boehmc023b732015-04-29 11:30:47 -0700431 if (mEvaluator.getExpr().hasInterestingOps()) {
432 mEvaluator.evaluateAndShowResult();
433 }
Hans Boehmbfe8c222015-04-02 16:26:07 -0700434 break;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700435 default:
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700436 addKeyToExpr(id);
437 redisplayAfterFormulaChange();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700438 break;
439 }
440 }
441
Hans Boehm84614952014-11-25 18:46:17 -0800442 void redisplayFormula() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700443 String formula = mEvaluator.getExpr().toString(this);
444 if (mUnprocessedChars != null) {
445 // Add and highlight characters we couldn't process.
446 SpannableString formatted = new SpannableString(formula + mUnprocessedChars);
447 // TODO: should probably match this to the error color.
448 formatted.setSpan(new ForegroundColorSpan(Color.RED),
449 formula.length(), formatted.length(),
450 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Hans Boehm08e8f322015-04-21 13:18:38 -0700451 mFormulaText.setText(formatted);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700452 } else {
Hans Boehm08e8f322015-04-21 13:18:38 -0700453 mFormulaText.setText(formula);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700454 }
Hans Boehm84614952014-11-25 18:46:17 -0800455 }
456
Justin Klaassen4b3af052014-05-27 17:53:10 -0700457 @Override
458 public boolean onLongClick(View view) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700459 mCurrentButton = view;
460
Justin Klaassen4b3af052014-05-27 17:53:10 -0700461 if (view.getId() == R.id.del) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700462 onClear();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700463 return true;
464 }
465 return false;
466 }
467
Hans Boehm84614952014-11-25 18:46:17 -0800468 // Initial evaluation completed successfully. Initiate display.
469 public void onEvaluate(int initDisplayPrec, String truncatedWholeNumber) {
Justin Klaassend48b7562015-04-16 16:51:38 -0700470 // Invalidate any options that may depend on the current result.
471 invalidateOptionsMenu();
472
Justin Klaassen4b3af052014-05-27 17:53:10 -0700473 if (mCurrentState == CalculatorState.INPUT) {
Hans Boehm84614952014-11-25 18:46:17 -0800474 // Just update small result display.
475 mResult.displayResult(initDisplayPrec, truncatedWholeNumber);
476 } else { // in EVALUATE or INIT state
477 mResult.displayResult(initDisplayPrec, truncatedWholeNumber);
478 onResult(mCurrentState != CalculatorState.INIT);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700479 }
Hans Boehm84614952014-11-25 18:46:17 -0800480 }
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700481
Hans Boehm84614952014-11-25 18:46:17 -0800482 public void onCancelled() {
483 // We should be in EVALUATE state.
484 // Display is still in input state.
485 setState(CalculatorState.INPUT);
Hans Boehmc023b732015-04-29 11:30:47 -0700486 mResult.clear();
Hans Boehm84614952014-11-25 18:46:17 -0800487 }
488
489 // Reevaluation completed; ask result to redisplay current value.
490 public void onReevaluate()
491 {
492 mResult.redisplay();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700493 }
494
Justin Klaassenfed941a2014-06-09 18:42:40 +0100495 @Override
496 public void onTextSizeChanged(final TextView textView, float oldSize) {
497 if (mCurrentState != CalculatorState.INPUT) {
498 // Only animate text changes that occur from user input.
499 return;
500 }
501
502 // Calculate the values needed to perform the scale and translation animations,
503 // maintaining the same apparent baseline for the displayed text.
504 final float textScale = oldSize / textView.getTextSize();
505 final float translationX = (1.0f - textScale) *
506 (textView.getWidth() / 2.0f - textView.getPaddingEnd());
507 final float translationY = (1.0f - textScale) *
508 (textView.getHeight() / 2.0f - textView.getPaddingBottom());
509
510 final AnimatorSet animatorSet = new AnimatorSet();
511 animatorSet.playTogether(
512 ObjectAnimator.ofFloat(textView, View.SCALE_X, textScale, 1.0f),
513 ObjectAnimator.ofFloat(textView, View.SCALE_Y, textScale, 1.0f),
514 ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, translationX, 0.0f),
515 ObjectAnimator.ofFloat(textView, View.TRANSLATION_Y, translationY, 0.0f));
Justin Klaassen94db7202014-06-11 11:22:31 -0700516 animatorSet.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
Justin Klaassenfed941a2014-06-09 18:42:40 +0100517 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
518 animatorSet.start();
519 }
520
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700521 private void onEquals() {
Hans Boehmc023b732015-04-29 11:30:47 -0700522 if (mCurrentState == CalculatorState.INPUT && !mEvaluator.getExpr().isEmpty()) {
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700523 setState(CalculatorState.EVALUATE);
Hans Boehm84614952014-11-25 18:46:17 -0800524 mEvaluator.requireResult();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700525 }
526 }
527
528 private void onDelete() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700529 // Delete works like backspace; remove the last character or operator from the expression.
530 // Note that we handle keyboard delete exactly like the delete button. For
531 // example the delete button can be used to delete a character from an incomplete
532 // function name typed on a physical keyboard.
Hans Boehm84614952014-11-25 18:46:17 -0800533 mEvaluator.cancelAll();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700534 // This should be impossible in RESULT state.
535 setState(CalculatorState.INPUT);
536 if (mUnprocessedChars != null) {
537 int len = mUnprocessedChars.length();
538 if (len > 0) {
539 mUnprocessedChars = mUnprocessedChars.substring(0, len-1);
540 } else {
Hans Boehmc023b732015-04-29 11:30:47 -0700541 mEvaluator.delete();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700542 }
543 } else {
Hans Boehmc023b732015-04-29 11:30:47 -0700544 mEvaluator.delete();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700545 }
546 redisplayAfterFormulaChange();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700547 }
548
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700549 private void reveal(View sourceView, int colorRes, AnimatorListener listener) {
Justin Klaassen06360f92014-08-28 11:08:44 -0700550 final ViewGroupOverlay groupOverlay =
551 (ViewGroupOverlay) getWindow().getDecorView().getOverlay();
Justin Klaassen8fff1442014-06-19 10:43:29 -0700552
553 final Rect displayRect = new Rect();
Justin Klaassen06360f92014-08-28 11:08:44 -0700554 mDisplayView.getGlobalVisibleRect(displayRect);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700555
556 // Make reveal cover the display and status bar.
557 final View revealView = new View(this);
Justin Klaassen8fff1442014-06-19 10:43:29 -0700558 revealView.setBottom(displayRect.bottom);
559 revealView.setLeft(displayRect.left);
560 revealView.setRight(displayRect.right);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700561 revealView.setBackgroundColor(getResources().getColor(colorRes));
Justin Klaassen06360f92014-08-28 11:08:44 -0700562 groupOverlay.add(revealView);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700563
Justin Klaassen4b3af052014-05-27 17:53:10 -0700564 final int[] clearLocation = new int[2];
565 sourceView.getLocationInWindow(clearLocation);
566 clearLocation[0] += sourceView.getWidth() / 2;
567 clearLocation[1] += sourceView.getHeight() / 2;
568
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700569 final int revealCenterX = clearLocation[0] - revealView.getLeft();
570 final int revealCenterY = clearLocation[1] - revealView.getTop();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700571
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700572 final double x1_2 = Math.pow(revealView.getLeft() - revealCenterX, 2);
573 final double x2_2 = Math.pow(revealView.getRight() - revealCenterX, 2);
574 final double y_2 = Math.pow(revealView.getTop() - revealCenterY, 2);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700575 final float revealRadius = (float) Math.max(Math.sqrt(x1_2 + y_2), Math.sqrt(x2_2 + y_2));
576
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700577 final Animator revealAnimator =
578 ViewAnimationUtils.createCircularReveal(revealView,
ztenghui3d6ecaf2014-06-05 09:56:00 -0700579 revealCenterX, revealCenterY, 0.0f, revealRadius);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700580 revealAnimator.setDuration(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700581 getResources().getInteger(android.R.integer.config_longAnimTime));
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700582 revealAnimator.addListener(listener);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700583
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700584 final Animator alphaAnimator = ObjectAnimator.ofFloat(revealView, View.ALPHA, 0.0f);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700585 alphaAnimator.setDuration(
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700586 getResources().getInteger(android.R.integer.config_mediumAnimTime));
Justin Klaassen4b3af052014-05-27 17:53:10 -0700587
588 final AnimatorSet animatorSet = new AnimatorSet();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700589 animatorSet.play(revealAnimator).before(alphaAnimator);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700590 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
591 animatorSet.addListener(new AnimatorListenerAdapter() {
592 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700593 public void onAnimationEnd(Animator animator) {
Justin Klaassen8fff1442014-06-19 10:43:29 -0700594 groupOverlay.remove(revealView);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700595 mCurrentAnimator = null;
596 }
597 });
598
599 mCurrentAnimator = animatorSet;
600 animatorSet.start();
601 }
602
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700603 private void onClear() {
Hans Boehm84614952014-11-25 18:46:17 -0800604 if (mEvaluator.getExpr().isEmpty()) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700605 return;
606 }
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700607 reveal(mCurrentButton, R.color.calculator_accent_color, new AnimatorListenerAdapter() {
608 @Override
609 public void onAnimationEnd(Animator animation) {
Hans Boehm760a9dc2015-04-20 10:27:12 -0700610 mUnprocessedChars = null;
611 mResult.clear();
612 mEvaluator.clear();
613 setState(CalculatorState.INPUT);
Hans Boehm84614952014-11-25 18:46:17 -0800614 redisplayFormula();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700615 }
616 });
617 }
618
Hans Boehm84614952014-11-25 18:46:17 -0800619 // Evaluation encountered en error. Display the error.
620 void onError(final int errorResourceId) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700621 if (mCurrentState == CalculatorState.EVALUATE) {
622 setState(CalculatorState.ANIMATE);
623 reveal(mCurrentButton, R.color.calculator_error_color,
624 new AnimatorListenerAdapter() {
625 @Override
626 public void onAnimationEnd(Animator animation) {
627 setState(CalculatorState.ERROR);
628 mResult.displayError(errorResourceId);
629 }
630 });
631 } else if (mCurrentState == CalculatorState.INIT) {
632 setState(CalculatorState.ERROR);
633 mResult.displayError(errorResourceId);
Hans Boehmc023b732015-04-29 11:30:47 -0700634 } else {
635 mResult.clear();
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700636 }
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700637 }
638
Hans Boehm84614952014-11-25 18:46:17 -0800639
640 // Animate movement of result into the top formula slot.
641 // Result window now remains translated in the top slot while the result is displayed.
642 // (We convert it back to formula use only when the user provides new input.)
643 // Historical note: In the Lollipop version, this invisibly and instantaeously moved
644 // formula and result displays back at the end of the animation. We no longer do that,
645 // so that we can continue to properly support scrolling of the result.
646 // We assume the result already contains the text to be expanded.
647 private void onResult(boolean animate) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700648 // Calculate the values needed to perform the scale and translation animations.
649 // We now fix the character size in the display to avoid weird effects
Hans Boehm84614952014-11-25 18:46:17 -0800650 // when we scroll.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700651 // Display.xml is designed to ensure exactly a 3/2 ratio between the formula
652 // slot and small result slot.
653 final float resultScale = 1.5f;
654 final float resultTranslationX = -mResult.getWidth() * (resultScale - 1)/2;
Hans Boehm08e8f322015-04-21 13:18:38 -0700655 // mFormulaText is aligned with mResult on the right.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700656 // When we enlarge it around its center, the right side
657 // moves to the right. This compensates.
658 float resultTranslationY = -mResult.getHeight();
659 // This is how much we want to move the bottom.
660 // Now compensate for the fact that we're
661 // simultaenously expanding it around its center by half its height
Hans Boehm760a9dc2015-04-20 10:27:12 -0700662 resultTranslationY += mResult.getHeight() * (resultScale - 1)/2;
Hans Boehm08e8f322015-04-21 13:18:38 -0700663 final float formulaTranslationY = -mFormulaText.getBottom();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700664
Hans Boehm84614952014-11-25 18:46:17 -0800665 // TODO: Reintroduce textColorAnimator?
666 // The initial and final colors seemed to be the same in L.
667 // With the new model, the result logically changes back to a formula
668 // only when we switch back to INPUT state, so it's unclear that animating
669 // a color change here makes sense.
670 if (animate) {
671 final AnimatorSet animatorSet = new AnimatorSet();
672 animatorSet.playTogether(
673 ObjectAnimator.ofFloat(mResult, View.SCALE_X, resultScale),
674 ObjectAnimator.ofFloat(mResult, View.SCALE_Y, resultScale),
675 ObjectAnimator.ofFloat(mResult, View.TRANSLATION_X, resultTranslationX),
676 ObjectAnimator.ofFloat(mResult, View.TRANSLATION_Y, resultTranslationY),
Hans Boehm08e8f322015-04-21 13:18:38 -0700677 ObjectAnimator.ofFloat(mFormulaText, View.TRANSLATION_Y,
Hans Boehm84614952014-11-25 18:46:17 -0800678 formulaTranslationY));
679 animatorSet.setDuration(
680 getResources().getInteger(android.R.integer.config_longAnimTime));
681 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
682 animatorSet.addListener(new AnimatorListenerAdapter() {
683 @Override
684 public void onAnimationStart(Animator animation) {
685 // Result should already be displayed; no need to do anything.
686 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700687
Hans Boehm84614952014-11-25 18:46:17 -0800688 @Override
689 public void onAnimationEnd(Animator animation) {
690 setState(CalculatorState.RESULT);
691 mCurrentAnimator = null;
692 }
693 });
Justin Klaassen4b3af052014-05-27 17:53:10 -0700694
Hans Boehm84614952014-11-25 18:46:17 -0800695 mCurrentAnimator = animatorSet;
696 animatorSet.start();
697 } else /* No animation desired; get there fast, e.g. when restarting */ {
698 mResult.setScaleX(resultScale);
699 mResult.setScaleY(resultScale);
700 mResult.setTranslationX(resultTranslationX);
701 mResult.setTranslationY(resultTranslationY);
Hans Boehm08e8f322015-04-21 13:18:38 -0700702 mFormulaText.setTranslationY(formulaTranslationY);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700703 setState(CalculatorState.RESULT);
Hans Boehm84614952014-11-25 18:46:17 -0800704 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700705 }
Hans Boehm84614952014-11-25 18:46:17 -0800706
707 // Restore positions of the formula and result displays back to their original,
708 // pre-animation state.
709 private void restoreDisplayPositions() {
710 // Clear result.
711 mResult.setText("");
712 // Reset all of the values modified during the animation.
713 mResult.setScaleX(1.0f);
714 mResult.setScaleY(1.0f);
715 mResult.setTranslationX(0.0f);
716 mResult.setTranslationY(0.0f);
Hans Boehm08e8f322015-04-21 13:18:38 -0700717 mFormulaText.setTranslationY(0.0f);
Hans Boehm84614952014-11-25 18:46:17 -0800718
Hans Boehm08e8f322015-04-21 13:18:38 -0700719 mFormulaText.requestFocus();
Hans Boehm84614952014-11-25 18:46:17 -0800720 }
721
Justin Klaassend48b7562015-04-16 16:51:38 -0700722 @Override
723 public boolean onCreateOptionsMenu(Menu menu) {
Justin Klaassend36d63e2015-05-05 12:59:36 -0700724 super.onCreateOptionsMenu(menu);
725
726 getMenuInflater().inflate(R.menu.activity_calculator, menu);
Justin Klaassend48b7562015-04-16 16:51:38 -0700727 return true;
728 }
729
730 @Override
731 public boolean onPrepareOptionsMenu(Menu menu) {
Justin Klaassend36d63e2015-05-05 12:59:36 -0700732 super.onPrepareOptionsMenu(menu);
733
734 // Show the leading option when displaying a result.
735 menu.findItem(R.id.menu_leading).setVisible(mCurrentState == CalculatorState.RESULT);
736
737 // Show the fraction option when displaying a rational result.
738 menu.findItem(R.id.menu_fraction).setVisible(mCurrentState == CalculatorState.RESULT
739 && mEvaluator.getRational() != null);
740
Justin Klaassend48b7562015-04-16 16:51:38 -0700741 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800742 }
743
744 @Override
Justin Klaassend48b7562015-04-16 16:51:38 -0700745 public boolean onOptionsItemSelected(MenuItem item) {
Hans Boehm84614952014-11-25 18:46:17 -0800746 switch (item.getItemId()) {
Justin Klaassend36d63e2015-05-05 12:59:36 -0700747 case R.id.menu_leading:
748 displayFull();
Hans Boehm84614952014-11-25 18:46:17 -0800749 return true;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700750 case R.id.menu_fraction:
751 displayFraction();
752 return true;
Justin Klaassend36d63e2015-05-05 12:59:36 -0700753 case R.id.menu_licenses:
754 startActivity(new Intent(this, Licenses.class));
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700755 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800756 default:
757 return super.onOptionsItemSelected(item);
758 }
759 }
760
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700761 private void displayMessage(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800762 AlertDialog.Builder builder = new AlertDialog.Builder(this);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700763 builder.setMessage(s)
764 .setNegativeButton(R.string.dismiss,
Hans Boehm84614952014-11-25 18:46:17 -0800765 new DialogInterface.OnClickListener() {
766 public void onClick(DialogInterface d, int which) { }
767 })
768 .show();
769 }
770
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700771 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 Boehm4a6b7cb2015-04-03 18:41:52 -0700788 // Add input characters to the end of the expression by mapping them to
789 // the appropriate button pushes when possible. Leftover characters
790 // are added to mUnprocessedChars, which is presumed to immediately
791 // precede the newly added characters.
792 private void addChars(String moreChars) {
793 if (mUnprocessedChars != null) {
794 moreChars = mUnprocessedChars + moreChars;
795 }
796 int current = 0;
797 int len = moreChars.length();
798 while (current < len) {
799 char c = moreChars.charAt(current);
Hans Boehm013969e2015-04-13 20:29:47 -0700800 int k = KeyMaps.keyForChar(c);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700801 if (k != View.NO_ID) {
802 mCurrentButton = findViewById(k);
803 addKeyToExpr(k);
804 if (Character.isSurrogate(c)) {
805 current += 2;
806 } else {
807 ++current;
808 }
809 continue;
810 }
Hans Boehm013969e2015-04-13 20:29:47 -0700811 int f = KeyMaps.funForString(moreChars, current);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700812 if (f != View.NO_ID) {
813 mCurrentButton = findViewById(f);
814 addKeyToExpr(f);
815 if (f == R.id.op_sqrt) {
816 // Square root entered as function; don't lose the parenthesis.
817 addKeyToExpr(R.id.lparen);
818 }
819 current = moreChars.indexOf('(', current) + 1;
820 continue;
821 }
822 // There are characters left, but we can't convert them to button presses.
823 mUnprocessedChars = moreChars.substring(current);
824 redisplayAfterFormulaChange();
825 return;
826 }
827 mUnprocessedChars = null;
828 redisplayAfterFormulaChange();
829 return;
Hans Boehm84614952014-11-25 18:46:17 -0800830 }
831
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700832 @Override
833 public boolean paste(Uri uri) {
834 if (mEvaluator.isLastSaved(uri)) {
835 if (mCurrentState == CalculatorState.ERROR
836 || mCurrentState == CalculatorState.RESULT) {
837 setState(CalculatorState.INPUT);
838 mEvaluator.clear();
Hans Boehm84614952014-11-25 18:46:17 -0800839 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700840 mEvaluator.addSaved();
841 redisplayAfterFormulaChange();
842 return true;
Hans Boehm84614952014-11-25 18:46:17 -0800843 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700844 return false;
Hans Boehm84614952014-11-25 18:46:17 -0800845 }
846
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700847 @Override
848 public void paste(String s) {
849 addChars(s);
Hans Boehm84614952014-11-25 18:46:17 -0800850 }
851
Justin Klaassen4b3af052014-05-27 17:53:10 -0700852}