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, to the beginning if it is on the top line but not at the
    120      * start, or return false if the cursor is already on the top line.
    121      */
    122     public static boolean moveUp(Spannable text, Layout layout) {
    123         int start = getSelectionStart(text);
    124         int end = getSelectionEnd(text);
    125 
    126         if (start != end) {
    127             int min = Math.min(start, end);
    128             int max = Math.max(start, end);
    129 
    130             setSelection(text, min);
    131 
    132             if (min == 0 && max == text.length()) {
    133                 return false;
    134             }
    135 
    136             return true;
    137         } else {
    138             int line = layout.getLineForOffset(end);
    139 
    140             if (line > 0) {
    141                 int move;
    142 
    143                 if (layout.getParagraphDirection(line) ==
    144                     layout.getParagraphDirection(line - 1)) {
    145                     float h = layout.getPrimaryHorizontal(end);
    146                     move = layout.getOffsetForHorizontal(line - 1, h);
    147                 } else {
    148                     move = layout.getLineStart(line - 1);
    149                 }
    150 
    151                 setSelection(text, move);
    152                 return true;
    153             } else if (end != 0) {
    154                 setSelection(text, 0);
    155                 return true;
    156             }
    157         }
    158 
    159         return false;
    160     }
    161 
    162     /**
    163      * Move the cursor to the buffer offset physically below the current
    164      * offset, to the end of the buffer if it is on the bottom line but
    165      * not at the end, or return false if the cursor is already at the
    166      * end of the buffer.
    167      */
    168     public static boolean moveDown(Spannable text, Layout layout) {
    169         int start = getSelectionStart(text);
    170         int end = getSelectionEnd(text);
    171 
    172         if (start != end) {
    173             int min = Math.min(start, end);
    174             int max = Math.max(start, end);
    175 
    176             setSelection(text, max);
    177 
    178             if (min == 0 && max == text.length()) {
    179                 return false;
    180             }
    181 
    182             return true;
    183         } else {
    184             int line = layout.getLineForOffset(end);
    185 
    186             if (line < layout.getLineCount() - 1) {
    187                 int move;
    188 
    189                 if (layout.getParagraphDirection(line) ==
    190                     layout.getParagraphDirection(line + 1)) {
    191                     float h = layout.getPrimaryHorizontal(end);
    192                     move = layout.getOffsetForHorizontal(line + 1, h);
    193                 } else {
    194                     move = layout.getLineStart(line + 1);
    195                 }
    196 
    197                 setSelection(text, move);
    198                 return true;
    199             } else if (end != text.length()) {
    200                 setSelection(text, text.length());
    201                 return true;
    202             }
    203         }
    204 
    205         return false;
    206     }
    207 
    208     /**
    209      * Move the cursor to the buffer offset physically to the left of
    210      * the current offset, or return false if the cursor is already
    211      * at the left edge of the line and there is not another line to move it to.
    212      */
    213     public static boolean moveLeft(Spannable text, Layout layout) {
    214         int start = getSelectionStart(text);
    215         int end = getSelectionEnd(text);
    216 
    217         if (start != end) {
    218             setSelection(text, chooseHorizontal(layout, -1, start, end));
    219             return true;
    220         } else {
    221             int to = layout.getOffsetToLeftOf(end);
    222 
    223             if (to != end) {
    224                 setSelection(text, to);
    225                 return true;
    226             }
    227         }
    228 
    229         return false;
    230     }
    231 
    232     /**
    233      * Move the cursor to the buffer offset physically to the right of
    234      * the current offset, or return false if the cursor is already at
    235      * at the right edge of the line and there is not another line
    236      * to move it to.
    237      */
    238     public static boolean moveRight(Spannable text, Layout layout) {
    239         int start = getSelectionStart(text);
    240         int end = getSelectionEnd(text);
    241 
    242         if (start != end) {
    243             setSelection(text, chooseHorizontal(layout, 1, start, end));
    244             return true;
    245         } else {
    246             int to = layout.getOffsetToRightOf(end);
    247 
    248             if (to != end) {
    249                 setSelection(text, to);
    250                 return true;
    251             }
    252         }
    253 
    254         return false;
    255     }
    256 
    257     /**
    258      * Move the selection end to the buffer offset physically above
    259      * the current selection end.
    260      */
    261     public static boolean extendUp(Spannable text, Layout layout) {
    262         int end = getSelectionEnd(text);
    263         int line = layout.getLineForOffset(end);
    264 
    265         if (line > 0) {
    266             int move;
    267 
    268             if (layout.getParagraphDirection(line) ==
    269                 layout.getParagraphDirection(line - 1)) {
    270                 float h = layout.getPrimaryHorizontal(end);
    271                 move = layout.getOffsetForHorizontal(line - 1, h);
    272             } else {
    273                 move = layout.getLineStart(line - 1);
    274             }
    275 
    276             extendSelection(text, move);
    277             return true;
    278         } else if (end != 0) {
    279             extendSelection(text, 0);
    280             return true;
    281         }
    282 
    283         return true;
    284     }
    285 
    286     /**
    287      * Move the selection end to the buffer offset physically below
    288      * the current selection end.
    289      */
    290     public static boolean extendDown(Spannable text, Layout layout) {
    291         int end = getSelectionEnd(text);
    292         int line = layout.getLineForOffset(end);
    293 
    294         if (line < layout.getLineCount() - 1) {
    295             int move;
    296 
    297             if (layout.getParagraphDirection(line) ==
    298                 layout.getParagraphDirection(line + 1)) {
    299                 float h = layout.getPrimaryHorizontal(end);
    300                 move = layout.getOffsetForHorizontal(line + 1, h);
    301             } else {
    302                 move = layout.getLineStart(line + 1);
    303             }
    304 
    305             extendSelection(text, move);
    306             return true;
    307         } else if (end != text.length()) {
    308             extendSelection(text, text.length());
    309             return true;
    310         }
    311 
    312         return true;
    313     }
    314 
    315     /**
    316      * Move the selection end to the buffer offset physically to the left of
    317      * the current selection end.
    318      */
    319     public static boolean extendLeft(Spannable text, Layout layout) {
    320         int end = getSelectionEnd(text);
    321         int to = layout.getOffsetToLeftOf(end);
    322 
    323         if (to != end) {
    324             extendSelection(text, to);
    325             return true;
    326         }
    327 
    328         return true;
    329     }
    330 
    331     /**
    332      * Move the selection end to the buffer offset physically to the right of
    333      * the current selection end.
    334      */
    335     public static boolean extendRight(Spannable text, Layout layout) {
    336         int end = getSelectionEnd(text);
    337         int to = layout.getOffsetToRightOf(end);
    338 
    339         if (to != end) {
    340             extendSelection(text, to);
    341             return true;
    342         }
    343 
    344         return true;
    345     }
    346 
    347     public static boolean extendToLeftEdge(Spannable text, Layout layout) {
    348         int where = findEdge(text, layout, -1);
    349         extendSelection(text, where);
    350         return true;
    351     }
    352 
    353     public static boolean extendToRightEdge(Spannable text, Layout layout) {
    354         int where = findEdge(text, layout, 1);
    355         extendSelection(text, where);
    356         return true;
    357     }
    358 
    359     public static boolean moveToLeftEdge(Spannable text, Layout layout) {
    360         int where = findEdge(text, layout, -1);
    361         setSelection(text, where);
    362         return true;
    363     }
    364 
    365     public static boolean moveToRightEdge(Spannable text, Layout layout) {
    366         int where = findEdge(text, layout, 1);
    367         setSelection(text, where);
    368         return true;
    369     }
    370 
    371     /** {@hide} */
    372     public static interface PositionIterator {
    373         public static final int DONE = BreakIterator.DONE;
    374 
    375         public int preceding(int position);
    376         public int following(int position);
    377     }
    378 
    379     /** {@hide} */
    380     public static boolean moveToPreceding(
    381             Spannable text, PositionIterator iter, boolean extendSelection) {
    382         final int offset = iter.preceding(getSelectionEnd(text));
    383         if (offset != PositionIterator.DONE) {
    384             if (extendSelection) {
    385                 extendSelection(text, offset);
    386             } else {
    387                 setSelection(text, offset);
    388             }
    389         }
    390         return true;
    391     }
    392 
    393     /** {@hide} */
    394     public static boolean moveToFollowing(
    395             Spannable text, PositionIterator iter, boolean extendSelection) {
    396         final int offset = iter.following(getSelectionEnd(text));
    397         if (offset != PositionIterator.DONE) {
    398             if (extendSelection) {
    399                 extendSelection(text, offset);
    400             } else {
    401                 setSelection(text, offset);
    402             }
    403         }
    404         return true;
    405     }
    406 
    407     private static int findEdge(Spannable text, Layout layout, int dir) {
    408         int pt = getSelectionEnd(text);
    409         int line = layout.getLineForOffset(pt);
    410         int pdir = layout.getParagraphDirection(line);
    411 
    412         if (dir * pdir < 0) {
    413             return layout.getLineStart(line);
    414         } else {
    415             int end = layout.getLineEnd(line);
    416 
    417             if (line == layout.getLineCount() - 1)
    418                 return end;
    419             else
    420                 return end - 1;
    421         }
    422     }
    423 
    424     private static int chooseHorizontal(Layout layout, int direction,
    425                                         int off1, int off2) {
    426         int line1 = layout.getLineForOffset(off1);
    427         int line2 = layout.getLineForOffset(off2);
    428 
    429         if (line1 == line2) {
    430             // same line, so it goes by pure physical direction
    431 
    432             float h1 = layout.getPrimaryHorizontal(off1);
    433             float h2 = layout.getPrimaryHorizontal(off2);
    434 
    435             if (direction < 0) {
    436                 // to left
    437 
    438                 if (h1 < h2)
    439                     return off1;
    440                 else
    441                     return off2;
    442             } else {
    443                 // to right
    444 
    445                 if (h1 > h2)
    446                     return off1;
    447                 else
    448                     return off2;
    449             }
    450         } else {
    451             // different line, so which line is "left" and which is "right"
    452             // depends upon the directionality of the text
    453 
    454             // This only checks at one end, but it's not clear what the
    455             // right thing to do is if the ends don't agree.  Even if it
    456             // is wrong it should still not be too bad.
    457             int line = layout.getLineForOffset(off1);
    458             int textdir = layout.getParagraphDirection(line);
    459 
    460             if (textdir == direction)
    461                 return Math.max(off1, off2);
    462             else
    463                 return Math.min(off1, off2);
    464         }
    465     }
    466 
    467     private static final class START implements NoCopySpan { }
    468     private static final class END implements NoCopySpan { }
    469 
    470     /*
    471      * Public constants
    472      */
    473 
    474     public static final Object SELECTION_START = new START();
    475     public static final Object SELECTION_END = new END();
    476 }
    477