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