Selection.java revision f013e1afd1e68af5e3b868c26a653bbfb39538f8
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
17package 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 */
24public 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);
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 { };
421    private static final class END { };
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