1 /* 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 17 package com.android.calculator2; 18 19 import android.content.ClipData; 20 import android.content.ClipboardManager; 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.text.Editable; 24 import android.text.InputType; 25 import android.text.TextUtils; 26 import android.util.AttributeSet; 27 import android.util.Log; 28 import android.view.ActionMode; 29 import android.view.ContextMenu; 30 import android.view.Menu; 31 import android.view.MenuItem; 32 import android.view.MotionEvent; 33 import android.view.accessibility.AccessibilityEvent; 34 import android.view.accessibility.AccessibilityNodeInfo; 35 import android.widget.EditText; 36 import android.widget.Toast; 37 38 import com.google.common.collect.ImmutableMap; 39 40 public class CalculatorEditText extends EditText { 41 42 private static final String LOG_TAG = "Calculator2"; 43 private static final int CUT = 0; 44 private static final int COPY = 1; 45 private static final int PASTE = 2; 46 private String[] mMenuItemsStrings; 47 private ImmutableMap<String, String> sReplacementTable; 48 private String[] sOperators; 49 50 public CalculatorEditText(Context context, AttributeSet attrs) { 51 super(context, attrs); 52 setCustomSelectionActionModeCallback(new NoTextSelectionMode()); 53 setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); 54 } 55 56 @Override 57 public boolean onTouchEvent(MotionEvent event) { 58 if (event.getActionMasked() == MotionEvent.ACTION_UP) { 59 // Hack to prevent keyboard and insertion handle from showing. 60 cancelLongPress(); 61 } 62 return super.onTouchEvent(event); 63 } 64 65 @Override 66 public boolean performLongClick() { 67 showContextMenu(); 68 return true; 69 } 70 71 @Override 72 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 73 super.onInitializeAccessibilityEvent(event); 74 String mathText = mathParse(getText().toString()); 75 // Parse the string into something more "mathematical" sounding. 76 if (!TextUtils.isEmpty(mathText)) { 77 event.getText().clear(); 78 event.getText().add(mathText); 79 setContentDescription(mathText); 80 } 81 } 82 83 @Override 84 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 85 super.onInitializeAccessibilityNodeInfo(info); 86 info.setText(mathParse(getText().toString())); 87 } 88 89 @Override 90 public void onPopulateAccessibilityEvent(AccessibilityEvent event) { 91 // Do nothing. 92 } 93 94 private String mathParse(String plainText) { 95 String parsedText = plainText; 96 if (!TextUtils.isEmpty(parsedText)) { 97 // Initialize replacement table. 98 initializeReplacementTable(); 99 for (String operator : sOperators) { 100 if (sReplacementTable.containsKey(operator)) { 101 parsedText = parsedText.replace(operator, sReplacementTable.get(operator)); 102 } 103 } 104 } 105 return parsedText; 106 } 107 108 private synchronized void initializeReplacementTable() { 109 if (sReplacementTable == null) { 110 ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); 111 Resources res = getContext().getResources(); 112 sOperators = res.getStringArray(R.array.operators); 113 String[] descs = res.getStringArray(R.array.operatorDescs); 114 int pos = 0; 115 for (String key : sOperators) { 116 builder.put(key, descs[pos]); 117 pos++; 118 } 119 sReplacementTable = builder.build(); 120 } 121 } 122 123 private class MenuHandler implements MenuItem.OnMenuItemClickListener { 124 public boolean onMenuItemClick(MenuItem item) { 125 return onTextContextMenuItem(item.getTitle()); 126 } 127 } 128 129 public boolean onTextContextMenuItem(CharSequence title) { 130 boolean handled = false; 131 if (TextUtils.equals(title, mMenuItemsStrings[CUT])) { 132 cutContent(); 133 handled = true; 134 } else if (TextUtils.equals(title, mMenuItemsStrings[COPY])) { 135 copyContent(); 136 handled = true; 137 } else if (TextUtils.equals(title, mMenuItemsStrings[PASTE])) { 138 pasteContent(); 139 handled = true; 140 } 141 return handled; 142 } 143 144 @Override 145 public void onCreateContextMenu(ContextMenu menu) { 146 MenuHandler handler = new MenuHandler(); 147 if (mMenuItemsStrings == null) { 148 Resources resources = getResources(); 149 mMenuItemsStrings = new String[3]; 150 mMenuItemsStrings[CUT] = resources.getString(android.R.string.cut); 151 mMenuItemsStrings[COPY] = resources.getString(android.R.string.copy); 152 mMenuItemsStrings[PASTE] = resources.getString(android.R.string.paste); 153 } 154 for (int i = 0; i < mMenuItemsStrings.length; i++) { 155 menu.add(Menu.NONE, i, i, mMenuItemsStrings[i]).setOnMenuItemClickListener(handler); 156 } 157 if (getText().length() == 0) { 158 menu.getItem(CUT).setVisible(false); 159 menu.getItem(COPY).setVisible(false); 160 } 161 ClipData primaryClip = getPrimaryClip(); 162 if (primaryClip == null || primaryClip.getItemCount() == 0 163 || !canPaste(primaryClip.getItemAt(0).coerceToText(getContext()))) { 164 menu.getItem(PASTE).setVisible(false); 165 } 166 } 167 168 private void setPrimaryClip(ClipData clip) { 169 ClipboardManager clipboard = (ClipboardManager) getContext(). 170 getSystemService(Context.CLIPBOARD_SERVICE); 171 clipboard.setPrimaryClip(clip); 172 } 173 174 private void copyContent() { 175 final Editable text = getText(); 176 int textLength = text.length(); 177 setSelection(0, textLength); 178 ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 179 Context.CLIPBOARD_SERVICE); 180 clipboard.setPrimaryClip(ClipData.newPlainText(null, text)); 181 Toast.makeText(getContext(), R.string.text_copied_toast, Toast.LENGTH_SHORT).show(); 182 setSelection(textLength); 183 } 184 185 private void cutContent() { 186 final Editable text = getText(); 187 int textLength = text.length(); 188 setSelection(0, textLength); 189 setPrimaryClip(ClipData.newPlainText(null, text)); 190 ((Editable) getText()).delete(0, textLength); 191 setSelection(0); 192 } 193 194 private ClipData getPrimaryClip() { 195 ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 196 Context.CLIPBOARD_SERVICE); 197 return clipboard.getPrimaryClip(); 198 } 199 200 private void pasteContent() { 201 ClipData clip = getPrimaryClip(); 202 if (clip != null) { 203 for (int i = 0; i < clip.getItemCount(); i++) { 204 CharSequence paste = clip.getItemAt(i).coerceToText(getContext()); 205 if (canPaste(paste)) { 206 ((Editable) getText()).insert(getSelectionEnd(), paste); 207 } 208 } 209 } 210 } 211 212 private boolean canPaste(CharSequence paste) { 213 boolean canPaste = true; 214 try { 215 Float.parseFloat(paste.toString()); 216 } catch (NumberFormatException e) { 217 Log.e(LOG_TAG, "Error turning string to integer. Ignoring paste.", e); 218 canPaste = false; 219 } 220 return canPaste; 221 } 222 223 class NoTextSelectionMode implements ActionMode.Callback { 224 @Override 225 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 226 return false; 227 } 228 229 @Override 230 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 231 copyContent(); 232 // Prevents the selection action mode on double tap. 233 return false; 234 } 235 236 @Override 237 public void onDestroyActionMode(ActionMode mode) {} 238 239 @Override 240 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 241 return false; 242 } 243 } 244 } 245