From 28294cc74491885c7fe4442353026f01f5b773e8 Mon Sep 17 00:00:00 2001 From: Gilles Debunne Date: Wed, 24 Aug 2011 12:02:05 -0700 Subject: [PATCH] Suggestions popup window is dismissed on tap outside. But now the tap is not handled at all, as it was before. To do this, the popup window is now focusable. As a result, the TextView's window loses focus. We hide the cursor to prevent a non-blinking visible cursor. We should also fake the state of the parent TextView to keep it visually focussed. SuggestionRangeSpan and SpellCheckSpan had to made Parcelable since they are recreatedi when the TextView is re-created when the popup is dismissed. Change-Id: Ic99b2c4f02c282394f214938dd19168547af4886 --- api/current.txt | 1 + core/java/android/text/TextUtils.java | 14 ++++ .../android/text/style/SpellCheckSpan.java | 25 +++++- .../text/style/SuggestionRangeSpan.java | 51 ++++++++++++ core/java/android/widget/TextView.java | 78 +++++++++---------- core/res/res/values/styles.xml | 2 - 6 files changed, 127 insertions(+), 44 deletions(-) create mode 100644 core/java/android/text/style/SuggestionRangeSpan.java diff --git a/api/current.txt b/api/current.txt index 88a708c9e9401..8cd2140ec1f37 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1605,6 +1605,7 @@ package android { field public static final int TextAppearance_StatusBar_EventContent_Title = 16973928; // 0x1030068 field public static final int TextAppearance_StatusBar_Icon = 16973926; // 0x1030066 field public static final int TextAppearance_StatusBar_Title = 16973925; // 0x1030065 + field public static final int TextAppearance_SuggestionHighlight = 16974104; // 0x1030118 field public static final int TextAppearance_Theme = 16973888; // 0x1030040 field public static final int TextAppearance_Theme_Dialog = 16973896; // 0x1030048 field public static final int TextAppearance_Widget = 16973897; // 0x1030049 diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 83ef6ce0a9a58..b8b54f47bd981 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -31,9 +31,11 @@ import android.text.style.QuoteSpan; import android.text.style.RelativeSizeSpan; import android.text.style.ReplacementSpan; import android.text.style.ScaleXSpan; +import android.text.style.SpellCheckSpan; import android.text.style.StrikethroughSpan; import android.text.style.StyleSpan; import android.text.style.SubscriptSpan; +import android.text.style.SuggestionRangeSpan; import android.text.style.SuggestionSpan; import android.text.style.SuperscriptSpan; import android.text.style.TextAppearanceSpan; @@ -579,6 +581,10 @@ public class TextUtils { public static final int ANNOTATION = 18; /** @hide */ public static final int SUGGESTION_SPAN = 19; + /** @hide */ + public static final int SPELL_CHECK_SPAN = 20; + /** @hide */ + public static final int SUGGESTION_RANGE_SPAN = 21; /** * Flatten a CharSequence and whatever styles can be copied across processes @@ -734,6 +740,14 @@ public class TextUtils { readSpan(p, sp, new SuggestionSpan(p)); break; + case SPELL_CHECK_SPAN: + readSpan(p, sp, new SpellCheckSpan(p)); + break; + + case SUGGESTION_RANGE_SPAN: + readSpan(p, sp, new SuggestionRangeSpan()); + break; + default: throw new RuntimeException("bogus span encoding " + kind); } diff --git a/core/java/android/text/style/SpellCheckSpan.java b/core/java/android/text/style/SpellCheckSpan.java index 9b23177ebfcda..caaae99630a14 100644 --- a/core/java/android/text/style/SpellCheckSpan.java +++ b/core/java/android/text/style/SpellCheckSpan.java @@ -16,6 +16,10 @@ package android.text.style; +import android.os.Parcel; +import android.text.ParcelableSpan; +import android.text.TextUtils; + /** * A SpellCheckSpan is an internal data structure created by the TextView's SpellChecker to * annotate portions of the text that are about to or currently being spell checked. They are @@ -23,7 +27,7 @@ package android.text.style; * * @hide */ -public class SpellCheckSpan { +public class SpellCheckSpan implements ParcelableSpan { private boolean mSpellCheckInProgress; @@ -31,6 +35,10 @@ public class SpellCheckSpan { mSpellCheckInProgress = false; } + public SpellCheckSpan(Parcel src) { + mSpellCheckInProgress = (src.readInt() != 0); + } + public void setSpellCheckInProgress() { mSpellCheckInProgress = true; } @@ -38,4 +46,19 @@ public class SpellCheckSpan { public boolean isSpellCheckInProgress() { return mSpellCheckInProgress; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mSpellCheckInProgress ? 1 : 0); + } + + @Override + public int getSpanTypeId() { + return TextUtils.SPELL_CHECK_SPAN; + } } diff --git a/core/java/android/text/style/SuggestionRangeSpan.java b/core/java/android/text/style/SuggestionRangeSpan.java new file mode 100644 index 0000000000000..fc91697558f18 --- /dev/null +++ b/core/java/android/text/style/SuggestionRangeSpan.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.text.style; + +import android.graphics.Color; +import android.os.Parcel; +import android.text.ParcelableSpan; +import android.text.TextPaint; +import android.text.TextUtils; + +/** + * A SuggestionRangeSpan is used to show which part of an EditText is affected by a suggestion + * popup window. + * + * @hide + */ +public class SuggestionRangeSpan extends CharacterStyle implements ParcelableSpan { + @Override + public void updateDrawState(TextPaint tp) { + tp.setColor(Color.GREEN); + } + + public SuggestionRangeSpan() { /* Nothing to do*/ } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { /* Nothing to do*/ } + + @Override + public int getSpanTypeId() { + return TextUtils.SUGGESTION_RANGE_SPAN; + } +} diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 683a9843e705e..d0153264bc8d5 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -84,10 +84,10 @@ import android.text.method.WordIterator; import android.text.style.ClickableSpan; import android.text.style.ParagraphStyle; import android.text.style.SpellCheckSpan; +import android.text.style.SuggestionRangeSpan; import android.text.style.SuggestionSpan; import android.text.style.TextAppearanceSpan; import android.text.style.URLSpan; -import android.text.style.UnderlineSpan; import android.text.style.UpdateAppearance; import android.text.util.Linkify; import android.util.AttributeSet; @@ -2894,7 +2894,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener sp.removeSpan(cw); } - // hideControllers would do it, but it gets called after this method on rotation sp.removeSpan(mSuggestionRangeSpan); ss.text = sp; @@ -5099,10 +5098,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { - boolean areSuggestionsShown = areSuggestionsShown(); boolean isInSelectionMode = mSelectionActionMode != null; - if (areSuggestionsShown || isInSelectionMode) { + if (isInSelectionMode) { if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { KeyEvent.DispatcherState state = getKeyDispatcherState(); if (state != null) { @@ -5115,10 +5113,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener state.handleUpEvent(event); } if (event.isTracking() && !event.isCanceled()) { - if (areSuggestionsShown) { - hideSuggestions(); - return true; - } if (isInSelectionMode) { stopSelectionActionMode(); return true; @@ -5282,10 +5276,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Has to be done on key down (and not on key up) to correctly be intercepted. case KeyEvent.KEYCODE_BACK: - if (areSuggestionsShown()) { - hideSuggestions(); - return -1; - } if (mSelectionActionMode != null) { stopSelectionActionMode(); return -1; @@ -7634,12 +7624,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener getSpellChecker().addSpellCheckSpan((SpellCheckSpan) what); } } - - if (what instanceof SuggestionSpan) { - if (newStart < 0) { - Log.d("spellcheck", "REMOVE suggspan " + mText.subSequence(oldStart, oldEnd)); - } - } } /** @@ -7956,9 +7940,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } hideControllers(); - - removeSpans(0, mText.length(), SuggestionSpan.class); - removeSpans(0, mText.length(), SpellCheckSpan.class); } startStopMarquee(hasWindowFocus); @@ -9202,11 +9183,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - private static class SuggestionRangeSpan extends UnderlineSpan { - // TODO themable, would be nice to make it a child class of TextAppearanceSpan, but - // there is no way to have underline and TextAppearanceSpan. - } - private class SuggestionsPopupWindow extends PinnedPopupWindow implements OnClickListener { private static final int MAX_NUMBER_SUGGESTIONS = SuggestionSpan.SUGGESTIONS_MAX_SIZE; private static final int NO_SUGGESTIONS = -1; @@ -9214,13 +9190,42 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private WordIterator mSuggestionWordIterator; private TextAppearanceSpan[] mHighlightSpans = new TextAppearanceSpan [(int) (AVERAGE_HIGHLIGHTS_PER_SUGGESTION * MAX_NUMBER_SUGGESTIONS)]; + private boolean mCursorWasVisibleBeforeSuggestions; + + private class CustomPopupWindow extends PopupWindow { + public CustomPopupWindow(Context context, int defStyle) { + super(context, null, defStyle); + } + + @Override + public void dismiss() { + super.dismiss(); + + if ((mText instanceof Editable) && mSuggestionRangeSpan != null) { + ((Editable) mText).removeSpan(mSuggestionRangeSpan); + } + + setCursorVisible(mCursorWasVisibleBeforeSuggestions); + if (hasInsertionController()) { + getInsertionController().show(); + } + } + } + + public SuggestionsPopupWindow() { + for (int i = 0; i < mHighlightSpans.length; i++) { + mHighlightSpans[i] = new TextAppearanceSpan(mContext, + android.R.style.TextAppearance_SuggestionHighlight); + } + mCursorWasVisibleBeforeSuggestions = mCursorVisible; + } @Override protected void createPopupWindow() { - mPopupWindow = new PopupWindow(TextView.this.mContext, null, + mPopupWindow = new CustomPopupWindow(TextView.this.mContext, com.android.internal.R.attr.textSuggestionsWindowStyle); mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); - mPopupWindow.setOutsideTouchable(true); + mPopupWindow.setFocusable(true); mPopupWindow.setClippingEnabled(false); } @@ -9294,6 +9299,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (!(mText instanceof Editable)) return; if (updateSuggestions()) { + mCursorWasVisibleBeforeSuggestions = mCursorVisible; + setCursorVisible(false); super.show(); } } @@ -9318,9 +9325,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public void hide() { super.hide(); - if ((mText instanceof Editable) && mSuggestionRangeSpan != null) { - ((Editable) mText).removeSpan(mSuggestionRangeSpan); - } } private boolean updateSuggestions() { @@ -9559,7 +9563,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int spanEnd = suggestionInfo.spanEnd; if (spanStart != NO_SUGGESTIONS) { // SuggestionSpans are removed by replace: save them before - Editable editable = ((Editable) mText); + Editable editable = (Editable) mText; SuggestionSpan[] suggestionSpans = editable.getSpans(spanStart, spanEnd, SuggestionSpan.class); final int length = suggestionSpans.length; @@ -9578,7 +9582,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final String suggestion = textView.getText().subSequence( suggestionStart, suggestionEnd).toString(); final String originalText = mText.subSequence(spanStart, spanEnd).toString(); - ((Editable) mText).replace(spanStart, spanEnd, suggestion); + editable.replace(spanStart, spanEnd, suggestion); // A replacement on a misspelled text removes the misspelled flag. // TODO restore the flag if the misspelled word is selected back? @@ -9630,12 +9634,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mSuggestionsPopupWindow.show(); } - void hideSuggestions() { - if (mSuggestionsPopupWindow != null) { - mSuggestionsPopupWindow.hide(); - } - } - boolean areSuggestionsShown() { return mSuggestionsPopupWindow != null && mSuggestionsPopupWindow.isShowing(); } @@ -10585,7 +10583,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mEndHandle.setActionPopupWindow(mStartHandle.getActionPopupWindow()); hideInsertionPointCursorController(); - hideSuggestions(); } public void hide() { @@ -10697,7 +10694,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private void hideControllers() { hideInsertionPointCursorController(); stopSelectionActionMode(); - hideSuggestions(); } /** diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index d8d613a25a724..3cbd682a77510 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -885,11 +885,9 @@ please see styles_device_defaults.xml. 30sp -