blob: 1b16bca43906c2be612933299fa7efb91c8de703 [file] [log] [blame]
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -08001/*
Justin Klaassen4b3af052014-05-27 17:53:10 -07002 * Copyright (C) 2014 The Android Open Source Project
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
Justin Klaassen4b3af052014-05-27 17:53:10 -07008 * http://www.apache.org/licenses/LICENSE-2.0
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -08009 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.calculator2;
18
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070019import android.content.ClipboardManager;
20import android.content.ClipData;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080021import android.content.Context;
Justin Klaassen4b3af052014-05-27 17:53:10 -070022import android.content.res.TypedArray;
Hongwei Wang245925e2014-05-11 14:38:47 -070023import android.graphics.Paint;
Justin Klaassen4b3af052014-05-27 17:53:10 -070024import android.graphics.Paint.FontMetricsInt;
25import android.graphics.Rect;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070026import android.net.Uri;
Justin Klaassenbfc4e4d2014-08-27 10:56:49 -070027import android.os.Parcelable;
Justin Klaassen4b3af052014-05-27 17:53:10 -070028import android.text.method.ScrollingMovementMethod;
29import android.text.TextPaint;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080030import android.util.AttributeSet;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070031import android.util.Log;
Hongwei Wang245925e2014-05-11 14:38:47 -070032import android.util.TypedValue;
Gilles Debunnef57b8b42011-01-27 10:54:07 -080033import android.view.ActionMode;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070034import android.view.GestureDetector;
Gilles Debunnef57b8b42011-01-27 10:54:07 -080035import android.view.Menu;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070036import android.view.MenuInflater;
Gilles Debunnef57b8b42011-01-27 10:54:07 -080037import android.view.MenuItem;
Gilles Debunne79525c32011-01-25 08:25:41 -080038import android.view.MotionEvent;
Hans Boehm76b78152015-04-17 10:50:35 -070039import android.view.View;
Justin Klaassenfed941a2014-06-09 18:42:40 +010040import android.widget.TextView;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080041
Hans Boehm84614952014-11-25 18:46:17 -080042/**
Hans Boehm08e8f322015-04-21 13:18:38 -070043 * TextView adapted for Calculator display.
Hans Boehm84614952014-11-25 18:46:17 -080044 */
45
Hans Boehm08e8f322015-04-21 13:18:38 -070046public class CalculatorText extends TextView implements View.OnLongClickListener{
Alan Viverette461992d2014-03-07 13:29:56 -080047
Hans Boehm1176f232015-05-11 16:26:03 -070048 private ActionMode mActionMode;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070049
50 private final ActionMode.Callback mPasteActionModeCallback =
Justin Klaassenbfc4e4d2014-08-27 10:56:49 -070051 new ActionMode.Callback() {
Gilles Debunnef57b8b42011-01-27 10:54:07 -080052 @Override
53 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070054 switch (item.getItemId()) {
55 case R.id.menu_paste:
56 pasteContent();
57 mode.finish();
58 return true;
59 default:
60 return false;
61 }
Gilles Debunnef57b8b42011-01-27 10:54:07 -080062 }
63
64 @Override
65 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070066 ClipboardManager clipboard =
67 (ClipboardManager) getContext().getSystemService(
68 Context.CLIPBOARD_SERVICE);
69 if (clipboard.hasPrimaryClip()) {
70 MenuInflater inflater = mode.getMenuInflater();
71 inflater.inflate(R.menu.paste, menu);
72 return true;
73 }
Gilles Debunnef57b8b42011-01-27 10:54:07 -080074 // Prevents the selection action mode on double tap.
75 return false;
76 }
77
78 @Override
Alan Viverette461992d2014-03-07 13:29:56 -080079 public void onDestroyActionMode(ActionMode mode) {
Hans Boehm1176f232015-05-11 16:26:03 -070080 mActionMode = null;
Alan Viverette461992d2014-03-07 13:29:56 -080081 }
Gilles Debunnef57b8b42011-01-27 10:54:07 -080082
83 @Override
84 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
85 return false;
86 }
Justin Klaassen4b3af052014-05-27 17:53:10 -070087 };
88
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070089 private PasteListener mPasteListener;
90
91 public void setPasteListener(PasteListener pasteListener) {
92 mPasteListener = pasteListener;
93 }
94
95 private void pasteContent() {
96 ClipboardManager clipboard =
97 (ClipboardManager) getContext().getSystemService(
98 Context.CLIPBOARD_SERVICE);
99 ClipData cd = clipboard.getPrimaryClip();
100 ClipData.Item item = cd.getItemAt(0);
101 // TODO: Should we handle multiple selections?
102 Uri uri = item.getUri();
103 if (uri == null || !mPasteListener.paste(uri)) {
104 mPasteListener.paste(item.coerceToText(getContext()).toString());
105 }
106 }
107
Justin Klaassen4b3af052014-05-27 17:53:10 -0700108 private final float mMaximumTextSize;
109 private final float mMinimumTextSize;
110 private final float mStepTextSize;
111
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700112 // Temporary objects for use in layout methods.
113 private final Paint mTempPaint = new TextPaint();
114 private final Rect mTempRect = new Rect();
115
Justin Klaassen4b3af052014-05-27 17:53:10 -0700116 private int mWidthConstraint = -1;
Justin Klaassenfed941a2014-06-09 18:42:40 +0100117 private OnTextSizeChangeListener mOnTextSizeChangeListener;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700118
Hans Boehm08e8f322015-04-21 13:18:38 -0700119 public CalculatorText(Context context) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700120 this(context, null);
121 }
122
Hans Boehm08e8f322015-04-21 13:18:38 -0700123 public CalculatorText(Context context, AttributeSet attrs) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700124 this(context, attrs, 0);
125 }
126
Hans Boehm08e8f322015-04-21 13:18:38 -0700127 public CalculatorText(Context context, AttributeSet attrs, int defStyle) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700128 super(context, attrs, defStyle);
129
130 final TypedArray a = context.obtainStyledAttributes(
Hans Boehm08e8f322015-04-21 13:18:38 -0700131 attrs, R.styleable.CalculatorText, defStyle, 0);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700132 mMaximumTextSize = a.getDimension(
Hans Boehm08e8f322015-04-21 13:18:38 -0700133 R.styleable.CalculatorText_maxTextSize, getTextSize());
Justin Klaassen4b3af052014-05-27 17:53:10 -0700134 mMinimumTextSize = a.getDimension(
Hans Boehm08e8f322015-04-21 13:18:38 -0700135 R.styleable.CalculatorText_minTextSize, getTextSize());
136 mStepTextSize = a.getDimension(R.styleable.CalculatorText_stepTextSize,
Justin Klaassen4b3af052014-05-27 17:53:10 -0700137 (mMaximumTextSize - mMinimumTextSize) / 3);
138
139 a.recycle();
140
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700141 // Paste ActionMode is triggered explicitly, not through
142 // setCustomSelectionActionModeCallback.
Hans Boehm76b78152015-04-17 10:50:35 -0700143 setOnLongClickListener(this);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700144
Hans Boehm08e8f322015-04-21 13:18:38 -0700145 // Enable scrolling
146 setMovementMethod(ScrollingMovementMethod.getInstance());
147
Justin Klaassen4b3af052014-05-27 17:53:10 -0700148 setTextSize(TypedValue.COMPLEX_UNIT_PX, mMaximumTextSize);
149 setMinHeight(getLineHeight() + getCompoundPaddingBottom() + getCompoundPaddingTop());
150 }
151
152 @Override
Hans Boehm76b78152015-04-17 10:50:35 -0700153 public boolean onLongClick(View v) {
Hans Boehm1176f232015-05-11 16:26:03 -0700154 mActionMode = startActionMode(mPasteActionModeCallback, ActionMode.TYPE_FLOATING);
Hans Boehm76b78152015-04-17 10:50:35 -0700155 return true;
156 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700157
158 @Override
159 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
160 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
161
162 mWidthConstraint =
163 MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
164 setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(getText().toString()));
165 }
166
Hans Boehm84614952014-11-25 18:46:17 -0800167 public int getWidthConstraint() { return mWidthConstraint; }
168
Justin Klaassen4b3af052014-05-27 17:53:10 -0700169 @Override
170 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
171 super.onTextChanged(text, start, lengthBefore, lengthAfter);
Justin Klaassenbfc4e4d2014-08-27 10:56:49 -0700172
173 final int textLength = text.length();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700174 setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(text.toString()));
175 }
176
Justin Klaassenfed941a2014-06-09 18:42:40 +0100177 @Override
178 public void setTextSize(int unit, float size) {
179 final float oldTextSize = getTextSize();
180 super.setTextSize(unit, size);
181
182 if (mOnTextSizeChangeListener != null && getTextSize() != oldTextSize) {
183 mOnTextSizeChangeListener.onTextSizeChanged(this, oldTextSize);
184 }
185 }
186
187 public void setOnTextSizeChangeListener(OnTextSizeChangeListener listener) {
188 mOnTextSizeChangeListener = listener;
189 }
190
Justin Klaassen4b3af052014-05-27 17:53:10 -0700191 public float getVariableTextSize(String text) {
192 if (mWidthConstraint < 0 || mMaximumTextSize <= mMinimumTextSize) {
193 // Not measured, bail early.
194 return getTextSize();
195 }
196
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700197 // Capture current paint state.
198 mTempPaint.set(getPaint());
199
200 // Step through increasing text sizes until the text would no longer fit.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700201 float lastFitTextSize = mMinimumTextSize;
202 while (lastFitTextSize < mMaximumTextSize) {
203 final float nextSize = Math.min(lastFitTextSize + mStepTextSize, mMaximumTextSize);
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700204 mTempPaint.setTextSize(nextSize);
205 if (mTempPaint.measureText(text) > mWidthConstraint) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700206 break;
207 } else {
208 lastFitTextSize = nextSize;
209 }
210 }
211
212 return lastFitTextSize;
213 }
214
215 @Override
216 public int getCompoundPaddingTop() {
217 // Measure the top padding from the capital letter height of the text instead of the top,
218 // but don't remove more than the available top padding otherwise clipping may occur.
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700219 getPaint().getTextBounds("H", 0, 1, mTempRect);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700220
221 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700222 final int paddingOffset = -(fontMetrics.ascent + mTempRect.height());
Justin Klaassen4b3af052014-05-27 17:53:10 -0700223 return super.getCompoundPaddingTop() - Math.min(getPaddingTop(), paddingOffset);
224 }
225
226 @Override
227 public int getCompoundPaddingBottom() {
228 // Measure the bottom padding from the baseline of the text instead of the bottom, but don't
229 // remove more than the available bottom padding otherwise clipping may occur.
230 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
231 return super.getCompoundPaddingBottom() - Math.min(getPaddingBottom(), fontMetrics.descent);
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -0800232 }
Justin Klaassenfed941a2014-06-09 18:42:40 +0100233
Hans Boehm1176f232015-05-11 16:26:03 -0700234 public boolean stopActionMode() {
235 if (mActionMode != null) {
236 mActionMode.finish();
237 return true;
238 }
239 return false;
240 }
241
Justin Klaassenfed941a2014-06-09 18:42:40 +0100242 public interface OnTextSizeChangeListener {
243 void onTextSizeChanged(TextView textView, float oldSize);
244 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700245
246 public interface PasteListener {
247 void paste(String s);
248 boolean paste(Uri u);
249 }
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -0800250}