Home | History | Annotate | Download | only in method
      1 /*
      2  * Copyright (C) 2006 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 android.text.method;
     18 
     19 import android.view.KeyEvent;
     20 import android.view.View;
     21 import android.text.*;
     22 import android.text.method.TextKeyListener.Capitalize;
     23 import android.widget.TextView;
     24 
     25 import java.text.BreakIterator;
     26 
     27 /**
     28  * Abstract base class for key listeners.
     29  *
     30  * Provides a basic foundation for entering and editing text.
     31  * Subclasses should override {@link #onKeyDown} and {@link #onKeyUp} to insert
     32  * characters as keys are pressed.
     33  * <p></p>
     34  * As for all implementations of {@link KeyListener}, this class is only concerned
     35  * with hardware keyboards.  Software input methods have no obligation to trigger
     36  * the methods in this class.
     37  */
     38 public abstract class BaseKeyListener extends MetaKeyKeyListener
     39         implements KeyListener {
     40     /* package */ static final Object OLD_SEL_START = new NoCopySpan.Concrete();
     41 
     42     /**
     43      * Performs the action that happens when you press the {@link KeyEvent#KEYCODE_DEL} key in
     44      * a {@link TextView}.  If there is a selection, deletes the selection; otherwise,
     45      * deletes the character before the cursor, if any; ALT+DEL deletes everything on
     46      * the line the cursor is on.
     47      *
     48      * @return true if anything was deleted; false otherwise.
     49      */
     50     public boolean backspace(View view, Editable content, int keyCode, KeyEvent event) {
     51         return backspaceOrForwardDelete(view, content, keyCode, event, false);
     52     }
     53 
     54     /**
     55      * Performs the action that happens when you press the {@link KeyEvent#KEYCODE_FORWARD_DEL}
     56      * key in a {@link TextView}.  If there is a selection, deletes the selection; otherwise,
     57      * deletes the character before the cursor, if any; ALT+FORWARD_DEL deletes everything on
     58      * the line the cursor is on.
     59      *
     60      * @return true if anything was deleted; false otherwise.
     61      */
     62     public boolean forwardDelete(View view, Editable content, int keyCode, KeyEvent event) {
     63         return backspaceOrForwardDelete(view, content, keyCode, event, true);
     64     }
     65 
     66     private boolean backspaceOrForwardDelete(View view, Editable content, int keyCode,
     67             KeyEvent event, boolean isForwardDelete) {
     68         // Ensure the key event does not have modifiers except ALT or SHIFT or CTRL.
     69         if (!KeyEvent.metaStateHasNoModifiers(event.getMetaState()
     70                 & ~(KeyEvent.META_SHIFT_MASK | KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK))) {
     71             return false;
     72         }
     73 
     74         // If there is a current selection, delete it.
     75         if (deleteSelection(view, content)) {
     76             return true;
     77         }
     78 
     79         // MetaKeyKeyListener doesn't track control key state. Need to check the KeyEvent instead.
     80         boolean isCtrlActive = ((event.getMetaState() & KeyEvent.META_CTRL_ON) != 0);
     81         boolean isShiftActive = (getMetaState(content, META_SHIFT_ON, event) == 1);
     82         boolean isAltActive = (getMetaState(content, META_ALT_ON, event) == 1);
     83 
     84         if (isCtrlActive) {
     85             if (isAltActive || isShiftActive) {
     86                 // Ctrl+Alt, Ctrl+Shift, Ctrl+Alt+Shift should not delete any characters.
     87                 return false;
     88             }
     89             return deleteUntilWordBoundary(view, content, isForwardDelete);
     90         }
     91 
     92         // Alt+Backspace or Alt+ForwardDelete deletes the current line, if possible.
     93         if (isAltActive && deleteLine(view, content)) {
     94             return true;
     95         }
     96 
     97         // Delete a character.
     98         final int start = Selection.getSelectionEnd(content);
     99         final int end;
    100         if (isForwardDelete) {
    101             end = TextUtils.getOffsetAfter(content, start);
    102         } else {
    103             end = TextUtils.getOffsetBefore(content, start);
    104         }
    105         if (start != end) {
    106             content.delete(Math.min(start, end), Math.max(start, end));
    107             return true;
    108         }
    109         return false;
    110     }
    111 
    112     private boolean deleteUntilWordBoundary(View view, Editable content, boolean isForwardDelete) {
    113         int currentCursorOffset = Selection.getSelectionStart(content);
    114 
    115         // If there is a selection, do nothing.
    116         if (currentCursorOffset != Selection.getSelectionEnd(content)) {
    117             return false;
    118         }
    119 
    120         // Early exit if there is no contents to delete.
    121         if ((!isForwardDelete && currentCursorOffset == 0) ||
    122             (isForwardDelete && currentCursorOffset == content.length())) {
    123             return false;
    124         }
    125 
    126         WordIterator wordIterator = null;
    127         if (view instanceof TextView) {
    128             wordIterator = ((TextView)view).getWordIterator();
    129         }
    130 
    131         if (wordIterator == null) {
    132             // Default locale is used for WordIterator since the appropriate locale is not clear
    133             // here.
    134             // TODO: Use appropriate locale for WordIterator.
    135             wordIterator = new WordIterator();
    136         }
    137 
    138         int deleteFrom;
    139         int deleteTo;
    140 
    141         if (isForwardDelete) {
    142             deleteFrom = currentCursorOffset;
    143             wordIterator.setCharSequence(content, deleteFrom, content.length());
    144             deleteTo = wordIterator.following(currentCursorOffset);
    145             if (deleteTo == BreakIterator.DONE) {
    146                 deleteTo = content.length();
    147             }
    148         } else {
    149             deleteTo = currentCursorOffset;
    150             wordIterator.setCharSequence(content, 0, deleteTo);
    151             deleteFrom = wordIterator.preceding(currentCursorOffset);
    152             if (deleteFrom == BreakIterator.DONE) {
    153                 deleteFrom = 0;
    154             }
    155         }
    156         content.delete(deleteFrom, deleteTo);
    157         return true;
    158     }
    159 
    160     private boolean deleteSelection(View view, Editable content) {
    161         int selectionStart = Selection.getSelectionStart(content);
    162         int selectionEnd = Selection.getSelectionEnd(content);
    163         if (selectionEnd < selectionStart) {
    164             int temp = selectionEnd;
    165             selectionEnd = selectionStart;
    166             selectionStart = temp;
    167         }
    168         if (selectionStart != selectionEnd) {
    169             content.delete(selectionStart, selectionEnd);
    170             return true;
    171         }
    172         return false;
    173     }
    174 
    175     private boolean deleteLine(View view, Editable content) {
    176         if (view instanceof TextView) {
    177             final Layout layout = ((TextView) view).getLayout();
    178             if (layout != null) {
    179                 final int line = layout.getLineForOffset(Selection.getSelectionStart(content));
    180                 final int start = layout.getLineStart(line);
    181                 final int end = layout.getLineEnd(line);
    182                 if (end != start) {
    183                     content.delete(start, end);
    184                     return true;
    185                 }
    186             }
    187         }
    188         return false;
    189     }
    190 
    191     static int makeTextContentType(Capitalize caps, boolean autoText) {
    192         int contentType = InputType.TYPE_CLASS_TEXT;
    193         switch (caps) {
    194             case CHARACTERS:
    195                 contentType |= InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
    196                 break;
    197             case WORDS:
    198                 contentType |= InputType.TYPE_TEXT_FLAG_CAP_WORDS;
    199                 break;
    200             case SENTENCES:
    201                 contentType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
    202                 break;
    203         }
    204         if (autoText) {
    205             contentType |= InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
    206         }
    207         return contentType;
    208     }
    209 
    210     public boolean onKeyDown(View view, Editable content,
    211                              int keyCode, KeyEvent event) {
    212         boolean handled;
    213         switch (keyCode) {
    214             case KeyEvent.KEYCODE_DEL:
    215                 handled = backspace(view, content, keyCode, event);
    216                 break;
    217             case KeyEvent.KEYCODE_FORWARD_DEL:
    218                 handled = forwardDelete(view, content, keyCode, event);
    219                 break;
    220             default:
    221                 handled = false;
    222                 break;
    223         }
    224 
    225         if (handled) {
    226             adjustMetaAfterKeypress(content);
    227         }
    228 
    229         return super.onKeyDown(view, content, keyCode, event);
    230     }
    231 
    232     /**
    233      * Base implementation handles ACTION_MULTIPLE KEYCODE_UNKNOWN by inserting
    234      * the event's text into the content.
    235      */
    236     public boolean onKeyOther(View view, Editable content, KeyEvent event) {
    237         if (event.getAction() != KeyEvent.ACTION_MULTIPLE
    238                 || event.getKeyCode() != KeyEvent.KEYCODE_UNKNOWN) {
    239             // Not something we are interested in.
    240             return false;
    241         }
    242 
    243         int selectionStart = Selection.getSelectionStart(content);
    244         int selectionEnd = Selection.getSelectionEnd(content);
    245         if (selectionEnd < selectionStart) {
    246             int temp = selectionEnd;
    247             selectionEnd = selectionStart;
    248             selectionStart = temp;
    249         }
    250 
    251         CharSequence text = event.getCharacters();
    252         if (text == null) {
    253             return false;
    254         }
    255 
    256         content.replace(selectionStart, selectionEnd, text);
    257         return true;
    258     }
    259 }
    260 
    261