summaryrefslogtreecommitdiffstats
path: root/src/com/android/calculator2
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2014-05-27 17:53:10 -0700
committerJustin Klaassen <justinklaassen@google.com>2014-05-28 15:17:38 -0700
commit4b3af0578b1a44038856bc56244aea8aaeac22d1 (patch)
tree98685ecfd26c20e42bda03ae976e742c02478663 /src/com/android/calculator2
parenta1d84550f39728b7a68e3f9c85d7f757ea55f114 (diff)
downloadandroid_packages_apps_ExactCalculator-4b3af0578b1a44038856bc56244aea8aaeac22d1.tar.gz
android_packages_apps_ExactCalculator-4b3af0578b1a44038856bc56244aea8aaeac22d1.tar.bz2
android_packages_apps_ExactCalculator-4b3af0578b1a44038856bc56244aea8aaeac22d1.zip
Overhaul Calculator UI.
Bug: 14418545 Bug: 14419084 Bug: 14419142 Bug: 14420277 Bug: 14466652 Bug: 14564559 Bug: 14564608 Bug: 14846724 Bug: 15090154 Bug: 15287699 Bug: 15289526 Bug: 15289616 Change-Id: I93e1530446d5bd6a4c3189f751c88ece1abc7767
Diffstat (limited to 'src/com/android/calculator2')
-rw-r--r--src/com/android/calculator2/Calculator.java134
-rw-r--r--src/com/android/calculator2/CalculatorActivity.java318
-rw-r--r--src/com/android/calculator2/CalculatorDisplay.java175
-rw-r--r--src/com/android/calculator2/CalculatorEditText.java320
-rw-r--r--src/com/android/calculator2/CalculatorEditable.java112
-rw-r--r--src/com/android/calculator2/CalculatorExpressionBuilder.java106
-rw-r--r--src/com/android/calculator2/CalculatorExpressionEvaluator.java79
-rw-r--r--src/com/android/calculator2/CalculatorExpressionTokenizer.java70
-rw-r--r--src/com/android/calculator2/CalculatorPadLayout.java120
-rw-r--r--src/com/android/calculator2/CalculatorPadViewPager.java93
-rw-r--r--src/com/android/calculator2/CalculatorViewPager.java82
-rw-r--r--src/com/android/calculator2/EventListener.java122
-rw-r--r--src/com/android/calculator2/History.java133
-rw-r--r--src/com/android/calculator2/HistoryAdapter.java88
-rw-r--r--src/com/android/calculator2/HistoryEntry.java69
-rw-r--r--src/com/android/calculator2/Logic.java338
-rw-r--r--src/com/android/calculator2/PageAdapter.java70
-rw-r--r--src/com/android/calculator2/PanelSwitcher.java179
-rw-r--r--src/com/android/calculator2/Persist.java95
19 files changed, 869 insertions, 1834 deletions
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java
deleted file mode 100644
index bb48147..0000000
--- a/src/com/android/calculator2/Calculator.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2008 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.
- */
-
-package com.android.calculator2;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.support.v4.view.ViewPager;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.PopupMenu.OnMenuItemClickListener;
-
-import com.android.calculator2.R;
-
-public class Calculator extends Activity implements PanelSwitcher.Listener, Logic.Listener,
- OnMenuItemClickListener, View.OnClickListener {
- private static final String STATE_CURRENT_VIEW = "state-current-view";
-
- private final EventListener mListener = new EventListener();
-
- private CalculatorDisplay mDisplay;
- private Persist mPersist;
- private History mHistory;
- private Logic mLogic;
- private ViewPager mPager;
-
- private View mClr;
- private View mDel;
-
- @Override
- public void onCreate(Bundle state) {
- super.onCreate(state);
-
- setContentView(R.layout.main);
-
- mPager = (ViewPager) findViewById(R.id.panelswitch);
- if (mPager != null) {
- final LayoutInflater inflater = LayoutInflater.from(this);
- final View simple = inflater.inflate(R.layout.simple_pad, mPager, false);
- final View advanced = inflater.inflate(R.layout.advanced_pad, mPager, false);
- mClr = simple.findViewById(R.id.clear);
- mDel = simple.findViewById(R.id.del);
-
- final PageAdapter adapter = new PageAdapter();
- adapter.add(simple);
- adapter.add(advanced);
-
- mPager.setAdapter(adapter);
- mPager.setCurrentItem(state == null ? 0 : state.getInt(STATE_CURRENT_VIEW, 0));
- } else {
- mClr = findViewById(R.id.clear);
- mDel = findViewById(R.id.del);
- }
-
- mPersist = new Persist(this);
- mPersist.load();
-
- mHistory = mPersist.getHistory();
-
- mDisplay = (CalculatorDisplay) findViewById(R.id.display);
-
- mLogic = new Logic(this, mHistory, mDisplay);
- mLogic.setListener(this);
- mLogic.setDeleteMode(mPersist.getDeleteMode());
- mLogic.setLineLength(mDisplay.getMaxDigits());
-
- final HistoryAdapter historyAdapter = new HistoryAdapter(this, mHistory, mLogic);
- mHistory.setObserver(historyAdapter);
-
- mListener.setHandler(mLogic, mPager);
- mDisplay.setOnKeyListener(mListener);
- mLogic.resumeWithHistory();
- }
-
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- return onOptionsItemSelected(item);
- }
-
- @Override
- public void onClick(View v) {
- mListener.onClick(v);
- }
-
- @Override
- protected void onSaveInstanceState(Bundle state) {
- super.onSaveInstanceState(state);
-
- if (mPager != null) {
- state.putInt(STATE_CURRENT_VIEW, mPager.getCurrentItem());
- }
- }
-
- @Override
- public void onPause() {
- super.onPause();
-
- mLogic.updateHistory();
- mPersist.setDeleteMode(mLogic.getDeleteMode());
- mPersist.save();
- }
-
- @Override
- public void onDeleteModeChange(int deleteMode) {
- if (deleteMode == Logic.DELETE_MODE_BACKSPACE) {
- mDel.setVisibility(View.VISIBLE);
- mClr.setVisibility(View.GONE);
- } else {
- mDel.setVisibility(View.GONE);
- mClr.setVisibility(View.VISIBLE);
- }
- }
-
- @Override
- public void onChange() {
- invalidateOptionsMenu();
- }
-}
diff --git a/src/com/android/calculator2/CalculatorActivity.java b/src/com/android/calculator2/CalculatorActivity.java
new file mode 100644
index 0000000..5c47535
--- /dev/null
+++ b/src/com/android/calculator2/CalculatorActivity.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+package com.android.calculator2;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ArgbEvaluator;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.Activity;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnLongClickListener;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.Button;
+
+public class CalculatorActivity extends Activity
+ implements CalculatorExpressionEvaluator.EvaluateCallback, OnLongClickListener {
+
+ public static final String CALCULATOR_ACTIVITY_CURRENT_STATE =
+ CalculatorActivity.class.getSimpleName() + "_currentState";
+
+ private enum CalculatorState {
+ INPUT, EVALUATE, RESULT, ERROR
+ }
+
+ 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) {
+ setState(CalculatorState.INPUT);
+ mEvaluator.evaluate(editable, CalculatorActivity.this);
+ }
+ };
+
+ private CalculatorState mCurrentState;
+ private CalculatorExpressionEvaluator mEvaluator;
+
+ private CalculatorEditText mFormulaEditText;
+ private CalculatorEditText mResultEditText;
+
+ private View mRevealView;
+ private View mDeleteButton;
+ private View mClearButton;
+
+ private Animator mCurrentAnimator;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_calculator);
+
+ mCurrentState = CalculatorState.INPUT;
+ mEvaluator = new CalculatorExpressionEvaluator(this);
+
+ mFormulaEditText = (CalculatorEditText) findViewById(R.id.formula);
+ mResultEditText = (CalculatorEditText) findViewById(R.id.result);
+
+ mRevealView = findViewById(R.id.reveal);
+ mDeleteButton = findViewById(R.id.del);
+ mClearButton = findViewById(R.id.clr);
+
+ mFormulaEditText.setEditableFactory(new CalculatorExpressionBuilder.Factory(this));
+ mFormulaEditText.addTextChangedListener(mFormulaTextWatcher);
+ mDeleteButton.setOnLongClickListener(this);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ setState(CalculatorState.values()[savedInstanceState.getInt(
+ CALCULATOR_ACTIVITY_CURRENT_STATE, CalculatorState.INPUT.ordinal())]);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(CALCULATOR_ACTIVITY_CURRENT_STATE, mCurrentState.ordinal());
+ }
+
+ private void setState(CalculatorState state) {
+ if (mCurrentState != state) {
+ mCurrentState = state;
+
+ if (state == CalculatorState.RESULT || state == CalculatorState.ERROR) {
+ mDeleteButton.setVisibility(View.GONE);
+ mClearButton.setVisibility(View.VISIBLE);
+ } else {
+ mDeleteButton.setVisibility(View.VISIBLE);
+ mClearButton.setVisibility(View.GONE);
+ }
+
+ if (state == CalculatorState.ERROR) {
+ final int errorColor = getResources().getColor(R.color.calculator_error_color);
+ mFormulaEditText.setTextColor(errorColor);
+ mResultEditText.setTextColor(errorColor);
+ getWindow().setStatusBarColor(errorColor);
+ } else {
+ mFormulaEditText.setTextColor(
+ getResources().getColor(R.color.display_formula_text_color));
+ mResultEditText.setTextColor(
+ getResources().getColor(R.color.display_result_text_color));
+ getWindow().setStatusBarColor(
+ getResources().getColor(R.color.calculator_accent_color));
+ }
+ }
+ }
+
+ @Override
+ public void onUserInteraction() {
+ super.onUserInteraction();
+
+ // If there's an animation in progress, cancel it so the user interaction can be handled
+ // immediately.
+ if (mCurrentAnimator != null) {
+ mCurrentAnimator.cancel();
+ }
+ }
+
+ public void onButtonClick(View view) {
+ switch (view.getId()) {
+ case R.id.eq:
+ if (mCurrentState != CalculatorState.INPUT) {
+ mFormulaEditText.getEditableText().clear();
+ } else {
+ setState(CalculatorState.EVALUATE);
+ mEvaluator.evaluate(mFormulaEditText.getText(), this);
+ }
+ break;
+ case R.id.del:
+ mFormulaEditText.dispatchKeyEvent(
+ new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
+ break;
+ case R.id.clr:
+ onClear(view);
+ break;
+ case R.id.fun_cos:
+ case R.id.fun_ln:
+ case R.id.fun_log:
+ case R.id.fun_sin:
+ case R.id.fun_tan:
+ // add left paren after functions
+ mFormulaEditText.append(((Button) view).getText() + "(");
+ break;
+ default:
+ mFormulaEditText.append(((Button) view).getText());
+ break;
+ }
+ }
+
+ @Override
+ public boolean onLongClick(View view) {
+ if (view.getId() == R.id.del) {
+ onClear(view);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onEvaluate(String expr, String result, String error) {
+ if (mCurrentState == CalculatorState.INPUT) {
+ mResultEditText.setText(result);
+ } else if (!TextUtils.isEmpty(error)) {
+ setState(CalculatorState.ERROR);
+ mResultEditText.setText(error);
+ } else if (!TextUtils.isEmpty(result)) {
+ onResult(result);
+ } else if (mCurrentState == CalculatorState.EVALUATE) {
+ // The current expression cannot be evaluated -> return to the input state.
+ setState(CalculatorState.INPUT);
+ }
+ }
+
+ private void onClear(View sourceView) {
+ final int[] clearLocation = new int[2];
+ sourceView.getLocationInWindow(clearLocation);
+ clearLocation[0] += sourceView.getWidth() / 2;
+ clearLocation[1] += sourceView.getHeight() / 2;
+
+ final int[] revealLocation = new int[2];
+ mRevealView.getLocationInWindow(revealLocation);
+
+ final int revealCenterX = clearLocation[0] - revealLocation[0];
+ final int revealCenterY = clearLocation[1] - revealLocation[1];
+
+ final double x1_2 = Math.pow(mRevealView.getLeft() - revealCenterX, 2);
+ final double x2_2 = Math.pow(mRevealView.getRight() - revealCenterX, 2);
+ final double y_2 = Math.pow(mRevealView.getTop() - revealCenterY, 2);
+ final float revealRadius = (float) Math.max(Math.sqrt(x1_2 + y_2), Math.sqrt(x2_2 + y_2));
+
+ final Animator clearAnimator = mRevealView.createRevealAnimator(
+ revealCenterX, revealCenterY, 0.0f, revealRadius);
+ clearAnimator.setDuration(
+ getResources().getInteger(android.R.integer.config_longAnimTime));
+ clearAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Clear the formula after the reveal is finished, but before it's faded out.
+ mFormulaEditText.getEditableText().clear();
+ }
+ });
+
+ final Animator alphaAnimator = ObjectAnimator.ofFloat(mRevealView, View.ALPHA, 0.0f);
+ alphaAnimator.setDuration(
+ getResources().getInteger(android.R.integer.config_shortAnimTime));
+
+ final AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.play(clearAnimator).before(alphaAnimator);
+ animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ mRevealView.setAlpha(1.0f);
+ mRevealView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mRevealView.setVisibility(View.GONE);
+ mCurrentAnimator = null;
+ }
+ });
+
+ mCurrentAnimator = animatorSet;
+ animatorSet.start();
+ }
+
+ private void onResult(final String result) {
+ // Calculate the values needed to perform the scale and translation animations,
+ // accounting for how the scale will affect the final position of the text.
+ final float resultScale =
+ mFormulaEditText.getVariableTextSize(result) / mResultEditText.getTextSize();
+ final float resultTranslationX = (1.0f - resultScale) *
+ (mResultEditText.getWidth() / 2.0f - mResultEditText.getPaddingEnd());
+ final float resultTranslationY = (1.0f - resultScale) *
+ (mResultEditText.getHeight() / 2.0f - mResultEditText.getPaddingBottom()) +
+ (mFormulaEditText.getBottom() - mResultEditText.getBottom()) +
+ (mResultEditText.getPaddingBottom() - mFormulaEditText.getPaddingBottom());
+ final float formulaTranslationY = -mFormulaEditText.getBottom();
+
+ // Use a value animator to fade to the final text color over the course of the animation.
+ final int resultTextColor = mResultEditText.getCurrentTextColor();
+ final int formulaTextColor = mFormulaEditText.getCurrentTextColor();
+ final ValueAnimator textColorAnimator =
+ ValueAnimator.ofObject(new ArgbEvaluator(), resultTextColor, formulaTextColor);
+ textColorAnimator.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ mResultEditText.setTextColor((int) valueAnimator.getAnimatedValue());
+ }
+ });
+
+ final AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(
+ textColorAnimator,
+ ObjectAnimator.ofFloat(mResultEditText, View.SCALE_X, resultScale),
+ ObjectAnimator.ofFloat(mResultEditText, View.SCALE_Y, resultScale),
+ ObjectAnimator.ofFloat(mResultEditText, View.TRANSLATION_X, resultTranslationX),
+ ObjectAnimator.ofFloat(mResultEditText, View.TRANSLATION_Y, resultTranslationY),
+ ObjectAnimator.ofFloat(mFormulaEditText, View.TRANSLATION_Y, formulaTranslationY));
+ animatorSet.setDuration(getResources().getInteger(android.R.integer.config_longAnimTime));
+ animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mResultEditText.setText(result);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Reset all of the values modified during the animation.
+ mResultEditText.setTextColor(resultTextColor);
+ mResultEditText.setScaleX(1.0f);
+ mResultEditText.setScaleY(1.0f);
+ mResultEditText.setTranslationX(0.0f);
+ mResultEditText.setTranslationY(0.0f);
+ mFormulaEditText.setTranslationY(0.0f);
+
+ // Finally update the formula to use the current result.
+ mFormulaEditText.setText(result);
+ setState(CalculatorState.RESULT);
+
+ mCurrentAnimator = null;
+ }
+ });
+
+ mCurrentAnimator = animatorSet;
+ animatorSet.start();
+ }
+}
diff --git a/src/com/android/calculator2/CalculatorDisplay.java b/src/com/android/calculator2/CalculatorDisplay.java
deleted file mode 100644
index ec4070e..0000000
--- a/src/com/android/calculator2/CalculatorDisplay.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2008 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.
- */
-
-package com.android.calculator2;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.text.Editable;
-import android.text.InputType;
-import android.text.Spanned;
-import android.text.method.NumberKeyListener;
-import android.util.AttributeSet;
-import android.view.animation.TranslateAnimation;
-import android.widget.EditText;
-import android.widget.ViewSwitcher;
-
-/**
- * Provides vertical scrolling for the input/result EditText.
- */
-class CalculatorDisplay extends ViewSwitcher {
-
- private static final String ATTR_MAX_DIGITS = "maxDigits";
- private static final int DEFAULT_MAX_DIGITS = 10;
-
- // only these chars are accepted from keyboard
- private static final char[] ACCEPTED_CHARS =
- "0123456789.+-*/\u2212\u00d7\u00f7()!%^".toCharArray();
-
- private static final int ANIM_DURATION = 500;
-
- enum Scroll { UP, DOWN, NONE }
-
- TranslateAnimation inAnimUp;
- TranslateAnimation outAnimUp;
- TranslateAnimation inAnimDown;
- TranslateAnimation outAnimDown;
-
- private int mMaxDigits = DEFAULT_MAX_DIGITS;
-
- public CalculatorDisplay(Context context) {
- this(context, null);
- }
-
- public CalculatorDisplay(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- if (attrs != null) {
- mMaxDigits = attrs.getAttributeIntValue(null, ATTR_MAX_DIGITS, DEFAULT_MAX_DIGITS);
- }
- }
-
- public int getMaxDigits() {
- return mMaxDigits;
- }
-
- protected void setLogic(Logic logic) {
- NumberKeyListener calculatorKeyListener =
- new NumberKeyListener() {
- @Override
- public int getInputType() {
- return InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
- }
-
- @Override
- protected char[] getAcceptedChars() {
- return ACCEPTED_CHARS;
- }
-
- @Override
- public CharSequence filter(CharSequence source, int start, int end,
- Spanned dest, int dstart, int dend) {
- /* the EditText should still accept letters (eg. 'sin')
- coming from the on-screen touch buttons, so don't filter anything.
- */
- return null;
- }
- };
-
- Editable.Factory factory = new CalculatorEditable.Factory(logic);
- for (int i = 0; i < 2; ++i) {
- EditText text = (EditText) getChildAt(i);
- text.setEditableFactory(factory);
- text.setKeyListener(calculatorKeyListener);
- text.setSingleLine();
- }
- }
-
- @Override
- public void setOnKeyListener(OnKeyListener l) {
- getChildAt(0).setOnKeyListener(l);
- getChildAt(1).setOnKeyListener(l);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldW, int oldH) {
- inAnimUp = new TranslateAnimation(0, 0, h, 0);
- inAnimUp.setDuration(ANIM_DURATION);
- outAnimUp = new TranslateAnimation(0, 0, 0, -h);
- outAnimUp.setDuration(ANIM_DURATION);
-
- inAnimDown = new TranslateAnimation(0, 0, -h, 0);
- inAnimDown.setDuration(ANIM_DURATION);
- outAnimDown = new TranslateAnimation(0, 0, 0, h);
- outAnimDown.setDuration(ANIM_DURATION);
- }
-
- void insert(String delta) {
- EditText editor = (EditText) getCurrentView();
- int cursor = editor.getSelectionStart();
- editor.getText().insert(cursor, delta);
- }
-
- void append(String delta) {
- EditText editor = (EditText) getCurrentView();
- editor.getText().append(delta);
- }
-
- EditText getEditText() {
- return (EditText) getCurrentView();
- }
-
- Editable getText() {
- EditText text = (EditText) getCurrentView();
- return text.getText();
- }
-
- void setText(CharSequence text, Scroll dir) {
- if (getText().length() == 0) {
- dir = Scroll.NONE;
- }
-
- if (dir == Scroll.UP) {
- setInAnimation(inAnimUp);
- setOutAnimation(outAnimUp);
- } else if (dir == Scroll.DOWN) {
- setInAnimation(inAnimDown);
- setOutAnimation(outAnimDown);
- } else { // Scroll.NONE
- setInAnimation(null);
- setOutAnimation(null);
- }
-
- EditText editText = (EditText) getNextView();
- editText.setText(text);
- //Calculator.log("selection to " + text.length() + "; " + text);
- editText.setSelection(text.length());
- showNext();
- }
-
- int getSelectionStart() {
- EditText text = (EditText) getCurrentView();
- return text.getSelectionStart();
- }
-
- @Override
- protected void onFocusChanged(boolean gain, int direction, Rect prev) {
- //Calculator.log("focus " + gain + "; " + direction + "; " + prev);
- if (!gain) {
- requestFocus();
- }
- }
-}
diff --git a/src/com/android/calculator2/CalculatorEditText.java b/src/com/android/calculator2/CalculatorEditText.java
index b40bb1e..e31d571 100644
--- a/src/com/android/calculator2/CalculatorEditText.java
+++ b/src/com/android/calculator2/CalculatorEditText.java
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2014 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
+ * 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,
@@ -16,107 +16,97 @@
package com.android.calculator2;
-import android.content.ClipData;
-import android.content.ClipboardManager;
import android.content.Context;
-import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Paint;
-import android.text.Editable;
-import android.text.InputType;
-import android.text.TextUtils;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.Rect;
+import android.text.method.ScrollingMovementMethod;
+import android.text.TextPaint;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.TypedValue;
import android.view.ActionMode;
-import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.EditText;
-import android.widget.Toast;
-import com.android.calculator2.R;
+public class CalculatorEditText extends EditText {
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
+ private final ActionMode.Callback mNoSelectionActionModeCallback = new ActionMode.Callback() {
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ return false;
+ }
-public class CalculatorEditText extends EditText {
- private static final String LOG_TAG = "Calculator2";
- private static final int CUT = 0;
- private static final int COPY = 1;
- private static final int PASTE = 2;
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ // Prevents the selection action mode on double tap.
+ return false;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ }
- private static Map<String, String> sReplacementTable;
- private static String[] sOperators;
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+ };
- private final int mMaximumTextSize;
- private final int mMinimumTextSize;
- private final int mStepTextSize;
+ private final float mMaximumTextSize;
+ private final float mMinimumTextSize;
+ private final float mStepTextSize;
private int mWidthConstraint = -1;
- private String[] mMenuItemsStrings;
-
public CalculatorEditText(Context context) {
this(context, null);
}
public CalculatorEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- setCustomSelectionActionModeCallback(new NoTextSelectionMode());
- setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
- setCursorVisible(false);
-
- final Resources res = getResources();
- mMaximumTextSize = res.getDimensionPixelSize(R.dimen.display_maximum_text_size);
- mMinimumTextSize = res.getDimensionPixelSize(R.dimen.display_minimum_text_size);
- mStepTextSize = res.getDimensionPixelSize(R.dimen.display_step_text_size);
+ this(context, attrs, 0);
}
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_UP) {
- // Hack to prevent keyboard and insertion handle from showing.
- cancelLongPress();
- }
+ public CalculatorEditText(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
- return super.onTouchEvent(event);
- }
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.CalculatorEditText, defStyle, 0);
+ mMaximumTextSize = a.getDimension(
+ R.styleable.CalculatorEditText_maxTextSize, getTextSize());
+ mMinimumTextSize = a.getDimension(
+ R.styleable.CalculatorEditText_minTextSize, getTextSize());
+ mStepTextSize = a.getDimension(R.styleable.CalculatorEditText_stepTextSize,
+ (mMaximumTextSize - mMinimumTextSize) / 3);
- @Override
- public boolean performLongClick() {
- showContextMenu();
+ a.recycle();
- return true;
+ setCustomSelectionActionModeCallback(mNoSelectionActionModeCallback);
+ setMovementMethod(ScrollingMovementMethod.getInstance());
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, mMaximumTextSize);
+ setMinHeight(getLineHeight() + getCompoundPaddingBottom() + getCompoundPaddingTop());
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
-
- final String mathText = mathParse(getText().toString());
- // Parse the string into something more "mathematical" sounding.
- if (!TextUtils.isEmpty(mathText)) {
- event.getText().clear();
- event.getText().add(mathText);
- setContentDescription(mathText);
+ protected void onSelectionChanged(int selStart, int selEnd) {
+ final int textLength = getText() == null ? 0 : getText().length();
+ if (selStart != textLength || selEnd != textLength) {
+ // Pin the selection to the end of the current text.
+ setSelection(textLength);
}
- }
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
-
- info.setText(mathParse(getText().toString()));
+ super.onSelectionChanged(selStart, selEnd);
}
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- // Do nothing.
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+ // Hack to prevent keyboard and insertion handle from showing.
+ cancelLongPress();
+ }
+ return super.onTouchEvent(event);
}
@Override
@@ -125,200 +115,56 @@ public class CalculatorEditText extends EditText {
mWidthConstraint =
MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
- setVariableFontSize();
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(getText().toString()));
}
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
- if (TextUtils.isEmpty(text)) {
- setTextSize(TypedValue.COMPLEX_UNIT_PX, mMaximumTextSize);
- return;
- }
-
- setVariableFontSize();
+ setSelection(text.length());
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(text.toString()));
}
- private void setVariableFontSize() {
- if (mWidthConstraint < 0) {
+ public float getVariableTextSize(String text) {
+ if (mWidthConstraint < 0 || mMaximumTextSize <= mMinimumTextSize) {
// Not measured, bail early.
- return;
+ return getTextSize();
}
- final Paint paint = new Paint();
- final String measureText = getText().toString();
- int lastFitTextSize = mMinimumTextSize;
-
+ final Paint paint = new TextPaint(getPaint());
+ float lastFitTextSize = mMinimumTextSize;
while (lastFitTextSize < mMaximumTextSize) {
- final int nextSize = lastFitTextSize + mStepTextSize;
+ final float nextSize = Math.min(lastFitTextSize + mStepTextSize, mMaximumTextSize);
paint.setTextSize(nextSize);
- final float measuredTextWidth = paint.measureText(measureText);
- if (measuredTextWidth > mWidthConstraint) {
+ if (paint.measureText(text) > mWidthConstraint) {
break;
} else {
lastFitTextSize = nextSize;
}
}
- setTextSize(TypedValue.COMPLEX_UNIT_PX, lastFitTextSize);
- }
-
- private String mathParse(String plainText) {
- String parsedText = plainText;
- if (!TextUtils.isEmpty(parsedText)) {
- // Initialize replacement table.
- initializeReplacementTable();
- for (String operator : sOperators) {
- if (sReplacementTable.containsKey(operator)) {
- parsedText = parsedText.replace(operator, sReplacementTable.get(operator));
- }
- }
- }
-
- return parsedText;
- }
-
- private synchronized void initializeReplacementTable() {
- if (sReplacementTable == null) {
- final Resources res = getContext().getResources();
- final String[] descs = res.getStringArray(R.array.operatorDescs);
- final String[] ops = res.getStringArray(R.array.operators);
- final HashMap<String, String> table = new HashMap<String, String>();
- final int len = ops.length;
- for (int i = 0; i < len; i++) {
- table.put(ops[i], descs[i]);
- }
-
- sOperators = ops;
- sReplacementTable = Collections.unmodifiableMap(table);
- }
- }
-
- public boolean onTextContextMenuItem(CharSequence title) {
- if (TextUtils.equals(title, mMenuItemsStrings[CUT])) {
- cutContent();
- return true;
- } else if (TextUtils.equals(title, mMenuItemsStrings[COPY])) {
- copyContent();
- return true;
- } else if (TextUtils.equals(title, mMenuItemsStrings[PASTE])) {
- pasteContent();
- return true;
- }
-
- return false;
+ return lastFitTextSize;
}
@Override
- public void onCreateContextMenu(ContextMenu menu) {
- if (mMenuItemsStrings == null) {
- final Resources resources = getResources();
- mMenuItemsStrings = new String[3];
- mMenuItemsStrings[CUT] = resources.getString(android.R.string.cut);
- mMenuItemsStrings[COPY] = resources.getString(android.R.string.copy);
- mMenuItemsStrings[PASTE] = resources.getString(android.R.string.paste);
- }
-
- final MenuHandler handler = new MenuHandler();
- final int len = mMenuItemsStrings.length;
- for (int i = 0; i < len; i++) {
- menu.add(Menu.NONE, i, i, mMenuItemsStrings[i]).setOnMenuItemClickListener(handler);
- }
-
- if (getText().length() == 0) {
- menu.getItem(CUT).setVisible(false);
- menu.getItem(COPY).setVisible(false);
- }
-
- final ClipData primaryClip = getPrimaryClip();
- if (primaryClip == null || primaryClip.getItemCount() == 0
- || !canPaste(primaryClip.getItemAt(0).coerceToText(getContext()))) {
- menu.getItem(PASTE).setVisible(false);
- }
- }
-
- private void setPrimaryClip(ClipData clip) {
- final ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
- Context.CLIPBOARD_SERVICE);
- clipboard.setPrimaryClip(clip);
- }
-
- private void copyContent() {
- final Editable text = getText();
- final int textLength = text.length();
- setSelection(0, textLength);
- setPrimaryClip(ClipData.newPlainText(null, text));
- setSelection(textLength);
-
- Toast.makeText(getContext(), R.string.text_copied_toast, Toast.LENGTH_SHORT).show();
- }
-
- private void cutContent() {
- final Editable text = getText();
- final int textLength = text.length();
- setSelection(0, textLength);
- setPrimaryClip(ClipData.newPlainText(null, text));
- getText().delete(0, textLength);
- setSelection(0);
- }
-
- private ClipData getPrimaryClip() {
- final ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
- Context.CLIPBOARD_SERVICE);
- return clipboard.getPrimaryClip();
- }
-
- private void pasteContent() {
- final ClipData clip = getPrimaryClip();
- if (clip != null) {
- final int len = clip.getItemCount();
- for (int i = 0; i < len; i++) {
- final CharSequence paste = clip.getItemAt(i).coerceToText(getContext());
- if (canPaste(paste)) {
- getText().insert(getSelectionEnd(), paste);
- }
- }
- }
- }
+ public int getCompoundPaddingTop() {
+ // Measure the top padding from the capital letter height of the text instead of the top,
+ // but don't remove more than the available top padding otherwise clipping may occur.
+ final Rect capBounds = new Rect();
+ getPaint().getTextBounds("H", 0, 1, capBounds);
- private boolean canPaste(CharSequence paste) {
- try {
- Float.parseFloat(paste.toString());
- return true;
- } catch (NumberFormatException e) {
- Log.e(LOG_TAG, "Error turning string to integer. Ignoring paste.", e);
- return false;
- }
- }
+ final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
+ final int paddingOffset = -(fontMetrics.ascent + capBounds.height());
- private class MenuHandler implements MenuItem.OnMenuItemClickListener {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- return onTextContextMenuItem(item.getTitle());
- }
+ return super.getCompoundPaddingTop() - Math.min(getPaddingTop(), paddingOffset);
}
- private class NoTextSelectionMode implements ActionMode.Callback {
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- return false;
- }
-
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- copyContent();
- // Prevents the selection action mode on double tap.
- return false;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- return false;
- }
+ @Override
+ public int getCompoundPaddingBottom() {
+ // Measure the bottom padding from the baseline of the text instead of the bottom, but don't
+ // remove more than the available bottom padding otherwise clipping may occur.
+ final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
+ return super.getCompoundPaddingBottom() - Math.min(getPaddingBottom(), fontMetrics.descent);
}
}
diff --git a/src/com/android/calculator2/CalculatorEditable.java b/src/com/android/calculator2/CalculatorEditable.java
deleted file mode 100644
index 4550971..0000000
--- a/src/com/android/calculator2/CalculatorEditable.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2008 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.
- */
-
-package com.android.calculator2;
-
-import android.text.SpannableStringBuilder;
-import android.text.Editable;
-
-class CalculatorEditable extends SpannableStringBuilder {
- private static final char[] ORIGINALS = { '-', '*', '/' };
- private static final char[] REPLACEMENTS = { '\u2212', '\u00d7', '\u00f7' };
-
- private boolean isInsideReplace = false;
- private Logic mLogic;
-
- private CalculatorEditable(CharSequence source, Logic logic) {
- super(source);
- mLogic = logic;
- }
-
- @Override
- public SpannableStringBuilder replace(int start, int end, CharSequence tb, int tbstart,
- int tbend) {
- if (isInsideReplace) {
- return super.replace(start, end, tb, tbstart, tbend);
- } else {
- isInsideReplace = true;
- try {
- String delta = tb.subSequence(tbstart, tbend).toString();
- return internalReplace(start, end, delta);
- } finally {
- isInsideReplace = false;
- }
- }
- }
-
- private SpannableStringBuilder internalReplace(int start, int end, String delta) {
- if (!mLogic.acceptInsert(delta)) {
- mLogic.cleared();
- start = 0;
- end = length();
- }
-
- for (int i = ORIGINALS.length - 1; i >= 0; --i) {
- delta = delta.replace(ORIGINALS[i], REPLACEMENTS[i]);
- }
-
- final int length = delta.length();
- if (length == 1) {
- final char text = delta.charAt(0);
-
- // don't allow two dots in the same number
- if (text == '.') {
- int p = start - 1;
- while (p >= 0 && Character.isDigit(charAt(p))) {
- --p;
- }
- if (p >= 0 && charAt(p) == '.') {
- return super.replace(start, end, "");
- }
- }
-
- char prevChar = start > 0 ? charAt(start - 1) : '\0';
-
- // don't allow 2 successive minuses
- if (text == Logic.MINUS && prevChar == Logic.MINUS) {
- return super.replace(start, end, "");
- }
-
- // don't allow multiple successive operators
- if (Logic.isOperator(text)) {
- while (Logic.isOperator(prevChar) && (text != Logic.MINUS || prevChar == '+')) {
- --start;
- prevChar = start > 0 ? charAt(start - 1) : '\0';
- }
- }
-
- // don't allow leading operator + * /
- if (start == 0 && Logic.isOperator(text) && text != Logic.MINUS) {
- return super.replace(start, end, "");
- }
- }
-
- return super.replace(start, end, delta);
- }
-
- public static class Factory extends Editable.Factory {
- private final Logic mLogic;
-
- public Factory(Logic logic) {
- mLogic = logic;
- }
-
- @Override
- public Editable newEditable(CharSequence source) {
- return new CalculatorEditable(source, mLogic);
- }
- }
-}
diff --git a/src/com/android/calculator2/CalculatorExpressionBuilder.java b/src/com/android/calculator2/CalculatorExpressionBuilder.java
new file mode 100644
index 0000000..f2125a4
--- /dev/null
+++ b/src/com/android/calculator2/CalculatorExpressionBuilder.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+package com.android.calculator2;
+
+import android.content.Context;
+import android.text.Editable;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+
+public class CalculatorExpressionBuilder extends SpannableStringBuilder {
+
+ private final CalculatorExpressionTokenizer mTokenizer;
+ private boolean mIsEdited;
+
+ public CalculatorExpressionBuilder(CharSequence text, CalculatorExpressionTokenizer tokenizer) {
+ super(text);
+ mIsEdited = false;
+ mTokenizer = tokenizer;
+ }
+
+ @Override
+ public SpannableStringBuilder replace(int start, int end, CharSequence tb, int tbstart,
+ int tbend) {
+ if (start != length() || end != length()) {
+ mIsEdited = true;
+ return super.replace(start, end, tb, tbstart, tbend);
+ }
+
+ String appendExpr =
+ mTokenizer.getNormalizedExpression(tb.subSequence(tbstart, tbend).toString());
+ if (appendExpr.length() == 1) {
+ final String expr = mTokenizer.getNormalizedExpression(toString());
+ switch (appendExpr.charAt(0)) {
+ case '.':
+ // don't allow two decimals in the same number
+ final int index = expr.lastIndexOf('.');
+ if (index != -1 && TextUtils.isDigitsOnly(expr.substring(index + 1, start))) {
+ appendExpr = "";
+ }
+ break;
+ case '+':
+ case '*':
+ case '/':
+ // don't allow leading operator
+ if (start == 0) {
+ appendExpr = "";
+ break;
+ }
+
+ // don't allow multiple successive operators
+ while (start > 0 && "+-*/".indexOf(expr.charAt(start - 1)) != -1) {
+ --start;
+ }
+ // fall through
+ case '-':
+ // don't allow -- or +-
+ if (start > 0 && "+-".indexOf(expr.charAt(start - 1)) != -1) {
+ --start;
+ }
+
+ // mark as edited since operators can always be appended
+ mIsEdited = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ // since this is the first edit replace the entire string
+ if (!mIsEdited && appendExpr.length() > 0) {
+ start = 0;
+ mIsEdited = true;
+ }
+
+ appendExpr = mTokenizer.getLocalizedExpression(appendExpr);
+ return super.replace(start, end, appendExpr, 0, appendExpr.length());
+ }
+
+ public static class Factory extends Editable.Factory {
+
+ private final CalculatorExpressionTokenizer mTokenizer;
+
+ public Factory(Context context) {
+ mTokenizer = new CalculatorExpressionTokenizer(context);
+ }
+
+ @Override
+ public Editable newEditable(CharSequence source) {
+ return new CalculatorExpressionBuilder(source, mTokenizer);
+ }
+ }
+}
diff --git a/src/com/android/calculator2/CalculatorExpressionEvaluator.java b/src/com/android/calculator2/CalculatorExpressionEvaluator.java
new file mode 100644
index 0000000..0f41423
--- /dev/null
+++ b/src/com/android/calculator2/CalculatorExpressionEvaluator.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+package com.android.calculator2;
+
+import android.content.Context;
+
+import org.javia.arity.Symbols;
+import org.javia.arity.SyntaxException;
+import org.javia.arity.Util;
+
+public class CalculatorExpressionEvaluator {
+ private static final int MAX_DIGITS = 14;
+
+ private final Symbols mSymbols;
+ private final CalculatorExpressionTokenizer mTokenizer;
+
+ private final String mErrorNaN;
+ private final String mErrorSyntax;
+
+ public CalculatorExpressionEvaluator(Context context) {
+ mSymbols = new Symbols();
+ mTokenizer = new CalculatorExpressionTokenizer(context);
+
+ mErrorNaN = context.getString(R.string.error_nan);
+ mErrorSyntax = context.getString(R.string.error_syntax);
+ }
+
+ public void evaluate(CharSequence expr, EvaluateCallback callback) {
+ evaluate(expr.toString(), callback);
+ }
+
+ public void evaluate(String expr, EvaluateCallback callback) {
+ expr = mTokenizer.getNormalizedExpression(expr);
+
+ // remove any trailing operators
+ while (expr.length() > 0 && "+-/*".indexOf(expr.charAt(expr.length() - 1)) != -1) {
+ expr = expr.substring(0, expr.length() - 1);
+ }
+
+ try {
+ if (expr == null || expr.length() == 0 || Double.valueOf(expr) != null) {
+ callback.onEvaluate(expr, null, null);
+ return;
+ }
+ } catch (NumberFormatException e) {
+ // expr is not a simple number
+ }
+
+ try {
+ double result = mSymbols.eval(expr);
+ if (Double.isNaN(result)) {
+ callback.onEvaluate(expr, null, mErrorNaN);
+ } else {
+ callback.onEvaluate(expr, mTokenizer.getLocalizedExpression(
+ Util.doubleToString(result, MAX_DIGITS, 0)), null);
+ }
+ } catch (SyntaxException e) {
+ callback.onEvaluate(expr, null, mErrorSyntax);
+ }
+ }
+
+ public interface EvaluateCallback {
+ public void onEvaluate(String expr, String result, String error);
+ }
+}
diff --git a/src/com/android/calculator2/CalculatorExpressionTokenizer.java b/src/com/android/calculator2/CalculatorExpressionTokenizer.java
new file mode 100644
index 0000000..c0e0abd
--- /dev/null
+++ b/src/com/android/calculator2/CalculatorExpressionTokenizer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+package com.android.calculator2;
+
+import android.content.Context;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class CalculatorExpressionTokenizer {
+ private final Map<String, String> mReplacementMap;
+
+ public CalculatorExpressionTokenizer(Context context) {
+ mReplacementMap = new HashMap<>();
+
+ mReplacementMap.put(".", context.getString(R.string.dec_point));
+
+ mReplacementMap.put("0", context.getString(R.string.digit_0));
+ mReplacementMap.put("1", context.getString(R.string.digit_1));
+ mReplacementMap.put("2", context.getString(R.string.digit_2));
+ mReplacementMap.put("3", context.getString(R.string.digit_3));
+ mReplacementMap.put("4", context.getString(R.string.digit_4));
+ mReplacementMap.put("5", context.getString(R.string.digit_5));
+ mReplacementMap.put("6", context.getString(R.string.digit_6));
+ mReplacementMap.put("7", context.getString(R.string.digit_7));
+ mReplacementMap.put("8", context.getString(R.string.digit_8));
+ mReplacementMap.put("9", context.getString(R.string.digit_9));
+
+ mReplacementMap.put("/", context.getString(R.string.op_div));
+ mReplacementMap.put("*", context.getString(R.string.op_mul));
+ mReplacementMap.put("-", context.getString(R.string.op_sub));
+
+ mReplacementMap.put("cos", context.getString(R.string.fun_cos));
+ mReplacementMap.put("ln", context.getString(R.string.fun_ln));
+ mReplacementMap.put("log", context.getString(R.string.fun_log));
+ mReplacementMap.put("sin", context.getString(R.string.fun_sin));
+ mReplacementMap.put("tan", context.getString(R.string.fun_tan));
+
+ mReplacementMap.put("Infinity", context.getString(R.string.inf));
+ }
+
+ public String getNormalizedExpression(String expr) {
+ for (Entry<String, String> replacementEntry : mReplacementMap.entrySet()) {
+ expr = expr.replace(replacementEntry.getValue(), replacementEntry.getKey());
+ }
+ return expr;
+ }
+
+ public String getLocalizedExpression(String expr) {
+ for (Entry<String, String> replacementEntry : mReplacementMap.entrySet()) {
+ expr = expr.replace(replacementEntry.getKey(), replacementEntry.getValue());
+ }
+ return expr;
+ }
+}
diff --git a/src/com/android/calculator2/CalculatorPadLayout.java b/src/com/android/calculator2/CalculatorPadLayout.java
new file mode 100644
index 0000000..729c55b
--- /dev/null
+++ b/src/com/android/calculator2/CalculatorPadLayout.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+package com.android.calculator2;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A layout that places children in an evenly distributed grid based on the specified
+ * {@link android.R.attr#columnCount} and {@link android.R.attr#rowCount} attributes.
+ */
+public class CalculatorPadLayout extends ViewGroup {
+
+ private int mRowCount;
+ private int mColumnCount;
+
+ public CalculatorPadLayout(Context context) {
+ this(context, null);
+ }
+
+ public CalculatorPadLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CalculatorPadLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ new int[] { android.R.attr.rowCount, android.R.attr.columnCount }, defStyle, 0);
+ mRowCount = a.getInt(0, 1);
+ mColumnCount = a.getInt(1, 1);
+
+ a.recycle();
+ }
+
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ final int paddingLeft = getPaddingLeft();
+ final int paddingRight = getPaddingRight();
+ final int paddingTop = getPaddingTop();
+ final int paddingBottom = getPaddingBottom();
+
+ final boolean isRTL = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ final int columnWidth =
+ Math.round((float) (right - left - paddingLeft - paddingRight)) / mColumnCount;
+ final int rowHeight =
+ Math.round((float) (bottom - top - paddingTop - paddingBottom)) / mRowCount;
+
+ int rowIndex = 0, columnIndex = 0;
+ for (int childIndex = 0; childIndex < getChildCount(); ++childIndex) {
+ final View childView = getChildAt(childIndex);
+ if (childView.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ final MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
+
+ final int childTop = paddingTop + lp.topMargin + rowIndex * rowHeight;
+ final int childBottom = childTop - lp.topMargin - lp.bottomMargin + rowHeight;
+ final int childLeft = paddingLeft + lp.leftMargin +
+ (isRTL ? (mColumnCount - 1) - columnIndex : columnIndex) * columnWidth;
+ final int childRight = childLeft - lp.leftMargin - lp.rightMargin + columnWidth;
+
+ final int childWidth = childRight - childLeft;
+ final int childHeight = childBottom - childTop;
+ if (childWidth != childView.getMeasuredWidth() ||
+ childHeight != childView.getMeasuredHeight()) {
+ childView.measure(
+ MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
+ }
+ childView.layout(childLeft, childTop, childRight, childBottom);
+
+ rowIndex = (rowIndex + (columnIndex + 1) / mColumnCount) % mRowCount;
+ columnIndex = (columnIndex + 1) % mColumnCount;
+ }
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new MarginLayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return new MarginLayoutParams(p);
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof MarginLayoutParams;
+ }
+}
diff --git a/src/com/android/calculator2/CalculatorPadViewPager.java b/src/com/android/calculator2/CalculatorPadViewPager.java
new file mode 100644
index 0000000..af16d47
--- /dev/null
+++ b/src/com/android/calculator2/CalculatorPadViewPager.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+package com.android.calculator2;
+
+import android.content.Context;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class CalculatorPadViewPager extends ViewPager {
+
+ private final PagerAdapter mStaticPagerAdapter = new PagerAdapter() {
+ @Override
+ public int getCount() {
+ return getChildCount();
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ return getChildAt(position);
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ removeViewAt(position);
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return view == object;
+ }
+
+ @Override
+ public float getPageWidth(int position) {
+ return position == 1 ? 0.8f : 1.0f;
+ }
+ };
+ private final PageTransformer mPageTransformer = new PageTransformer() {
+ @Override
+ public void transformPage(View view, float position) {
+ if (position < -1.0f) {
+ view.setAlpha(0.0f);
+ } else if (position <= 0.0f) {
+ // Pin the left page to the left side.
+ view.setTranslationX(getWidth() * -position);
+ } else if (position <= 1.0f) {
+ // Use the default slide transition when moving to the next page.
+ view.setAlpha(1.0f);
+ view.setTranslationX(0.0f);
+ } else {
+ view.setAlpha(0.0f);
+ }
+ }
+ };
+
+ public CalculatorPadViewPager(Context context) {
+ this(context, null);
+ }
+
+ public CalculatorPadViewPager(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ setAdapter(mStaticPagerAdapter);
+ setPageMargin(getResources().getDimensionPixelSize(R.dimen.pad_page_margin));
+ setPageTransformer(false, mPageTransformer);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ // Invalidate the adapter's data set since children may have been added during inflation.
+ if (getAdapter() == mStaticPagerAdapter) {
+ mStaticPagerAdapter.notifyDataSetChanged();
+ }
+ }
+}
diff --git a/src/com/android/calculator2/CalculatorViewPager.java b/src/com/android/calculator2/CalculatorViewPager.java
deleted file mode 100644
index 25e9575..0000000
--- a/src/com/android/calculator2/CalculatorViewPager.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2011 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.
- */
-
-package com.android.calculator2;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.support.v4.view.ViewPager;
-import android.util.AttributeSet;
-import android.view.View;
-
-import com.android.calculator2.R;
-
-public class CalculatorViewPager extends ViewPager {
- private Drawable mShadowRight;
- private int mShadowWidth;
-
- public CalculatorViewPager(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- setPageTransformer(false, mTransformer);
-
- final Resources res = context.getResources();
- mShadowRight = res.getDrawable(R.drawable.shadow_right);
- mShadowWidth = res.getDimensionPixelSize(R.dimen.pager_shadow_width);
- }
-
- /**
- * ViewPager inherits ViewGroup's default behavior of delayed clicks on its
- * children, but in order to make the calc buttons more responsive we
- * disable that here.
- */
- @Override
- public boolean shouldDelayChildPressedState() {
- return false;
- }
-
- @Override
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- final boolean result = super.drawChild(canvas, child, drawingTime);
- mShadowRight.setBounds(child.getLeft() - mShadowWidth, child.getTop(), child.getLeft(),
- child.getBottom());
- mShadowRight.draw(canvas);
- return result;
- }
-
- private final PageTransformer mTransformer = new PageTransformer() {
- @Override
- public void transformPage(View v, float position) {
- final int pageWidth = v.getWidth();
-
- if (position < -1) {
- v.setAlpha(0);
- } else if (position <= 0) {
- // Pin the left page to the left side.
- v.setTranslationX(pageWidth * -position);
- } else if (position <= 1) {
- // Use the default slide transition when moving to the right
- // page
- v.setAlpha(1);
- v.setTranslationX(0);
- } else {
- v.setAlpha(0);
- }
- }
- };
-}
diff --git a/src/com/android/calculator2/EventListener.java b/src/com/android/calculator2/EventListener.java
deleted file mode 100644
index 580ff9b..0000000
--- a/src/com/android/calculator2/EventListener.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2008 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.
- */
-
-package com.android.calculator2;
-
-import android.support.v4.view.ViewPager;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.Button;
-
-import com.android.calculator2.R;
-
-class EventListener implements View.OnKeyListener, View.OnClickListener, View.OnLongClickListener {
- private Logic mHandler;
- private ViewPager mPager;
-
- public void setHandler(Logic handler, ViewPager pager) {
- mHandler = handler;
- mPager = pager;
- }
-
- @Override
- public void onClick(View view) {
- final int id = view.getId();
- switch (id) {
- case R.id.del:
- mHandler.onDelete();
- break;
- case R.id.clear:
- mHandler.onClear();
- break;
- case R.id.equal:
- mHandler.onEnter();
- break;
- default:
- if (view instanceof Button) {
- String text = ((Button) view).getText().toString();
- if (text.length() >= 2) {
- // Add paren after sin, cos, ln, etc. from buttons.
- text += '(';
- }
-
- mHandler.append(text);
- }
- }
- }
-
- @Override
- public boolean onLongClick(View view) {
- final int id = view.getId();
- if (id == R.id.del) {
- mHandler.onClear();
- return true;
- }
-
- return false;
- }
-
- @Override
- public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
- if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
- return mHandler.eatHorizontalMove(keyCode == KeyEvent.KEYCODE_DPAD_LEFT);
- }
-
- // Work-around for spurious key event from IME, bug #1639445
- final int action = keyEvent.getAction();
- if (action == KeyEvent.ACTION_MULTIPLE && keyCode == KeyEvent.KEYCODE_UNKNOWN) {
- return true;
- }
-
- if (keyEvent.getUnicodeChar() == '=') {
- if (action == KeyEvent.ACTION_UP) {
- mHandler.onEnter();
- }
- return true;
- }
-
- if (keyCode != KeyEvent.KEYCODE_DPAD_CENTER && keyCode != KeyEvent.KEYCODE_DPAD_UP &&
- keyCode != KeyEvent.KEYCODE_DPAD_DOWN && keyCode != KeyEvent.KEYCODE_ENTER) {
- if (keyEvent.isPrintingKey() && action == KeyEvent.ACTION_UP) {
- // Tell the handler that text was updated.
- mHandler.onTextChanged();
- }
- return false;
- }
-
- // We should act on KeyEvent.ACTION_DOWN, but strangely sometimes the
- // DOWN event isn't received, only the UP. So the workaround is to act
- // on UP... http://b/issue?id=1022478
- if (action == KeyEvent.ACTION_UP) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_ENTER:
- case KeyEvent.KEYCODE_DPAD_CENTER:
- mHandler.onEnter();
- break;
-
- case KeyEvent.KEYCODE_DPAD_UP:
- mHandler.onUp();
- break;
-
- case KeyEvent.KEYCODE_DPAD_DOWN:
- mHandler.onDown();
- break;
- }
- }
-
- return true;
- }
-}
diff --git a/src/com/android/calculator2/History.java b/src/com/android/calculator2/History.java
deleted file mode 100644
index c205209..0000000
--- a/src/com/android/calculator2/History.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2008 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.
- */
-
-package com.android.calculator2;
-
-import android.widget.BaseAdapter;
-
-import java.io.DataInput;
-import java.io.DataOutput;
-import java.io.IOException;
-import java.util.Vector;
-
-class History {
- private static final int VERSION_1 = 1;
- private static final int MAX_ENTRIES = 100;
-
- private final Vector<HistoryEntry> mEntries = new Vector<HistoryEntry>();
-
- private int mPos;
- private BaseAdapter mObserver;
-
- public History() {
- clear();
- }
-
- public History(int version, DataInput in) throws IOException {
- if (version >= VERSION_1) {
- int size = in.readInt();
- for (int i = 0; i < size; ++i) {
- mEntries.add(new HistoryEntry(version, in));
- }
-
- mPos = in.readInt();
- } else {
- throw new IOException("invalid version " + version);
- }
- }
-
- public void setObserver(BaseAdapter observer) {
- mObserver = observer;
- }
-
- private void notifyChanged() {
- if (mObserver != null) {
- mObserver.notifyDataSetChanged();
- }
- }
-
- public Vector<HistoryEntry> getEntries() {
- return mEntries;
- }
-
- public void clear() {
- mEntries.clear();
- mEntries.add(new HistoryEntry(""));
- mPos = 0;
-
- notifyChanged();
- }
-
- public void write(DataOutput out) throws IOException {
- out.writeInt(mEntries.size());
-
- for (HistoryEntry entry : mEntries) {
- entry.write(out);
- }
-
- out.writeInt(mPos);
- }
-
- public void update(String text) {
- current().setEdited(text);
- }
-
- public boolean moveToPrevious() {
- if (mPos > 0) {
- --mPos;
- return true;
- }
-
- return false;
- }
-
- public boolean moveToNext() {
- if (mPos < mEntries.size() - 1) {
- ++mPos;
- return true;
- }
-
- return false;
- }
-
- public void enter(String text) {
- current().clearEdited();
-
- if (mEntries.size() >= MAX_ENTRIES) {
- mEntries.remove(0);
- }
-
- if (mEntries.size() < 2 ||
- !text.equals(mEntries.elementAt(mEntries.size() - 2).getBase())) {
- mEntries.insertElementAt(new HistoryEntry(text), mEntries.size() - 1);
- }
-
- mPos = mEntries.size() - 1;
- notifyChanged();
- }
-
- public HistoryEntry current() {
- return mEntries.elementAt(mPos);
- }
-
- public String getText() {
- return current().getEdited();
- }
-
- public String getBase() {
- return current().getBase();
- }
-}
diff --git a/src/com/android/calculator2/HistoryAdapter.java b/src/com/android/calculator2/HistoryAdapter.java
deleted file mode 100644
index 061e68e..0000000
--- a/src/com/android/calculator2/HistoryAdapter.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2008 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.
- */
-
-package com.android.calculator2;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.TextView;
-
-import com.android.calculator2.R;
-
-import org.javia.arity.SyntaxException;
-
-import java.util.Vector;
-
-class HistoryAdapter extends BaseAdapter {
- private final Vector<HistoryEntry> mEntries;
- private final LayoutInflater mInflater;
- private final Logic mEval;
-
- public HistoryAdapter(Context context, History history, Logic evaluator) {
- mEntries = history.getEntries();
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mEval = evaluator;
- }
-
- @Override
- public int getCount() {
- return mEntries.size() - 1;
- }
-
- @Override
- public Object getItem(int position) {
- return mEntries.elementAt(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public boolean hasStableIds() {
- return true;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final View view;
- if (convertView == null) {
- view = mInflater.inflate(R.layout.history_item, parent, false);
- } else {
- view = convertView;
- }
-
- final TextView expr = (TextView) view.findViewById(R.id.historyExpr);
- final TextView result = (TextView) view.findViewById(R.id.historyResult);
-
- final HistoryEntry entry = mEntries.elementAt(position);
- final String base = entry.getBase();
- expr.setText(entry.getBase());
-
- try {
- final String res = mEval.evaluate(base);
- result.setText("= " + res);
- } catch (SyntaxException e) {
- result.setText("");
- }
-
- return view;
- }
-}
diff --git a/src/com/android/calculator2/HistoryEntry.java b/src/com/android/calculator2/HistoryEntry.java
deleted file mode 100644
index 80319d8..0000000
--- a/src/com/android/calculator2/HistoryEntry.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2008 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.
- */
-
-package com.android.calculator2;
-
-import java.io.DataInput;
-import java.io.DataOutput;
-import java.io.IOException;
-
-class HistoryEntry {
- private static final int VERSION_1 = 1;
- private String mBase;
- private String mEdited;
-
- HistoryEntry(String str) {
- mBase = str;
- clearEdited();
- }
-
- HistoryEntry(int version, DataInput in) throws IOException {
- if (version >= VERSION_1) {
- mBase = in.readUTF();
- mEdited = in.readUTF();
- //Calculator.log("load " + mEdited);
- } else {
- throw new IOException("invalid version " + version);
- }
- }
-
- void write(DataOutput out) throws IOException {
- out.writeUTF(mBase);
- out.writeUTF(mEdited);
- //Calculator.log("save " + mEdited);
- }
-
- @Override
- public String toString() {
- return mBase;
- }
-
- void clearEdited() {
- mEdited = mBase;
- }
-
- String getEdited() {
- return mEdited;
- }
-
- void setEdited(String edited) {
- mEdited = edited;
- }
-
- String getBase() {
- return mBase;
- }
-}
diff --git a/src/com/android/calculator2/Logic.java b/src/com/android/calculator2/Logic.java
deleted file mode 100644
index 839e6f3..0000000
--- a/src/com/android/calculator2/Logic.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright (C) 2008 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.
- */
-
-package com.android.calculator2;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.text.TextUtils;
-import android.view.KeyEvent;
-import android.widget.EditText;
-
-import com.android.calculator2.R;
-import com.android.calculator2.CalculatorDisplay.Scroll;
-
-import org.javia.arity.Symbols;
-import org.javia.arity.SyntaxException;
-
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map.Entry;
-import java.util.Set;
-
-class Logic {
- private CalculatorDisplay mDisplay;
- private Symbols mSymbols = new Symbols();
- private History mHistory;
- private String mResult = "";
- private boolean mIsError = false;
- private int mLineLength = 0;
-
- private static final String INFINITY_UNICODE = "\u221e";
-
- public static final String MARKER_EVALUATE_ON_RESUME = "?";
-
- // the two strings below are the result of Double.toString() for Infinity & NaN
- // they are not output to the user and don't require internationalization
- private static final String INFINITY = "Infinity";
- private static final String NAN = "NaN";
-
- static final char MINUS = '\u2212';
-
- private final String mErrorString;
-
- public final static int DELETE_MODE_BACKSPACE = 0;
- public final static int DELETE_MODE_CLEAR = 1;
-
- private int mDeleteMode = DELETE_MODE_BACKSPACE;
-
- public interface Listener {
- void onDeleteModeChange(int mode);
- }
-
- private Listener mListener;
- private Context mContext;
- private Set<Entry<String, String>> mTranslationsSet;
-
- Logic(Context context, History history, CalculatorDisplay display) {
- mContext = context;
- mErrorString = mContext.getResources().getString(R.string.error);
- mHistory = history;
- mDisplay = display;
- mDisplay.setLogic(this);
- }
-
- public void setListener(Listener listener) {
- this.mListener = listener;
- }
-
- public void setDeleteMode(int mode) {
- if (mDeleteMode != mode) {
- mDeleteMode = mode;
- mListener.onDeleteModeChange(mode);
- }
- }
-
- public int getDeleteMode() {
- return mDeleteMode;
- }
-
- void setLineLength(int nDigits) {
- mLineLength = nDigits;
- }
-
- boolean eatHorizontalMove(boolean toLeft) {
- EditText editText = mDisplay.getEditText();
- int cursorPos = editText.getSelectionStart();
- return toLeft ? cursorPos == 0 : cursorPos >= editText.length();
- }
-
- private String getText() {
- return mDisplay.getText().toString();
- }
-
- void insert(String delta) {
- mDisplay.insert(delta);
- setDeleteMode(DELETE_MODE_BACKSPACE);
- }
-
- void append(String delta) {
- mDisplay.append(delta);
- setDeleteMode(DELETE_MODE_BACKSPACE);
- }
-
- public void onTextChanged() {
- setDeleteMode(DELETE_MODE_BACKSPACE);
- }
-
- public void resumeWithHistory() {
- clearWithHistory(false);
- }
-
- private void clearWithHistory(boolean scroll) {
- String text = mHistory.getText();
- if (MARKER_EVALUATE_ON_RESUME.equals(text)) {
- if (!mHistory.moveToPrevious()) {
- text = "";
- }
- text = mHistory.getText();
- evaluateAndShowResult(text, CalculatorDisplay.Scroll.NONE);
- } else {
- mResult = "";
- mDisplay.setText(
- text, scroll ? CalculatorDisplay.Scroll.UP : CalculatorDisplay.Scroll.NONE);
- mIsError = false;
- }
- }
-
- private void clear(boolean scroll) {
- mHistory.enter("");
- mDisplay.setText("", scroll ? CalculatorDisplay.Scroll.UP : CalculatorDisplay.Scroll.NONE);
- cleared();
- }
-
- void cleared() {
- mResult = "";
- mIsError = false;
- updateHistory();
-
- setDeleteMode(DELETE_MODE_BACKSPACE);
- }
-
- boolean acceptInsert(String delta) {
- String text = getText();
- return !mIsError &&
- (!mResult.equals(text) ||
- isOperator(delta) ||
- mDisplay.getSelectionStart() != text.length());
- }
-
- void onDelete() {
- if (getText().equals(mResult) || mIsError) {
- clear(false);
- } else {
- mDisplay.dispatchKeyEvent(new KeyEvent(0, KeyEvent.KEYCODE_DEL));
- mResult = "";
- }
- }
-
- void onClear() {
- clear(mDeleteMode == DELETE_MODE_CLEAR);
- }
-
- void onEnter() {
- if (mDeleteMode == DELETE_MODE_CLEAR) {
- clearWithHistory(false); // clear after an Enter on result
- } else {
- evaluateAndShowResult(getText(), CalculatorDisplay.Scroll.UP);
- }
- }
-
- public void evaluateAndShowResult(String text, Scroll scroll) {
- try {
- String result = evaluate(text);
- if (!text.equals(result)) {
- mHistory.enter(text);
- mResult = result;
- mDisplay.setText(mResult, scroll);
- setDeleteMode(DELETE_MODE_CLEAR);
- }
- } catch (SyntaxException e) {
- mIsError = true;
- mResult = mErrorString;
- mDisplay.setText(mResult, scroll);
- setDeleteMode(DELETE_MODE_CLEAR);
- }
- }
-
- void onUp() {
- String text = getText();
- if (!text.equals(mResult)) {
- mHistory.update(text);
- }
- if (mHistory.moveToPrevious()) {
- mDisplay.setText(mHistory.getText(), CalculatorDisplay.Scroll.DOWN);
- }
- }
-
- void onDown() {
- String text = getText();
- if (!text.equals(mResult)) {
- mHistory.update(text);
- }
- if (mHistory.moveToNext()) {
- mDisplay.setText(mHistory.getText(), CalculatorDisplay.Scroll.UP);
- }
- }
-
- void updateHistory() {
- String text = getText();
- // Don't set the ? marker for empty text or the error string.
- // There is no need to evaluate those later.
- if (!TextUtils.isEmpty(text) && !TextUtils.equals(text, mErrorString)
- && text.equals(mResult)) {
- mHistory.update(MARKER_EVALUATE_ON_RESUME);
- } else {
- mHistory.update(getText());
- }
- }
-
- String evaluate(String input) throws SyntaxException {
- if (input.trim().equals("")) {
- return "";
- }
-
- // drop final infix operators (they can only result in error)
- int size = input.length();
- while (size > 0 && isOperator(input.charAt(size - 1))) {
- input = input.substring(0, size - 1);
- --size;
- }
- // Find and replace any translated mathematical functions.
- input = replaceTranslations(input);
- double value = mSymbols.eval(input);
-
- String result = "";
- for (int precision = mLineLength; precision > 6; precision--) {
- result = tryFormattingWithPrecision(value, precision);
- if (result.length() <= mLineLength) {
- break;
- }
- }
- return result.replace('-', MINUS).replace(INFINITY, INFINITY_UNICODE);
- }
-
- private void addTranslation(HashMap<String, String> map, int t, int m) {
- Resources res = mContext.getResources();
- String translated = res.getString(t);
- String math = res.getString(m);
- if (!TextUtils.equals(translated, math)) {
- map.put(translated, math);
- }
- }
-
- private String replaceTranslations(String input) {
- if (mTranslationsSet == null) {
- HashMap<String, String> map = new HashMap<String, String>();
- addTranslation(map, R.string.sin, R.string.sin_mathematical_value);
- addTranslation(map, R.string.cos, R.string.cos_mathematical_value);
- addTranslation(map, R.string.tan, R.string.tan_mathematical_value);
- addTranslation(map, R.string.e, R.string.e_mathematical_value);
- addTranslation(map, R.string.ln, R.string.ln_mathematical_value);
- addTranslation(map, R.string.lg, R.string.lg_mathematical_value);
- mTranslationsSet = map.entrySet();
- }
- for (Entry<String, String> entry : mTranslationsSet) {
- input = input.replace(entry.getKey(), entry.getValue());
- }
- return input;
- }
-
- private String tryFormattingWithPrecision(double value, int precision) {
- // The standard scientific formatter is basically what we need. We will
- // start with what it produces and then massage it a bit.
- String result = String.format(Locale.US, "%" + mLineLength + "." + precision + "g", value);
- if (result.equals(NAN)) { // treat NaN as Error
- mIsError = true;
- return mErrorString;
- }
- String mantissa = result;
- String exponent = null;
- int e = result.indexOf('e');
- if (e != -1) {
- mantissa = result.substring(0, e);
-
- // Strip "+" and unnecessary 0's from the exponent
- exponent = result.substring(e + 1);
- if (exponent.startsWith("+")) {
- exponent = exponent.substring(1);
- }
- exponent = String.valueOf(Integer.parseInt(exponent));
- } else {
- mantissa = result;
- }
-
- int period = mantissa.indexOf('.');
- if (period == -1) {
- period = mantissa.indexOf(',');
- }
- if (period != -1) {
- // Strip trailing 0's
- while (mantissa.length() > 0 && mantissa.endsWith("0")) {
- mantissa = mantissa.substring(0, mantissa.length() - 1);
- }
- if (mantissa.length() == period + 1) {
- mantissa = mantissa.substring(0, mantissa.length() - 1);
- }
- }
-
- if (exponent != null) {
- result = mantissa + 'e' + exponent;
- } else {
- result = mantissa;
- }
- return result;
- }
-
- static boolean isOperator(String text) {
- return text.length() == 1 && isOperator(text.charAt(0));
- }
-
- static boolean isOperator(char c) {
- //plus minus times div
- return "+\u2212\u00d7\u00f7/*".indexOf(c) != -1;
- }
-}
diff --git a/src/com/android/calculator2/PageAdapter.java b/src/com/android/calculator2/PageAdapter.java
deleted file mode 100644
index a8ff566..0000000
--- a/src/com/android/calculator2/PageAdapter.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2014 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.
- */
-
-package com.android.calculator2;
-
-import android.os.Parcelable;
-import android.support.v4.view.PagerAdapter;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.ArrayList;
-
-class PageAdapter extends PagerAdapter {
- private final ArrayList<View> mPages = new ArrayList<View>();
-
- public void add(View page) {
- mPages.add(page);
-
- notifyDataSetChanged();
- }
-
- @Override
- public int getCount() {
- return mPages.size();
- }
-
- @Override
- public float getPageWidth(int position) {
- return 0.90f;
- }
-
- @Override
- public Object instantiateItem(ViewGroup container, int position) {
- final View page = mPages.get(position);
- container.addView(page);
- return page;
- }
-
- @Override
- public void destroyItem(ViewGroup container, int position, Object object) {
- container.removeView((View) object);
- }
-
- @Override
- public boolean isViewFromObject(View view, Object object) {
- return view == object;
- }
-
- @Override
- public Parcelable saveState() {
- return null;
- }
-
- @Override
- public void restoreState(Parcelable state, ClassLoader loader) {
- }
-}
diff --git a/src/com/android/calculator2/PanelSwitcher.java b/src/com/android/calculator2/PanelSwitcher.java
deleted file mode 100644
index 0933c21..0000000
--- a/src/com/android/calculator2/PanelSwitcher.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2008 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.
- */
-
-package com.android.calculator2;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.animation.Animation;
-import android.view.animation.Animation.AnimationListener;
-import android.view.animation.TranslateAnimation;
-import android.widget.FrameLayout;
-
-class PanelSwitcher extends FrameLayout implements AnimationListener {
- private static final int MAJOR_MOVE = 60;
- private static final int ANIM_DURATION = 400;
-
- private GestureDetector mGestureDetector;
- private int mCurrentView;
- private View mChildren[] = new View[0];
-
- private int mWidth;
- private TranslateAnimation inLeft;
- private TranslateAnimation outLeft;
-
- private TranslateAnimation inRight;
- private TranslateAnimation outRight;
-
- private static final int LEFT = 1;
- private static final int RIGHT = 2;
- private int mPreviousMove;
-
- public interface Listener {
- void onChange();
- }
-
- private Listener mListener;
-
- public PanelSwitcher(Context context, AttributeSet attrs) {
- super(context, attrs);
- mCurrentView = 0;
- mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
- float velocityY) {
- int dx = (int) (e2.getX() - e1.getX());
-
- // don't accept the fling if it's too short
- // as it may conflict with a button push
- if (Math.abs(dx) > MAJOR_MOVE && Math.abs(velocityX) > Math.abs(velocityY)) {
- if (velocityX > 0) {
- moveRight();
- } else {
- moveLeft();
- }
- return true;
- } else {
- return false;
- }
- }
- });
- }
-
- public void setListener(Listener listener) {
- this.mListener = listener;
- }
-
- void setCurrentIndex(int current) {
- boolean changed = mCurrentView != current;
- mCurrentView = current;
- updateCurrentView();
- if (changed && mListener != null) {
- mListener.onChange();
- }
- }
-
- private void updateCurrentView() {
- for (int i = mChildren.length-1; i >= 0 ; --i) {
- mChildren[i].setVisibility(i==mCurrentView ? View.VISIBLE : View.GONE);
- }
- }
-
- @Override
- public void onSizeChanged(int w, int h, int oldW, int oldH) {
- mWidth = w;
- inLeft = new TranslateAnimation(mWidth, 0, 0, 0);
- inLeft.setAnimationListener(this);
- outLeft = new TranslateAnimation(0, -mWidth, 0, 0);
- inRight = new TranslateAnimation(-mWidth, 0, 0, 0);
- inRight.setAnimationListener(this);
- outRight = new TranslateAnimation(0, mWidth, 0, 0);
-
- inLeft.setDuration(ANIM_DURATION);
- outLeft.setDuration(ANIM_DURATION);
- inRight.setDuration(ANIM_DURATION);
- outRight.setDuration(ANIM_DURATION);
- }
-
- @Override
- protected void onFinishInflate() {
- int count = getChildCount();
- mChildren = new View[count];
- for (int i = 0; i < count; ++i) {
- mChildren[i] = getChildAt(i);
- }
- updateCurrentView();
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- mGestureDetector.onTouchEvent(event);
- return true;
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- return mGestureDetector.onTouchEvent(event);
- }
-
- void moveLeft() {
- // <--
- if (mCurrentView < mChildren.length - 1 && mPreviousMove != LEFT) {
- mChildren[mCurrentView+1].setVisibility(View.VISIBLE);
- mChildren[mCurrentView+1].startAnimation(inLeft);
- mChildren[mCurrentView].startAnimation(outLeft);
- mChildren[mCurrentView].setVisibility(View.GONE);
-
- mCurrentView++;
- mPreviousMove = LEFT;
- }
- }
-
- void moveRight() {
- // -->
- if (mCurrentView > 0 && mPreviousMove != RIGHT) {
- mChildren[mCurrentView-1].setVisibility(View.VISIBLE);
- mChildren[mCurrentView-1].startAnimation(inRight);
- mChildren[mCurrentView].startAnimation(outRight);
- mChildren[mCurrentView].setVisibility(View.GONE);
-
- mCurrentView--;
- mPreviousMove = RIGHT;
- }
- }
-
- int getCurrentIndex() {
- return mCurrentView;
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) {
- }
-
- @Override
- public void onAnimationStart(Animation animation) {
- }
-
- @Override
- public void onAnimationEnd(Animation animation) {
- if (mListener != null) {
- mListener.onChange();
- }
- }
-}
diff --git a/src/com/android/calculator2/Persist.java b/src/com/android/calculator2/Persist.java
deleted file mode 100644
index 5f23551..0000000
--- a/src/com/android/calculator2/Persist.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2008 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.
- */
-
-package com.android.calculator2;
-
-import android.content.Context;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
-class Persist {
- private static final int LAST_VERSION = 2;
- private static final String FILE_NAME = "calculator.data";
-
- private Context mContext;
- private History mHistory;
- private int mDeleteMode;
-
- public Persist(Context context) {
- mContext = context;
- }
-
- public History getHistory() {
- return mHistory;
- }
-
- public void setDeleteMode(int mode) {
- mDeleteMode = mode;
- }
-
- public int getDeleteMode() {
- return mDeleteMode;
- }
-
- public void load() {
- try {
- final DataInputStream in = new DataInputStream(
- new BufferedInputStream(mContext.openFileInput(FILE_NAME), 8192));
- final int version = in.readInt();
- if (version > 1) {
- mDeleteMode = in.readInt();
- } else if (version > LAST_VERSION) {
- throw new IOException("data version " + version + "; expected " + LAST_VERSION);
- }
-
- mHistory = new History(version, in);
-
- in.close();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- if (mHistory == null) {
- mHistory = new History();
- }
- }
-
- public void save() {
- if (mHistory == null) {
- return;
- }
-
- try {
- final DataOutputStream out = new DataOutputStream(
- new BufferedOutputStream(mContext.openFileOutput(FILE_NAME, 0), 8192));
- out.writeInt(LAST_VERSION);
- out.writeInt(mDeleteMode);
-
- mHistory.write(out);
-
- out.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-}