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 { 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|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 */ 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 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