ArrowKeyMovementMethod.java revision 49271c941ed965c20ed834c5efc52b07ed616e34
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 17package android.text.method; 18 19import android.text.Layout; 20import android.text.Selection; 21import android.text.Spannable; 22import android.view.KeyEvent; 23import android.view.MotionEvent; 24import android.view.View; 25import android.widget.TextView; 26import android.widget.TextView.CursorController; 27 28// XXX this doesn't extend MetaKeyKeyListener because the signatures 29// don't match. Need to figure that out. Meanwhile the meta keys 30// won't work in fields that don't take input. 31 32public class ArrowKeyMovementMethod implements MovementMethod { 33 /** 34 * An optional controller for the cursor. 35 * Use {@link #setCursorController(CursorController)} to set this field. 36 */ 37 private CursorController mCursorController; 38 39 private boolean isCap(Spannable buffer) { 40 return ((MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_SHIFT_ON) == 1) || 41 (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0)); 42 } 43 44 private boolean isAlt(Spannable buffer) { 45 return MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1; 46 } 47 48 private boolean up(TextView widget, Spannable buffer) { 49 boolean cap = isCap(buffer); 50 boolean alt = isAlt(buffer); 51 Layout layout = widget.getLayout(); 52 53 if (cap) { 54 if (alt) { 55 Selection.extendSelection(buffer, 0); 56 return true; 57 } else { 58 return Selection.extendUp(buffer, layout); 59 } 60 } else { 61 if (alt) { 62 Selection.setSelection(buffer, 0); 63 return true; 64 } else { 65 return Selection.moveUp(buffer, layout); 66 } 67 } 68 } 69 70 private boolean down(TextView widget, Spannable buffer) { 71 boolean cap = isCap(buffer); 72 boolean alt = isAlt(buffer); 73 Layout layout = widget.getLayout(); 74 75 if (cap) { 76 if (alt) { 77 Selection.extendSelection(buffer, buffer.length()); 78 return true; 79 } else { 80 return Selection.extendDown(buffer, layout); 81 } 82 } else { 83 if (alt) { 84 Selection.setSelection(buffer, buffer.length()); 85 return true; 86 } else { 87 return Selection.moveDown(buffer, layout); 88 } 89 } 90 } 91 92 private boolean left(TextView widget, Spannable buffer) { 93 boolean cap = isCap(buffer); 94 boolean alt = isAlt(buffer); 95 Layout layout = widget.getLayout(); 96 97 if (cap) { 98 if (alt) { 99 return Selection.extendToLeftEdge(buffer, layout); 100 } else { 101 return Selection.extendLeft(buffer, layout); 102 } 103 } else { 104 if (alt) { 105 return Selection.moveToLeftEdge(buffer, layout); 106 } else { 107 return Selection.moveLeft(buffer, layout); 108 } 109 } 110 } 111 112 private boolean right(TextView widget, Spannable buffer) { 113 boolean cap = isCap(buffer); 114 boolean alt = isAlt(buffer); 115 Layout layout = widget.getLayout(); 116 117 if (cap) { 118 if (alt) { 119 return Selection.extendToRightEdge(buffer, layout); 120 } else { 121 return Selection.extendRight(buffer, layout); 122 } 123 } else { 124 if (alt) { 125 return Selection.moveToRightEdge(buffer, layout); 126 } else { 127 return Selection.moveRight(buffer, layout); 128 } 129 } 130 } 131 132 public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { 133 if (executeDown(widget, buffer, keyCode)) { 134 MetaKeyKeyListener.adjustMetaAfterKeypress(buffer); 135 MetaKeyKeyListener.resetLockedMeta(buffer); 136 return true; 137 } 138 139 return false; 140 } 141 142 private boolean executeDown(TextView widget, Spannable buffer, int keyCode) { 143 boolean handled = false; 144 145 switch (keyCode) { 146 case KeyEvent.KEYCODE_DPAD_UP: 147 handled |= up(widget, buffer); 148 break; 149 150 case KeyEvent.KEYCODE_DPAD_DOWN: 151 handled |= down(widget, buffer); 152 break; 153 154 case KeyEvent.KEYCODE_DPAD_LEFT: 155 handled |= left(widget, buffer); 156 break; 157 158 case KeyEvent.KEYCODE_DPAD_RIGHT: 159 handled |= right(widget, buffer); 160 break; 161 162 case KeyEvent.KEYCODE_DPAD_CENTER: 163 if ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) && 164 (widget.showContextMenu())) { 165 handled = true; 166 } 167 } 168 169 if (handled) { 170 MetaKeyKeyListener.adjustMetaAfterKeypress(buffer); 171 MetaKeyKeyListener.resetLockedMeta(buffer); 172 } 173 174 return handled; 175 } 176 177 public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { 178 return false; 179 } 180 181 public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) { 182 int code = event.getKeyCode(); 183 if (code != KeyEvent.KEYCODE_UNKNOWN && event.getAction() == KeyEvent.ACTION_MULTIPLE) { 184 int repeat = event.getRepeatCount(); 185 boolean handled = false; 186 while ((--repeat) > 0) { 187 handled |= executeDown(view, text, code); 188 } 189 return handled; 190 } 191 return false; 192 } 193 194 public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) { 195 if (mCursorController != null) { 196 mCursorController.hide(); 197 } 198 return false; 199 } 200 201 public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { 202 if (mCursorController != null) { 203 return onTouchEventCursor(widget, buffer, event); 204 } else { 205 return onTouchEventStandard(widget, buffer, event); 206 } 207 } 208 209 private boolean onTouchEventStandard(TextView widget, Spannable buffer, MotionEvent event) { 210 int initialScrollX = -1, initialScrollY = -1; 211 if (event.getAction() == MotionEvent.ACTION_UP) { 212 initialScrollX = Touch.getInitialScrollX(widget, buffer); 213 initialScrollY = Touch.getInitialScrollY(widget, buffer); 214 } 215 216 boolean handled = Touch.onTouchEvent(widget, buffer, event); 217 218 if (widget.isFocused() && !widget.didTouchFocusSelect()) { 219 if (event.getAction() == MotionEvent.ACTION_DOWN) { 220 boolean cap = isCap(buffer); 221 if (cap) { 222 int offset = widget.getOffset((int) event.getX(), (int) event.getY()); 223 224 buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT); 225 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); 231 } 232 } else if (event.getAction() == MotionEvent.ACTION_MOVE) { 233 boolean cap = isCap(buffer); 234 235 if (cap && handled) { 236 // 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 239 240 // Turn long press off while we're selecting. User needs to 241 // re-tap on the selection to enable long press 242 widget.cancelLongPress(); 243 244 // Update selection as we're moving the selection area. 245 246 // Get the current touch position 247 int offset = widget.getOffset((int) event.getX(), (int) event.getY()); 248 249 Selection.extendSelection(buffer, offset); 250 return true; 251 } 252 } else if (event.getAction() == MotionEvent.ACTION_UP) { 253 // 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()) || 258 (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) { 259 widget.moveCursorToVisibleOffset(); 260 return true; 261 } 262 263 int offset = widget.getOffset((int) event.getX(), (int) event.getY()); 264 if (isCap(buffer)) { 265 buffer.removeSpan(LAST_TAP_DOWN); 266 Selection.extendSelection(buffer, offset); 267 } else { 268 Selection.setSelection(buffer, offset); 269 } 270 271 MetaKeyKeyListener.adjustMetaAfterKeypress(buffer); 272 MetaKeyKeyListener.resetLockedMeta(buffer); 273 274 return true; 275 } 276 } 277 278 return handled; 279 } 280 281 private boolean onTouchEventCursor(TextView widget, Spannable buffer, MotionEvent event) { 282 if (widget.isFocused() && !widget.didTouchFocusSelect()) { 283 switch (event.getActionMasked()) { 284 case MotionEvent.ACTION_MOVE: 285 widget.cancelLongPress(); 286 287 // Offset the current touch position (from controller to cursor) 288 final float x = event.getX() + mCursorController.getOffsetX(); 289 final float y = event.getY() + mCursorController.getOffsetY(); 290 mCursorController.updatePosition((int) x, (int) y); 291 return true; 292 293 case MotionEvent.ACTION_UP: 294 case MotionEvent.ACTION_CANCEL: 295 mCursorController = null; 296 return true; 297 } 298 } 299 return false; 300 } 301 302 /** 303 * Defines the cursor controller. 304 * 305 * When set, this object can be used to handle touch events, that can be translated into cursor 306 * updates. 307 * 308 * {@link MotionEvent#ACTION_MOVE} events will call back the 309 * {@link CursorController#updatePosition(int, int)} controller's method, passing the current 310 * finger coordinates (offset by {@link CursorController#getOffsetX()} and 311 * {@link CursorController#getOffsetY()}) as parameters. 312 * 313 * When the gesture is finished (on a {@link MotionEvent#ACTION_UP} or 314 * {@link MotionEvent#ACTION_CANCEL} event), the controller is reset to null. 315 * 316 * @param cursorController A cursor controller implementation 317 */ 318 public void setCursorController(CursorController cursorController) { 319 mCursorController = cursorController; 320 } 321 322 public boolean canSelectArbitrarily() { 323 return true; 324 } 325 326 public void initialize(TextView widget, Spannable text) { 327 Selection.setSelection(text, 0); 328 } 329 330 public void onTakeFocus(TextView view, Spannable text, int dir) { 331 if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) { 332 if (view.getLayout() == null) { 333 // This shouldn't be null, but do something sensible if it is. 334 Selection.setSelection(text, text.length()); 335 } 336 } else { 337 Selection.setSelection(text, text.length()); 338 } 339 } 340 341 public static MovementMethod getInstance() { 342 if (sInstance == null) { 343 sInstance = new ArrowKeyMovementMethod(); 344 } 345 346 return sInstance; 347 } 348 349 350 private static final Object LAST_TAP_DOWN = new Object(); 351 private static ArrowKeyMovementMethod sInstance; 352} 353