blob: ba6f1d4ba07a1462547da2ca17573730b4d16883 [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
Jeff Brown67b6ab72010-12-17 18:33:02 -080019import android.graphics.Rect;
Gilles Debunneb0d6ba12010-08-17 20:01:42 -070020import android.text.Layout;
21import android.text.Selection;
22import android.text.Spannable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.view.KeyEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.view.MotionEvent;
Gilles Debunneb0d6ba12010-08-17 20:01:42 -070025import android.view.View;
26import android.widget.TextView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027
Jeff Brown67b6ab72010-12-17 18:33:02 -080028/**
29 * A movement method that provides cursor movement and selection.
30 * Supports displaying the context menu on DPad Center.
31 */
32public class ArrowKeyMovementMethod extends BaseMovementMethod implements MovementMethod {
33 private static boolean isSelecting(Spannable buffer) {
Jeff Brown497a92c2010-09-12 17:55:08 -070034 return ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SHIFT_ON) == 1) ||
Gilles Debunneb0d6ba12010-08-17 20:01:42 -070035 (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0));
36 }
37
Gilles Debunne287d6c62011-10-05 18:22:11 -070038 private static int getCurrentLineTop(Spannable buffer, Layout layout) {
Jeff Brown67b6ab72010-12-17 18:33:02 -080039 return layout.getLineTop(layout.getLineForOffset(Selection.getSelectionEnd(buffer)));
Gilles Debunneb0d6ba12010-08-17 20:01:42 -070040 }
41
Gilles Debunne287d6c62011-10-05 18:22:11 -070042 private static int getPageHeight(TextView widget) {
Jeff Brown67b6ab72010-12-17 18:33:02 -080043 // This calculation does not take into account the view transformations that
44 // may have been applied to the child or its containers. In case of scaling or
45 // rotation, the calculated page height may be incorrect.
46 final Rect rect = new Rect();
47 return widget.getGlobalVisibleRect(rect) ? rect.height() : 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048 }
49
Jeff Brown67b6ab72010-12-17 18:33:02 -080050 @Override
51 protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode,
52 int movementMetaState, KeyEvent event) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053 switch (keyCode) {
Jeff Brown67b6ab72010-12-17 18:33:02 -080054 case KeyEvent.KEYCODE_DPAD_CENTER:
55 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
56 if (event.getAction() == KeyEvent.ACTION_DOWN
57 && event.getRepeatCount() == 0
58 && MetaKeyKeyListener.getMetaState(buffer,
Jean Chalard8a1597b2013-03-04 18:45:12 -080059 MetaKeyKeyListener.META_SELECTING, event) != 0) {
Jeff Brown67b6ab72010-12-17 18:33:02 -080060 return widget.showContextMenu();
61 }
62 }
63 break;
64 }
65 return super.handleMovementKey(widget, buffer, keyCode, movementMetaState, event);
66 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067
Jeff Brown67b6ab72010-12-17 18:33:02 -080068 @Override
69 protected boolean left(TextView widget, Spannable buffer) {
70 final Layout layout = widget.getLayout();
71 if (isSelecting(buffer)) {
72 return Selection.extendLeft(buffer, layout);
73 } else {
74 return Selection.moveLeft(buffer, layout);
75 }
76 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077
Jeff Brown67b6ab72010-12-17 18:33:02 -080078 @Override
79 protected boolean right(TextView widget, Spannable buffer) {
80 final Layout layout = widget.getLayout();
81 if (isSelecting(buffer)) {
82 return Selection.extendRight(buffer, layout);
83 } else {
84 return Selection.moveRight(buffer, layout);
85 }
86 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080087
Jeff Brown67b6ab72010-12-17 18:33:02 -080088 @Override
89 protected boolean up(TextView widget, Spannable buffer) {
90 final Layout layout = widget.getLayout();
91 if (isSelecting(buffer)) {
92 return Selection.extendUp(buffer, layout);
93 } else {
94 return Selection.moveUp(buffer, layout);
95 }
96 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080097
Jeff Brown67b6ab72010-12-17 18:33:02 -080098 @Override
99 protected boolean down(TextView widget, Spannable buffer) {
100 final Layout layout = widget.getLayout();
101 if (isSelecting(buffer)) {
102 return Selection.extendDown(buffer, layout);
103 } else {
104 return Selection.moveDown(buffer, layout);
105 }
106 }
107
108 @Override
109 protected boolean pageUp(TextView widget, Spannable buffer) {
110 final Layout layout = widget.getLayout();
111 final boolean selecting = isSelecting(buffer);
112 final int targetY = getCurrentLineTop(buffer, layout) - getPageHeight(widget);
113 boolean handled = false;
114 for (;;) {
115 final int previousSelectionEnd = Selection.getSelectionEnd(buffer);
116 if (selecting) {
117 Selection.extendUp(buffer, layout);
118 } else {
119 Selection.moveUp(buffer, layout);
120 }
121 if (Selection.getSelectionEnd(buffer) == previousSelectionEnd) {
122 break;
123 }
124 handled = true;
125 if (getCurrentLineTop(buffer, layout) <= targetY) {
126 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800127 }
128 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 return handled;
130 }
131
Jeff Brown67b6ab72010-12-17 18:33:02 -0800132 @Override
133 protected boolean pageDown(TextView widget, Spannable buffer) {
134 final Layout layout = widget.getLayout();
135 final boolean selecting = isSelecting(buffer);
136 final int targetY = getCurrentLineTop(buffer, layout) + getPageHeight(widget);
137 boolean handled = false;
138 for (;;) {
139 final int previousSelectionEnd = Selection.getSelectionEnd(buffer);
140 if (selecting) {
141 Selection.extendDown(buffer, layout);
142 } else {
143 Selection.moveDown(buffer, layout);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144 }
Jeff Brown67b6ab72010-12-17 18:33:02 -0800145 if (Selection.getSelectionEnd(buffer) == previousSelectionEnd) {
146 break;
147 }
148 handled = true;
149 if (getCurrentLineTop(buffer, layout) >= targetY) {
150 break;
151 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152 }
Jeff Brown67b6ab72010-12-17 18:33:02 -0800153 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154 }
Maryam Garrettce083792009-12-01 14:40:03 -0500155
Jeff Brown67b6ab72010-12-17 18:33:02 -0800156 @Override
157 protected boolean top(TextView widget, Spannable buffer) {
158 if (isSelecting(buffer)) {
159 Selection.extendSelection(buffer, 0);
160 } else {
161 Selection.setSelection(buffer, 0);
162 }
163 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164 }
Maryam Garrettce083792009-12-01 14:40:03 -0500165
Jeff Brown67b6ab72010-12-17 18:33:02 -0800166 @Override
167 protected boolean bottom(TextView widget, Spannable buffer) {
168 if (isSelecting(buffer)) {
169 Selection.extendSelection(buffer, buffer.length());
170 } else {
171 Selection.setSelection(buffer, buffer.length());
172 }
173 return true;
174 }
175
176 @Override
177 protected boolean lineStart(TextView widget, Spannable buffer) {
178 final Layout layout = widget.getLayout();
179 if (isSelecting(buffer)) {
180 return Selection.extendToLeftEdge(buffer, layout);
181 } else {
182 return Selection.moveToLeftEdge(buffer, layout);
183 }
184 }
185
186 @Override
187 protected boolean lineEnd(TextView widget, Spannable buffer) {
188 final Layout layout = widget.getLayout();
189 if (isSelecting(buffer)) {
190 return Selection.extendToRightEdge(buffer, layout);
191 } else {
192 return Selection.moveToRightEdge(buffer, layout);
193 }
194 }
195
Jeff Sharkeye982dfc12011-03-21 16:40:23 -0700196 /** {@hide} */
197 @Override
198 protected boolean leftWord(TextView widget, Spannable buffer) {
Gilles Debunne287d6c62011-10-05 18:22:11 -0700199 final int selectionEnd = widget.getSelectionEnd();
Gilles Debunne9d8d3f12011-10-13 12:15:10 -0700200 final WordIterator wordIterator = widget.getWordIterator();
201 wordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
202 return Selection.moveToPreceding(buffer, wordIterator, isSelecting(buffer));
Jeff Sharkeye982dfc12011-03-21 16:40:23 -0700203 }
204
205 /** {@hide} */
206 @Override
207 protected boolean rightWord(TextView widget, Spannable buffer) {
Gilles Debunne287d6c62011-10-05 18:22:11 -0700208 final int selectionEnd = widget.getSelectionEnd();
Gilles Debunne9d8d3f12011-10-13 12:15:10 -0700209 final WordIterator wordIterator = widget.getWordIterator();
210 wordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
211 return Selection.moveToFollowing(buffer, wordIterator, isSelecting(buffer));
Jeff Sharkeye982dfc12011-03-21 16:40:23 -0700212 }
213
Jeff Brown67b6ab72010-12-17 18:33:02 -0800214 @Override
215 protected boolean home(TextView widget, Spannable buffer) {
216 return lineStart(widget, buffer);
217 }
218
219 @Override
220 protected boolean end(TextView widget, Spannable buffer) {
221 return lineEnd(widget, buffer);
222 }
223
224 @Override
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700225 public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
Jeff Sharkeye982dfc12011-03-21 16:40:23 -0700226 int initialScrollX = -1;
227 int initialScrollY = -1;
Gilles Debunne0eb704c2010-11-30 12:50:54 -0800228 final int action = event.getAction();
229
230 if (action == MotionEvent.ACTION_UP) {
Dianne Hackborn1c9aefd2009-03-24 18:18:28 -0700231 initialScrollX = Touch.getInitialScrollX(widget, buffer);
232 initialScrollY = Touch.getInitialScrollY(widget, buffer);
233 }
Maryam Garrettce083792009-12-01 14:40:03 -0500234
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235 boolean handled = Touch.onTouchEvent(widget, buffer, event);
236
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700237 if (widget.isFocused() && !widget.didTouchFocusSelect()) {
Gilles Debunne0eb704c2010-11-30 12:50:54 -0800238 if (action == MotionEvent.ACTION_DOWN) {
Gilles Debunne70a63122011-09-01 13:27:33 -0700239 if (isSelecting(buffer)) {
Gilles Debunne3bca69b2011-05-23 18:20:22 -0700240 int offset = widget.getOffsetForPosition(event.getX(), event.getY());
Jeff Sharkeye982dfc12011-03-21 16:40:23 -0700241
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700242 buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
Maryam Garrettab928932009-12-10 15:42:30 -0500243
244 // Disallow intercepting of the touch events, so that
245 // users can scroll and select at the same time.
246 // without this, users would get booted out of select
247 // mode once the view detected it needed to scroll.
248 widget.getParent().requestDisallowInterceptTouchEvent(true);
Maryam Garrettce083792009-12-01 14:40:03 -0500249 }
Gilles Debunne0eb704c2010-11-30 12:50:54 -0800250 } else if (action == MotionEvent.ACTION_MOVE) {
Gilles Debunne70a63122011-09-01 13:27:33 -0700251 if (isSelecting(buffer) && handled) {
Maryam Garrett39f0efb2009-12-11 13:52:06 -0500252 // Before selecting, make sure we've moved out of the "slop".
253 // handled will be true, if we're in select mode AND we're
254 // OUT of the slop
Maryam Garrettce083792009-12-01 14:40:03 -0500255
Maryam Garrett39f0efb2009-12-11 13:52:06 -0500256 // Turn long press off while we're selecting. User needs to
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700257 // re-tap on the selection to enable long press
Maryam Garrett39f0efb2009-12-11 13:52:06 -0500258 widget.cancelLongPress();
Maryam Garrettce083792009-12-01 14:40:03 -0500259
Maryam Garrett39f0efb2009-12-11 13:52:06 -0500260 // Update selection as we're moving the selection area.
Maryam Garrettce083792009-12-01 14:40:03 -0500261
Maryam Garrett39f0efb2009-12-11 13:52:06 -0500262 // Get the current touch position
Gilles Debunne3bca69b2011-05-23 18:20:22 -0700263 int offset = widget.getOffsetForPosition(event.getX(), event.getY());
Maryam Garrett39f0efb2009-12-11 13:52:06 -0500264
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700265 Selection.extendSelection(buffer, offset);
Maryam Garrett39f0efb2009-12-11 13:52:06 -0500266 return true;
Maryam Garrettce083792009-12-01 14:40:03 -0500267 }
Gilles Debunne0eb704c2010-11-30 12:50:54 -0800268 } else if (action == MotionEvent.ACTION_UP) {
Dianne Hackborn1c9aefd2009-03-24 18:18:28 -0700269 // If we have scrolled, then the up shouldn't move the cursor,
270 // but we do need to make sure the cursor is still visible at
271 // the current scroll offset to avoid the scroll jumping later
272 // to show it.
273 if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) ||
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700274 (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
Dianne Hackborn1c9aefd2009-03-24 18:18:28 -0700275 widget.moveCursorToVisibleOffset();
276 return true;
277 }
Maryam Garrettce083792009-12-01 14:40:03 -0500278
Gilles Debunne3bca69b2011-05-23 18:20:22 -0700279 int offset = widget.getOffsetForPosition(event.getX(), event.getY());
Jeff Brown67b6ab72010-12-17 18:33:02 -0800280 if (isSelecting(buffer)) {
Maryam Garrett62c4ad32010-01-06 13:06:20 -0500281 buffer.removeSpan(LAST_TAP_DOWN);
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700282 Selection.extendSelection(buffer, offset);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800283 }
284
285 MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
286 MetaKeyKeyListener.resetLockedMeta(buffer);
287
288 return true;
289 }
290 }
291
292 return handled;
293 }
294
Jeff Brown67b6ab72010-12-17 18:33:02 -0800295 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800296 public boolean canSelectArbitrarily() {
297 return true;
298 }
299
Jeff Brown67b6ab72010-12-17 18:33:02 -0800300 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800301 public void initialize(TextView widget, Spannable text) {
302 Selection.setSelection(text, 0);
303 }
304
Jeff Brown67b6ab72010-12-17 18:33:02 -0800305 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306 public void onTakeFocus(TextView view, Spannable text, int dir) {
307 if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
Gilles Debunne2703a422010-08-23 15:14:03 -0700308 if (view.getLayout() == null) {
309 // This shouldn't be null, but do something sensible if it is.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800310 Selection.setSelection(text, text.length());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800311 }
312 } else {
313 Selection.setSelection(text, text.length());
314 }
315 }
316
317 public static MovementMethod getInstance() {
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700318 if (sInstance == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800319 sInstance = new ArrowKeyMovementMethod();
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700320 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800321
322 return sInstance;
323 }
324
Maryam Garrettce083792009-12-01 14:40:03 -0500325 private static final Object LAST_TAP_DOWN = new Object();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800326 private static ArrowKeyMovementMethod sInstance;
327}