• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
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      */
getSelectionStart(CharSequence text)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      */
getSelectionEnd(CharSequence text)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      */
setSelection(Spannable text, int start, int stop)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      */
setSelection(Spannable text, int index)84     public static final void setSelection(Spannable text, int index) {
85         setSelection(text, index, index);
86     }
87 
88     /**
89      * Select the entire text.
90      */
selectAll(Spannable text)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      */
extendSelection(Spannable text, int index)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      */
removeSelection(Spannable text)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      */
moveUp(Spannable text, Layout layout)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      */
moveDown(Spannable text, Layout layout)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      */
moveLeft(Spannable text, Layout layout)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      */
moveRight(Spannable text, Layout layout)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      */
extendUp(Spannable text, Layout layout)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      */
extendDown(Spannable text, Layout layout)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      */
extendLeft(Spannable text, Layout layout)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      */
extendRight(Spannable text, Layout layout)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 
extendToLeftEdge(Spannable text, Layout layout)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 
extendToRightEdge(Spannable text, Layout layout)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 
moveToLeftEdge(Spannable text, Layout layout)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 
moveToRightEdge(Spannable text, Layout layout)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 
findEdge(Spannable text, Layout layout, int dir)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 
chooseHorizontal(Layout layout, int direction, int off1, int off2)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