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 sub-class of {@link HandwritingGesture} for deleting an area of text using single rectangle.
31  * This class holds the information required for deletion of text in
32  * toolkit widgets like {@link TextView}.
33  * <p>Note: This deletes all text <em>within</em> the given area. To delete a range <em>between</em>
34  * two areas, use {@link DeleteRangeGesture}.</p>
35  */
36 public final class DeleteGesture extends PreviewableHandwritingGesture implements Parcelable {
37 
38     private @Granularity int mGranularity;
39     private RectF mArea;
40 
DeleteGesture(@ranularity int granularity, RectF area, String fallbackText)41     private DeleteGesture(@Granularity int granularity, RectF area, String fallbackText) {
42         mType = GESTURE_TYPE_DELETE;
43         mArea = area;
44         mGranularity = granularity;
45         mFallbackText = fallbackText;
46     }
47 
DeleteGesture(@onNull final Parcel source)48     private DeleteGesture(@NonNull final Parcel source) {
49         mType = GESTURE_TYPE_DELETE;
50         mFallbackText = source.readString8();
51         mGranularity = source.readInt();
52         mArea = source.readTypedObject(RectF.CREATOR);
53     }
54 
55     /**
56      * Returns Granular level on which text should be operated.
57      * @see HandwritingGesture#GRANULARITY_CHARACTER
58      * @see HandwritingGesture#GRANULARITY_WORD
59      */
60     @Granularity
getGranularity()61     public int getGranularity() {
62         return mGranularity;
63     }
64 
65     /**
66      * Returns the deletion area {@link RectF} in screen coordinates.
67      *
68      * Getter for deletion area set with {@link DeleteGesture.Builder#setDeletionArea(RectF)}.
69      */
70     @NonNull
getDeletionArea()71     public RectF getDeletionArea() {
72         return mArea;
73     }
74 
75     /**
76      * Builder for {@link DeleteGesture}. This class is not designed to be thread-safe.
77      */
78     public static final class Builder {
79         private int mGranularity;
80         private RectF mArea;
81         private String mFallbackText;
82 
83         /**
84          * Set text deletion granularity. Intersecting words/characters will be
85          * included in the operation.
86          * @param granularity {@link HandwritingGesture#GRANULARITY_WORD} or
87          * {@link HandwritingGesture#GRANULARITY_CHARACTER}.
88          * @return {@link Builder}.
89          */
90         @NonNull
91         @SuppressLint("MissingGetterMatchingBuilder")
setGranularity(@ranularity int granularity)92         public Builder setGranularity(@Granularity int granularity) {
93             mGranularity = granularity;
94             return this;
95         }
96 
97         /**
98          * Set rectangular single/multiline text deletion area intersecting with text.
99          *
100          * The resulting deletion would be performed for all text intersecting rectangle. The
101          * deletion includes the first word/character in the rectangle, and the last
102          * word/character in the rectangle, and includes  everything in between even if it's not
103          * in the rectangle.
104          *
105          * Intersection is determined using
106          * {@link #setGranularity(int)}. e.g. {@link HandwritingGesture#GRANULARITY_WORD} includes
107          * all the words with their width/height center included in the deletion rectangle.
108          * @param area {@link RectF} (in screen coordinates) for which text will be deleted.
109          * @see HandwritingGesture#GRANULARITY_WORD
110          * @see HandwritingGesture#GRANULARITY_CHARACTER
111          */
112         @SuppressLint("MissingGetterMatchingBuilder")
113         @NonNull
setDeletionArea(@onNull RectF area)114         public Builder setDeletionArea(@NonNull RectF area) {
115             mArea = area;
116             return this;
117         }
118 
119         /**
120          * Set fallback text that will be committed at current cursor position if there is no
121          * applicable text beneath the area of gesture.
122          * @param fallbackText text to set
123          */
124         @NonNull
setFallbackText(@ullable String fallbackText)125         public Builder setFallbackText(@Nullable String fallbackText) {
126             mFallbackText = fallbackText;
127             return this;
128         }
129 
130         /**
131          * @return {@link DeleteGesture} using parameters in this {@link DeleteGesture.Builder}.
132          * @throws IllegalArgumentException if one or more positional parameters are not specified.
133          */
134         @NonNull
build()135         public DeleteGesture build() {
136             if (mArea == null || mArea.isEmpty()) {
137                 throw new IllegalArgumentException("Deletion area must be set.");
138             }
139             if (mGranularity <= GRANULARITY_UNDEFINED) {
140                 throw new IllegalArgumentException("Deletion granularity must be set.");
141             }
142             return new DeleteGesture(mGranularity, mArea, mFallbackText);
143         }
144     }
145 
146     /**
147      * Used to make this class parcelable.
148      */
149     @NonNull
150     public static final Creator<DeleteGesture> CREATOR =
151             new Creator<DeleteGesture>() {
152         @Override
153         public DeleteGesture createFromParcel(Parcel source) {
154             return new DeleteGesture(source);
155         }
156 
157         @Override
158         public DeleteGesture[] newArray(int size) {
159             return new DeleteGesture[size];
160         }
161     };
162 
163     @Override
hashCode()164     public int hashCode() {
165         return Objects.hash(mArea, mGranularity, mFallbackText);
166     }
167 
168     @Override
equals(Object o)169     public boolean equals(Object o) {
170         if (this == o) return true;
171         if (!(o instanceof DeleteGesture)) return false;
172 
173         DeleteGesture that = (DeleteGesture) o;
174 
175         if (mGranularity != that.mGranularity) return false;
176         if (!Objects.equals(mFallbackText, that.mFallbackText)) return false;
177         return Objects.equals(mArea, that.mArea);
178     }
179 
180     @Override
describeContents()181     public int describeContents() {
182         return 0;
183     }
184 
185     /**
186      * Used to package this object into a {@link Parcel}.
187      *
188      * @param dest The {@link Parcel} to be written.
189      * @param flags The flags used for parceling.
190      */
191     @Override
writeToParcel(@onNull Parcel dest, int flags)192     public void writeToParcel(@NonNull Parcel dest, int flags) {
193         dest.writeString8(mFallbackText);
194         dest.writeInt(mGranularity);
195         dest.writeTypedObject(mArea, flags);
196     }
197 }
198