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