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) != 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 handled = Touch.onTouchEvent(widget, buffer, event);
    236 
    237         if (widget.isFocused() && !widget.didTouchFocusSelect()) {
    238             if (action == MotionEvent.ACTION_DOWN) {
    239               if (isSelecting(buffer)) {
    240                   int offset = widget.getOffsetForPosition(event.getX(), event.getY());
    241 
    242                   buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
    243 
    244                   // Disallow intercepting of the touch events, so that
    245                   // users can scroll and select at the same time.
    246                   // without this, users would get booted out of select
    247                   // mode once the view detected it needed to scroll.
    248                   widget.getParent().requestDisallowInterceptTouchEvent(true);
    249               }
    250             } else if (action == MotionEvent.ACTION_MOVE) {
    251                 if (isSelecting(buffer) && handled) {
    252                     // Before selecting, make sure we've moved out of the "slop".
    253                     // handled will be true, if we're in select mode AND we're
    254                     // OUT of the slop
    255 
    256                     // Turn long press off while we're selecting. User needs to
    257                     // re-tap on the selection to enable long press
    258                     widget.cancelLongPress();
    259 
    260                     // Update selection as we're moving the selection area.
    261 
    262                     // Get the current touch position
    263                     int offset = widget.getOffsetForPosition(event.getX(), event.getY());
    264 
    265                     Selection.extendSelection(buffer, offset);
    266                     return true;
    267                 }
    268             } else if (action == MotionEvent.ACTION_UP) {
    269                 // If we have scrolled, then the up shouldn't move the cursor,
    270                 // but we do need to make sure the cursor is still visible at
    271                 // the current scroll offset to avoid the scroll jumping later
    272                 // to show it.
    273                 if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) ||
    274                     (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
    275                     widget.moveCursorToVisibleOffset();
    276                     return true;
    277                 }
    278 
    279                 int offset = widget.getOffsetForPosition(event.getX(), event.getY());
    280                 if (isSelecting(buffer)) {
    281                     buffer.removeSpan(LAST_TAP_DOWN);
    282                     Selection.extendSelection(buffer, offset);
    283                 }
    284 
    285                 MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
    286                 MetaKeyKeyListener.resetLockedMeta(buffer);
    287 
    288                 return true;
    289             }
    290         }
    291 
    292         return handled;
    293     }
    294 
    295     @Override
    296     public boolean canSelectArbitrarily() {
    297         return true;
    298     }
    299 
    300     @Override
    301     public void initialize(TextView widget, Spannable text) {
    302         Selection.setSelection(text, 0);
    303     }
    304 
    305     @Override
    306     public void onTakeFocus(TextView view, Spannable text, int dir) {
    307         if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
    308             if (view.getLayout() == null) {
    309                 // This shouldn't be null, but do something sensible if it is.
    310                 Selection.setSelection(text, text.length());
    311             }
    312         } else {
    313             Selection.setSelection(text, text.length());
    314         }
    315     }
    316 
    317     public static MovementMethod getInstance() {
    318         if (sInstance == null) {
    319             sInstance = new ArrowKeyMovementMethod();
    320         }
    321 
    322         return sInstance;
    323     }
    324 
    325     private static final Object LAST_TAP_DOWN = new Object();
    326     private static ArrowKeyMovementMethod sInstance;
    327 }
    328