blob: a61ff1361b6090434776dd28da2965f3275981cd [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
Jeff Brown67b6ab72010-12-17 18:33:02 -080038 private int getCurrentLineTop(Spannable buffer, Layout layout) {
39 return layout.getLineTop(layout.getLineForOffset(Selection.getSelectionEnd(buffer)));
Gilles Debunneb0d6ba12010-08-17 20:01:42 -070040 }
41
Jeff Brown67b6ab72010-12-17 18:33:02 -080042 private int getPageHeight(TextView widget) {
43 // 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,
59 MetaKeyKeyListener.META_SELECTING) != 0) {
60 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
196 @Override
197 protected boolean home(TextView widget, Spannable buffer) {
198 return lineStart(widget, buffer);
199 }
200
201 @Override
202 protected boolean end(TextView widget, Spannable buffer) {
203 return lineEnd(widget, buffer);
204 }
205
206 @Override
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700207 public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
Dianne Hackborn1c9aefd2009-03-24 18:18:28 -0700208 int initialScrollX = -1, initialScrollY = -1;
Gilles Debunne0eb704c2010-11-30 12:50:54 -0800209 final int action = event.getAction();
210
211 if (action == MotionEvent.ACTION_UP) {
Dianne Hackborn1c9aefd2009-03-24 18:18:28 -0700212 initialScrollX = Touch.getInitialScrollX(widget, buffer);
213 initialScrollY = Touch.getInitialScrollY(widget, buffer);
214 }
Maryam Garrettce083792009-12-01 14:40:03 -0500215
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 boolean handled = Touch.onTouchEvent(widget, buffer, event);
217
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700218 if (widget.isFocused() && !widget.didTouchFocusSelect()) {
Gilles Debunne0eb704c2010-11-30 12:50:54 -0800219 if (action == MotionEvent.ACTION_DOWN) {
Jeff Brown67b6ab72010-12-17 18:33:02 -0800220 boolean cap = isSelecting(buffer);
Maryam Garrettce083792009-12-01 14:40:03 -0500221 if (cap) {
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700222 int offset = widget.getOffset((int) event.getX(), (int) event.getY());
223
224 buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
Maryam Garrettab928932009-12-10 15:42:30 -0500225
226 // Disallow intercepting of the touch events, so that
227 // users can scroll and select at the same time.
228 // without this, users would get booted out of select
229 // mode once the view detected it needed to scroll.
230 widget.getParent().requestDisallowInterceptTouchEvent(true);
Maryam Garrettce083792009-12-01 14:40:03 -0500231 }
Gilles Debunne0eb704c2010-11-30 12:50:54 -0800232 } else if (action == MotionEvent.ACTION_MOVE) {
Jeff Brown67b6ab72010-12-17 18:33:02 -0800233 boolean cap = isSelecting(buffer);
Maryam Garrettce083792009-12-01 14:40:03 -0500234
Kenny Root8cdb6842010-04-05 14:27:59 -0700235 if (cap && handled) {
Maryam Garrett39f0efb2009-12-11 13:52:06 -0500236 // Before selecting, make sure we've moved out of the "slop".
237 // handled will be true, if we're in select mode AND we're
238 // OUT of the slop
Maryam Garrettce083792009-12-01 14:40:03 -0500239
Maryam Garrett39f0efb2009-12-11 13:52:06 -0500240 // Turn long press off while we're selecting. User needs to
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700241 // re-tap on the selection to enable long press
Maryam Garrett39f0efb2009-12-11 13:52:06 -0500242 widget.cancelLongPress();
Maryam Garrettce083792009-12-01 14:40:03 -0500243
Maryam Garrett39f0efb2009-12-11 13:52:06 -0500244 // Update selection as we're moving the selection area.
Maryam Garrettce083792009-12-01 14:40:03 -0500245
Maryam Garrett39f0efb2009-12-11 13:52:06 -0500246 // Get the current touch position
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700247 int offset = widget.getOffset((int) event.getX(), (int) event.getY());
Maryam Garrett39f0efb2009-12-11 13:52:06 -0500248
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700249 Selection.extendSelection(buffer, offset);
Maryam Garrett39f0efb2009-12-11 13:52:06 -0500250 return true;
Maryam Garrettce083792009-12-01 14:40:03 -0500251 }
Gilles Debunne0eb704c2010-11-30 12:50:54 -0800252 } else if (action == MotionEvent.ACTION_UP) {
Dianne Hackborn1c9aefd2009-03-24 18:18:28 -0700253 // If we have scrolled, then the up shouldn't move the cursor,
254 // but we do need to make sure the cursor is still visible at
255 // the current scroll offset to avoid the scroll jumping later
256 // to show it.
257 if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) ||
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700258 (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
Dianne Hackborn1c9aefd2009-03-24 18:18:28 -0700259 widget.moveCursorToVisibleOffset();
260 return true;
261 }
Maryam Garrettce083792009-12-01 14:40:03 -0500262
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700263 int offset = widget.getOffset((int) event.getX(), (int) event.getY());
Jeff Brown67b6ab72010-12-17 18:33:02 -0800264 if (isSelecting(buffer)) {
Maryam Garrett62c4ad32010-01-06 13:06:20 -0500265 buffer.removeSpan(LAST_TAP_DOWN);
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700266 Selection.extendSelection(buffer, offset);
Eric Fischera6e50452009-07-24 18:14:35 -0700267 } else {
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700268 Selection.setSelection(buffer, offset);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269 }
270
271 MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
272 MetaKeyKeyListener.resetLockedMeta(buffer);
273
274 return true;
275 }
276 }
277
278 return handled;
279 }
280
Jeff Brown67b6ab72010-12-17 18:33:02 -0800281 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282 public boolean canSelectArbitrarily() {
283 return true;
284 }
285
Jeff Brown67b6ab72010-12-17 18:33:02 -0800286 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800287 public void initialize(TextView widget, Spannable text) {
288 Selection.setSelection(text, 0);
289 }
290
Jeff Brown67b6ab72010-12-17 18:33:02 -0800291 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800292 public void onTakeFocus(TextView view, Spannable text, int dir) {
293 if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
Gilles Debunne2703a422010-08-23 15:14:03 -0700294 if (view.getLayout() == null) {
295 // This shouldn't be null, but do something sensible if it is.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800296 Selection.setSelection(text, text.length());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800297 }
298 } else {
299 Selection.setSelection(text, text.length());
300 }
301 }
302
303 public static MovementMethod getInstance() {
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700304 if (sInstance == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800305 sInstance = new ArrowKeyMovementMethod();
Gilles Debunneb0d6ba12010-08-17 20:01:42 -0700306 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800307
308 return sInstance;
309 }
310
Maryam Garrettce083792009-12-01 14:40:03 -0500311
312 private static final Object LAST_TAP_DOWN = new Object();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800313 private static ArrowKeyMovementMethod sInstance;
314}