• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
CalculatorEditText(Context context, AttributeSet attrs)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
onTouchEvent(MotionEvent event)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
performLongClick()66     public boolean performLongClick() {
67         showContextMenu();
68         return true;
69     }
70 
71     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)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
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)84     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
85         super.onInitializeAccessibilityNodeInfo(info);
86         info.setText(mathParse(getText().toString()));
87     }
88 
89     @Override
onPopulateAccessibilityEvent(AccessibilityEvent event)90     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
91         // Do nothing.
92     }
93 
mathParse(String plainText)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 
initializeReplacementTable()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 {
onMenuItemClick(MenuItem item)124         public boolean onMenuItemClick(MenuItem item) {
125             return onTextContextMenuItem(item.getTitle());
126         }
127     }
128 
onTextContextMenuItem(CharSequence title)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
onCreateContextMenu(ContextMenu menu)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 
setPrimaryClip(ClipData clip)168     private void setPrimaryClip(ClipData clip) {
169         ClipboardManager clipboard = (ClipboardManager) getContext().
170                 getSystemService(Context.CLIPBOARD_SERVICE);
171         clipboard.setPrimaryClip(clip);
172     }
173 
copyContent()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 
cutContent()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 
getPrimaryClip()194     private ClipData getPrimaryClip() {
195         ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
196                 Context.CLIPBOARD_SERVICE);
197         return clipboard.getPrimaryClip();
198     }
199 
pasteContent()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 
canPaste(CharSequence paste)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
onActionItemClicked(ActionMode mode, MenuItem item)225         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
226             return false;
227         }
228 
229         @Override
onCreateActionMode(ActionMode mode, Menu menu)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
onDestroyActionMode(ActionMode mode)237         public void onDestroyActionMode(ActionMode mode) {}
238 
239         @Override
onPrepareActionMode(ActionMode mode, Menu menu)240         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
241             return false;
242         }
243     }
244 }
245