summaryrefslogtreecommitdiff
path: root/src/com/android/calculator2/Calculator.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/calculator2/Calculator.java')
-rw-r--r--src/com/android/calculator2/Calculator.java1538
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();
- }
-}