blob: 6c727e01138a47fec0875f9a78be28b08eeed570 [file] [log] [blame]
Hans Boehm84614952014-11-25 18:46:17 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
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
19import android.widget.TextView;
20import android.graphics.Typeface;
21import android.graphics.Paint;
22import android.graphics.Rect;
23import android.graphics.Color;
24import android.widget.OverScroller;
25import android.view.GestureDetector;
26import android.content.Context;
27import android.util.AttributeSet;
28import android.view.MotionEvent;
29import android.view.View;
30import android.text.Editable;
31import android.text.Spanned;
32import android.text.SpannableString;
33import android.text.style.ForegroundColorSpan;
34
35import android.support.v4.view.ViewCompat;
36
37
38// A text widget that is "infinitely" scrollable to the right,
39// and obtains the text to display via a callback to Logic.
40public class CalculatorResult extends CalculatorEditText {
41 final static int MAX_RIGHT_SCROLL = 100000000;
42 final static int INVALID = MAX_RIGHT_SCROLL + 10000;
43 // A larger value is unlikely to avoid running out of space
44 final OverScroller mScroller;
45 final GestureDetector mGestureDetector;
46 class MyTouchListener implements View.OnTouchListener {
47 @Override
48 public boolean onTouch(View v, MotionEvent event) {
49 boolean res = mGestureDetector.onTouchEvent(event);
50 return res;
51 }
52 }
53 final MyTouchListener mTouchListener = new MyTouchListener();
54 private Evaluator mEvaluator;
55 private boolean mScrollable = false;
56 // A scrollable result is currently displayed.
57 private int mCurrentPos;// Position of right of display relative
58 // to decimal point, in pixels.
59 // Large positive values mean the decimal
60 // point is scrolled off the left of the
61 // display. Zero means decimal point is
62 // barely displayed on the right.
63 private int mLastPos; // Position already reflected in display.
64 private int mMinPos; // Maximum position before all digits
65 // digits disappear of the right.
66 private int mCharWidth; // Use monospaced font for now.
67 // This shouldn't be much harder with a variable
68 // width font, except it may be even less smooth
69 // FIXME: This is not really a fixed width font anymore.
70 private Paint mPaint; // Paint object matching display.
71
72 public CalculatorResult(Context context, AttributeSet attrs) {
73 super(context, attrs);
74 mScroller = new OverScroller(context);
75 mGestureDetector = new GestureDetector(context,
76 new GestureDetector.SimpleOnGestureListener() {
77 @Override
78 public boolean onFling(MotionEvent e1, MotionEvent e2,
79 float velocityX, float velocityY) {
80 if (!mScroller.isFinished()) {
81 mCurrentPos = mScroller.getFinalX();
82 }
83 mScroller.forceFinished(true);
84 CalculatorResult.this.cancelLongPress(); // Ignore scrolls of error string, etc.
85 if (!mScrollable) return true;
86 mScroller.fling(mCurrentPos, 0, - (int) velocityX,
87 0 /* horizontal only */, mMinPos,
88 MAX_RIGHT_SCROLL, 0, 0);
89 ViewCompat.postInvalidateOnAnimation(CalculatorResult.this);
90 return true;
91 }
92 @Override
93 public boolean onScroll(MotionEvent e1, MotionEvent e2,
94 float distanceX, float distanceY) {
95 // TODO: Should we be dealing with any edge effects here?
96 if (!mScroller.isFinished()) {
97 mCurrentPos = mScroller.getFinalX();
98 }
99 mScroller.forceFinished(true);
100 CalculatorResult.this.cancelLongPress();
101 if (!mScrollable) return true;
102 int duration = (int)(e2.getEventTime() - e1.getEventTime());
103 if (duration < 1 || duration > 100) duration = 10;
104 mScroller.startScroll(mCurrentPos, 0, (int)distanceX, 0,
105 (int)duration);
106 ViewCompat.postInvalidateOnAnimation(CalculatorResult.this);
107 return true;
108 }
109 });
110 setOnTouchListener(mTouchListener);
111 setHorizontallyScrolling(false); // do it ourselves
112 setCursorVisible(false);
113 setTypeface(Typeface.MONOSPACE);
114 mPaint = getPaint();
115 mCharWidth = (int) mPaint.measureText("5");
116 }
117
118 void setEvaluator(Evaluator evaluator) {
119 mEvaluator = evaluator;
120 }
121
122 // Display a new result, given initial displayed
123 // precision and the string representing the whole part of
124 // the number to be displayed.
125 // We pass the string, instead of just the length, so we have
126 // one less place to fix in case we ever decide to use a variable
127 // width font.
128 void displayResult(int initPrec, String truncatedWholePart) {
129 mLastPos = INVALID;
130 mCurrentPos = initPrec * mCharWidth;
131 mMinPos = - (int) Math.ceil(mPaint.measureText(truncatedWholePart));
132 redisplay();
133 }
134
135 // May be called from non-UI thread, but after initialization.
136 int getCharWidth() {
137 return mCharWidth;
138 }
139
140 void displayError(int resourceId) {
141 mScrollable = false;
142 setText(resourceId);
143 }
144
145 // Return entire result (within reason) up to current displayed precision.
146 public CharSequence getFullText() {
147 if (!mScrollable) return getText();
148 int currentCharPos = mCurrentPos/mCharWidth;
149 return mEvaluator.getString(currentCharPos, 1000000);
150 }
151
152 int getMaxChars() {
153 int result = getWidthConstraint() / mCharWidth;
154 // FIXME: We can apparently finish evaluating before
155 // onMeasure in CalculatorEditText has been called, in
156 // which case we get 0 or -1 as the width constraint.
157 // Perhaps guess conservatively here and reevaluate
158 // in InitialResult.onPostExecute?
159 if (result <= 0) {
160 return 8;
161 } else {
162 return result;
163 }
164 }
165
166 void clear() {
167 setText("");
168 }
169
170 void redisplay() {
171 int currentCharPos = mCurrentPos/mCharWidth;
172 int maxChars = getMaxChars();
173 String result = mEvaluator.getString(currentCharPos, maxChars);
174 int epos = result.indexOf('e');
175 // TODO: Internationalization for decimal point?
176 if (epos > 0 && result.indexOf('.') == -1) {
177 // Gray out exponent if used as position indicator
178 SpannableString formattedResult = new SpannableString(result);
179 formattedResult.setSpan(new ForegroundColorSpan(Color.GRAY),
180 epos, result.length(),
181 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
182 setText(formattedResult);
183 } else {
184 setText(result);
185 }
186 mScrollable = true;
187 }
188
189 @Override
190 public void computeScroll() {
191 if (!mScrollable) return;
192 if (mScroller.computeScrollOffset()) {
193 mCurrentPos = mScroller.getCurrX();
194 if (mCurrentPos != mLastPos) {
195 mLastPos = mCurrentPos;
196 redisplay();
197 }
198 if (!mScroller.isFinished()) {
199 ViewCompat.postInvalidateOnAnimation(this);
200 }
201 }
202 }
203
204}