Chromium Code Reviews| 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. |
|
Ted C
2017/06/21 17:59:21
you can wrap comments to 100chars in java, so this
rlanday
2017/06/28 01:35:34
It does :)
|
| + */ |
| + |
|
Ted C
2017/06/21 17:59:22
remove blank line
rlanday
2017/06/28 01:35:34
Ok
|
| +public class SuggestionsPopupWindow implements OnItemClickListener, OnDismissListener { |
|
Ted C
2017/06/21 17:59:21
Did you look at extending DropdownPopupWindow? Th
|
| + 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); |
|
Ted C
2017/06/21 17:59:21
any reason these aren't in createPopupWindow?
rlanday
2017/06/28 01:35:34
I was copying from Android's Editor.java where the
|
| + 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)); |
|
Ted C
2017/06/21 17:59:21
would null work here or does that make it upset?
rlanday
2017/06/28 01:35:34
Yes, null works
|
| + |
| + mPopupWindow.setFocusable(true); |
| + mPopupWindow.setClippingEnabled(false); |
|
Theresa
2017/06/21 23:44:53
This will allow the popup to draw off screen. Is t
rlanday
2017/06/28 01:35:34
I think so, I copied this from the native Android
Theresa
2017/06/28 15:43:49
Unfortunately copying Android's framework code wit
|
| + |
| + 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) { |
|
Ted C
2017/06/21 17:59:21
@Override here and below
It is often better to co
rlanday
2017/06/28 01:35:34
Ok, I'll do that
|
| + 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() { |
|
Ted C
2017/06/21 17:59:21
all non-private methods should have javadoc
rlanday
2017/06/28 01:35:34
Ok
|
| + mPopupWindow.dismiss(); |
| + } |
| + |
| + public boolean isShowing() { |
| + return mPopupWindow.isShowing(); |
| + } |
| + |
| + private void addToDictionary() { |
| + final Intent intent = new Intent(ACTION_USER_DICTIONARY_INSERT); |
|
Ted C
2017/06/21 17:59:21
are we guaranteed that this action will exist on t
rlanday
2017/06/28 01:35:34
So, I've gotten multiple opinions about this; I ta
|
| + 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(); |
|
Ted C
2017/06/21 17:59:21
in multi-window, does this represent the full scre
rlanday
2017/06/28 01:35:34
I think it's probably the display...I just tested
|
| + 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; |
|
Theresa
2017/06/21 23:44:53
The background is a transparent color drawable, ri
rlanday
2017/06/28 01:35:34
Yeah, plus tedchoc@ told me to set it to null, so
|
| + } |
| + 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); |
| + |
|
Theresa
2017/06/21 23:44:53
DropdownPopupWindow may be well suited for your us
|
| + 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; |
| + } |
| +} |