| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.calculator2; |
| |
| import android.content.ClipData; |
| import android.content.ClipDescription; |
| import android.content.ClipboardManager; |
| import android.content.Context; |
| import android.graphics.Rect; |
| import android.text.Layout; |
| import android.text.Spannable; |
| import android.text.SpannableString; |
| import android.text.Spanned; |
| import android.text.TextPaint; |
| import android.text.style.BackgroundColorSpan; |
| import android.text.style.ForegroundColorSpan; |
| import android.util.AttributeSet; |
| import android.view.ActionMode; |
| import android.view.GestureDetector; |
| import android.view.Menu; |
| import android.view.MenuInflater; |
| import android.view.MenuItem; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.widget.OverScroller; |
| import android.widget.Toast; |
| |
| // A text widget that is "infinitely" scrollable to the right, |
| // and obtains the text to display via a callback to Logic. |
| public class CalculatorResult extends AlignedTextView { |
| static final int MAX_RIGHT_SCROLL = 10000000; |
| static final int INVALID = MAX_RIGHT_SCROLL + 10000; |
| // A larger value is unlikely to avoid running out of space |
| final OverScroller mScroller; |
| final GestureDetector mGestureDetector; |
| class MyTouchListener implements View.OnTouchListener { |
| @Override |
| public boolean onTouch(View v, MotionEvent event) { |
| return mGestureDetector.onTouchEvent(event); |
| } |
| } |
| final MyTouchListener mTouchListener = new MyTouchListener(); |
| private Evaluator mEvaluator; |
| private boolean mScrollable = false; |
| // A scrollable result is currently displayed. |
| private boolean mValid = false; |
| // The result holds something valid; either a a number or an error |
| // message. |
| // A suffix of "Pos" denotes a pixel offset. Zero represents a scroll position |
| // in which the decimal point is just barely visible on the right of the display. |
| private int mCurrentPos;// Position of right of display relative to decimal point, in pixels. |
| // Large positive values mean the decimal point is scrolled off the |
| // left of the display. Zero means decimal point is barely displayed |
| // on the right. |
| private int mLastPos; // Position already reflected in display. Pixels. |
| private int mMinPos; // Minimum position before all digits disappear off the right. Pixels. |
| private int mMaxPos; // Maximum position before we start displaying the infinite |
| // sequence of trailing zeroes on the right. Pixels. |
| // In the following, we use a suffix of Offset to denote a character position in a numeric |
| // string relative to the decimal point. Positive is to the right and negative is to |
| // the left. 1 = tenths position, -1 = units. Integer.MAX_VALUE is sometimes used |
| // for the offset of the last digit in an a nonterminating decimal expansion. |
| // We use the suffix "Index" to denote a zero-based index into a string representing a |
| // result. |
| // TODO: Apply the same convention to other classes. |
| private int mMaxCharOffset; // Character offset from decimal point of rightmost digit |
| // that should be displayed. Essentially the same as |
| private int mLsdOffset; // Position of least-significant digit in result |
| private int mLastDisplayedOffset; // Offset of last digit actually displayed after adding |
| // exponent. |
| private final Object mWidthLock = new Object(); |
| // Protects the next two fields. |
| private int mWidthConstraint = -1; |
| // Our total width in pixels minus space for ellipsis. |
| private float mCharWidth = 1; |
| // Maximum character width. For now we pretend that all characters |
| // have this width. |
| // TODO: We're not really using a fixed width font. But it appears |
| // to be close enough for the characters we use that the difference |
| // is not noticeable. |
| private static final int MAX_WIDTH = 100; |
| // Maximum number of digits displayed |
| public static final int MAX_LEADING_ZEROES = 6; |
| // Maximum number of leading zeroes after decimal point before we |
| // switch to scientific notation with negative exponent. |
| public static final int MAX_TRAILING_ZEROES = 6; |
| // Maximum number of trailing zeroes before the decimal point before |
| // we switch to scientific notation with positive exponent. |
| private static final int SCI_NOTATION_EXTRA = 1; |
| // Extra digits for standard scientific notation. In this case we |
| // have a deecimal point and no ellipsis. |
| private ActionMode mActionMode; |
| private final ForegroundColorSpan mExponentColorSpan; |
| |
| public CalculatorResult(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| mScroller = new OverScroller(context); |
| mGestureDetector = new GestureDetector(context, |
| new GestureDetector.SimpleOnGestureListener() { |
| @Override |
| public boolean onDown(MotionEvent e) { |
| return true; |
| } |
| @Override |
| public boolean onFling(MotionEvent e1, MotionEvent e2, |
| float velocityX, float velocityY) { |
| if (!mScroller.isFinished()) { |
| mCurrentPos = mScroller.getFinalX(); |
| } |
| mScroller.forceFinished(true); |
| stopActionMode(); |
| CalculatorResult.this.cancelLongPress(); |
| // Ignore scrolls of error string, etc. |
| if (!mScrollable) return true; |
| mScroller.fling(mCurrentPos, 0, - (int) velocityX, 0 /* horizontal only */, |
| mMinPos, mMaxPos, 0, 0); |
| postInvalidateOnAnimation(); |
| return true; |
| } |
| @Override |
| public boolean onScroll(MotionEvent e1, MotionEvent e2, |
| float distanceX, float distanceY) { |
| int distance = (int)distanceX; |
| if (!mScroller.isFinished()) { |
| mCurrentPos = mScroller.getFinalX(); |
| } |
| mScroller.forceFinished(true); |
| stopActionMode(); |
| CalculatorResult.this.cancelLongPress(); |
| if (!mScrollable) return true; |
| if (mCurrentPos + distance < mMinPos) { |
| distance = mMinPos - mCurrentPos; |
| } else if (mCurrentPos + distance > mMaxPos) { |
| distance = mMaxPos - mCurrentPos; |
| } |
| int duration = (int)(e2.getEventTime() - e1.getEventTime()); |
| if (duration < 1 || duration > 100) duration = 10; |
| mScroller.startScroll(mCurrentPos, 0, distance, 0, (int)duration); |
| postInvalidateOnAnimation(); |
| return true; |
| } |
| @Override |
| public void onLongPress(MotionEvent e) { |
| if (mValid) { |
| mActionMode = startActionMode(mCopyActionModeCallback, |
| ActionMode.TYPE_FLOATING); |
| } |
| } |
| }); |
| setOnTouchListener(mTouchListener); |
| setHorizontallyScrolling(false); // do it ourselves |
| setCursorVisible(false); |
| mExponentColorSpan = new ForegroundColorSpan( |
| context.getColor(R.color.display_result_exponent_text_color)); |
| |
| // Copy ActionMode is triggered explicitly, not through |
| // setCustomSelectionActionModeCallback. |
| } |
| |
| void setEvaluator(Evaluator evaluator) { |
| mEvaluator = evaluator; |
| } |
| |
| @Override |
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
| |
| final TextPaint paint = getPaint(); |
| final int newWidthConstraint = MeasureSpec.getSize(widthMeasureSpec) |
| - (getPaddingLeft() + getPaddingRight()) |
| - (int) Math.ceil(Layout.getDesiredWidth(KeyMaps.ELLIPSIS, paint)); |
| final float newCharWidth = Layout.getDesiredWidth("\u2007", paint); |
| synchronized(mWidthLock) { |
| mWidthConstraint = newWidthConstraint; |
| mCharWidth = newCharWidth; |
| } |
| } |
| |
| // Return the length of the exponent representation for the given exponent, in |
| // characters. |
| private final int expLen(int exp) { |
| if (exp == 0) return 0; |
| final int abs_exp_digits = (int) Math.ceil(Math.log10(Math.abs((double)exp)) |
| + 0.0000000001d /* Round whole numbers to next integer */); |
| return abs_exp_digits + (exp >= 0 ? 1 : 2); |
| } |
| |
| /** |
| * Initiate display of a new result. |
| * The parameters specify various properties of the result. |
| * @param initPrec Initial display precision computed by evaluator. (1 = tenths digit) |
| * @param msd Position of most significant digit. Offset from left of string. |
| Evaluator.INVALID_MSD if unknown. |
| * @param leastDigPos Position of least significant digit (1 = tenths digit) |
| * or Integer.MAX_VALUE. |
| * @param truncatedWholePart Result up to but not including decimal point. |
| Currently we only use the length. |
| */ |
| void displayResult(int initPrec, int msd, int leastDigPos, String truncatedWholePart) { |
| initPositions(initPrec, msd, leastDigPos, truncatedWholePart); |
| redisplay(); |
| } |
| |
| /** |
| * Set up scroll bounds (mMinPos, mMaxPos, etc.) and determine whether the result is |
| * scrollable, based on the supplied information about the result. |
| * This is unfortunately complicated because we need to predict whether trailing digits |
| * will eventually be replaced by an exponent. |
| * Just appending the exponent during formatting would be simpler, but would produce |
| * jumpier results during transitions. |
| */ |
| private void initPositions(int initPrecOffset, int msdIndex, int lsdOffset, |
| String truncatedWholePart) { |
| float charWidth; |
| int maxChars = getMaxChars(); |
| mLastPos = INVALID; |
| mLsdOffset = lsdOffset; |
| synchronized(mWidthLock) { |
| charWidth = mCharWidth; |
| } |
| mCurrentPos = mMinPos = (int) Math.round(initPrecOffset * charWidth); |
| // Prevent scrolling past initial position, which is calculated to show leading digits. |
| if (msdIndex == Evaluator.INVALID_MSD) { |
| // Possible zero value |
| if (lsdOffset == Integer.MIN_VALUE) { |
| // Definite zero value. |
| mMaxPos = mMinPos; |
| mMaxCharOffset = (int) Math.round(mMaxPos/charWidth); |
| mScrollable = false; |
| } else { |
| // May be very small nonzero value. Allow user to find out. |
| mMaxPos = mMaxCharOffset = MAX_RIGHT_SCROLL; |
| mMinPos -= charWidth; // Allow for future minus sign. |
| mScrollable = true; |
| } |
| return; |
| } |
| int wholeLen = truncatedWholePart.length(); |
| int negative = truncatedWholePart.charAt(0) == '-' ? 1 : 0; |
| if (msdIndex > wholeLen && msdIndex <= wholeLen + 3) { |
| // Avoid tiny negative exponent; pretend msdIndex is just to the right of decimal point. |
| msdIndex = wholeLen - 1; |
| } |
| int minCharOffset = msdIndex - wholeLen; |
| // Position of leftmost significant digit relative to dec. point. |
| // Usually negative. |
| mMaxCharOffset = MAX_RIGHT_SCROLL; // How far does it make sense to scroll right? |
| // If msd is left of decimal point should logically be |
| // mMinPos = - (int) Math.ceil(getPaint().measureText(truncatedWholePart)), but |
| // we eventually translate to a character position by dividing by mCharWidth. |
| // To avoid rounding issues, we use the analogous computation here. |
| if (minCharOffset > -1 && minCharOffset < MAX_LEADING_ZEROES + 2) { |
| // Small number of leading zeroes, avoid scientific notation. |
| minCharOffset = -1; |
| } |
| if (lsdOffset < MAX_RIGHT_SCROLL) { |
| mMaxCharOffset = lsdOffset; |
| if (mMaxCharOffset < -1 && mMaxCharOffset > -(MAX_TRAILING_ZEROES + 2)) { |
| mMaxCharOffset = -1; |
| } |
| // lsdOffset is positive or negative, never 0. |
| int currentExpLen = 0; // Length of required standard scientific notation exponent. |
| if (mMaxCharOffset < -1) { |
| currentExpLen = expLen(-minCharOffset - 1); |
| } else if (minCharOffset > -1 || mMaxCharOffset >= maxChars) { |
| // Number either entirely to the right of decimal point, or decimal point not |
| // visible when scrolled to the right. |
| currentExpLen = expLen(-minCharOffset); |
| } |
| mScrollable = (mMaxCharOffset + currentExpLen - minCharOffset + negative >= maxChars); |
| int newMaxCharOffset; |
| if (currentExpLen > 0) { |
| if (mScrollable) { |
| // We'll use exponent corresponding to leastDigPos when scrolled to right. |
| newMaxCharOffset = mMaxCharOffset + expLen(-lsdOffset); |
| } else { |
| newMaxCharOffset = mMaxCharOffset + currentExpLen; |
| } |
| if (mMaxCharOffset <= -1 && newMaxCharOffset > -1) { |
| // Very unlikely; just drop exponent. |
| mMaxCharOffset = -1; |
| } else { |
| mMaxCharOffset = newMaxCharOffset; |
| } |
| } |
| mMaxPos = Math.min((int) Math.round(mMaxCharOffset * charWidth), MAX_RIGHT_SCROLL); |
| if (!mScrollable) { |
| // Position the number consistently with our assumptions to make sure it |
| // actually fits. |
| mCurrentPos = mMaxPos; |
| } |
| } else { |
| mMaxPos = mMaxCharOffset = MAX_RIGHT_SCROLL; |
| mScrollable = true; |
| } |
| } |
| |
| void displayError(int resourceId) { |
| mValid = true; |
| mScrollable = false; |
| setText(resourceId); |
| } |
| |
| private final int MAX_COPY_SIZE = 1000000; |
| |
| /* |
| * Return the most significant digit position in the given string or Evaluator.INVALID_MSD. |
| * Unlike Evaluator.getMsdPos, we treat a final 1 as significant. |
| */ |
| public static int getNaiveMsdIndex(String s) { |
| int len = s.length(); |
| for (int i = 0; i < len; ++i) { |
| char c = s.charAt(i); |
| if (c != '-' && c != '.' && c != '0') { |
| return i; |
| } |
| } |
| return Evaluator.INVALID_MSD; |
| } |
| |
| // Format a result returned by Evaluator.getString() into a single line containing ellipses |
| // (if appropriate) and an exponent (if appropriate). prec is the value that was passed to |
| // getString and thus identifies the significance of the rightmost digit. |
| // A value of 1 means the rightmost digits corresponds to tenths. |
| // maxDigs is the maximum number of characters in the result. |
| // We set lastDisplayedOffset[0] to the offset of the last digit actually appearing in |
| // the display. |
| // If forcePrecision is true, we make sure that the last displayed digit corresponds to |
| // prec, and allow maxDigs to be exceeded in assing the exponent. |
| // We add two distinct kinds of exponents: |
| // (1) If the final result contains the leading digit we use standard scientific notation. |
| // (2) If not, we add an exponent corresponding to an interpretation of the final result as |
| // an integer. |
| // We add an ellipsis on the left if the result was truncated. |
| // We add ellipses and exponents in a way that leaves most digits in the position they |
| // would have been in had we not done so. |
| // This minimizes jumps as a result of scrolling. Result is NOT internationalized, |
| // uses "E" for exponent. |
| public String formatResult(String in, int precOffset, int maxDigs, boolean truncated, |
| boolean negative, int lastDisplayedOffset[], boolean forcePrecision) { |
| final int minusSpace = negative ? 1 : 0; |
| final int msdIndex = truncated ? -1 : getNaiveMsdIndex(in); // INVALID_MSD is OK. |
| final int decIndex = in.indexOf('.'); |
| String result = in; |
| lastDisplayedOffset[0] = precOffset; |
| if ((decIndex == -1 || msdIndex != Evaluator.INVALID_MSD |
| && msdIndex - decIndex > MAX_LEADING_ZEROES + 1) && precOffset != -1) { |
| // No decimal point displayed, and it's not just to the right of the last digit, |
| // or we should suppress leading zeroes. |
| // Add an exponent to let the user track which digits are currently displayed. |
| // Start with type (2) exponent if we dropped no digits. -1 accounts for decimal point. |
| final int initExponent = precOffset > 0 ? -precOffset : -precOffset - 1; |
| int exponent = initExponent; |
| boolean hasPoint = false; |
| if (!truncated && msdIndex < maxDigs - 1 |
| && result.length() - msdIndex + 1 + minusSpace |
| <= maxDigs + SCI_NOTATION_EXTRA) { |
| // Type (1) exponent computation and transformation: |
| // Leading digit is in display window. Use standard calculator scientific notation |
| // with one digit to the left of the decimal point. Insert decimal point and |
| // delete leading zeroes. |
| // We try to keep leading digits roughly in position, and never |
| // lengthen the result by more than SCI_NOTATION_EXTRA. |
| final int resLen = result.length(); |
| String fraction = result.substring(msdIndex + 1, resLen); |
| result = (negative ? "-" : "") + result.substring(msdIndex, msdIndex + 1) |
| + "." + fraction; |
| // Original exp was correct for decimal point at right of fraction. |
| // Adjust by length of fraction. |
| exponent = initExponent + resLen - msdIndex - 1; |
| hasPoint = true; |
| } |
| if (exponent != 0 || truncated) { |
| // Actually add the exponent of either type: |
| if (!forcePrecision) { |
| int dropDigits; // Digits to drop to make room for exponent. |
| if (hasPoint) { |
| // Type (1) exponent. |
| // Drop digits even if there is room. Otherwise the scrolling gets jumpy. |
| dropDigits = expLen(exponent); |
| if (dropDigits >= result.length() - 1) { |
| // Jumpy is better than no mantissa. Probably impossible anyway. |
| dropDigits = Math.max(result.length() - 2, 0); |
| } |
| } else { |
| // Type (2) exponent. |
| // Exponent depends on the number of digits we drop, which depends on |
| // exponent ... |
| for (dropDigits = 2; expLen(initExponent + dropDigits) > dropDigits; |
| ++dropDigits) {} |
| exponent = initExponent + dropDigits; |
| if (precOffset - dropDigits > mLsdOffset) { |
| // This can happen if e.g. result = 10^40 + 10^10 |
| // It turns out we would otherwise display ...10e9 because it takes |
| // the same amount of space as ...1e10 but shows one more digit. |
| // But we don't want to display a trailing zero, even if it's free. |
| ++dropDigits; |
| ++exponent; |
| } |
| } |
| result = result.substring(0, result.length() - dropDigits); |
| lastDisplayedOffset[0] -= dropDigits; |
| } |
| result = result + "E" + Integer.toString(exponent); |
| } // else don't add zero exponent |
| } |
| if (truncated || negative && result.charAt(0) != '-') { |
| result = KeyMaps.ELLIPSIS + result.substring(1, result.length()); |
| } |
| return result; |
| } |
| |
| /** |
| * Get formatted, but not internationalized, result from mEvaluator. |
| * @param precOffset requested position (1 = tenths) of last included digit. |
| * @param maxSize Maximum number of characters (more or less) in result. |
| * @param lastDisplayedOffset Zeroth entry is set to actual offset of last included digit, |
| * after adjusting for exponent, etc. |
| * @param forcePrecision Ensure that last included digit is at pos, at the expense |
| * of treating maxSize as a soft limit. |
| */ |
| private String getFormattedResult(int precOffset, int maxSize, int lastDisplayedOffset[], |
| boolean forcePrecision) { |
| final boolean truncated[] = new boolean[1]; |
| final boolean negative[] = new boolean[1]; |
| final int requestedPrecOffset[] = {precOffset}; |
| final String rawResult = mEvaluator.getString(requestedPrecOffset, mMaxCharOffset, |
| maxSize, truncated, negative); |
| return formatResult(rawResult, requestedPrecOffset[0], maxSize, truncated[0], negative[0], |
| lastDisplayedOffset, forcePrecision); |
| } |
| |
| // Return entire result (within reason) up to current displayed precision. |
| public String getFullText() { |
| if (!mValid) return ""; |
| if (!mScrollable) return getText().toString(); |
| int currentCharOffset = getCurrentCharOffset(); |
| int unused[] = new int[1]; |
| return KeyMaps.translateResult(getFormattedResult(mLastDisplayedOffset, MAX_COPY_SIZE, |
| unused, true)); |
| } |
| |
| public boolean fullTextIsExact() { |
| return !mScrollable |
| || mMaxCharOffset == getCurrentCharOffset() && mMaxCharOffset != MAX_RIGHT_SCROLL; |
| } |
| |
| /** |
| * Return the maximum number of characters that will fit in the result display. |
| * May be called asynchronously from non-UI thread. |
| */ |
| int getMaxChars() { |
| int result; |
| synchronized(mWidthLock) { |
| result = (int) Math.floor(mWidthConstraint / mCharWidth); |
| // We can apparently finish evaluating before onMeasure in CalculatorText has been |
| // called, in which case we get 0 or -1 as the width constraint. |
| } |
| if (result <= 0) { |
| // Return something conservatively big, to force sufficient evaluation. |
| return MAX_WIDTH; |
| } else { |
| // Always allow for the ellipsis character which already accounted for in the width |
| // constraint. |
| return result + 1; |
| } |
| } |
| |
| /** |
| * @return {@code true} if the currently displayed result is scrollable |
| */ |
| public boolean isScrollable() { |
| return mScrollable; |
| } |
| |
| int getCurrentCharOffset() { |
| synchronized(mWidthLock) { |
| return (int) Math.round(mCurrentPos / mCharWidth); |
| } |
| } |
| |
| void clear() { |
| mValid = false; |
| mScrollable = false; |
| setText(""); |
| } |
| |
| void redisplay() { |
| int currentCharOffset = getCurrentCharOffset(); |
| int maxChars = getMaxChars(); |
| int lastDisplayedOffset[] = new int[1]; |
| String result = getFormattedResult(currentCharOffset, maxChars, lastDisplayedOffset, false); |
| int expIndex = result.indexOf('E'); |
| result = KeyMaps.translateResult(result); |
| if (expIndex > 0 && result.indexOf('.') == -1) { |
| // Gray out exponent if used as position indicator |
| SpannableString formattedResult = new SpannableString(result); |
| formattedResult.setSpan(mExponentColorSpan, expIndex, result.length(), |
| Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); |
| setText(formattedResult); |
| } else { |
| setText(result); |
| } |
| mLastDisplayedOffset = lastDisplayedOffset[0]; |
| mValid = true; |
| } |
| |
| @Override |
| public void computeScroll() { |
| if (!mScrollable) return; |
| if (mScroller.computeScrollOffset()) { |
| mCurrentPos = mScroller.getCurrX(); |
| if (mCurrentPos != mLastPos) { |
| mLastPos = mCurrentPos; |
| redisplay(); |
| } |
| if (!mScroller.isFinished()) { |
| postInvalidateOnAnimation(); |
| } |
| } |
| } |
| |
| // Copy support: |
| |
| private ActionMode.Callback2 mCopyActionModeCallback = new ActionMode.Callback2() { |
| |
| private BackgroundColorSpan mHighlightSpan; |
| |
| private void highlightResult() { |
| final Spannable text = (Spannable) getText(); |
| mHighlightSpan = new BackgroundColorSpan(getHighlightColor()); |
| text.setSpan(mHighlightSpan, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); |
| } |
| |
| private void unhighlightResult() { |
| final Spannable text = (Spannable) getText(); |
| text.removeSpan(mHighlightSpan); |
| } |
| |
| @Override |
| public boolean onCreateActionMode(ActionMode mode, Menu menu) { |
| MenuInflater inflater = mode.getMenuInflater(); |
| inflater.inflate(R.menu.copy, menu); |
| highlightResult(); |
| return true; |
| } |
| |
| @Override |
| public boolean onPrepareActionMode(ActionMode mode, Menu menu) { |
| return false; // Return false if nothing is done |
| } |
| |
| @Override |
| public boolean onActionItemClicked(ActionMode mode, MenuItem item) { |
| switch (item.getItemId()) { |
| case R.id.menu_copy: |
| copyContent(); |
| mode.finish(); |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| @Override |
| public void onDestroyActionMode(ActionMode mode) { |
| unhighlightResult(); |
| mActionMode = null; |
| } |
| |
| @Override |
| public void onGetContentRect(ActionMode mode, View view, Rect outRect) { |
| super.onGetContentRect(mode, view, outRect); |
| outRect.left += getPaddingLeft(); |
| outRect.top += getPaddingTop(); |
| outRect.right -= getPaddingRight(); |
| outRect.bottom -= getPaddingBottom(); |
| final int width = (int) Layout.getDesiredWidth(getText(), getPaint()); |
| if (width < outRect.width()) { |
| outRect.left = outRect.right - width; |
| } |
| } |
| }; |
| |
| public boolean stopActionMode() { |
| if (mActionMode != null) { |
| mActionMode.finish(); |
| return true; |
| } |
| return false; |
| } |
| |
| private void setPrimaryClip(ClipData clip) { |
| ClipboardManager clipboard = (ClipboardManager) getContext(). |
| getSystemService(Context.CLIPBOARD_SERVICE); |
| clipboard.setPrimaryClip(clip); |
| } |
| |
| private void copyContent() { |
| final CharSequence text = getFullText(); |
| ClipboardManager clipboard = |
| (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); |
| // We include a tag URI, to allow us to recognize our own results and handle them |
| // specially. |
| ClipData.Item newItem = new ClipData.Item(text, null, mEvaluator.capture()); |
| String[] mimeTypes = new String[] {ClipDescription.MIMETYPE_TEXT_PLAIN}; |
| ClipData cd = new ClipData("calculator result", mimeTypes, newItem); |
| clipboard.setPrimaryClip(cd); |
| Toast.makeText(getContext(), R.string.text_copied_toast, Toast.LENGTH_SHORT).show(); |
| } |
| |
| } |