blob: 3222dbf8718e4b48972415dd43d506043fe5bfa3 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Jeff Sharkeye982dfc12011-03-21 16:40:23 -070019import java.text.BreakIterator;
Jeff Sharkeye982dfc12011-03-21 16:40:23 -070020
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021
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 */
26public 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 }
Jeff Sharkeye982dfc12011-03-21 16:40:23 -070043
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044 /**
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 // }
Jeff Sharkeye982dfc12011-03-21 16:40:23 -070062
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063 /**
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);
Jeff Sharkeye982dfc12011-03-21 16:40:23 -070074
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075 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
Raph Levienfb0431b2014-09-04 15:03:14 -0700119 * 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.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800121 */
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;
Raph Levienfb0431b2014-09-04 15:03:14 -0700153 } else if (end != 0) {
154 setSelection(text, 0);
155 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800156 }
157 }
158
159 return false;
160 }
161
162 /**
163 * Move the cursor to the buffer offset physically below the current
Raph Levienfb0431b2014-09-04 15:03:14 -0700164 * 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.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 */
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;
Raph Levienfb0431b2014-09-04 15:03:14 -0700199 } else if (end != text.length()) {
200 setSelection(text, text.length());
201 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800202 }
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
Jeff Sharkeye982dfc12011-03-21 16:40:23 -0700371 /** {@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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800407 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
Gilles Debunne2d0e87b2010-07-13 10:14:24 -0700467 private static final class START implements NoCopySpan { }
468 private static final class END implements NoCopySpan { }
Jeff Sharkeye982dfc12011-03-21 16:40:23 -0700469
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800470 /*
471 * Public constants
472 */
473
474 public static final Object SELECTION_START = new START();
475 public static final Object SELECTION_END = new END();
476}