blob: 75e7b43b402b989648e2f4e9ead5a73ba0fc1425 [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;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080023import android.text.Editable;
Gilles Debunneed0e2172011-08-16 13:50:51 -070024import android.text.InputType;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080025import android.text.TextUtils;
26import android.util.AttributeSet;
Mindy Pereira220cf992011-09-13 14:02:11 -070027import android.util.Log;
Gilles Debunnef57b8b42011-01-27 10:54:07 -080028import android.view.ActionMode;
Mindy Pereira220cf992011-09-13 14:02:11 -070029import android.view.ContextMenu;
Gilles Debunnef57b8b42011-01-27 10:54:07 -080030import android.view.Menu;
31import android.view.MenuItem;
Gilles Debunne79525c32011-01-25 08:25:41 -080032import android.view.MotionEvent;
Mindy Pereira88a84db2011-12-06 13:28:15 -080033import android.view.accessibility.AccessibilityEvent;
34import android.view.accessibility.AccessibilityNodeInfo;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080035import android.widget.EditText;
36import android.widget.Toast;
37
Alan Viverette8bea4942014-03-03 14:25:51 -080038import com.android.calculator2.R;
Mindy Pereira88a84db2011-12-06 13:28:15 -080039
Alan Viverette461992d2014-03-07 13:29:56 -080040import java.util.Collections;
41import java.util.HashMap;
42import java.util.Map;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080043
Alan Viverette461992d2014-03-07 13:29:56 -080044public class CalculatorEditText extends EditText {
Mindy Pereira220cf992011-09-13 14:02:11 -070045 private static final String LOG_TAG = "Calculator2";
46 private static final int CUT = 0;
47 private static final int COPY = 1;
48 private static final int PASTE = 2;
Alan Viverette461992d2014-03-07 13:29:56 -080049
50 private static Map<String, String> sReplacementTable;
51 private static String[] sOperators;
52
Mindy Pereira220cf992011-09-13 14:02:11 -070053 private String[] mMenuItemsStrings;
54
Alan Viverette8bea4942014-03-03 14:25:51 -080055 public CalculatorEditText(Context context) {
56 this(context, null);
57 }
58
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080059 public CalculatorEditText(Context context, AttributeSet attrs) {
60 super(context, attrs);
Alan Viverette461992d2014-03-07 13:29:56 -080061
Gilles Debunnef57b8b42011-01-27 10:54:07 -080062 setCustomSelectionActionModeCallback(new NoTextSelectionMode());
Gilles Debunneed0e2172011-08-16 13:50:51 -070063 setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
Alan Viverette8bea4942014-03-03 14:25:51 -080064 setCursorVisible(false);
Gilles Debunne79525c32011-01-25 08:25:41 -080065 }
66
67 @Override
68 public boolean onTouchEvent(MotionEvent event) {
Alan Viverette461992d2014-03-07 13:29:56 -080069 if (event.getActionMasked() == MotionEvent.ACTION_UP) {
Gilles Debunne79525c32011-01-25 08:25:41 -080070 // Hack to prevent keyboard and insertion handle from showing.
Alan Viverette461992d2014-03-07 13:29:56 -080071 cancelLongPress();
Gilles Debunne79525c32011-01-25 08:25:41 -080072 }
Alan Viverette461992d2014-03-07 13:29:56 -080073
Gilles Debunne79525c32011-01-25 08:25:41 -080074 return super.onTouchEvent(event);
75 }
76
77 @Override
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080078 public boolean performLongClick() {
Mindy Pereira220cf992011-09-13 14:02:11 -070079 showContextMenu();
Alan Viverette461992d2014-03-07 13:29:56 -080080
Gilles Debunnef57b8b42011-01-27 10:54:07 -080081 return true;
82 }
83
Mindy Pereira88a84db2011-12-06 13:28:15 -080084 @Override
85 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
86 super.onInitializeAccessibilityEvent(event);
Alan Viverette461992d2014-03-07 13:29:56 -080087
88 final String mathText = mathParse(getText().toString());
Mindy Pereira88a84db2011-12-06 13:28:15 -080089 // Parse the string into something more "mathematical" sounding.
90 if (!TextUtils.isEmpty(mathText)) {
91 event.getText().clear();
92 event.getText().add(mathText);
93 setContentDescription(mathText);
94 }
95 }
96
97 @Override
98 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
99 super.onInitializeAccessibilityNodeInfo(info);
Alan Viverette461992d2014-03-07 13:29:56 -0800100
Mindy Pereira88a84db2011-12-06 13:28:15 -0800101 info.setText(mathParse(getText().toString()));
102 }
103
104 @Override
105 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
106 // Do nothing.
107 }
108
109 private String mathParse(String plainText) {
110 String parsedText = plainText;
111 if (!TextUtils.isEmpty(parsedText)) {
112 // Initialize replacement table.
113 initializeReplacementTable();
114 for (String operator : sOperators) {
115 if (sReplacementTable.containsKey(operator)) {
116 parsedText = parsedText.replace(operator, sReplacementTable.get(operator));
117 }
118 }
119 }
Alan Viverette461992d2014-03-07 13:29:56 -0800120
Mindy Pereira88a84db2011-12-06 13:28:15 -0800121 return parsedText;
122 }
123
124 private synchronized void initializeReplacementTable() {
125 if (sReplacementTable == null) {
Alan Viverette461992d2014-03-07 13:29:56 -0800126 final Resources res = getContext().getResources();
127 final String[] descs = res.getStringArray(R.array.operatorDescs);
128 final String[] ops = res.getStringArray(R.array.operators);
129 final HashMap<String, String> table = new HashMap<String, String>();
130 final int len = ops.length;
131 for (int i = 0; i < len; i++) {
132 table.put(ops[i], descs[i]);
Mindy Pereira88a84db2011-12-06 13:28:15 -0800133 }
Mindy Pereira88a84db2011-12-06 13:28:15 -0800134
Alan Viverette461992d2014-03-07 13:29:56 -0800135 sOperators = ops;
136 sReplacementTable = Collections.unmodifiableMap(table);
Mindy Pereira220cf992011-09-13 14:02:11 -0700137 }
138 }
139
140 public boolean onTextContextMenuItem(CharSequence title) {
Mindy Pereira220cf992011-09-13 14:02:11 -0700141 if (TextUtils.equals(title, mMenuItemsStrings[CUT])) {
142 cutContent();
Alan Viverette461992d2014-03-07 13:29:56 -0800143 return true;
144 } else if (TextUtils.equals(title, mMenuItemsStrings[COPY])) {
Mindy Pereira220cf992011-09-13 14:02:11 -0700145 copyContent();
Alan Viverette461992d2014-03-07 13:29:56 -0800146 return true;
147 } else if (TextUtils.equals(title, mMenuItemsStrings[PASTE])) {
Mindy Pereira220cf992011-09-13 14:02:11 -0700148 pasteContent();
Alan Viverette461992d2014-03-07 13:29:56 -0800149 return true;
Mindy Pereira220cf992011-09-13 14:02:11 -0700150 }
Alan Viverette461992d2014-03-07 13:29:56 -0800151
152 return false;
Mindy Pereira220cf992011-09-13 14:02:11 -0700153 }
154
155 @Override
156 public void onCreateContextMenu(ContextMenu menu) {
Mindy Pereira220cf992011-09-13 14:02:11 -0700157 if (mMenuItemsStrings == null) {
Alan Viverette461992d2014-03-07 13:29:56 -0800158 final Resources resources = getResources();
Mindy Pereira220cf992011-09-13 14:02:11 -0700159 mMenuItemsStrings = new String[3];
160 mMenuItemsStrings[CUT] = resources.getString(android.R.string.cut);
161 mMenuItemsStrings[COPY] = resources.getString(android.R.string.copy);
162 mMenuItemsStrings[PASTE] = resources.getString(android.R.string.paste);
163 }
Alan Viverette461992d2014-03-07 13:29:56 -0800164
165 final MenuHandler handler = new MenuHandler();
166 final int len = mMenuItemsStrings.length;
167 for (int i = 0; i < len; i++) {
Mindy Pereira220cf992011-09-13 14:02:11 -0700168 menu.add(Menu.NONE, i, i, mMenuItemsStrings[i]).setOnMenuItemClickListener(handler);
169 }
Alan Viverette461992d2014-03-07 13:29:56 -0800170
Mindy Pereira220cf992011-09-13 14:02:11 -0700171 if (getText().length() == 0) {
172 menu.getItem(CUT).setVisible(false);
173 menu.getItem(COPY).setVisible(false);
174 }
Alan Viverette461992d2014-03-07 13:29:56 -0800175
176 final ClipData primaryClip = getPrimaryClip();
Mindy Pereira220cf992011-09-13 14:02:11 -0700177 if (primaryClip == null || primaryClip.getItemCount() == 0
178 || !canPaste(primaryClip.getItemAt(0).coerceToText(getContext()))) {
179 menu.getItem(PASTE).setVisible(false);
180 }
181 }
182
183 private void setPrimaryClip(ClipData clip) {
Alan Viverette461992d2014-03-07 13:29:56 -0800184 final ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
185 Context.CLIPBOARD_SERVICE);
Mindy Pereira220cf992011-09-13 14:02:11 -0700186 clipboard.setPrimaryClip(clip);
187 }
188
Gilles Debunnef57b8b42011-01-27 10:54:07 -0800189 private void copyContent() {
190 final Editable text = getText();
Alan Viverette461992d2014-03-07 13:29:56 -0800191 final int textLength = text.length();
Mindy Pereira220cf992011-09-13 14:02:11 -0700192 setSelection(0, textLength);
Alan Viverette461992d2014-03-07 13:29:56 -0800193 setPrimaryClip(ClipData.newPlainText(null, text));
Mindy Pereira220cf992011-09-13 14:02:11 -0700194 setSelection(textLength);
Alan Viverette461992d2014-03-07 13:29:56 -0800195
196 Toast.makeText(getContext(), R.string.text_copied_toast, Toast.LENGTH_SHORT).show();
Mindy Pereira220cf992011-09-13 14:02:11 -0700197 }
198
199 private void cutContent() {
200 final Editable text = getText();
Alan Viverette461992d2014-03-07 13:29:56 -0800201 final int textLength = text.length();
Mindy Pereira220cf992011-09-13 14:02:11 -0700202 setSelection(0, textLength);
203 setPrimaryClip(ClipData.newPlainText(null, text));
Alan Viverette461992d2014-03-07 13:29:56 -0800204 getText().delete(0, textLength);
Mindy Pereira220cf992011-09-13 14:02:11 -0700205 setSelection(0);
206 }
207
208 private ClipData getPrimaryClip() {
Alan Viverette461992d2014-03-07 13:29:56 -0800209 final ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
Mindy Pereira220cf992011-09-13 14:02:11 -0700210 Context.CLIPBOARD_SERVICE);
211 return clipboard.getPrimaryClip();
212 }
213
214 private void pasteContent() {
Alan Viverette461992d2014-03-07 13:29:56 -0800215 final ClipData clip = getPrimaryClip();
Mindy Pereira220cf992011-09-13 14:02:11 -0700216 if (clip != null) {
Alan Viverette461992d2014-03-07 13:29:56 -0800217 final int len = clip.getItemCount();
218 for (int i = 0; i < len; i++) {
219 final CharSequence paste = clip.getItemAt(i).coerceToText(getContext());
Mindy Pereira220cf992011-09-13 14:02:11 -0700220 if (canPaste(paste)) {
Alan Viverette461992d2014-03-07 13:29:56 -0800221 getText().insert(getSelectionEnd(), paste);
Mindy Pereira220cf992011-09-13 14:02:11 -0700222 }
223 }
224 }
225 }
226
227 private boolean canPaste(CharSequence paste) {
Mindy Pereira220cf992011-09-13 14:02:11 -0700228 try {
229 Float.parseFloat(paste.toString());
Alan Viverette461992d2014-03-07 13:29:56 -0800230 return true;
Mindy Pereira220cf992011-09-13 14:02:11 -0700231 } catch (NumberFormatException e) {
232 Log.e(LOG_TAG, "Error turning string to integer. Ignoring paste.", e);
Alan Viverette461992d2014-03-07 13:29:56 -0800233 return false;
Mindy Pereira220cf992011-09-13 14:02:11 -0700234 }
Gilles Debunnef57b8b42011-01-27 10:54:07 -0800235 }
236
Alan Viverette461992d2014-03-07 13:29:56 -0800237 private class MenuHandler implements MenuItem.OnMenuItemClickListener {
238 @Override
239 public boolean onMenuItemClick(MenuItem item) {
240 return onTextContextMenuItem(item.getTitle());
241 }
242 }
243
244 private class NoTextSelectionMode implements ActionMode.Callback {
Gilles Debunnef57b8b42011-01-27 10:54:07 -0800245 @Override
246 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
247 return false;
248 }
249
250 @Override
251 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
252 copyContent();
Gilles Debunnef57b8b42011-01-27 10:54:07 -0800253 // Prevents the selection action mode on double tap.
254 return false;
255 }
256
257 @Override
Alan Viverette461992d2014-03-07 13:29:56 -0800258 public void onDestroyActionMode(ActionMode mode) {
259 }
Gilles Debunnef57b8b42011-01-27 10:54:07 -0800260
261 @Override
262 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
263 return false;
264 }
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -0800265 }
266}