blob: b0bbb7ca4718bd8f91aef623942def3abb0075a1 [file] [log] [blame]
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -08001/*
Justin Klaassen44595162015-05-28 17:55:20 -07002 * Copyright (C) 2015 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.ClipData;
Justin Klaassen44595162015-05-28 17:55:20 -070020import android.content.ClipboardManager;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080021import android.content.Context;
Justin Klaassen4b3af052014-05-27 17:53:10 -070022import android.content.res.TypedArray;
Hans Boehm7f83e362015-06-10 15:41:04 -070023import android.graphics.Rect;
Justin Klaassenfc5ac822015-06-18 13:15:17 -070024import android.text.Layout;
Justin Klaassen4b3af052014-05-27 17:53:10 -070025import android.text.TextPaint;
Justin Klaassen44595162015-05-28 17:55:20 -070026import android.text.method.ScrollingMovementMethod;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080027import android.util.AttributeSet;
Hongwei Wang245925e2014-05-11 14:38:47 -070028import android.util.TypedValue;
Gilles Debunnef57b8b42011-01-27 10:54:07 -080029import android.view.ActionMode;
30import android.view.Menu;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070031import android.view.MenuInflater;
Gilles Debunnef57b8b42011-01-27 10:54:07 -080032import android.view.MenuItem;
Hans Boehm76b78152015-04-17 10:50:35 -070033import android.view.View;
Justin Klaassenfed941a2014-06-09 18:42:40 +010034import android.widget.TextView;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080035
Hans Boehm84614952014-11-25 18:46:17 -080036/**
Hans Boehm08e8f322015-04-21 13:18:38 -070037 * TextView adapted for Calculator display.
Hans Boehm84614952014-11-25 18:46:17 -080038 */
Justin Klaassen44595162015-05-28 17:55:20 -070039public class CalculatorText extends AlignedTextView implements View.OnLongClickListener {
Alan Viverette461992d2014-03-07 13:29:56 -080040
Hans Boehm7f83e362015-06-10 15:41:04 -070041 private final ActionMode.Callback2 mPasteActionModeCallback = new ActionMode.Callback2() {
42
Gilles Debunnef57b8b42011-01-27 10:54:07 -080043 @Override
44 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Justin Klaassenfc5ac822015-06-18 13:15:17 -070045 if (item.getItemId() == R.id.menu_paste) {
46 paste();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070047 mode.finish();
48 return true;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070049 }
Justin Klaassenfc5ac822015-06-18 13:15:17 -070050 return false;
Gilles Debunnef57b8b42011-01-27 10:54:07 -080051 }
52
53 @Override
54 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
Justin Klaassenfc5ac822015-06-18 13:15:17 -070055 final ClipboardManager clipboard = (ClipboardManager) getContext()
56 .getSystemService(Context.CLIPBOARD_SERVICE);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070057 if (clipboard.hasPrimaryClip()) {
Hans Boehm7f83e362015-06-10 15:41:04 -070058 bringPointIntoView(length());
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070059 MenuInflater inflater = mode.getMenuInflater();
60 inflater.inflate(R.menu.paste, menu);
61 return true;
62 }
Gilles Debunnef57b8b42011-01-27 10:54:07 -080063 // Prevents the selection action mode on double tap.
64 return false;
65 }
66
67 @Override
Gilles Debunnef57b8b42011-01-27 10:54:07 -080068 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
69 return false;
70 }
Justin Klaassenfc5ac822015-06-18 13:15:17 -070071
72 @Override
73 public void onDestroyActionMode(ActionMode mode) {
74 mActionMode = null;
75 }
Hans Boehm7f83e362015-06-10 15:41:04 -070076
77 @Override
78 public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
79 super.onGetContentRect(mode, view, outRect);
80 outRect.top += getTotalPaddingTop();
81 outRect.right -= getTotalPaddingRight();
82 outRect.bottom -= getTotalPaddingBottom();
83 // Encourage menu positioning towards the right, possibly over formula.
84 outRect.left = outRect.right;
85 }
Justin Klaassen4b3af052014-05-27 17:53:10 -070086 };
87
Justin Klaassenfc5ac822015-06-18 13:15:17 -070088 // Temporary paint for use in layout methods.
89 private final TextPaint mTempPaint = new TextPaint();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070090
Justin Klaassen4b3af052014-05-27 17:53:10 -070091 private final float mMaximumTextSize;
92 private final float mMinimumTextSize;
93 private final float mStepTextSize;
94
95 private int mWidthConstraint = -1;
Justin Klaassenfc5ac822015-06-18 13:15:17 -070096
97 private ActionMode mActionMode;
98
99 private OnPasteListener mOnPasteListener;
Justin Klaassenfed941a2014-06-09 18:42:40 +0100100 private OnTextSizeChangeListener mOnTextSizeChangeListener;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700101
Hans Boehm08e8f322015-04-21 13:18:38 -0700102 public CalculatorText(Context context) {
Justin Klaassenfc5ac822015-06-18 13:15:17 -0700103 this(context, null /* attrs */);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700104 }
105
Hans Boehm08e8f322015-04-21 13:18:38 -0700106 public CalculatorText(Context context, AttributeSet attrs) {
Justin Klaassenfc5ac822015-06-18 13:15:17 -0700107 this(context, attrs, 0 /* defStyleAttr */);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700108 }
109
Justin Klaassenfc5ac822015-06-18 13:15:17 -0700110 public CalculatorText(Context context, AttributeSet attrs, int defStyleAttr) {
111 super(context, attrs, defStyleAttr);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700112
113 final TypedArray a = context.obtainStyledAttributes(
Justin Klaassenfc5ac822015-06-18 13:15:17 -0700114 attrs, R.styleable.CalculatorText, defStyleAttr, 0);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700115 mMaximumTextSize = a.getDimension(
Hans Boehm08e8f322015-04-21 13:18:38 -0700116 R.styleable.CalculatorText_maxTextSize, getTextSize());
Justin Klaassen4b3af052014-05-27 17:53:10 -0700117 mMinimumTextSize = a.getDimension(
Hans Boehm08e8f322015-04-21 13:18:38 -0700118 R.styleable.CalculatorText_minTextSize, getTextSize());
119 mStepTextSize = a.getDimension(R.styleable.CalculatorText_stepTextSize,
Justin Klaassen4b3af052014-05-27 17:53:10 -0700120 (mMaximumTextSize - mMinimumTextSize) / 3);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700121 a.recycle();
122
Justin Klaassenfc5ac822015-06-18 13:15:17 -0700123 // Allow scrolling by default.
Hans Boehm08e8f322015-04-21 13:18:38 -0700124 setMovementMethod(ScrollingMovementMethod.getInstance());
125
Justin Klaassenfc5ac822015-06-18 13:15:17 -0700126 // Reset the clickable flag, which is added when specifying a movement method.
127 setClickable(false);
128
129 // Add a long click to start the ActionMode manually.
130 setOnLongClickListener(this);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700131 }
132
133 @Override
Hans Boehm76b78152015-04-17 10:50:35 -0700134 public boolean onLongClick(View v) {
Hans Boehm1176f232015-05-11 16:26:03 -0700135 mActionMode = startActionMode(mPasteActionModeCallback, ActionMode.TYPE_FLOATING);
Hans Boehm76b78152015-04-17 10:50:35 -0700136 return true;
137 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700138
139 @Override
140 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Justin Klaassen44595162015-05-28 17:55:20 -0700141 // Prevent shrinking/resizing with our variable textSize.
142 if (!isLaidOut()) {
Justin Klaassenfc5ac822015-06-18 13:15:17 -0700143 setTextSize(TypedValue.COMPLEX_UNIT_PX, mMaximumTextSize);
Justin Klaassen44595162015-05-28 17:55:20 -0700144 setMinHeight(getLineHeight() + getCompoundPaddingBottom() + getCompoundPaddingTop());
145 }
146
Justin Klaassenfc5ac822015-06-18 13:15:17 -0700147 // Re-calculate our textSize based on new width.
148 final int width = MeasureSpec.getSize(widthMeasureSpec)
Justin Klaassen44595162015-05-28 17:55:20 -0700149 - getPaddingLeft() - getPaddingRight();
Justin Klaassenfc5ac822015-06-18 13:15:17 -0700150 if (mWidthConstraint != width) {
151 mWidthConstraint = width;
152 setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(getText()));
153 }
154
155 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700156 }
157
Hans Boehm84614952014-11-25 18:46:17 -0800158 public int getWidthConstraint() { return mWidthConstraint; }
159
Justin Klaassen4b3af052014-05-27 17:53:10 -0700160 @Override
161 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
162 super.onTextChanged(text, start, lengthBefore, lengthAfter);
Justin Klaassenbfc4e4d2014-08-27 10:56:49 -0700163
Justin Klaassen4b3af052014-05-27 17:53:10 -0700164 setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(text.toString()));
165 }
166
Justin Klaassenfed941a2014-06-09 18:42:40 +0100167 @Override
168 public void setTextSize(int unit, float size) {
169 final float oldTextSize = getTextSize();
170 super.setTextSize(unit, size);
171
172 if (mOnTextSizeChangeListener != null && getTextSize() != oldTextSize) {
173 mOnTextSizeChangeListener.onTextSizeChanged(this, oldTextSize);
174 }
175 }
176
Justin Klaassen44595162015-05-28 17:55:20 -0700177 public float getMinimumTextSize() {
178 return mMinimumTextSize;
179 }
180
181 public float getMaximumTextSize() {
182 return mMaximumTextSize;
183 }
184
Justin Klaassenfc5ac822015-06-18 13:15:17 -0700185 public float getVariableTextSize(CharSequence text) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700186 if (mWidthConstraint < 0 || mMaximumTextSize <= mMinimumTextSize) {
187 // Not measured, bail early.
188 return getTextSize();
189 }
190
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700191 // Capture current paint state.
192 mTempPaint.set(getPaint());
193
194 // Step through increasing text sizes until the text would no longer fit.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700195 float lastFitTextSize = mMinimumTextSize;
196 while (lastFitTextSize < mMaximumTextSize) {
Justin Klaassenfc5ac822015-06-18 13:15:17 -0700197 mTempPaint.setTextSize(Math.min(lastFitTextSize + mStepTextSize, mMaximumTextSize));
198 if (Layout.getDesiredWidth(text, mTempPaint) > mWidthConstraint) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700199 break;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700200 }
Justin Klaassenfc5ac822015-06-18 13:15:17 -0700201 lastFitTextSize = mTempPaint.getTextSize();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700202 }
203
204 return lastFitTextSize;
205 }
206
Hans Boehm1176f232015-05-11 16:26:03 -0700207 public boolean stopActionMode() {
208 if (mActionMode != null) {
209 mActionMode.finish();
210 return true;
211 }
212 return false;
213 }
214
Justin Klaassenfc5ac822015-06-18 13:15:17 -0700215 public void setOnTextSizeChangeListener(OnTextSizeChangeListener listener) {
216 mOnTextSizeChangeListener = listener;
217 }
218
219 public void setOnPasteListener(OnPasteListener listener) {
220 mOnPasteListener = listener;
221 }
222
223 private void paste() {
224 final ClipboardManager clipboard = (ClipboardManager) getContext()
225 .getSystemService(Context.CLIPBOARD_SERVICE);
226 final ClipData primaryClip = clipboard.getPrimaryClip();
227 if (primaryClip != null && mOnPasteListener != null) {
228 mOnPasteListener.onPaste(primaryClip);
229 }
230 }
231
Justin Klaassenfed941a2014-06-09 18:42:40 +0100232 public interface OnTextSizeChangeListener {
233 void onTextSizeChanged(TextView textView, float oldSize);
234 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700235
Justin Klaassenfc5ac822015-06-18 13:15:17 -0700236 public interface OnPasteListener {
237 boolean onPaste(ClipData clip);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700238 }
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -0800239}