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 */ backspace(View view, Editable content, int keyCode, KeyEvent event)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 */ forwardDelete(View view, Editable content, int keyCode, KeyEvent event)62 public boolean forwardDelete(View view, Editable content, int keyCode, KeyEvent event) { 63 return backspaceOrForwardDelete(view, content, keyCode, event, true); 64 } 65 backspaceOrForwardDelete(View view, Editable content, int keyCode, KeyEvent event, boolean isForwardDelete)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 deleteUntilWordBoundary(View view, Editable content, boolean isForwardDelete)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 deleteSelection(View view, Editable content)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 deleteLine(View view, Editable content)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 makeTextContentType(Capitalize caps, boolean autoText)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 onKeyDown(View view, Editable content, int keyCode, KeyEvent event)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 */ onKeyOther(View view, Editable content, KeyEvent event)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