blob: b916a2607683162a1b41d9a29ab4682a29b5e0e0 [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;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080040import android.widget.EditText;
Justin Klaassenfed941a2014-06-09 18:42:40 +010041import android.widget.TextView;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080042
Hans Boehm84614952014-11-25 18:46:17 -080043/**
44 * EditText adapted for Calculator display.
45 */
46
Hans Boehm76b78152015-04-17 10:50:35 -070047public class CalculatorEditText extends EditText implements View.OnLongClickListener{
Alan Viverette461992d2014-03-07 13:29:56 -080048
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) {
80 }
Gilles Debunnef57b8b42011-01-27 10:54:07 -080081
82 @Override
83 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
84 return false;
85 }
Justin Klaassen4b3af052014-05-27 17:53:10 -070086 };
87
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070088 private PasteListener mPasteListener;
89
90 public void setPasteListener(PasteListener pasteListener) {
91 mPasteListener = pasteListener;
92 }
93
94 private void pasteContent() {
95 ClipboardManager clipboard =
96 (ClipboardManager) getContext().getSystemService(
97 Context.CLIPBOARD_SERVICE);
98 ClipData cd = clipboard.getPrimaryClip();
99 ClipData.Item item = cd.getItemAt(0);
100 // TODO: Should we handle multiple selections?
101 Uri uri = item.getUri();
102 if (uri == null || !mPasteListener.paste(uri)) {
103 mPasteListener.paste(item.coerceToText(getContext()).toString());
104 }
105 }
106
Justin Klaassen4b3af052014-05-27 17:53:10 -0700107 private final float mMaximumTextSize;
108 private final float mMinimumTextSize;
109 private final float mStepTextSize;
110
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700111 // Temporary objects for use in layout methods.
112 private final Paint mTempPaint = new TextPaint();
113 private final Rect mTempRect = new Rect();
114
Justin Klaassen4b3af052014-05-27 17:53:10 -0700115 private int mWidthConstraint = -1;
Justin Klaassenfed941a2014-06-09 18:42:40 +0100116 private OnTextSizeChangeListener mOnTextSizeChangeListener;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700117
118 public CalculatorEditText(Context context) {
119 this(context, null);
120 }
121
122 public CalculatorEditText(Context context, AttributeSet attrs) {
123 this(context, attrs, 0);
124 }
125
126 public CalculatorEditText(Context context, AttributeSet attrs, int defStyle) {
127 super(context, attrs, defStyle);
128
129 final TypedArray a = context.obtainStyledAttributes(
130 attrs, R.styleable.CalculatorEditText, defStyle, 0);
131 mMaximumTextSize = a.getDimension(
132 R.styleable.CalculatorEditText_maxTextSize, getTextSize());
133 mMinimumTextSize = a.getDimension(
134 R.styleable.CalculatorEditText_minTextSize, getTextSize());
135 mStepTextSize = a.getDimension(R.styleable.CalculatorEditText_stepTextSize,
136 (mMaximumTextSize - mMinimumTextSize) / 3);
137
138 a.recycle();
139
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700140 // Paste ActionMode is triggered explicitly, not through
141 // setCustomSelectionActionModeCallback.
Hans Boehm76b78152015-04-17 10:50:35 -0700142 setOnLongClickListener(this);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700143
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700144 if (isFocusable()) {
145 setMovementMethod(ScrollingMovementMethod.getInstance());
146 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700147 setTextSize(TypedValue.COMPLEX_UNIT_PX, mMaximumTextSize);
148 setMinHeight(getLineHeight() + getCompoundPaddingBottom() + getCompoundPaddingTop());
149 }
150
151 @Override
Hans Boehm76b78152015-04-17 10:50:35 -0700152 public boolean onLongClick(View v) {
153 startActionMode(mPasteActionModeCallback);
154 return true;
155 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700156
157 @Override
158 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
159 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
160
161 mWidthConstraint =
162 MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
163 setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(getText().toString()));
164 }
165
Hans Boehm84614952014-11-25 18:46:17 -0800166 public int getWidthConstraint() { return mWidthConstraint; }
167
Justin Klaassen4b3af052014-05-27 17:53:10 -0700168 @Override
Justin Klaassenbfc4e4d2014-08-27 10:56:49 -0700169 public Parcelable onSaveInstanceState() {
170 super.onSaveInstanceState();
171
172 // EditText will freeze any text with a selection regardless of getFreezesText() ->
173 // return null to prevent any state from being preserved at the instance level.
174 return null;
175 }
176
177 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700178 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
179 super.onTextChanged(text, start, lengthBefore, lengthAfter);
Justin Klaassenbfc4e4d2014-08-27 10:56:49 -0700180
181 final int textLength = text.length();
182 if (getSelectionStart() != textLength || getSelectionEnd() != textLength) {
183 // Pin the selection to the end of the current text.
184 setSelection(textLength);
185 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700186 setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(text.toString()));
187 }
188
Justin Klaassenfed941a2014-06-09 18:42:40 +0100189 @Override
190 public void setTextSize(int unit, float size) {
191 final float oldTextSize = getTextSize();
192 super.setTextSize(unit, size);
193
194 if (mOnTextSizeChangeListener != null && getTextSize() != oldTextSize) {
195 mOnTextSizeChangeListener.onTextSizeChanged(this, oldTextSize);
196 }
197 }
198
199 public void setOnTextSizeChangeListener(OnTextSizeChangeListener listener) {
200 mOnTextSizeChangeListener = listener;
201 }
202
Justin Klaassen4b3af052014-05-27 17:53:10 -0700203 public float getVariableTextSize(String text) {
204 if (mWidthConstraint < 0 || mMaximumTextSize <= mMinimumTextSize) {
205 // Not measured, bail early.
206 return getTextSize();
207 }
208
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700209 // Capture current paint state.
210 mTempPaint.set(getPaint());
211
212 // Step through increasing text sizes until the text would no longer fit.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700213 float lastFitTextSize = mMinimumTextSize;
214 while (lastFitTextSize < mMaximumTextSize) {
215 final float nextSize = Math.min(lastFitTextSize + mStepTextSize, mMaximumTextSize);
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700216 mTempPaint.setTextSize(nextSize);
217 if (mTempPaint.measureText(text) > mWidthConstraint) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700218 break;
219 } else {
220 lastFitTextSize = nextSize;
221 }
222 }
223
224 return lastFitTextSize;
225 }
226
227 @Override
228 public int getCompoundPaddingTop() {
229 // Measure the top padding from the capital letter height of the text instead of the top,
230 // but don't remove more than the available top padding otherwise clipping may occur.
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700231 getPaint().getTextBounds("H", 0, 1, mTempRect);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700232
233 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700234 final int paddingOffset = -(fontMetrics.ascent + mTempRect.height());
Justin Klaassen4b3af052014-05-27 17:53:10 -0700235 return super.getCompoundPaddingTop() - Math.min(getPaddingTop(), paddingOffset);
236 }
237
238 @Override
239 public int getCompoundPaddingBottom() {
240 // Measure the bottom padding from the baseline of the text instead of the bottom, but don't
241 // remove more than the available bottom padding otherwise clipping may occur.
242 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
243 return super.getCompoundPaddingBottom() - Math.min(getPaddingBottom(), fontMetrics.descent);
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -0800244 }
Justin Klaassenfed941a2014-06-09 18:42:40 +0100245
246 public interface OnTextSizeChangeListener {
247 void onTextSizeChanged(TextView textView, float oldSize);
248 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700249
250 public interface PasteListener {
251 void paste(String s);
252 boolean paste(Uri u);
253 }
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -0800254}