1 /* 2 * Copyright (C) 2022 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.view.inputmethod; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SuppressLint; 22 import android.graphics.RectF; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.widget.TextView; 26 27 import java.util.Objects; 28 29 /** 30 * A subclass of {@link HandwritingGesture} for deleting a range of text by defining start and end 31 * rectangles. This can be useful when the range cannot be defined with a single rectangle. 32 * This class holds the information required for deletion of text in 33 * toolkit widgets like {@link TextView}. 34 * <p>Note: this deletes text within a range <em>between</em> two given areas. To delete all text 35 * <em>within</em> a single area, use {@link DeleteGesture}.</p> 36 */ 37 public final class DeleteRangeGesture extends PreviewableHandwritingGesture implements Parcelable { 38 39 private @Granularity int mGranularity; 40 private RectF mStartArea; 41 private RectF mEndArea; 42 DeleteRangeGesture( int granularity, RectF startArea, RectF endArea, String fallbackText)43 private DeleteRangeGesture( 44 int granularity, RectF startArea, RectF endArea, String fallbackText) { 45 mType = GESTURE_TYPE_DELETE_RANGE; 46 mStartArea = startArea; 47 mEndArea = endArea; 48 mGranularity = granularity; 49 mFallbackText = fallbackText; 50 } 51 DeleteRangeGesture(@onNull Parcel source)52 private DeleteRangeGesture(@NonNull Parcel source) { 53 mType = GESTURE_TYPE_DELETE_RANGE; 54 mFallbackText = source.readString8(); 55 mGranularity = source.readInt(); 56 mStartArea = source.readTypedObject(RectF.CREATOR); 57 mEndArea = source.readTypedObject(RectF.CREATOR); 58 } 59 60 /** 61 * Returns Granular level on which text should be operated. 62 * @see #GRANULARITY_CHARACTER 63 * @see #GRANULARITY_WORD 64 */ 65 @Granularity getGranularity()66 public int getGranularity() { 67 return mGranularity; 68 } 69 70 /** 71 * Returns the Deletion start area {@link RectF} in screen coordinates. 72 * 73 * Getter for deletion area set with {@link Builder#setDeletionStartArea(RectF)}. 74 */ 75 @NonNull getDeletionStartArea()76 public RectF getDeletionStartArea() { 77 return mStartArea; 78 } 79 80 /** 81 * Returns the Deletion end area {@link RectF} in screen coordinates. 82 * 83 * Getter for deletion area set with {@link Builder#setDeletionEndArea(RectF)}. 84 */ 85 @NonNull getDeletionEndArea()86 public RectF getDeletionEndArea() { 87 return mEndArea; 88 } 89 90 /** 91 * Builder for {@link DeleteRangeGesture}. This class is not designed to be thread-safe. 92 */ 93 public static final class Builder { 94 private int mGranularity; 95 private RectF mStartArea; 96 private RectF mEndArea; 97 private String mFallbackText; 98 99 /** 100 * Define text deletion granularity. Intersecting words/characters will be 101 * included in the operation. 102 * @param granularity {@link HandwritingGesture#GRANULARITY_WORD} or 103 * {@link HandwritingGesture#GRANULARITY_CHARACTER}. 104 * @return {@link Builder}. 105 */ 106 @NonNull 107 @SuppressLint("MissingGetterMatchingBuilder") setGranularity(@ranularity int granularity)108 public Builder setGranularity(@Granularity int granularity) { 109 mGranularity = granularity; 110 return this; 111 } 112 113 /** 114 * Set rectangular single/multiline start of text deletion area intersecting with text. 115 * 116 * The resulting deletion is performed from the start of first word/character in the start 117 * rectangle to the end of the last word/character in the end rectangle 118 * {@link #setDeletionEndArea(RectF)}. 119 * <br/> 120 * <img src="{@docRoot}reference/android/images/input_method/stylus_handwriting 121 * /delete_range_gesture_rects.png" 122 * height="300" alt="Deletion strategy using two rectangles"/> 123 * <br/> 124 * 125 * Intersection is determined using 126 * {@link #setGranularity(int)}. e.g. {@link HandwritingGesture#GRANULARITY_WORD} includes 127 * all the words with their width/height center included in the deletion rectangle. 128 * @param startArea {@link RectF} (in screen coordinates) for start of deletion. This 129 * rectangle belongs to first line where deletion should start. 130 */ 131 @NonNull 132 @SuppressLint("MissingGetterMatchingBuilder") setDeletionStartArea(@onNull RectF startArea)133 public Builder setDeletionStartArea(@NonNull RectF startArea) { 134 mStartArea = startArea; 135 return this; 136 } 137 138 /** 139 * Set rectangular single/multiline end of text deletion area intersecting with text. 140 * 141 * The resulting deletion is performed from the start of first word/character in the start 142 * rectangle {@link #setDeletionStartArea(RectF)} to the end of the last word/character in 143 * the end rectangle. 144 * <br/> 145 * <img src="{@docRoot}reference/android/images/input_method/stylus_handwriting 146 * /delete_range_gesture_rects.png" 147 * height="300" alt="Deletion strategy using two rectangles"/> 148 * 149 * Intersection is determined using 150 * {@link #setGranularity(int)}. e.g. {@link HandwritingGesture#GRANULARITY_WORD} includes 151 * all the words with their width/height center included in the deletion rectangle. 152 * @param endArea {@link RectF} (in screen coordinates) for start of deletion. This 153 * rectangle belongs to the last line where deletion should end. 154 */ 155 @NonNull 156 @SuppressLint("MissingGetterMatchingBuilder") setDeletionEndArea(@onNull RectF endArea)157 public Builder setDeletionEndArea(@NonNull RectF endArea) { 158 mEndArea = endArea; 159 return this; 160 } 161 162 /** 163 * Set fallback text that will be committed at current cursor position if there is no 164 * applicable text beneath the area of gesture. 165 * @param fallbackText text to set 166 */ 167 @NonNull setFallbackText(@ullable String fallbackText)168 public Builder setFallbackText(@Nullable String fallbackText) { 169 mFallbackText = fallbackText; 170 return this; 171 } 172 173 /** 174 * @return {@link DeleteRangeGesture} using parameters in this 175 * {@link DeleteRangeGesture.Builder}. 176 * @throws IllegalArgumentException if one or more positional parameters are not specified. 177 */ 178 @NonNull build()179 public DeleteRangeGesture build() { 180 if (mStartArea == null || mStartArea.isEmpty() || mEndArea == null 181 || mEndArea.isEmpty()) { 182 throw new IllegalArgumentException("Deletion area must be set."); 183 } 184 if (mGranularity <= GRANULARITY_UNDEFINED) { 185 throw new IllegalArgumentException("Deletion granularity must be set."); 186 } 187 return new DeleteRangeGesture(mGranularity, mStartArea, mEndArea, mFallbackText); 188 } 189 } 190 191 /** 192 * Used to make this class parcelable. 193 */ 194 @NonNull 195 public static final Creator<DeleteRangeGesture> CREATOR = 196 new Creator<DeleteRangeGesture>() { 197 @Override 198 public DeleteRangeGesture createFromParcel(Parcel source) { 199 return new DeleteRangeGesture(source); 200 } 201 202 @Override 203 public DeleteRangeGesture[] newArray(int size) { 204 return new DeleteRangeGesture[size]; 205 } 206 }; 207 208 @Override hashCode()209 public int hashCode() { 210 return Objects.hash(mGranularity, mStartArea, mEndArea, mFallbackText); 211 } 212 213 @Override equals(Object o)214 public boolean equals(Object o) { 215 if (!(o instanceof DeleteRangeGesture)) return false; 216 217 DeleteRangeGesture that = (DeleteRangeGesture) o; 218 219 if (mGranularity != that.mGranularity) return false; 220 if (!Objects.equals(mFallbackText, that.mFallbackText)) return false; 221 if (!Objects.equals(mStartArea, that.mStartArea)) return false; 222 return Objects.equals(mEndArea, that.mEndArea); 223 } 224 225 @Override describeContents()226 public int describeContents() { 227 return 0; 228 } 229 230 /** 231 * Used to package this object into a {@link Parcel}. 232 * 233 * @param dest The {@link Parcel} to be written. 234 * @param flags The flags used for parceling. 235 */ 236 @Override writeToParcel(@onNull Parcel dest, int flags)237 public void writeToParcel(@NonNull Parcel dest, int flags) { 238 dest.writeString8(mFallbackText); 239 dest.writeInt(mGranularity); 240 dest.writeTypedObject(mStartArea, flags); 241 dest.writeTypedObject(mEndArea, flags); 242 } 243 } 244