blob: fe7571f62176ef424ec8ee454bd0bae356add349 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.text.method;
18
19import android.view.KeyEvent;
20import android.view.View;
21import android.text.*;
22import android.text.method.TextKeyListener.Capitalize;
23import android.widget.TextView;
24
Seigo Nonakaf2b233d2015-04-07 21:02:13 +090025import java.text.BreakIterator;
26
Jeff Brown6b53e8d2010-11-10 16:03:06 -080027/**
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.
Jean Chalard405bc512012-05-29 19:12:34 +090033 * <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.
Jeff Brown6b53e8d2010-11-10 16:03:06 -080037 */
38public abstract class BaseKeyListener extends MetaKeyKeyListener
39 implements KeyListener {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040 /* package */ static final Object OLD_SEL_START = new NoCopySpan.Concrete();
41
42 /**
Jeff Brown14d0ca12010-12-21 16:06:44 -080043 * 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.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047 *
Jeff Brown6b53e8d2010-11-10 16:03:06 -080048 * @return true if anything was deleted; false otherwise.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049 */
Jeff Brown14d0ca12010-12-21 16:06:44 -080050 public boolean backspace(View view, Editable content, int keyCode, KeyEvent event) {
Jeff Brownd1724712011-02-26 14:32:13 -080051 return backspaceOrForwardDelete(view, content, keyCode, event, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052 }
53
Jeff Brown14d0ca12010-12-21 16:06:44 -080054 /**
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) {
Jeff Brownd1724712011-02-26 14:32:13 -080063 return backspaceOrForwardDelete(view, content, keyCode, event, true);
64 }
65
66 private boolean backspaceOrForwardDelete(View view, Editable content, int keyCode,
67 KeyEvent event, boolean isForwardDelete) {
Seigo Nonakaf2b233d2015-04-07 21:02:13 +090068 // Ensure the key event does not have modifiers except ALT or SHIFT or CTRL.
Jeff Brownd1724712011-02-26 14:32:13 -080069 if (!KeyEvent.metaStateHasNoModifiers(event.getMetaState()
Seigo Nonakaf2b233d2015-04-07 21:02:13 +090070 & ~(KeyEvent.META_SHIFT_MASK | KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071 return false;
72 }
73
Jeff Brownd1724712011-02-26 14:32:13 -080074 // If there is a current selection, delete it.
Jeff Brown14d0ca12010-12-21 16:06:44 -080075 if (deleteSelection(view, content)) {
76 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077 }
78
Seigo Nonakaf2b233d2015-04-07 21:02:13 +090079 // 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;
Jeff Brownd1724712011-02-26 14:32:13 -080088 }
Seigo Nonakaf2b233d2015-04-07 21:02:13 +090089 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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095 }
96
Jeff Brownd1724712011-02-26 14:32:13 -080097 // Delete a character.
Jeff Brown14d0ca12010-12-21 16:06:44 -080098 final int start = Selection.getSelectionEnd(content);
Jeff Brownd1724712011-02-26 14:32:13 -080099 final int end;
Michael Wright375f3152015-04-15 12:30:31 +0100100 if (isForwardDelete) {
Jeff Brownd1724712011-02-26 14:32:13 -0800101 end = TextUtils.getOffsetAfter(content, start);
102 } else {
103 end = TextUtils.getOffsetBefore(content, start);
104 }
Jeff Brown14d0ca12010-12-21 16:06:44 -0800105 if (start != end) {
106 content.delete(Math.min(start, end), Math.max(start, end));
107 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108 }
Jeff Brown14d0ca12010-12-21 16:06:44 -0800109 return false;
110 }
111
Seigo Nonakaf2b233d2015-04-07 21:02:13 +0900112 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
Jeff Brown14d0ca12010-12-21 16:06:44 -0800160 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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800189 }
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 }
Jeff Brown14d0ca12010-12-21 16:06:44 -0800209
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210 public boolean onKeyDown(View view, Editable content,
211 int keyCode, KeyEvent event) {
Jeff Brown14d0ca12010-12-21 16:06:44 -0800212 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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800223 }
Jeff Brown14d0ca12010-12-21 16:06:44 -0800224
225 if (handled) {
226 adjustMetaAfterKeypress(content);
227 }
228
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800229 return super.onKeyDown(view, content, keyCode, event);
230 }
Jeff Brown14d0ca12010-12-21 16:06:44 -0800231
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 /**
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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800242
Jeff Brown14d0ca12010-12-21 16:06:44 -0800243 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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800249 }
250
251 CharSequence text = event.getCharacters();
252 if (text == null) {
253 return false;
254 }
Jeff Brown14d0ca12010-12-21 16:06:44 -0800255
256 content.replace(selectionStart, selectionEnd, text);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800257 return true;
258 }
259}
260