blob: c1750ef64373fe8e094e0e976cbef31df2470791 [file] [log] [blame]
Hans Boehm84614952014-11-25 18:46:17 -08001/*
Hans Boehm24c91ed2016-06-30 18:53:44 -07002 * Copyright (C) 2016 The Android Open Source Project
Hans Boehm84614952014-11-25 18:46:17 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.calculator2;
18
Chenjie Yu3937b652016-06-01 23:14:26 -070019import android.annotation.TargetApi;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070020import android.content.ClipData;
21import android.content.ClipDescription;
Justin Klaassen44595162015-05-28 17:55:20 -070022import android.content.ClipboardManager;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070023import android.content.Context;
Hans Boehm7f83e362015-06-10 15:41:04 -070024import android.graphics.Rect;
Chenjie Yu3937b652016-06-01 23:14:26 -070025import android.os.Build;
Hans Boehmbd01e4b2016-11-23 10:12:58 -080026import android.support.annotation.IntDef;
Chenjie Yu3937b652016-06-01 23:14:26 -070027import android.support.v4.content.ContextCompat;
Justin Klaassenf1b61f42016-04-27 16:00:11 -070028import android.support.v4.os.BuildCompat;
Justin Klaassen44595162015-05-28 17:55:20 -070029import android.text.Layout;
Hans Boehm7f83e362015-06-10 15:41:04 -070030import android.text.Spannable;
Hans Boehm84614952014-11-25 18:46:17 -080031import android.text.SpannableString;
Hans Boehm1176f232015-05-11 16:26:03 -070032import android.text.Spanned;
Justin Klaassen44595162015-05-28 17:55:20 -070033import android.text.TextPaint;
Hans Boehm7f83e362015-06-10 15:41:04 -070034import android.text.style.BackgroundColorSpan;
Hans Boehm84614952014-11-25 18:46:17 -080035import android.text.style.ForegroundColorSpan;
Hans Boehm14344ff2016-06-08 13:01:51 -070036import android.text.style.RelativeSizeSpan;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070037import android.util.AttributeSet;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070038import android.view.ActionMode;
Chenjie Yu3937b652016-06-01 23:14:26 -070039import android.view.ContextMenu;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070040import android.view.GestureDetector;
41import android.view.Menu;
42import android.view.MenuInflater;
43import android.view.MenuItem;
44import android.view.MotionEvent;
45import android.view.View;
Justin Klaassen44595162015-05-28 17:55:20 -070046import android.widget.OverScroller;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070047import android.widget.Toast;
Hans Boehm84614952014-11-25 18:46:17 -080048
Hans Boehmbd01e4b2016-11-23 10:12:58 -080049import java.lang.annotation.Retention;
50import java.lang.annotation.RetentionPolicy;
51
Hans Boehm84614952014-11-25 18:46:17 -080052// A text widget that is "infinitely" scrollable to the right,
53// and obtains the text to display via a callback to Logic.
Hans Boehm8f051c32016-10-03 16:53:58 -070054public class CalculatorResult extends AlignedTextView implements MenuItem.OnMenuItemClickListener,
55 Evaluator.EvaluationListener, Evaluator.CharMetricsInfo {
Hans Boehm61568a12015-05-18 18:25:41 -070056 static final int MAX_RIGHT_SCROLL = 10000000;
Hans Boehm08e8f322015-04-21 13:18:38 -070057 static final int INVALID = MAX_RIGHT_SCROLL + 10000;
Hans Boehm84614952014-11-25 18:46:17 -080058 // A larger value is unlikely to avoid running out of space
59 final OverScroller mScroller;
60 final GestureDetector mGestureDetector;
Hans Boehm8f051c32016-10-03 16:53:58 -070061 private long mIndex; // Index of expression we are displaying.
Hans Boehm84614952014-11-25 18:46:17 -080062 private Evaluator mEvaluator;
63 private boolean mScrollable = false;
64 // A scrollable result is currently displayed.
Hans Boehm760a9dc2015-04-20 10:27:12 -070065 private boolean mValid = false;
Hans Boehmc01cd7f2015-05-12 18:32:19 -070066 // The result holds something valid; either a a number or an error
67 // message.
Hans Boehm5e802f32015-06-22 17:18:52 -070068 // A suffix of "Pos" denotes a pixel offset. Zero represents a scroll position
69 // in which the decimal point is just barely visible on the right of the display.
Hans Boehmc01cd7f2015-05-12 18:32:19 -070070 private int mCurrentPos;// Position of right of display relative to decimal point, in pixels.
71 // Large positive values mean the decimal point is scrolled off the
72 // left of the display. Zero means decimal point is barely displayed
73 // on the right.
Hans Boehm61568a12015-05-18 18:25:41 -070074 private int mLastPos; // Position already reflected in display. Pixels.
Hans Boehm65a99a42016-02-03 18:16:07 -080075 private int mMinPos; // Minimum position to avoid unnecessary blanks on the left. Pixels.
Hans Boehm61568a12015-05-18 18:25:41 -070076 private int mMaxPos; // Maximum position before we start displaying the infinite
77 // sequence of trailing zeroes on the right. Pixels.
Hans Boehm65a99a42016-02-03 18:16:07 -080078 private int mWholeLen; // Length of the whole part of current result.
Hans Boehm5e802f32015-06-22 17:18:52 -070079 // In the following, we use a suffix of Offset to denote a character position in a numeric
80 // string relative to the decimal point. Positive is to the right and negative is to
81 // the left. 1 = tenths position, -1 = units. Integer.MAX_VALUE is sometimes used
82 // for the offset of the last digit in an a nonterminating decimal expansion.
83 // We use the suffix "Index" to denote a zero-based index into a string representing a
84 // result.
Hans Boehm5e802f32015-06-22 17:18:52 -070085 private int mMaxCharOffset; // Character offset from decimal point of rightmost digit
Hans Boehm24c91ed2016-06-30 18:53:44 -070086 // that should be displayed, plus the length of any exponent
87 // needed to display that digit.
88 // Limited to MAX_RIGHT_SCROLL. Often the same as:
Hans Boehm5e802f32015-06-22 17:18:52 -070089 private int mLsdOffset; // Position of least-significant digit in result
90 private int mLastDisplayedOffset; // Offset of last digit actually displayed after adding
Hans Boehmf6dae112015-06-18 17:57:50 -070091 // exponent.
Hans Boehm24c91ed2016-06-30 18:53:44 -070092 private boolean mWholePartFits; // Scientific notation not needed for initial display.
93 private float mNoExponentCredit;
94 // Fraction of digit width saved by avoiding scientific notation.
95 // Only accessed from UI thread.
96 private boolean mAppendExponent;
97 // The result fits entirely in the display, even with an exponent,
98 // but not with grouping separators. Since the result is not
99 // scrollable, and we do not add the exponent to max. scroll position,
100 // append an exponent insteadd of replacing trailing digits.
Justin Klaassen44595162015-05-28 17:55:20 -0700101 private final Object mWidthLock = new Object();
Hans Boehm24c91ed2016-06-30 18:53:44 -0700102 // Protects the next five fields. These fields are only
103 // Updated by the UI thread, and read accesses by the UI thread
104 // sometimes do not acquire the lock.
Hans Boehmd4959e82016-11-15 18:01:28 -0800105 private int mWidthConstraint = 0;
Hans Boehma0e45f32015-05-30 13:20:35 -0700106 // Our total width in pixels minus space for ellipsis.
Hans Boehmd4959e82016-11-15 18:01:28 -0800107 // 0 ==> uninitialized.
Justin Klaassen44595162015-05-28 17:55:20 -0700108 private float mCharWidth = 1;
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700109 // Maximum character width. For now we pretend that all characters
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700110 // have this width.
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700111 // TODO: We're not really using a fixed width font. But it appears
112 // to be close enough for the characters we use that the difference
113 // is not noticeable.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700114 private float mGroupingSeparatorWidthRatio;
115 // Fraction of digit width occupied by a digit separator.
116 private float mDecimalCredit;
117 // Fraction of digit width saved by replacing digit with decimal point.
118 private float mNoEllipsisCredit;
119 // Fraction of digit width saved by both replacing ellipsis with digit
120 // and avoiding scientific notation.
Hans Boehmbd01e4b2016-11-23 10:12:58 -0800121 @Retention(RetentionPolicy.SOURCE)
122 @IntDef({SHOULD_REQUIRE, SHOULD_EVALUATE, SHOULD_NOT_EVALUATE})
123 public @interface EvaluationRequest {}
124 public static final int SHOULD_REQUIRE = 2;
125 public static final int SHOULD_EVALUATE = 1;
126 public static final int SHOULD_NOT_EVALUATE = 0;
127 @EvaluationRequest private int mEvaluationRequest = SHOULD_REQUIRE;
128 // Should we evaluate when layout completes, and how?
Hans Boehmd4959e82016-11-15 18:01:28 -0800129 private Evaluator.EvaluationListener mEvaluationListener = this;
130 // Listener to use if/when evaluation is requested.
Hans Boehm50ed3202015-06-09 14:35:49 -0700131 public static final int MAX_LEADING_ZEROES = 6;
Hans Boehma0e45f32015-05-30 13:20:35 -0700132 // Maximum number of leading zeroes after decimal point before we
133 // switch to scientific notation with negative exponent.
Hans Boehm50ed3202015-06-09 14:35:49 -0700134 public static final int MAX_TRAILING_ZEROES = 6;
Hans Boehma0e45f32015-05-30 13:20:35 -0700135 // Maximum number of trailing zeroes before the decimal point before
136 // we switch to scientific notation with positive exponent.
137 private static final int SCI_NOTATION_EXTRA = 1;
138 // Extra digits for standard scientific notation. In this case we
Hans Boehm80018c82015-08-02 16:59:07 -0700139 // have a decimal point and no ellipsis.
140 // We assume that we do not drop digits to make room for the decimal
141 // point in ordinary scientific notation. Thus >= 1.
Hans Boehm65a99a42016-02-03 18:16:07 -0800142 private static final int MAX_COPY_EXTRA = 100;
143 // The number of extra digits we are willing to compute to copy
144 // a result as an exact number.
145 private static final int MAX_RECOMPUTE_DIGITS = 2000;
146 // The maximum number of digits we're willing to recompute in the UI
147 // thread. We only do this for known rational results, where we
148 // can bound the computation cost.
Chenjie Yu3937b652016-06-01 23:14:26 -0700149 private final ForegroundColorSpan mExponentColorSpan;
150 private final BackgroundColorSpan mHighlightSpan;
Hans Boehm65a99a42016-02-03 18:16:07 -0800151
Hans Boehm1176f232015-05-11 16:26:03 -0700152 private ActionMode mActionMode;
Chenjie Yu3937b652016-06-01 23:14:26 -0700153 private ActionMode.Callback mCopyActionModeCallback;
154 private ContextMenu mContextMenu;
Hans Boehm84614952014-11-25 18:46:17 -0800155
Annie Chin37c33b62016-11-22 14:46:28 -0800156 // The user requested that the result currently being evaluated should be stored to "memory".
157 private boolean mStoreToMemoryRequested = false;
158
Hans Boehm84614952014-11-25 18:46:17 -0800159 public CalculatorResult(Context context, AttributeSet attrs) {
160 super(context, attrs);
161 mScroller = new OverScroller(context);
Chenjie Yu3937b652016-06-01 23:14:26 -0700162 mHighlightSpan = new BackgroundColorSpan(getHighlightColor());
163 mExponentColorSpan = new ForegroundColorSpan(
164 ContextCompat.getColor(context, R.color.display_result_exponent_text_color));
Hans Boehm84614952014-11-25 18:46:17 -0800165 mGestureDetector = new GestureDetector(context,
166 new GestureDetector.SimpleOnGestureListener() {
167 @Override
Justin Klaassend48b7562015-04-16 16:51:38 -0700168 public boolean onDown(MotionEvent e) {
169 return true;
170 }
171 @Override
Hans Boehm84614952014-11-25 18:46:17 -0800172 public boolean onFling(MotionEvent e1, MotionEvent e2,
173 float velocityX, float velocityY) {
174 if (!mScroller.isFinished()) {
175 mCurrentPos = mScroller.getFinalX();
176 }
177 mScroller.forceFinished(true);
Chenjie Yu3937b652016-06-01 23:14:26 -0700178 stopActionModeOrContextMenu();
Hans Boehmfbcef702015-04-27 18:07:47 -0700179 CalculatorResult.this.cancelLongPress();
180 // Ignore scrolls of error string, etc.
181 if (!mScrollable) return true;
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700182 mScroller.fling(mCurrentPos, 0, - (int) velocityX, 0 /* horizontal only */,
Hans Boehm61568a12015-05-18 18:25:41 -0700183 mMinPos, mMaxPos, 0, 0);
Justin Klaassen44595162015-05-28 17:55:20 -0700184 postInvalidateOnAnimation();
Hans Boehm84614952014-11-25 18:46:17 -0800185 return true;
186 }
187 @Override
188 public boolean onScroll(MotionEvent e1, MotionEvent e2,
189 float distanceX, float distanceY) {
Hans Boehm61568a12015-05-18 18:25:41 -0700190 int distance = (int)distanceX;
Hans Boehm84614952014-11-25 18:46:17 -0800191 if (!mScroller.isFinished()) {
192 mCurrentPos = mScroller.getFinalX();
193 }
194 mScroller.forceFinished(true);
Chenjie Yu3937b652016-06-01 23:14:26 -0700195 stopActionModeOrContextMenu();
Hans Boehm84614952014-11-25 18:46:17 -0800196 CalculatorResult.this.cancelLongPress();
197 if (!mScrollable) return true;
Hans Boehm61568a12015-05-18 18:25:41 -0700198 if (mCurrentPos + distance < mMinPos) {
199 distance = mMinPos - mCurrentPos;
200 } else if (mCurrentPos + distance > mMaxPos) {
201 distance = mMaxPos - mCurrentPos;
202 }
Hans Boehm84614952014-11-25 18:46:17 -0800203 int duration = (int)(e2.getEventTime() - e1.getEventTime());
204 if (duration < 1 || duration > 100) duration = 10;
Hans Boehm61568a12015-05-18 18:25:41 -0700205 mScroller.startScroll(mCurrentPos, 0, distance, 0, (int)duration);
Justin Klaassen44595162015-05-28 17:55:20 -0700206 postInvalidateOnAnimation();
Hans Boehm84614952014-11-25 18:46:17 -0800207 return true;
208 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700209 @Override
210 public void onLongPress(MotionEvent e) {
Hans Boehm1176f232015-05-11 16:26:03 -0700211 if (mValid) {
Justin Klaassen3a05c7e2016-03-04 12:40:02 -0800212 performLongClick();
Hans Boehm1176f232015-05-11 16:26:03 -0700213 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700214 }
Hans Boehm84614952014-11-25 18:46:17 -0800215 });
Justin Klaassen3a05c7e2016-03-04 12:40:02 -0800216 setOnTouchListener(new View.OnTouchListener() {
217 @Override
218 public boolean onTouch(View v, MotionEvent event) {
219 return mGestureDetector.onTouchEvent(event);
220 }
221 });
Hans Boehm14344ff2016-06-08 13:01:51 -0700222
Chenjie Yu3937b652016-06-01 23:14:26 -0700223 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
224 setupActionMode();
225 } else {
226 setupContextMenu();
227 }
Hans Boehm14344ff2016-06-08 13:01:51 -0700228
Hans Boehm84614952014-11-25 18:46:17 -0800229 setCursorVisible(false);
Christine Franksafe28bb2016-07-29 17:24:52 -0700230 setLongClickable(false);
Christine Franks6f6c24a2016-09-08 18:21:47 -0700231 setContentDescription(context.getString(R.string.desc_result));
Hans Boehm84614952014-11-25 18:46:17 -0800232 }
233
Hans Boehm8f051c32016-10-03 16:53:58 -0700234 void setEvaluator(Evaluator evaluator, long index) {
Hans Boehm84614952014-11-25 18:46:17 -0800235 mEvaluator = evaluator;
Hans Boehm8f051c32016-10-03 16:53:58 -0700236 mIndex = index;
Annie Chin7c586042016-11-18 15:57:37 -0800237 requestLayout();
Hans Boehm84614952014-11-25 18:46:17 -0800238 }
239
Hans Boehmcd72f7e2016-06-01 16:21:25 -0700240 // Compute maximum digit width the hard way.
241 private static float getMaxDigitWidth(TextPaint paint) {
242 // Compute the maximum advance width for each digit, thus accounting for between-character
243 // spaces. If we ever support other kinds of digits, we may have to avoid kerning effects
244 // that could reduce the advance width within this particular string.
245 final String allDigits = "0123456789";
246 final float[] widths = new float[allDigits.length()];
247 paint.getTextWidths(allDigits, widths);
248 float maxWidth = 0;
249 for (float x : widths) {
250 maxWidth = Math.max(x, maxWidth);
251 }
252 return maxWidth;
253 }
254
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700255 @Override
256 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Justin Klaassend06f51d2016-08-03 00:41:31 -0700257 if (!isLaidOut()) {
Annie Chin45e59972016-12-02 14:46:09 -0800258 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Justin Klaassend06f51d2016-08-03 00:41:31 -0700259 // Set a minimum height so scaled error messages won't affect our layout.
260 setMinimumHeight(getLineHeight() + getCompoundPaddingBottom()
261 + getCompoundPaddingTop());
262 }
263
Justin Klaassen44595162015-05-28 17:55:20 -0700264 final TextPaint paint = getPaint();
Hans Boehm80018c82015-08-02 16:59:07 -0700265 final Context context = getContext();
Hans Boehmcd72f7e2016-06-01 16:21:25 -0700266 final float newCharWidth = getMaxDigitWidth(paint);
Hans Boehm80018c82015-08-02 16:59:07 -0700267 // Digits are presumed to have no more than newCharWidth.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700268 // There are two instances when we know that the result is otherwise narrower than
269 // expected:
270 // 1. For standard scientific notation (our type 1), we know that we have a norrow decimal
271 // point and no (usually wide) ellipsis symbol. We allow one extra digit
272 // (SCI_NOTATION_EXTRA) to compensate, and consider that in determining available width.
273 // 2. If we are using digit grouping separators and a decimal point, we give ourselves
274 // a fractional extra space for those separators, the value of which depends on whether
275 // there is also an ellipsis.
276 //
277 // Maximum extra space we need in various cases:
278 // Type 1 scientific notation, assuming ellipsis, minus sign and E are wider than a digit:
279 // Two minus signs + "E" + "." - 3 digits.
280 // Type 2 scientific notation:
281 // Ellipsis + "E" + "-" - 3 digits.
282 // In the absence of scientific notation, we may need a little less space.
283 // We give ourselves a bit of extra credit towards comma insertion and give
284 // ourselves more if we have either
285 // No ellipsis, or
286 // A decimal separator.
287
288 // Calculate extra space we need to reserve, in addition to character count.
Hans Boehm80018c82015-08-02 16:59:07 -0700289 final float decimalSeparatorWidth = Layout.getDesiredWidth(
290 context.getString(R.string.dec_point), paint);
Hans Boehm24c91ed2016-06-30 18:53:44 -0700291 final float minusWidth = Layout.getDesiredWidth(context.getString(R.string.op_sub), paint);
292 final float minusExtraWidth = Math.max(minusWidth - newCharWidth, 0.0f);
293 final float ellipsisWidth = Layout.getDesiredWidth(KeyMaps.ELLIPSIS, paint);
294 final float ellipsisExtraWidth = Math.max(ellipsisWidth - newCharWidth, 0.0f);
295 final float expWidth = Layout.getDesiredWidth(KeyMaps.translateResult("e"), paint);
296 final float expExtraWidth = Math.max(expWidth - newCharWidth, 0.0f);
297 final float type1Extra = 2 * minusExtraWidth + expExtraWidth + decimalSeparatorWidth;
298 final float type2Extra = ellipsisExtraWidth + expExtraWidth + minusExtraWidth;
299 final float extraWidth = Math.max(type1Extra, type2Extra);
300 final int intExtraWidth = (int) Math.ceil(extraWidth) + 1 /* to cover rounding sins */;
Hans Boehm80018c82015-08-02 16:59:07 -0700301 final int newWidthConstraint = MeasureSpec.getSize(widthMeasureSpec)
Hans Boehm24c91ed2016-06-30 18:53:44 -0700302 - (getPaddingLeft() + getPaddingRight()) - intExtraWidth;
303
304 // Calculate other width constants we need to handle grouping separators.
305 final float groupingSeparatorW =
306 Layout.getDesiredWidth(KeyMaps.translateResult(","), paint);
307 // Credits in the absence of any scientific notation:
308 float noExponentCredit = extraWidth - Math.max(ellipsisExtraWidth, minusExtraWidth);
309 final float noEllipsisCredit = extraWidth - minusExtraWidth; // includes noExponentCredit.
310 final float decimalCredit = Math.max(newCharWidth - decimalSeparatorWidth, 0.0f);
311
312 mNoExponentCredit = noExponentCredit / newCharWidth;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700313 synchronized(mWidthLock) {
Hans Boehm013969e2015-04-13 20:29:47 -0700314 mWidthConstraint = newWidthConstraint;
315 mCharWidth = newCharWidth;
Hans Boehm24c91ed2016-06-30 18:53:44 -0700316 mNoEllipsisCredit = noEllipsisCredit / newCharWidth;
317 mDecimalCredit = decimalCredit / newCharWidth;
318 mGroupingSeparatorWidthRatio = groupingSeparatorW / newCharWidth;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700319 }
Hans Boehm14344ff2016-06-08 13:01:51 -0700320
321 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700322 }
323
Annie Chin06fd3cf2016-11-07 16:04:33 -0800324 @Override
325 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
326 super.onLayout(changed, left, top, right, bottom);
327
Hans Boehmbd01e4b2016-11-23 10:12:58 -0800328 if (mEvaluator != null && mEvaluationRequest != SHOULD_NOT_EVALUATE) {
Annie Chin06fd3cf2016-11-07 16:04:33 -0800329 final CalculatorExpr expr = mEvaluator.getExpr(mIndex);
330 if (expr != null && expr.hasInterestingOps()) {
Hans Boehmbd01e4b2016-11-23 10:12:58 -0800331 if (mEvaluationRequest == SHOULD_REQUIRE) {
332 mEvaluator.requireResult(mIndex, mEvaluationListener, this);
333 } else {
334 mEvaluator.evaluateAndNotify(mIndex, mEvaluationListener, this);
335 }
Annie Chin06fd3cf2016-11-07 16:04:33 -0800336 }
337 }
338 }
339
Hans Boehmbd01e4b2016-11-23 10:12:58 -0800340 /**
341 * Specify whether we should evaluate result on layout.
342 * @param should one of SHOULD_REQUIRE, SHOULD_EVALUATE, SHOULD_NOT_EVALUATE
343 */
344 public void setShouldEvaluateResult(@EvaluationRequest int request,
345 Evaluator.EvaluationListener listener) {
Hans Boehmd4959e82016-11-15 18:01:28 -0800346 mEvaluationListener = listener;
Hans Boehmbd01e4b2016-11-23 10:12:58 -0800347 mEvaluationRequest = request;
Annie Chinbc001882016-11-09 19:41:21 -0800348 }
349
Hans Boehm8f051c32016-10-03 16:53:58 -0700350 // From Evaluator.CharMetricsInfo.
351 @Override
Hans Boehm24c91ed2016-06-30 18:53:44 -0700352 public float separatorChars(String s, int len) {
353 int start = 0;
354 while (start < len && !Character.isDigit(s.charAt(start))) {
355 ++start;
356 }
357 // We assume the rest consists of digits, and for consistency with the rest
358 // of the code, we assume all digits have width mCharWidth.
359 final int nDigits = len - start;
360 // We currently insert a digit separator every three digits.
361 final int nSeparators = (nDigits - 1) / 3;
362 synchronized(mWidthLock) {
363 // Always return an upper bound, even in the presence of rounding errors.
364 return nSeparators * mGroupingSeparatorWidthRatio;
365 }
366 }
367
Hans Boehm8f051c32016-10-03 16:53:58 -0700368 // From Evaluator.CharMetricsInfo.
369 @Override
Hans Boehm24c91ed2016-06-30 18:53:44 -0700370 public float getNoEllipsisCredit() {
371 synchronized(mWidthLock) {
372 return mNoEllipsisCredit;
373 }
374 }
375
Hans Boehm8f051c32016-10-03 16:53:58 -0700376 // From Evaluator.CharMetricsInfo.
377 @Override
Hans Boehm24c91ed2016-06-30 18:53:44 -0700378 public float getDecimalCredit() {
379 synchronized(mWidthLock) {
380 return mDecimalCredit;
381 }
382 }
383
Hans Boehma0e45f32015-05-30 13:20:35 -0700384 // Return the length of the exponent representation for the given exponent, in
385 // characters.
386 private final int expLen(int exp) {
387 if (exp == 0) return 0;
Hans Boehm5e802f32015-06-22 17:18:52 -0700388 final int abs_exp_digits = (int) Math.ceil(Math.log10(Math.abs((double)exp))
389 + 0.0000000001d /* Round whole numbers to next integer */);
390 return abs_exp_digits + (exp >= 0 ? 1 : 2);
Hans Boehm61568a12015-05-18 18:25:41 -0700391 }
392
Hans Boehma0e45f32015-05-30 13:20:35 -0700393 /**
394 * Initiate display of a new result.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700395 * Only called from UI thread.
Hans Boehma0e45f32015-05-30 13:20:35 -0700396 * The parameters specify various properties of the result.
Hans Boehm8f051c32016-10-03 16:53:58 -0700397 * @param index Index of expression that was just evaluated. Currently ignored, since we only
398 * expect notification for the expression result being displayed.
Hans Boehma0e45f32015-05-30 13:20:35 -0700399 * @param initPrec Initial display precision computed by evaluator. (1 = tenths digit)
400 * @param msd Position of most significant digit. Offset from left of string.
401 Evaluator.INVALID_MSD if unknown.
402 * @param leastDigPos Position of least significant digit (1 = tenths digit)
403 * or Integer.MAX_VALUE.
404 * @param truncatedWholePart Result up to but not including decimal point.
405 Currently we only use the length.
406 */
Hans Boehm8f051c32016-10-03 16:53:58 -0700407 @Override
408 public void onEvaluate(long index, int initPrec, int msd, int leastDigPos,
409 String truncatedWholePart) {
Hans Boehma0e45f32015-05-30 13:20:35 -0700410 initPositions(initPrec, msd, leastDigPos, truncatedWholePart);
Annie Chin37c33b62016-11-22 14:46:28 -0800411
412 if (mStoreToMemoryRequested) {
413 mEvaluator.copyToMemory(index);
414 mStoreToMemoryRequested = false;
415 }
Hans Boehm84614952014-11-25 18:46:17 -0800416 redisplay();
417 }
418
Hans Boehma0e45f32015-05-30 13:20:35 -0700419 /**
Annie Chin37c33b62016-11-22 14:46:28 -0800420 * Store the result for this index if it is available.
421 * If it is unavailable, set mStoreToMemoryRequested to indicate that we should store
422 * when evaluation is complete.
423 */
424 public void onMemoryStore() {
425 if (mEvaluator.hasResult(mIndex)) {
426 mEvaluator.copyToMemory(mIndex);
427 } else {
428 mStoreToMemoryRequested = true;
429 mEvaluator.requireResult(mIndex, this /* listener */, this /* CharMetricsInfo */);
430 }
431 }
432
433 /**
Christine Franks1d99be12016-11-14 14:00:36 -0800434 * Add the result to the value currently in memory.
435 */
436 public void onMemoryAdd() {
Christine Franksff8e0d82016-11-23 12:28:26 -0800437 mEvaluator.addToMemory(mIndex);
Christine Franks1d99be12016-11-14 14:00:36 -0800438 }
439
440 /**
441 * Subtract the result from the value currently in memory.
442 */
443 public void onMemorySubtract() {
Christine Franksff8e0d82016-11-23 12:28:26 -0800444 mEvaluator.subtractFromMemory(mIndex);
Christine Franks1d99be12016-11-14 14:00:36 -0800445 }
446
447 /**
Hans Boehm5e802f32015-06-22 17:18:52 -0700448 * Set up scroll bounds (mMinPos, mMaxPos, etc.) and determine whether the result is
449 * scrollable, based on the supplied information about the result.
Hans Boehma0e45f32015-05-30 13:20:35 -0700450 * This is unfortunately complicated because we need to predict whether trailing digits
451 * will eventually be replaced by an exponent.
452 * Just appending the exponent during formatting would be simpler, but would produce
453 * jumpier results during transitions.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700454 * Only called from UI thread.
Hans Boehma0e45f32015-05-30 13:20:35 -0700455 */
Hans Boehm5e802f32015-06-22 17:18:52 -0700456 private void initPositions(int initPrecOffset, int msdIndex, int lsdOffset,
457 String truncatedWholePart) {
Hans Boehma0e45f32015-05-30 13:20:35 -0700458 int maxChars = getMaxChars();
Hans Boehm24c91ed2016-06-30 18:53:44 -0700459 mWholeLen = truncatedWholePart.length();
460 // Allow a tiny amount of slop for associativity/rounding differences in length
461 // calculation. If getPreferredPrec() decided it should fit, we want to make it fit, too.
462 // We reserved one extra pixel, so the extra length is OK.
463 final int nSeparatorChars = (int) Math.ceil(
464 separatorChars(truncatedWholePart, truncatedWholePart.length())
465 - getNoEllipsisCredit() - 0.0001f);
466 mWholePartFits = mWholeLen + nSeparatorChars <= maxChars;
Hans Boehma0e45f32015-05-30 13:20:35 -0700467 mLastPos = INVALID;
Hans Boehm5e802f32015-06-22 17:18:52 -0700468 mLsdOffset = lsdOffset;
Hans Boehm24c91ed2016-06-30 18:53:44 -0700469 mAppendExponent = false;
Hans Boehma0e45f32015-05-30 13:20:35 -0700470 // Prevent scrolling past initial position, which is calculated to show leading digits.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700471 mCurrentPos = mMinPos = (int) Math.round(initPrecOffset * mCharWidth);
Hans Boehm5e802f32015-06-22 17:18:52 -0700472 if (msdIndex == Evaluator.INVALID_MSD) {
Hans Boehma0e45f32015-05-30 13:20:35 -0700473 // Possible zero value
Hans Boehm5e802f32015-06-22 17:18:52 -0700474 if (lsdOffset == Integer.MIN_VALUE) {
Hans Boehma0e45f32015-05-30 13:20:35 -0700475 // Definite zero value.
476 mMaxPos = mMinPos;
Hans Boehm24c91ed2016-06-30 18:53:44 -0700477 mMaxCharOffset = (int) Math.round(mMaxPos/mCharWidth);
Hans Boehma0e45f32015-05-30 13:20:35 -0700478 mScrollable = false;
479 } else {
480 // May be very small nonzero value. Allow user to find out.
Hans Boehm5e802f32015-06-22 17:18:52 -0700481 mMaxPos = mMaxCharOffset = MAX_RIGHT_SCROLL;
Hans Boehm24c91ed2016-06-30 18:53:44 -0700482 mMinPos -= mCharWidth; // Allow for future minus sign.
Hans Boehma0e45f32015-05-30 13:20:35 -0700483 mScrollable = true;
484 }
485 return;
486 }
Hans Boehma0e45f32015-05-30 13:20:35 -0700487 int negative = truncatedWholePart.charAt(0) == '-' ? 1 : 0;
Hans Boehm65a99a42016-02-03 18:16:07 -0800488 if (msdIndex > mWholeLen && msdIndex <= mWholeLen + 3) {
Hans Boehm5e802f32015-06-22 17:18:52 -0700489 // Avoid tiny negative exponent; pretend msdIndex is just to the right of decimal point.
Hans Boehm65a99a42016-02-03 18:16:07 -0800490 msdIndex = mWholeLen - 1;
Hans Boehma0e45f32015-05-30 13:20:35 -0700491 }
Hans Boehm24c91ed2016-06-30 18:53:44 -0700492 // Set to position of leftmost significant digit relative to dec. point. Usually negative.
Hans Boehm65a99a42016-02-03 18:16:07 -0800493 int minCharOffset = msdIndex - mWholeLen;
Hans Boehm5e802f32015-06-22 17:18:52 -0700494 if (minCharOffset > -1 && minCharOffset < MAX_LEADING_ZEROES + 2) {
Hans Boehma0e45f32015-05-30 13:20:35 -0700495 // Small number of leading zeroes, avoid scientific notation.
Hans Boehm5e802f32015-06-22 17:18:52 -0700496 minCharOffset = -1;
Hans Boehma0e45f32015-05-30 13:20:35 -0700497 }
Hans Boehm5e802f32015-06-22 17:18:52 -0700498 if (lsdOffset < MAX_RIGHT_SCROLL) {
499 mMaxCharOffset = lsdOffset;
500 if (mMaxCharOffset < -1 && mMaxCharOffset > -(MAX_TRAILING_ZEROES + 2)) {
501 mMaxCharOffset = -1;
Hans Boehma0e45f32015-05-30 13:20:35 -0700502 }
Hans Boehm5e802f32015-06-22 17:18:52 -0700503 // lsdOffset is positive or negative, never 0.
504 int currentExpLen = 0; // Length of required standard scientific notation exponent.
505 if (mMaxCharOffset < -1) {
506 currentExpLen = expLen(-minCharOffset - 1);
507 } else if (minCharOffset > -1 || mMaxCharOffset >= maxChars) {
Hans Boehm24c91ed2016-06-30 18:53:44 -0700508 // Number is either entirely to the right of decimal point, or decimal point is
509 // not visible when scrolled to the right.
Hans Boehm5e802f32015-06-22 17:18:52 -0700510 currentExpLen = expLen(-minCharOffset);
Hans Boehma0e45f32015-05-30 13:20:35 -0700511 }
Hans Boehm24c91ed2016-06-30 18:53:44 -0700512 // Exponent length does not included added decimal point. But whenever we add a
513 // decimal point, we allow an extra character (SCI_NOTATION_EXTRA).
514 final int separatorLength = mWholePartFits && minCharOffset < -3 ? nSeparatorChars : 0;
515 mScrollable = (mMaxCharOffset + currentExpLen + separatorLength - minCharOffset
516 + negative >= maxChars);
517 // Now adjust mMaxCharOffset for any required exponent.
Hans Boehm5e802f32015-06-22 17:18:52 -0700518 int newMaxCharOffset;
519 if (currentExpLen > 0) {
520 if (mScrollable) {
521 // We'll use exponent corresponding to leastDigPos when scrolled to right.
522 newMaxCharOffset = mMaxCharOffset + expLen(-lsdOffset);
523 } else {
524 newMaxCharOffset = mMaxCharOffset + currentExpLen;
525 }
526 if (mMaxCharOffset <= -1 && newMaxCharOffset > -1) {
527 // Very unlikely; just drop exponent.
528 mMaxCharOffset = -1;
529 } else {
Hans Boehm24c91ed2016-06-30 18:53:44 -0700530 mMaxCharOffset = Math.min(newMaxCharOffset, MAX_RIGHT_SCROLL);
Hans Boehma0e45f32015-05-30 13:20:35 -0700531 }
Hans Boehm24c91ed2016-06-30 18:53:44 -0700532 mMaxPos = Math.min((int) Math.round(mMaxCharOffset * mCharWidth),
533 MAX_RIGHT_SCROLL);
534 } else if (!mWholePartFits && !mScrollable) {
535 // Corner case in which entire number fits, but not with grouping separators. We
536 // will use an exponent in un-scrolled position, which may hide digits. Scrolling
537 // by one character will remove the exponent and reveal the last digits. Note
538 // that in the forced scientific notation case, the exponent length is not
539 // factored into mMaxCharOffset, since we do not want such an increase to impact
540 // scrolling behavior. In the unscrollable case, we thus have to append the
541 // exponent at the end using the forcePrecision argument to formatResult, in order
542 // to ensure that we get the entire result.
543 mScrollable = (mMaxCharOffset + expLen(-minCharOffset - 1) - minCharOffset
544 + negative >= maxChars);
545 if (mScrollable) {
546 mMaxPos = (int) Math.ceil(mMinPos + mCharWidth);
547 // Single character scroll will remove exponent and show remaining piece.
548 } else {
549 mMaxPos = mMinPos;
550 mAppendExponent = true;
551 }
552 } else {
553 mMaxPos = Math.min((int) Math.round(mMaxCharOffset * mCharWidth),
554 MAX_RIGHT_SCROLL);
Hans Boehma0e45f32015-05-30 13:20:35 -0700555 }
Hans Boehma0e45f32015-05-30 13:20:35 -0700556 if (!mScrollable) {
557 // Position the number consistently with our assumptions to make sure it
558 // actually fits.
559 mCurrentPos = mMaxPos;
560 }
561 } else {
Hans Boehm5e802f32015-06-22 17:18:52 -0700562 mMaxPos = mMaxCharOffset = MAX_RIGHT_SCROLL;
Hans Boehma0e45f32015-05-30 13:20:35 -0700563 mScrollable = true;
564 }
565 }
566
Hans Boehm24c91ed2016-06-30 18:53:44 -0700567 /**
568 * Display error message indicated by resourceId.
569 * UI thread only.
570 */
Hans Boehm8f051c32016-10-03 16:53:58 -0700571 @Override
572 public void onError(long index, int resourceId) {
Annie Chin37c33b62016-11-22 14:46:28 -0800573 mStoreToMemoryRequested = false;
Hans Boehm760a9dc2015-04-20 10:27:12 -0700574 mValid = true;
Christine Franksafe28bb2016-07-29 17:24:52 -0700575 setLongClickable(false);
Hans Boehm84614952014-11-25 18:46:17 -0800576 mScrollable = false;
Hans Boehm14344ff2016-06-08 13:01:51 -0700577 final String msg = getContext().getString(resourceId);
Hans Boehm14344ff2016-06-08 13:01:51 -0700578 final float measuredWidth = Layout.getDesiredWidth(msg, getPaint());
Hans Boehm24c91ed2016-06-30 18:53:44 -0700579 if (measuredWidth > mWidthConstraint) {
Hans Boehm14344ff2016-06-08 13:01:51 -0700580 // Multiply by .99 to avoid rounding effects.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700581 final float scaleFactor = 0.99f * mWidthConstraint / measuredWidth;
Hans Boehm14344ff2016-06-08 13:01:51 -0700582 final RelativeSizeSpan smallTextSpan = new RelativeSizeSpan(scaleFactor);
583 final SpannableString scaledMsg = new SpannableString(msg);
584 scaledMsg.setSpan(smallTextSpan, 0, msg.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
585 setText(scaledMsg);
586 } else {
587 setText(msg);
588 }
Hans Boehm84614952014-11-25 18:46:17 -0800589 }
590
Hans Boehm013969e2015-04-13 20:29:47 -0700591 private final int MAX_COPY_SIZE = 1000000;
592
Hans Boehma0e45f32015-05-30 13:20:35 -0700593 /*
594 * Return the most significant digit position in the given string or Evaluator.INVALID_MSD.
Hans Boehm3666e632015-07-27 18:33:12 -0700595 * Unlike Evaluator.getMsdIndexOf, we treat a final 1 as significant.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700596 * Pure function; callable from anywhere.
Hans Boehma0e45f32015-05-30 13:20:35 -0700597 */
Hans Boehm3666e632015-07-27 18:33:12 -0700598 public static int getNaiveMsdIndexOf(String s) {
Hans Boehm65a99a42016-02-03 18:16:07 -0800599 final int len = s.length();
Hans Boehma0e45f32015-05-30 13:20:35 -0700600 for (int i = 0; i < len; ++i) {
601 char c = s.charAt(i);
602 if (c != '-' && c != '.' && c != '0') {
603 return i;
604 }
605 }
606 return Evaluator.INVALID_MSD;
607 }
608
Hans Boehm24c91ed2016-06-30 18:53:44 -0700609 /**
610 * Format a result returned by Evaluator.getString() into a single line containing ellipses
611 * (if appropriate) and an exponent (if appropriate).
612 * We add two distinct kinds of exponents:
613 * (1) If the final result contains the leading digit we use standard scientific notation.
614 * (2) If not, we add an exponent corresponding to an interpretation of the final result as
615 * an integer.
616 * We add an ellipsis on the left if the result was truncated.
617 * We add ellipses and exponents in a way that leaves most digits in the position they
618 * would have been in had we not done so. This minimizes jumps as a result of scrolling.
619 * Result is NOT internationalized, uses "E" for exponent.
620 * Called only from UI thread; We sometimes omit locking for fields.
621 * @param precOffset The value that was passed to getString. Identifies the significance of
622 the rightmost digit. A value of 1 means the rightmost digits corresponds to tenths.
623 * @param maxDigs The maximum number of characters in the result
624 * @param truncated The in parameter was already truncated, beyond possibly removing the
625 minus sign.
626 * @param negative The in parameter represents a negative result. (Minus sign may be removed
627 without setting truncated.)
628 * @param lastDisplayedOffset If not null, we set lastDisplayedOffset[0] to the offset of
629 the last digit actually appearing in the display.
630 * @param forcePrecision If true, we make sure that the last displayed digit corresponds to
631 precOffset, and allow maxDigs to be exceeded in adding the exponent and commas.
632 * @param forceSciNotation Force scientific notation. May be set because we don't have
633 space for grouping separators, but whole number otherwise fits.
634 * @param insertCommas Insert commas (literally, not internationalized) as digit separators.
635 We only ever do this for the integral part of a number, and only when no
636 exponent is displayed in the initial position. The combination of which means
637 that we only do it when no exponent is displayed.
638 We insert commas in a way that does consider the width of the actual localized digit
639 separator. Commas count towards maxDigs as the appropriate fraction of a digit.
640 */
641 private String formatResult(String in, int precOffset, int maxDigs, boolean truncated,
642 boolean negative, int lastDisplayedOffset[], boolean forcePrecision,
643 boolean forceSciNotation, boolean insertCommas) {
Hans Boehm5e802f32015-06-22 17:18:52 -0700644 final int minusSpace = negative ? 1 : 0;
Hans Boehm3666e632015-07-27 18:33:12 -0700645 final int msdIndex = truncated ? -1 : getNaiveMsdIndexOf(in); // INVALID_MSD is OK.
Hans Boehm5e802f32015-06-22 17:18:52 -0700646 String result = in;
Hans Boehm24c91ed2016-06-30 18:53:44 -0700647 boolean needEllipsis = false;
Hans Boehm73ecff22015-09-03 16:04:50 -0700648 if (truncated || (negative && result.charAt(0) != '-')) {
Hans Boehm24c91ed2016-06-30 18:53:44 -0700649 needEllipsis = true;
Hans Boehm73ecff22015-09-03 16:04:50 -0700650 result = KeyMaps.ELLIPSIS + result.substring(1, result.length());
651 // Ellipsis may be removed again in the type(1) scientific notation case.
652 }
653 final int decIndex = result.indexOf('.');
Hans Boehm65a99a42016-02-03 18:16:07 -0800654 if (lastDisplayedOffset != null) {
655 lastDisplayedOffset[0] = precOffset;
656 }
Hans Boehm24c91ed2016-06-30 18:53:44 -0700657 if (forceSciNotation || (decIndex == -1 || msdIndex != Evaluator.INVALID_MSD
Hans Boehm5e802f32015-06-22 17:18:52 -0700658 && msdIndex - decIndex > MAX_LEADING_ZEROES + 1) && precOffset != -1) {
Hans Boehm24c91ed2016-06-30 18:53:44 -0700659 // Either:
660 // 1) No decimal point displayed, and it's not just to the right of the last digit, or
661 // 2) we are at the front of a number whos integral part is too large to allow
662 // comma insertion, or
663 // 3) we should suppress leading zeroes.
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700664 // Add an exponent to let the user track which digits are currently displayed.
Hans Boehm5e802f32015-06-22 17:18:52 -0700665 // Start with type (2) exponent if we dropped no digits. -1 accounts for decimal point.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700666 // We currently never show digit separators together with an exponent.
Hans Boehm5e802f32015-06-22 17:18:52 -0700667 final int initExponent = precOffset > 0 ? -precOffset : -precOffset - 1;
668 int exponent = initExponent;
Hans Boehm08e8f322015-04-21 13:18:38 -0700669 boolean hasPoint = false;
Hans Boehm5e802f32015-06-22 17:18:52 -0700670 if (!truncated && msdIndex < maxDigs - 1
671 && result.length() - msdIndex + 1 + minusSpace
672 <= maxDigs + SCI_NOTATION_EXTRA) {
673 // Type (1) exponent computation and transformation:
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700674 // Leading digit is in display window. Use standard calculator scientific notation
675 // with one digit to the left of the decimal point. Insert decimal point and
676 // delete leading zeroes.
Hans Boehma0e45f32015-05-30 13:20:35 -0700677 // We try to keep leading digits roughly in position, and never
Hans Boehmf6dae112015-06-18 17:57:50 -0700678 // lengthen the result by more than SCI_NOTATION_EXTRA.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700679 if (decIndex > msdIndex) {
680 // In the forceSciNotation, we can have a decimal point in the relevant digit
681 // range. Remove it.
682 result = result.substring(0, decIndex)
683 + result.substring(decIndex + 1, result.length());
684 // msdIndex and precOffset unaffected.
685 }
Hans Boehm5e802f32015-06-22 17:18:52 -0700686 final int resLen = result.length();
687 String fraction = result.substring(msdIndex + 1, resLen);
688 result = (negative ? "-" : "") + result.substring(msdIndex, msdIndex + 1)
689 + "." + fraction;
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700690 // Original exp was correct for decimal point at right of fraction.
691 // Adjust by length of fraction.
Hans Boehm5e802f32015-06-22 17:18:52 -0700692 exponent = initExponent + resLen - msdIndex - 1;
Hans Boehm08e8f322015-04-21 13:18:38 -0700693 hasPoint = true;
694 }
Hans Boehm73ecff22015-09-03 16:04:50 -0700695 // Exponent can't be zero.
696 // Actually add the exponent of either type:
697 if (!forcePrecision) {
698 int dropDigits; // Digits to drop to make room for exponent.
699 if (hasPoint) {
700 // Type (1) exponent.
701 // Drop digits even if there is room. Otherwise the scrolling gets jumpy.
702 dropDigits = expLen(exponent);
703 if (dropDigits >= result.length() - 1) {
704 // Jumpy is better than no mantissa. Probably impossible anyway.
705 dropDigits = Math.max(result.length() - 2, 0);
Hans Boehma0e45f32015-05-30 13:20:35 -0700706 }
Hans Boehm73ecff22015-09-03 16:04:50 -0700707 } else {
708 // Type (2) exponent.
709 // Exponent depends on the number of digits we drop, which depends on
710 // exponent ...
711 for (dropDigits = 2; expLen(initExponent + dropDigits) > dropDigits;
712 ++dropDigits) {}
713 exponent = initExponent + dropDigits;
714 if (precOffset - dropDigits > mLsdOffset) {
715 // This can happen if e.g. result = 10^40 + 10^10
716 // It turns out we would otherwise display ...10e9 because it takes
717 // the same amount of space as ...1e10 but shows one more digit.
718 // But we don't want to display a trailing zero, even if it's free.
719 ++dropDigits;
720 ++exponent;
721 }
Hans Boehm08e8f322015-04-21 13:18:38 -0700722 }
Hans Boehm73ecff22015-09-03 16:04:50 -0700723 result = result.substring(0, result.length() - dropDigits);
Hans Boehm65a99a42016-02-03 18:16:07 -0800724 if (lastDisplayedOffset != null) {
725 lastDisplayedOffset[0] -= dropDigits;
726 }
Hans Boehm73ecff22015-09-03 16:04:50 -0700727 }
728 result = result + "E" + Integer.toString(exponent);
Hans Boehm24c91ed2016-06-30 18:53:44 -0700729 } else if (insertCommas) {
730 // Add commas to the whole number section, and then truncate on left to fit,
731 // counting commas as a fractional digit.
732 final int wholeStart = needEllipsis ? 1 : 0;
733 int orig_length = result.length();
734 final float nCommaChars;
735 if (decIndex != -1) {
736 nCommaChars = separatorChars(result, decIndex);
737 result = StringUtils.addCommas(result, wholeStart, decIndex)
738 + result.substring(decIndex, orig_length);
739 } else {
740 nCommaChars = separatorChars(result, orig_length);
741 result = StringUtils.addCommas(result, wholeStart, orig_length);
742 }
743 if (needEllipsis) {
744 orig_length -= 1; // Exclude ellipsis.
745 }
746 final float len = orig_length + nCommaChars;
747 int deletedChars = 0;
748 final float ellipsisCredit = getNoEllipsisCredit();
749 final float decimalCredit = getDecimalCredit();
750 final float effectiveLen = len - (decIndex == -1 ? 0 : getDecimalCredit());
751 final float ellipsisAdjustment =
752 needEllipsis ? mNoExponentCredit : getNoEllipsisCredit();
753 // As above, we allow for a tiny amount of extra length here, for consistency with
754 // getPreferredPrec().
755 if (effectiveLen - ellipsisAdjustment > (float) (maxDigs - wholeStart) + 0.0001f
756 && !forcePrecision) {
757 float deletedWidth = 0.0f;
758 while (effectiveLen - mNoExponentCredit - deletedWidth
759 > (float) (maxDigs - 1 /* for ellipsis */)) {
760 if (result.charAt(deletedChars) == ',') {
761 deletedWidth += mGroupingSeparatorWidthRatio;
762 } else {
763 deletedWidth += 1.0f;
764 }
765 deletedChars++;
766 }
767 }
768 if (deletedChars > 0) {
769 result = KeyMaps.ELLIPSIS + result.substring(deletedChars, result.length());
770 } else if (needEllipsis) {
771 result = KeyMaps.ELLIPSIS + result;
772 }
Hans Boehm5e802f32015-06-22 17:18:52 -0700773 }
774 return result;
Hans Boehm08e8f322015-04-21 13:18:38 -0700775 }
776
Hans Boehmf6dae112015-06-18 17:57:50 -0700777 /**
778 * Get formatted, but not internationalized, result from mEvaluator.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700779 * @param precOffset requested position (1 = tenths) of last included digit
780 * @param maxSize maximum number of characters (more or less) in result
781 * @param lastDisplayedOffset zeroth entry is set to actual offset of last included digit,
Hans Boehm65a99a42016-02-03 18:16:07 -0800782 * after adjusting for exponent, etc. May be null.
Hans Boehmf6dae112015-06-18 17:57:50 -0700783 * @param forcePrecision Ensure that last included digit is at pos, at the expense
784 * of treating maxSize as a soft limit.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700785 * @param forceSciNotation Force scientific notation, even if not required by maxSize.
786 * @param insertCommas Insert commas as digit separators.
Hans Boehmf6dae112015-06-18 17:57:50 -0700787 */
Hans Boehm5e802f32015-06-22 17:18:52 -0700788 private String getFormattedResult(int precOffset, int maxSize, int lastDisplayedOffset[],
Hans Boehm24c91ed2016-06-30 18:53:44 -0700789 boolean forcePrecision, boolean forceSciNotation, boolean insertCommas) {
Hans Boehm08e8f322015-04-21 13:18:38 -0700790 final boolean truncated[] = new boolean[1];
791 final boolean negative[] = new boolean[1];
Hans Boehm5e802f32015-06-22 17:18:52 -0700792 final int requestedPrecOffset[] = {precOffset};
Hans Boehm8f051c32016-10-03 16:53:58 -0700793 final String rawResult = mEvaluator.getString(mIndex, requestedPrecOffset, mMaxCharOffset,
794 maxSize, truncated, negative, this);
Hans Boehm5e802f32015-06-22 17:18:52 -0700795 return formatResult(rawResult, requestedPrecOffset[0], maxSize, truncated[0], negative[0],
Hans Boehm24c91ed2016-06-30 18:53:44 -0700796 lastDisplayedOffset, forcePrecision, forceSciNotation, insertCommas);
Hans Boehm08e8f322015-04-21 13:18:38 -0700797 }
798
Hans Boehm65a99a42016-02-03 18:16:07 -0800799 /**
800 * Return entire result (within reason) up to current displayed precision.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700801 * @param withSeparators Add digit separators
Hans Boehm65a99a42016-02-03 18:16:07 -0800802 */
Hans Boehm24c91ed2016-06-30 18:53:44 -0700803 public String getFullText(boolean withSeparators) {
Hans Boehm760a9dc2015-04-20 10:27:12 -0700804 if (!mValid) return "";
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700805 if (!mScrollable) return getText().toString();
Hans Boehm5e802f32015-06-22 17:18:52 -0700806 return KeyMaps.translateResult(getFormattedResult(mLastDisplayedOffset, MAX_COPY_SIZE,
Hans Boehm24c91ed2016-06-30 18:53:44 -0700807 null, true /* forcePrecision */, false /* forceSciNotation */, withSeparators));
Hans Boehm84614952014-11-25 18:46:17 -0800808 }
809
Hans Boehm24c91ed2016-06-30 18:53:44 -0700810 /**
811 * Did the above produce a correct result?
812 * UI thread only.
813 */
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700814 public boolean fullTextIsExact() {
Hans Boehm24c91ed2016-06-30 18:53:44 -0700815 return !mScrollable || (mMaxCharOffset == getCharOffset(mCurrentPos)
816 && mMaxCharOffset != MAX_RIGHT_SCROLL);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700817 }
818
Hans Boehm61568a12015-05-18 18:25:41 -0700819 /**
Hans Boehm65a99a42016-02-03 18:16:07 -0800820 * Get entire result up to current displayed precision, or up to MAX_COPY_EXTRA additional
821 * digits, if it will lead to an exact result.
822 */
823 public String getFullCopyText() {
824 if (!mValid
825 || mLsdOffset == Integer.MAX_VALUE
826 || fullTextIsExact()
827 || mWholeLen > MAX_RECOMPUTE_DIGITS
828 || mWholeLen + mLsdOffset > MAX_RECOMPUTE_DIGITS
829 || mLsdOffset - mLastDisplayedOffset > MAX_COPY_EXTRA) {
Hans Boehm24c91ed2016-06-30 18:53:44 -0700830 return getFullText(false /* withSeparators */);
Hans Boehm65a99a42016-02-03 18:16:07 -0800831 }
832 // It's reasonable to compute and copy the exact result instead.
833 final int nonNegLsdOffset = Math.max(0, mLsdOffset);
Hans Boehm8f051c32016-10-03 16:53:58 -0700834 final String rawResult = mEvaluator.getResult(mIndex).toStringTruncated(nonNegLsdOffset);
Hans Boehm65a99a42016-02-03 18:16:07 -0800835 final String formattedResult = formatResult(rawResult, nonNegLsdOffset, MAX_COPY_SIZE,
Hans Boehm24c91ed2016-06-30 18:53:44 -0700836 false, rawResult.charAt(0) == '-', null, true /* forcePrecision */,
837 false /* forceSciNotation */, false /* insertCommas */);
Hans Boehm65a99a42016-02-03 18:16:07 -0800838 return KeyMaps.translateResult(formattedResult);
839 }
840
841 /**
Hans Boehm61568a12015-05-18 18:25:41 -0700842 * Return the maximum number of characters that will fit in the result display.
Hans Boehm8f051c32016-10-03 16:53:58 -0700843 * May be called asynchronously from non-UI thread. From Evaluator.CharMetricsInfo.
Hans Boehmd4959e82016-11-15 18:01:28 -0800844 * Returns zero if measurement hasn't completed.
Hans Boehm61568a12015-05-18 18:25:41 -0700845 */
Hans Boehm8f051c32016-10-03 16:53:58 -0700846 @Override
847 public int getMaxChars() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700848 int result;
849 synchronized(mWidthLock) {
Hans Boehmd4959e82016-11-15 18:01:28 -0800850 return (int) Math.floor(mWidthConstraint / mCharWidth);
Hans Boehm84614952014-11-25 18:46:17 -0800851 }
852 }
853
Hans Boehm61568a12015-05-18 18:25:41 -0700854 /**
Justin Klaassen44595162015-05-28 17:55:20 -0700855 * @return {@code true} if the currently displayed result is scrollable
Hans Boehm61568a12015-05-18 18:25:41 -0700856 */
Justin Klaassen44595162015-05-28 17:55:20 -0700857 public boolean isScrollable() {
858 return mScrollable;
Hans Boehm61568a12015-05-18 18:25:41 -0700859 }
860
Hans Boehm24c91ed2016-06-30 18:53:44 -0700861 /**
862 * Map pixel position to digit offset.
863 * UI thread only.
864 */
865 int getCharOffset(int pos) {
866 return (int) Math.round(pos / mCharWidth); // Lock not needed.
Hans Boehm013969e2015-04-13 20:29:47 -0700867 }
868
Hans Boehm84614952014-11-25 18:46:17 -0800869 void clear() {
Hans Boehm760a9dc2015-04-20 10:27:12 -0700870 mValid = false;
Hans Boehm1176f232015-05-11 16:26:03 -0700871 mScrollable = false;
Hans Boehm84614952014-11-25 18:46:17 -0800872 setText("");
Christine Franksafe28bb2016-07-29 17:24:52 -0700873 setLongClickable(false);
Hans Boehm84614952014-11-25 18:46:17 -0800874 }
875
Hans Boehm8f051c32016-10-03 16:53:58 -0700876 @Override
877 public void onCancelled(long index) {
878 clear();
Annie Chin37c33b62016-11-22 14:46:28 -0800879 mStoreToMemoryRequested = false;
Hans Boehm8f051c32016-10-03 16:53:58 -0700880 }
881
Hans Boehm24c91ed2016-06-30 18:53:44 -0700882 /**
883 * Refresh display.
Hans Boehm8f051c32016-10-03 16:53:58 -0700884 * Only called in UI thread. Index argument is currently ignored.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700885 */
Hans Boehm8f051c32016-10-03 16:53:58 -0700886 @Override
887 public void onReevaluate(long index) {
888 redisplay();
889 }
890
891 public void redisplay() {
Christine Franks6f6c24a2016-09-08 18:21:47 -0700892 if (mScroller.isFinished() && length() > 0) {
Christine Franksd21205c2016-08-04 10:06:15 -0700893 setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
894 }
Hans Boehm24c91ed2016-06-30 18:53:44 -0700895 int currentCharOffset = getCharOffset(mCurrentPos);
Hans Boehm84614952014-11-25 18:46:17 -0800896 int maxChars = getMaxChars();
Hans Boehm5e802f32015-06-22 17:18:52 -0700897 int lastDisplayedOffset[] = new int[1];
Hans Boehm24c91ed2016-06-30 18:53:44 -0700898 String result = getFormattedResult(currentCharOffset, maxChars, lastDisplayedOffset,
899 mAppendExponent /* forcePrecision; preserve entire result */,
900 !mWholePartFits
901 && currentCharOffset == getCharOffset(mMinPos) /* forceSciNotation */,
902 mWholePartFits /* insertCommas */ );
Hans Boehm0b9806f2015-06-29 16:07:15 -0700903 int expIndex = result.indexOf('E');
Hans Boehm013969e2015-04-13 20:29:47 -0700904 result = KeyMaps.translateResult(result);
Hans Boehm5e802f32015-06-22 17:18:52 -0700905 if (expIndex > 0 && result.indexOf('.') == -1) {
Hans Boehm84614952014-11-25 18:46:17 -0800906 // Gray out exponent if used as position indicator
907 SpannableString formattedResult = new SpannableString(result);
Hans Boehm5e802f32015-06-22 17:18:52 -0700908 formattedResult.setSpan(mExponentColorSpan, expIndex, result.length(),
Hans Boehm84614952014-11-25 18:46:17 -0800909 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
910 setText(formattedResult);
911 } else {
912 setText(result);
913 }
Hans Boehm5e802f32015-06-22 17:18:52 -0700914 mLastDisplayedOffset = lastDisplayedOffset[0];
Hans Boehm760a9dc2015-04-20 10:27:12 -0700915 mValid = true;
Christine Franksafe28bb2016-07-29 17:24:52 -0700916 setLongClickable(true);
Hans Boehm84614952014-11-25 18:46:17 -0800917 }
918
919 @Override
Christine Franks6f6c24a2016-09-08 18:21:47 -0700920 protected void onTextChanged(java.lang.CharSequence text, int start, int lengthBefore,
921 int lengthAfter) {
922 super.onTextChanged(text, start, lengthBefore, lengthAfter);
923
924 if (!mScrollable || mScroller.isFinished()) {
925 if (lengthBefore == 0 && lengthAfter > 0) {
926 setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
927 setContentDescription(null);
928 } else if (lengthBefore > 0 && lengthAfter == 0) {
929 setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_NONE);
930 setContentDescription(getContext().getString(R.string.desc_result));
931 }
932 }
933 }
934
935 @Override
Hans Boehm84614952014-11-25 18:46:17 -0800936 public void computeScroll() {
Christine Franks6f6c24a2016-09-08 18:21:47 -0700937 if (!mScrollable) {
938 return;
939 }
940
Hans Boehm84614952014-11-25 18:46:17 -0800941 if (mScroller.computeScrollOffset()) {
942 mCurrentPos = mScroller.getCurrX();
Hans Boehm24c91ed2016-06-30 18:53:44 -0700943 if (getCharOffset(mCurrentPos) != getCharOffset(mLastPos)) {
Hans Boehm84614952014-11-25 18:46:17 -0800944 mLastPos = mCurrentPos;
945 redisplay();
946 }
Christine Franks6f6c24a2016-09-08 18:21:47 -0700947 }
948
949 if (!mScroller.isFinished()) {
Justin Klaassen44595162015-05-28 17:55:20 -0700950 postInvalidateOnAnimation();
Christine Franksd21205c2016-08-04 10:06:15 -0700951 setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_NONE);
Christine Franks6f6c24a2016-09-08 18:21:47 -0700952 } else if (length() > 0){
Christine Franksd21205c2016-08-04 10:06:15 -0700953 setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
Hans Boehm84614952014-11-25 18:46:17 -0800954 }
955 }
956
Chenjie Yu3937b652016-06-01 23:14:26 -0700957 /**
Christine Franks1d99be12016-11-14 14:00:36 -0800958 * Use ActionMode for copy/memory support on M and higher.
Chenjie Yu3937b652016-06-01 23:14:26 -0700959 */
960 @TargetApi(Build.VERSION_CODES.M)
961 private void setupActionMode() {
962 mCopyActionModeCallback = new ActionMode.Callback2() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700963
Chenjie Yu3937b652016-06-01 23:14:26 -0700964 @Override
965 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
966 final MenuInflater inflater = mode.getMenuInflater();
967 return createCopyMenu(inflater, menu);
968 }
Hans Boehm7f83e362015-06-10 15:41:04 -0700969
Chenjie Yu3937b652016-06-01 23:14:26 -0700970 @Override
971 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
972 return false; // Return false if nothing is done
973 }
Hans Boehm7f83e362015-06-10 15:41:04 -0700974
Chenjie Yu3937b652016-06-01 23:14:26 -0700975 @Override
976 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
977 if (onMenuItemClick(item)) {
Hans Boehm65a99a42016-02-03 18:16:07 -0800978 mode.finish();
979 return true;
Chenjie Yu3937b652016-06-01 23:14:26 -0700980 } else {
981 return false;
Hans Boehm65a99a42016-02-03 18:16:07 -0800982 }
Chenjie Yu3937b652016-06-01 23:14:26 -0700983 }
984
985 @Override
986 public void onDestroyActionMode(ActionMode mode) {
987 unhighlightResult();
988 mActionMode = null;
989 }
990
991 @Override
992 public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
993 super.onGetContentRect(mode, view, outRect);
994
995 outRect.left += view.getPaddingLeft();
996 outRect.top += view.getPaddingTop();
997 outRect.right -= view.getPaddingRight();
998 outRect.bottom -= view.getPaddingBottom();
999 final int width = (int) Layout.getDesiredWidth(getText(), getPaint());
1000 if (width < outRect.width()) {
1001 outRect.left = outRect.right - width;
1002 }
1003
1004 if (!BuildCompat.isAtLeastN()) {
1005 // The CAB (prior to N) only takes the translation of a view into account, so
1006 // if a scale is applied to the view then the offset outRect will end up being
1007 // positioned incorrectly. We workaround that limitation by manually applying
1008 // the scale to the outRect, which the CAB will then offset to the correct
1009 // position.
1010 final float scaleX = view.getScaleX();
1011 final float scaleY = view.getScaleY();
1012 outRect.left *= scaleX;
1013 outRect.right *= scaleX;
1014 outRect.top *= scaleY;
1015 outRect.bottom *= scaleY;
1016 }
1017 }
1018 };
1019 setOnLongClickListener(new View.OnLongClickListener() {
1020 @Override
1021 public boolean onLongClick(View v) {
1022 if (mValid) {
1023 mActionMode = startActionMode(mCopyActionModeCallback,
1024 ActionMode.TYPE_FLOATING);
1025 return true;
1026 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001027 return false;
1028 }
Chenjie Yu3937b652016-06-01 23:14:26 -07001029 });
1030 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001031
Chenjie Yu3937b652016-06-01 23:14:26 -07001032 /**
Christine Franks1d99be12016-11-14 14:00:36 -08001033 * Use ContextMenu for copy/memory support on L and lower.
Chenjie Yu3937b652016-06-01 23:14:26 -07001034 */
1035 private void setupContextMenu() {
1036 setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
1037 @Override
1038 public void onCreateContextMenu(ContextMenu contextMenu, View view,
1039 ContextMenu.ContextMenuInfo contextMenuInfo) {
1040 final MenuInflater inflater = new MenuInflater(getContext());
1041 createCopyMenu(inflater, contextMenu);
1042 mContextMenu = contextMenu;
1043 for(int i = 0; i < contextMenu.size(); i ++) {
1044 contextMenu.getItem(i).setOnMenuItemClickListener(CalculatorResult.this);
1045 }
Hans Boehm7f83e362015-06-10 15:41:04 -07001046 }
Chenjie Yu3937b652016-06-01 23:14:26 -07001047 });
1048 setOnLongClickListener(new View.OnLongClickListener() {
1049 @Override
1050 public boolean onLongClick(View v) {
1051 if (mValid) {
1052 return showContextMenu();
1053 }
1054 return false;
Justin Klaassenf1b61f42016-04-27 16:00:11 -07001055 }
Chenjie Yu3937b652016-06-01 23:14:26 -07001056 });
1057 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001058
Chenjie Yu3937b652016-06-01 23:14:26 -07001059 private boolean createCopyMenu(MenuInflater inflater, Menu menu) {
Christine Franks1d99be12016-11-14 14:00:36 -08001060 inflater.inflate(R.menu.menu_result, menu);
1061 final boolean displayMemory = mEvaluator.getMemoryIndex() != 0;
1062 final MenuItem memoryAddItem = menu.findItem(R.id.memory_add);
1063 final MenuItem memorySubtractItem = menu.findItem(R.id.memory_subtract);
1064 memoryAddItem.setEnabled(displayMemory);
1065 memorySubtractItem.setEnabled(displayMemory);
Chenjie Yu3937b652016-06-01 23:14:26 -07001066 highlightResult();
1067 return true;
1068 }
1069
1070 public boolean stopActionModeOrContextMenu() {
Hans Boehm1176f232015-05-11 16:26:03 -07001071 if (mActionMode != null) {
1072 mActionMode.finish();
1073 return true;
1074 }
Chenjie Yu3937b652016-06-01 23:14:26 -07001075 if (mContextMenu != null) {
1076 unhighlightResult();
1077 mContextMenu.close();
1078 return true;
1079 }
Hans Boehm1176f232015-05-11 16:26:03 -07001080 return false;
1081 }
1082
Chenjie Yu3937b652016-06-01 23:14:26 -07001083 private void highlightResult() {
1084 final Spannable text = (Spannable) getText();
1085 text.setSpan(mHighlightSpan, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
1086 }
1087
1088 private void unhighlightResult() {
1089 final Spannable text = (Spannable) getText();
1090 text.removeSpan(mHighlightSpan);
1091 }
1092
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001093 private void setPrimaryClip(ClipData clip) {
1094 ClipboardManager clipboard = (ClipboardManager) getContext().
Hans Boehmc01cd7f2015-05-12 18:32:19 -07001095 getSystemService(Context.CLIPBOARD_SERVICE);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001096 clipboard.setPrimaryClip(clip);
1097 }
1098
1099 private void copyContent() {
Hans Boehm65a99a42016-02-03 18:16:07 -08001100 final CharSequence text = getFullCopyText();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001101 ClipboardManager clipboard =
Hans Boehmc01cd7f2015-05-12 18:32:19 -07001102 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
1103 // We include a tag URI, to allow us to recognize our own results and handle them
1104 // specially.
Hans Boehm8f051c32016-10-03 16:53:58 -07001105 ClipData.Item newItem = new ClipData.Item(text, null, mEvaluator.capture(mIndex));
Hans Boehmc01cd7f2015-05-12 18:32:19 -07001106 String[] mimeTypes = new String[] {ClipDescription.MIMETYPE_TEXT_PLAIN};
1107 ClipData cd = new ClipData("calculator result", mimeTypes, newItem);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001108 clipboard.setPrimaryClip(cd);
Hans Boehmc01cd7f2015-05-12 18:32:19 -07001109 Toast.makeText(getContext(), R.string.text_copied_toast, Toast.LENGTH_SHORT).show();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001110 }
1111
Chenjie Yu3937b652016-06-01 23:14:26 -07001112 @Override
1113 public boolean onMenuItemClick(MenuItem item) {
1114 switch (item.getItemId()) {
Christine Franks1d99be12016-11-14 14:00:36 -08001115 case R.id.memory_add:
1116 onMemoryAdd();
1117 return true;
1118 case R.id.memory_subtract:
1119 onMemorySubtract();
1120 return true;
1121 case R.id.memory_store:
1122 onMemoryStore();
1123 return true;
Chenjie Yu3937b652016-06-01 23:14:26 -07001124 case R.id.menu_copy:
Hans Boehm8f051c32016-10-03 16:53:58 -07001125 if (mEvaluator.evaluationInProgress(mIndex)) {
Chenjie Yu3937b652016-06-01 23:14:26 -07001126 // Refuse to copy placeholder characters.
1127 return false;
1128 } else {
1129 copyContent();
1130 unhighlightResult();
1131 return true;
1132 }
1133 default:
1134 return false;
1135 }
1136 }
Hans Boehmbd01e4b2016-11-23 10:12:58 -08001137}