OLD | NEW |
---|---|
(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 } | |
OLD | NEW |