blob: 5e61dc4231f7a2d58cd3bbb3148de3280282a10c [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()) {
258 // Set a minimum height so scaled error messages won't affect our layout.
259 setMinimumHeight(getLineHeight() + getCompoundPaddingBottom()
260 + getCompoundPaddingTop());
261 }
262
Justin Klaassen44595162015-05-28 17:55:20 -0700263 final TextPaint paint = getPaint();
Hans Boehm80018c82015-08-02 16:59:07 -0700264 final Context context = getContext();
Hans Boehmcd72f7e2016-06-01 16:21:25 -0700265 final float newCharWidth = getMaxDigitWidth(paint);
Hans Boehm80018c82015-08-02 16:59:07 -0700266 // Digits are presumed to have no more than newCharWidth.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700267 // There are two instances when we know that the result is otherwise narrower than
268 // expected:
269 // 1. For standard scientific notation (our type 1), we know that we have a norrow decimal
270 // point and no (usually wide) ellipsis symbol. We allow one extra digit
271 // (SCI_NOTATION_EXTRA) to compensate, and consider that in determining available width.
272 // 2. If we are using digit grouping separators and a decimal point, we give ourselves
273 // a fractional extra space for those separators, the value of which depends on whether
274 // there is also an ellipsis.
275 //
276 // Maximum extra space we need in various cases:
277 // Type 1 scientific notation, assuming ellipsis, minus sign and E are wider than a digit:
278 // Two minus signs + "E" + "." - 3 digits.
279 // Type 2 scientific notation:
280 // Ellipsis + "E" + "-" - 3 digits.
281 // In the absence of scientific notation, we may need a little less space.
282 // We give ourselves a bit of extra credit towards comma insertion and give
283 // ourselves more if we have either
284 // No ellipsis, or
285 // A decimal separator.
286
287 // Calculate extra space we need to reserve, in addition to character count.
Hans Boehm80018c82015-08-02 16:59:07 -0700288 final float decimalSeparatorWidth = Layout.getDesiredWidth(
289 context.getString(R.string.dec_point), paint);
Hans Boehm24c91ed2016-06-30 18:53:44 -0700290 final float minusWidth = Layout.getDesiredWidth(context.getString(R.string.op_sub), paint);
291 final float minusExtraWidth = Math.max(minusWidth - newCharWidth, 0.0f);
292 final float ellipsisWidth = Layout.getDesiredWidth(KeyMaps.ELLIPSIS, paint);
293 final float ellipsisExtraWidth = Math.max(ellipsisWidth - newCharWidth, 0.0f);
294 final float expWidth = Layout.getDesiredWidth(KeyMaps.translateResult("e"), paint);
295 final float expExtraWidth = Math.max(expWidth - newCharWidth, 0.0f);
296 final float type1Extra = 2 * minusExtraWidth + expExtraWidth + decimalSeparatorWidth;
297 final float type2Extra = ellipsisExtraWidth + expExtraWidth + minusExtraWidth;
298 final float extraWidth = Math.max(type1Extra, type2Extra);
299 final int intExtraWidth = (int) Math.ceil(extraWidth) + 1 /* to cover rounding sins */;
Hans Boehm80018c82015-08-02 16:59:07 -0700300 final int newWidthConstraint = MeasureSpec.getSize(widthMeasureSpec)
Hans Boehm24c91ed2016-06-30 18:53:44 -0700301 - (getPaddingLeft() + getPaddingRight()) - intExtraWidth;
302
303 // Calculate other width constants we need to handle grouping separators.
304 final float groupingSeparatorW =
305 Layout.getDesiredWidth(KeyMaps.translateResult(","), paint);
306 // Credits in the absence of any scientific notation:
307 float noExponentCredit = extraWidth - Math.max(ellipsisExtraWidth, minusExtraWidth);
308 final float noEllipsisCredit = extraWidth - minusExtraWidth; // includes noExponentCredit.
309 final float decimalCredit = Math.max(newCharWidth - decimalSeparatorWidth, 0.0f);
310
311 mNoExponentCredit = noExponentCredit / newCharWidth;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700312 synchronized(mWidthLock) {
Hans Boehm013969e2015-04-13 20:29:47 -0700313 mWidthConstraint = newWidthConstraint;
314 mCharWidth = newCharWidth;
Hans Boehm24c91ed2016-06-30 18:53:44 -0700315 mNoEllipsisCredit = noEllipsisCredit / newCharWidth;
316 mDecimalCredit = decimalCredit / newCharWidth;
317 mGroupingSeparatorWidthRatio = groupingSeparatorW / newCharWidth;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700318 }
Hans Boehm14344ff2016-06-08 13:01:51 -0700319
320 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700321 }
322
Annie Chin06fd3cf2016-11-07 16:04:33 -0800323 @Override
324 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
325 super.onLayout(changed, left, top, right, bottom);
326
Hans Boehmbd01e4b2016-11-23 10:12:58 -0800327 if (mEvaluator != null && mEvaluationRequest != SHOULD_NOT_EVALUATE) {
Annie Chin06fd3cf2016-11-07 16:04:33 -0800328 final CalculatorExpr expr = mEvaluator.getExpr(mIndex);
329 if (expr != null && expr.hasInterestingOps()) {
Hans Boehmbd01e4b2016-11-23 10:12:58 -0800330 if (mEvaluationRequest == SHOULD_REQUIRE) {
331 mEvaluator.requireResult(mIndex, mEvaluationListener, this);
332 } else {
333 mEvaluator.evaluateAndNotify(mIndex, mEvaluationListener, this);
334 }
Annie Chin06fd3cf2016-11-07 16:04:33 -0800335 }
336 }
337 }
338
Hans Boehmbd01e4b2016-11-23 10:12:58 -0800339 /**
340 * Specify whether we should evaluate result on layout.
341 * @param should one of SHOULD_REQUIRE, SHOULD_EVALUATE, SHOULD_NOT_EVALUATE
342 */
343 public void setShouldEvaluateResult(@EvaluationRequest int request,
344 Evaluator.EvaluationListener listener) {
Hans Boehmd4959e82016-11-15 18:01:28 -0800345 mEvaluationListener = listener;
Hans Boehmbd01e4b2016-11-23 10:12:58 -0800346 mEvaluationRequest = request;
Annie Chinbc001882016-11-09 19:41:21 -0800347 }
348
Hans Boehm8f051c32016-10-03 16:53:58 -0700349 // From Evaluator.CharMetricsInfo.
350 @Override
Hans Boehm24c91ed2016-06-30 18:53:44 -0700351 public float separatorChars(String s, int len) {
352 int start = 0;
353 while (start < len && !Character.isDigit(s.charAt(start))) {
354 ++start;
355 }
356 // We assume the rest consists of digits, and for consistency with the rest
357 // of the code, we assume all digits have width mCharWidth.
358 final int nDigits = len - start;
359 // We currently insert a digit separator every three digits.
360 final int nSeparators = (nDigits - 1) / 3;
361 synchronized(mWidthLock) {
362 // Always return an upper bound, even in the presence of rounding errors.
363 return nSeparators * mGroupingSeparatorWidthRatio;
364 }
365 }
366
Hans Boehm8f051c32016-10-03 16:53:58 -0700367 // From Evaluator.CharMetricsInfo.
368 @Override
Hans Boehm24c91ed2016-06-30 18:53:44 -0700369 public float getNoEllipsisCredit() {
370 synchronized(mWidthLock) {
371 return mNoEllipsisCredit;
372 }
373 }
374
Hans Boehm8f051c32016-10-03 16:53:58 -0700375 // From Evaluator.CharMetricsInfo.
376 @Override
Hans Boehm24c91ed2016-06-30 18:53:44 -0700377 public float getDecimalCredit() {
378 synchronized(mWidthLock) {
379 return mDecimalCredit;
380 }
381 }
382
Hans Boehma0e45f32015-05-30 13:20:35 -0700383 // Return the length of the exponent representation for the given exponent, in
384 // characters.
385 private final int expLen(int exp) {
386 if (exp == 0) return 0;
Hans Boehm5e802f32015-06-22 17:18:52 -0700387 final int abs_exp_digits = (int) Math.ceil(Math.log10(Math.abs((double)exp))
388 + 0.0000000001d /* Round whole numbers to next integer */);
389 return abs_exp_digits + (exp >= 0 ? 1 : 2);
Hans Boehm61568a12015-05-18 18:25:41 -0700390 }
391
Hans Boehma0e45f32015-05-30 13:20:35 -0700392 /**
393 * Initiate display of a new result.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700394 * Only called from UI thread.
Hans Boehma0e45f32015-05-30 13:20:35 -0700395 * The parameters specify various properties of the result.
Hans Boehm8f051c32016-10-03 16:53:58 -0700396 * @param index Index of expression that was just evaluated. Currently ignored, since we only
397 * expect notification for the expression result being displayed.
Hans Boehma0e45f32015-05-30 13:20:35 -0700398 * @param initPrec Initial display precision computed by evaluator. (1 = tenths digit)
399 * @param msd Position of most significant digit. Offset from left of string.
400 Evaluator.INVALID_MSD if unknown.
401 * @param leastDigPos Position of least significant digit (1 = tenths digit)
402 * or Integer.MAX_VALUE.
403 * @param truncatedWholePart Result up to but not including decimal point.
404 Currently we only use the length.
405 */
Hans Boehm8f051c32016-10-03 16:53:58 -0700406 @Override
407 public void onEvaluate(long index, int initPrec, int msd, int leastDigPos,
408 String truncatedWholePart) {
Hans Boehma0e45f32015-05-30 13:20:35 -0700409 initPositions(initPrec, msd, leastDigPos, truncatedWholePart);
Annie Chin37c33b62016-11-22 14:46:28 -0800410
411 if (mStoreToMemoryRequested) {
412 mEvaluator.copyToMemory(index);
413 mStoreToMemoryRequested = false;
414 }
Hans Boehm84614952014-11-25 18:46:17 -0800415 redisplay();
416 }
417
Hans Boehma0e45f32015-05-30 13:20:35 -0700418 /**
Annie Chin37c33b62016-11-22 14:46:28 -0800419 * Store the result for this index if it is available.
420 * If it is unavailable, set mStoreToMemoryRequested to indicate that we should store
421 * when evaluation is complete.
422 */
423 public void onMemoryStore() {
424 if (mEvaluator.hasResult(mIndex)) {
425 mEvaluator.copyToMemory(mIndex);
426 } else {
427 mStoreToMemoryRequested = true;
428 mEvaluator.requireResult(mIndex, this /* listener */, this /* CharMetricsInfo */);
429 }
430 }
431
432 /**
Christine Franks1d99be12016-11-14 14:00:36 -0800433 * Add the result to the value currently in memory.
434 */
435 public void onMemoryAdd() {
436 mEvaluator.addToMemory(Evaluator.MAIN_INDEX);
437 }
438
439 /**
440 * Subtract the result from the value currently in memory.
441 */
442 public void onMemorySubtract() {
443 mEvaluator.subtractFromMemory(Evaluator.MAIN_INDEX);
444 }
445
446 /**
Hans Boehm5e802f32015-06-22 17:18:52 -0700447 * Set up scroll bounds (mMinPos, mMaxPos, etc.) and determine whether the result is
448 * scrollable, based on the supplied information about the result.
Hans Boehma0e45f32015-05-30 13:20:35 -0700449 * This is unfortunately complicated because we need to predict whether trailing digits
450 * will eventually be replaced by an exponent.
451 * Just appending the exponent during formatting would be simpler, but would produce
452 * jumpier results during transitions.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700453 * Only called from UI thread.
Hans Boehma0e45f32015-05-30 13:20:35 -0700454 */
Hans Boehm5e802f32015-06-22 17:18:52 -0700455 private void initPositions(int initPrecOffset, int msdIndex, int lsdOffset,
456 String truncatedWholePart) {
Hans Boehma0e45f32015-05-30 13:20:35 -0700457 int maxChars = getMaxChars();
Hans Boehm24c91ed2016-06-30 18:53:44 -0700458 mWholeLen = truncatedWholePart.length();
459 // Allow a tiny amount of slop for associativity/rounding differences in length
460 // calculation. If getPreferredPrec() decided it should fit, we want to make it fit, too.
461 // We reserved one extra pixel, so the extra length is OK.
462 final int nSeparatorChars = (int) Math.ceil(
463 separatorChars(truncatedWholePart, truncatedWholePart.length())
464 - getNoEllipsisCredit() - 0.0001f);
465 mWholePartFits = mWholeLen + nSeparatorChars <= maxChars;
Hans Boehma0e45f32015-05-30 13:20:35 -0700466 mLastPos = INVALID;
Hans Boehm5e802f32015-06-22 17:18:52 -0700467 mLsdOffset = lsdOffset;
Hans Boehm24c91ed2016-06-30 18:53:44 -0700468 mAppendExponent = false;
Hans Boehma0e45f32015-05-30 13:20:35 -0700469 // Prevent scrolling past initial position, which is calculated to show leading digits.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700470 mCurrentPos = mMinPos = (int) Math.round(initPrecOffset * mCharWidth);
Hans Boehm5e802f32015-06-22 17:18:52 -0700471 if (msdIndex == Evaluator.INVALID_MSD) {
Hans Boehma0e45f32015-05-30 13:20:35 -0700472 // Possible zero value
Hans Boehm5e802f32015-06-22 17:18:52 -0700473 if (lsdOffset == Integer.MIN_VALUE) {
Hans Boehma0e45f32015-05-30 13:20:35 -0700474 // Definite zero value.
475 mMaxPos = mMinPos;
Hans Boehm24c91ed2016-06-30 18:53:44 -0700476 mMaxCharOffset = (int) Math.round(mMaxPos/mCharWidth);
Hans Boehma0e45f32015-05-30 13:20:35 -0700477 mScrollable = false;
478 } else {
479 // May be very small nonzero value. Allow user to find out.
Hans Boehm5e802f32015-06-22 17:18:52 -0700480 mMaxPos = mMaxCharOffset = MAX_RIGHT_SCROLL;
Hans Boehm24c91ed2016-06-30 18:53:44 -0700481 mMinPos -= mCharWidth; // Allow for future minus sign.
Hans Boehma0e45f32015-05-30 13:20:35 -0700482 mScrollable = true;
483 }
484 return;
485 }
Hans Boehma0e45f32015-05-30 13:20:35 -0700486 int negative = truncatedWholePart.charAt(0) == '-' ? 1 : 0;
Hans Boehm65a99a42016-02-03 18:16:07 -0800487 if (msdIndex > mWholeLen && msdIndex <= mWholeLen + 3) {
Hans Boehm5e802f32015-06-22 17:18:52 -0700488 // Avoid tiny negative exponent; pretend msdIndex is just to the right of decimal point.
Hans Boehm65a99a42016-02-03 18:16:07 -0800489 msdIndex = mWholeLen - 1;
Hans Boehma0e45f32015-05-30 13:20:35 -0700490 }
Hans Boehm24c91ed2016-06-30 18:53:44 -0700491 // Set to position of leftmost significant digit relative to dec. point. Usually negative.
Hans Boehm65a99a42016-02-03 18:16:07 -0800492 int minCharOffset = msdIndex - mWholeLen;
Hans Boehm5e802f32015-06-22 17:18:52 -0700493 if (minCharOffset > -1 && minCharOffset < MAX_LEADING_ZEROES + 2) {
Hans Boehma0e45f32015-05-30 13:20:35 -0700494 // Small number of leading zeroes, avoid scientific notation.
Hans Boehm5e802f32015-06-22 17:18:52 -0700495 minCharOffset = -1;
Hans Boehma0e45f32015-05-30 13:20:35 -0700496 }
Hans Boehm5e802f32015-06-22 17:18:52 -0700497 if (lsdOffset < MAX_RIGHT_SCROLL) {
498 mMaxCharOffset = lsdOffset;
499 if (mMaxCharOffset < -1 && mMaxCharOffset > -(MAX_TRAILING_ZEROES + 2)) {
500 mMaxCharOffset = -1;
Hans Boehma0e45f32015-05-30 13:20:35 -0700501 }
Hans Boehm5e802f32015-06-22 17:18:52 -0700502 // lsdOffset is positive or negative, never 0.
503 int currentExpLen = 0; // Length of required standard scientific notation exponent.
504 if (mMaxCharOffset < -1) {
505 currentExpLen = expLen(-minCharOffset - 1);
506 } else if (minCharOffset > -1 || mMaxCharOffset >= maxChars) {
Hans Boehm24c91ed2016-06-30 18:53:44 -0700507 // Number is either entirely to the right of decimal point, or decimal point is
508 // not visible when scrolled to the right.
Hans Boehm5e802f32015-06-22 17:18:52 -0700509 currentExpLen = expLen(-minCharOffset);
Hans Boehma0e45f32015-05-30 13:20:35 -0700510 }
Hans Boehm24c91ed2016-06-30 18:53:44 -0700511 // Exponent length does not included added decimal point. But whenever we add a
512 // decimal point, we allow an extra character (SCI_NOTATION_EXTRA).
513 final int separatorLength = mWholePartFits && minCharOffset < -3 ? nSeparatorChars : 0;
514 mScrollable = (mMaxCharOffset + currentExpLen + separatorLength - minCharOffset
515 + negative >= maxChars);
516 // Now adjust mMaxCharOffset for any required exponent.
Hans Boehm5e802f32015-06-22 17:18:52 -0700517 int newMaxCharOffset;
518 if (currentExpLen > 0) {
519 if (mScrollable) {
520 // We'll use exponent corresponding to leastDigPos when scrolled to right.
521 newMaxCharOffset = mMaxCharOffset + expLen(-lsdOffset);
522 } else {
523 newMaxCharOffset = mMaxCharOffset + currentExpLen;
524 }
525 if (mMaxCharOffset <= -1 && newMaxCharOffset > -1) {
526 // Very unlikely; just drop exponent.
527 mMaxCharOffset = -1;
528 } else {
Hans Boehm24c91ed2016-06-30 18:53:44 -0700529 mMaxCharOffset = Math.min(newMaxCharOffset, MAX_RIGHT_SCROLL);
Hans Boehma0e45f32015-05-30 13:20:35 -0700530 }
Hans Boehm24c91ed2016-06-30 18:53:44 -0700531 mMaxPos = Math.min((int) Math.round(mMaxCharOffset * mCharWidth),
532 MAX_RIGHT_SCROLL);
533 } else if (!mWholePartFits && !mScrollable) {
534 // Corner case in which entire number fits, but not with grouping separators. We
535 // will use an exponent in un-scrolled position, which may hide digits. Scrolling
536 // by one character will remove the exponent and reveal the last digits. Note
537 // that in the forced scientific notation case, the exponent length is not
538 // factored into mMaxCharOffset, since we do not want such an increase to impact
539 // scrolling behavior. In the unscrollable case, we thus have to append the
540 // exponent at the end using the forcePrecision argument to formatResult, in order
541 // to ensure that we get the entire result.
542 mScrollable = (mMaxCharOffset + expLen(-minCharOffset - 1) - minCharOffset
543 + negative >= maxChars);
544 if (mScrollable) {
545 mMaxPos = (int) Math.ceil(mMinPos + mCharWidth);
546 // Single character scroll will remove exponent and show remaining piece.
547 } else {
548 mMaxPos = mMinPos;
549 mAppendExponent = true;
550 }
551 } else {
552 mMaxPos = Math.min((int) Math.round(mMaxCharOffset * mCharWidth),
553 MAX_RIGHT_SCROLL);
Hans Boehma0e45f32015-05-30 13:20:35 -0700554 }
Hans Boehma0e45f32015-05-30 13:20:35 -0700555 if (!mScrollable) {
556 // Position the number consistently with our assumptions to make sure it
557 // actually fits.
558 mCurrentPos = mMaxPos;
559 }
560 } else {
Hans Boehm5e802f32015-06-22 17:18:52 -0700561 mMaxPos = mMaxCharOffset = MAX_RIGHT_SCROLL;
Hans Boehma0e45f32015-05-30 13:20:35 -0700562 mScrollable = true;
563 }
564 }
565
Hans Boehm24c91ed2016-06-30 18:53:44 -0700566 /**
567 * Display error message indicated by resourceId.
568 * UI thread only.
569 */
Hans Boehm8f051c32016-10-03 16:53:58 -0700570 @Override
571 public void onError(long index, int resourceId) {
Annie Chin37c33b62016-11-22 14:46:28 -0800572 mStoreToMemoryRequested = false;
Hans Boehm760a9dc2015-04-20 10:27:12 -0700573 mValid = true;
Christine Franksafe28bb2016-07-29 17:24:52 -0700574 setLongClickable(false);
Hans Boehm84614952014-11-25 18:46:17 -0800575 mScrollable = false;
Hans Boehm14344ff2016-06-08 13:01:51 -0700576 final String msg = getContext().getString(resourceId);
Hans Boehm14344ff2016-06-08 13:01:51 -0700577 final float measuredWidth = Layout.getDesiredWidth(msg, getPaint());
Hans Boehm24c91ed2016-06-30 18:53:44 -0700578 if (measuredWidth > mWidthConstraint) {
Hans Boehm14344ff2016-06-08 13:01:51 -0700579 // Multiply by .99 to avoid rounding effects.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700580 final float scaleFactor = 0.99f * mWidthConstraint / measuredWidth;
Hans Boehm14344ff2016-06-08 13:01:51 -0700581 final RelativeSizeSpan smallTextSpan = new RelativeSizeSpan(scaleFactor);
582 final SpannableString scaledMsg = new SpannableString(msg);
583 scaledMsg.setSpan(smallTextSpan, 0, msg.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
584 setText(scaledMsg);
585 } else {
586 setText(msg);
587 }
Hans Boehm84614952014-11-25 18:46:17 -0800588 }
589
Hans Boehm013969e2015-04-13 20:29:47 -0700590 private final int MAX_COPY_SIZE = 1000000;
591
Hans Boehma0e45f32015-05-30 13:20:35 -0700592 /*
593 * Return the most significant digit position in the given string or Evaluator.INVALID_MSD.
Hans Boehm3666e632015-07-27 18:33:12 -0700594 * Unlike Evaluator.getMsdIndexOf, we treat a final 1 as significant.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700595 * Pure function; callable from anywhere.
Hans Boehma0e45f32015-05-30 13:20:35 -0700596 */
Hans Boehm3666e632015-07-27 18:33:12 -0700597 public static int getNaiveMsdIndexOf(String s) {
Hans Boehm65a99a42016-02-03 18:16:07 -0800598 final int len = s.length();
Hans Boehma0e45f32015-05-30 13:20:35 -0700599 for (int i = 0; i < len; ++i) {
600 char c = s.charAt(i);
601 if (c != '-' && c != '.' && c != '0') {
602 return i;
603 }
604 }
605 return Evaluator.INVALID_MSD;
606 }
607
Hans Boehm24c91ed2016-06-30 18:53:44 -0700608 /**
609 * Format a result returned by Evaluator.getString() into a single line containing ellipses
610 * (if appropriate) and an exponent (if appropriate).
611 * We add two distinct kinds of exponents:
612 * (1) If the final result contains the leading digit we use standard scientific notation.
613 * (2) If not, we add an exponent corresponding to an interpretation of the final result as
614 * an integer.
615 * We add an ellipsis on the left if the result was truncated.
616 * We add ellipses and exponents in a way that leaves most digits in the position they
617 * would have been in had we not done so. This minimizes jumps as a result of scrolling.
618 * Result is NOT internationalized, uses "E" for exponent.
619 * Called only from UI thread; We sometimes omit locking for fields.
620 * @param precOffset The value that was passed to getString. Identifies the significance of
621 the rightmost digit. A value of 1 means the rightmost digits corresponds to tenths.
622 * @param maxDigs The maximum number of characters in the result
623 * @param truncated The in parameter was already truncated, beyond possibly removing the
624 minus sign.
625 * @param negative The in parameter represents a negative result. (Minus sign may be removed
626 without setting truncated.)
627 * @param lastDisplayedOffset If not null, we set lastDisplayedOffset[0] to the offset of
628 the last digit actually appearing in the display.
629 * @param forcePrecision If true, we make sure that the last displayed digit corresponds to
630 precOffset, and allow maxDigs to be exceeded in adding the exponent and commas.
631 * @param forceSciNotation Force scientific notation. May be set because we don't have
632 space for grouping separators, but whole number otherwise fits.
633 * @param insertCommas Insert commas (literally, not internationalized) as digit separators.
634 We only ever do this for the integral part of a number, and only when no
635 exponent is displayed in the initial position. The combination of which means
636 that we only do it when no exponent is displayed.
637 We insert commas in a way that does consider the width of the actual localized digit
638 separator. Commas count towards maxDigs as the appropriate fraction of a digit.
639 */
640 private String formatResult(String in, int precOffset, int maxDigs, boolean truncated,
641 boolean negative, int lastDisplayedOffset[], boolean forcePrecision,
642 boolean forceSciNotation, boolean insertCommas) {
Hans Boehm5e802f32015-06-22 17:18:52 -0700643 final int minusSpace = negative ? 1 : 0;
Hans Boehm3666e632015-07-27 18:33:12 -0700644 final int msdIndex = truncated ? -1 : getNaiveMsdIndexOf(in); // INVALID_MSD is OK.
Hans Boehm5e802f32015-06-22 17:18:52 -0700645 String result = in;
Hans Boehm24c91ed2016-06-30 18:53:44 -0700646 boolean needEllipsis = false;
Hans Boehm73ecff22015-09-03 16:04:50 -0700647 if (truncated || (negative && result.charAt(0) != '-')) {
Hans Boehm24c91ed2016-06-30 18:53:44 -0700648 needEllipsis = true;
Hans Boehm73ecff22015-09-03 16:04:50 -0700649 result = KeyMaps.ELLIPSIS + result.substring(1, result.length());
650 // Ellipsis may be removed again in the type(1) scientific notation case.
651 }
652 final int decIndex = result.indexOf('.');
Hans Boehm65a99a42016-02-03 18:16:07 -0800653 if (lastDisplayedOffset != null) {
654 lastDisplayedOffset[0] = precOffset;
655 }
Hans Boehm24c91ed2016-06-30 18:53:44 -0700656 if (forceSciNotation || (decIndex == -1 || msdIndex != Evaluator.INVALID_MSD
Hans Boehm5e802f32015-06-22 17:18:52 -0700657 && msdIndex - decIndex > MAX_LEADING_ZEROES + 1) && precOffset != -1) {
Hans Boehm24c91ed2016-06-30 18:53:44 -0700658 // Either:
659 // 1) No decimal point displayed, and it's not just to the right of the last digit, or
660 // 2) we are at the front of a number whos integral part is too large to allow
661 // comma insertion, or
662 // 3) we should suppress leading zeroes.
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700663 // Add an exponent to let the user track which digits are currently displayed.
Hans Boehm5e802f32015-06-22 17:18:52 -0700664 // Start with type (2) exponent if we dropped no digits. -1 accounts for decimal point.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700665 // We currently never show digit separators together with an exponent.
Hans Boehm5e802f32015-06-22 17:18:52 -0700666 final int initExponent = precOffset > 0 ? -precOffset : -precOffset - 1;
667 int exponent = initExponent;
Hans Boehm08e8f322015-04-21 13:18:38 -0700668 boolean hasPoint = false;
Hans Boehm5e802f32015-06-22 17:18:52 -0700669 if (!truncated && msdIndex < maxDigs - 1
670 && result.length() - msdIndex + 1 + minusSpace
671 <= maxDigs + SCI_NOTATION_EXTRA) {
672 // Type (1) exponent computation and transformation:
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700673 // Leading digit is in display window. Use standard calculator scientific notation
674 // with one digit to the left of the decimal point. Insert decimal point and
675 // delete leading zeroes.
Hans Boehma0e45f32015-05-30 13:20:35 -0700676 // We try to keep leading digits roughly in position, and never
Hans Boehmf6dae112015-06-18 17:57:50 -0700677 // lengthen the result by more than SCI_NOTATION_EXTRA.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700678 if (decIndex > msdIndex) {
679 // In the forceSciNotation, we can have a decimal point in the relevant digit
680 // range. Remove it.
681 result = result.substring(0, decIndex)
682 + result.substring(decIndex + 1, result.length());
683 // msdIndex and precOffset unaffected.
684 }
Hans Boehm5e802f32015-06-22 17:18:52 -0700685 final int resLen = result.length();
686 String fraction = result.substring(msdIndex + 1, resLen);
687 result = (negative ? "-" : "") + result.substring(msdIndex, msdIndex + 1)
688 + "." + fraction;
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700689 // Original exp was correct for decimal point at right of fraction.
690 // Adjust by length of fraction.
Hans Boehm5e802f32015-06-22 17:18:52 -0700691 exponent = initExponent + resLen - msdIndex - 1;
Hans Boehm08e8f322015-04-21 13:18:38 -0700692 hasPoint = true;
693 }
Hans Boehm73ecff22015-09-03 16:04:50 -0700694 // Exponent can't be zero.
695 // Actually add the exponent of either type:
696 if (!forcePrecision) {
697 int dropDigits; // Digits to drop to make room for exponent.
698 if (hasPoint) {
699 // Type (1) exponent.
700 // Drop digits even if there is room. Otherwise the scrolling gets jumpy.
701 dropDigits = expLen(exponent);
702 if (dropDigits >= result.length() - 1) {
703 // Jumpy is better than no mantissa. Probably impossible anyway.
704 dropDigits = Math.max(result.length() - 2, 0);
Hans Boehma0e45f32015-05-30 13:20:35 -0700705 }
Hans Boehm73ecff22015-09-03 16:04:50 -0700706 } else {
707 // Type (2) exponent.
708 // Exponent depends on the number of digits we drop, which depends on
709 // exponent ...
710 for (dropDigits = 2; expLen(initExponent + dropDigits) > dropDigits;
711 ++dropDigits) {}
712 exponent = initExponent + dropDigits;
713 if (precOffset - dropDigits > mLsdOffset) {
714 // This can happen if e.g. result = 10^40 + 10^10
715 // It turns out we would otherwise display ...10e9 because it takes
716 // the same amount of space as ...1e10 but shows one more digit.
717 // But we don't want to display a trailing zero, even if it's free.
718 ++dropDigits;
719 ++exponent;
720 }
Hans Boehm08e8f322015-04-21 13:18:38 -0700721 }
Hans Boehm73ecff22015-09-03 16:04:50 -0700722 result = result.substring(0, result.length() - dropDigits);
Hans Boehm65a99a42016-02-03 18:16:07 -0800723 if (lastDisplayedOffset != null) {
724 lastDisplayedOffset[0] -= dropDigits;
725 }
Hans Boehm73ecff22015-09-03 16:04:50 -0700726 }
727 result = result + "E" + Integer.toString(exponent);
Hans Boehm24c91ed2016-06-30 18:53:44 -0700728 } else if (insertCommas) {
729 // Add commas to the whole number section, and then truncate on left to fit,
730 // counting commas as a fractional digit.
731 final int wholeStart = needEllipsis ? 1 : 0;
732 int orig_length = result.length();
733 final float nCommaChars;
734 if (decIndex != -1) {
735 nCommaChars = separatorChars(result, decIndex);
736 result = StringUtils.addCommas(result, wholeStart, decIndex)
737 + result.substring(decIndex, orig_length);
738 } else {
739 nCommaChars = separatorChars(result, orig_length);
740 result = StringUtils.addCommas(result, wholeStart, orig_length);
741 }
742 if (needEllipsis) {
743 orig_length -= 1; // Exclude ellipsis.
744 }
745 final float len = orig_length + nCommaChars;
746 int deletedChars = 0;
747 final float ellipsisCredit = getNoEllipsisCredit();
748 final float decimalCredit = getDecimalCredit();
749 final float effectiveLen = len - (decIndex == -1 ? 0 : getDecimalCredit());
750 final float ellipsisAdjustment =
751 needEllipsis ? mNoExponentCredit : getNoEllipsisCredit();
752 // As above, we allow for a tiny amount of extra length here, for consistency with
753 // getPreferredPrec().
754 if (effectiveLen - ellipsisAdjustment > (float) (maxDigs - wholeStart) + 0.0001f
755 && !forcePrecision) {
756 float deletedWidth = 0.0f;
757 while (effectiveLen - mNoExponentCredit - deletedWidth
758 > (float) (maxDigs - 1 /* for ellipsis */)) {
759 if (result.charAt(deletedChars) == ',') {
760 deletedWidth += mGroupingSeparatorWidthRatio;
761 } else {
762 deletedWidth += 1.0f;
763 }
764 deletedChars++;
765 }
766 }
767 if (deletedChars > 0) {
768 result = KeyMaps.ELLIPSIS + result.substring(deletedChars, result.length());
769 } else if (needEllipsis) {
770 result = KeyMaps.ELLIPSIS + result;
771 }
Hans Boehm5e802f32015-06-22 17:18:52 -0700772 }
773 return result;
Hans Boehm08e8f322015-04-21 13:18:38 -0700774 }
775
Hans Boehmf6dae112015-06-18 17:57:50 -0700776 /**
777 * Get formatted, but not internationalized, result from mEvaluator.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700778 * @param precOffset requested position (1 = tenths) of last included digit
779 * @param maxSize maximum number of characters (more or less) in result
780 * @param lastDisplayedOffset zeroth entry is set to actual offset of last included digit,
Hans Boehm65a99a42016-02-03 18:16:07 -0800781 * after adjusting for exponent, etc. May be null.
Hans Boehmf6dae112015-06-18 17:57:50 -0700782 * @param forcePrecision Ensure that last included digit is at pos, at the expense
783 * of treating maxSize as a soft limit.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700784 * @param forceSciNotation Force scientific notation, even if not required by maxSize.
785 * @param insertCommas Insert commas as digit separators.
Hans Boehmf6dae112015-06-18 17:57:50 -0700786 */
Hans Boehm5e802f32015-06-22 17:18:52 -0700787 private String getFormattedResult(int precOffset, int maxSize, int lastDisplayedOffset[],
Hans Boehm24c91ed2016-06-30 18:53:44 -0700788 boolean forcePrecision, boolean forceSciNotation, boolean insertCommas) {
Hans Boehm08e8f322015-04-21 13:18:38 -0700789 final boolean truncated[] = new boolean[1];
790 final boolean negative[] = new boolean[1];
Hans Boehm5e802f32015-06-22 17:18:52 -0700791 final int requestedPrecOffset[] = {precOffset};
Hans Boehm8f051c32016-10-03 16:53:58 -0700792 final String rawResult = mEvaluator.getString(mIndex, requestedPrecOffset, mMaxCharOffset,
793 maxSize, truncated, negative, this);
Hans Boehm5e802f32015-06-22 17:18:52 -0700794 return formatResult(rawResult, requestedPrecOffset[0], maxSize, truncated[0], negative[0],
Hans Boehm24c91ed2016-06-30 18:53:44 -0700795 lastDisplayedOffset, forcePrecision, forceSciNotation, insertCommas);
Hans Boehm08e8f322015-04-21 13:18:38 -0700796 }
797
Hans Boehm65a99a42016-02-03 18:16:07 -0800798 /**
799 * Return entire result (within reason) up to current displayed precision.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700800 * @param withSeparators Add digit separators
Hans Boehm65a99a42016-02-03 18:16:07 -0800801 */
Hans Boehm24c91ed2016-06-30 18:53:44 -0700802 public String getFullText(boolean withSeparators) {
Hans Boehm760a9dc2015-04-20 10:27:12 -0700803 if (!mValid) return "";
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700804 if (!mScrollable) return getText().toString();
Hans Boehm5e802f32015-06-22 17:18:52 -0700805 return KeyMaps.translateResult(getFormattedResult(mLastDisplayedOffset, MAX_COPY_SIZE,
Hans Boehm24c91ed2016-06-30 18:53:44 -0700806 null, true /* forcePrecision */, false /* forceSciNotation */, withSeparators));
Hans Boehm84614952014-11-25 18:46:17 -0800807 }
808
Hans Boehm24c91ed2016-06-30 18:53:44 -0700809 /**
810 * Did the above produce a correct result?
811 * UI thread only.
812 */
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700813 public boolean fullTextIsExact() {
Hans Boehm24c91ed2016-06-30 18:53:44 -0700814 return !mScrollable || (mMaxCharOffset == getCharOffset(mCurrentPos)
815 && mMaxCharOffset != MAX_RIGHT_SCROLL);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700816 }
817
Hans Boehm61568a12015-05-18 18:25:41 -0700818 /**
Hans Boehm65a99a42016-02-03 18:16:07 -0800819 * Get entire result up to current displayed precision, or up to MAX_COPY_EXTRA additional
820 * digits, if it will lead to an exact result.
821 */
822 public String getFullCopyText() {
823 if (!mValid
824 || mLsdOffset == Integer.MAX_VALUE
825 || fullTextIsExact()
826 || mWholeLen > MAX_RECOMPUTE_DIGITS
827 || mWholeLen + mLsdOffset > MAX_RECOMPUTE_DIGITS
828 || mLsdOffset - mLastDisplayedOffset > MAX_COPY_EXTRA) {
Hans Boehm24c91ed2016-06-30 18:53:44 -0700829 return getFullText(false /* withSeparators */);
Hans Boehm65a99a42016-02-03 18:16:07 -0800830 }
831 // It's reasonable to compute and copy the exact result instead.
832 final int nonNegLsdOffset = Math.max(0, mLsdOffset);
Hans Boehm8f051c32016-10-03 16:53:58 -0700833 final String rawResult = mEvaluator.getResult(mIndex).toStringTruncated(nonNegLsdOffset);
Hans Boehm65a99a42016-02-03 18:16:07 -0800834 final String formattedResult = formatResult(rawResult, nonNegLsdOffset, MAX_COPY_SIZE,
Hans Boehm24c91ed2016-06-30 18:53:44 -0700835 false, rawResult.charAt(0) == '-', null, true /* forcePrecision */,
836 false /* forceSciNotation */, false /* insertCommas */);
Hans Boehm65a99a42016-02-03 18:16:07 -0800837 return KeyMaps.translateResult(formattedResult);
838 }
839
840 /**
Hans Boehm61568a12015-05-18 18:25:41 -0700841 * Return the maximum number of characters that will fit in the result display.
Hans Boehm8f051c32016-10-03 16:53:58 -0700842 * May be called asynchronously from non-UI thread. From Evaluator.CharMetricsInfo.
Hans Boehmd4959e82016-11-15 18:01:28 -0800843 * Returns zero if measurement hasn't completed.
Hans Boehm61568a12015-05-18 18:25:41 -0700844 */
Hans Boehm8f051c32016-10-03 16:53:58 -0700845 @Override
846 public int getMaxChars() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700847 int result;
848 synchronized(mWidthLock) {
Hans Boehmd4959e82016-11-15 18:01:28 -0800849 return (int) Math.floor(mWidthConstraint / mCharWidth);
Hans Boehm84614952014-11-25 18:46:17 -0800850 }
851 }
852
Hans Boehm61568a12015-05-18 18:25:41 -0700853 /**
Justin Klaassen44595162015-05-28 17:55:20 -0700854 * @return {@code true} if the currently displayed result is scrollable
Hans Boehm61568a12015-05-18 18:25:41 -0700855 */
Justin Klaassen44595162015-05-28 17:55:20 -0700856 public boolean isScrollable() {
857 return mScrollable;
Hans Boehm61568a12015-05-18 18:25:41 -0700858 }
859
Hans Boehm24c91ed2016-06-30 18:53:44 -0700860 /**
861 * Map pixel position to digit offset.
862 * UI thread only.
863 */
864 int getCharOffset(int pos) {
865 return (int) Math.round(pos / mCharWidth); // Lock not needed.
Hans Boehm013969e2015-04-13 20:29:47 -0700866 }
867
Hans Boehm84614952014-11-25 18:46:17 -0800868 void clear() {
Hans Boehm760a9dc2015-04-20 10:27:12 -0700869 mValid = false;
Hans Boehm1176f232015-05-11 16:26:03 -0700870 mScrollable = false;
Hans Boehm84614952014-11-25 18:46:17 -0800871 setText("");
Christine Franksafe28bb2016-07-29 17:24:52 -0700872 setLongClickable(false);
Hans Boehm84614952014-11-25 18:46:17 -0800873 }
874
Hans Boehm8f051c32016-10-03 16:53:58 -0700875 @Override
876 public void onCancelled(long index) {
877 clear();
Annie Chin37c33b62016-11-22 14:46:28 -0800878 mStoreToMemoryRequested = false;
Hans Boehm8f051c32016-10-03 16:53:58 -0700879 }
880
Hans Boehm24c91ed2016-06-30 18:53:44 -0700881 /**
882 * Refresh display.
Hans Boehm8f051c32016-10-03 16:53:58 -0700883 * Only called in UI thread. Index argument is currently ignored.
Hans Boehm24c91ed2016-06-30 18:53:44 -0700884 */
Hans Boehm8f051c32016-10-03 16:53:58 -0700885 @Override
886 public void onReevaluate(long index) {
887 redisplay();
888 }
889
890 public void redisplay() {
Christine Franks6f6c24a2016-09-08 18:21:47 -0700891 if (mScroller.isFinished() && length() > 0) {
Christine Franksd21205c2016-08-04 10:06:15 -0700892 setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
893 }
Hans Boehm24c91ed2016-06-30 18:53:44 -0700894 int currentCharOffset = getCharOffset(mCurrentPos);
Hans Boehm84614952014-11-25 18:46:17 -0800895 int maxChars = getMaxChars();
Hans Boehm5e802f32015-06-22 17:18:52 -0700896 int lastDisplayedOffset[] = new int[1];
Hans Boehm24c91ed2016-06-30 18:53:44 -0700897 String result = getFormattedResult(currentCharOffset, maxChars, lastDisplayedOffset,
898 mAppendExponent /* forcePrecision; preserve entire result */,
899 !mWholePartFits
900 && currentCharOffset == getCharOffset(mMinPos) /* forceSciNotation */,
901 mWholePartFits /* insertCommas */ );
Hans Boehm0b9806f2015-06-29 16:07:15 -0700902 int expIndex = result.indexOf('E');
Hans Boehm013969e2015-04-13 20:29:47 -0700903 result = KeyMaps.translateResult(result);
Hans Boehm5e802f32015-06-22 17:18:52 -0700904 if (expIndex > 0 && result.indexOf('.') == -1) {
Hans Boehm84614952014-11-25 18:46:17 -0800905 // Gray out exponent if used as position indicator
906 SpannableString formattedResult = new SpannableString(result);
Hans Boehm5e802f32015-06-22 17:18:52 -0700907 formattedResult.setSpan(mExponentColorSpan, expIndex, result.length(),
Hans Boehm84614952014-11-25 18:46:17 -0800908 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
909 setText(formattedResult);
910 } else {
911 setText(result);
912 }
Hans Boehm5e802f32015-06-22 17:18:52 -0700913 mLastDisplayedOffset = lastDisplayedOffset[0];
Hans Boehm760a9dc2015-04-20 10:27:12 -0700914 mValid = true;
Christine Franksafe28bb2016-07-29 17:24:52 -0700915 setLongClickable(true);
Hans Boehm84614952014-11-25 18:46:17 -0800916 }
917
918 @Override
Christine Franks6f6c24a2016-09-08 18:21:47 -0700919 protected void onTextChanged(java.lang.CharSequence text, int start, int lengthBefore,
920 int lengthAfter) {
921 super.onTextChanged(text, start, lengthBefore, lengthAfter);
922
923 if (!mScrollable || mScroller.isFinished()) {
924 if (lengthBefore == 0 && lengthAfter > 0) {
925 setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
926 setContentDescription(null);
927 } else if (lengthBefore > 0 && lengthAfter == 0) {
928 setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_NONE);
929 setContentDescription(getContext().getString(R.string.desc_result));
930 }
931 }
932 }
933
934 @Override
Hans Boehm84614952014-11-25 18:46:17 -0800935 public void computeScroll() {
Christine Franks6f6c24a2016-09-08 18:21:47 -0700936 if (!mScrollable) {
937 return;
938 }
939
Hans Boehm84614952014-11-25 18:46:17 -0800940 if (mScroller.computeScrollOffset()) {
941 mCurrentPos = mScroller.getCurrX();
Hans Boehm24c91ed2016-06-30 18:53:44 -0700942 if (getCharOffset(mCurrentPos) != getCharOffset(mLastPos)) {
Hans Boehm84614952014-11-25 18:46:17 -0800943 mLastPos = mCurrentPos;
944 redisplay();
945 }
Christine Franks6f6c24a2016-09-08 18:21:47 -0700946 }
947
948 if (!mScroller.isFinished()) {
Justin Klaassen44595162015-05-28 17:55:20 -0700949 postInvalidateOnAnimation();
Christine Franksd21205c2016-08-04 10:06:15 -0700950 setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_NONE);
Christine Franks6f6c24a2016-09-08 18:21:47 -0700951 } else if (length() > 0){
Christine Franksd21205c2016-08-04 10:06:15 -0700952 setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
Hans Boehm84614952014-11-25 18:46:17 -0800953 }
954 }
955
Chenjie Yu3937b652016-06-01 23:14:26 -0700956 /**
Christine Franks1d99be12016-11-14 14:00:36 -0800957 * Use ActionMode for copy/memory support on M and higher.
Chenjie Yu3937b652016-06-01 23:14:26 -0700958 */
959 @TargetApi(Build.VERSION_CODES.M)
960 private void setupActionMode() {
961 mCopyActionModeCallback = new ActionMode.Callback2() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700962
Chenjie Yu3937b652016-06-01 23:14:26 -0700963 @Override
964 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
965 final MenuInflater inflater = mode.getMenuInflater();
966 return createCopyMenu(inflater, menu);
967 }
Hans Boehm7f83e362015-06-10 15:41:04 -0700968
Chenjie Yu3937b652016-06-01 23:14:26 -0700969 @Override
970 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
971 return false; // Return false if nothing is done
972 }
Hans Boehm7f83e362015-06-10 15:41:04 -0700973
Chenjie Yu3937b652016-06-01 23:14:26 -0700974 @Override
975 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
976 if (onMenuItemClick(item)) {
Hans Boehm65a99a42016-02-03 18:16:07 -0800977 mode.finish();
978 return true;
Chenjie Yu3937b652016-06-01 23:14:26 -0700979 } else {
980 return false;
Hans Boehm65a99a42016-02-03 18:16:07 -0800981 }
Chenjie Yu3937b652016-06-01 23:14:26 -0700982 }
983
984 @Override
985 public void onDestroyActionMode(ActionMode mode) {
986 unhighlightResult();
987 mActionMode = null;
988 }
989
990 @Override
991 public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
992 super.onGetContentRect(mode, view, outRect);
993
994 outRect.left += view.getPaddingLeft();
995 outRect.top += view.getPaddingTop();
996 outRect.right -= view.getPaddingRight();
997 outRect.bottom -= view.getPaddingBottom();
998 final int width = (int) Layout.getDesiredWidth(getText(), getPaint());
999 if (width < outRect.width()) {
1000 outRect.left = outRect.right - width;
1001 }
1002
1003 if (!BuildCompat.isAtLeastN()) {
1004 // The CAB (prior to N) only takes the translation of a view into account, so
1005 // if a scale is applied to the view then the offset outRect will end up being
1006 // positioned incorrectly. We workaround that limitation by manually applying
1007 // the scale to the outRect, which the CAB will then offset to the correct
1008 // position.
1009 final float scaleX = view.getScaleX();
1010 final float scaleY = view.getScaleY();
1011 outRect.left *= scaleX;
1012 outRect.right *= scaleX;
1013 outRect.top *= scaleY;
1014 outRect.bottom *= scaleY;
1015 }
1016 }
1017 };
1018 setOnLongClickListener(new View.OnLongClickListener() {
1019 @Override
1020 public boolean onLongClick(View v) {
1021 if (mValid) {
1022 mActionMode = startActionMode(mCopyActionModeCallback,
1023 ActionMode.TYPE_FLOATING);
1024 return true;
1025 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001026 return false;
1027 }
Chenjie Yu3937b652016-06-01 23:14:26 -07001028 });
1029 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001030
Chenjie Yu3937b652016-06-01 23:14:26 -07001031 /**
Christine Franks1d99be12016-11-14 14:00:36 -08001032 * Use ContextMenu for copy/memory support on L and lower.
Chenjie Yu3937b652016-06-01 23:14:26 -07001033 */
1034 private void setupContextMenu() {
1035 setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
1036 @Override
1037 public void onCreateContextMenu(ContextMenu contextMenu, View view,
1038 ContextMenu.ContextMenuInfo contextMenuInfo) {
1039 final MenuInflater inflater = new MenuInflater(getContext());
1040 createCopyMenu(inflater, contextMenu);
1041 mContextMenu = contextMenu;
1042 for(int i = 0; i < contextMenu.size(); i ++) {
1043 contextMenu.getItem(i).setOnMenuItemClickListener(CalculatorResult.this);
1044 }
Hans Boehm7f83e362015-06-10 15:41:04 -07001045 }
Chenjie Yu3937b652016-06-01 23:14:26 -07001046 });
1047 setOnLongClickListener(new View.OnLongClickListener() {
1048 @Override
1049 public boolean onLongClick(View v) {
1050 if (mValid) {
1051 return showContextMenu();
1052 }
1053 return false;
Justin Klaassenf1b61f42016-04-27 16:00:11 -07001054 }
Chenjie Yu3937b652016-06-01 23:14:26 -07001055 });
1056 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001057
Chenjie Yu3937b652016-06-01 23:14:26 -07001058 private boolean createCopyMenu(MenuInflater inflater, Menu menu) {
Christine Franks1d99be12016-11-14 14:00:36 -08001059 inflater.inflate(R.menu.menu_result, menu);
1060 final boolean displayMemory = mEvaluator.getMemoryIndex() != 0;
1061 final MenuItem memoryAddItem = menu.findItem(R.id.memory_add);
1062 final MenuItem memorySubtractItem = menu.findItem(R.id.memory_subtract);
1063 memoryAddItem.setEnabled(displayMemory);
1064 memorySubtractItem.setEnabled(displayMemory);
Chenjie Yu3937b652016-06-01 23:14:26 -07001065 highlightResult();
1066 return true;
1067 }
1068
1069 public boolean stopActionModeOrContextMenu() {
Hans Boehm1176f232015-05-11 16:26:03 -07001070 if (mActionMode != null) {
1071 mActionMode.finish();
1072 return true;
1073 }
Chenjie Yu3937b652016-06-01 23:14:26 -07001074 if (mContextMenu != null) {
1075 unhighlightResult();
1076 mContextMenu.close();
1077 return true;
1078 }
Hans Boehm1176f232015-05-11 16:26:03 -07001079 return false;
1080 }
1081
Chenjie Yu3937b652016-06-01 23:14:26 -07001082 private void highlightResult() {
1083 final Spannable text = (Spannable) getText();
1084 text.setSpan(mHighlightSpan, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
1085 }
1086
1087 private void unhighlightResult() {
1088 final Spannable text = (Spannable) getText();
1089 text.removeSpan(mHighlightSpan);
1090 }
1091
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001092 private void setPrimaryClip(ClipData clip) {
1093 ClipboardManager clipboard = (ClipboardManager) getContext().
Hans Boehmc01cd7f2015-05-12 18:32:19 -07001094 getSystemService(Context.CLIPBOARD_SERVICE);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001095 clipboard.setPrimaryClip(clip);
1096 }
1097
1098 private void copyContent() {
Hans Boehm65a99a42016-02-03 18:16:07 -08001099 final CharSequence text = getFullCopyText();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001100 ClipboardManager clipboard =
Hans Boehmc01cd7f2015-05-12 18:32:19 -07001101 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
1102 // We include a tag URI, to allow us to recognize our own results and handle them
1103 // specially.
Hans Boehm8f051c32016-10-03 16:53:58 -07001104 ClipData.Item newItem = new ClipData.Item(text, null, mEvaluator.capture(mIndex));
Hans Boehmc01cd7f2015-05-12 18:32:19 -07001105 String[] mimeTypes = new String[] {ClipDescription.MIMETYPE_TEXT_PLAIN};
1106 ClipData cd = new ClipData("calculator result", mimeTypes, newItem);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001107 clipboard.setPrimaryClip(cd);
Hans Boehmc01cd7f2015-05-12 18:32:19 -07001108 Toast.makeText(getContext(), R.string.text_copied_toast, Toast.LENGTH_SHORT).show();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001109 }
1110
Chenjie Yu3937b652016-06-01 23:14:26 -07001111 @Override
1112 public boolean onMenuItemClick(MenuItem item) {
1113 switch (item.getItemId()) {
Christine Franks1d99be12016-11-14 14:00:36 -08001114 case R.id.memory_add:
1115 onMemoryAdd();
1116 return true;
1117 case R.id.memory_subtract:
1118 onMemorySubtract();
1119 return true;
1120 case R.id.memory_store:
1121 onMemoryStore();
1122 return true;
Chenjie Yu3937b652016-06-01 23:14:26 -07001123 case R.id.menu_copy:
Hans Boehm8f051c32016-10-03 16:53:58 -07001124 if (mEvaluator.evaluationInProgress(mIndex)) {
Chenjie Yu3937b652016-06-01 23:14:26 -07001125 // Refuse to copy placeholder characters.
1126 return false;
1127 } else {
1128 copyContent();
1129 unhighlightResult();
1130 return true;
1131 }
1132 default:
1133 return false;
1134 }
1135 }
Hans Boehmbd01e4b2016-11-23 10:12:58 -08001136}