blob: 5b4fb8604670274c7bef0cfbe131c9550428ac45 [file] [log] [blame]
Hans Boehm84614952014-11-25 18:46:17 -08001/*
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07002 * Copyright (C) 2015 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
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070019import android.content.ClipData;
20import android.content.ClipDescription;
Justin Klaassen44595162015-05-28 17:55:20 -070021import android.content.ClipboardManager;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070022import android.content.Context;
Justin Klaassen44595162015-05-28 17:55:20 -070023import android.text.Layout;
Hans Boehm84614952014-11-25 18:46:17 -080024import android.text.SpannableString;
Hans Boehm1176f232015-05-11 16:26:03 -070025import android.text.Spanned;
Justin Klaassen44595162015-05-28 17:55:20 -070026import android.text.TextPaint;
Hans Boehm84614952014-11-25 18:46:17 -080027import android.text.style.ForegroundColorSpan;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070028import android.util.AttributeSet;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070029import android.view.ActionMode;
30import android.view.GestureDetector;
31import android.view.Menu;
32import android.view.MenuInflater;
33import android.view.MenuItem;
34import android.view.MotionEvent;
35import android.view.View;
Justin Klaassen44595162015-05-28 17:55:20 -070036import android.widget.OverScroller;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070037import android.widget.Toast;
Hans Boehm84614952014-11-25 18:46:17 -080038
Hans Boehm84614952014-11-25 18:46:17 -080039// A text widget that is "infinitely" scrollable to the right,
40// and obtains the text to display via a callback to Logic.
Justin Klaassen44595162015-05-28 17:55:20 -070041public class CalculatorResult extends AlignedTextView {
Hans Boehm61568a12015-05-18 18:25:41 -070042 static final int MAX_RIGHT_SCROLL = 10000000;
Hans Boehm08e8f322015-04-21 13:18:38 -070043 static final int INVALID = MAX_RIGHT_SCROLL + 10000;
Hans Boehm84614952014-11-25 18:46:17 -080044 // A larger value is unlikely to avoid running out of space
45 final OverScroller mScroller;
46 final GestureDetector mGestureDetector;
47 class MyTouchListener implements View.OnTouchListener {
48 @Override
49 public boolean onTouch(View v, MotionEvent event) {
Justin Klaassen44595162015-05-28 17:55:20 -070050 return mGestureDetector.onTouchEvent(event);
Hans Boehm84614952014-11-25 18:46:17 -080051 }
52 }
53 final MyTouchListener mTouchListener = new MyTouchListener();
54 private Evaluator mEvaluator;
55 private boolean mScrollable = false;
56 // A scrollable result is currently displayed.
Hans Boehm760a9dc2015-04-20 10:27:12 -070057 private boolean mValid = false;
Hans Boehmc01cd7f2015-05-12 18:32:19 -070058 // The result holds something valid; either a a number or an error
59 // message.
60 private int mCurrentPos;// Position of right of display relative to decimal point, in pixels.
61 // Large positive values mean the decimal point is scrolled off the
62 // left of the display. Zero means decimal point is barely displayed
63 // on the right.
Hans Boehm61568a12015-05-18 18:25:41 -070064 private int mLastPos; // Position already reflected in display. Pixels.
65 private int mMinPos; // Minimum position before all digits disappear off the right. Pixels.
66 private int mMaxPos; // Maximum position before we start displaying the infinite
67 // sequence of trailing zeroes on the right. Pixels.
Justin Klaassen44595162015-05-28 17:55:20 -070068 private final Object mWidthLock = new Object();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070069 // Protects the next two fields.
70 private int mWidthConstraint = -1;
71 // Our total width in pixels.
Justin Klaassen44595162015-05-28 17:55:20 -070072 private float mCharWidth = 1;
Hans Boehmc01cd7f2015-05-12 18:32:19 -070073 // Maximum character width. For now we pretend that all characters
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070074 // have this width.
Hans Boehmc01cd7f2015-05-12 18:32:19 -070075 // TODO: We're not really using a fixed width font. But it appears
76 // to be close enough for the characters we use that the difference
77 // is not noticeable.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070078 private static final int MAX_WIDTH = 100;
79 // Maximum number of digits displayed
Hans Boehm1176f232015-05-11 16:26:03 -070080 private ActionMode mActionMode;
81 private final ForegroundColorSpan mExponentColorSpan;
Hans Boehm84614952014-11-25 18:46:17 -080082
83 public CalculatorResult(Context context, AttributeSet attrs) {
84 super(context, attrs);
85 mScroller = new OverScroller(context);
86 mGestureDetector = new GestureDetector(context,
87 new GestureDetector.SimpleOnGestureListener() {
88 @Override
Justin Klaassend48b7562015-04-16 16:51:38 -070089 public boolean onDown(MotionEvent e) {
90 return true;
91 }
92 @Override
Hans Boehm84614952014-11-25 18:46:17 -080093 public boolean onFling(MotionEvent e1, MotionEvent e2,
94 float velocityX, float velocityY) {
95 if (!mScroller.isFinished()) {
96 mCurrentPos = mScroller.getFinalX();
97 }
98 mScroller.forceFinished(true);
Hans Boehm1176f232015-05-11 16:26:03 -070099 stopActionMode();
Hans Boehmfbcef702015-04-27 18:07:47 -0700100 CalculatorResult.this.cancelLongPress();
101 // Ignore scrolls of error string, etc.
102 if (!mScrollable) return true;
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700103 mScroller.fling(mCurrentPos, 0, - (int) velocityX, 0 /* horizontal only */,
Hans Boehm61568a12015-05-18 18:25:41 -0700104 mMinPos, mMaxPos, 0, 0);
Justin Klaassen44595162015-05-28 17:55:20 -0700105 postInvalidateOnAnimation();
Hans Boehm84614952014-11-25 18:46:17 -0800106 return true;
107 }
108 @Override
109 public boolean onScroll(MotionEvent e1, MotionEvent e2,
110 float distanceX, float distanceY) {
Hans Boehm61568a12015-05-18 18:25:41 -0700111 int distance = (int)distanceX;
Hans Boehm84614952014-11-25 18:46:17 -0800112 if (!mScroller.isFinished()) {
113 mCurrentPos = mScroller.getFinalX();
114 }
115 mScroller.forceFinished(true);
Hans Boehm1176f232015-05-11 16:26:03 -0700116 stopActionMode();
Hans Boehm84614952014-11-25 18:46:17 -0800117 CalculatorResult.this.cancelLongPress();
118 if (!mScrollable) return true;
Hans Boehm61568a12015-05-18 18:25:41 -0700119 if (mCurrentPos + distance < mMinPos) {
120 distance = mMinPos - mCurrentPos;
121 } else if (mCurrentPos + distance > mMaxPos) {
122 distance = mMaxPos - mCurrentPos;
123 }
Hans Boehm84614952014-11-25 18:46:17 -0800124 int duration = (int)(e2.getEventTime() - e1.getEventTime());
125 if (duration < 1 || duration > 100) duration = 10;
Hans Boehm61568a12015-05-18 18:25:41 -0700126 mScroller.startScroll(mCurrentPos, 0, distance, 0, (int)duration);
Justin Klaassen44595162015-05-28 17:55:20 -0700127 postInvalidateOnAnimation();
Hans Boehm84614952014-11-25 18:46:17 -0800128 return true;
129 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700130 @Override
131 public void onLongPress(MotionEvent e) {
Hans Boehm1176f232015-05-11 16:26:03 -0700132 if (mValid) {
133 mActionMode = startActionMode(mCopyActionModeCallback,
134 ActionMode.TYPE_FLOATING);
135 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700136 }
Hans Boehm84614952014-11-25 18:46:17 -0800137 });
138 setOnTouchListener(mTouchListener);
139 setHorizontallyScrolling(false); // do it ourselves
140 setCursorVisible(false);
Hans Boehm1176f232015-05-11 16:26:03 -0700141 mExponentColorSpan = new ForegroundColorSpan(
142 context.getColor(R.color.display_result_exponent_text_color));
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700143
144 // Copy ActionMode is triggered explicitly, not through
145 // setCustomSelectionActionModeCallback.
Hans Boehm84614952014-11-25 18:46:17 -0800146 }
147
148 void setEvaluator(Evaluator evaluator) {
149 mEvaluator = evaluator;
150 }
151
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700152 @Override
153 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
154 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
155
Justin Klaassen44595162015-05-28 17:55:20 -0700156 final TextPaint paint = getPaint();
157 final int newWidthConstraint = MeasureSpec.getSize(widthMeasureSpec)
158 - (getPaddingLeft() + getPaddingRight())
159 - (int) Math.ceil(Layout.getDesiredWidth(KeyMaps.ELLIPSIS, paint));
160 final float newCharWidth = Layout.getDesiredWidth("\u2007", paint);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700161 synchronized(mWidthLock) {
Hans Boehm013969e2015-04-13 20:29:47 -0700162 mWidthConstraint = newWidthConstraint;
163 mCharWidth = newCharWidth;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700164 }
165 }
166
Hans Boehm61568a12015-05-18 18:25:41 -0700167 // Given that the last non-zero digit is at pos, compute the precision we have to ask
168 // ask for to actually get the digit at pos displayed. This is not an identity
169 // function, since we may need to drop digits to the right to make room for the exponent.
170 private int addExpSpace(int lastDigit) {
171 if (lastDigit < getMaxChars() - 1) {
172 // The decimal point will be in view when displaying the rightmost digit.
173 // no exponent needed.
174 // TODO: This will change if we stop scrolling to the left of the decimal
175 // point, which might be desirable in the traditional scientific notation case.
176 return lastDigit;
177 }
178 // When the last digit is displayed, the exponent will look like "e-<lastDigit>".
179 // The length of that string is the extra precision we need.
180 return lastDigit + (int)Math.ceil(Math.log10((double)lastDigit)) + 2;
181 }
182
183 // Display a new result, given initial displayed precision, position of the rightmost
184 // nonzero digit (or Integer.MAX_VALUE if non-terminating), and the string representing
185 // the whole part of the number to be displayed.
186 // We pass the string, instead of just the length, so we have one less place to fix in case
187 // we ever decide to fully handle a variable width font.
188 void displayResult(int initPrec, int leastDigPos, String truncatedWholePart) {
Hans Boehm84614952014-11-25 18:46:17 -0800189 mLastPos = INVALID;
Hans Boehm013969e2015-04-13 20:29:47 -0700190 synchronized(mWidthLock) {
Justin Klaassen44595162015-05-28 17:55:20 -0700191 mCurrentPos = (int) Math.ceil(initPrec * mCharWidth);
Hans Boehm013969e2015-04-13 20:29:47 -0700192 }
Hans Boehm61568a12015-05-18 18:25:41 -0700193 // Should logically be
194 // mMinPos = - (int) Math.ceil(getPaint().measureText(truncatedWholePart)), but
195 // we eventually transalate to a character position by dividing by mCharWidth.
196 // To avoid rounding issues, we use the analogous computation here.
Justin Klaassen44595162015-05-28 17:55:20 -0700197 mMinPos = - (int) Math.ceil(truncatedWholePart.length() * mCharWidth);
Hans Boehm61568a12015-05-18 18:25:41 -0700198 if (leastDigPos < MAX_RIGHT_SCROLL) {
Justin Klaassen44595162015-05-28 17:55:20 -0700199 mMaxPos = Math.min((int) Math.ceil(addExpSpace(leastDigPos) * mCharWidth),
200 MAX_RIGHT_SCROLL);
Hans Boehm61568a12015-05-18 18:25:41 -0700201 } else {
202 mMaxPos = MAX_RIGHT_SCROLL;
203 }
204 mScrollable = (leastDigPos != (initPrec == -1 ? 0 : initPrec));
205 // We assume that initPrec allows most significant digit to be displayed.
206 // If there is nothing to the right of initPrec, there is no point in scrolling.
Hans Boehm84614952014-11-25 18:46:17 -0800207 redisplay();
208 }
209
Hans Boehm84614952014-11-25 18:46:17 -0800210 void displayError(int resourceId) {
Hans Boehm760a9dc2015-04-20 10:27:12 -0700211 mValid = true;
Hans Boehm84614952014-11-25 18:46:17 -0800212 mScrollable = false;
213 setText(resourceId);
214 }
215
Hans Boehm013969e2015-04-13 20:29:47 -0700216 private final int MAX_COPY_SIZE = 1000000;
217
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700218 // Format a result returned by Evaluator.getString() into a single line containing ellipses
219 // (if appropriate) and an exponent (if appropriate). digs is the value that was passed to
220 // getString and thus identifies the significance of the rightmost digit.
Hans Boehm08e8f322015-04-21 13:18:38 -0700221 // We add two distinct kinds of exponents:
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700222 // 1) If the final result contains the leading digit we use standard scientific notation.
223 // 2) If not, we add an exponent corresponding to an interpretation of the final result as
224 // an integer.
Hans Boehm08e8f322015-04-21 13:18:38 -0700225 // We add an ellipsis on the left if the result was truncated.
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700226 // We add ellipses and exponents in a way that leaves most digits in the position they
227 // would have been in had we not done so.
228 // This minimizes jumps as a result of scrolling. Result is NOT internationalized,
229 // uses "e" for exponent.
Hans Boehm08e8f322015-04-21 13:18:38 -0700230 public String formatResult(String res, int digs,
231 int maxDigs, boolean truncated,
232 boolean negative) {
233 if (truncated) {
234 res = KeyMaps.ELLIPSIS + res.substring(1, res.length());
235 }
236 int decIndex = res.indexOf('.');
237 int resLen = res.length();
238 if (decIndex == -1 && digs != -1) {
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700239 // No decimal point displayed, and it's not just to the right of the last digit.
240 // Add an exponent to let the user track which digits are currently displayed.
241 // This is a bit tricky, since the number of displayed digits affects the displayed
242 // exponent, which can affect the room we have for mantissa digits. We occasionally
243 // display one digit too few. This is sometimes unavoidable, but we could
Hans Boehm08e8f322015-04-21 13:18:38 -0700244 // avoid it in more cases.
245 int exp = digs > 0 ? -digs : -digs - 1;
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700246 // Can be used as TYPE (2) EXPONENT. -1 accounts for decimal point.
247 int msd; // Position of most significant digit in res or indication its outside res.
Hans Boehm08e8f322015-04-21 13:18:38 -0700248 boolean hasPoint = false;
249 if (truncated) {
250 msd = -1;
251 } else {
252 msd = Evaluator.getMsdPos(res); // INVALID_MSD is OK
253 }
254 if (msd < maxDigs - 1 && msd >= 0) {
255 // TYPE (1) EXPONENT computation and transformation:
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700256 // Leading digit is in display window. Use standard calculator scientific notation
257 // with one digit to the left of the decimal point. Insert decimal point and
258 // delete leading zeroes.
259 String fraction = res.substring(msd + 1, resLen);
260 res = (negative ? "-" : "") + res.substring(msd, msd+1) + "." + fraction;
Hans Boehm08e8f322015-04-21 13:18:38 -0700261 exp += resLen - msd - 1;
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700262 // Original exp was correct for decimal point at right of fraction.
263 // Adjust by length of fraction.
Hans Boehm08e8f322015-04-21 13:18:38 -0700264 resLen = res.length();
265 hasPoint = true;
266 }
267 if (exp != 0 || truncated) {
268 // Actually add the exponent of either type:
269 String expAsString = Integer.toString(exp);
270 int expDigits = expAsString.length();
271 int dropDigits = expDigits + 1;
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700272 // Drop digits even if there is room. Otherwise the scrolling gets jumpy.
Hans Boehm08e8f322015-04-21 13:18:38 -0700273 if (dropDigits >= resLen - 1) {
274 dropDigits = Math.max(resLen - 2, 0);
275 // Jumpy is better than no mantissa.
276 }
277 if (!hasPoint) {
278 // Special handling for TYPE(2) EXPONENT:
279 exp += dropDigits;
280 expAsString = Integer.toString(exp);
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700281 // Adjust for digits we are about to drop to drop to make room for exponent.
282 // This can affect the room we have for the mantissa. We adjust only for
283 // positive exponents, when it could otherwise result in a truncated
Hans Boehm08e8f322015-04-21 13:18:38 -0700284 // displayed result.
285 if (exp > 0 && expAsString.length() > expDigits) {
286 // ++expDigits; (dead code)
287 ++dropDigits;
288 ++exp;
289 // This cannot increase the length a second time.
290 }
291 }
292 res = res.substring(0, resLen - dropDigits);
293 res = res + "e" + expAsString;
294 } // else don't add zero exponent
295 }
296 return res;
297 }
298
299 // Get formatted, but not internationalized, result from
300 // mEvaluator.
301 private String getFormattedResult(int pos, int maxSize) {
302 final boolean truncated[] = new boolean[1];
303 final boolean negative[] = new boolean[1];
304 final int requested_prec[] = {pos};
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700305 final String raw_res = mEvaluator.getString(requested_prec, maxSize, truncated, negative);
306 return formatResult(raw_res, requested_prec[0], maxSize, truncated[0], negative[0]);
Hans Boehm08e8f322015-04-21 13:18:38 -0700307 }
308
Hans Boehm84614952014-11-25 18:46:17 -0800309 // Return entire result (within reason) up to current displayed precision.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700310 public String getFullText() {
Hans Boehm760a9dc2015-04-20 10:27:12 -0700311 if (!mValid) return "";
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700312 if (!mScrollable) return getText().toString();
Hans Boehm013969e2015-04-13 20:29:47 -0700313 int currentCharPos = getCurrentCharPos();
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700314 return KeyMaps.translateResult(getFormattedResult(currentCharPos, MAX_COPY_SIZE));
Hans Boehm84614952014-11-25 18:46:17 -0800315 }
316
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700317 public boolean fullTextIsExact() {
318 BoundedRational rat = mEvaluator.getRational();
Hans Boehm013969e2015-04-13 20:29:47 -0700319 int currentCharPos = getCurrentCharPos();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700320 if (currentCharPos == -1) {
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700321 // Suppressing decimal point; still showing all integral digits.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700322 currentCharPos = 0;
323 }
324 // TODO: Could handle scientific notation cases better;
325 // We currently treat those conservatively as approximate.
326 return (currentCharPos >= BoundedRational.digitsRequired(rat));
327 }
328
Hans Boehm61568a12015-05-18 18:25:41 -0700329 /**
330 * Return the maximum number of characters that will fit in the result display.
331 * May be called asynchronously from non-UI thread.
332 */
Hans Boehm84614952014-11-25 18:46:17 -0800333 int getMaxChars() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700334 int result;
335 synchronized(mWidthLock) {
Justin Klaassen44595162015-05-28 17:55:20 -0700336 result = (int) Math.floor(mWidthConstraint / mCharWidth);
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700337 // We can apparently finish evaluating before onMeasure in CalculatorText has been
338 // called, in which case we get 0 or -1 as the width constraint.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700339 }
Hans Boehm84614952014-11-25 18:46:17 -0800340 if (result <= 0) {
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700341 // Return something conservatively big, to force sufficient evaluation.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700342 return MAX_WIDTH;
Hans Boehm84614952014-11-25 18:46:17 -0800343 } else {
Justin Klaassen44595162015-05-28 17:55:20 -0700344 // Always allow for the ellipsis character which already accounted for in the width
345 // constraint.
346 return result + 1;
Hans Boehm84614952014-11-25 18:46:17 -0800347 }
348 }
349
Hans Boehm61568a12015-05-18 18:25:41 -0700350 /**
Justin Klaassen44595162015-05-28 17:55:20 -0700351 * @return {@code true} if the currently displayed result is scrollable
Hans Boehm61568a12015-05-18 18:25:41 -0700352 */
Justin Klaassen44595162015-05-28 17:55:20 -0700353 public boolean isScrollable() {
354 return mScrollable;
Hans Boehm61568a12015-05-18 18:25:41 -0700355 }
356
Hans Boehm013969e2015-04-13 20:29:47 -0700357 int getCurrentCharPos() {
358 synchronized(mWidthLock) {
Justin Klaassen44595162015-05-28 17:55:20 -0700359 return (int) Math.ceil(mCurrentPos / mCharWidth);
Hans Boehm013969e2015-04-13 20:29:47 -0700360 }
361 }
362
Hans Boehm84614952014-11-25 18:46:17 -0800363 void clear() {
Hans Boehm760a9dc2015-04-20 10:27:12 -0700364 mValid = false;
Hans Boehm1176f232015-05-11 16:26:03 -0700365 mScrollable = false;
Hans Boehm84614952014-11-25 18:46:17 -0800366 setText("");
367 }
368
369 void redisplay() {
Hans Boehm013969e2015-04-13 20:29:47 -0700370 int currentCharPos = getCurrentCharPos();
Hans Boehm84614952014-11-25 18:46:17 -0800371 int maxChars = getMaxChars();
Hans Boehm08e8f322015-04-21 13:18:38 -0700372 String result = getFormattedResult(currentCharPos, maxChars);
Hans Boehm84614952014-11-25 18:46:17 -0800373 int epos = result.indexOf('e');
Hans Boehm013969e2015-04-13 20:29:47 -0700374 result = KeyMaps.translateResult(result);
Hans Boehm84614952014-11-25 18:46:17 -0800375 if (epos > 0 && result.indexOf('.') == -1) {
376 // Gray out exponent if used as position indicator
377 SpannableString formattedResult = new SpannableString(result);
Hans Boehm1176f232015-05-11 16:26:03 -0700378 formattedResult.setSpan(mExponentColorSpan, epos, result.length(),
Hans Boehm84614952014-11-25 18:46:17 -0800379 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
380 setText(formattedResult);
381 } else {
382 setText(result);
383 }
Hans Boehm760a9dc2015-04-20 10:27:12 -0700384 mValid = true;
Hans Boehm84614952014-11-25 18:46:17 -0800385 }
386
387 @Override
388 public void computeScroll() {
389 if (!mScrollable) return;
390 if (mScroller.computeScrollOffset()) {
391 mCurrentPos = mScroller.getCurrX();
392 if (mCurrentPos != mLastPos) {
393 mLastPos = mCurrentPos;
394 redisplay();
395 }
396 if (!mScroller.isFinished()) {
Justin Klaassen44595162015-05-28 17:55:20 -0700397 postInvalidateOnAnimation();
Hans Boehm84614952014-11-25 18:46:17 -0800398 }
399 }
400 }
401
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700402 // Copy support:
403
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700404 private ActionMode.Callback mCopyActionModeCallback = new ActionMode.Callback() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700405 @Override
406 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
407 MenuInflater inflater = mode.getMenuInflater();
408 inflater.inflate(R.menu.copy, menu);
409 return true;
410 }
411
412 @Override
413 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
414 return false; // Return false if nothing is done
415 }
416
417 @Override
418 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
419 switch (item.getItemId()) {
420 case R.id.menu_copy:
421 copyContent();
422 mode.finish();
423 return true;
424 default:
425 return false;
426 }
427 }
428
429 @Override
430 public void onDestroyActionMode(ActionMode mode) {
Hans Boehm1176f232015-05-11 16:26:03 -0700431 mActionMode = null;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700432 }
433 };
434
Hans Boehm1176f232015-05-11 16:26:03 -0700435 public boolean stopActionMode() {
436 if (mActionMode != null) {
437 mActionMode.finish();
438 return true;
439 }
440 return false;
441 }
442
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700443 private void setPrimaryClip(ClipData clip) {
444 ClipboardManager clipboard = (ClipboardManager) getContext().
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700445 getSystemService(Context.CLIPBOARD_SERVICE);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700446 clipboard.setPrimaryClip(clip);
447 }
448
449 private void copyContent() {
450 final CharSequence text = getFullText();
451 ClipboardManager clipboard =
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700452 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
453 // We include a tag URI, to allow us to recognize our own results and handle them
454 // specially.
455 ClipData.Item newItem = new ClipData.Item(text, null, mEvaluator.capture());
456 String[] mimeTypes = new String[] {ClipDescription.MIMETYPE_TEXT_PLAIN};
457 ClipData cd = new ClipData("calculator result", mimeTypes, newItem);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700458 clipboard.setPrimaryClip(cd);
Hans Boehmc01cd7f2015-05-12 18:32:19 -0700459 Toast.makeText(getContext(), R.string.text_copied_toast, Toast.LENGTH_SHORT).show();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700460 }
461
Hans Boehm84614952014-11-25 18:46:17 -0800462}