ArrowKeyMovementMethod.java revision a6e50454890629fe369538d8e473945bc9d68136
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.util.Log; 20import android.view.KeyEvent; 21import android.graphics.Rect; 22import android.text.*; 23import android.widget.TextView; 24import android.view.View; 25import android.view.ViewConfiguration; 26import android.view.MotionEvent; 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 33ArrowKeyMovementMethod 34implements MovementMethod 35{ 36 private boolean up(TextView widget, Spannable buffer) { 37 boolean cap = (MetaKeyKeyListener.getMetaState(buffer, 38 KeyEvent.META_SHIFT_ON) == 1) || 39 (MetaKeyKeyListener.getMetaState(buffer, 40 MetaKeyKeyListener.META_SELECTING) != 0); 41 boolean alt = MetaKeyKeyListener.getMetaState(buffer, 42 KeyEvent.META_ALT_ON) == 1; 43 Layout layout = widget.getLayout(); 44 45 if (cap) { 46 if (alt) { 47 Selection.extendSelection(buffer, 0); 48 return true; 49 } else { 50 return Selection.extendUp(buffer, layout); 51 } 52 } else { 53 if (alt) { 54 Selection.setSelection(buffer, 0); 55 return true; 56 } else { 57 return Selection.moveUp(buffer, layout); 58 } 59 } 60 } 61 62 private boolean down(TextView widget, Spannable buffer) { 63 boolean cap = (MetaKeyKeyListener.getMetaState(buffer, 64 KeyEvent.META_SHIFT_ON) == 1) || 65 (MetaKeyKeyListener.getMetaState(buffer, 66 MetaKeyKeyListener.META_SELECTING) != 0); 67 boolean alt = MetaKeyKeyListener.getMetaState(buffer, 68 KeyEvent.META_ALT_ON) == 1; 69 Layout layout = widget.getLayout(); 70 71 if (cap) { 72 if (alt) { 73 Selection.extendSelection(buffer, buffer.length()); 74 return true; 75 } else { 76 return Selection.extendDown(buffer, layout); 77 } 78 } else { 79 if (alt) { 80 Selection.setSelection(buffer, buffer.length()); 81 return true; 82 } else { 83 return Selection.moveDown(buffer, layout); 84 } 85 } 86 } 87 88 private boolean left(TextView widget, Spannable buffer) { 89 boolean cap = (MetaKeyKeyListener.getMetaState(buffer, 90 KeyEvent.META_SHIFT_ON) == 1) || 91 (MetaKeyKeyListener.getMetaState(buffer, 92 MetaKeyKeyListener.META_SELECTING) != 0); 93 boolean alt = MetaKeyKeyListener.getMetaState(buffer, 94 KeyEvent.META_ALT_ON) == 1; 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 = (MetaKeyKeyListener.getMetaState(buffer, 114 KeyEvent.META_SHIFT_ON) == 1) || 115 (MetaKeyKeyListener.getMetaState(buffer, 116 MetaKeyKeyListener.META_SELECTING) != 0); 117 boolean alt = MetaKeyKeyListener.getMetaState(buffer, 118 KeyEvent.META_ALT_ON) == 1; 119 Layout layout = widget.getLayout(); 120 121 if (cap) { 122 if (alt) { 123 return Selection.extendToRightEdge(buffer, layout); 124 } else { 125 return Selection.extendRight(buffer, layout); 126 } 127 } else { 128 if (alt) { 129 return Selection.moveToRightEdge(buffer, layout); 130 } else { 131 return Selection.moveRight(buffer, layout); 132 } 133 } 134 } 135 136 public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { 137 if (executeDown(widget, buffer, keyCode)) { 138 MetaKeyKeyListener.adjustMetaAfterKeypress(buffer); 139 MetaKeyKeyListener.resetLockedMeta(buffer); 140 return true; 141 } 142 143 return false; 144 } 145 146 private boolean executeDown(TextView widget, Spannable buffer, int keyCode) { 147 boolean handled = false; 148 149 switch (keyCode) { 150 case KeyEvent.KEYCODE_DPAD_UP: 151 handled |= up(widget, buffer); 152 break; 153 154 case KeyEvent.KEYCODE_DPAD_DOWN: 155 handled |= down(widget, buffer); 156 break; 157 158 case KeyEvent.KEYCODE_DPAD_LEFT: 159 handled |= left(widget, buffer); 160 break; 161 162 case KeyEvent.KEYCODE_DPAD_RIGHT: 163 handled |= right(widget, buffer); 164 break; 165 166 case KeyEvent.KEYCODE_DPAD_CENTER: 167 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { 168 if (widget.showContextMenu()) { 169 handled = true; 170 } 171 } 172 } 173 174 if (handled) { 175 MetaKeyKeyListener.adjustMetaAfterKeypress(buffer); 176 MetaKeyKeyListener.resetLockedMeta(buffer); 177 } 178 179 return handled; 180 } 181 182 public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { 183 return false; 184 } 185 186 public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) { 187 int code = event.getKeyCode(); 188 if (code != KeyEvent.KEYCODE_UNKNOWN 189 && event.getAction() == KeyEvent.ACTION_MULTIPLE) { 190 int repeat = event.getRepeatCount(); 191 boolean handled = false; 192 while ((--repeat) > 0) { 193 handled |= executeDown(view, text, code); 194 } 195 return handled; 196 } 197 return false; 198 } 199 200 public boolean onTrackballEvent(TextView widget, Spannable text, 201 MotionEvent event) { 202 return false; 203 } 204 205 public boolean onTouchEvent(TextView widget, Spannable buffer, 206 MotionEvent event) { 207 int initialScrollX = -1, initialScrollY = -1; 208 if (event.getAction() == MotionEvent.ACTION_UP) { 209 initialScrollX = Touch.getInitialScrollX(widget, buffer); 210 initialScrollY = Touch.getInitialScrollY(widget, buffer); 211 } 212 213 boolean handled = Touch.onTouchEvent(widget, buffer, event); 214 215 if (widget.isFocused() && !widget.didTouchFocusSelect()) { 216 if (event.getAction() == MotionEvent.ACTION_UP) { 217 // If we have scrolled, then the up shouldn't move the cursor, 218 // but we do need to make sure the cursor is still visible at 219 // the current scroll offset to avoid the scroll jumping later 220 // to show it. 221 if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) || 222 (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) { 223 widget.moveCursorToVisibleOffset(); 224 return true; 225 } 226 227 int x = (int) event.getX(); 228 int y = (int) event.getY(); 229 230 x -= widget.getTotalPaddingLeft(); 231 y -= widget.getTotalPaddingTop(); 232 233 // Clamp the position to inside of the view. 234 if (x < 0) { 235 x = 0; 236 } else if (x >= (widget.getWidth()-widget.getTotalPaddingRight())) { 237 x = widget.getWidth()-widget.getTotalPaddingRight() - 1; 238 } 239 if (y < 0) { 240 y = 0; 241 } else if (y >= (widget.getHeight()-widget.getTotalPaddingBottom())) { 242 y = widget.getHeight()-widget.getTotalPaddingBottom() - 1; 243 } 244 245 x += widget.getScrollX(); 246 y += widget.getScrollY(); 247 248 Layout layout = widget.getLayout(); 249 int line = layout.getLineForVertical(y); 250 251 int off = layout.getOffsetForHorizontal(line, x); 252 253 // XXX should do the same adjust for x as we do for the line. 254 255 boolean cap = (MetaKeyKeyListener.getMetaState(buffer, 256 KeyEvent.META_SHIFT_ON) == 1) || 257 (MetaKeyKeyListener.getMetaState(buffer, 258 MetaKeyKeyListener.META_SELECTING) != 0); 259 260 DoubleTapState[] tap = buffer.getSpans(0, buffer.length(), 261 DoubleTapState.class); 262 boolean doubletap = false; 263 264 if (tap.length > 0) { 265 if (event.getEventTime() - tap[0].mWhen <= 266 ViewConfiguration.getDoubleTapTimeout()) { 267 if (sameWord(buffer, off, Selection.getSelectionEnd(buffer))) { 268 doubletap = true; 269 } 270 } 271 272 tap[0].mWhen = event.getEventTime(); 273 } else { 274 DoubleTapState newtap = new DoubleTapState(); 275 newtap.mWhen = event.getEventTime(); 276 buffer.setSpan(newtap, 0, buffer.length(), 277 Spannable.SPAN_INCLUSIVE_INCLUSIVE); 278 } 279 280 if (cap) { 281 Selection.extendSelection(buffer, off); 282 } else if (doubletap) { 283 Selection.setSelection(buffer, 284 findWordStart(buffer, off), 285 findWordEnd(buffer, off)); 286 } else { 287 Selection.setSelection(buffer, off); 288 } 289 290 MetaKeyKeyListener.adjustMetaAfterKeypress(buffer); 291 MetaKeyKeyListener.resetLockedMeta(buffer); 292 293 return true; 294 } 295 } 296 297 return handled; 298 } 299 300 private static class DoubleTapState implements NoCopySpan { 301 long mWhen; 302 } 303 304 private static boolean sameWord(CharSequence text, int one, int two) { 305 int start = findWordStart(text, one); 306 int end = findWordEnd(text, one); 307 308 if (end == start) { 309 return false; 310 } 311 312 return start == findWordStart(text, two) && 313 end == findWordEnd(text, two); 314 } 315 316 // TODO: Unify with TextView.getWordForDictionary() 317 private static int findWordStart(CharSequence text, int start) { 318 for (; start > 0; start--) { 319 char c = text.charAt(start - 1); 320 int type = Character.getType(c); 321 322 if (c != '\'' && 323 type != Character.UPPERCASE_LETTER && 324 type != Character.LOWERCASE_LETTER && 325 type != Character.TITLECASE_LETTER && 326 type != Character.MODIFIER_LETTER && 327 type != Character.DECIMAL_DIGIT_NUMBER) { 328 break; 329 } 330 } 331 332 return start; 333 } 334 335 // TODO: Unify with TextView.getWordForDictionary() 336 private static int findWordEnd(CharSequence text, int end) { 337 int len = text.length(); 338 339 for (; end < len; end++) { 340 char c = text.charAt(end); 341 int type = Character.getType(c); 342 343 if (c != '\'' && 344 type != Character.UPPERCASE_LETTER && 345 type != Character.LOWERCASE_LETTER && 346 type != Character.TITLECASE_LETTER && 347 type != Character.MODIFIER_LETTER && 348 type != Character.DECIMAL_DIGIT_NUMBER) { 349 break; 350 } 351 } 352 353 return end; 354 } 355 356 public boolean canSelectArbitrarily() { 357 return true; 358 } 359 360 public void initialize(TextView widget, Spannable text) { 361 Selection.setSelection(text, 0); 362 } 363 364 public void onTakeFocus(TextView view, Spannable text, int dir) { 365 if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) { 366 Layout layout = view.getLayout(); 367 368 if (layout == null) { 369 /* 370 * This shouldn't be null, but do something sensible if it is. 371 */ 372 Selection.setSelection(text, text.length()); 373 } else { 374 /* 375 * Put the cursor at the end of the first line, which is 376 * either the last offset if there is only one line, or the 377 * offset before the first character of the second line 378 * if there is more than one line. 379 */ 380 if (layout.getLineCount() == 1) { 381 Selection.setSelection(text, text.length()); 382 } else { 383 Selection.setSelection(text, layout.getLineStart(1) - 1); 384 } 385 } 386 } else { 387 Selection.setSelection(text, text.length()); 388 } 389 } 390 391 public static MovementMethod getInstance() { 392 if (sInstance == null) 393 sInstance = new ArrowKeyMovementMethod(); 394 395 return sInstance; 396 } 397 398 private static ArrowKeyMovementMethod sInstance; 399} 400