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