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.text.Layout;
     20 import android.text.NoCopySpan;
     21 import android.text.Selection;
     22 import android.text.Spannable;
     23 import android.text.style.ClickableSpan;
     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 traverses links in the text buffer and scrolls if necessary.
     31  * Supports clicking on links with DPad Center or Enter.
     32  */
     33 public class LinkMovementMethod extends ScrollingMovementMethod {
     34     private static final int CLICK = 1;
     35     private static final int UP = 2;
     36     private static final int DOWN = 3;
     37 
     38     @Override
     39     public boolean canSelectArbitrarily() {
     40         return true;
     41     }
     42 
     43     @Override
     44     protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode,
     45             int movementMetaState, KeyEvent event) {
     46         switch (keyCode) {
     47             case KeyEvent.KEYCODE_DPAD_CENTER:
     48             case KeyEvent.KEYCODE_ENTER:
     49                 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
     50                     if (event.getAction() == KeyEvent.ACTION_DOWN &&
     51                             event.getRepeatCount() == 0 && action(CLICK, widget, buffer)) {
     52                         return true;
     53                     }
     54                 }
     55                 break;
     56         }
     57         return super.handleMovementKey(widget, buffer, keyCode, movementMetaState, event);
     58     }
     59 
     60     @Override
     61     protected boolean up(TextView widget, Spannable buffer) {
     62         if (action(UP, widget, buffer)) {
     63             return true;
     64         }
     65 
     66         return super.up(widget, buffer);
     67     }
     68 
     69     @Override
     70     protected boolean down(TextView widget, Spannable buffer) {
     71         if (action(DOWN, widget, buffer)) {
     72             return true;
     73         }
     74 
     75         return super.down(widget, buffer);
     76     }
     77 
     78     @Override
     79     protected boolean left(TextView widget, Spannable buffer) {
     80         if (action(UP, widget, buffer)) {
     81             return true;
     82         }
     83 
     84         return super.left(widget, buffer);
     85     }
     86 
     87     @Override
     88     protected boolean right(TextView widget, Spannable buffer) {
     89         if (action(DOWN, widget, buffer)) {
     90             return true;
     91         }
     92 
     93         return super.right(widget, buffer);
     94     }
     95 
     96     private boolean action(int what, TextView widget, Spannable buffer) {
     97         Layout layout = widget.getLayout();
     98 
     99         int padding = widget.getTotalPaddingTop() +
    100                       widget.getTotalPaddingBottom();
    101         int areatop = widget.getScrollY();
    102         int areabot = areatop + widget.getHeight() - padding;
    103 
    104         int linetop = layout.getLineForVertical(areatop);
    105         int linebot = layout.getLineForVertical(areabot);
    106 
    107         int first = layout.getLineStart(linetop);
    108         int last = layout.getLineEnd(linebot);
    109 
    110         ClickableSpan[] candidates = buffer.getSpans(first, last, ClickableSpan.class);
    111 
    112         int a = Selection.getSelectionStart(buffer);
    113         int b = Selection.getSelectionEnd(buffer);
    114 
    115         int selStart = Math.min(a, b);
    116         int selEnd = Math.max(a, b);
    117 
    118         if (selStart < 0) {
    119             if (buffer.getSpanStart(FROM_BELOW) >= 0) {
    120                 selStart = selEnd = buffer.length();
    121             }
    122         }
    123 
    124         if (selStart > last)
    125             selStart = selEnd = Integer.MAX_VALUE;
    126         if (selEnd < first)
    127             selStart = selEnd = -1;
    128 
    129         switch (what) {
    130         case CLICK:
    131             if (selStart == selEnd) {
    132                 return false;
    133             }
    134 
    135             ClickableSpan[] link = buffer.getSpans(selStart, selEnd, ClickableSpan.class);
    136 
    137             if (link.length != 1)
    138                 return false;
    139 
    140             link[0].onClick(widget);
    141             break;
    142 
    143         case UP:
    144             int beststart, bestend;
    145 
    146             beststart = -1;
    147             bestend = -1;
    148 
    149             for (int i = 0; i < candidates.length; i++) {
    150                 int end = buffer.getSpanEnd(candidates[i]);
    151 
    152                 if (end < selEnd || selStart == selEnd) {
    153                     if (end > bestend) {
    154                         beststart = buffer.getSpanStart(candidates[i]);
    155                         bestend = end;
    156                     }
    157                 }
    158             }
    159 
    160             if (beststart >= 0) {
    161                 Selection.setSelection(buffer, bestend, beststart);
    162                 return true;
    163             }
    164 
    165             break;
    166 
    167         case DOWN:
    168             beststart = Integer.MAX_VALUE;
    169             bestend = Integer.MAX_VALUE;
    170 
    171             for (int i = 0; i < candidates.length; i++) {
    172                 int start = buffer.getSpanStart(candidates[i]);
    173 
    174                 if (start > selStart || selStart == selEnd) {
    175                     if (start < beststart) {
    176                         beststart = start;
    177                         bestend = buffer.getSpanEnd(candidates[i]);
    178                     }
    179                 }
    180             }
    181 
    182             if (bestend < Integer.MAX_VALUE) {
    183                 Selection.setSelection(buffer, beststart, bestend);
    184                 return true;
    185             }
    186 
    187             break;
    188         }
    189 
    190         return false;
    191     }
    192 
    193     @Override
    194     public boolean onTouchEvent(TextView widget, Spannable buffer,
    195                                 MotionEvent event) {
    196         int action = event.getAction();
    197 
    198         if (action == MotionEvent.ACTION_UP ||
    199             action == MotionEvent.ACTION_DOWN) {
    200             int x = (int) event.getX();
    201             int y = (int) event.getY();
    202 
    203             x -= widget.getTotalPaddingLeft();
    204             y -= widget.getTotalPaddingTop();
    205 
    206             x += widget.getScrollX();
    207             y += widget.getScrollY();
    208 
    209             Layout layout = widget.getLayout();
    210             int line = layout.getLineForVertical(y);
    211             int off = layout.getOffsetForHorizontal(line, x);
    212 
    213             ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
    214 
    215             if (link.length != 0) {
    216                 if (action == MotionEvent.ACTION_UP) {
    217                     link[0].onClick(widget);
    218                 } else if (action == MotionEvent.ACTION_DOWN) {
    219                     Selection.setSelection(buffer,
    220                                            buffer.getSpanStart(link[0]),
    221                                            buffer.getSpanEnd(link[0]));
    222                 }
    223 
    224                 return true;
    225             } else {
    226                 Selection.removeSelection(buffer);
    227             }
    228         }
    229 
    230         return super.onTouchEvent(widget, buffer, event);
    231     }
    232 
    233     @Override
    234     public void initialize(TextView widget, Spannable text) {
    235         Selection.removeSelection(text);
    236         text.removeSpan(FROM_BELOW);
    237     }
    238 
    239     @Override
    240     public void onTakeFocus(TextView view, Spannable text, int dir) {
    241         Selection.removeSelection(text);
    242 
    243         if ((dir & View.FOCUS_BACKWARD) != 0) {
    244             text.setSpan(FROM_BELOW, 0, 0, Spannable.SPAN_POINT_POINT);
    245         } else {
    246             text.removeSpan(FROM_BELOW);
    247         }
    248     }
    249 
    250     public static MovementMethod getInstance() {
    251         if (sInstance == null)
    252             sInstance = new LinkMovementMethod();
    253 
    254         return sInstance;
    255     }
    256 
    257     private static LinkMovementMethod sInstance;
    258     private static Object FROM_BELOW = new NoCopySpan.Concrete();
    259 }
    260