19066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/* 29066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Copyright (C) 2006 The Android Open Source Project 39066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 49066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); 59066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * you may not use this file except in compliance with the License. 69066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * You may obtain a copy of the License at 79066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 89066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0 99066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Unless required by applicable law or agreed to in writing, software 119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, 129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * See the License for the specific language governing permissions and 149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * limitations under the License. 159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpackage android.text.method; 189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brownimport android.graphics.Rect; 20b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunneimport android.text.Layout; 21b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunneimport android.text.Selection; 22b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunneimport android.text.Spannable; 23cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnanimport android.view.InputDevice; 249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.view.KeyEvent; 259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.view.MotionEvent; 26b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunneimport android.view.View; 27b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunneimport android.widget.TextView; 289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown/** 3067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown * A movement method that provides cursor movement and selection. 3167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown * Supports displaying the context menu on DPad Center. 3267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown */ 3367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brownpublic class ArrowKeyMovementMethod extends BaseMovementMethod implements MovementMethod { 3467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown private static boolean isSelecting(Spannable buffer) { 35497a92cc5ba2176b8a8484b0a7da040eac0e887bJeff Brown return ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SHIFT_ON) == 1) || 36b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0)); 37b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne } 38b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne 39287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne private static int getCurrentLineTop(Spannable buffer, Layout layout) { 4067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return layout.getLineTop(layout.getLineForOffset(Selection.getSelectionEnd(buffer))); 41b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne } 42b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne 43287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne private static int getPageHeight(TextView widget) { 4467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown // This calculation does not take into account the view transformations that 4567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown // may have been applied to the child or its containers. In case of scaling or 4667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown // rotation, the calculated page height may be incorrect. 4767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown final Rect rect = new Rect(); 4867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return widget.getGlobalVisibleRect(rect) ? rect.height() : 0; 4967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } 509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 5167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown @Override 5267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode, 5367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown int movementMetaState, KeyEvent event) { 5467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown switch (keyCode) { 5567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown case KeyEvent.KEYCODE_DPAD_CENTER: 5667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { 5767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown if (event.getAction() == KeyEvent.ACTION_DOWN 5867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown && event.getRepeatCount() == 0 5967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown && MetaKeyKeyListener.getMetaState(buffer, 6014f10e5d5f51bc54ca2a45ee62d3cfb6debd3af0Raph Levien MetaKeyKeyListener.META_SELECTING, event) != 0) { 6167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return widget.showContextMenu(); 6267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } 6367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } 6467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown break; 6567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } 6667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return super.handleMovementKey(widget, buffer, keyCode, movementMetaState, event); 6767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } 6867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown 6967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown @Override 7067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown protected boolean left(TextView widget, Spannable buffer) { 7167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown final Layout layout = widget.getLayout(); 7267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown if (isSelecting(buffer)) { 7367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return Selection.extendLeft(buffer, layout); 749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } else { 7567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return Selection.moveLeft(buffer, layout); 769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 7967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown @Override 8067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown protected boolean right(TextView widget, Spannable buffer) { 8167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown final Layout layout = widget.getLayout(); 8267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown if (isSelecting(buffer)) { 8367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return Selection.extendRight(buffer, layout); 8467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } else { 8567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return Selection.moveRight(buffer, layout); 8667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } 8767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } 889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 8967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown @Override 9067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown protected boolean up(TextView widget, Spannable buffer) { 9167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown final Layout layout = widget.getLayout(); 9267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown if (isSelecting(buffer)) { 9367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return Selection.extendUp(buffer, layout); 949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } else { 9567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return Selection.moveUp(buffer, layout); 969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 9967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown @Override 10067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown protected boolean down(TextView widget, Spannable buffer) { 10167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown final Layout layout = widget.getLayout(); 10267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown if (isSelecting(buffer)) { 10367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return Selection.extendDown(buffer, layout); 10467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } else { 10567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return Selection.moveDown(buffer, layout); 10667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } 10767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } 1089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 10967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown @Override 11067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown protected boolean pageUp(TextView widget, Spannable buffer) { 11167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown final Layout layout = widget.getLayout(); 11267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown final boolean selecting = isSelecting(buffer); 11367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown final int targetY = getCurrentLineTop(buffer, layout) - getPageHeight(widget); 11467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown boolean handled = false; 11567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown for (;;) { 11667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown final int previousSelectionEnd = Selection.getSelectionEnd(buffer); 11767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown if (selecting) { 11867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown Selection.extendUp(buffer, layout); 1199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } else { 12067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown Selection.moveUp(buffer, layout); 1219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 12267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown if (Selection.getSelectionEnd(buffer) == previousSelectionEnd) { 12367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown break; 12467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } 12567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown handled = true; 12667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown if (getCurrentLineTop(buffer, layout) <= targetY) { 12767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown break; 1289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 13067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return handled; 1319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 13367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown @Override 13467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown protected boolean pageDown(TextView widget, Spannable buffer) { 13567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown final Layout layout = widget.getLayout(); 13667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown final boolean selecting = isSelecting(buffer); 13767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown final int targetY = getCurrentLineTop(buffer, layout) + getPageHeight(widget); 13867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown boolean handled = false; 13967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown for (;;) { 14067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown final int previousSelectionEnd = Selection.getSelectionEnd(buffer); 14167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown if (selecting) { 14267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown Selection.extendDown(buffer, layout); 1439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } else { 14467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown Selection.moveDown(buffer, layout); 1459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 14667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown if (Selection.getSelectionEnd(buffer) == previousSelectionEnd) { 14767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown break; 14867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } 14967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown handled = true; 15067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown if (getCurrentLineTop(buffer, layout) >= targetY) { 15167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown break; 1529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 15467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return handled; 1559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 15767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown @Override 15867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown protected boolean top(TextView widget, Spannable buffer) { 15967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown if (isSelecting(buffer)) { 16067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown Selection.extendSelection(buffer, 0); 16167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } else { 16267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown Selection.setSelection(buffer, 0); 1639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 16467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return true; 1659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 16767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown @Override 16867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown protected boolean bottom(TextView widget, Spannable buffer) { 16967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown if (isSelecting(buffer)) { 17067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown Selection.extendSelection(buffer, buffer.length()); 17167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } else { 17267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown Selection.setSelection(buffer, buffer.length()); 1739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 17467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return true; 17567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } 1769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 17767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown @Override 17867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown protected boolean lineStart(TextView widget, Spannable buffer) { 17967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown final Layout layout = widget.getLayout(); 18067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown if (isSelecting(buffer)) { 18167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return Selection.extendToLeftEdge(buffer, layout); 18267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } else { 18367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return Selection.moveToLeftEdge(buffer, layout); 1849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 18767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown @Override 18867b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown protected boolean lineEnd(TextView widget, Spannable buffer) { 18967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown final Layout layout = widget.getLayout(); 19067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown if (isSelecting(buffer)) { 19167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return Selection.extendToRightEdge(buffer, layout); 19267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } else { 19367b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return Selection.moveToRightEdge(buffer, layout); 19467b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown } 1959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 197e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey /** {@hide} */ 198e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey @Override 199e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey protected boolean leftWord(TextView widget, Spannable buffer) { 200287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne final int selectionEnd = widget.getSelectionEnd(); 2019d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne final WordIterator wordIterator = widget.getWordIterator(); 2029d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne wordIterator.setCharSequence(buffer, selectionEnd, selectionEnd); 2039d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne return Selection.moveToPreceding(buffer, wordIterator, isSelecting(buffer)); 204e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey } 205e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey 206e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey /** {@hide} */ 207e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey @Override 208e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey protected boolean rightWord(TextView widget, Spannable buffer) { 209287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne final int selectionEnd = widget.getSelectionEnd(); 2109d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne final WordIterator wordIterator = widget.getWordIterator(); 2119d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne wordIterator.setCharSequence(buffer, selectionEnd, selectionEnd); 2129d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne return Selection.moveToFollowing(buffer, wordIterator, isSelecting(buffer)); 213e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey } 214e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey 21567b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown @Override 21667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown protected boolean home(TextView widget, Spannable buffer) { 21767b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return lineStart(widget, buffer); 2189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 219ce08379e22609415971ece6ba3417d6d3fd338d2Maryam Garrett 22067b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown @Override 22167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown protected boolean end(TextView widget, Spannable buffer) { 22267b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown return lineEnd(widget, buffer); 2239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 224ce08379e22609415971ece6ba3417d6d3fd338d2Maryam Garrett 225cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan private static boolean isTouchSelecting(boolean isMouse, Spannable buffer) { 226cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan return isMouse ? Touch.isActivelySelecting(buffer) : isSelecting(buffer); 227cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan } 228cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan 22967b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown @Override 230b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { 231e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey int initialScrollX = -1; 232e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey int initialScrollY = -1; 2330eb704ca7a0844186e0755e86bc4afc23297797dGilles Debunne final int action = event.getAction(); 234cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE); 2350eb704ca7a0844186e0755e86bc4afc23297797dGilles Debunne 2360eb704ca7a0844186e0755e86bc4afc23297797dGilles Debunne if (action == MotionEvent.ACTION_UP) { 2371c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn initialScrollX = Touch.getInitialScrollX(widget, buffer); 2381c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn initialScrollY = Touch.getInitialScrollY(widget, buffer); 2391c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn } 240ce08379e22609415971ece6ba3417d6d3fd338d2Maryam Garrett 2419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project boolean handled = Touch.onTouchEvent(widget, buffer, event); 2429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 243b2a3dd88a53cc8c6d19f6dc8ec4f3d6c4abd9b54The Android Open Source Project if (widget.isFocused() && !widget.didTouchFocusSelect()) { 2440eb704ca7a0844186e0755e86bc4afc23297797dGilles Debunne if (action == MotionEvent.ACTION_DOWN) { 245cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan // Capture the mouse pointer down location to ensure selection starts 246cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan // right under the mouse (and is not influenced by cursor location). 247cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan // The code below needs to run for mouse events. 248cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan // For touch events, the code should run only when selection is active. 249cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan if (isMouse || isTouchSelecting(isMouse, buffer)) { 250cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan int offset = widget.getOffsetForPosition(event.getX(), event.getY()); 251cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT); 252cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan // Disallow intercepting of the touch events, so that 253cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan // users can scroll and select at the same time. 254cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan // without this, users would get booted out of select 255cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan // mode once the view detected it needed to scroll. 256cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan widget.getParent().requestDisallowInterceptTouchEvent(true); 257cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan } 258cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan } else if (action == MotionEvent.ACTION_MOVE) { 259e982dfc1bae36620f67371efc7b0a0f8adc9450dJeff Sharkey 260cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan // Cursor can be active at any location in the text while mouse pointer can start 261cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan // selection from a totally different location. Use LAST_TAP_DOWN span to ensure 262cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan // text selection will start from mouse pointer location. 263cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan if (isMouse && Touch.isSelectionStarted(buffer)) { 264cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan int offset = buffer.getSpanStart(LAST_TAP_DOWN); 265cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan Selection.setSelection(buffer, offset); 266cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan } 267ab9289320f598509cf358523ba173d69178a55eaMaryam Garrett 268cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan if (isTouchSelecting(isMouse, buffer) && handled) { 26939f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett // Before selecting, make sure we've moved out of the "slop". 27039f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett // handled will be true, if we're in select mode AND we're 27139f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett // OUT of the slop 27239f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett 27339f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett // Turn long press off while we're selecting. User needs to 274b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne // re-tap on the selection to enable long press 27539f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett widget.cancelLongPress(); 27639f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett 27739f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett // Update selection as we're moving the selection area. 27839f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett 27939f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett // Get the current touch position 2803bca69b09fb51116b5eb18fb91cb991c1450e384Gilles Debunne int offset = widget.getOffsetForPosition(event.getX(), event.getY()); 281b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne 282b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne Selection.extendSelection(buffer, offset); 28339f0efba92a4420f77e3abc53c367ea3cacde3cfMaryam Garrett return true; 284ce08379e22609415971ece6ba3417d6d3fd338d2Maryam Garrett } 2850eb704ca7a0844186e0755e86bc4afc23297797dGilles Debunne } else if (action == MotionEvent.ACTION_UP) { 2861c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn // If we have scrolled, then the up shouldn't move the cursor, 2871c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn // but we do need to make sure the cursor is still visible at 2881c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn // the current scroll offset to avoid the scroll jumping later 2891c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn // to show it. 2901c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) || 291b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) { 2921c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn widget.moveCursorToVisibleOffset(); 2931c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn return true; 2941c9aefd471cec85f905bea4099f4a641f347e0a0Dianne Hackborn } 295ce08379e22609415971ece6ba3417d6d3fd338d2Maryam Garrett 2963bca69b09fb51116b5eb18fb91cb991c1450e384Gilles Debunne int offset = widget.getOffsetForPosition(event.getX(), event.getY()); 297cc32bd83e4d420c1adaeb3ea40f8986471e54590Sujith Ramakrishnan if (isTouchSelecting(isMouse, buffer)) { 29862c4ad3b6ba162540c3fb57fcacb375ccfa53454Maryam Garrett buffer.removeSpan(LAST_TAP_DOWN); 299b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne Selection.extendSelection(buffer, offset); 3009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project MetaKeyKeyListener.adjustMetaAfterKeypress(buffer); 3039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project MetaKeyKeyListener.resetLockedMeta(buffer); 3049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return true; 3069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return handled; 3099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 31167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown @Override 3129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public boolean canSelectArbitrarily() { 3139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return true; 3149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 31667b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown @Override 3179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public void initialize(TextView widget, Spannable text) { 3189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project Selection.setSelection(text, 0); 3199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 32167b6ab72ae96a9f2be929de2c32c110df5390fddJeff Brown @Override 3229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public void onTakeFocus(TextView view, Spannable text, int dir) { 3239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) { 3242703a42d16af0e62da1bba02b6c935d98debf936Gilles Debunne if (view.getLayout() == null) { 3252703a42d16af0e62da1bba02b6c935d98debf936Gilles Debunne // This shouldn't be null, but do something sensible if it is. 3269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project Selection.setSelection(text, text.length()); 3279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } else { 3299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project Selection.setSelection(text, text.length()); 3309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static MovementMethod getInstance() { 334b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne if (sInstance == null) { 3359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project sInstance = new ArrowKeyMovementMethod(); 336b0d6ba1ec4f71b96cab7d1ff62b846d5cf162c4fGilles Debunne } 3379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return sInstance; 3399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 341ce08379e22609415971ece6ba3417d6d3fd338d2Maryam Garrett private static final Object LAST_TAP_DOWN = new Object(); 3429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private static ArrowKeyMovementMethod sInstance; 3439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project} 344