Home | History | Annotate | Download | only in method
      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 
     17 package android.text.method;
     18 
     19 import android.graphics.Rect;
     20 import android.text.Layout;
     21 import android.text.Selection;
     22 import android.text.Spannable;
     23 import android.view.InputDevice;
     24 import android.view.KeyEvent;
     25 import android.view.MotionEvent;
     26 import android.view.View;
     27 import android.widget.TextView;
     28 
     29 /**
     30  * A movement method that provides cursor movement and selection.
     31  * Supports displaying the context menu on DPad Center.
     32  */
     33 public class ArrowKeyMovementMethod extends BaseMovementMethod implements MovementMethod {
     34     private static boolean isSelecting(Spannable buffer) {
     35         return ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SHIFT_ON) == 1) ||
     36                 (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0));
     37     }
     38 
     39     private static int getCurrentLineTop(Spannable buffer, Layout layout) {
     40         return layout.getLineTop(layout.getLineForOffset(Selection.getSelectionEnd(buffer)));
     41     }
     42 
     43     private static int getPageHeight(TextView widget) {
     44         // This calculation does not take into account the view transformations that
     45         // may have been applied to the child or its containers.  In case of scaling or
     46         // rotation, the calculated page height may be incorrect.
     47         final Rect rect = new Rect();
     48         return widget.getGlobalVisibleRect(rect) ? rect.height() : 0;
     49     }
     50 
     51     @Override
     52     protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode,
     53             int movementMetaState, KeyEvent event) {
     54         switch (keyCode) {
     55             case KeyEvent.KEYCODE_DPAD_CENTER:
     56                 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
     57                     if (event.getAction() == KeyEvent.ACTION_DOWN
     58                             && event.getRepeatCount() == 0
     59                             && MetaKeyKeyListener.getMetaState(buffer,
     60                                         MetaKeyKeyListener.META_SELECTING, event) != 0) {
     61                         return widget.showContextMenu();
     62                     }
     63                 }
     64                 break;
     65         }
     66         return super.handleMovementKey(widget, buffer, keyCode, movementMetaState, event);
     67     }
     68 
     69     @Override
     70     protected boolean left(TextView widget, Spannable buffer) {
     71         final Layout layout = widget.getLayout();
     72         if (isSelecting(buffer)) {
     73             return Selection.extendLeft(buffer, layout);
     74         } else {
     75             return Selection.moveLeft(buffer, layout);
     76         }
     77     }
     78 
     79     @Override
     80     protected boolean right(TextView widget, Spannable buffer) {
     81         final Layout layout = widget.getLayout();
     82         if (isSelecting(buffer)) {
     83             return Selection.extendRight(buffer, layout);
     84         } else {
     85             return Selection.moveRight(buffer, layout);
     86         }
     87     }
     88 
     89     @Override
     90     protected boolean up(TextView widget, Spannable buffer) {
     91         final Layout layout = widget.getLayout();
     92         if (isSelecting(buffer)) {
     93             return Selection.extendUp(buffer, layout);
     94         } else {
     95             return Selection.moveUp(buffer, layout);
     96         }
     97     }
     98 
     99     @Override
    100     protected boolean down(TextView widget, Spannable buffer) {
    101         final Layout layout = widget.getLayout();
    102         if (isSelecting(buffer)) {
    103             return Selection.extendDown(buffer, layout);
    104         } else {
    105             return Selection.moveDown(buffer, layout);
    106         }
    107     }
    108 
    109     @Override
    110     protected boolean pageUp(TextView widget, Spannable buffer) {
    111         final Layout layout = widget.getLayout();
    112         final boolean selecting = isSelecting(buffer);
    113         final int targetY = getCurrentLineTop(buffer, layout) - getPageHeight(widget);
    114         boolean handled = false;
    115         for (;;) {
    116             final int previousSelectionEnd = Selection.getSelectionEnd(buffer);
    117             if (selecting) {
    118                 Selection.extendUp(buffer, layout);
    119             } else {
    120                 Selection.moveUp(buffer, layout);
    121             }
    122             if (Selection.getSelectionEnd(buffer) == previousSelectionEnd) {
    123                 break;
    124             }
    125             handled = true;
    126             if (getCurrentLineTop(buffer, layout) <= targetY) {
    127                 break;
    128             }
    129         }
    130         return handled;
    131     }
    132 
    133     @Override
    134     protected boolean pageDown(TextView widget, Spannable buffer) {
    135         final Layout layout = widget.getLayout();
    136         final boolean selecting = isSelecting(buffer);
    137         final int targetY = getCurrentLineTop(buffer, layout) + getPageHeight(widget);
    138         boolean handled = false;
    139         for (;;) {
    140             final int previousSelectionEnd = Selection.getSelectionEnd(buffer);
    141             if (selecting) {
    142                 Selection.extendDown(buffer, layout);
    143             } else {
    144                 Selection.moveDown(buffer, layout);
    145             }
    146             if (Selection.getSelectionEnd(buffer) == previousSelectionEnd) {
    147                 break;
    148             }
    149             handled = true;
    150             if (getCurrentLineTop(buffer, layout) >= targetY) {
    151                 break;
    152             }
    153         }
    154         return handled;
    155     }
    156 
    157     @Override
    158     protected boolean top(TextView widget, Spannable buffer) {
    159         if (isSelecting(buffer)) {
    160             Selection.extendSelection(buffer, 0);
    161         } else {
    162             Selection.setSelection(buffer, 0);
    163         }
    164         return true;
    165     }
    166 
    167     @Override
    168     protected boolean bottom(TextView widget, Spannable buffer) {
    169         if (isSelecting(buffer)) {
    170             Selection.extendSelection(buffer, buffer.length());
    171         } else {
    172             Selection.setSelection(buffer, buffer.length());
    173         }
    174         return true;
    175     }
    176 
    177     @Override
    178     protected boolean lineStart(TextView widget, Spannable buffer) {
    179         final Layout layout = widget.getLayout();
    180         if (isSelecting(buffer)) {
    181             return Selection.extendToLeftEdge(buffer, layout);
    182         } else {
    183             return Selection.moveToLeftEdge(buffer, layout);
    184         }
    185     }
    186 
    187     @Override
    188     protected boolean lineEnd(TextView widget, Spannable buffer) {
    189         final Layout layout = widget.getLayout();
    190         if (isSelecting(buffer)) {
    191             return Selection.extendToRightEdge(buffer, layout);
    192         } else {
    193             return Selection.moveToRightEdge(buffer, layout);
    194         }
    195     }
    196 
    197     /** {@hide} */
    198     @Override
    199     protected boolean leftWord(TextView widget, Spannable buffer) {
    200         final int selectionEnd = widget.getSelectionEnd();
    201         final WordIterator wordIterator = widget.getWordIterator();
    202         wordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
    203         return Selection.moveToPreceding(buffer, wordIterator, isSelecting(buffer));
    204     }
    205 
    206     /** {@hide} */
    207     @Override
    208     protected boolean rightWord(TextView widget, Spannable buffer) {
    209         final int selectionEnd = widget.getSelectionEnd();
    210         final WordIterator wordIterator = widget.getWordIterator();
    211         wordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
    212         return Selection.moveToFollowing(buffer, wordIterator, isSelecting(buffer));
    213     }
    214 
    215     @Override
    216     protected boolean home(TextView widget, Spannable buffer) {
    217         return lineStart(widget, buffer);
    218     }
    219 
    220     @Override
    221     protected boolean end(TextView widget, Spannable buffer) {
    222         return lineEnd(widget, buffer);
    223     }
    224 
    225     private static boolean isTouchSelecting(boolean isMouse, Spannable buffer) {
    226         return isMouse ? Touch.isActivelySelecting(buffer) : isSelecting(buffer);
    227     }
    228 
    229     @Override
    230     public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
    231         int initialScrollX = -1;
    232         int initialScrollY = -1;
    233         final int action = event.getAction();
    234         final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
    235 
    236         if (action == MotionEvent.ACTION_UP) {
    237             initialScrollX = Touch.getInitialScrollX(widget, buffer);
    238             initialScrollY = Touch.getInitialScrollY(widget, buffer);
    239         }
    240 
    241         boolean handled = Touch.onTouchEvent(widget, buffer, event);
    242 
    243         if (widget.isFocused() && !widget.didTouchFocusSelect()) {
    244             if (action == MotionEvent.ACTION_DOWN) {
    245                 // Capture the mouse pointer down location to ensure selection starts
    246                 // right under the mouse (and is not influenced by cursor location).
    247                 // The code below needs to run for mouse events.
    248                 // For touch events, the code should run only when selection is active.
    249                 if (isMouse || isTouchSelecting(isMouse, buffer)) {
    250                     int offset = widget.getOffsetForPosition(event.getX(), event.getY());
    251                     buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
    252                     // Disallow intercepting of the touch events, so that
    253                     // users can scroll and select at the same time.
    254                     // without this, users would get booted out of select
    255                     // mode once the view detected it needed to scroll.
    256                     widget.getParent().requestDisallowInterceptTouchEvent(true);
    257                 }
    258             } else if (action == MotionEvent.ACTION_MOVE) {
    259 
    260                 // Cursor can be active at any location in the text while mouse pointer can start
    261                 // selection from a totally different location. Use LAST_TAP_DOWN span to ensure
    262                 // text selection will start from mouse pointer location.
    263                 if (isMouse && Touch.isSelectionStarted(buffer)) {
    264                     int offset = buffer.getSpanStart(LAST_TAP_DOWN);
    265                     Selection.setSelection(buffer, offset);
    266                 }
    267 
    268                 if (isTouchSelecting(isMouse, buffer) && handled) {
    269                     // Before selecting, make sure we've moved out of the "slop".
    270                     // handled will be true, if we're in select mode AND we're
    271                     // OUT of the slop
    272 
    273                     // Turn long press off while we're selecting. User needs to
    274                     // re-tap on the selection to enable long press
    275                     widget.cancelLongPress();
    276 
    277                     // Update selection as we're moving the selection area.
    278 
    279                     // Get the current touch position
    280                     int offset = widget.getOffsetForPosition(event.getX(), event.getY());
    281 
    282                     Selection.extendSelection(buffer, offset);
    283                     return true;
    284                 }
    285             } else if (action == MotionEvent.ACTION_UP) {
    286                 // If we have scrolled, then the up shouldn't move the cursor,
    287                 // but we do need to make sure the cursor is still visible at
    288                 // the current scroll offset to avoid the scroll jumping later
    289                 // to show it.
    290                 if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) ||
    291                     (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
    292                     widget.moveCursorToVisibleOffset();
    293                     return true;
    294                 }
    295 
    296                 int offset = widget.getOffsetForPosition(event.getX(), event.getY());
    297                 if (isTouchSelecting(isMouse, buffer)) {
    298                     buffer.removeSpan(LAST_TAP_DOWN);
    299                     Selection.extendSelection(buffer, offset);
    300                 }
    301 
    302                 MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
    303                 MetaKeyKeyListener.resetLockedMeta(buffer);
    304 
    305                 return true;
    306             }
    307         }
    308         return handled;
    309     }
    310 
    311     @Override
    312     public boolean canSelectArbitrarily() {
    313         return true;
    314     }
    315 
    316     @Override
    317     public void initialize(TextView widget, Spannable text) {
    318         Selection.setSelection(text, 0);
    319     }
    320 
    321     @Override
    322     public void onTakeFocus(TextView view, Spannable text, int dir) {
    323         if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
    324             if (view.getLayout() == null) {
    325                 // This shouldn't be null, but do something sensible if it is.
    326                 Selection.setSelection(text, text.length());
    327             }
    328         } else {
    329             Selection.setSelection(text, text.length());
    330         }
    331     }
    332 
    333     public static MovementMethod getInstance() {
    334         if (sInstance == null) {
    335             sInstance = new ArrowKeyMovementMethod();
    336         }
    337 
    338         return sInstance;
    339     }
    340 
    341     private static final Object LAST_TAP_DOWN = new Object();
    342     private static ArrowKeyMovementMethod sInstance;
    343 }
    344