Home | History | Annotate | Download | only in calculator2
      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