blob: b40bb1e7ee1dd4727650b3ebfae04bd9e0616aae [file] [log] [blame]
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -08001/*
2 * Copyright (C) 2010 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.content.ClipData;
20import android.content.ClipboardManager;
21import android.content.Context;
Mindy Pereira220cf992011-09-13 14:02:11 -070022import android.content.res.Resources;
Hongwei Wang245925e2014-05-11 14:38:47 -070023import android.graphics.Paint;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080024import android.text.Editable;
Gilles Debunneed0e2172011-08-16 13:50:51 -070025import android.text.InputType;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080026import android.text.TextUtils;
27import android.util.AttributeSet;
Mindy Pereira220cf992011-09-13 14:02:11 -070028import android.util.Log;
Hongwei Wang245925e2014-05-11 14:38:47 -070029import android.util.TypedValue;
Gilles Debunnef57b8b42011-01-27 10:54:07 -080030import android.view.ActionMode;
Mindy Pereira220cf992011-09-13 14:02:11 -070031import android.view.ContextMenu;
Gilles Debunnef57b8b42011-01-27 10:54:07 -080032import android.view.Menu;
33import android.view.MenuItem;
Gilles Debunne79525c32011-01-25 08:25:41 -080034import android.view.MotionEvent;
Mindy Pereira88a84db2011-12-06 13:28:15 -080035import android.view.accessibility.AccessibilityEvent;
36import android.view.accessibility.AccessibilityNodeInfo;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080037import android.widget.EditText;
38import android.widget.Toast;
39
Alan Viverette8bea4942014-03-03 14:25:51 -080040import com.android.calculator2.R;
Mindy Pereira88a84db2011-12-06 13:28:15 -080041
Alan Viverette461992d2014-03-07 13:29:56 -080042import java.util.Collections;
43import java.util.HashMap;
44import java.util.Map;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080045
Alan Viverette461992d2014-03-07 13:29:56 -080046public class CalculatorEditText extends EditText {
Mindy Pereira220cf992011-09-13 14:02:11 -070047 private static final String LOG_TAG = "Calculator2";
48 private static final int CUT = 0;
49 private static final int COPY = 1;
50 private static final int PASTE = 2;
Alan Viverette461992d2014-03-07 13:29:56 -080051
52 private static Map<String, String> sReplacementTable;
53 private static String[] sOperators;
54
Hongwei Wang245925e2014-05-11 14:38:47 -070055 private final int mMaximumTextSize;
56 private final int mMinimumTextSize;
57 private final int mStepTextSize;
58
59 private int mWidthConstraint = -1;
60
Mindy Pereira220cf992011-09-13 14:02:11 -070061 private String[] mMenuItemsStrings;
62
Alan Viverette8bea4942014-03-03 14:25:51 -080063 public CalculatorEditText(Context context) {
64 this(context, null);
65 }
66
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080067 public CalculatorEditText(Context context, AttributeSet attrs) {
68 super(context, attrs);
Alan Viverette461992d2014-03-07 13:29:56 -080069
Gilles Debunnef57b8b42011-01-27 10:54:07 -080070 setCustomSelectionActionModeCallback(new NoTextSelectionMode());
Gilles Debunneed0e2172011-08-16 13:50:51 -070071 setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
Alan Viverette8bea4942014-03-03 14:25:51 -080072 setCursorVisible(false);
Hongwei Wang245925e2014-05-11 14:38:47 -070073
74 final Resources res = getResources();
75 mMaximumTextSize = res.getDimensionPixelSize(R.dimen.display_maximum_text_size);
76 mMinimumTextSize = res.getDimensionPixelSize(R.dimen.display_minimum_text_size);
77 mStepTextSize = res.getDimensionPixelSize(R.dimen.display_step_text_size);
Gilles Debunne79525c32011-01-25 08:25:41 -080078 }
79
80 @Override
81 public boolean onTouchEvent(MotionEvent event) {
Alan Viverette461992d2014-03-07 13:29:56 -080082 if (event.getActionMasked() == MotionEvent.ACTION_UP) {
Gilles Debunne79525c32011-01-25 08:25:41 -080083 // Hack to prevent keyboard and insertion handle from showing.
Alan Viverette461992d2014-03-07 13:29:56 -080084 cancelLongPress();
Gilles Debunne79525c32011-01-25 08:25:41 -080085 }
Alan Viverette461992d2014-03-07 13:29:56 -080086
Gilles Debunne79525c32011-01-25 08:25:41 -080087 return super.onTouchEvent(event);
88 }
89
90 @Override
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080091 public boolean performLongClick() {
Mindy Pereira220cf992011-09-13 14:02:11 -070092 showContextMenu();
Alan Viverette461992d2014-03-07 13:29:56 -080093
Gilles Debunnef57b8b42011-01-27 10:54:07 -080094 return true;
95 }
96
Mindy Pereira88a84db2011-12-06 13:28:15 -080097 @Override
98 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
99 super.onInitializeAccessibilityEvent(event);
Alan Viverette461992d2014-03-07 13:29:56 -0800100
101 final String mathText = mathParse(getText().toString());
Mindy Pereira88a84db2011-12-06 13:28:15 -0800102 // Parse the string into something more "mathematical" sounding.
103 if (!TextUtils.isEmpty(mathText)) {
104 event.getText().clear();
105 event.getText().add(mathText);
106 setContentDescription(mathText);
107 }
108 }
109
110 @Override
111 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
112 super.onInitializeAccessibilityNodeInfo(info);
Alan Viverette461992d2014-03-07 13:29:56 -0800113
Mindy Pereira88a84db2011-12-06 13:28:15 -0800114 info.setText(mathParse(getText().toString()));
115 }
116
117 @Override
118 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
119 // Do nothing.
120 }
121
Hongwei Wang245925e2014-05-11 14:38:47 -0700122 @Override
123 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
124 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
125
126 mWidthConstraint =
127 MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
128 setVariableFontSize();
129 }
130
131 @Override
132 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
133 super.onTextChanged(text, start, lengthBefore, lengthAfter);
134
135 if (TextUtils.isEmpty(text)) {
136 setTextSize(TypedValue.COMPLEX_UNIT_PX, mMaximumTextSize);
137 return;
138 }
139
140 setVariableFontSize();
141 }
142
143 private void setVariableFontSize() {
144 if (mWidthConstraint < 0) {
145 // Not measured, bail early.
146 return;
147 }
148
149 final Paint paint = new Paint();
150 final String measureText = getText().toString();
151 int lastFitTextSize = mMinimumTextSize;
152
153 while (lastFitTextSize < mMaximumTextSize) {
154 final int nextSize = lastFitTextSize + mStepTextSize;
155 paint.setTextSize(nextSize);
156 final float measuredTextWidth = paint.measureText(measureText);
157 if (measuredTextWidth > mWidthConstraint) {
158 break;
159 } else {
160 lastFitTextSize = nextSize;
161 }
162 }
163
164 setTextSize(TypedValue.COMPLEX_UNIT_PX, lastFitTextSize);
165 }
166
Mindy Pereira88a84db2011-12-06 13:28:15 -0800167 private String mathParse(String plainText) {
168 String parsedText = plainText;
169 if (!TextUtils.isEmpty(parsedText)) {
170 // Initialize replacement table.
171 initializeReplacementTable();
172 for (String operator : sOperators) {
173 if (sReplacementTable.containsKey(operator)) {
174 parsedText = parsedText.replace(operator, sReplacementTable.get(operator));
175 }
176 }
177 }
Alan Viverette461992d2014-03-07 13:29:56 -0800178
Mindy Pereira88a84db2011-12-06 13:28:15 -0800179 return parsedText;
180 }
181
182 private synchronized void initializeReplacementTable() {
183 if (sReplacementTable == null) {
Alan Viverette461992d2014-03-07 13:29:56 -0800184 final Resources res = getContext().getResources();
185 final String[] descs = res.getStringArray(R.array.operatorDescs);
186 final String[] ops = res.getStringArray(R.array.operators);
187 final HashMap<String, String> table = new HashMap<String, String>();
188 final int len = ops.length;
189 for (int i = 0; i < len; i++) {
190 table.put(ops[i], descs[i]);
Mindy Pereira88a84db2011-12-06 13:28:15 -0800191 }
Mindy Pereira88a84db2011-12-06 13:28:15 -0800192
Alan Viverette461992d2014-03-07 13:29:56 -0800193 sOperators = ops;
194 sReplacementTable = Collections.unmodifiableMap(table);
Mindy Pereira220cf992011-09-13 14:02:11 -0700195 }
196 }
197
198 public boolean onTextContextMenuItem(CharSequence title) {
Mindy Pereira220cf992011-09-13 14:02:11 -0700199 if (TextUtils.equals(title, mMenuItemsStrings[CUT])) {
200 cutContent();
Alan Viverette461992d2014-03-07 13:29:56 -0800201 return true;
202 } else if (TextUtils.equals(title, mMenuItemsStrings[COPY])) {
Mindy Pereira220cf992011-09-13 14:02:11 -0700203 copyContent();
Alan Viverette461992d2014-03-07 13:29:56 -0800204 return true;
205 } else if (TextUtils.equals(title, mMenuItemsStrings[PASTE])) {
Mindy Pereira220cf992011-09-13 14:02:11 -0700206 pasteContent();
Alan Viverette461992d2014-03-07 13:29:56 -0800207 return true;
Mindy Pereira220cf992011-09-13 14:02:11 -0700208 }
Alan Viverette461992d2014-03-07 13:29:56 -0800209
210 return false;
Mindy Pereira220cf992011-09-13 14:02:11 -0700211 }
212
213 @Override
214 public void onCreateContextMenu(ContextMenu menu) {
Mindy Pereira220cf992011-09-13 14:02:11 -0700215 if (mMenuItemsStrings == null) {
Alan Viverette461992d2014-03-07 13:29:56 -0800216 final Resources resources = getResources();
Mindy Pereira220cf992011-09-13 14:02:11 -0700217 mMenuItemsStrings = new String[3];
218 mMenuItemsStrings[CUT] = resources.getString(android.R.string.cut);
219 mMenuItemsStrings[COPY] = resources.getString(android.R.string.copy);
220 mMenuItemsStrings[PASTE] = resources.getString(android.R.string.paste);
221 }
Alan Viverette461992d2014-03-07 13:29:56 -0800222
223 final MenuHandler handler = new MenuHandler();
224 final int len = mMenuItemsStrings.length;
225 for (int i = 0; i < len; i++) {
Mindy Pereira220cf992011-09-13 14:02:11 -0700226 menu.add(Menu.NONE, i, i, mMenuItemsStrings[i]).setOnMenuItemClickListener(handler);
227 }
Alan Viverette461992d2014-03-07 13:29:56 -0800228
Mindy Pereira220cf992011-09-13 14:02:11 -0700229 if (getText().length() == 0) {
230 menu.getItem(CUT).setVisible(false);
231 menu.getItem(COPY).setVisible(false);
232 }
Alan Viverette461992d2014-03-07 13:29:56 -0800233
234 final ClipData primaryClip = getPrimaryClip();
Mindy Pereira220cf992011-09-13 14:02:11 -0700235 if (primaryClip == null || primaryClip.getItemCount() == 0
236 || !canPaste(primaryClip.getItemAt(0).coerceToText(getContext()))) {
237 menu.getItem(PASTE).setVisible(false);
238 }
239 }
240
241 private void setPrimaryClip(ClipData clip) {
Alan Viverette461992d2014-03-07 13:29:56 -0800242 final ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
243 Context.CLIPBOARD_SERVICE);
Mindy Pereira220cf992011-09-13 14:02:11 -0700244 clipboard.setPrimaryClip(clip);
245 }
246
Gilles Debunnef57b8b42011-01-27 10:54:07 -0800247 private void copyContent() {
248 final Editable text = getText();
Alan Viverette461992d2014-03-07 13:29:56 -0800249 final int textLength = text.length();
Mindy Pereira220cf992011-09-13 14:02:11 -0700250 setSelection(0, textLength);
Alan Viverette461992d2014-03-07 13:29:56 -0800251 setPrimaryClip(ClipData.newPlainText(null, text));
Mindy Pereira220cf992011-09-13 14:02:11 -0700252 setSelection(textLength);
Alan Viverette461992d2014-03-07 13:29:56 -0800253
254 Toast.makeText(getContext(), R.string.text_copied_toast, Toast.LENGTH_SHORT).show();
Mindy Pereira220cf992011-09-13 14:02:11 -0700255 }
256
257 private void cutContent() {
258 final Editable text = getText();
Alan Viverette461992d2014-03-07 13:29:56 -0800259 final int textLength = text.length();
Mindy Pereira220cf992011-09-13 14:02:11 -0700260 setSelection(0, textLength);
261 setPrimaryClip(ClipData.newPlainText(null, text));
Alan Viverette461992d2014-03-07 13:29:56 -0800262 getText().delete(0, textLength);
Mindy Pereira220cf992011-09-13 14:02:11 -0700263 setSelection(0);
264 }
265
266 private ClipData getPrimaryClip() {
Alan Viverette461992d2014-03-07 13:29:56 -0800267 final ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
Mindy Pereira220cf992011-09-13 14:02:11 -0700268 Context.CLIPBOARD_SERVICE);
269 return clipboard.getPrimaryClip();
270 }
271
272 private void pasteContent() {
Alan Viverette461992d2014-03-07 13:29:56 -0800273 final ClipData clip = getPrimaryClip();
Mindy Pereira220cf992011-09-13 14:02:11 -0700274 if (clip != null) {
Alan Viverette461992d2014-03-07 13:29:56 -0800275 final int len = clip.getItemCount();
276 for (int i = 0; i < len; i++) {
277 final CharSequence paste = clip.getItemAt(i).coerceToText(getContext());
Mindy Pereira220cf992011-09-13 14:02:11 -0700278 if (canPaste(paste)) {
Alan Viverette461992d2014-03-07 13:29:56 -0800279 getText().insert(getSelectionEnd(), paste);
Mindy Pereira220cf992011-09-13 14:02:11 -0700280 }
281 }
282 }
283 }
284
285 private boolean canPaste(CharSequence paste) {
Mindy Pereira220cf992011-09-13 14:02:11 -0700286 try {
287 Float.parseFloat(paste.toString());
Alan Viverette461992d2014-03-07 13:29:56 -0800288 return true;
Mindy Pereira220cf992011-09-13 14:02:11 -0700289 } catch (NumberFormatException e) {
290 Log.e(LOG_TAG, "Error turning string to integer. Ignoring paste.", e);
Alan Viverette461992d2014-03-07 13:29:56 -0800291 return false;
Mindy Pereira220cf992011-09-13 14:02:11 -0700292 }
Gilles Debunnef57b8b42011-01-27 10:54:07 -0800293 }
294
Alan Viverette461992d2014-03-07 13:29:56 -0800295 private class MenuHandler implements MenuItem.OnMenuItemClickListener {
296 @Override
297 public boolean onMenuItemClick(MenuItem item) {
298 return onTextContextMenuItem(item.getTitle());
299 }
300 }
301
302 private class NoTextSelectionMode implements ActionMode.Callback {
Gilles Debunnef57b8b42011-01-27 10:54:07 -0800303 @Override
304 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
305 return false;
306 }
307
308 @Override
309 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
310 copyContent();
Gilles Debunnef57b8b42011-01-27 10:54:07 -0800311 // Prevents the selection action mode on double tap.
312 return false;
313 }
314
315 @Override
Alan Viverette461992d2014-03-07 13:29:56 -0800316 public void onDestroyActionMode(ActionMode mode) {
317 }
Gilles Debunnef57b8b42011-01-27 10:54:07 -0800318
319 @Override
320 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
321 return false;
322 }
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -0800323 }
324}