Home | History | Annotate | Download | only in text
      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;
     18 
     19 
     20 /**
     21  * Utility class for manipulating cursors and selections in CharSequences.
     22  * A cursor is a selection where the start and end are at the same offset.
     23  */
     24 public class Selection {
     25     private Selection() { /* cannot be instantiated */ }
     26 
     27     /*
     28      * Retrieving the selection
     29      */
     30 
     31     /**
     32      * Return the offset of the selection anchor or cursor, or -1 if
     33      * there is no selection or cursor.
     34      */
     35     public static final int getSelectionStart(CharSequence text) {
     36         if (text instanceof Spanned)
     37             return ((Spanned) text).getSpanStart(SELECTION_START);
     38         else
     39             return -1;
     40     }
     41 
     42     /**
     43      * Return the offset of the selection edge or cursor, or -1 if
     44      * there is no selection or cursor.
     45      */
     46     public static final int getSelectionEnd(CharSequence text) {
     47         if (text instanceof Spanned)
     48             return ((Spanned) text).getSpanStart(SELECTION_END);
     49         else
     50             return -1;
     51     }
     52 
     53     /*
     54      * Setting the selection
     55      */
     56 
     57     // private static int pin(int value, int min, int max) {
     58     //     return value < min ? 0 : (value > max ? max : value);
     59     // }
     60 
     61     /**
     62      * Set the selection anchor to <code>start</code> and the selection edge
     63      * to <code>stop</code>.
     64      */
     65     public static void setSelection(Spannable text, int start, int stop) {
     66         // int len = text.length();
     67         // start = pin(start, 0, len);  XXX remove unless we really need it
     68         // stop = pin(stop, 0, len);
     69 
     70         int ostart = getSelectionStart(text);
     71         int oend = getSelectionEnd(text);
     72 
     73         if (ostart != start || oend != stop) {
     74             text.setSpan(SELECTION_START, start, start,
     75                          Spanned.SPAN_POINT_POINT|Spanned.SPAN_INTERMEDIATE);
     76             text.setSpan(SELECTION_END, stop, stop,
     77                          Spanned.SPAN_POINT_POINT);
     78         }
     79     }
     80 
     81     /**
     82      * Move the cursor to offset <code>index</code>.
     83      */
     84     public static final void setSelection(Spannable text, int index) {
     85         setSelection(text, index, index);
     86     }
     87 
     88     /**
     89      * Select the entire text.
     90      */
     91     public static final void selectAll(Spannable text) {
     92         setSelection(text, 0, text.length());
     93     }
     94 
     95     /**
     96      * Move the selection edge to offset <code>index</code>.
     97      */
     98     public static final void extendSelection(Spannable text, int index) {
     99         if (text.getSpanStart(SELECTION_END) != index)
    100             text.setSpan(SELECTION_END, index, index, Spanned.SPAN_POINT_POINT);
    101     }
    102 
    103     /**
    104      * Remove the selection or cursor, if any, from the text.
    105      */
    106     public static final void removeSelection(Spannable text) {
    107         text.removeSpan(SELECTION_START);
    108         text.removeSpan(SELECTION_END);
    109     }
    110 
    111     /*
    112      * Moving the selection within the layout
    113      */
    114 
    115     /**
    116      * Move the cursor to the buffer offset physically above the current
    117      * offset, or return false if the cursor is already on the top line.
    118      */
    119     public static boolean moveUp(Spannable text, Layout layout) {
    120         int start = getSelectionStart(text);
    121         int end = getSelectionEnd(text);
    122 
    123         if (start != end) {
    124             int min = Math.min(start, end);
    125             int max = Math.max(start, end);
    126 
    127             setSelection(text, min);
    128 
    129             if (min == 0 && max == text.length()) {
    130                 return false;
    131             }
    132 
    133             return true;
    134         } else {
    135             int line = layout.getLineForOffset(end);
    136 
    137             if (line > 0) {
    138                 int move;
    139 
    140                 if (layout.getParagraphDirection(line) ==
    141                     layout.getParagraphDirection(line - 1)) {
    142                     float h = layout.getPrimaryHorizontal(end);
    143                     move = layout.getOffsetForHorizontal(line - 1, h);
    144                 } else {
    145                     move = layout.getLineStart(line - 1);
    146                 }
    147 
    148                 setSelection(text, move);
    149                 return true;
    150             }
    151         }
    152 
    153         return false;
    154     }
    155 
    156     /**
    157      * Move the cursor to the buffer offset physically below the current
    158      * offset, or return false if the cursor is already on the bottom line.
    159      */
    160     public static boolean moveDown(Spannable text, Layout layout) {
    161         int start = getSelectionStart(text);
    162         int end = getSelectionEnd(text);
    163 
    164         if (start != end) {
    165             int min = Math.min(start, end);
    166             int max = Math.max(start, end);
    167 
    168             setSelection(text, max);
    169 
    170             if (min == 0 && max == text.length()) {
    171                 return false;
    172             }
    173 
    174             return true;
    175         } else {
    176             int line = layout.getLineForOffset(end);
    177 
    178             if (line < layout.getLineCount() - 1) {
    179                 int move;
    180 
    181                 if (layout.getParagraphDirection(line) ==
    182                     layout.getParagraphDirection(line + 1)) {
    183                     float h = layout.getPrimaryHorizontal(end);
    184                     move = layout.getOffsetForHorizontal(line + 1, h);
    185                 } else {
    186                     move = layout.getLineStart(line + 1);
    187                 }
    188 
    189                 setSelection(text, move);
    190                 return true;
    191             }
    192         }
    193 
    194         return false;
    195     }
    196 
    197     /**
    198      * Move the cursor to the buffer offset physically to the left of
    199      * the current offset, or return false if the cursor is already
    200      * at the left edge of the line and there is not another line to move it to.
    201      */
    202     public static boolean moveLeft(Spannable text, Layout layout) {
    203         int start = getSelectionStart(text);
    204         int end = getSelectionEnd(text);
    205 
    206         if (start != end) {
    207             setSelection(text, chooseHorizontal(layout, -1, start, end));
    208             return true;
    209         } else {
    210             int to = layout.getOffsetToLeftOf(end);
    211 
    212             if (to != end) {
    213                 setSelection(text, to);
    214                 return true;
    215             }
    216         }
    217 
    218         return false;
    219     }
    220 
    221     /**
    222      * Move the cursor to the buffer offset physically to the right of
    223      * the current offset, or return false if the cursor is already at
    224      * at the right edge of the line and there is not another line
    225      * to move it to.
    226      */
    227     public static boolean moveRight(Spannable text, Layout layout) {
    228         int start = getSelectionStart(text);
    229         int end = getSelectionEnd(text);
    230 
    231         if (start != end) {
    232             setSelection(text, chooseHorizontal(layout, 1, start, end));
    233             return true;
    234         } else {
    235             int to = layout.getOffsetToRightOf(end);
    236 
    237             if (to != end) {
    238                 setSelection(text, to);
    239                 return true;
    240             }
    241         }
    242 
    243         return false;
    244     }
    245 
    246     /**
    247      * Move the selection end to the buffer offset physically above
    248      * the current selection end.
    249      */
    250     public static boolean extendUp(Spannable text, Layout layout) {
    251         int end = getSelectionEnd(text);
    252         int line = layout.getLineForOffset(end);
    253 
    254         if (line > 0) {
    255             int move;
    256 
    257             if (layout.getParagraphDirection(line) ==
    258                 layout.getParagraphDirection(line - 1)) {
    259                 float h = layout.getPrimaryHorizontal(end);
    260                 move = layout.getOffsetForHorizontal(line - 1, h);
    261             } else {
    262                 move = layout.getLineStart(line - 1);
    263             }
    264 
    265             extendSelection(text, move);
    266             return true;
    267         } else if (end != 0) {
    268             extendSelection(text, 0);
    269             return true;
    270         }
    271 
    272         return true;
    273     }
    274 
    275     /**
    276      * Move the selection end to the buffer offset physically below
    277      * the current selection end.
    278      */
    279     public static boolean extendDown(Spannable text, Layout layout) {
    280         int end = getSelectionEnd(text);
    281         int line = layout.getLineForOffset(end);
    282 
    283         if (line < layout.getLineCount() - 1) {
    284             int move;
    285 
    286             if (layout.getParagraphDirection(line) ==
    287                 layout.getParagraphDirection(line + 1)) {
    288                 float h = layout.getPrimaryHorizontal(end);
    289                 move = layout.getOffsetForHorizontal(line + 1, h);
    290             } else {
    291                 move = layout.getLineStart(line + 1);
    292             }
    293 
    294             extendSelection(text, move);
    295             return true;
    296         } else if (end != text.length()) {
    297             extendSelection(text, text.length());
    298             return true;
    299         }
    300 
    301         return true;
    302     }
    303 
    304     /**
    305      * Move the selection end to the buffer offset physically to the left of
    306      * the current selection end.
    307      */
    308     public static boolean extendLeft(Spannable text, Layout layout) {
    309         int end = getSelectionEnd(text);
    310         int to = layout.getOffsetToLeftOf(end);
    311 
    312         if (to != end) {
    313             extendSelection(text, to);
    314             return true;
    315         }
    316 
    317         return true;
    318     }
    319 
    320     /**
    321      * Move the selection end to the buffer offset physically to the right of
    322      * the current selection end.
    323      */
    324     public static boolean extendRight(Spannable text, Layout layout) {
    325         int end = getSelectionEnd(text);
    326         int to = layout.getOffsetToRightOf(end);
    327 
    328         if (to != end) {
    329             extendSelection(text, to);
    330             return true;
    331         }
    332 
    333         return true;
    334     }
    335 
    336     public static boolean extendToLeftEdge(Spannable text, Layout layout) {
    337         int where = findEdge(text, layout, -1);
    338         extendSelection(text, where);
    339         return true;
    340     }
    341 
    342     public static boolean extendToRightEdge(Spannable text, Layout layout) {
    343         int where = findEdge(text, layout, 1);
    344         extendSelection(text, where);
    345         return true;
    346     }
    347 
    348     public static boolean moveToLeftEdge(Spannable text, Layout layout) {
    349         int where = findEdge(text, layout, -1);
    350         setSelection(text, where);
    351         return true;
    352     }
    353 
    354     public static boolean moveToRightEdge(Spannable text, Layout layout) {
    355         int where = findEdge(text, layout, 1);
    356         setSelection(text, where);
    357         return true;
    358     }
    359 
    360     private static int findEdge(Spannable text, Layout layout, int dir) {
    361         int pt = getSelectionEnd(text);
    362         int line = layout.getLineForOffset(pt);
    363         int pdir = layout.getParagraphDirection(line);
    364 
    365         if (dir * pdir < 0) {
    366             return layout.getLineStart(line);
    367         } else {
    368             int end = layout.getLineEnd(line);
    369 
    370             if (line == layout.getLineCount() - 1)
    371                 return end;
    372             else
    373                 return end - 1;
    374         }
    375     }
    376 
    377     private static int chooseHorizontal(Layout layout, int direction,
    378                                         int off1, int off2) {
    379         int line1 = layout.getLineForOffset(off1);
    380         int line2 = layout.getLineForOffset(off2);
    381 
    382         if (line1 == line2) {
    383             // same line, so it goes by pure physical direction
    384 
    385             float h1 = layout.getPrimaryHorizontal(off1);
    386             float h2 = layout.getPrimaryHorizontal(off2);
    387 
    388             if (direction < 0) {
    389                 // to left
    390 
    391                 if (h1 < h2)
    392                     return off1;
    393                 else
    394                     return off2;
    395             } else {
    396                 // to right
    397 
    398                 if (h1 > h2)
    399                     return off1;
    400                 else
    401                     return off2;
    402             }
    403         } else {
    404             // different line, so which line is "left" and which is "right"
    405             // depends upon the directionality of the text
    406 
    407             // This only checks at one end, but it's not clear what the
    408             // right thing to do is if the ends don't agree.  Even if it
    409             // is wrong it should still not be too bad.
    410             int line = layout.getLineForOffset(off1);
    411             int textdir = layout.getParagraphDirection(line);
    412 
    413             if (textdir == direction)
    414                 return Math.max(off1, off2);
    415             else
    416                 return Math.min(off1, off2);
    417         }
    418     }
    419 
    420     private static final class START implements NoCopySpan { }
    421     private static final class END implements NoCopySpan { }
    422 
    423     /*
    424      * Public constants
    425      */
    426 
    427     public static final Object SELECTION_START = new START();
    428     public static final Object SELECTION_END = new END();
    429 }
    430