| 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..96626ef1c4e9c68b502b9deb596911d9f8e08882
|
| --- /dev/null
|
| +++ b/content/public/android/java/src/org/chromium/content/browser/input/SuggestionsPopupWindow.java
|
| @@ -0,0 +1,271 @@
|
| +// 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 TextSuggestionHost mTextSuggestionHost;
|
| + private final View mAttachedView;
|
| +
|
| + private PopupWindow mPopupWindow;
|
| + private ViewGroup mContentView;
|
| + 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 LinearLayout mContainerView;
|
| +
|
| + private boolean mDismissedByItemTap;
|
| +
|
| + public SuggestionsPopupWindow(
|
| + Context context, TextSuggestionHost textSuggestionHost, View attachedView) {
|
| + mContext = context;
|
| + mTextSuggestionHost = textSuggestionHost;
|
| + 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) {
|
| + mTextSuggestionHost.deleteActiveSuggestionRange();
|
| + mDismissedByItemTap = true;
|
| + mPopupWindow.dismiss();
|
| + }
|
| + });
|
| +
|
| + mAddToDictionaryButton = (TextView) mContentView.findViewById(R.id.addToDictionaryButton);
|
| + mAddToDictionaryButton.setOnClickListener(new View.OnClickListener() {
|
| + public void onClick(View v) {
|
| + addToDictionary();
|
| + mTextSuggestionHost.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.clone();
|
| + }
|
| +
|
| + 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();
|
| +
|
| + measureContent();
|
| + int width = mContentView.getMeasuredWidth();
|
| +
|
| + int positionX = (int) Math.round(caretX - width / 2.0f);
|
| + int positionY = (int) Math.round(caretY);
|
| +
|
| + 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];
|
| + mTextSuggestionHost.applySpellCheckSuggestion(suggestion);
|
| + mDismissedByItemTap = true;
|
| + mPopupWindow.dismiss();
|
| + }
|
| +
|
| + @Override
|
| + public void onDismiss() {
|
| + mTextSuggestionHost.suggestionMenuClosed(mDismissedByItemTap);
|
| + mDismissedByItemTap = false;
|
| + }
|
| +}
|
|
|