184614957604253d51296e06c97daced699a0a9deHans Boehm/*
24a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm * Copyright (C) 2015 The Android Open Source Project
384614957604253d51296e06c97daced699a0a9deHans Boehm *
484614957604253d51296e06c97daced699a0a9deHans Boehm * Licensed under the Apache License, Version 2.0 (the "License");
584614957604253d51296e06c97daced699a0a9deHans Boehm * you may not use this file except in compliance with the License.
684614957604253d51296e06c97daced699a0a9deHans Boehm * You may obtain a copy of the License at
784614957604253d51296e06c97daced699a0a9deHans Boehm *
884614957604253d51296e06c97daced699a0a9deHans Boehm *      http://www.apache.org/licenses/LICENSE-2.0
984614957604253d51296e06c97daced699a0a9deHans Boehm *
1084614957604253d51296e06c97daced699a0a9deHans Boehm * Unless required by applicable law or agreed to in writing, software
1184614957604253d51296e06c97daced699a0a9deHans Boehm * distributed under the License is distributed on an "AS IS" BASIS,
1284614957604253d51296e06c97daced699a0a9deHans Boehm * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1384614957604253d51296e06c97daced699a0a9deHans Boehm * See the License for the specific language governing permissions and
1484614957604253d51296e06c97daced699a0a9deHans Boehm * limitations under the License.
1584614957604253d51296e06c97daced699a0a9deHans Boehm */
1684614957604253d51296e06c97daced699a0a9deHans Boehm
1784614957604253d51296e06c97daced699a0a9deHans Boehmpackage com.android.calculator2;
1884614957604253d51296e06c97daced699a0a9deHans Boehm
194a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.content.ClipData;
204a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.content.ClipDescription;
214459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassenimport android.content.ClipboardManager;
224a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.content.Context;
237f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehmimport android.graphics.Rect;
244459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassenimport android.text.Layout;
257f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehmimport android.text.Spannable;
2684614957604253d51296e06c97daced699a0a9deHans Boehmimport android.text.SpannableString;
271176f23dae4d3740782e46463003e9f36a381c9dHans Boehmimport android.text.Spanned;
284459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassenimport android.text.TextPaint;
297f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehmimport android.text.style.BackgroundColorSpan;
3084614957604253d51296e06c97daced699a0a9deHans Boehmimport android.text.style.ForegroundColorSpan;
314a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.util.AttributeSet;
324a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.view.ActionMode;
334a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.view.GestureDetector;
344a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.view.Menu;
354a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.view.MenuInflater;
364a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.view.MenuItem;
374a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.view.MotionEvent;
384a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.view.View;
394459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassenimport android.widget.OverScroller;
404a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.widget.Toast;
4184614957604253d51296e06c97daced699a0a9deHans Boehm
4284614957604253d51296e06c97daced699a0a9deHans Boehm// A text widget that is "infinitely" scrollable to the right,
4384614957604253d51296e06c97daced699a0a9deHans Boehm// and obtains the text to display via a callback to Logic.
444459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassenpublic class CalculatorResult extends AlignedTextView {
4561568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm    static final int MAX_RIGHT_SCROLL = 10000000;
4608e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm    static final int INVALID = MAX_RIGHT_SCROLL + 10000;
4784614957604253d51296e06c97daced699a0a9deHans Boehm        // A larger value is unlikely to avoid running out of space
4884614957604253d51296e06c97daced699a0a9deHans Boehm    final OverScroller mScroller;
4984614957604253d51296e06c97daced699a0a9deHans Boehm    final GestureDetector mGestureDetector;
5084614957604253d51296e06c97daced699a0a9deHans Boehm    class MyTouchListener implements View.OnTouchListener {
5184614957604253d51296e06c97daced699a0a9deHans Boehm        @Override
5284614957604253d51296e06c97daced699a0a9deHans Boehm        public boolean onTouch(View v, MotionEvent event) {
534459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen            return mGestureDetector.onTouchEvent(event);
5484614957604253d51296e06c97daced699a0a9deHans Boehm        }
5584614957604253d51296e06c97daced699a0a9deHans Boehm    }
5684614957604253d51296e06c97daced699a0a9deHans Boehm    final MyTouchListener mTouchListener = new MyTouchListener();
5784614957604253d51296e06c97daced699a0a9deHans Boehm    private Evaluator mEvaluator;
5884614957604253d51296e06c97daced699a0a9deHans Boehm    private boolean mScrollable = false;
5984614957604253d51296e06c97daced699a0a9deHans Boehm                            // A scrollable result is currently displayed.
60760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm    private boolean mValid = false;
61c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm                            // The result holds something valid; either a a number or an error
62c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm                            // message.
635e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    // A suffix of "Pos" denotes a pixel offset.  Zero represents a scroll position
645e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    // in which the decimal point is just barely visible on the right of the display.
65c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm    private int mCurrentPos;// Position of right of display relative to decimal point, in pixels.
66c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm                            // Large positive values mean the decimal point is scrolled off the
67c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm                            // left of the display.  Zero means decimal point is barely displayed
68c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm                            // on the right.
6961568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm    private int mLastPos;   // Position already reflected in display. Pixels.
7061568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm    private int mMinPos;    // Minimum position before all digits disappear off the right. Pixels.
7161568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm    private int mMaxPos;    // Maximum position before we start displaying the infinite
7261568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm                            // sequence of trailing zeroes on the right. Pixels.
735e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    // In the following, we use a suffix of Offset to denote a character position in a numeric
745e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    // string relative to the decimal point.  Positive is to the right and negative is to
755e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    // the left. 1 = tenths position, -1 = units.  Integer.MAX_VALUE is sometimes used
765e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    // for the offset of the last digit in an a nonterminating decimal expansion.
775e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    // We use the suffix "Index" to denote a zero-based index into a string representing a
785e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    // result.
795e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    // TODO: Apply the same convention to other classes.
805e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    private int mMaxCharOffset;  // Character offset from decimal point of rightmost digit
815e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                                 // that should be displayed.  Essentially the same as
825e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    private int mLsdOffset;      // Position of least-significant digit in result
835e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    private int mLastDisplayedOffset; // Offset of last digit actually displayed after adding
84f6dae114d7e7c5a2d2d5a6b0f2c8d1fc6cf2937fHans Boehm                                      // exponent.
854459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen    private final Object mWidthLock = new Object();
864a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                            // Protects the next two fields.
874a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    private int mWidthConstraint = -1;
88a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                            // Our total width in pixels minus space for ellipsis.
894459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen    private float mCharWidth = 1;
90c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm                            // Maximum character width. For now we pretend that all characters
914a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                            // have this width.
92c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm                            // TODO: We're not really using a fixed width font.  But it appears
93c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm                            // to be close enough for the characters we use that the difference
94c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm                            // is not noticeable.
954a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    private static final int MAX_WIDTH = 100;
964a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                            // Maximum number of digits displayed
9750ed320c1cdd1b3624f73956a80eb2f2c2f5a01dHans Boehm    public static final int MAX_LEADING_ZEROES = 6;
98a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                            // Maximum number of leading zeroes after decimal point before we
99a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                            // switch to scientific notation with negative exponent.
10050ed320c1cdd1b3624f73956a80eb2f2c2f5a01dHans Boehm    public static final int MAX_TRAILING_ZEROES = 6;
101a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                            // Maximum number of trailing zeroes before the decimal point before
102a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                            // we switch to scientific notation with positive exponent.
103a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm    private static final int SCI_NOTATION_EXTRA = 1;
104a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                            // Extra digits for standard scientific notation.  In this case we
10580018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm                            // have a decimal point and no ellipsis.
10680018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm                            // We assume that we do not drop digits to make room for the decimal
10780018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm                            // point in ordinary scientific notation. Thus >= 1.
1081176f23dae4d3740782e46463003e9f36a381c9dHans Boehm    private ActionMode mActionMode;
1091176f23dae4d3740782e46463003e9f36a381c9dHans Boehm    private final ForegroundColorSpan mExponentColorSpan;
11084614957604253d51296e06c97daced699a0a9deHans Boehm
11184614957604253d51296e06c97daced699a0a9deHans Boehm    public CalculatorResult(Context context, AttributeSet attrs) {
11284614957604253d51296e06c97daced699a0a9deHans Boehm        super(context, attrs);
11384614957604253d51296e06c97daced699a0a9deHans Boehm        mScroller = new OverScroller(context);
11484614957604253d51296e06c97daced699a0a9deHans Boehm        mGestureDetector = new GestureDetector(context,
11584614957604253d51296e06c97daced699a0a9deHans Boehm            new GestureDetector.SimpleOnGestureListener() {
11684614957604253d51296e06c97daced699a0a9deHans Boehm                @Override
117d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen                public boolean onDown(MotionEvent e) {
118d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen                    return true;
119d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen                }
120d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen                @Override
12184614957604253d51296e06c97daced699a0a9deHans Boehm                public boolean onFling(MotionEvent e1, MotionEvent e2,
12284614957604253d51296e06c97daced699a0a9deHans Boehm                                       float velocityX, float velocityY) {
12384614957604253d51296e06c97daced699a0a9deHans Boehm                    if (!mScroller.isFinished()) {
12484614957604253d51296e06c97daced699a0a9deHans Boehm                        mCurrentPos = mScroller.getFinalX();
12584614957604253d51296e06c97daced699a0a9deHans Boehm                    }
12684614957604253d51296e06c97daced699a0a9deHans Boehm                    mScroller.forceFinished(true);
1271176f23dae4d3740782e46463003e9f36a381c9dHans Boehm                    stopActionMode();
128fbcef7005de4436682072927f83000b502928d25Hans Boehm                    CalculatorResult.this.cancelLongPress();
129fbcef7005de4436682072927f83000b502928d25Hans Boehm                    // Ignore scrolls of error string, etc.
130fbcef7005de4436682072927f83000b502928d25Hans Boehm                    if (!mScrollable) return true;
131c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm                    mScroller.fling(mCurrentPos, 0, - (int) velocityX, 0  /* horizontal only */,
13261568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm                                    mMinPos, mMaxPos, 0, 0);
1334459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                    postInvalidateOnAnimation();
13484614957604253d51296e06c97daced699a0a9deHans Boehm                    return true;
13584614957604253d51296e06c97daced699a0a9deHans Boehm                }
13684614957604253d51296e06c97daced699a0a9deHans Boehm                @Override
13784614957604253d51296e06c97daced699a0a9deHans Boehm                public boolean onScroll(MotionEvent e1, MotionEvent e2,
13884614957604253d51296e06c97daced699a0a9deHans Boehm                                        float distanceX, float distanceY) {
13961568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm                    int distance = (int)distanceX;
14084614957604253d51296e06c97daced699a0a9deHans Boehm                    if (!mScroller.isFinished()) {
14184614957604253d51296e06c97daced699a0a9deHans Boehm                        mCurrentPos = mScroller.getFinalX();
14284614957604253d51296e06c97daced699a0a9deHans Boehm                    }
14384614957604253d51296e06c97daced699a0a9deHans Boehm                    mScroller.forceFinished(true);
1441176f23dae4d3740782e46463003e9f36a381c9dHans Boehm                    stopActionMode();
14584614957604253d51296e06c97daced699a0a9deHans Boehm                    CalculatorResult.this.cancelLongPress();
14684614957604253d51296e06c97daced699a0a9deHans Boehm                    if (!mScrollable) return true;
14761568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm                    if (mCurrentPos + distance < mMinPos) {
14861568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm                        distance = mMinPos - mCurrentPos;
14961568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm                    } else if (mCurrentPos + distance > mMaxPos) {
15061568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm                        distance = mMaxPos - mCurrentPos;
15161568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm                    }
15284614957604253d51296e06c97daced699a0a9deHans Boehm                    int duration = (int)(e2.getEventTime() - e1.getEventTime());
15384614957604253d51296e06c97daced699a0a9deHans Boehm                    if (duration < 1 || duration > 100) duration = 10;
15461568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm                    mScroller.startScroll(mCurrentPos, 0, distance, 0, (int)duration);
1554459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                    postInvalidateOnAnimation();
15684614957604253d51296e06c97daced699a0a9deHans Boehm                    return true;
15784614957604253d51296e06c97daced699a0a9deHans Boehm                }
1584a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                @Override
1594a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                public void onLongPress(MotionEvent e) {
1601176f23dae4d3740782e46463003e9f36a381c9dHans Boehm                    if (mValid) {
1611176f23dae4d3740782e46463003e9f36a381c9dHans Boehm                        mActionMode = startActionMode(mCopyActionModeCallback,
1621176f23dae4d3740782e46463003e9f36a381c9dHans Boehm                                ActionMode.TYPE_FLOATING);
1631176f23dae4d3740782e46463003e9f36a381c9dHans Boehm                    }
1644a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                }
16584614957604253d51296e06c97daced699a0a9deHans Boehm            });
16684614957604253d51296e06c97daced699a0a9deHans Boehm        setOnTouchListener(mTouchListener);
16784614957604253d51296e06c97daced699a0a9deHans Boehm        setHorizontallyScrolling(false);  // do it ourselves
16884614957604253d51296e06c97daced699a0a9deHans Boehm        setCursorVisible(false);
1691176f23dae4d3740782e46463003e9f36a381c9dHans Boehm        mExponentColorSpan = new ForegroundColorSpan(
1701176f23dae4d3740782e46463003e9f36a381c9dHans Boehm                context.getColor(R.color.display_result_exponent_text_color));
1714a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm
1724a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        // Copy ActionMode is triggered explicitly, not through
1734a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        // setCustomSelectionActionModeCallback.
17484614957604253d51296e06c97daced699a0a9deHans Boehm    }
17584614957604253d51296e06c97daced699a0a9deHans Boehm
17684614957604253d51296e06c97daced699a0a9deHans Boehm    void setEvaluator(Evaluator evaluator) {
17784614957604253d51296e06c97daced699a0a9deHans Boehm        mEvaluator = evaluator;
17884614957604253d51296e06c97daced699a0a9deHans Boehm    }
17984614957604253d51296e06c97daced699a0a9deHans Boehm
1804a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    @Override
1814a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1824a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1834a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm
1844459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        final TextPaint paint = getPaint();
18580018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm        final Context context = getContext();
1864459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        final float newCharWidth = Layout.getDesiredWidth("\u2007", paint);
18780018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm        // Digits are presumed to have no more than newCharWidth.
18880018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm        // We sometimes replace a character by an ellipsis or, due to SCI_NOTATION_EXTRA, add
18980018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm        // an extra decimal separator beyond the maximum number of characters we normally allow.
19080018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm        // Empirically, our minus sign is also slightly wider than a digit, so we have to
19180018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm        // account for that.  We never have both an ellipsis and two minus signs, and
19280018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm        // we assume an ellipsis is no narrower than a minus sign.
19380018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm        final float decimalSeparatorWidth = Layout.getDesiredWidth(
19480018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm                context.getString(R.string.dec_point), paint);
19580018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm        final float minusExtraWidth = Layout.getDesiredWidth(
19680018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm                context.getString(R.string.op_sub), paint) - newCharWidth;
19780018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm        final float ellipsisExtraWidth = Layout.getDesiredWidth(KeyMaps.ELLIPSIS, paint)
19880018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm                - newCharWidth;
19980018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm        final int extraWidth = (int) (Math.ceil(Math.max(decimalSeparatorWidth + minusExtraWidth,
20080018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm                ellipsisExtraWidth)) + Math.max(minusExtraWidth, 0.0f));
20180018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm        final int newWidthConstraint = MeasureSpec.getSize(widthMeasureSpec)
20280018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm                - (getPaddingLeft() + getPaddingRight()) - extraWidth;
2034a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        synchronized(mWidthLock) {
204013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm            mWidthConstraint = newWidthConstraint;
205013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm            mCharWidth = newCharWidth;
2064a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        }
2074a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    }
2084a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm
209a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm    // Return the length of the exponent representation for the given exponent, in
210a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm    // characters.
211a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm    private final int expLen(int exp) {
212a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        if (exp == 0) return 0;
2135e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        final int abs_exp_digits = (int) Math.ceil(Math.log10(Math.abs((double)exp))
2145e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                + 0.0000000001d /* Round whole numbers to next integer */);
2155e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        return abs_exp_digits + (exp >= 0 ? 1 : 2);
216a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm    }
217a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm
218a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm    /**
219a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm     * Initiate display of a new result.
220a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm     * The parameters specify various properties of the result.
221a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm     * @param initPrec Initial display precision computed by evaluator. (1 = tenths digit)
222a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm     * @param msd Position of most significant digit.  Offset from left of string.
223a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                  Evaluator.INVALID_MSD if unknown.
224a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm     * @param leastDigPos Position of least significant digit (1 = tenths digit)
225a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm     *                    or Integer.MAX_VALUE.
226a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm     * @param truncatedWholePart Result up to but not including decimal point.
227a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                                 Currently we only use the length.
228a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm     */
229a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm    void displayResult(int initPrec, int msd, int leastDigPos, String truncatedWholePart) {
230a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        initPositions(initPrec, msd, leastDigPos, truncatedWholePart);
231a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        redisplay();
23261568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm    }
23361568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm
234a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm    /**
2355e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm     * Set up scroll bounds (mMinPos, mMaxPos, etc.) and determine whether the result is
2365e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm     * scrollable, based on the supplied information about the result.
237a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm     * This is unfortunately complicated because we need to predict whether trailing digits
238a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm     * will eventually be replaced by an exponent.
239a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm     * Just appending the exponent during formatting would be simpler, but would produce
240a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm     * jumpier results during transitions.
241a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm     */
2425e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    private void initPositions(int initPrecOffset, int msdIndex, int lsdOffset,
2435e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            String truncatedWholePart) {
244a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        float charWidth;
245a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        int maxChars = getMaxChars();
24684614957604253d51296e06c97daced699a0a9deHans Boehm        mLastPos = INVALID;
2475e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        mLsdOffset = lsdOffset;
248013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm        synchronized(mWidthLock) {
249a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            charWidth = mCharWidth;
250a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        }
2515e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        mCurrentPos = mMinPos = (int) Math.round(initPrecOffset * charWidth);
252a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        // Prevent scrolling past initial position, which is calculated to show leading digits.
2535e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        if (msdIndex == Evaluator.INVALID_MSD) {
254a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            // Possible zero value
2555e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            if (lsdOffset == Integer.MIN_VALUE) {
256a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                // Definite zero value.
257a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                mMaxPos = mMinPos;
2585e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                mMaxCharOffset = (int) Math.round(mMaxPos/charWidth);
259a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                mScrollable = false;
260a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            } else {
261a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                // May be very small nonzero value.  Allow user to find out.
2625e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                mMaxPos = mMaxCharOffset = MAX_RIGHT_SCROLL;
2635e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                mMinPos -= charWidth;  // Allow for future minus sign.
264a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                mScrollable = true;
265a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            }
266a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            return;
267013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm        }
268a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        int wholeLen =  truncatedWholePart.length();
269a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        int negative = truncatedWholePart.charAt(0) == '-' ? 1 : 0;
2705e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        if (msdIndex > wholeLen && msdIndex <= wholeLen + 3) {
2715e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            // Avoid tiny negative exponent; pretend msdIndex is just to the right of decimal point.
2725e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            msdIndex = wholeLen - 1;
273a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        }
2745e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        int minCharOffset = msdIndex - wholeLen;
275a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                                // Position of leftmost significant digit relative to dec. point.
276a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                                // Usually negative.
2775e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        mMaxCharOffset = MAX_RIGHT_SCROLL; // How far does it make sense to scroll right?
278a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        // If msd is left of decimal point should logically be
27961568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm        // mMinPos = - (int) Math.ceil(getPaint().measureText(truncatedWholePart)), but
280a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        // we eventually translate to a character position by dividing by mCharWidth.
28161568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm        // To avoid rounding issues, we use the analogous computation here.
2825e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        if (minCharOffset > -1 && minCharOffset < MAX_LEADING_ZEROES + 2) {
283a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            // Small number of leading zeroes, avoid scientific notation.
2845e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            minCharOffset = -1;
285a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        }
2865e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        if (lsdOffset < MAX_RIGHT_SCROLL) {
2875e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            mMaxCharOffset = lsdOffset;
2885e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            if (mMaxCharOffset < -1 && mMaxCharOffset > -(MAX_TRAILING_ZEROES + 2)) {
2895e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                mMaxCharOffset = -1;
290a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            }
2915e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            // lsdOffset is positive or negative, never 0.
2925e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            int currentExpLen = 0;  // Length of required standard scientific notation exponent.
2935e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            if (mMaxCharOffset < -1) {
2945e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                currentExpLen = expLen(-minCharOffset - 1);
2955e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            } else if (minCharOffset > -1 || mMaxCharOffset >= maxChars) {
296a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                // Number either entirely to the right of decimal point, or decimal point not
297a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                // visible when scrolled to the right.
2985e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                currentExpLen = expLen(-minCharOffset);
299a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            }
3005e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            mScrollable = (mMaxCharOffset + currentExpLen - minCharOffset + negative >= maxChars);
3015e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            int newMaxCharOffset;
3025e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            if (currentExpLen > 0) {
3035e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                if (mScrollable) {
3045e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                    // We'll use exponent corresponding to leastDigPos when scrolled to right.
3055e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                    newMaxCharOffset = mMaxCharOffset + expLen(-lsdOffset);
3065e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                } else {
3075e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                    newMaxCharOffset = mMaxCharOffset + currentExpLen;
3085e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                }
3095e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                if (mMaxCharOffset <= -1 && newMaxCharOffset > -1) {
3105e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                    // Very unlikely; just drop exponent.
3115e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                    mMaxCharOffset = -1;
3125e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                } else {
3135e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                    mMaxCharOffset = newMaxCharOffset;
314a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                }
315a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            }
3165e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            mMaxPos = Math.min((int) Math.round(mMaxCharOffset * charWidth), MAX_RIGHT_SCROLL);
317a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            if (!mScrollable) {
318a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                // Position the number consistently with our assumptions to make sure it
319a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                // actually fits.
320a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                mCurrentPos = mMaxPos;
321a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            }
32261568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm        } else {
3235e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            mMaxPos = mMaxCharOffset = MAX_RIGHT_SCROLL;
324a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            mScrollable = true;
32561568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm        }
32684614957604253d51296e06c97daced699a0a9deHans Boehm    }
32784614957604253d51296e06c97daced699a0a9deHans Boehm
32884614957604253d51296e06c97daced699a0a9deHans Boehm    void displayError(int resourceId) {
329760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm        mValid = true;
33084614957604253d51296e06c97daced699a0a9deHans Boehm        mScrollable = false;
33184614957604253d51296e06c97daced699a0a9deHans Boehm        setText(resourceId);
33284614957604253d51296e06c97daced699a0a9deHans Boehm    }
33384614957604253d51296e06c97daced699a0a9deHans Boehm
334013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm    private final int MAX_COPY_SIZE = 1000000;
335013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm
336a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm    /*
337a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm     * Return the most significant digit position in the given string or Evaluator.INVALID_MSD.
338b13daf1050757fe3c69c2f0246de33e7e69b5fa9Hans Boehm     * Unlike Evaluator.getMsdIndexOf, we treat a final 1 as significant.
339a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm     */
340b13daf1050757fe3c69c2f0246de33e7e69b5fa9Hans Boehm    public static int getNaiveMsdIndexOf(String s) {
341a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        int len = s.length();
342a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        for (int i = 0; i < len; ++i) {
343a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            char c = s.charAt(i);
344a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            if (c != '-' && c != '.' && c != '0') {
345a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                return i;
346a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            }
347a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        }
348a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        return Evaluator.INVALID_MSD;
349a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm    }
350a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm
351c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm    // Format a result returned by Evaluator.getString() into a single line containing ellipses
352b13daf1050757fe3c69c2f0246de33e7e69b5fa9Hans Boehm    // (if appropriate) and an exponent (if appropriate).  precOffset is the value that was passed
353b13daf1050757fe3c69c2f0246de33e7e69b5fa9Hans Boehm    // to getString and thus identifies the significance of the rightmost digit.
354a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm    // A value of 1 means the rightmost digits corresponds to tenths.
355a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm    // maxDigs is the maximum number of characters in the result.
3565e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    // We set lastDisplayedOffset[0] to the offset of the last digit actually appearing in
357f6dae114d7e7c5a2d2d5a6b0f2c8d1fc6cf2937fHans Boehm    // the display.
358f6dae114d7e7c5a2d2d5a6b0f2c8d1fc6cf2937fHans Boehm    // If forcePrecision is true, we make sure that the last displayed digit corresponds to
359b13daf1050757fe3c69c2f0246de33e7e69b5fa9Hans Boehm    // precOffset, and allow maxDigs to be exceeded in assing the exponent.
36008e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm    // We add two distinct kinds of exponents:
3615e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    // (1) If the final result contains the leading digit we use standard scientific notation.
3625e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    // (2) If not, we add an exponent corresponding to an interpretation of the final result as
3635e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    //     an integer.
36408e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm    // We add an ellipsis on the left if the result was truncated.
365c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm    // We add ellipses and exponents in a way that leaves most digits in the position they
366c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm    // would have been in had we not done so.
367c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm    // This minimizes jumps as a result of scrolling.  Result is NOT internationalized,
3680b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm    // uses "E" for exponent.
3695e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    public String formatResult(String in, int precOffset, int maxDigs, boolean truncated,
3705e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            boolean negative, int lastDisplayedOffset[], boolean forcePrecision) {
3715e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        final int minusSpace = negative ? 1 : 0;
372b13daf1050757fe3c69c2f0246de33e7e69b5fa9Hans Boehm        final int msdIndex = truncated ? -1 : getNaiveMsdIndexOf(in);  // INVALID_MSD is OK.
3735e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        String result = in;
374ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm        if (truncated || (negative && result.charAt(0) != '-')) {
375ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm            result = KeyMaps.ELLIPSIS + result.substring(1, result.length());
376ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm            // Ellipsis may be removed again in the type(1) scientific notation case.
377ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm        }
378ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm        final int decIndex = result.indexOf('.');
3795e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        lastDisplayedOffset[0] = precOffset;
3805e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        if ((decIndex == -1 || msdIndex != Evaluator.INVALID_MSD
3815e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                && msdIndex - decIndex > MAX_LEADING_ZEROES + 1) &&  precOffset != -1) {
382a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            // No decimal point displayed, and it's not just to the right of the last digit,
383a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            // or we should suppress leading zeroes.
384c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm            // Add an exponent to let the user track which digits are currently displayed.
3855e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            // Start with type (2) exponent if we dropped no digits. -1 accounts for decimal point.
3865e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            final int initExponent = precOffset > 0 ? -precOffset : -precOffset - 1;
3875e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            int exponent = initExponent;
38808e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm            boolean hasPoint = false;
3895e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            if (!truncated && msdIndex < maxDigs - 1
3905e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                    && result.length() - msdIndex + 1 + minusSpace
3915e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                    <= maxDigs + SCI_NOTATION_EXTRA) {
3925e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                // Type (1) exponent computation and transformation:
393c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm                // Leading digit is in display window. Use standard calculator scientific notation
394c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm                // with one digit to the left of the decimal point. Insert decimal point and
395c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm                // delete leading zeroes.
396a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                // We try to keep leading digits roughly in position, and never
397f6dae114d7e7c5a2d2d5a6b0f2c8d1fc6cf2937fHans Boehm                // lengthen the result by more than SCI_NOTATION_EXTRA.
3985e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                final int resLen = result.length();
3995e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                String fraction = result.substring(msdIndex + 1, resLen);
4005e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                result = (negative ? "-" : "") + result.substring(msdIndex, msdIndex + 1)
4015e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                        + "." + fraction;
402c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm                // Original exp was correct for decimal point at right of fraction.
403c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm                // Adjust by length of fraction.
4045e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                exponent = initExponent + resLen - msdIndex - 1;
40508e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm                hasPoint = true;
40608e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm            }
407ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm            // Exponent can't be zero.
408ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm            // Actually add the exponent of either type:
409ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm            if (!forcePrecision) {
410ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                int dropDigits;  // Digits to drop to make room for exponent.
411ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                if (hasPoint) {
412ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                    // Type (1) exponent.
413ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                    // Drop digits even if there is room. Otherwise the scrolling gets jumpy.
414ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                    dropDigits = expLen(exponent);
415ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                    if (dropDigits >= result.length() - 1) {
416ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                        // Jumpy is better than no mantissa.  Probably impossible anyway.
417ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                        dropDigits = Math.max(result.length() - 2, 0);
418ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                    }
419ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                } else {
420ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                    // Type (2) exponent.
421ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                    // Exponent depends on the number of digits we drop, which depends on
422ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                    // exponent ...
423ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                    for (dropDigits = 2; expLen(initExponent + dropDigits) > dropDigits;
424ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                            ++dropDigits) {}
425ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                    exponent = initExponent + dropDigits;
426ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                    if (precOffset - dropDigits > mLsdOffset) {
427ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                        // This can happen if e.g. result = 10^40 + 10^10
428ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                        // It turns out we would otherwise display ...10e9 because it takes
429ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                        // the same amount of space as ...1e10 but shows one more digit.
430ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                        // But we don't want to display a trailing zero, even if it's free.
431ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                        ++dropDigits;
432ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                        ++exponent;
433a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                    }
43408e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm                }
435ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                result = result.substring(0, result.length() - dropDigits);
436ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm                lastDisplayedOffset[0] -= dropDigits;
437ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm            }
438ec7517f38758f67233f2b06bc7bab17a8ffeccbaHans Boehm            result = result + "E" + Integer.toString(exponent);
4395e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        }
4405e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        return result;
44108e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm    }
44208e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm
443f6dae114d7e7c5a2d2d5a6b0f2c8d1fc6cf2937fHans Boehm    /**
444f6dae114d7e7c5a2d2d5a6b0f2c8d1fc6cf2937fHans Boehm     * Get formatted, but not internationalized, result from mEvaluator.
4455e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm     * @param precOffset requested position (1 = tenths) of last included digit.
446f6dae114d7e7c5a2d2d5a6b0f2c8d1fc6cf2937fHans Boehm     * @param maxSize Maximum number of characters (more or less) in result.
4475e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm     * @param lastDisplayedOffset Zeroth entry is set to actual offset of last included digit,
4485e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm     *                            after adjusting for exponent, etc.
449f6dae114d7e7c5a2d2d5a6b0f2c8d1fc6cf2937fHans Boehm     * @param forcePrecision Ensure that last included digit is at pos, at the expense
450f6dae114d7e7c5a2d2d5a6b0f2c8d1fc6cf2937fHans Boehm     *                       of treating maxSize as a soft limit.
451f6dae114d7e7c5a2d2d5a6b0f2c8d1fc6cf2937fHans Boehm     */
4525e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    private String getFormattedResult(int precOffset, int maxSize, int lastDisplayedOffset[],
453f6dae114d7e7c5a2d2d5a6b0f2c8d1fc6cf2937fHans Boehm            boolean forcePrecision) {
45408e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm        final boolean truncated[] = new boolean[1];
45508e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm        final boolean negative[] = new boolean[1];
4565e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        final int requestedPrecOffset[] = {precOffset};
4575e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        final String rawResult = mEvaluator.getString(requestedPrecOffset, mMaxCharOffset,
458a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm                maxSize, truncated, negative);
4595e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        return formatResult(rawResult, requestedPrecOffset[0], maxSize, truncated[0], negative[0],
4605e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                lastDisplayedOffset, forcePrecision);
46108e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm   }
46208e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm
46384614957604253d51296e06c97daced699a0a9deHans Boehm    // Return entire result (within reason) up to current displayed precision.
4644a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    public String getFullText() {
465760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm        if (!mValid) return "";
4664a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        if (!mScrollable) return getText().toString();
4675e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        int currentCharOffset = getCurrentCharOffset();
468f6dae114d7e7c5a2d2d5a6b0f2c8d1fc6cf2937fHans Boehm        int unused[] = new int[1];
4695e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        return KeyMaps.translateResult(getFormattedResult(mLastDisplayedOffset, MAX_COPY_SIZE,
470f6dae114d7e7c5a2d2d5a6b0f2c8d1fc6cf2937fHans Boehm                unused, true));
47184614957604253d51296e06c97daced699a0a9deHans Boehm    }
47284614957604253d51296e06c97daced699a0a9deHans Boehm
4734a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    public boolean fullTextIsExact() {
474f6dae114d7e7c5a2d2d5a6b0f2c8d1fc6cf2937fHans Boehm        return !mScrollable
4755e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm                || mMaxCharOffset == getCurrentCharOffset() && mMaxCharOffset != MAX_RIGHT_SCROLL;
4764a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    }
4774a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm
47861568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm    /**
47961568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm     * Return the maximum number of characters that will fit in the result display.
48061568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm     * May be called asynchronously from non-UI thread.
48161568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm     */
48284614957604253d51296e06c97daced699a0a9deHans Boehm    int getMaxChars() {
4834a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        int result;
4844a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        synchronized(mWidthLock) {
4854459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen            result = (int) Math.floor(mWidthConstraint / mCharWidth);
486c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm            // We can apparently finish evaluating before onMeasure in CalculatorText has been
487c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm            // called, in which case we get 0 or -1 as the width constraint.
4884a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        }
48984614957604253d51296e06c97daced699a0a9deHans Boehm        if (result <= 0) {
490c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm            // Return something conservatively big, to force sufficient evaluation.
4914a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            return MAX_WIDTH;
49284614957604253d51296e06c97daced699a0a9deHans Boehm        } else {
49380018c885a8f8c2ae32b05c4b2bcd7ae164b04feHans Boehm            return result;
49484614957604253d51296e06c97daced699a0a9deHans Boehm        }
49584614957604253d51296e06c97daced699a0a9deHans Boehm    }
49684614957604253d51296e06c97daced699a0a9deHans Boehm
49761568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm    /**
4984459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen     * @return {@code true} if the currently displayed result is scrollable
49961568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm     */
5004459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen    public boolean isScrollable() {
5014459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        return mScrollable;
50261568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm    }
50361568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm
5045e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm    int getCurrentCharOffset() {
505013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm        synchronized(mWidthLock) {
506a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            return (int) Math.round(mCurrentPos / mCharWidth);
507013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm        }
508013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm    }
509013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm
51084614957604253d51296e06c97daced699a0a9deHans Boehm    void clear() {
511760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm        mValid = false;
5121176f23dae4d3740782e46463003e9f36a381c9dHans Boehm        mScrollable = false;
51384614957604253d51296e06c97daced699a0a9deHans Boehm        setText("");
51484614957604253d51296e06c97daced699a0a9deHans Boehm    }
51584614957604253d51296e06c97daced699a0a9deHans Boehm
51684614957604253d51296e06c97daced699a0a9deHans Boehm    void redisplay() {
5175e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        int currentCharOffset = getCurrentCharOffset();
51884614957604253d51296e06c97daced699a0a9deHans Boehm        int maxChars = getMaxChars();
5195e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        int lastDisplayedOffset[] = new int[1];
5205e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        String result = getFormattedResult(currentCharOffset, maxChars, lastDisplayedOffset, false);
5210b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm        int expIndex = result.indexOf('E');
522013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm        result = KeyMaps.translateResult(result);
5235e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        if (expIndex > 0 && result.indexOf('.') == -1) {
52484614957604253d51296e06c97daced699a0a9deHans Boehm          // Gray out exponent if used as position indicator
52584614957604253d51296e06c97daced699a0a9deHans Boehm            SpannableString formattedResult = new SpannableString(result);
5265e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm            formattedResult.setSpan(mExponentColorSpan, expIndex, result.length(),
52784614957604253d51296e06c97daced699a0a9deHans Boehm                                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
52884614957604253d51296e06c97daced699a0a9deHans Boehm            setText(formattedResult);
52984614957604253d51296e06c97daced699a0a9deHans Boehm        } else {
53084614957604253d51296e06c97daced699a0a9deHans Boehm            setText(result);
53184614957604253d51296e06c97daced699a0a9deHans Boehm        }
5325e802f30a0f18df4e2ecbf030e4aebb4ee70e8e8Hans Boehm        mLastDisplayedOffset = lastDisplayedOffset[0];
533760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm        mValid = true;
53484614957604253d51296e06c97daced699a0a9deHans Boehm    }
53584614957604253d51296e06c97daced699a0a9deHans Boehm
53684614957604253d51296e06c97daced699a0a9deHans Boehm    @Override
53784614957604253d51296e06c97daced699a0a9deHans Boehm    public void computeScroll() {
53884614957604253d51296e06c97daced699a0a9deHans Boehm        if (!mScrollable) return;
53984614957604253d51296e06c97daced699a0a9deHans Boehm        if (mScroller.computeScrollOffset()) {
54084614957604253d51296e06c97daced699a0a9deHans Boehm            mCurrentPos = mScroller.getCurrX();
54184614957604253d51296e06c97daced699a0a9deHans Boehm            if (mCurrentPos != mLastPos) {
54284614957604253d51296e06c97daced699a0a9deHans Boehm                mLastPos = mCurrentPos;
54384614957604253d51296e06c97daced699a0a9deHans Boehm                redisplay();
54484614957604253d51296e06c97daced699a0a9deHans Boehm            }
54584614957604253d51296e06c97daced699a0a9deHans Boehm            if (!mScroller.isFinished()) {
5464459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                postInvalidateOnAnimation();
54784614957604253d51296e06c97daced699a0a9deHans Boehm            }
54884614957604253d51296e06c97daced699a0a9deHans Boehm        }
54984614957604253d51296e06c97daced699a0a9deHans Boehm    }
55084614957604253d51296e06c97daced699a0a9deHans Boehm
5514a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    // Copy support:
5524a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm
5537f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm    private ActionMode.Callback2 mCopyActionModeCallback = new ActionMode.Callback2() {
5547f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm
5557f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm        private BackgroundColorSpan mHighlightSpan;
5567f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm
5577f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm        private void highlightResult() {
5587f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm            final Spannable text = (Spannable) getText();
5597f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm            mHighlightSpan = new BackgroundColorSpan(getHighlightColor());
5607f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm            text.setSpan(mHighlightSpan, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
5617f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm        }
5627f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm
5637f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm        private void unhighlightResult() {
5647f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm            final Spannable text = (Spannable) getText();
5657f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm            text.removeSpan(mHighlightSpan);
5667f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm        }
5677f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm
5684a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        @Override
5694a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
5704a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            MenuInflater inflater = mode.getMenuInflater();
5714a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            inflater.inflate(R.menu.copy, menu);
5727f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm            highlightResult();
5734a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            return true;
5744a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        }
5754a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm
5764a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        @Override
5774a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
5784a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            return false; // Return false if nothing is done
5794a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        }
5804a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm
5814a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        @Override
5824a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
5834a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            switch (item.getItemId()) {
5844a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            case R.id.menu_copy:
5854a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                copyContent();
5864a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                mode.finish();
5874a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                return true;
5884a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            default:
5894a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                return false;
5904a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            }
5914a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        }
5924a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm
5934a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        @Override
5944a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        public void onDestroyActionMode(ActionMode mode) {
5957f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm            unhighlightResult();
5961176f23dae4d3740782e46463003e9f36a381c9dHans Boehm            mActionMode = null;
5974a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        }
5987f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm
5997f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm        @Override
6007f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm        public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
6017f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm            super.onGetContentRect(mode, view, outRect);
6027f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm            outRect.left += getPaddingLeft();
6037f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm            outRect.top += getPaddingTop();
6047f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm            outRect.right -= getPaddingRight();
6057f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm            outRect.bottom -= getPaddingBottom();
6067f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm            final int width = (int) Layout.getDesiredWidth(getText(), getPaint());
6077f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm            if (width < outRect.width()) {
6087f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm                outRect.left = outRect.right - width;
6097f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm            }
6107f83e36b7a1cb358b8dd44da842a3897b65bfddeHans Boehm        }
6114a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    };
6124a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm
6131176f23dae4d3740782e46463003e9f36a381c9dHans Boehm    public boolean stopActionMode() {
6141176f23dae4d3740782e46463003e9f36a381c9dHans Boehm        if (mActionMode != null) {
6151176f23dae4d3740782e46463003e9f36a381c9dHans Boehm            mActionMode.finish();
6161176f23dae4d3740782e46463003e9f36a381c9dHans Boehm            return true;
6171176f23dae4d3740782e46463003e9f36a381c9dHans Boehm        }
6181176f23dae4d3740782e46463003e9f36a381c9dHans Boehm        return false;
6191176f23dae4d3740782e46463003e9f36a381c9dHans Boehm    }
6201176f23dae4d3740782e46463003e9f36a381c9dHans Boehm
6214a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    private void setPrimaryClip(ClipData clip) {
6224a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        ClipboardManager clipboard = (ClipboardManager) getContext().
623c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm                                               getSystemService(Context.CLIPBOARD_SERVICE);
6244a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        clipboard.setPrimaryClip(clip);
6254a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    }
6264a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm
6274a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    private void copyContent() {
6284a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        final CharSequence text = getFullText();
6294a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        ClipboardManager clipboard =
630c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm                (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
631c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm        // We include a tag URI, to allow us to recognize our own results and handle them
632c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm        // specially.
633c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm        ClipData.Item newItem = new ClipData.Item(text, null, mEvaluator.capture());
634c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm        String[] mimeTypes = new String[] {ClipDescription.MIMETYPE_TEXT_PLAIN};
635c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm        ClipData cd = new ClipData("calculator result", mimeTypes, newItem);
6364a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        clipboard.setPrimaryClip(cd);
637c01cd7f21c3f74d83ba0f9284deb930a298efbd6Hans Boehm        Toast.makeText(getContext(), R.string.text_copied_toast, Toast.LENGTH_SHORT).show();
6384a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    }
6394a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm
64084614957604253d51296e06c97daced699a0a9deHans Boehm}
641