14b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen/* 24459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen * Copyright (C) 2015 The Android Open Source Project 34b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * 44b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * Licensed under the Apache License, Version 2.0 (the "License"); 54b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * you may not use this file except in compliance with the License. 64b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * You may obtain a copy of the License at 74b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * 84b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * http://www.apache.org/licenses/LICENSE-2.0 94b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * 104b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * Unless required by applicable law or agreed to in writing, software 114b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * distributed under the License is distributed on an "AS IS" BASIS, 124b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 134b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * See the License for the specific language governing permissions and 144b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * limitations under the License. 154b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen */ 164b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 17013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm// TODO: Copy & more general paste in formula? Note that this requires 18013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm// great care: Currently the text version of a displayed formula 19013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm// is not directly useful for re-evaluating the formula later, since 20013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm// it contains ellipses representing subexpressions evaluated with 21013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm// a different degree mode. Rather than supporting copy from the 22013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm// formula window, we may eventually want to support generation of a 23013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm// more useful text version in a separate window. It's not clear 24013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm// this is worth the added (code and user) complexity. 2584614957604253d51296e06c97daced699a0a9deHans Boehm 264b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenpackage com.android.calculator2; 274b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 284b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.animation.Animator; 295f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassenimport android.animation.Animator.AnimatorListener; 304b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.animation.AnimatorListenerAdapter; 314b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.animation.AnimatorSet; 324b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.animation.ObjectAnimator; 334459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassenimport android.animation.PropertyValuesHolder; 344b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.app.Activity; 359192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehmimport android.app.AlertDialog; 36fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassenimport android.content.ClipData; 379192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehmimport android.content.DialogInterface; 38d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassenimport android.content.Intent; 39bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehmimport android.content.res.Resources; 404a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.graphics.Color; 418fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassenimport android.graphics.Rect; 424a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.net.Uri; 434b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.os.Bundle; 44f79d6f699b04a735e1627b47a059760ff40c26b9Justin Klaassenimport android.support.annotation.NonNull; 453b4d13d528a14a0f46e7ae022357dfde90a307adJustin Klaassenimport android.support.v4.view.ViewPager; 464a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.text.SpannableString; 478a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehmimport android.text.SpannableStringBuilder; 484a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.text.Spanned; 494a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.text.style.ForegroundColorSpan; 508a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehmimport android.text.TextUtils; 514459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassenimport android.util.Property; 524a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.view.KeyCharacterMap; 53ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoroimport android.view.KeyEvent; 5484614957604253d51296e06c97daced699a0a9deHans Boehmimport android.view.Menu; 5584614957604253d51296e06c97daced699a0a9deHans Boehmimport android.view.MenuItem; 564b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.view.View; 57ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoroimport android.view.View.OnKeyListener; 584b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.view.View.OnLongClickListener; 595f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassenimport android.view.ViewAnimationUtils; 608fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassenimport android.view.ViewGroupOverlay; 614b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.view.animation.AccelerateDecelerateInterpolator; 62fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassenimport android.widget.TextView; 63d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassenimport android.widget.Toolbar; 64fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen 6508e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehmimport com.android.calculator2.CalculatorText.OnTextSizeChangeListener; 664b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 6784614957604253d51296e06c97daced699a0a9deHans Boehmimport java.io.ByteArrayInputStream; 6884614957604253d51296e06c97daced699a0a9deHans Boehmimport java.io.ByteArrayOutputStream; 69721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassenimport java.io.IOException; 7084614957604253d51296e06c97daced699a0a9deHans Boehmimport java.io.ObjectInput; 71721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassenimport java.io.ObjectInputStream; 7284614957604253d51296e06c97daced699a0a9deHans Boehmimport java.io.ObjectOutput; 73721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassenimport java.io.ObjectOutputStream; 742be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen 7584614957604253d51296e06c97daced699a0a9deHans Boehmpublic class Calculator extends Activity 769192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm implements OnTextSizeChangeListener, OnLongClickListener, CalculatorText.OnPasteListener, 779192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm AlertDialogFragment.OnClickListener { 782be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen 792be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen /** 802be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen * Constant for an invalid resource id. 812be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen */ 822be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen public static final int INVALID_RES_ID = -1; 834b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 844b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen private enum CalculatorState { 8584614957604253d51296e06c97daced699a0a9deHans Boehm INPUT, // Result and formula both visible, no evaluation requested, 8684614957604253d51296e06c97daced699a0a9deHans Boehm // Though result may be visible on bottom line. 8784614957604253d51296e06c97daced699a0a9deHans Boehm EVALUATE, // Both visible, evaluation requested, evaluation/animation incomplete. 88c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm // Not used for instant result evaluation. 8984614957604253d51296e06c97daced699a0a9deHans Boehm INIT, // Very temporary state used as alternative to EVALUATE 9084614957604253d51296e06c97daced699a0a9deHans Boehm // during reinitialization. Do not animate on completion. 9184614957604253d51296e06c97daced699a0a9deHans Boehm ANIMATE, // Result computed, animation to enlarge result window in progress. 9284614957604253d51296e06c97daced699a0a9deHans Boehm RESULT, // Result displayed, formula invisible. 9384614957604253d51296e06c97daced699a0a9deHans Boehm // If we are in RESULT state, the formula was evaluated without 9484614957604253d51296e06c97daced699a0a9deHans Boehm // error to initial precision. 9584614957604253d51296e06c97daced699a0a9deHans Boehm ERROR // Error displayed: Formula visible, result shows error message. 9684614957604253d51296e06c97daced699a0a9deHans Boehm // Display similar to INPUT state. 974b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 9884614957604253d51296e06c97daced699a0a9deHans Boehm // Normal transition sequence is 9984614957604253d51296e06c97daced699a0a9deHans Boehm // INPUT -> EVALUATE -> ANIMATE -> RESULT (or ERROR) -> INPUT 10084614957604253d51296e06c97daced699a0a9deHans Boehm // A RESULT -> ERROR transition is possible in rare corner cases, in which 10184614957604253d51296e06c97daced699a0a9deHans Boehm // a higher precision evaluation exposes an error. This is possible, since we 10284614957604253d51296e06c97daced699a0a9deHans Boehm // initially evaluate assuming we were given a well-defined problem. If we 10384614957604253d51296e06c97daced699a0a9deHans Boehm // were actually asked to compute sqrt(<extremely tiny negative number>) we produce 0 10484614957604253d51296e06c97daced699a0a9deHans Boehm // unless we are asked for enough precision that we can distinguish the argument from zero. 10584614957604253d51296e06c97daced699a0a9deHans Boehm // TODO: Consider further heuristics to reduce the chance of observing this? 10684614957604253d51296e06c97daced699a0a9deHans Boehm // It already seems to be observable only in contrived cases. 10784614957604253d51296e06c97daced699a0a9deHans Boehm // ANIMATE, ERROR, and RESULT are translated to an INIT state if the application 10884614957604253d51296e06c97daced699a0a9deHans Boehm // is restarted in that state. This leads us to recompute and redisplay the result 10984614957604253d51296e06c97daced699a0a9deHans Boehm // ASAP. 11084614957604253d51296e06c97daced699a0a9deHans Boehm // TODO: Possibly save a bit more information, e.g. its initial display string 11184614957604253d51296e06c97daced699a0a9deHans Boehm // or most significant digit position, to speed up restart. 1124b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 1134459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen private final Property<TextView, Integer> TEXT_COLOR = 1144459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen new Property<TextView, Integer>(Integer.class, "textColor") { 1154459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen @Override 1164459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen public Integer get(TextView textView) { 1174459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen return textView.getCurrentTextColor(); 1184459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen } 1194459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen 1204459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen @Override 1214459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen public void set(TextView textView, Integer textColor) { 1224459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen textView.setTextColor(textColor); 1234459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen } 1244459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen }; 1254459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen 1264a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // We currently assume that the formula does not change out from under us in 1274a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // any way. We explicitly handle all input to the formula here. 128ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro private final OnKeyListener mFormulaOnKeyListener = new OnKeyListener() { 129ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro @Override 130ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro public boolean onKey(View view, int keyCode, KeyEvent keyEvent) { 1311176f23dae4d3740782e46463003e9f36a381c9dHans Boehm stopActionMode(); 13206c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen // Never consume DPAD key events. 13306c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen switch (keyCode) { 13406c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen case KeyEvent.KEYCODE_DPAD_UP: 13506c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen case KeyEvent.KEYCODE_DPAD_DOWN: 13606c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen case KeyEvent.KEYCODE_DPAD_LEFT: 13706c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen case KeyEvent.KEYCODE_DPAD_RIGHT: 13806c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen return false; 13906c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen } 140c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm // Always cancel unrequested in-progress evaluation, so that we don't have 141c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm // to worry about subsequent asynchronous completion. 142c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm // Requested in-progress evaluations are handled below. 143c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm if (mCurrentState != CalculatorState.EVALUATE) { 144c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm mEvaluator.cancelAll(true); 145c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm } 146c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm // In other cases we go ahead and process the input normally after cancelling: 14706c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen if (keyEvent.getAction() != KeyEvent.ACTION_UP) { 14806c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen return true; 14906c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen } 150ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro switch (keyCode) { 151ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro case KeyEvent.KEYCODE_NUMPAD_ENTER: 152ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro case KeyEvent.KEYCODE_ENTER: 1534a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm case KeyEvent.KEYCODE_DPAD_CENTER: 1544a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm mCurrentButton = mEqualButton; 1554a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm onEquals(); 1564a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm return true; 1574a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm case KeyEvent.KEYCODE_DEL: 1584a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm mCurrentButton = mDeleteButton; 1594a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm onDelete(); 1604a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm return true; 1614a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm default: 162c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm cancelIfEvaluating(false); 1634a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm final int raw = keyEvent.getKeyCharacterMap() 1644459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen .get(keyCode, keyEvent.getMetaState()); 1654a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm if ((raw & KeyCharacterMap.COMBINING_ACCENT) != 0) { 1664a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm return true; // discard 1674a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } 1684a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // Try to discard non-printing characters and the like. 1694a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // The user will have to explicitly delete other junk that gets past us. 1704a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm if (Character.isIdentifierIgnorable(raw) 1714459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen || Character.isWhitespace(raw)) { 1724a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm return true; 1734a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } 1744459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen char c = (char) raw; 1754a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm if (c == '=') { 176e57fb01d48dbb06b4ee9c331a108212cd21a1c39Hans Boehm mCurrentButton = mEqualButton; 177ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro onEquals(); 1784a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } else { 179017de9893efd33e179db10bc71189e150bc3486dHans Boehm addChars(String.valueOf(c), true); 1804a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm redisplayAfterFormulaChange(); 181ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro } 182ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro } 183ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro return false; 184ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro } 185ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro }; 186ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro 18784614957604253d51296e06c97daced699a0a9deHans Boehm private static final String NAME = Calculator.class.getName(); 18884614957604253d51296e06c97daced699a0a9deHans Boehm private static final String KEY_DISPLAY_STATE = NAME + "_display_state"; 189760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm private static final String KEY_UNPROCESSED_CHARS = NAME + "_unprocessed_chars"; 19084614957604253d51296e06c97daced699a0a9deHans Boehm private static final String KEY_EVAL_STATE = NAME + "_eval_state"; 19184614957604253d51296e06c97daced699a0a9deHans Boehm // Associated value is a byte array holding both mCalculatorState 19284614957604253d51296e06c97daced699a0a9deHans Boehm // and the (much more complex) evaluator state. 193741471e3e99760acd44f8536534ac6121af9b03fJustin Klaassen 1944b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen private CalculatorState mCurrentState; 19584614957604253d51296e06c97daced699a0a9deHans Boehm private Evaluator mEvaluator; 1964b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 19706360f9211fc2c6df4c5749bebb65202e1bb12a8Justin Klaassen private View mDisplayView; 198d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen private TextView mModeView; 19908e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm private CalculatorText mFormulaText; 2004459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen private CalculatorResult mResultText; 201d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen 2023b4d13d528a14a0f46e7ae022357dfde90a307adJustin Klaassen private ViewPager mPadViewPager; 2034b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen private View mDeleteButton; 2044b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen private View mClearButton; 205d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen private View mEqualButton; 206e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen 207e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen private TextView mInverseToggle; 208e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen private TextView mModeToggle; 209e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen 210721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassen private View[] mInvertibleButtons; 211e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen private View[] mInverseButtons; 2124b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 2135f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen private View mCurrentButton; 2144b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen private Animator mCurrentAnimator; 2154b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 2168a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm // Characters that were recently entered at the end of the display that have not yet 2178a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm // been added to the underlying expression. 2188a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm private String mUnprocessedChars = null; 2198a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm 2208a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm // Color to highlight unprocessed characters from physical keyboard. 2218a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm // TODO: should probably match this to the error color? 2228a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm private ForegroundColorSpan mUnprocessedColorSpan = new ForegroundColorSpan(Color.RED); 2234a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm 2244b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen @Override 2254b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen protected void onCreate(Bundle savedInstanceState) { 2264b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen super.onCreate(savedInstanceState); 2275f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen setContentView(R.layout.activity_calculator); 228d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen setActionBar((Toolbar) findViewById(R.id.toolbar)); 229d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen 230d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen // Hide all default options in the ActionBar. 231d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen getActionBar().setDisplayOptions(0); 2324b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 23306360f9211fc2c6df4c5749bebb65202e1bb12a8Justin Klaassen mDisplayView = findViewById(R.id.display); 234e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen mModeView = (TextView) findViewById(R.id.mode); 23508e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm mFormulaText = (CalculatorText) findViewById(R.id.formula); 2364459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText = (CalculatorResult) findViewById(R.id.result); 237d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen 2383b4d13d528a14a0f46e7ae022357dfde90a307adJustin Klaassen mPadViewPager = (ViewPager) findViewById(R.id.pad_pager); 2394b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen mDeleteButton = findViewById(R.id.del); 2404b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen mClearButton = findViewById(R.id.clr); 241ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro mEqualButton = findViewById(R.id.pad_numeric).findViewById(R.id.eq); 242ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro if (mEqualButton == null || mEqualButton.getVisibility() != View.VISIBLE) { 243ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro mEqualButton = findViewById(R.id.pad_operator).findViewById(R.id.eq); 244ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro } 245e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen 246e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen mInverseToggle = (TextView) findViewById(R.id.toggle_inv); 247e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen mModeToggle = (TextView) findViewById(R.id.toggle_mode); 248e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen 249721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassen mInvertibleButtons = new View[] { 250721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassen findViewById(R.id.fun_sin), 251721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassen findViewById(R.id.fun_cos), 2524db31b490443e4454d98a5ae2bc44b87149accfeHans Boehm findViewById(R.id.fun_tan), 2534db31b490443e4454d98a5ae2bc44b87149accfeHans Boehm findViewById(R.id.fun_ln), 2544db31b490443e4454d98a5ae2bc44b87149accfeHans Boehm findViewById(R.id.fun_log), 2554db31b490443e4454d98a5ae2bc44b87149accfeHans Boehm findViewById(R.id.op_sqrt) 256e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen }; 257e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen mInverseButtons = new View[] { 258e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen findViewById(R.id.fun_arcsin), 259e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen findViewById(R.id.fun_arccos), 2604db31b490443e4454d98a5ae2bc44b87149accfeHans Boehm findViewById(R.id.fun_arctan), 2614db31b490443e4454d98a5ae2bc44b87149accfeHans Boehm findViewById(R.id.fun_exp), 2624db31b490443e4454d98a5ae2bc44b87149accfeHans Boehm findViewById(R.id.fun_10pow), 2634db31b490443e4454d98a5ae2bc44b87149accfeHans Boehm findViewById(R.id.op_sqr) 264e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen }; 26584614957604253d51296e06c97daced699a0a9deHans Boehm 2664459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mEvaluator = new Evaluator(this, mResultText); 2674459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.setEvaluator(mEvaluator); 268013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm KeyMaps.setActivity(this); 26984614957604253d51296e06c97daced699a0a9deHans Boehm 27084614957604253d51296e06c97daced699a0a9deHans Boehm if (savedInstanceState != null) { 27184614957604253d51296e06c97daced699a0a9deHans Boehm setState(CalculatorState.values()[ 27284614957604253d51296e06c97daced699a0a9deHans Boehm savedInstanceState.getInt(KEY_DISPLAY_STATE, 27384614957604253d51296e06c97daced699a0a9deHans Boehm CalculatorState.INPUT.ordinal())]); 274760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm CharSequence unprocessed = savedInstanceState.getCharSequence(KEY_UNPROCESSED_CHARS); 275760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm if (unprocessed != null) { 276760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm mUnprocessedChars = unprocessed.toString(); 277760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm } 278760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm byte[] state = savedInstanceState.getByteArray(KEY_EVAL_STATE); 27984614957604253d51296e06c97daced699a0a9deHans Boehm if (state != null) { 28084614957604253d51296e06c97daced699a0a9deHans Boehm try (ObjectInput in = new ObjectInputStream(new ByteArrayInputStream(state))) { 28184614957604253d51296e06c97daced699a0a9deHans Boehm mEvaluator.restoreInstanceState(in); 28284614957604253d51296e06c97daced699a0a9deHans Boehm } catch (Throwable ignored) { 28384614957604253d51296e06c97daced699a0a9deHans Boehm // When in doubt, revert to clean state 28484614957604253d51296e06c97daced699a0a9deHans Boehm mCurrentState = CalculatorState.INPUT; 28584614957604253d51296e06c97daced699a0a9deHans Boehm mEvaluator.clear(); 28684614957604253d51296e06c97daced699a0a9deHans Boehm } 28784614957604253d51296e06c97daced699a0a9deHans Boehm } 288fbcef7005de4436682072927f83000b502928d25Hans Boehm } else { 289fbcef7005de4436682072927f83000b502928d25Hans Boehm mCurrentState = CalculatorState.INPUT; 290fbcef7005de4436682072927f83000b502928d25Hans Boehm mEvaluator.clear(); 29184614957604253d51296e06c97daced699a0a9deHans Boehm } 292e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen 29308e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm mFormulaText.setOnKeyListener(mFormulaOnKeyListener); 29408e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm mFormulaText.setOnTextSizeChangeListener(this); 295fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen mFormulaText.setOnPasteListener(this); 2964b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen mDeleteButton.setOnLongClickListener(this); 297e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen 298e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen onInverseToggled(mInverseToggle.isSelected()); 299e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen onModeChanged(mEvaluator.getDegreeMode()); 300e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen 30184614957604253d51296e06c97daced699a0a9deHans Boehm if (mCurrentState != CalculatorState.INPUT) { 302fbcef7005de4436682072927f83000b502928d25Hans Boehm // Just reevaluate. 303fbcef7005de4436682072927f83000b502928d25Hans Boehm redisplayFormula(); 30484614957604253d51296e06c97daced699a0a9deHans Boehm setState(CalculatorState.INIT); 30584614957604253d51296e06c97daced699a0a9deHans Boehm mEvaluator.requireResult(); 30684614957604253d51296e06c97daced699a0a9deHans Boehm } else { 3074a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm redisplayAfterFormulaChange(); 30884614957604253d51296e06c97daced699a0a9deHans Boehm } 30984614957604253d51296e06c97daced699a0a9deHans Boehm // TODO: We're currently not saving and restoring scroll position. 31084614957604253d51296e06c97daced699a0a9deHans Boehm // We probably should. Details may require care to deal with: 31184614957604253d51296e06c97daced699a0a9deHans Boehm // - new display size 31284614957604253d51296e06c97daced699a0a9deHans Boehm // - slow recomputation if we've scrolled far. 3134b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 3144b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 3154b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen @Override 316f79d6f699b04a735e1627b47a059760ff40c26b9Justin Klaassen protected void onSaveInstanceState(@NonNull Bundle outState) { 317f79d6f699b04a735e1627b47a059760ff40c26b9Justin Klaassen // If there's an animation in progress, cancel it first to ensure our state is up-to-date. 318f79d6f699b04a735e1627b47a059760ff40c26b9Justin Klaassen if (mCurrentAnimator != null) { 319f79d6f699b04a735e1627b47a059760ff40c26b9Justin Klaassen mCurrentAnimator.cancel(); 320f79d6f699b04a735e1627b47a059760ff40c26b9Justin Klaassen } 321f79d6f699b04a735e1627b47a059760ff40c26b9Justin Klaassen 3224b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen super.onSaveInstanceState(outState); 32384614957604253d51296e06c97daced699a0a9deHans Boehm outState.putInt(KEY_DISPLAY_STATE, mCurrentState.ordinal()); 324760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm outState.putCharSequence(KEY_UNPROCESSED_CHARS, mUnprocessedChars); 32584614957604253d51296e06c97daced699a0a9deHans Boehm ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream(); 32684614957604253d51296e06c97daced699a0a9deHans Boehm try (ObjectOutput out = new ObjectOutputStream(byteArrayStream)) { 32784614957604253d51296e06c97daced699a0a9deHans Boehm mEvaluator.saveInstanceState(out); 32884614957604253d51296e06c97daced699a0a9deHans Boehm } catch (IOException e) { 32984614957604253d51296e06c97daced699a0a9deHans Boehm // Impossible; No IO involved. 33084614957604253d51296e06c97daced699a0a9deHans Boehm throw new AssertionError("Impossible IO exception", e); 33184614957604253d51296e06c97daced699a0a9deHans Boehm } 33284614957604253d51296e06c97daced699a0a9deHans Boehm outState.putByteArray(KEY_EVAL_STATE, byteArrayStream.toByteArray()); 3334b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 3344b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 3354a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // Set the state, updating delete label and display colors. 3364a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // This restores display positions on moving to INPUT. 337d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen // But movement/animation for moving to RESULT has already been done. 3384b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen private void setState(CalculatorState state) { 3394b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen if (mCurrentState != state) { 34084614957604253d51296e06c97daced699a0a9deHans Boehm if (state == CalculatorState.INPUT) { 34184614957604253d51296e06c97daced699a0a9deHans Boehm restoreDisplayPositions(); 34284614957604253d51296e06c97daced699a0a9deHans Boehm } 3434b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen mCurrentState = state; 3444b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 3454a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm if (mCurrentState == CalculatorState.RESULT) { 3464a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // No longer do this for ERROR; allow mistakes to be corrected. 3474b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen mDeleteButton.setVisibility(View.GONE); 3484b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen mClearButton.setVisibility(View.VISIBLE); 3494b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } else { 3504b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen mDeleteButton.setVisibility(View.VISIBLE); 3514b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen mClearButton.setVisibility(View.GONE); 3524b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 3534b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 35484614957604253d51296e06c97daced699a0a9deHans Boehm if (mCurrentState == CalculatorState.ERROR) { 3554459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen final int errorColor = getColor(R.color.calculator_error_color); 35608e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm mFormulaText.setTextColor(errorColor); 3574459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.setTextColor(errorColor); 3588fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassen getWindow().setStatusBarColor(errorColor); 3594459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen } else if (mCurrentState != CalculatorState.RESULT) { 3604459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mFormulaText.setTextColor(getColor(R.color.display_formula_text_color)); 3614459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.setTextColor(getColor(R.color.display_result_text_color)); 3624459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen getWindow().setStatusBarColor(getColor(R.color.calculator_accent_color)); 3634b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 364d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen 365d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen invalidateOptionsMenu(); 3664b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 3674b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 3684b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 3691176f23dae4d3740782e46463003e9f36a381c9dHans Boehm // Stop any active ActionMode. Return true if there was one. 3701176f23dae4d3740782e46463003e9f36a381c9dHans Boehm private boolean stopActionMode() { 3714459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen if (mResultText.stopActionMode()) { 3721176f23dae4d3740782e46463003e9f36a381c9dHans Boehm return true; 3731176f23dae4d3740782e46463003e9f36a381c9dHans Boehm } 3741176f23dae4d3740782e46463003e9f36a381c9dHans Boehm if (mFormulaText.stopActionMode()) { 3751176f23dae4d3740782e46463003e9f36a381c9dHans Boehm return true; 3761176f23dae4d3740782e46463003e9f36a381c9dHans Boehm } 3771176f23dae4d3740782e46463003e9f36a381c9dHans Boehm return false; 3781176f23dae4d3740782e46463003e9f36a381c9dHans Boehm } 3791176f23dae4d3740782e46463003e9f36a381c9dHans Boehm 3804b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen @Override 3813b4d13d528a14a0f46e7ae022357dfde90a307adJustin Klaassen public void onBackPressed() { 3821176f23dae4d3740782e46463003e9f36a381c9dHans Boehm if (!stopActionMode()) { 3831176f23dae4d3740782e46463003e9f36a381c9dHans Boehm if (mPadViewPager != null && mPadViewPager.getCurrentItem() != 0) { 3841176f23dae4d3740782e46463003e9f36a381c9dHans Boehm // Select the previous pad. 3851176f23dae4d3740782e46463003e9f36a381c9dHans Boehm mPadViewPager.setCurrentItem(mPadViewPager.getCurrentItem() - 1); 3861176f23dae4d3740782e46463003e9f36a381c9dHans Boehm } else { 3871176f23dae4d3740782e46463003e9f36a381c9dHans Boehm // If the user is currently looking at the first pad (or the pad is not paged), 3881176f23dae4d3740782e46463003e9f36a381c9dHans Boehm // allow the system to handle the Back button. 3891176f23dae4d3740782e46463003e9f36a381c9dHans Boehm super.onBackPressed(); 3901176f23dae4d3740782e46463003e9f36a381c9dHans Boehm } 3913b4d13d528a14a0f46e7ae022357dfde90a307adJustin Klaassen } 3923b4d13d528a14a0f46e7ae022357dfde90a307adJustin Klaassen } 3933b4d13d528a14a0f46e7ae022357dfde90a307adJustin Klaassen 3943b4d13d528a14a0f46e7ae022357dfde90a307adJustin Klaassen @Override 3954b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen public void onUserInteraction() { 3964b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen super.onUserInteraction(); 3974b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 398c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm // If there's an animation in progress, end it immediately, so the user interaction can 399c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm // be handled. 4004b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen if (mCurrentAnimator != null) { 401c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm mCurrentAnimator.end(); 4024b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 4034b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 4044b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 405e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen /** 406e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen * Invoked whenever the inverse button is toggled to update the UI. 407e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen * 408e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen * @param showInverse {@code true} if inverse functions should be shown 409e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen */ 410e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen private void onInverseToggled(boolean showInverse) { 411e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen if (showInverse) { 412e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen mInverseToggle.setContentDescription(getString(R.string.desc_inv_on)); 413721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassen for (View invertibleButton : mInvertibleButtons) { 414721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassen invertibleButton.setVisibility(View.GONE); 415e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen } 416e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen for (View inverseButton : mInverseButtons) { 417e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen inverseButton.setVisibility(View.VISIBLE); 418e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen } 419e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen } else { 420e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen mInverseToggle.setContentDescription(getString(R.string.desc_inv_off)); 421721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassen for (View invertibleButton : mInvertibleButtons) { 422721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassen invertibleButton.setVisibility(View.VISIBLE); 423e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen } 424e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen for (View inverseButton : mInverseButtons) { 425e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen inverseButton.setVisibility(View.GONE); 426e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen } 427e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen } 428e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen } 429e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen 430e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen /** 431e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen * Invoked whenever the deg/rad mode may have changed to update the UI. 432e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen * 433e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen * @param degreeMode {@code true} if in degree mode 434e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen */ 435e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen private void onModeChanged(boolean degreeMode) { 436e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen if (degreeMode) { 437d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen mModeView.setText(R.string.mode_deg); 438e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen mModeView.setContentDescription(getString(R.string.desc_mode_deg)); 439e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen 440e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen mModeToggle.setText(R.string.mode_rad); 441e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen mModeToggle.setContentDescription(getString(R.string.desc_switch_rad)); 442bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm } else { 443d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen mModeView.setText(R.string.mode_rad); 444e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen mModeView.setContentDescription(getString(R.string.desc_mode_rad)); 445e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen 446e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen mModeToggle.setText(R.string.mode_deg); 447e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen mModeToggle.setContentDescription(getString(R.string.desc_switch_deg)); 448bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm } 449bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm } 45084614957604253d51296e06c97daced699a0a9deHans Boehm 451f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm /** 452f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm * Switch to INPUT from RESULT state in response to input of the specified button_id. 453f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm * View.NO_ID is treated as an incomplete function id. 454f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm */ 455f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm private void switchToInput(int button_id) { 456f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm if (KeyMaps.isBinary(button_id) || KeyMaps.isSuffix(button_id)) { 457f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm mEvaluator.collapse(); 458f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm } else { 459f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm announceClearedForAccessibility(); 460f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm mEvaluator.clear(); 461f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm } 462f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm setState(CalculatorState.INPUT); 463f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm } 464f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm 4654a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // Add the given button id to input expression. 4664a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // If appropriate, clear the expression before doing so. 4674a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm private void addKeyToExpr(int id) { 4684a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm if (mCurrentState == CalculatorState.ERROR) { 4694a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm setState(CalculatorState.INPUT); 4704a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } else if (mCurrentState == CalculatorState.RESULT) { 471f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm switchToInput(id); 4724a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } 4734a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm if (!mEvaluator.append(id)) { 4744a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // TODO: Some user visible feedback? 4754a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } 4764a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } 4774a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm 478017de9893efd33e179db10bc71189e150bc3486dHans Boehm /** 479017de9893efd33e179db10bc71189e150bc3486dHans Boehm * Add the given button id to input expression, assuming it was explicitly 480017de9893efd33e179db10bc71189e150bc3486dHans Boehm * typed/touched. 481017de9893efd33e179db10bc71189e150bc3486dHans Boehm * We perform slightly more aggressive correction than in pasted expressions. 482017de9893efd33e179db10bc71189e150bc3486dHans Boehm */ 483017de9893efd33e179db10bc71189e150bc3486dHans Boehm private void addExplicitKeyToExpr(int id) { 484017de9893efd33e179db10bc71189e150bc3486dHans Boehm if (mCurrentState == CalculatorState.INPUT && id == R.id.op_sub) { 485017de9893efd33e179db10bc71189e150bc3486dHans Boehm mEvaluator.getExpr().removeTrailingAdditiveOperators(); 486017de9893efd33e179db10bc71189e150bc3486dHans Boehm } 487017de9893efd33e179db10bc71189e150bc3486dHans Boehm addKeyToExpr(id); 488017de9893efd33e179db10bc71189e150bc3486dHans Boehm } 489017de9893efd33e179db10bc71189e150bc3486dHans Boehm 4904a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm private void redisplayAfterFormulaChange() { 4914a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // TODO: Could do this more incrementally. 4924a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm redisplayFormula(); 4934a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm setState(CalculatorState.INPUT); 494c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm if (mEvaluator.getExpr().hasInterestingOps()) { 495c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm mEvaluator.evaluateAndShowResult(); 496c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm } else { 4974459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.clear(); 498c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm } 4994a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } 5004a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm 5014b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen public void onButtonClick(View view) { 502c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm // Any animation is ended before we get here. 5035f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen mCurrentButton = view; 5041176f23dae4d3740782e46463003e9f36a381c9dHans Boehm stopActionMode(); 505c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm // See onKey above for the rationale behind some of the behavior below: 506c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm if (mCurrentState != CalculatorState.EVALUATE) { 507c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm // Cancel evaluations that were not specifically requested. 508c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm mEvaluator.cancelAll(true); 50984614957604253d51296e06c97daced699a0a9deHans Boehm } 510d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen final int id = view.getId(); 51184614957604253d51296e06c97daced699a0a9deHans Boehm switch (id) { 5124b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen case R.id.eq: 513ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro onEquals(); 5144b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen break; 5154b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen case R.id.del: 516ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro onDelete(); 5174b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen break; 5184b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen case R.id.clr: 5195f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen onClear(); 5204b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen break; 521e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen case R.id.toggle_inv: 522e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen final boolean selected = !mInverseToggle.isSelected(); 523e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen mInverseToggle.setSelected(selected); 524e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen onInverseToggled(selected); 525c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm if (mCurrentState == CalculatorState.RESULT) { 526c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm mResultText.redisplay(); // In case we cancelled reevaluation. 527c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm } 528e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen break; 529e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen case R.id.toggle_mode: 530c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm cancelIfEvaluating(false); 531e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen final boolean mode = !mEvaluator.getDegreeMode(); 532bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm if (mCurrentState == CalculatorState.RESULT) { 533bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm mEvaluator.collapse(); // Capture result evaluated in old mode 534bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm redisplayFormula(); 535bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm } 536bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm // In input mode, we reinterpret already entered trig functions. 537bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm mEvaluator.setDegreeMode(mode); 538e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen onModeChanged(mode); 539bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm setState(CalculatorState.INPUT); 5404459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.clear(); 541c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm if (mEvaluator.getExpr().hasInterestingOps()) { 542c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm mEvaluator.evaluateAndShowResult(); 543c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm } 544bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm break; 5454b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen default: 546c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm cancelIfEvaluating(false); 547017de9893efd33e179db10bc71189e150bc3486dHans Boehm addExplicitKeyToExpr(id); 5484a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm redisplayAfterFormulaChange(); 5494b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen break; 5504b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 5514b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 5524b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 55384614957604253d51296e06c97daced699a0a9deHans Boehm void redisplayFormula() { 5548a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm SpannableStringBuilder formula = mEvaluator.getExpr().toSpannableStringBuilder(this); 5554a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm if (mUnprocessedChars != null) { 5564a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // Add and highlight characters we couldn't process. 5578a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm formula.append(mUnprocessedChars, mUnprocessedColorSpan, 5588a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 5594a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } 5608a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm mFormulaText.changeTextTo(formula); 56184614957604253d51296e06c97daced699a0a9deHans Boehm } 56284614957604253d51296e06c97daced699a0a9deHans Boehm 5634b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen @Override 5644b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen public boolean onLongClick(View view) { 5655f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen mCurrentButton = view; 5665f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen 5674b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen if (view.getId() == R.id.del) { 5685f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen onClear(); 5694b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen return true; 5704b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 5714b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen return false; 5724b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 5734b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 57484614957604253d51296e06c97daced699a0a9deHans Boehm // Initial evaluation completed successfully. Initiate display. 575a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm public void onEvaluate(int initDisplayPrec, int msd, int leastDigPos, 576a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm String truncatedWholeNumber) { 577d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen // Invalidate any options that may depend on the current result. 578d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen invalidateOptionsMenu(); 579d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen 580a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm mResultText.displayResult(initDisplayPrec, msd, leastDigPos, truncatedWholeNumber); 58161568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm if (mCurrentState != CalculatorState.INPUT) { // in EVALUATE or INIT state 58284614957604253d51296e06c97daced699a0a9deHans Boehm onResult(mCurrentState != CalculatorState.INIT); 5834b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 58484614957604253d51296e06c97daced699a0a9deHans Boehm } 585ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro 586c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm // Reset state to reflect evaluator cancellation. Invoked by evaluator. 58784614957604253d51296e06c97daced699a0a9deHans Boehm public void onCancelled() { 58884614957604253d51296e06c97daced699a0a9deHans Boehm // We should be in EVALUATE state. 58984614957604253d51296e06c97daced699a0a9deHans Boehm setState(CalculatorState.INPUT); 5904459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.clear(); 59184614957604253d51296e06c97daced699a0a9deHans Boehm } 59284614957604253d51296e06c97daced699a0a9deHans Boehm 59384614957604253d51296e06c97daced699a0a9deHans Boehm // Reevaluation completed; ask result to redisplay current value. 59484614957604253d51296e06c97daced699a0a9deHans Boehm public void onReevaluate() 59584614957604253d51296e06c97daced699a0a9deHans Boehm { 5964459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.redisplay(); 5974b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 5984b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 599fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen @Override 600fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen public void onTextSizeChanged(final TextView textView, float oldSize) { 601fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen if (mCurrentState != CalculatorState.INPUT) { 602fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen // Only animate text changes that occur from user input. 603fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen return; 604fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen } 605fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen 606fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen // Calculate the values needed to perform the scale and translation animations, 607fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen // maintaining the same apparent baseline for the displayed text. 608fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen final float textScale = oldSize / textView.getTextSize(); 609fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen final float translationX = (1.0f - textScale) * 610fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen (textView.getWidth() / 2.0f - textView.getPaddingEnd()); 611fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen final float translationY = (1.0f - textScale) * 612fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen (textView.getHeight() / 2.0f - textView.getPaddingBottom()); 613fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen 614fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen final AnimatorSet animatorSet = new AnimatorSet(); 615fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen animatorSet.playTogether( 616fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen ObjectAnimator.ofFloat(textView, View.SCALE_X, textScale, 1.0f), 617fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen ObjectAnimator.ofFloat(textView, View.SCALE_Y, textScale, 1.0f), 618fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, translationX, 0.0f), 619fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen ObjectAnimator.ofFloat(textView, View.TRANSLATION_Y, translationY, 0.0f)); 62094db720f7daf99923cc8e3d5ba8765b5529913f1Justin Klaassen animatorSet.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime)); 621fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen animatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); 622fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen animatorSet.start(); 623fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen } 624fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen 625c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm /** 626c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm * Cancel any in-progress explicitly requested evaluations. 627c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm * @param quiet suppress pop-up message. Explicit evaluation can change the expression 628c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm value, and certainly changes the display, so it seems reasonable to warn. 629c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm * @return true if there was such an evaluation 630c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm */ 631c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm private boolean cancelIfEvaluating(boolean quiet) { 632c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm if (mCurrentState == CalculatorState.EVALUATE) { 633c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm mEvaluator.cancelAll(quiet); 634c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm return true; 635c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm } else { 636c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm return false; 637c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm } 638c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm } 639c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm 640ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro private void onEquals() { 641c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm // In non-INPUT state assume this was redundant and ignore it. 642c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm if (mCurrentState == CalculatorState.INPUT && !mEvaluator.getExpr().isEmpty()) { 643ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro setState(CalculatorState.EVALUATE); 64484614957604253d51296e06c97daced699a0a9deHans Boehm mEvaluator.requireResult(); 645ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro } 646ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro } 647ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro 648ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro private void onDelete() { 6494a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // Delete works like backspace; remove the last character or operator from the expression. 6504a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // Note that we handle keyboard delete exactly like the delete button. For 6514a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // example the delete button can be used to delete a character from an incomplete 6524a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // function name typed on a physical keyboard. 6534a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // This should be impossible in RESULT state. 654c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm // If there is an in-progress explicit evaluation, just cancel it and return. 655c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm if (cancelIfEvaluating(false)) return; 6564a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm setState(CalculatorState.INPUT); 6574a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm if (mUnprocessedChars != null) { 6584a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm int len = mUnprocessedChars.length(); 6594a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm if (len > 0) { 6604a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm mUnprocessedChars = mUnprocessedChars.substring(0, len-1); 6614a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } else { 662c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm mEvaluator.delete(); 6634a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } 6644a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } else { 665c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm mEvaluator.delete(); 6664a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } 6674d63cfce55fa71eb208467310277bebc828be07dHans Boehm if (mEvaluator.getExpr().isEmpty() 6684d63cfce55fa71eb208467310277bebc828be07dHans Boehm && (mUnprocessedChars == null || mUnprocessedChars.isEmpty())) { 6694d63cfce55fa71eb208467310277bebc828be07dHans Boehm // Resulting formula won't be announced, since it's empty. 6704d63cfce55fa71eb208467310277bebc828be07dHans Boehm announceClearedForAccessibility(); 6714d63cfce55fa71eb208467310277bebc828be07dHans Boehm } 6724a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm redisplayAfterFormulaChange(); 673ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro } 674ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro 6755f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen private void reveal(View sourceView, int colorRes, AnimatorListener listener) { 67606360f9211fc2c6df4c5749bebb65202e1bb12a8Justin Klaassen final ViewGroupOverlay groupOverlay = 67706360f9211fc2c6df4c5749bebb65202e1bb12a8Justin Klaassen (ViewGroupOverlay) getWindow().getDecorView().getOverlay(); 6788fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassen 6798fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassen final Rect displayRect = new Rect(); 68006360f9211fc2c6df4c5749bebb65202e1bb12a8Justin Klaassen mDisplayView.getGlobalVisibleRect(displayRect); 6815f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen 6825f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen // Make reveal cover the display and status bar. 6835f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen final View revealView = new View(this); 6848fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassen revealView.setBottom(displayRect.bottom); 6858fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassen revealView.setLeft(displayRect.left); 6868fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassen revealView.setRight(displayRect.right); 6875f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen revealView.setBackgroundColor(getResources().getColor(colorRes)); 68806360f9211fc2c6df4c5749bebb65202e1bb12a8Justin Klaassen groupOverlay.add(revealView); 6895f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen 6904b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen final int[] clearLocation = new int[2]; 6914b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen sourceView.getLocationInWindow(clearLocation); 6924b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen clearLocation[0] += sourceView.getWidth() / 2; 6934b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen clearLocation[1] += sourceView.getHeight() / 2; 6944b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 6955f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen final int revealCenterX = clearLocation[0] - revealView.getLeft(); 6965f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen final int revealCenterY = clearLocation[1] - revealView.getTop(); 6974b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 6985f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen final double x1_2 = Math.pow(revealView.getLeft() - revealCenterX, 2); 6995f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen final double x2_2 = Math.pow(revealView.getRight() - revealCenterX, 2); 7005f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen final double y_2 = Math.pow(revealView.getTop() - revealCenterY, 2); 7014b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen final float revealRadius = (float) Math.max(Math.sqrt(x1_2 + y_2), Math.sqrt(x2_2 + y_2)); 7024b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 7035f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen final Animator revealAnimator = 7045f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen ViewAnimationUtils.createCircularReveal(revealView, 7053d6ecaf4d3365eeaff75a4bedb57fbe136cf5f64ztenghui revealCenterX, revealCenterY, 0.0f, revealRadius); 7065f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen revealAnimator.setDuration( 7074b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen getResources().getInteger(android.R.integer.config_longAnimTime)); 7085f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen revealAnimator.addListener(listener); 7094b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 7105f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen final Animator alphaAnimator = ObjectAnimator.ofFloat(revealView, View.ALPHA, 0.0f); 7114b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen alphaAnimator.setDuration( 7125f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen getResources().getInteger(android.R.integer.config_mediumAnimTime)); 7134b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 7144b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen final AnimatorSet animatorSet = new AnimatorSet(); 7155f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen animatorSet.play(revealAnimator).before(alphaAnimator); 7164b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen animatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); 7174b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen animatorSet.addListener(new AnimatorListenerAdapter() { 7184b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen @Override 7194b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen public void onAnimationEnd(Animator animator) { 7208fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassen groupOverlay.remove(revealView); 7214b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen mCurrentAnimator = null; 7224b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 7234b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen }); 7244b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 7254b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen mCurrentAnimator = animatorSet; 7264b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen animatorSet.start(); 7274b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 7284b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 7294d63cfce55fa71eb208467310277bebc828be07dHans Boehm private void announceClearedForAccessibility() { 7304d63cfce55fa71eb208467310277bebc828be07dHans Boehm mResultText.announceForAccessibility(getResources().getString(R.string.cleared)); 731ccc556621d3c0fe59c4878ce9c62322ff326041eHans Boehm } 732ccc556621d3c0fe59c4878ce9c62322ff326041eHans Boehm 7335f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen private void onClear() { 73484614957604253d51296e06c97daced699a0a9deHans Boehm if (mEvaluator.getExpr().isEmpty()) { 7355f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen return; 7365f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen } 737c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm cancelIfEvaluating(true); 7384d63cfce55fa71eb208467310277bebc828be07dHans Boehm announceClearedForAccessibility(); 7395f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen reveal(mCurrentButton, R.color.calculator_accent_color, new AnimatorListenerAdapter() { 7405f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen @Override 7415f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen public void onAnimationEnd(Animator animation) { 742760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm mUnprocessedChars = null; 7434459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.clear(); 744760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm mEvaluator.clear(); 745760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm setState(CalculatorState.INPUT); 74684614957604253d51296e06c97daced699a0a9deHans Boehm redisplayFormula(); 7475f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen } 7485f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen }); 7495f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen } 7505f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen 75184614957604253d51296e06c97daced699a0a9deHans Boehm // Evaluation encountered en error. Display the error. 75284614957604253d51296e06c97daced699a0a9deHans Boehm void onError(final int errorResourceId) { 753fbcef7005de4436682072927f83000b502928d25Hans Boehm if (mCurrentState == CalculatorState.EVALUATE) { 754fbcef7005de4436682072927f83000b502928d25Hans Boehm setState(CalculatorState.ANIMATE); 755ccc556621d3c0fe59c4878ce9c62322ff326041eHans Boehm mResultText.announceForAccessibility(getResources().getString(errorResourceId)); 756fbcef7005de4436682072927f83000b502928d25Hans Boehm reveal(mCurrentButton, R.color.calculator_error_color, 757fbcef7005de4436682072927f83000b502928d25Hans Boehm new AnimatorListenerAdapter() { 758fbcef7005de4436682072927f83000b502928d25Hans Boehm @Override 759fbcef7005de4436682072927f83000b502928d25Hans Boehm public void onAnimationEnd(Animator animation) { 760fbcef7005de4436682072927f83000b502928d25Hans Boehm setState(CalculatorState.ERROR); 7614459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.displayError(errorResourceId); 762fbcef7005de4436682072927f83000b502928d25Hans Boehm } 763fbcef7005de4436682072927f83000b502928d25Hans Boehm }); 764fbcef7005de4436682072927f83000b502928d25Hans Boehm } else if (mCurrentState == CalculatorState.INIT) { 765fbcef7005de4436682072927f83000b502928d25Hans Boehm setState(CalculatorState.ERROR); 7664459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.displayError(errorResourceId); 767c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm } else { 7684459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.clear(); 7692be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen } 7705f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen } 7715f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen 77284614957604253d51296e06c97daced699a0a9deHans Boehm 77384614957604253d51296e06c97daced699a0a9deHans Boehm // Animate movement of result into the top formula slot. 77484614957604253d51296e06c97daced699a0a9deHans Boehm // Result window now remains translated in the top slot while the result is displayed. 77584614957604253d51296e06c97daced699a0a9deHans Boehm // (We convert it back to formula use only when the user provides new input.) 7764459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen // Historical note: In the Lollipop version, this invisibly and instantaneously moved 77784614957604253d51296e06c97daced699a0a9deHans Boehm // formula and result displays back at the end of the animation. We no longer do that, 77884614957604253d51296e06c97daced699a0a9deHans Boehm // so that we can continue to properly support scrolling of the result. 77984614957604253d51296e06c97daced699a0a9deHans Boehm // We assume the result already contains the text to be expanded. 78084614957604253d51296e06c97daced699a0a9deHans Boehm private void onResult(boolean animate) { 7814459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen // Calculate the textSize that would be used to display the result in the formula. 7824459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen // For scrollable results just use the minimum textSize to maximize the number of digits 7834459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen // that are visible on screen. 7844459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen float textSize = mFormulaText.getMinimumTextSize(); 7854459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen if (!mResultText.isScrollable()) { 7864459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen textSize = mFormulaText.getVariableTextSize(mResultText.getText().toString()); 7874459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen } 7884459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen 7894459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen // Scale the result to match the calculated textSize, minimizing the jump-cut transition 7904459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen // when a result is reused in a subsequent expression. 7914459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen final float resultScale = textSize / mResultText.getTextSize(); 7924459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen 7934459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen // Set the result's pivot to match its gravity. 7944459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.setPivotX(mResultText.getWidth() - mResultText.getPaddingRight()); 7954459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.setPivotY(mResultText.getHeight() - mResultText.getPaddingBottom()); 7964459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen 7974459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen // Calculate the necessary translations so the result takes the place of the formula and 7984459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen // the formula moves off the top of the screen. 7994459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen final float resultTranslationY = (mFormulaText.getBottom() - mResultText.getBottom()) 8004459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen - (mFormulaText.getPaddingBottom() - mResultText.getPaddingBottom()); 80108e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm final float formulaTranslationY = -mFormulaText.getBottom(); 8024b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 8034459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen // Change the result's textColor to match the formula. 8044459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen final int formulaTextColor = mFormulaText.getCurrentTextColor(); 8054459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen 80684614957604253d51296e06c97daced699a0a9deHans Boehm if (animate) { 807ccc556621d3c0fe59c4878ce9c62322ff326041eHans Boehm mResultText.announceForAccessibility(getResources().getString(R.string.desc_eq)); 808ccc556621d3c0fe59c4878ce9c62322ff326041eHans Boehm mResultText.announceForAccessibility(mResultText.getText()); 809c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm setState(CalculatorState.ANIMATE); 81084614957604253d51296e06c97daced699a0a9deHans Boehm final AnimatorSet animatorSet = new AnimatorSet(); 81184614957604253d51296e06c97daced699a0a9deHans Boehm animatorSet.playTogether( 8124459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen ObjectAnimator.ofPropertyValuesHolder(mResultText, 8134459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen PropertyValuesHolder.ofFloat(View.SCALE_X, resultScale), 8144459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen PropertyValuesHolder.ofFloat(View.SCALE_Y, resultScale), 8154459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, resultTranslationY)), 8164459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen ObjectAnimator.ofArgb(mResultText, TEXT_COLOR, formulaTextColor), 8174459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen ObjectAnimator.ofFloat(mFormulaText, View.TRANSLATION_Y, formulaTranslationY)); 8184459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen animatorSet.setDuration(getResources().getInteger( 8194459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen android.R.integer.config_longAnimTime)); 82084614957604253d51296e06c97daced699a0a9deHans Boehm animatorSet.addListener(new AnimatorListenerAdapter() { 82184614957604253d51296e06c97daced699a0a9deHans Boehm @Override 82284614957604253d51296e06c97daced699a0a9deHans Boehm public void onAnimationEnd(Animator animation) { 82384614957604253d51296e06c97daced699a0a9deHans Boehm setState(CalculatorState.RESULT); 82484614957604253d51296e06c97daced699a0a9deHans Boehm mCurrentAnimator = null; 82584614957604253d51296e06c97daced699a0a9deHans Boehm } 82684614957604253d51296e06c97daced699a0a9deHans Boehm }); 82784614957604253d51296e06c97daced699a0a9deHans Boehm 82884614957604253d51296e06c97daced699a0a9deHans Boehm mCurrentAnimator = animatorSet; 82984614957604253d51296e06c97daced699a0a9deHans Boehm animatorSet.start(); 83084614957604253d51296e06c97daced699a0a9deHans Boehm } else /* No animation desired; get there fast, e.g. when restarting */ { 8314459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.setScaleX(resultScale); 8324459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.setScaleY(resultScale); 8334459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.setTranslationY(resultTranslationY); 8344459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.setTextColor(formulaTextColor); 83508e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm mFormulaText.setTranslationY(formulaTranslationY); 8364a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm setState(CalculatorState.RESULT); 83784614957604253d51296e06c97daced699a0a9deHans Boehm } 83884614957604253d51296e06c97daced699a0a9deHans Boehm } 8394b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 84084614957604253d51296e06c97daced699a0a9deHans Boehm // Restore positions of the formula and result displays back to their original, 84184614957604253d51296e06c97daced699a0a9deHans Boehm // pre-animation state. 84284614957604253d51296e06c97daced699a0a9deHans Boehm private void restoreDisplayPositions() { 84384614957604253d51296e06c97daced699a0a9deHans Boehm // Clear result. 8444459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.setText(""); 84584614957604253d51296e06c97daced699a0a9deHans Boehm // Reset all of the values modified during the animation. 8464459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.setScaleX(1.0f); 8474459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.setScaleY(1.0f); 8484459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.setTranslationX(0.0f); 8494459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen mResultText.setTranslationY(0.0f); 85008e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm mFormulaText.setTranslationY(0.0f); 8514b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 85208e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm mFormulaText.requestFocus(); 8539192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm } 8549192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm 8559192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm @Override 8569192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm public void onClick(AlertDialogFragment fragment, int which) { 8579192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm if (which == DialogInterface.BUTTON_POSITIVE) { 8589192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm // Timeout extension request. 8599192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm mEvaluator.setLongTimeOut(); 8609192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm } 8619192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm } 86284614957604253d51296e06c97daced699a0a9deHans Boehm 863d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen @Override 864d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen public boolean onCreateOptionsMenu(Menu menu) { 865d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen super.onCreateOptionsMenu(menu); 866d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen 867d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen getMenuInflater().inflate(R.menu.activity_calculator, menu); 868d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen return true; 869d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen } 870d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen 871d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen @Override 872d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen public boolean onPrepareOptionsMenu(Menu menu) { 873d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen super.onPrepareOptionsMenu(menu); 874d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen 875d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen // Show the leading option when displaying a result. 876d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen menu.findItem(R.id.menu_leading).setVisible(mCurrentState == CalculatorState.RESULT); 877d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen 878d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen // Show the fraction option when displaying a rational result. 879d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen menu.findItem(R.id.menu_fraction).setVisible(mCurrentState == CalculatorState.RESULT 880d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen && mEvaluator.getRational() != null); 881d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen 882d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen return true; 88384614957604253d51296e06c97daced699a0a9deHans Boehm } 8844b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 88584614957604253d51296e06c97daced699a0a9deHans Boehm @Override 886d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen public boolean onOptionsItemSelected(MenuItem item) { 88784614957604253d51296e06c97daced699a0a9deHans Boehm switch (item.getItemId()) { 888d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen case R.id.menu_leading: 889d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen displayFull(); 89084614957604253d51296e06c97daced699a0a9deHans Boehm return true; 8914a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm case R.id.menu_fraction: 8924a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm displayFraction(); 8934a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm return true; 894d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen case R.id.menu_licenses: 895d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen startActivity(new Intent(this, Licenses.class)); 8964a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm return true; 89784614957604253d51296e06c97daced699a0a9deHans Boehm default: 89884614957604253d51296e06c97daced699a0a9deHans Boehm return super.onOptionsItemSelected(item); 89984614957604253d51296e06c97daced699a0a9deHans Boehm } 90084614957604253d51296e06c97daced699a0a9deHans Boehm } 90184614957604253d51296e06c97daced699a0a9deHans Boehm 9024a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm private void displayMessage(String s) { 9039192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm AlertDialogFragment.showMessageDialog(this, s, null); 90484614957604253d51296e06c97daced699a0a9deHans Boehm } 90584614957604253d51296e06c97daced699a0a9deHans Boehm 9064a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm private void displayFraction() { 9074a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm BoundedRational result = mEvaluator.getRational(); 908013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm displayMessage(KeyMaps.translateResult(result.toNiceString())); 9094a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } 9104a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm 9114a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // Display full result to currently evaluated precision 9124a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm private void displayFull() { 9134a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm Resources res = getResources(); 9144459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen String msg = mResultText.getFullText() + " "; 9154459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen if (mResultText.fullTextIsExact()) { 9164a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm msg += res.getString(R.string.exact); 9174a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } else { 9184a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm msg += res.getString(R.string.approximate); 9194a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } 9204a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm displayMessage(msg); 9214a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } 9224a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm 923017de9893efd33e179db10bc71189e150bc3486dHans Boehm /** 924017de9893efd33e179db10bc71189e150bc3486dHans Boehm * Add input characters to the end of the expression. 925017de9893efd33e179db10bc71189e150bc3486dHans Boehm * Map them to the appropriate button pushes when possible. Leftover characters 926017de9893efd33e179db10bc71189e150bc3486dHans Boehm * are added to mUnprocessedChars, which is presumed to immediately precede the newly 927017de9893efd33e179db10bc71189e150bc3486dHans Boehm * added characters. 928017de9893efd33e179db10bc71189e150bc3486dHans Boehm * @param moreChars Characters to be added. 9290b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm * @param explicit These characters were explicitly typed by the user, not pasted. 930017de9893efd33e179db10bc71189e150bc3486dHans Boehm */ 931017de9893efd33e179db10bc71189e150bc3486dHans Boehm private void addChars(String moreChars, boolean explicit) { 9324a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm if (mUnprocessedChars != null) { 9334a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm moreChars = mUnprocessedChars + moreChars; 9344a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } 9354a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm int current = 0; 9364a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm int len = moreChars.length(); 9370b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm boolean lastWasDigit = false; 938f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm if (mCurrentState == CalculatorState.RESULT && len != 0) { 939f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm // Clear display immediately for incomplete function name. 940f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm switchToInput(KeyMaps.keyForChar(moreChars.charAt(current))); 941f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm } 9424a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm while (current < len) { 9434a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm char c = moreChars.charAt(current); 944013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm int k = KeyMaps.keyForChar(c); 9450b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm if (!explicit) { 9460b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm int expEnd; 9470b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm if (lastWasDigit && current != 9480b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm (expEnd = Evaluator.exponentEnd(moreChars, current))) { 9490b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm // Process scientific notation with 'E' when pasting, in spite of ambiguity 9500b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm // with base of natural log. 9510b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm // Otherwise the 10^x key is the user's friend. 9520b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm mEvaluator.addExponent(moreChars, current, expEnd); 9530b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm current = expEnd; 9540b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm lastWasDigit = false; 9550b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm continue; 9560b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm } else { 9570b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm boolean isDigit = KeyMaps.digVal(k) != KeyMaps.NOT_DIGIT; 9580b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm if (current == 0 && (isDigit || k == R.id.dec_point) 9590b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm && mEvaluator.getExpr().hasTrailingConstant()) { 9600b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm // Refuse to concatenate pasted content to trailing constant. 9610b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm // This makes pasting of calculator results more consistent, whether or 9620b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm // not the old calculator instance is still around. 9630b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm addKeyToExpr(R.id.op_mul); 9640b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm } 9650b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm lastWasDigit = (isDigit || lastWasDigit && k == R.id.dec_point); 9660b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm } 9670b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm } 9684a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm if (k != View.NO_ID) { 9694a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm mCurrentButton = findViewById(k); 970017de9893efd33e179db10bc71189e150bc3486dHans Boehm if (explicit) { 971017de9893efd33e179db10bc71189e150bc3486dHans Boehm addExplicitKeyToExpr(k); 972017de9893efd33e179db10bc71189e150bc3486dHans Boehm } else { 973017de9893efd33e179db10bc71189e150bc3486dHans Boehm addKeyToExpr(k); 974017de9893efd33e179db10bc71189e150bc3486dHans Boehm } 9754a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm if (Character.isSurrogate(c)) { 9764a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm current += 2; 9774a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } else { 9784a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm ++current; 9794a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } 9804a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm continue; 9814a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } 982013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm int f = KeyMaps.funForString(moreChars, current); 9834a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm if (f != View.NO_ID) { 9844a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm mCurrentButton = findViewById(f); 985017de9893efd33e179db10bc71189e150bc3486dHans Boehm if (explicit) { 986017de9893efd33e179db10bc71189e150bc3486dHans Boehm addExplicitKeyToExpr(f); 987017de9893efd33e179db10bc71189e150bc3486dHans Boehm } else { 988017de9893efd33e179db10bc71189e150bc3486dHans Boehm addKeyToExpr(f); 989017de9893efd33e179db10bc71189e150bc3486dHans Boehm } 9904a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm if (f == R.id.op_sqrt) { 9914a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // Square root entered as function; don't lose the parenthesis. 9924a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm addKeyToExpr(R.id.lparen); 9934a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm } 9944a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm current = moreChars.indexOf('(', current) + 1; 9954a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm continue; 9964b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 9974a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm // There are characters left, but we can't convert them to button presses. 9984a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm mUnprocessedChars = moreChars.substring(current); 9994a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm redisplayAfterFormulaChange(); 10004a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm return; 100184614957604253d51296e06c97daced699a0a9deHans Boehm } 10024a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm mUnprocessedChars = null; 10034a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm redisplayAfterFormulaChange(); 100484614957604253d51296e06c97daced699a0a9deHans Boehm } 10054b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen 10064a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm @Override 1007fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen public boolean onPaste(ClipData clip) { 1008fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen final ClipData.Item item = clip.getItemCount() == 0 ? null : clip.getItemAt(0); 1009fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen if (item == null) { 1010fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen // nothing to paste, bail early... 1011fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen return false; 1012fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen } 1013fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen 1014fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen // Check if the item is a previously copied result, otherwise paste as raw text. 1015fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen final Uri uri = item.getUri(); 1016fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen if (uri != null && mEvaluator.isLastSaved(uri)) { 10174a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm if (mCurrentState == CalculatorState.ERROR 1018fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen || mCurrentState == CalculatorState.RESULT) { 10194a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm setState(CalculatorState.INPUT); 10204a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm mEvaluator.clear(); 102184614957604253d51296e06c97daced699a0a9deHans Boehm } 1022b13daf1050757fe3c69c2f0246de33e7e69b5fa9Hans Boehm mEvaluator.appendSaved(); 10234a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm redisplayAfterFormulaChange(); 1024fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen } else { 1025017de9893efd33e179db10bc71189e150bc3486dHans Boehm addChars(item.coerceToText(this).toString(), false); 102684614957604253d51296e06c97daced699a0a9deHans Boehm } 1027fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen return true; 10284b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen } 10294b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen} 1030