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 { 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 */ getSelectionStart(CharSequence text)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 */ getSelectionEnd(CharSequence text)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 */ setSelection(Spannable text, int start, int stop)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 */ setSelection(Spannable text, int index)86 public static final void setSelection(Spannable text, int index) { 87 setSelection(text, index, index); 88 } 89 90 /** 91 * Select the entire text. 92 */ selectAll(Spannable text)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 */ extendSelection(Spannable text, int index)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 */ removeSelection(Spannable text)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 */ moveUp(Spannable text, Layout layout)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 */ moveDown(Spannable text, Layout layout)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 */ moveLeft(Spannable text, Layout layout)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 */ moveRight(Spannable text, Layout layout)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 */ extendUp(Spannable text, Layout layout)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 */ extendDown(Spannable text, Layout layout)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 */ extendLeft(Spannable text, Layout layout)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 */ extendRight(Spannable text, Layout layout)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 extendToLeftEdge(Spannable text, Layout layout)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 extendToRightEdge(Spannable text, Layout layout)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 moveToLeftEdge(Spannable text, Layout layout)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 moveToRightEdge(Spannable text, Layout layout)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 preceding(int position)375 public int preceding(int position); following(int position)376 public int following(int position); 377 } 378 379 /** {@hide} */ moveToPreceding( Spannable text, PositionIterator iter, boolean extendSelection)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} */ moveToFollowing( Spannable text, PositionIterator iter, boolean extendSelection)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 findEdge(Spannable text, Layout layout, int dir)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 chooseHorizontal(Layout layout, int direction, int off1, int off2)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