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