Index: content/public/android/java/src/org/chromium/content/browser/input/SuggestionsPopupWindow.java |
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/SuggestionsPopupWindow.java b/content/public/android/java/src/org/chromium/content/browser/input/SuggestionsPopupWindow.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..632c6c634e83bc97ab139d353bb1b8b67c7e32fd |
--- /dev/null |
+++ b/content/public/android/java/src/org/chromium/content/browser/input/SuggestionsPopupWindow.java |
@@ -0,0 +1,281 @@ |
+// Copyright 2017 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.content.browser.input; |
+ |
+import android.content.Context; |
+import android.content.Intent; |
+import android.graphics.Color; |
+import android.graphics.Rect; |
+import android.graphics.drawable.ColorDrawable; |
+import android.graphics.drawable.Drawable; |
+import android.util.DisplayMetrics; |
+import android.view.Gravity; |
+import android.view.LayoutInflater; |
+import android.view.View; |
+import android.view.ViewGroup; |
+import android.widget.AdapterView; |
+import android.widget.AdapterView.OnItemClickListener; |
+import android.widget.BaseAdapter; |
+import android.widget.LinearLayout; |
+import android.widget.ListView; |
+import android.widget.PopupWindow; |
+import android.widget.PopupWindow.OnDismissListener; |
+import android.widget.TextView; |
+ |
+import org.chromium.content.R; |
+ |
+/** |
+ * Popup window that displays a menu for viewing and applying text replacement |
+ * suggestions. |
+ */ |
+ |
+public class SuggestionsPopupWindow implements OnItemClickListener, OnDismissListener { |
+ private static final String ACTION_USER_DICTIONARY_INSERT = |
+ "com.android.settings.USER_DICTIONARY_INSERT"; |
+ private static final String USER_DICTIONARY_EXTRA_WORD = "word"; |
+ |
+ private final Context mContext; |
+ private final ImeAdapter mImeAdapter; |
+ private final View mAttachedView; |
+ |
+ private PopupWindow mPopupWindow; |
+ private ViewGroup mContentView; |
+ private int mPositionX; |
+ private int mPositionY; |
+ private int mClippingLimitLeft; |
+ private int mClippingLimitRight; |
+ private Rect mTempRect; |
+ |
+ private SuggestionAdapter mSuggestionsAdapter; |
+ private String mHighlightedText; |
+ private String[] mSpellCheckSuggestions = new String[0]; |
+ private TextView mDeleteButton; |
+ private TextView mAddToDictionaryButton; |
+ private ListView mSuggestionListView; |
+ private int mContainerMarginWidth; |
+ private int mContainerMarginTop; |
+ private double mLastInsertionMarkerBottom; |
+ private LinearLayout mContainerView; |
+ |
+ private boolean mDismissedByItemTap; |
+ |
+ public SuggestionsPopupWindow(Context context, ImeAdapter imeAdapter, View attachedView, |
+ CursorAnchorInfoController cursorAnchorInfoController) { |
+ mContext = context; |
+ mImeAdapter = imeAdapter; |
+ mAttachedView = attachedView; |
+ |
+ createPopupWindow(); |
+ |
+ mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT); |
+ mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); |
+ |
+ initContentView(); |
+ |
+ ViewGroup.LayoutParams wrapContent = new ViewGroup.LayoutParams( |
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); |
+ mContentView.setLayoutParams(wrapContent); |
+ mPopupWindow.setContentView(mContentView); |
+ } |
+ |
+ private void createPopupWindow() { |
+ mPopupWindow = new PopupWindow(); |
+ mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); |
+ mPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); |
+ |
+ mPopupWindow.setFocusable(true); |
+ mPopupWindow.setClippingEnabled(false); |
+ |
+ mPopupWindow.setOnDismissListener(this); |
+ } |
+ |
+ private void initContentView() { |
+ final LayoutInflater inflater = |
+ (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
+ mContentView = (ViewGroup) inflater.inflate(R.layout.text_edit_suggestion_container, null); |
+ mContainerView = (LinearLayout) mContentView.findViewById(R.id.suggestionWindowContainer); |
+ ViewGroup.MarginLayoutParams lp = |
+ (ViewGroup.MarginLayoutParams) mContainerView.getLayoutParams(); |
+ mContainerMarginWidth = lp.leftMargin + lp.rightMargin; |
+ mContainerMarginTop = lp.topMargin; |
+ mClippingLimitLeft = lp.leftMargin; |
+ mClippingLimitRight = lp.rightMargin; |
+ |
+ mSuggestionListView = (ListView) mContentView.findViewById(R.id.suggestionContainer); |
+ // android:divider="@null" in the XML file crashes on Android N and O |
+ // when running as a WebView (b/38346876) |
+ mSuggestionListView.setDivider(null); |
+ |
+ mSuggestionsAdapter = new SuggestionAdapter(); |
+ mSuggestionListView.setAdapter(mSuggestionsAdapter); |
+ mSuggestionListView.setOnItemClickListener(this); |
+ |
+ mDeleteButton = (TextView) mContentView.findViewById(R.id.deleteButton); |
+ mDeleteButton.setOnClickListener(new View.OnClickListener() { |
+ public void onClick(View v) { |
+ mImeAdapter.deleteActiveSuggestionRange(); |
+ mDismissedByItemTap = true; |
+ mPopupWindow.dismiss(); |
+ } |
+ }); |
+ |
+ mAddToDictionaryButton = (TextView) mContentView.findViewById(R.id.addToDictionaryButton); |
+ mAddToDictionaryButton.setOnClickListener(new View.OnClickListener() { |
+ public void onClick(View v) { |
+ addToDictionary(); |
+ mImeAdapter.newWordAddedToDictionary(mHighlightedText); |
+ mDismissedByItemTap = true; |
+ mPopupWindow.dismiss(); |
+ } |
+ }); |
+ } |
+ |
+ public void dismiss() { |
+ mPopupWindow.dismiss(); |
+ } |
+ |
+ public boolean isShowing() { |
+ return mPopupWindow.isShowing(); |
+ } |
+ |
+ private void addToDictionary() { |
+ final Intent intent = new Intent(ACTION_USER_DICTIONARY_INSERT); |
+ intent.putExtra(USER_DICTIONARY_EXTRA_WORD, mHighlightedText); |
+ intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); |
+ mContext.startActivity(intent); |
+ } |
+ |
+ private class SuggestionAdapter extends BaseAdapter { |
+ private LayoutInflater mInflater = |
+ (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
+ |
+ @Override |
+ public int getCount() { |
+ return mSpellCheckSuggestions.length; |
+ } |
+ |
+ @Override |
+ public Object getItem(int position) { |
+ return mSpellCheckSuggestions[position]; |
+ } |
+ |
+ @Override |
+ public long getItemId(int position) { |
+ return position; |
+ } |
+ |
+ @Override |
+ public View getView(int position, View convertView, ViewGroup parent) { |
+ TextView textView = (TextView) convertView; |
+ if (textView == null) { |
+ textView = (TextView) mInflater.inflate( |
+ R.layout.text_edit_suggestion_item, parent, false); |
+ } |
+ final String suggestion = mSpellCheckSuggestions[position]; |
+ textView.setText(suggestion); |
+ return textView; |
+ } |
+ } |
+ |
+ private void measureContent() { |
+ final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); |
+ final int horizontalMeasure = View.MeasureSpec.makeMeasureSpec( |
+ displayMetrics.widthPixels, View.MeasureSpec.AT_MOST); |
+ final int verticalMeasure = View.MeasureSpec.makeMeasureSpec( |
+ displayMetrics.heightPixels, View.MeasureSpec.AT_MOST); |
+ int width = 0; |
+ View view = null; |
+ for (int i = 0; i < mSpellCheckSuggestions.length; i++) { |
+ view = mSuggestionsAdapter.getView(i, view, mContentView); |
+ view.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT; |
+ view.measure(horizontalMeasure, verticalMeasure); |
+ width = Math.max(width, view.getMeasuredWidth()); |
+ } |
+ mDeleteButton.measure(horizontalMeasure, verticalMeasure); |
+ width = Math.max(width, mDeleteButton.getMeasuredWidth()); |
+ mAddToDictionaryButton.measure(horizontalMeasure, verticalMeasure); |
+ width = Math.max(width, mAddToDictionaryButton.getMeasuredWidth()); |
+ width += mContainerView.getPaddingLeft() + mContainerView.getPaddingRight() |
+ + mContainerMarginWidth; |
+ // Enforce the width based on actual text widths |
+ mContentView.measure( |
+ View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), verticalMeasure); |
+ Drawable popupBackground = mPopupWindow.getBackground(); |
+ if (popupBackground != null) { |
+ if (mTempRect == null) mTempRect = new Rect(); |
+ popupBackground.getPadding(mTempRect); |
+ width += mTempRect.left + mTempRect.right; |
+ } |
+ mPopupWindow.setWidth(width); |
+ } |
+ |
+ public void setHighlightedText(String text) { |
+ mHighlightedText = text; |
+ } |
+ |
+ public void setSpellCheckSuggestions(String[] suggestions) { |
+ mSpellCheckSuggestions = suggestions; |
+ } |
+ |
+ public void show(double caretX, double caretY) { |
+ mSuggestionsAdapter.notifyDataSetChanged(); |
+ // We get the insertion point coords relative to the WebView bounds. |
+ // We need to render the popup relative to the display |
+ int[] locationOnScreen = new int[2]; |
+ mAttachedView.getLocationOnScreen(locationOnScreen); |
+ |
+ final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); |
+ |
+ float density = displayMetrics.density; |
+ |
+ measureContent(); |
+ int width = mContentView.getMeasuredWidth(); |
+ |
+ int positionX = 0; |
+ int positionY = 0; |
+ |
+ positionX = (int) Math.round(caretX * density - width / 2.0f); |
+ positionY = (int) Math.round(mImeAdapter.getContentOffsetYPix() + caretY * density); |
+ |
+ final int[] positionInWindow = new int[2]; |
+ mAttachedView.getLocationInWindow(positionInWindow); |
+ |
+ positionX += positionInWindow[0]; |
+ positionY += positionInWindow[1]; |
+ |
+ positionY -= mContainerMarginTop; |
+ |
+ // horizontal clipping |
+ width = mContentView.getMeasuredWidth(); |
+ positionX = Math.min(displayMetrics.widthPixels - width + mClippingLimitRight, positionX); |
+ positionX = Math.max(-mClippingLimitLeft, positionX); |
+ |
+ // vertical clipping |
+ final int height = mContentView.getMeasuredHeight(); |
+ positionY = Math.min(positionY, displayMetrics.heightPixels - height); |
+ |
+ if (isShowing()) { |
+ mPopupWindow.update(positionX, positionY, -1, -1); |
+ } else { |
+ mPopupWindow.showAtLocation(mAttachedView, Gravity.NO_GRAVITY, positionX, positionY); |
+ } |
+ } |
+ |
+ @Override |
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
+ String suggestion = mSpellCheckSuggestions[position]; |
+ mImeAdapter.applySpellCheckSuggestion(suggestion); |
+ mDismissedByItemTap = true; |
+ mPopupWindow.dismiss(); |
+ } |
+ |
+ @Override |
+ public void onDismiss() { |
+ if (!mDismissedByItemTap) { |
+ mImeAdapter.suggestionMenuClosed(); |
+ } |
+ mDismissedByItemTap = false; |
+ } |
+} |