CursorAnchorInfo.java revision c46b5f04aa2a9fd292c117d2824f70fcf06e86ba
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package android.view.inputmethod; 18 19import android.graphics.Matrix; 20import android.graphics.RectF; 21import android.os.Parcel; 22import android.os.Parcelable; 23import android.text.Layout; 24import android.view.inputmethod.SparseRectFArray.SparseRectFArrayBuilder; 25 26import java.util.Objects; 27 28/** 29 * Positional information about the text insertion point and characters in the composition string. 30 * 31 * <p>This class encapsulates locations of the text insertion point and the composition string in 32 * the screen coordinates so that IMEs can render their UI components near where the text is 33 * actually inserted.</p> 34 */ 35public final class CursorAnchorInfo implements Parcelable { 36 private final int mSelectionStart; 37 private final int mSelectionEnd; 38 39 private final int mComposingTextStart; 40 /** 41 * The text, tracked as a composing region. 42 */ 43 private final String mComposingText; 44 45 /** 46 * Horizontal position of the insertion marker, in the local coordinates that will be 47 * transformed with the transformation matrix when rendered on the screen. This should be 48 * calculated or compatible with {@link Layout#getPrimaryHorizontal(int)}. This can be 49 * {@code java.lang.Float.NaN} when no value is specified. 50 */ 51 private final float mInsertionMarkerHorizontal; 52 /** 53 * Vertical position of the insertion marker, in the local coordinates that will be 54 * transformed with the transformation matrix when rendered on the screen. This should be 55 * calculated or compatible with {@link Layout#getLineTop(int)}. This can be 56 * {@code java.lang.Float.NaN} when no value is specified. 57 */ 58 private final float mInsertionMarkerTop; 59 /** 60 * Vertical position of the insertion marker, in the local coordinates that will be 61 * transformed with the transformation matrix when rendered on the screen. This should be 62 * calculated or compatible with {@link Layout#getLineBaseline(int)}. This can be 63 * {@code java.lang.Float.NaN} when no value is specified. 64 */ 65 private final float mInsertionMarkerBaseline; 66 /** 67 * Vertical position of the insertion marker, in the local coordinates that will be 68 * transformed with the transformation matrix when rendered on the screen. This should be 69 * calculated or compatible with {@link Layout#getLineBottom(int)}. This can be 70 * {@code java.lang.Float.NaN} when no value is specified. 71 */ 72 private final float mInsertionMarkerBottom; 73 74 /** 75 * Container of rectangular position of characters, keyed with character index in a unit of 76 * Java chars, in the local coordinates that will be transformed with the transformation matrix 77 * when rendered on the screen. 78 */ 79 private final SparseRectFArray mCharacterRects; 80 81 /** 82 * Transformation matrix that is applied to any positional information of this class to 83 * transform local coordinates into screen coordinates. 84 */ 85 private final Matrix mMatrix; 86 87 public CursorAnchorInfo(final Parcel source) { 88 mSelectionStart = source.readInt(); 89 mSelectionEnd = source.readInt(); 90 mComposingTextStart = source.readInt(); 91 mComposingText = source.readString(); 92 mInsertionMarkerHorizontal = source.readFloat(); 93 mInsertionMarkerTop = source.readFloat(); 94 mInsertionMarkerBaseline = source.readFloat(); 95 mInsertionMarkerBottom = source.readFloat(); 96 mCharacterRects = source.readParcelable(SparseRectFArray.class.getClassLoader()); 97 mMatrix = new Matrix(); 98 mMatrix.setValues(source.createFloatArray()); 99 } 100 101 /** 102 * Used to package this object into a {@link Parcel}. 103 * 104 * @param dest The {@link Parcel} to be written. 105 * @param flags The flags used for parceling. 106 */ 107 @Override 108 public void writeToParcel(Parcel dest, int flags) { 109 dest.writeInt(mSelectionStart); 110 dest.writeInt(mSelectionEnd); 111 dest.writeInt(mComposingTextStart); 112 dest.writeString(mComposingText); 113 dest.writeFloat(mInsertionMarkerHorizontal); 114 dest.writeFloat(mInsertionMarkerTop); 115 dest.writeFloat(mInsertionMarkerBaseline); 116 dest.writeFloat(mInsertionMarkerBottom); 117 dest.writeParcelable(mCharacterRects, flags); 118 final float[] matrixArray = new float[9]; 119 mMatrix.getValues(matrixArray); 120 dest.writeFloatArray(matrixArray); 121 } 122 123 @Override 124 public int hashCode(){ 125 // TODO: Improve the hash function. 126 final float floatHash = mInsertionMarkerHorizontal + mInsertionMarkerTop 127 + mInsertionMarkerBaseline + mInsertionMarkerBottom; 128 int hash = floatHash > 0 ? (int) floatHash : (int)(-floatHash); 129 hash *= 31; 130 hash += mSelectionStart + mSelectionEnd + mComposingTextStart; 131 hash *= 31; 132 hash += Objects.hashCode(mComposingText); 133 hash *= 31; 134 hash += Objects.hashCode(mCharacterRects); 135 hash *= 31; 136 hash += Objects.hashCode(mMatrix); 137 return hash; 138 } 139 140 @Override 141 public boolean equals(Object obj){ 142 if (obj == null) { 143 return false; 144 } 145 if (this == obj) { 146 return true; 147 } 148 if (!(obj instanceof CursorAnchorInfo)) { 149 return false; 150 } 151 final CursorAnchorInfo that = (CursorAnchorInfo) obj; 152 if (hashCode() != that.hashCode()) { 153 return false; 154 } 155 if (mSelectionStart != that.mSelectionStart 156 || mSelectionEnd != that.mSelectionEnd 157 || mComposingTextStart != that.mComposingTextStart) { 158 return false; 159 } 160 if (!Objects.equals(mComposingTextStart, that.mComposingTextStart)) { 161 return false; 162 } 163 if (!Objects.equals(mCharacterRects, that.mCharacterRects)) { 164 return false; 165 } 166 if (!Objects.equals(mMatrix, that.mMatrix)) { 167 return false; 168 } 169 return true; 170 } 171 172 @Override 173 public String toString() { 174 return "SelectionInfo{mSelection=" + mSelectionStart + "," + mSelectionEnd 175 + " mComposingTextStart=" + mComposingTextStart 176 + " mComposingText=" + Objects.toString(mComposingText) 177 + " mInsertionMarkerHorizontal=" + mInsertionMarkerHorizontal 178 + " mInsertionMarkerTop=" + mInsertionMarkerTop 179 + " mInsertionMarkerBaseline=" + mInsertionMarkerBaseline 180 + " mInsertionMarkerBottom=" + mInsertionMarkerBottom 181 + " mCharacterRects=" + Objects.toString(mCharacterRects) 182 + " mMatrix=" + Objects.toString(mMatrix) 183 + "}"; 184 } 185 186 /** 187 * Builder for {@link CursorAnchorInfo}. This class is not designed to be thread-safe. 188 */ 189 public static final class Builder { 190 /** 191 * Sets the text range of the selection. Calling this can be skipped if there is no 192 * selection. 193 */ 194 public Builder setSelectionRange(final int newStart, final int newEnd) { 195 mSelectionStart = newStart; 196 mSelectionEnd = newEnd; 197 return this; 198 } 199 private int mSelectionStart = -1; 200 private int mSelectionEnd = -1; 201 202 /** 203 * Sets the text range of the composing text. Calling this can be skipped if there is 204 * no composing text. 205 * @param index index where the composing text starts. 206 * @param composingText the entire composing text. 207 */ 208 public Builder setComposingText(final int index, final CharSequence composingText) { 209 mComposingTextStart = index; 210 if (composingText == null) { 211 mComposingText = null; 212 } else { 213 mComposingText = composingText.toString(); 214 } 215 return this; 216 } 217 private int mComposingTextStart = -1; 218 private String mComposingText = null; 219 220 /** 221 * Sets the location of the text insertion point (zero width cursor) as a rectangle in 222 * local coordinates. Calling this can be skipped when there is no text insertion point; 223 * however if there is an insertion point, editors must call this method. 224 * @param horizontalPosition horizontal position of the insertion marker, in the local 225 * coordinates that will be transformed with the transformation matrix when rendered on the 226 * screen. This should be calculated or compatible with 227 * {@link Layout#getPrimaryHorizontal(int)}. 228 * @param lineTop vertical position of the insertion marker, in the local coordinates that 229 * will be transformed with the transformation matrix when rendered on the screen. This 230 * should be calculated or compatible with {@link Layout#getLineTop(int)}. 231 * @param lineBaseline vertical position of the insertion marker, in the local coordinates 232 * that will be transformed with the transformation matrix when rendered on the screen. This 233 * should be calculated or compatible with {@link Layout#getLineBaseline(int)}. 234 * @param lineBottom vertical position of the insertion marker, in the local coordinates 235 * that will be transformed with the transformation matrix when rendered on the screen. This 236 * should be calculated or compatible with {@link Layout#getLineBottom(int)}. 237 */ 238 public Builder setInsertionMarkerLocation(final float horizontalPosition, 239 final float lineTop, final float lineBaseline, final float lineBottom){ 240 mInsertionMarkerHorizontal = horizontalPosition; 241 mInsertionMarkerTop = lineTop; 242 mInsertionMarkerBaseline = lineBaseline; 243 mInsertionMarkerBottom = lineBottom; 244 return this; 245 } 246 private float mInsertionMarkerHorizontal = Float.NaN; 247 private float mInsertionMarkerTop = Float.NaN; 248 private float mInsertionMarkerBaseline = Float.NaN; 249 private float mInsertionMarkerBottom = Float.NaN; 250 251 /** 252 * Adds the bounding box of the character specified with the index. 253 * <p> 254 * Editor authors should not call this method for characters that are invisible. 255 * </p> 256 * 257 * @param index index of the character in Java chars units. Must be specified in 258 * ascending order across successive calls. 259 * @param leadingEdgeX x coordinate of the leading edge of the character in local 260 * coordinates, that is, left edge for LTR text and right edge for RTL text. 261 * @param leadingEdgeY y coordinate of the leading edge of the character in local 262 * coordinates. 263 * @param trailingEdgeX x coordinate of the trailing edge of the character in local 264 * coordinates, that is, right edge for LTR text and left edge for RTL text. 265 * @param trailingEdgeY y coordinate of the trailing edge of the character in local 266 * coordinates. 267 * @throws IllegalArgumentException If the index is a negative value, or not greater than 268 * all of the previously called indices. 269 */ 270 public Builder addCharacterRect(final int index, final float leadingEdgeX, 271 final float leadingEdgeY, final float trailingEdgeX, final float trailingEdgeY) { 272 if (index < 0) { 273 throw new IllegalArgumentException("index must not be a negative integer."); 274 } 275 if (mCharacterRectBuilder == null) { 276 mCharacterRectBuilder = new SparseRectFArrayBuilder(); 277 } 278 mCharacterRectBuilder.append(index, leadingEdgeX, leadingEdgeY, trailingEdgeX, 279 trailingEdgeY); 280 return this; 281 } 282 private SparseRectFArrayBuilder mCharacterRectBuilder = null; 283 284 /** 285 * Sets the matrix that transforms local coordinates into screen coordinates. 286 * @param matrix transformation matrix from local coordinates into screen coordinates. null 287 * is interpreted as an identity matrix. 288 */ 289 public Builder setMatrix(final Matrix matrix) { 290 mMatrix.set(matrix != null ? matrix : Matrix.IDENTITY_MATRIX); 291 return this; 292 } 293 private final Matrix mMatrix = new Matrix(Matrix.IDENTITY_MATRIX); 294 295 /** 296 * @return {@link CursorAnchorInfo} using parameters in this 297 * {@link Builder}. 298 */ 299 public CursorAnchorInfo build() { 300 return new CursorAnchorInfo(this); 301 } 302 303 /** 304 * Resets the internal state so that this instance can be reused to build another 305 * instance of {@link CursorAnchorInfo}. 306 */ 307 public void reset() { 308 mSelectionStart = -1; 309 mSelectionEnd = -1; 310 mComposingTextStart = -1; 311 mComposingText = null; 312 mInsertionMarkerHorizontal = Float.NaN; 313 mInsertionMarkerTop = Float.NaN; 314 mInsertionMarkerBaseline = Float.NaN; 315 mInsertionMarkerBottom = Float.NaN; 316 mMatrix.set(Matrix.IDENTITY_MATRIX); 317 if (mCharacterRectBuilder != null) { 318 mCharacterRectBuilder.reset(); 319 } 320 } 321 } 322 323 private CursorAnchorInfo(final Builder builder) { 324 mSelectionStart = builder.mSelectionStart; 325 mSelectionEnd = builder.mSelectionEnd; 326 mComposingTextStart = builder.mComposingTextStart; 327 mComposingText = builder.mComposingText; 328 mInsertionMarkerHorizontal = builder.mInsertionMarkerHorizontal; 329 mInsertionMarkerTop = builder.mInsertionMarkerTop; 330 mInsertionMarkerBaseline = builder.mInsertionMarkerBaseline; 331 mInsertionMarkerBottom = builder.mInsertionMarkerBottom; 332 mCharacterRects = builder.mCharacterRectBuilder != null ? 333 builder.mCharacterRectBuilder.build() : null; 334 mMatrix = new Matrix(builder.mMatrix); 335 } 336 337 /** 338 * Returns the index where the selection starts. 339 * @return -1 if there is no selection. 340 */ 341 public int getSelectionStart() { 342 return mSelectionStart; 343 } 344 345 /** 346 * Returns the index where the selection ends. 347 * @return -1 if there is no selection. 348 */ 349 public int getSelectionEnd() { 350 return mSelectionEnd; 351 } 352 353 /** 354 * Returns the index where the composing text starts. 355 * @return -1 if there is no composing text. 356 */ 357 public int getComposingTextStart() { 358 return mComposingTextStart; 359 } 360 361 /** 362 * Returns the entire composing text. 363 * @return null if there is no composition. 364 */ 365 public String getComposingText() { 366 return mComposingText; 367 } 368 369 /** 370 * Returns the horizontal start of the insertion marker, in the local coordinates that will 371 * be transformed with {@link #getMatrix()} when rendered on the screen. 372 * @return x coordinate that is compatible with {@link Layout#getPrimaryHorizontal(int)}. 373 * Pay special care to RTL/LTR handling. 374 * {@code java.lang.Float.NaN} if not specified. 375 * @see Layout#getPrimaryHorizontal(int) 376 */ 377 public float getInsertionMarkerHorizontal() { 378 return mInsertionMarkerHorizontal; 379 } 380 /** 381 * Returns the vertical top position of the insertion marker, in the local coordinates that 382 * will be transformed with {@link #getMatrix()} when rendered on the screen. 383 * @return y coordinate that is compatible with {@link Layout#getLineTop(int)}. 384 * {@code java.lang.Float.NaN} if not specified. 385 */ 386 public float getInsertionMarkerTop() { 387 return mInsertionMarkerTop; 388 } 389 /** 390 * Returns the vertical baseline position of the insertion marker, in the local coordinates 391 * that will be transformed with {@link #getMatrix()} when rendered on the screen. 392 * @return y coordinate that is compatible with {@link Layout#getLineBaseline(int)}. 393 * {@code java.lang.Float.NaN} if not specified. 394 */ 395 public float getInsertionMarkerBaseline() { 396 return mInsertionMarkerBaseline; 397 } 398 /** 399 * Returns the vertical bottom position of the insertion marker, in the local coordinates 400 * that will be transformed with {@link #getMatrix()} when rendered on the screen. 401 * @return y coordinate that is compatible with {@link Layout#getLineBottom(int)}. 402 * {@code java.lang.Float.NaN} if not specified. 403 */ 404 public float getInsertionMarkerBottom() { 405 return mInsertionMarkerBottom; 406 } 407 408 /** 409 * Returns a new instance of {@link RectF} that indicates the location of the character 410 * specified with the index. 411 * <p> 412 * Note that coordinates are not necessarily contiguous or even monotonous, especially when 413 * RTL text and LTR text are mixed. 414 * </p> 415 * @param index index of the character in a Java chars. 416 * @return a new instance of {@link RectF} that represents the location of the character in 417 * local coordinates. null if the character is invisible or the application did not provide 418 * the location. Note that the {@code left} field can be greater than the {@code right} field 419 * if the character is in RTL text. 420 */ 421 // TODO: Prepare a document about the expected behavior for surrogate pairs, combining 422 // characters, and non-graphical chars. 423 public RectF getCharacterRect(final int index) { 424 if (mCharacterRects == null) { 425 return null; 426 } 427 return mCharacterRects.get(index); 428 } 429 430 /** 431 * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation 432 * matrix that is to be applied other positional data in this class. 433 * @return a new instance (copy) of the transformation matrix. 434 */ 435 public Matrix getMatrix() { 436 return new Matrix(mMatrix); 437 } 438 439 /** 440 * Used to make this class parcelable. 441 */ 442 public static final Parcelable.Creator<CursorAnchorInfo> CREATOR 443 = new Parcelable.Creator<CursorAnchorInfo>() { 444 @Override 445 public CursorAnchorInfo createFromParcel(Parcel source) { 446 return new CursorAnchorInfo(source); 447 } 448 449 @Override 450 public CursorAnchorInfo[] newArray(int size) { 451 return new CursorAnchorInfo[size]; 452 } 453 }; 454 455 @Override 456 public int describeContents() { 457 return 0; 458 } 459} 460