diff options
author | Justin Klaassen <justinklaassen@google.com> | 2014-05-27 17:53:10 -0700 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2014-05-28 15:17:38 -0700 |
commit | 4b3af0578b1a44038856bc56244aea8aaeac22d1 (patch) | |
tree | 98685ecfd26c20e42bda03ae976e742c02478663 /src/com/android/calculator2 | |
parent | a1d84550f39728b7a68e3f9c85d7f757ea55f114 (diff) | |
download | android_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')
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(); - } - } -} |