ArrowKeyMovementMethod.java revision 3e05a0beb2fad0b21558019d2adf6805da70e10e
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 protected 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 events, that can be translated in cursor updates. 306 * @param cursorController A cursor controller implementation 307 */ 308 public void setCursorController(CursorController cursorController) { 309 mCursorController = cursorController; 310 } 311 312 public boolean canSelectArbitrarily() { 313 return true; 314 } 315 316 public void initialize(TextView widget, Spannable text) { 317 Selection.setSelection(text, 0); 318 } 319 320 public void onTakeFocus(TextView view, Spannable text, int dir) { 321 if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) { 322 Layout layout = view.getLayout(); 323 324 if (layout == null) { 325 /* 326 * This shouldn't be null, but do something sensible if it is. 327 */ 328 Selection.setSelection(text, text.length()); 329 } else { 330 /* 331 * Put the cursor at the end of the first line, which is 332 * either the last offset if there is only one line, or the 333 * offset before the first character of the second line 334 * if there is more than one line. 335 */ 336 if (layout.getLineCount() == 1) { 337 Selection.setSelection(text, text.length()); 338 } else { 339 Selection.setSelection(text, layout.getLineStart(1) - 1); 340 } 341 } 342 } else { 343 Selection.setSelection(text, text.length()); 344 } 345 } 346 347 public static MovementMethod getInstance() { 348 if (sInstance == null) { 349 sInstance = new ArrowKeyMovementMethod(); 350 } 351 352 return sInstance; 353 } 354 355 356 private static final Object LAST_TAP_DOWN = new Object(); 357 private static ArrowKeyMovementMethod sInstance; 358} 359