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

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

Issue 2931443003: Add support for Android spellcheck menu in Chrome/WebViews (Closed)
Patch Set: Actually split off LayoutTheme changes Created 3 years, 6 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 side-by-side diff with in-line comments
Download patch
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;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698