diff options
Diffstat (limited to 'src/com/android/calculator2/Calculator.java')
-rw-r--r-- | src/com/android/calculator2/Calculator.java | 1538 |
1 files changed, 0 insertions, 1538 deletions
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java deleted file mode 100644 index 81ab1f6..0000000 --- a/src/com/android/calculator2/Calculator.java +++ /dev/null @@ -1,1538 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// TODO: Copy & more general paste in formula? Note that this requires -// great care: Currently the text version of a displayed formula -// is not directly useful for re-evaluating the formula later, since -// it contains ellipses representing subexpressions evaluated with -// a different degree mode. Rather than supporting copy from the -// formula window, we may eventually want to support generation of a -// more useful text version in a separate window. It's not clear -// this is worth the added (code and user) complexity. - -package com.android.calculator2; - -import android.animation.Animator; -import android.animation.Animator.AnimatorListener; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; -import android.app.ActionBar; -import android.app.Activity; -import android.app.Fragment; -import android.app.FragmentManager; -import android.app.FragmentTransaction; -import android.content.ClipData; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.res.Resources; -import android.graphics.Color; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.StringRes; -import androidx.core.content.ContextCompat; -import androidx.viewpager.widget.ViewPager; -import android.text.Editable; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.text.style.ForegroundColorSpan; -import android.util.Log; -import android.util.Property; -import android.view.ActionMode; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnLongClickListener; -import android.view.ViewAnimationUtils; -import android.view.ViewGroupOverlay; -import android.view.ViewTreeObserver; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.widget.HorizontalScrollView; -import android.widget.TextView; -import android.widget.Toolbar; - -import com.android.calculator2.CalculatorFormula.OnTextSizeChangeListener; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectInput; -import java.io.ObjectInputStream; -import java.io.ObjectOutput; -import java.io.ObjectOutputStream; -import java.text.DecimalFormatSymbols; - -import static com.android.calculator2.CalculatorFormula.OnFormulaContextMenuClickListener; - -public class Calculator extends Activity - implements OnTextSizeChangeListener, OnLongClickListener, - AlertDialogFragment.OnClickListener, Evaluator.EvaluationListener /* for main result */, - DragLayout.CloseCallback, DragLayout.DragCallback { - - private static final String TAG = "Calculator"; - /** - * Constant for an invalid resource id. - */ - public static final int INVALID_RES_ID = -1; - - private enum CalculatorState { - INPUT, // Result and formula both visible, no evaluation requested, - // Though result may be visible on bottom line. - EVALUATE, // Both visible, evaluation requested, evaluation/animation incomplete. - // Not used for instant result evaluation. - INIT, // Very temporary state used as alternative to EVALUATE - // during reinitialization. Do not animate on completion. - INIT_FOR_RESULT, // Identical to INIT, but evaluation is known to terminate - // with result, and current expression has been copied to history. - ANIMATE, // Result computed, animation to enlarge result window in progress. - RESULT, // Result displayed, formula invisible. - // If we are in RESULT state, the formula was evaluated without - // error to initial precision. - // The current formula is now also the last history entry. - ERROR // Error displayed: Formula visible, result shows error message. - // Display similar to INPUT state. - } - // Normal transition sequence is - // INPUT -> EVALUATE -> ANIMATE -> RESULT (or ERROR) -> INPUT - // A RESULT -> ERROR transition is possible in rare corner cases, in which - // a higher precision evaluation exposes an error. This is possible, since we - // initially evaluate assuming we were given a well-defined problem. If we - // were actually asked to compute sqrt(<extremely tiny negative number>) we produce 0 - // unless we are asked for enough precision that we can distinguish the argument from zero. - // ERROR and RESULT are translated to INIT or INIT_FOR_RESULT state if the application - // is restarted in that state. This leads us to recompute and redisplay the result - // ASAP. We avoid saving the ANIMATE state or activating history in that state. - // In INIT_FOR_RESULT, and RESULT state, a copy of the current - // expression has been saved in the history db; in the other non-ANIMATE states, - // it has not. - // TODO: Possibly save a bit more information, e.g. its initial display string - // or most significant digit position, to speed up restart. - - private final Property<TextView, Integer> TEXT_COLOR = - new Property<TextView, Integer>(Integer.class, "textColor") { - @Override - public Integer get(TextView textView) { - return textView.getCurrentTextColor(); - } - - @Override - public void set(TextView textView, Integer textColor) { - textView.setTextColor(textColor); - } - }; - - private static final String NAME = "Calculator"; - private static final String KEY_DISPLAY_STATE = NAME + "_display_state"; - private static final String KEY_UNPROCESSED_CHARS = NAME + "_unprocessed_chars"; - /** - * Associated value is a byte array holding the evaluator state. - */ - private static final String KEY_EVAL_STATE = NAME + "_eval_state"; - private static final String KEY_INVERSE_MODE = NAME + "_inverse_mode"; - /** - * Associated value is an boolean holding the visibility state of the toolbar. - */ - private static final String KEY_SHOW_TOOLBAR = NAME + "_show_toolbar"; - - private final ViewTreeObserver.OnPreDrawListener mPreDrawListener = - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - mFormulaContainer.scrollTo(mFormulaText.getRight(), 0); - final ViewTreeObserver observer = mFormulaContainer.getViewTreeObserver(); - if (observer.isAlive()) { - observer.removeOnPreDrawListener(this); - } - return false; - } - }; - - private final Evaluator.Callback mEvaluatorCallback = new Evaluator.Callback() { - @Override - public void onMemoryStateChanged() { - mFormulaText.onMemoryStateChanged(); - } - - @Override - public void showMessageDialog(@StringRes int title, @StringRes int message, - @StringRes int positiveButtonLabel, String tag) { - AlertDialogFragment.showMessageDialog(Calculator.this, title, message, - positiveButtonLabel, tag); - - } - }; - - private final OnDisplayMemoryOperationsListener mOnDisplayMemoryOperationsListener = - new OnDisplayMemoryOperationsListener() { - @Override - public boolean shouldDisplayMemory() { - return mEvaluator.getMemoryIndex() != 0; - } - }; - - private final OnFormulaContextMenuClickListener mOnFormulaContextMenuClickListener = - new OnFormulaContextMenuClickListener() { - @Override - public boolean onPaste(ClipData clip) { - final ClipData.Item item = clip.getItemCount() == 0 ? null : clip.getItemAt(0); - if (item == null) { - // nothing to paste, bail early... - return false; - } - - // Check if the item is a previously copied result, otherwise paste as raw text. - final Uri uri = item.getUri(); - if (uri != null && mEvaluator.isLastSaved(uri)) { - clearIfNotInputState(); - mEvaluator.appendExpr(mEvaluator.getSavedIndex()); - redisplayAfterFormulaChange(); - } else { - addChars(item.coerceToText(Calculator.this).toString(), false); - } - return true; - } - - @Override - public void onMemoryRecall() { - clearIfNotInputState(); - long memoryIndex = mEvaluator.getMemoryIndex(); - if (memoryIndex != 0) { - mEvaluator.appendExpr(mEvaluator.getMemoryIndex()); - redisplayAfterFormulaChange(); - } - } - }; - - - private final TextWatcher mFormulaTextWatcher = new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence charSequence, int start, int count, int after) { - } - - @Override - public void afterTextChanged(Editable editable) { - final ViewTreeObserver observer = mFormulaContainer.getViewTreeObserver(); - if (observer.isAlive()) { - observer.removeOnPreDrawListener(mPreDrawListener); - observer.addOnPreDrawListener(mPreDrawListener); - } - } - }; - - private CalculatorState mCurrentState; - private Evaluator mEvaluator; - - private CalculatorDisplay mDisplayView; - private TextView mModeView; - private CalculatorFormula mFormulaText; - private CalculatorResult mResultText; - private HorizontalScrollView mFormulaContainer; - private DragLayout mDragLayout; - - private ViewPager mPadViewPager; - private View mDeleteButton; - private View mClearButton; - private View mEqualButton; - private View mMainCalculator; - - private TextView mInverseToggle; - private TextView mModeToggle; - - private View[] mInvertibleButtons; - private View[] mInverseButtons; - - private View mCurrentButton; - private Animator mCurrentAnimator; - - // Characters that were recently entered at the end of the display that have not yet - // been added to the underlying expression. - private String mUnprocessedChars = null; - - // Color to highlight unprocessed characters from physical keyboard. - // TODO: should probably match this to the error color? - private ForegroundColorSpan mUnprocessedColorSpan = new ForegroundColorSpan(Color.RED); - - // Whether the display is one line. - private boolean mIsOneLine; - - /** - * Map the old saved state to a new state reflecting requested result reevaluation. - */ - private CalculatorState mapFromSaved(CalculatorState savedState) { - switch (savedState) { - case RESULT: - case INIT_FOR_RESULT: - // Evaluation is expected to terminate normally. - return CalculatorState.INIT_FOR_RESULT; - case ERROR: - case INIT: - return CalculatorState.INIT; - case EVALUATE: - case INPUT: - return savedState; - default: // Includes ANIMATE state. - throw new AssertionError("Impossible saved state"); - } - } - - /** - * Restore Evaluator state and mCurrentState from savedInstanceState. - * Return true if the toolbar should be visible. - */ - private void restoreInstanceState(Bundle savedInstanceState) { - final CalculatorState savedState = CalculatorState.values()[ - savedInstanceState.getInt(KEY_DISPLAY_STATE, - CalculatorState.INPUT.ordinal())]; - setState(savedState); - CharSequence unprocessed = savedInstanceState.getCharSequence(KEY_UNPROCESSED_CHARS); - if (unprocessed != null) { - mUnprocessedChars = unprocessed.toString(); - } - byte[] state = savedInstanceState.getByteArray(KEY_EVAL_STATE); - if (state != null) { - try (ObjectInput in = new ObjectInputStream(new ByteArrayInputStream(state))) { - mEvaluator.restoreInstanceState(in); - } catch (Throwable ignored) { - // When in doubt, revert to clean state - mCurrentState = CalculatorState.INPUT; - mEvaluator.clearMain(); - } - } - if (savedInstanceState.getBoolean(KEY_SHOW_TOOLBAR, true)) { - showAndMaybeHideToolbar(); - } else { - mDisplayView.hideToolbar(); - } - onInverseToggled(savedInstanceState.getBoolean(KEY_INVERSE_MODE)); - // TODO: We're currently not saving and restoring scroll position. - // We probably should. Details may require care to deal with: - // - new display size - // - slow recomputation if we've scrolled far. - } - - private void restoreDisplay() { - onModeChanged(mEvaluator.getDegreeMode(Evaluator.MAIN_INDEX)); - if (mCurrentState != CalculatorState.RESULT - && mCurrentState != CalculatorState.INIT_FOR_RESULT) { - redisplayFormula(); - } - if (mCurrentState == CalculatorState.INPUT) { - // This resultText will explicitly call evaluateAndNotify when ready. - mResultText.setShouldEvaluateResult(CalculatorResult.SHOULD_EVALUATE, this); - } else { - // Just reevaluate. - setState(mapFromSaved(mCurrentState)); - // Request evaluation when we know display width. - mResultText.setShouldEvaluateResult(CalculatorResult.SHOULD_REQUIRE, this); - } - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_calculator_main); - setActionBar((Toolbar) findViewById(R.id.toolbar)); - - // Hide all default options in the ActionBar. - getActionBar().setDisplayOptions(0); - - // Ensure the toolbar stays visible while the options menu is displayed. - getActionBar().addOnMenuVisibilityListener(new ActionBar.OnMenuVisibilityListener() { - @Override - public void onMenuVisibilityChanged(boolean isVisible) { - mDisplayView.setForceToolbarVisible(isVisible); - } - }); - - mMainCalculator = findViewById(R.id.main_calculator); - mDisplayView = (CalculatorDisplay) findViewById(R.id.display); - mModeView = (TextView) findViewById(R.id.mode); - mFormulaText = (CalculatorFormula) findViewById(R.id.formula); - mResultText = (CalculatorResult) findViewById(R.id.result); - mFormulaContainer = (HorizontalScrollView) findViewById(R.id.formula_container); - mEvaluator = Evaluator.getInstance(this); - mEvaluator.setCallback(mEvaluatorCallback); - mResultText.setEvaluator(mEvaluator, Evaluator.MAIN_INDEX); - KeyMaps.setActivity(this); - - mPadViewPager = (ViewPager) findViewById(R.id.pad_pager); - mDeleteButton = findViewById(R.id.del); - mClearButton = findViewById(R.id.clr); - final View numberPad = findViewById(R.id.pad_numeric); - mEqualButton = numberPad.findViewById(R.id.eq); - if (mEqualButton == null || mEqualButton.getVisibility() != View.VISIBLE) { - mEqualButton = findViewById(R.id.pad_operator).findViewById(R.id.eq); - } - final TextView decimalPointButton = (TextView) numberPad.findViewById(R.id.dec_point); - decimalPointButton.setText(getDecimalSeparator()); - - mInverseToggle = (TextView) findViewById(R.id.toggle_inv); - mModeToggle = (TextView) findViewById(R.id.toggle_mode); - - mIsOneLine = mResultText.getVisibility() == View.INVISIBLE; - - mInvertibleButtons = new View[] { - findViewById(R.id.fun_sin), - findViewById(R.id.fun_cos), - findViewById(R.id.fun_tan), - findViewById(R.id.fun_ln), - findViewById(R.id.fun_log), - findViewById(R.id.op_sqrt) - }; - mInverseButtons = new View[] { - findViewById(R.id.fun_arcsin), - findViewById(R.id.fun_arccos), - findViewById(R.id.fun_arctan), - findViewById(R.id.fun_exp), - findViewById(R.id.fun_10pow), - findViewById(R.id.op_sqr) - }; - - mDragLayout = (DragLayout) findViewById(R.id.drag_layout); - mDragLayout.removeDragCallback(this); - mDragLayout.addDragCallback(this); - mDragLayout.setCloseCallback(this); - - mFormulaText.setOnContextMenuClickListener(mOnFormulaContextMenuClickListener); - mFormulaText.setOnDisplayMemoryOperationsListener(mOnDisplayMemoryOperationsListener); - - mFormulaText.setOnTextSizeChangeListener(this); - mFormulaText.addTextChangedListener(mFormulaTextWatcher); - mDeleteButton.setOnLongClickListener(this); - - if (savedInstanceState != null) { - restoreInstanceState(savedInstanceState); - } else { - mCurrentState = CalculatorState.INPUT; - mEvaluator.clearMain(); - showAndMaybeHideToolbar(); - onInverseToggled(false); - } - restoreDisplay(); - } - - @Override - protected void onResume() { - super.onResume(); - if (mDisplayView.isToolbarVisible()) { - showAndMaybeHideToolbar(); - } - // If HistoryFragment is showing, hide the main Calculator elements from accessibility. - // This is because Talkback does not use visibility as a cue for RelativeLayout elements, - // and RelativeLayout is the base class of DragLayout. - // If we did not do this, it would be possible to traverse to main Calculator elements from - // HistoryFragment. - mMainCalculator.setImportantForAccessibility( - mDragLayout.isOpen() ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); - } - - @Override - protected void onSaveInstanceState(@NonNull Bundle outState) { - mEvaluator.cancelAll(true); - // If there's an animation in progress, cancel it first to ensure our state is up-to-date. - if (mCurrentAnimator != null) { - mCurrentAnimator.cancel(); - } - - super.onSaveInstanceState(outState); - outState.putInt(KEY_DISPLAY_STATE, mCurrentState.ordinal()); - outState.putCharSequence(KEY_UNPROCESSED_CHARS, mUnprocessedChars); - ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream(); - try (ObjectOutput out = new ObjectOutputStream(byteArrayStream)) { - mEvaluator.saveInstanceState(out); - } catch (IOException e) { - // Impossible; No IO involved. - throw new AssertionError("Impossible IO exception", e); - } - outState.putByteArray(KEY_EVAL_STATE, byteArrayStream.toByteArray()); - outState.putBoolean(KEY_INVERSE_MODE, mInverseToggle.isSelected()); - outState.putBoolean(KEY_SHOW_TOOLBAR, mDisplayView.isToolbarVisible()); - // We must wait for asynchronous writes to complete, since outState may contain - // references to expressions being written. - mEvaluator.waitForWrites(); - } - - // Set the state, updating delete label and display colors. - // This restores display positions on moving to INPUT. - // But movement/animation for moving to RESULT has already been done. - private void setState(CalculatorState state) { - if (mCurrentState != state) { - if (state == CalculatorState.INPUT) { - // We'll explicitly request evaluation from now on. - mResultText.setShouldEvaluateResult(CalculatorResult.SHOULD_NOT_EVALUATE, null); - restoreDisplayPositions(); - } - mCurrentState = state; - - if (mCurrentState == CalculatorState.RESULT) { - // No longer do this for ERROR; allow mistakes to be corrected. - mDeleteButton.setVisibility(View.GONE); - mClearButton.setVisibility(View.VISIBLE); - } else { - mDeleteButton.setVisibility(View.VISIBLE); - mClearButton.setVisibility(View.GONE); - } - - if (mIsOneLine) { - if (mCurrentState == CalculatorState.RESULT - || mCurrentState == CalculatorState.EVALUATE - || mCurrentState == CalculatorState.ANIMATE) { - mFormulaText.setVisibility(View.VISIBLE); - mResultText.setVisibility(View.VISIBLE); - } else if (mCurrentState == CalculatorState.ERROR) { - mFormulaText.setVisibility(View.INVISIBLE); - mResultText.setVisibility(View.VISIBLE); - } else { - mFormulaText.setVisibility(View.VISIBLE); - mResultText.setVisibility(View.INVISIBLE); - } - } - - if (mCurrentState == CalculatorState.ERROR) { - final int errorColor = - ContextCompat.getColor(this, R.color.calculator_error_color); - mFormulaText.setTextColor(errorColor); - mResultText.setTextColor(errorColor); - getWindow().setStatusBarColor(errorColor); - } else if (mCurrentState != CalculatorState.RESULT) { - mFormulaText.setTextColor( - ContextCompat.getColor(this, R.color.display_formula_text_color)); - mResultText.setTextColor( - ContextCompat.getColor(this, R.color.display_result_text_color)); - getWindow().setStatusBarColor( - ContextCompat.getColor(this, R.color.calculator_statusbar_color)); - } - - invalidateOptionsMenu(); - } - } - - public boolean isResultLayout() { - // Note that ERROR has INPUT, not RESULT layout. - return mCurrentState == CalculatorState.INIT_FOR_RESULT - || mCurrentState == CalculatorState.RESULT; - } - - public boolean isOneLine() { - return mIsOneLine; - } - - @Override - protected void onDestroy() { - mDragLayout.removeDragCallback(this); - super.onDestroy(); - } - - /** - * Destroy the evaluator and close the underlying database. - */ - public void destroyEvaluator() { - mEvaluator.destroyEvaluator(); - } - - @Override - public void onActionModeStarted(ActionMode mode) { - super.onActionModeStarted(mode); - if (mode.getTag() == CalculatorFormula.TAG_ACTION_MODE) { - mFormulaContainer.scrollTo(mFormulaText.getRight(), 0); - } - } - - /** - * Stop any active ActionMode or ContextMenu for copy/paste actions. - * Return true if there was one. - */ - private boolean stopActionModeOrContextMenu() { - return mResultText.stopActionModeOrContextMenu() - || mFormulaText.stopActionModeOrContextMenu(); - } - - @Override - public void onUserInteraction() { - super.onUserInteraction(); - - // If there's an animation in progress, end it immediately, so the user interaction can - // be handled. - if (mCurrentAnimator != null) { - mCurrentAnimator.end(); - } - } - - @Override - public boolean dispatchTouchEvent(MotionEvent e) { - if (e.getActionMasked() == MotionEvent.ACTION_DOWN) { - stopActionModeOrContextMenu(); - - final HistoryFragment historyFragment = getHistoryFragment(); - if (mDragLayout.isOpen() && historyFragment != null) { - historyFragment.stopActionModeOrContextMenu(); - } - } - return super.dispatchTouchEvent(e); - } - - @Override - public void onBackPressed() { - if (!stopActionModeOrContextMenu()) { - final HistoryFragment historyFragment = getHistoryFragment(); - if (mDragLayout.isOpen() && historyFragment != null) { - if (!historyFragment.stopActionModeOrContextMenu()) { - removeHistoryFragment(); - } - return; - } - if (mPadViewPager != null && mPadViewPager.getCurrentItem() != 0) { - // Select the previous pad. - mPadViewPager.setCurrentItem(mPadViewPager.getCurrentItem() - 1); - } else { - // If the user is currently looking at the first pad (or the pad is not paged), - // allow the system to handle the Back button. - super.onBackPressed(); - } - } - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - // Allow the system to handle special key codes (e.g. "BACK" or "DPAD"). - switch (keyCode) { - case KeyEvent.KEYCODE_BACK: - case KeyEvent.KEYCODE_ESCAPE: - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_DOWN: - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_DPAD_RIGHT: - return super.onKeyUp(keyCode, event); - } - - // Stop the action mode or context menu if it's showing. - stopActionModeOrContextMenu(); - - // Always cancel unrequested in-progress evaluation of the main expression, so that - // we don't have to worry about subsequent asynchronous completion. - // Requested in-progress evaluations are handled below. - cancelUnrequested(); - - switch (keyCode) { - case KeyEvent.KEYCODE_NUMPAD_ENTER: - case KeyEvent.KEYCODE_ENTER: - case KeyEvent.KEYCODE_DPAD_CENTER: - mCurrentButton = mEqualButton; - onEquals(); - return true; - case KeyEvent.KEYCODE_DEL: - mCurrentButton = mDeleteButton; - onDelete(); - return true; - case KeyEvent.KEYCODE_CLEAR: - mCurrentButton = mClearButton; - onClear(); - return true; - default: - cancelIfEvaluating(false); - final int raw = event.getKeyCharacterMap().get(keyCode, event.getMetaState()); - if ((raw & KeyCharacterMap.COMBINING_ACCENT) != 0) { - return true; // discard - } - // Try to discard non-printing characters and the like. - // The user will have to explicitly delete other junk that gets past us. - if (Character.isIdentifierIgnorable(raw) || Character.isWhitespace(raw)) { - return true; - } - char c = (char) raw; - if (c == '=') { - mCurrentButton = mEqualButton; - onEquals(); - } else { - addChars(String.valueOf(c), true); - redisplayAfterFormulaChange(); - } - return true; - } - } - - /** - * Invoked whenever the inverse button is toggled to update the UI. - * - * @param showInverse {@code true} if inverse functions should be shown - */ - private void onInverseToggled(boolean showInverse) { - mInverseToggle.setSelected(showInverse); - if (showInverse) { - mInverseToggle.setContentDescription(getString(R.string.desc_inv_on)); - for (View invertibleButton : mInvertibleButtons) { - invertibleButton.setVisibility(View.GONE); - } - for (View inverseButton : mInverseButtons) { - inverseButton.setVisibility(View.VISIBLE); - } - } else { - mInverseToggle.setContentDescription(getString(R.string.desc_inv_off)); - for (View invertibleButton : mInvertibleButtons) { - invertibleButton.setVisibility(View.VISIBLE); - } - for (View inverseButton : mInverseButtons) { - inverseButton.setVisibility(View.GONE); - } - } - } - - /** - * Invoked whenever the deg/rad mode may have changed to update the UI. Note that the mode has - * not necessarily actually changed where this is invoked. - * - * @param degreeMode {@code true} if in degree mode - */ - private void onModeChanged(boolean degreeMode) { - if (degreeMode) { - mModeView.setText(R.string.mode_deg); - mModeView.setContentDescription(getString(R.string.desc_mode_deg)); - - mModeToggle.setText(R.string.mode_rad); - mModeToggle.setContentDescription(getString(R.string.desc_switch_rad)); - } else { - mModeView.setText(R.string.mode_rad); - mModeView.setContentDescription(getString(R.string.desc_mode_rad)); - - mModeToggle.setText(R.string.mode_deg); - mModeToggle.setContentDescription(getString(R.string.desc_switch_deg)); - } - } - - private void removeHistoryFragment() { - final FragmentManager manager = getFragmentManager(); - if (manager != null && !manager.isDestroyed()) { - manager.popBackStack(HistoryFragment.TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); - } - - // When HistoryFragment is hidden, the main Calculator is important for accessibility again. - mMainCalculator.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); - } - - /** - * Switch to INPUT from RESULT state in response to input of the specified button_id. - * View.NO_ID is treated as an incomplete function id. - */ - private void switchToInput(int button_id) { - if (KeyMaps.isBinary(button_id) || KeyMaps.isSuffix(button_id)) { - mEvaluator.collapse(mEvaluator.getMaxIndex() /* Most recent history entry */); - } else { - announceClearedForAccessibility(); - mEvaluator.clearMain(); - } - setState(CalculatorState.INPUT); - } - - // Add the given button id to input expression. - // If appropriate, clear the expression before doing so. - private void addKeyToExpr(int id) { - if (mCurrentState == CalculatorState.ERROR) { - setState(CalculatorState.INPUT); - } else if (mCurrentState == CalculatorState.RESULT) { - switchToInput(id); - } - if (!mEvaluator.append(id)) { - // TODO: Some user visible feedback? - } - } - - /** - * Add the given button id to input expression, assuming it was explicitly - * typed/touched. - * We perform slightly more aggressive correction than in pasted expressions. - */ - private void addExplicitKeyToExpr(int id) { - if (mCurrentState == CalculatorState.INPUT && id == R.id.op_sub) { - mEvaluator.getExpr(Evaluator.MAIN_INDEX).removeTrailingAdditiveOperators(); - } - addKeyToExpr(id); - } - - public void evaluateInstantIfNecessary() { - if (mCurrentState == CalculatorState.INPUT - && mEvaluator.getExpr(Evaluator.MAIN_INDEX).hasInterestingOps()) { - mEvaluator.evaluateAndNotify(Evaluator.MAIN_INDEX, this, mResultText); - } - } - - private void redisplayAfterFormulaChange() { - // TODO: Could do this more incrementally. - redisplayFormula(); - setState(CalculatorState.INPUT); - mResultText.clear(); - if (haveUnprocessed()) { - // Force reevaluation when text is deleted, even if expression is unchanged. - mEvaluator.touch(); - } else { - evaluateInstantIfNecessary(); - } - } - - /** - * Show the toolbar. - * Automatically hide it again if it's not relevant to current formula. - */ - private void showAndMaybeHideToolbar() { - final boolean shouldBeVisible = - mCurrentState == CalculatorState.INPUT && mEvaluator.hasTrigFuncs(); - mDisplayView.showToolbar(!shouldBeVisible); - } - - /** - * Display or hide the toolbar depending on calculator state. - */ - private void showOrHideToolbar() { - final boolean shouldBeVisible = - mCurrentState == CalculatorState.INPUT && mEvaluator.hasTrigFuncs(); - if (shouldBeVisible) { - mDisplayView.showToolbar(false); - } else { - mDisplayView.hideToolbar(); - } - } - - public void onButtonClick(View view) { - // Any animation is ended before we get here. - mCurrentButton = view; - stopActionModeOrContextMenu(); - - // See onKey above for the rationale behind some of the behavior below: - cancelUnrequested(); - - final int id = view.getId(); - switch (id) { - case R.id.eq: - onEquals(); - break; - case R.id.del: - onDelete(); - break; - case R.id.clr: - onClear(); - return; // Toolbar visibility adjusted at end of animation. - case R.id.toggle_inv: - final boolean selected = !mInverseToggle.isSelected(); - mInverseToggle.setSelected(selected); - onInverseToggled(selected); - if (mCurrentState == CalculatorState.RESULT) { - mResultText.redisplay(); // In case we cancelled reevaluation. - } - break; - case R.id.toggle_mode: - cancelIfEvaluating(false); - final boolean mode = !mEvaluator.getDegreeMode(Evaluator.MAIN_INDEX); - if (mCurrentState == CalculatorState.RESULT - && mEvaluator.getExpr(Evaluator.MAIN_INDEX).hasTrigFuncs()) { - // Capture current result evaluated in old mode. - mEvaluator.collapse(mEvaluator.getMaxIndex()); - redisplayFormula(); - } - // In input mode, we reinterpret already entered trig functions. - mEvaluator.setDegreeMode(mode); - onModeChanged(mode); - // Show the toolbar to highlight the mode change. - showAndMaybeHideToolbar(); - setState(CalculatorState.INPUT); - mResultText.clear(); - if (!haveUnprocessed()) { - evaluateInstantIfNecessary(); - } - return; - default: - cancelIfEvaluating(false); - if (haveUnprocessed()) { - // For consistency, append as uninterpreted characters. - // This may actually be useful for a left parenthesis. - addChars(KeyMaps.toString(this, id), true); - } else { - addExplicitKeyToExpr(id); - redisplayAfterFormulaChange(); - } - break; - } - showOrHideToolbar(); - } - - void redisplayFormula() { - SpannableStringBuilder formula - = mEvaluator.getExpr(Evaluator.MAIN_INDEX).toSpannableStringBuilder(this); - if (mUnprocessedChars != null) { - // Add and highlight characters we couldn't process. - formula.append(mUnprocessedChars, mUnprocessedColorSpan, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - mFormulaText.changeTextTo(formula); - mFormulaText.setContentDescription(TextUtils.isEmpty(formula) - ? getString(R.string.desc_formula) : null); - } - - @Override - public boolean onLongClick(View view) { - mCurrentButton = view; - - if (view.getId() == R.id.del) { - onClear(); - return true; - } - return false; - } - - // Initial evaluation completed successfully. Initiate display. - public void onEvaluate(long index, int initDisplayPrec, int msd, int leastDigPos, - String truncatedWholeNumber) { - if (index != Evaluator.MAIN_INDEX) { - throw new AssertionError("Unexpected evaluation result index\n"); - } - - // Invalidate any options that may depend on the current result. - invalidateOptionsMenu(); - - mResultText.onEvaluate(index, initDisplayPrec, msd, leastDigPos, truncatedWholeNumber); - if (mCurrentState != CalculatorState.INPUT) { - // In EVALUATE, INIT, RESULT, or INIT_FOR_RESULT state. - onResult(mCurrentState == CalculatorState.EVALUATE /* animate */, - mCurrentState == CalculatorState.INIT_FOR_RESULT - || mCurrentState == CalculatorState.RESULT /* previously preserved */); - } - } - - // Reset state to reflect evaluator cancellation. Invoked by evaluator. - public void onCancelled(long index) { - // Index is Evaluator.MAIN_INDEX. We should be in EVALUATE state. - setState(CalculatorState.INPUT); - mResultText.onCancelled(index); - } - - // Reevaluation completed; ask result to redisplay current value. - public void onReevaluate(long index) { - // Index is Evaluator.MAIN_INDEX. - mResultText.onReevaluate(index); - } - - @Override - public void onTextSizeChanged(final TextView textView, float oldSize) { - if (mCurrentState != CalculatorState.INPUT) { - // Only animate text changes that occur from user input. - return; - } - - // Calculate the values needed to perform the scale and translation animations, - // maintaining the same apparent baseline for the displayed text. - final float textScale = oldSize / textView.getTextSize(); - final float translationX = (1.0f - textScale) * - (textView.getWidth() / 2.0f - textView.getPaddingEnd()); - final float translationY = (1.0f - textScale) * - (textView.getHeight() / 2.0f - textView.getPaddingBottom()); - - final AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether( - ObjectAnimator.ofFloat(textView, View.SCALE_X, textScale, 1.0f), - ObjectAnimator.ofFloat(textView, View.SCALE_Y, textScale, 1.0f), - ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, translationX, 0.0f), - ObjectAnimator.ofFloat(textView, View.TRANSLATION_Y, translationY, 0.0f)); - animatorSet.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime)); - animatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); - animatorSet.start(); - } - - /** - * Cancel any in-progress explicitly requested evaluations. - * @param quiet suppress pop-up message. Explicit evaluation can change the expression - value, and certainly changes the display, so it seems reasonable to warn. - * @return true if there was such an evaluation - */ - private boolean cancelIfEvaluating(boolean quiet) { - if (mCurrentState == CalculatorState.EVALUATE) { - mEvaluator.cancel(Evaluator.MAIN_INDEX, quiet); - return true; - } else { - return false; - } - } - - - private void cancelUnrequested() { - if (mCurrentState == CalculatorState.INPUT) { - mEvaluator.cancel(Evaluator.MAIN_INDEX, true); - } - } - - private boolean haveUnprocessed() { - return mUnprocessedChars != null && !mUnprocessedChars.isEmpty(); - } - - private void onEquals() { - // Ignore if in non-INPUT state, or if there are no operators. - if (mCurrentState == CalculatorState.INPUT) { - if (haveUnprocessed()) { - setState(CalculatorState.EVALUATE); - onError(Evaluator.MAIN_INDEX, R.string.error_syntax); - } else if (mEvaluator.getExpr(Evaluator.MAIN_INDEX).hasInterestingOps()) { - setState(CalculatorState.EVALUATE); - mEvaluator.requireResult(Evaluator.MAIN_INDEX, this, mResultText); - } - } - } - - private void onDelete() { - // Delete works like backspace; remove the last character or operator from the expression. - // Note that we handle keyboard delete exactly like the delete button. For - // example the delete button can be used to delete a character from an incomplete - // function name typed on a physical keyboard. - // This should be impossible in RESULT state. - // If there is an in-progress explicit evaluation, just cancel it and return. - if (cancelIfEvaluating(false)) return; - setState(CalculatorState.INPUT); - if (haveUnprocessed()) { - mUnprocessedChars = mUnprocessedChars.substring(0, mUnprocessedChars.length() - 1); - } else { - mEvaluator.delete(); - } - if (mEvaluator.getExpr(Evaluator.MAIN_INDEX).isEmpty() && !haveUnprocessed()) { - // Resulting formula won't be announced, since it's empty. - announceClearedForAccessibility(); - } - redisplayAfterFormulaChange(); - } - - private void reveal(View sourceView, int colorRes, AnimatorListener listener) { - final ViewGroupOverlay groupOverlay = - (ViewGroupOverlay) getWindow().getDecorView().getOverlay(); - - final Rect displayRect = new Rect(); - mDisplayView.getGlobalVisibleRect(displayRect); - - // Make reveal cover the display and status bar. - final View revealView = new View(this); - revealView.setBottom(displayRect.bottom); - revealView.setLeft(displayRect.left); - revealView.setRight(displayRect.right); - revealView.setBackgroundColor(ContextCompat.getColor(this, colorRes)); - groupOverlay.add(revealView); - - final int[] clearLocation = new int[2]; - sourceView.getLocationInWindow(clearLocation); - clearLocation[0] += sourceView.getWidth() / 2; - clearLocation[1] += sourceView.getHeight() / 2; - - final int revealCenterX = clearLocation[0] - revealView.getLeft(); - final int revealCenterY = clearLocation[1] - revealView.getTop(); - - final double x1_2 = Math.pow(revealView.getLeft() - revealCenterX, 2); - final double x2_2 = Math.pow(revealView.getRight() - revealCenterX, 2); - final double y_2 = Math.pow(revealView.getTop() - revealCenterY, 2); - final float revealRadius = (float) Math.max(Math.sqrt(x1_2 + y_2), Math.sqrt(x2_2 + y_2)); - - final Animator revealAnimator = - ViewAnimationUtils.createCircularReveal(revealView, - revealCenterX, revealCenterY, 0.0f, revealRadius); - revealAnimator.setDuration( - getResources().getInteger(android.R.integer.config_longAnimTime)); - revealAnimator.addListener(listener); - - final Animator alphaAnimator = ObjectAnimator.ofFloat(revealView, View.ALPHA, 0.0f); - alphaAnimator.setDuration( - getResources().getInteger(android.R.integer.config_mediumAnimTime)); - - final AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.play(revealAnimator).before(alphaAnimator); - animatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); - animatorSet.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - groupOverlay.remove(revealView); - mCurrentAnimator = null; - } - }); - - mCurrentAnimator = animatorSet; - animatorSet.start(); - } - - private void announceClearedForAccessibility() { - mResultText.announceForAccessibility(getResources().getString(R.string.cleared)); - } - - public void onClearAnimationEnd() { - mUnprocessedChars = null; - mResultText.clear(); - mEvaluator.clearMain(); - setState(CalculatorState.INPUT); - redisplayFormula(); - } - - private void onClear() { - if (mEvaluator.getExpr(Evaluator.MAIN_INDEX).isEmpty() && !haveUnprocessed()) { - return; - } - cancelIfEvaluating(true); - announceClearedForAccessibility(); - reveal(mCurrentButton, R.color.calculator_primary_color, new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - onClearAnimationEnd(); - showOrHideToolbar(); - } - }); - } - - // Evaluation encountered en error. Display the error. - @Override - public void onError(final long index, final int errorResourceId) { - if (index != Evaluator.MAIN_INDEX) { - throw new AssertionError("Unexpected error source"); - } - if (mCurrentState == CalculatorState.EVALUATE) { - setState(CalculatorState.ANIMATE); - mResultText.announceForAccessibility(getResources().getString(errorResourceId)); - reveal(mCurrentButton, R.color.calculator_error_color, - new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - setState(CalculatorState.ERROR); - mResultText.onError(index, errorResourceId); - } - }); - } else if (mCurrentState == CalculatorState.INIT - || mCurrentState == CalculatorState.INIT_FOR_RESULT /* very unlikely */) { - setState(CalculatorState.ERROR); - mResultText.onError(index, errorResourceId); - } else { - mResultText.clear(); - } - } - - // Animate movement of result into the top formula slot. - // Result window now remains translated in the top slot while the result is displayed. - // (We convert it back to formula use only when the user provides new input.) - // Historical note: In the Lollipop version, this invisibly and instantaneously moved - // formula and result displays back at the end of the animation. We no longer do that, - // so that we can continue to properly support scrolling of the result. - // We assume the result already contains the text to be expanded. - private void onResult(boolean animate, boolean resultWasPreserved) { - // Calculate the textSize that would be used to display the result in the formula. - // For scrollable results just use the minimum textSize to maximize the number of digits - // that are visible on screen. - float textSize = mFormulaText.getMinimumTextSize(); - if (!mResultText.isScrollable()) { - textSize = mFormulaText.getVariableTextSize(mResultText.getText().toString()); - } - - // Scale the result to match the calculated textSize, minimizing the jump-cut transition - // when a result is reused in a subsequent expression. - final float resultScale = textSize / mResultText.getTextSize(); - - // Set the result's pivot to match its gravity. - mResultText.setPivotX(mResultText.getWidth() - mResultText.getPaddingRight()); - mResultText.setPivotY(mResultText.getHeight() - mResultText.getPaddingBottom()); - - // Calculate the necessary translations so the result takes the place of the formula and - // the formula moves off the top of the screen. - final float resultTranslationY = (mFormulaContainer.getBottom() - mResultText.getBottom()) - - (mFormulaText.getPaddingBottom() - mResultText.getPaddingBottom()); - float formulaTranslationY = -mFormulaContainer.getBottom(); - if (mIsOneLine) { - // Position the result text. - mResultText.setY(mResultText.getBottom()); - formulaTranslationY = -(findViewById(R.id.toolbar).getBottom() - + mFormulaContainer.getBottom()); - } - - // Change the result's textColor to match the formula. - final int formulaTextColor = mFormulaText.getCurrentTextColor(); - - if (resultWasPreserved) { - // Result was previously addded to history. - mEvaluator.represerve(); - } else { - // Add current result to history. - mEvaluator.preserve(Evaluator.MAIN_INDEX, true); - } - - if (animate) { - mResultText.announceForAccessibility(getResources().getString(R.string.desc_eq)); - mResultText.announceForAccessibility(mResultText.getText()); - setState(CalculatorState.ANIMATE); - final AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether( - ObjectAnimator.ofPropertyValuesHolder(mResultText, - PropertyValuesHolder.ofFloat(View.SCALE_X, resultScale), - PropertyValuesHolder.ofFloat(View.SCALE_Y, resultScale), - PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, resultTranslationY)), - ObjectAnimator.ofArgb(mResultText, TEXT_COLOR, formulaTextColor), - ObjectAnimator.ofFloat(mFormulaContainer, View.TRANSLATION_Y, - formulaTranslationY)); - animatorSet.setDuration(getResources().getInteger( - android.R.integer.config_longAnimTime)); - animatorSet.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - setState(CalculatorState.RESULT); - mCurrentAnimator = null; - } - }); - - mCurrentAnimator = animatorSet; - animatorSet.start(); - } else /* No animation desired; get there fast when restarting */ { - mResultText.setScaleX(resultScale); - mResultText.setScaleY(resultScale); - mResultText.setTranslationY(resultTranslationY); - mResultText.setTextColor(formulaTextColor); - mFormulaContainer.setTranslationY(formulaTranslationY); - setState(CalculatorState.RESULT); - } - } - - // Restore positions of the formula and result displays back to their original, - // pre-animation state. - private void restoreDisplayPositions() { - // Clear result. - mResultText.setText(""); - // Reset all of the values modified during the animation. - mResultText.setScaleX(1.0f); - mResultText.setScaleY(1.0f); - mResultText.setTranslationX(0.0f); - mResultText.setTranslationY(0.0f); - mFormulaContainer.setTranslationY(0.0f); - - mFormulaText.requestFocus(); - } - - @Override - public void onClick(AlertDialogFragment fragment, int which) { - if (which == DialogInterface.BUTTON_POSITIVE) { - if (HistoryFragment.CLEAR_DIALOG_TAG.equals(fragment.getTag())) { - // TODO: Try to preserve the current, saved, and memory expressions. How should we - // handle expressions to which they refer? - mEvaluator.clearEverything(); - // TODO: It's not clear what we should really do here. This is an initial hack. - // May want to make onClearAnimationEnd() private if/when we fix this. - onClearAnimationEnd(); - mEvaluatorCallback.onMemoryStateChanged(); - onBackPressed(); - } else if (Evaluator.TIMEOUT_DIALOG_TAG.equals(fragment.getTag())) { - // Timeout extension request. - mEvaluator.setLongTimeout(); - } else { - Log.e(TAG, "Unknown AlertDialogFragment click:" + fragment.getTag()); - } - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - - getMenuInflater().inflate(R.menu.activity_calculator, menu); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - - // Show the leading option when displaying a result. - menu.findItem(R.id.menu_leading).setVisible(mCurrentState == CalculatorState.RESULT); - - // Show the fraction option when displaying a rational result. - boolean visible = mCurrentState == CalculatorState.RESULT; - final UnifiedReal mainResult = mEvaluator.getResult(Evaluator.MAIN_INDEX); - // mainResult should never be null, but it happens. Check as a workaround to protect - // against crashes until we find the root cause (b/34763650). - visible &= mainResult != null && mainResult.exactlyDisplayable(); - menu.findItem(R.id.menu_fraction).setVisible(visible); - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_history: - showHistoryFragment(); - return true; - case R.id.menu_leading: - displayFull(); - return true; - case R.id.menu_fraction: - displayFraction(); - return true; - case R.id.menu_licenses: - startActivity(new Intent(this, Licenses.class)); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - /* Begin override CloseCallback method. */ - - @Override - public void onClose() { - removeHistoryFragment(); - } - - /* End override CloseCallback method. */ - - /* Begin override DragCallback methods */ - - public void onStartDraggingOpen() { - mDisplayView.hideToolbar(); - showHistoryFragment(); - } - - @Override - public void onInstanceStateRestored(boolean isOpen) { - } - - @Override - public void whileDragging(float yFraction) { - } - - @Override - public boolean shouldCaptureView(View view, int x, int y) { - return view.getId() == R.id.history_frame - && (mDragLayout.isMoving() || mDragLayout.isViewUnder(view, x, y)); - } - - @Override - public int getDisplayHeight() { - return mDisplayView.getMeasuredHeight(); - } - - /* End override DragCallback methods */ - - /** - * Change evaluation state to one that's friendly to the history fragment. - * Return false if that was not easily possible. - */ - private boolean prepareForHistory() { - if (mCurrentState == CalculatorState.ANIMATE) { - // End the current animation and signal that preparation has failed. - // onUserInteraction is unreliable and onAnimationEnd() is asynchronous, so we - // aren't guaranteed to be out of the ANIMATE state by the time prepareForHistory is - // called. - if (mCurrentAnimator != null) { - mCurrentAnimator.end(); - } - return false; - } else if (mCurrentState == CalculatorState.EVALUATE) { - // Cancel current evaluation - cancelIfEvaluating(true /* quiet */ ); - setState(CalculatorState.INPUT); - return true; - } else if (mCurrentState == CalculatorState.INIT) { - // Easiest to just refuse. Otherwise we can see a state change - // while in history mode, which causes all sorts of problems. - // TODO: Consider other alternatives. If we're just doing the decimal conversion - // at the end of an evaluation, we could treat this as RESULT state. - return false; - } - // We should be in INPUT, INIT_FOR_RESULT, RESULT, or ERROR state. - return true; - } - - private HistoryFragment getHistoryFragment() { - final FragmentManager manager = getFragmentManager(); - if (manager == null || manager.isDestroyed()) { - return null; - } - final Fragment fragment = manager.findFragmentByTag(HistoryFragment.TAG); - return fragment == null || fragment.isRemoving() ? null : (HistoryFragment) fragment; - } - - private void showHistoryFragment() { - if (getHistoryFragment() != null) { - // If the fragment already exists, do nothing. - return; - } - - final FragmentManager manager = getFragmentManager(); - if (manager == null || manager.isDestroyed() || !prepareForHistory()) { - // If the history fragment can not be shown, close the draglayout. - mDragLayout.setClosed(); - return; - } - - stopActionModeOrContextMenu(); - manager.beginTransaction() - .replace(R.id.history_frame, new HistoryFragment(), HistoryFragment.TAG) - .setTransition(FragmentTransaction.TRANSIT_NONE) - .addToBackStack(HistoryFragment.TAG) - .commit(); - - // When HistoryFragment is visible, hide all descendants of the main Calculator view. - mMainCalculator.setImportantForAccessibility( - View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - // TODO: pass current scroll position of result - } - - private void displayMessage(String title, String message) { - AlertDialogFragment.showMessageDialog(this, title, message, null, null /* tag */); - } - - private void displayFraction() { - UnifiedReal result = mEvaluator.getResult(Evaluator.MAIN_INDEX); - displayMessage(getString(R.string.menu_fraction), - KeyMaps.translateResult(result.toNiceString())); - } - - // Display full result to currently evaluated precision - private void displayFull() { - Resources res = getResources(); - String msg = mResultText.getFullText(true /* withSeparators */) + " "; - if (mResultText.fullTextIsExact()) { - msg += res.getString(R.string.exact); - } else { - msg += res.getString(R.string.approximate); - } - displayMessage(getString(R.string.menu_leading), msg); - } - - /** - * Add input characters to the end of the expression. - * Map them to the appropriate button pushes when possible. Leftover characters - * are added to mUnprocessedChars, which is presumed to immediately precede the newly - * added characters. - * @param moreChars characters to be added - * @param explicit these characters were explicitly typed by the user, not pasted - */ - private void addChars(String moreChars, boolean explicit) { - if (mUnprocessedChars != null) { - moreChars = mUnprocessedChars + moreChars; - } - int current = 0; - int len = moreChars.length(); - boolean lastWasDigit = false; - if (mCurrentState == CalculatorState.RESULT && len != 0) { - // Clear display immediately for incomplete function name. - switchToInput(KeyMaps.keyForChar(moreChars.charAt(current))); - } - char groupingSeparator = KeyMaps.translateResult(",").charAt(0); - while (current < len) { - char c = moreChars.charAt(current); - if (Character.isSpaceChar(c) || c == groupingSeparator) { - ++current; - continue; - } - int k = KeyMaps.keyForChar(c); - if (!explicit) { - int expEnd; - if (lastWasDigit && current != - (expEnd = Evaluator.exponentEnd(moreChars, current))) { - // Process scientific notation with 'E' when pasting, in spite of ambiguity - // with base of natural log. - // Otherwise the 10^x key is the user's friend. - mEvaluator.addExponent(moreChars, current, expEnd); - current = expEnd; - lastWasDigit = false; - continue; - } else { - boolean isDigit = KeyMaps.digVal(k) != KeyMaps.NOT_DIGIT; - if (current == 0 && (isDigit || k == R.id.dec_point) - && mEvaluator.getExpr(Evaluator.MAIN_INDEX).hasTrailingConstant()) { - // Refuse to concatenate pasted content to trailing constant. - // This makes pasting of calculator results more consistent, whether or - // not the old calculator instance is still around. - addKeyToExpr(R.id.op_mul); - } - lastWasDigit = (isDigit || lastWasDigit && k == R.id.dec_point); - } - } - if (k != View.NO_ID) { - mCurrentButton = findViewById(k); - if (explicit) { - addExplicitKeyToExpr(k); - } else { - addKeyToExpr(k); - } - if (Character.isSurrogate(c)) { - current += 2; - } else { - ++current; - } - continue; - } - int f = KeyMaps.funForString(moreChars, current); - if (f != View.NO_ID) { - mCurrentButton = findViewById(f); - if (explicit) { - addExplicitKeyToExpr(f); - } else { - addKeyToExpr(f); - } - if (f == R.id.op_sqrt) { - // Square root entered as function; don't lose the parenthesis. - addKeyToExpr(R.id.lparen); - } - current = moreChars.indexOf('(', current) + 1; - continue; - } - // There are characters left, but we can't convert them to button presses. - mUnprocessedChars = moreChars.substring(current); - redisplayAfterFormulaChange(); - showOrHideToolbar(); - return; - } - mUnprocessedChars = null; - redisplayAfterFormulaChange(); - showOrHideToolbar(); - } - - private void clearIfNotInputState() { - if (mCurrentState == CalculatorState.ERROR - || mCurrentState == CalculatorState.RESULT) { - setState(CalculatorState.INPUT); - mEvaluator.clearMain(); - } - } - - /** - * Since we only support LTR format, using the RTL comma does not make sense. - */ - private String getDecimalSeparator() { - final char defaultSeparator = DecimalFormatSymbols.getInstance().getDecimalSeparator(); - final char rtlComma = '\u066b'; - return defaultSeparator == rtlComma ? "," : String.valueOf(defaultSeparator); - } - - /** - * Clean up animation for context menu. - */ - @Override - public void onContextMenuClosed(Menu menu) { - stopActionModeOrContextMenu(); - } - - public interface OnDisplayMemoryOperationsListener { - boolean shouldDisplayMemory(); - } -} |