Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(762)

Side by Side Diff: content/public/android/java/src/org/chromium/content/browser/input/SuggestionsPopupWindow.java

Issue 2650113004: [WIP] Add support for Android SuggestionSpans when editing text (Closed)
Patch Set: Remove logging statements, fix copyright years in new files Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.content.browser.input;
6
7 import android.annotation.TargetApi;
8 import android.content.Context;
9 import android.graphics.Color;
10 import android.graphics.Rect;
11 import android.graphics.drawable.ColorDrawable;
12 import android.graphics.drawable.Drawable;
13 import android.os.Build;
14 import android.text.SpannableString;
15 import android.text.Spanned;
16 import android.text.style.TextAppearanceSpan;
17 import android.util.DisplayMetrics;
18 import android.view.Gravity;
19 import android.view.LayoutInflater;
20 import android.view.View;
21 import android.view.ViewGroup;
22 import android.view.WindowManager;
23 import android.view.inputmethod.CursorAnchorInfo;
24 import android.widget.AdapterView;
25 import android.widget.AdapterView.OnItemClickListener;
26 import android.widget.BaseAdapter;
27 import android.widget.LinearLayout;
28 import android.widget.ListView;
29 import android.widget.PopupWindow;
30 import android.widget.PopupWindow.OnDismissListener;
31 import android.widget.TextView;
32
33 import org.chromium.content.R;
34
35 /**
36 * Popup window that displays a menu for viewing and applying text replacement
37 * suggestions.
38 */
39 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
40 public class SuggestionsPopupWindow
41 implements OnItemClickListener, OnDismissListener, CursorAnchorInfoContr oller.Listener {
42 private static final double MENU_Y_OFFSET_FROM_INSERTION_MARKER_BOTTOM = 24. 0;
43
44 private final Context mContext;
45 private final ImeAdapter mImeAdapter;
46 private final View mAttachedView;
47
48 private CursorAnchorInfoController mCursorAnchorInfoController;
49 private CursorAnchorInfo mLastCursorAnchorInfo;
50 private boolean mListeningForCursorAnchorInfoUpdates = false;
Changwan Ryu 2017/02/14 00:58:01 no need to add '= false' or '= 0.0'. here and othe
51 private boolean mFinishShowingPopupOnCursorAnchorInfoUpdate = false;
52
53 protected PopupWindow mPopupWindow;
54 protected ViewGroup mContentView;
55 int mPositionX, mPositionY;
Changwan Ryu 2017/02/14 00:58:01 nit: you should define one variable in one line
56 int mClippingLimitLeft, mClippingLimitRight;
57 private Rect mTempRect;
58
59 private SuggestionAdapter mSuggestionsAdapter;
60 private SuggestionInfo[] mSuggestionInfos = new SuggestionInfo[0];
61 private final TextAppearanceSpan mHighlightSpan;
62 private TextView mDeleteButton;
63 private ListView mSuggestionListView;
64 private int mContainerMarginWidth;
65 private int mContainerMarginTop;
66 private double mLastInsertionMarkerBottom = 0.0;
67 private LinearLayout mContainerView;
68
69 private boolean mDismissedByItemTap = false;
70
71 public SuggestionsPopupWindow(Context context, ImeAdapter imeAdapter, View a ttachedView,
72 CursorAnchorInfoController cursorAnchorInfoController) {
73 mContext = context;
74 mImeAdapter = imeAdapter;
75 mAttachedView = attachedView;
76 mCursorAnchorInfoController = cursorAnchorInfoController;
77 mHighlightSpan = new TextAppearanceSpan(mContext, R.style.SuggestionHigh light);
78
79 createPopupWindow();
80
81 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
82 mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APP LICATION_SUB_PANEL);
Changwan Ryu 2017/02/14 00:58:01 could you add a comment on the effect of this line
83 }
84 mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
85 mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
86 initContentView();
87
88 ViewGroup.LayoutParams wrapContent = new ViewGroup.LayoutParams(
89 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP _CONTENT);
90 mContentView.setLayoutParams(wrapContent);
91 mPopupWindow.setContentView(mContentView);
92 }
93
94 protected void createPopupWindow() {
Changwan Ryu 2017/02/14 00:58:01 no need to use protected
95 mPopupWindow = new PopupWindow();
96 mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
97 mPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)) ;
98
99 mPopupWindow.setFocusable(true);
100 mPopupWindow.setClippingEnabled(false);
101
102 mPopupWindow.setOnDismissListener(this);
103 }
104
105 protected void initContentView() {
Changwan Ryu 2017/02/14 00:58:01 no need to use protected
106 final LayoutInflater inflater =
107 (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLAT ER_SERVICE);
108 mContentView = (ViewGroup) inflater.inflate(R.layout.text_edit_suggestio n_container, null);
109 mContainerView = (LinearLayout) mContentView.findViewById(R.id.suggestio nWindowContainer);
110 ViewGroup.MarginLayoutParams lp =
111 (ViewGroup.MarginLayoutParams) mContainerView.getLayoutParams();
112 mContainerMarginWidth = lp.leftMargin + lp.rightMargin;
113 mContainerMarginTop = lp.topMargin;
114 mClippingLimitLeft = lp.leftMargin;
115 mClippingLimitRight = lp.rightMargin;
116
117 mSuggestionListView = (ListView) mContentView.findViewById(R.id.suggesti onContainer);
118
119 mSuggestionsAdapter = new SuggestionAdapter();
120 mSuggestionListView.setAdapter(mSuggestionsAdapter);
121 mSuggestionListView.setOnItemClickListener(this);
122
123 mDeleteButton = (TextView) mContentView.findViewById(R.id.deleteButton);
124 mDeleteButton.setOnClickListener(new View.OnClickListener() {
125 public void onClick(View v) {
126 mImeAdapter.deleteSuggestionHighlight();
127 mDismissedByItemTap = true;
128 mPopupWindow.dismiss();
129 }
130 });
131 }
132
133 public void updatePosition() {
134 if (!mPopupWindow.isShowing()) {
135 return;
136 }
137 show();
138 }
139
140 public boolean isShowing() {
141 return mPopupWindow.isShowing();
142 }
143
144 private class SuggestionAdapter extends BaseAdapter {
145 private LayoutInflater mInflater =
146 (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLAT ER_SERVICE);
147 @Override
148 public int getCount() {
149 return mSuggestionInfos.length;
150 }
151 @Override
152 public Object getItem(int position) {
153 return mSuggestionInfos[position];
154 }
155 @Override
156 public long getItemId(int position) {
157 return position;
158 }
159 @Override
160 public View getView(int position, View convertView, ViewGroup parent) {
161 TextView textView = (TextView) convertView;
162 if (textView == null) {
163 textView = (TextView) mInflater.inflate(
164 R.layout.text_edit_suggestion_item, parent, false);
165 }
166 final SuggestionInfo suggestionInfo = mSuggestionInfos[position];
167 SpannableString textToSet = new SpannableString(
168 suggestionInfo.mPrefix + suggestionInfo.mText + suggestionIn fo.mSuffix);
169 textToSet.setSpan(mHighlightSpan, suggestionInfo.mPrefix.length(),
170 suggestionInfo.mPrefix.length() + suggestionInfo.mText.lengt h(),
171 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
172 textView.setText(textToSet);
173 return textView;
174 }
175 }
176
177 protected void measureContent() {
Changwan Ryu 2017/02/14 00:58:01 no need to use protected
178 final DisplayMetrics displayMetrics = mContext.getResources().getDisplay Metrics();
179 final int horizontalMeasure = View.MeasureSpec.makeMeasureSpec(
180 displayMetrics.widthPixels, View.MeasureSpec.AT_MOST);
181 final int verticalMeasure = View.MeasureSpec.makeMeasureSpec(
182 displayMetrics.heightPixels, View.MeasureSpec.AT_MOST);
183 int width = 0;
184 View view = null;
185 for (int i = 0; i < mSuggestionInfos.length; i++) {
186 view = mSuggestionsAdapter.getView(i, view, mContentView);
187 view.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
188 view.measure(horizontalMeasure, verticalMeasure);
189 width = Math.max(width, view.getMeasuredWidth());
190 }
191 mDeleteButton.measure(horizontalMeasure, verticalMeasure);
192 width = Math.max(width, mDeleteButton.getMeasuredWidth());
193 width += mContainerView.getPaddingLeft() + mContainerView.getPaddingRigh t()
194 + mContainerMarginWidth;
195 // Enforce the width based on actual text widths
196 mContentView.measure(
197 View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY ), verticalMeasure);
198 Drawable popupBackground = mPopupWindow.getBackground();
199 if (popupBackground != null) {
200 if (mTempRect == null) mTempRect = new Rect();
201 popupBackground.getPadding(mTempRect);
202 width += mTempRect.left + mTempRect.right;
203 }
204 mPopupWindow.setWidth(width);
205 }
206
207 public void setSuggestionInfos(SuggestionInfo[] suggestionInfos) {
208 mSuggestionInfos = suggestionInfos;
209 }
210
211 public void onCursorAnchorInfoUpdate(CursorAnchorInfo info) {
212 mLastCursorAnchorInfo = info;
213 if (mFinishShowingPopupOnCursorAnchorInfoUpdate) {
214 mFinishShowingPopupOnCursorAnchorInfoUpdate = false;
215 finishShow();
216 }
217 }
218
219 public void show() {
220 if (!mListeningForCursorAnchorInfoUpdates) {
221 mCursorAnchorInfoController.addListener(this, mAttachedView);
222 mListeningForCursorAnchorInfoUpdates = true;
223 }
224 mFinishShowingPopupOnCursorAnchorInfoUpdate = true;
225 }
226
227 // Called asynchronously after show() returns once we get an up-to-date
228 // CursorAnchorInfo back
229 private void finishShow() {
230 mSuggestionsAdapter.notifyDataSetChanged();
231 // We get the insertion point coords relative to the WebView bounds.
232 // We need to render the popup relative to the display
233 int[] embedderViewCoords = new int[2];
234 mAttachedView.getLocationOnScreen(embedderViewCoords);
235
236 final DisplayMetrics displayMetrics = mContext.getResources().getDisplay Metrics();
237
238 float density = displayMetrics.density;
239
240 measureContent();
241 int width = mContentView.getMeasuredWidth();
242
243 int positionX = 0;
244 int positionY = 0;
245
246 positionX = Math.round(
247 mLastCursorAnchorInfo.getInsertionMarkerHorizontal() * density - width / 2.0f);
248 positionY = (int) Math.round(
249 (embedderViewCoords[1] + mLastCursorAnchorInfo.getInsertionMarke rBottom()
250 - MENU_Y_OFFSET_FROM_INSERTION_MARKER_BOTTOM)
251 * density);
252
253 // horizontal clipping
254 width = mContentView.getMeasuredWidth();
255 positionX = Math.min(displayMetrics.widthPixels - width + mClippingLimit Right, positionX);
256 positionX = Math.max(-mClippingLimitLeft, positionX);
257
258 // vertical clipping
259 final int height = mContentView.getMeasuredHeight();
260 positionY = Math.min(positionY, displayMetrics.heightPixels - height);
261
262 if (isShowing()) {
263 mPopupWindow.update(positionX, positionY, -1, -1);
264 } else {
265 mPopupWindow.showAtLocation(mAttachedView, Gravity.NO_GRAVITY, posit ionX, positionY);
266 }
267 }
268
269 @Override
270 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
271 SuggestionInfo suggestionInfo = mSuggestionInfos[position];
272 mImeAdapter.applySuggestionReplacement(
273 suggestionInfo.mDocumentMarkerID, suggestionInfo.mSuggestionInde x);
274 mDismissedByItemTap = true;
275 mPopupWindow.dismiss();
276 }
277
278 @Override
279 public void onDismiss() {
280 if (!mDismissedByItemTap) {
281 mImeAdapter.suggestionMenuClosed();
282 }
283 mDismissedByItemTap = false;
284 mCursorAnchorInfoController.removeListener(this, mAttachedView);
285 mListeningForCursorAnchorInfoUpdates = false;
286 }
287 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698