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

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

Issue 2650113004: [WIP] Add support for Android SuggestionSpans when editing text (Closed)
Patch Set: Remove logging statements, fix copyright years in new files Created 3 years, 11 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..b8db288cdb34ee0dc1897cdab813def505d068f5
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/input/SuggestionsPopupWindow.java
@@ -0,0 +1,287 @@
+// 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.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.TextAppearanceSpan;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.inputmethod.CursorAnchorInfo;
+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.
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class SuggestionsPopupWindow
+ implements OnItemClickListener, OnDismissListener, CursorAnchorInfoController.Listener {
+ private static final double MENU_Y_OFFSET_FROM_INSERTION_MARKER_BOTTOM = 24.0;
+
+ private final Context mContext;
+ private final ImeAdapter mImeAdapter;
+ private final View mAttachedView;
+
+ private CursorAnchorInfoController mCursorAnchorInfoController;
+ private CursorAnchorInfo mLastCursorAnchorInfo;
+ private boolean mListeningForCursorAnchorInfoUpdates = false;
Changwan Ryu 2017/02/14 00:58:01 no need to add '= false' or '= 0.0'. here and othe
+ private boolean mFinishShowingPopupOnCursorAnchorInfoUpdate = false;
+
+ protected PopupWindow mPopupWindow;
+ protected ViewGroup mContentView;
+ int mPositionX, mPositionY;
Changwan Ryu 2017/02/14 00:58:01 nit: you should define one variable in one line
+ int mClippingLimitLeft, mClippingLimitRight;
+ private Rect mTempRect;
+
+ private SuggestionAdapter mSuggestionsAdapter;
+ private SuggestionInfo[] mSuggestionInfos = new SuggestionInfo[0];
+ private final TextAppearanceSpan mHighlightSpan;
+ private TextView mDeleteButton;
+ private ListView mSuggestionListView;
+ private int mContainerMarginWidth;
+ private int mContainerMarginTop;
+ private double mLastInsertionMarkerBottom = 0.0;
+ private LinearLayout mContainerView;
+
+ private boolean mDismissedByItemTap = false;
+
+ public SuggestionsPopupWindow(Context context, ImeAdapter imeAdapter, View attachedView,
+ CursorAnchorInfoController cursorAnchorInfoController) {
+ mContext = context;
+ mImeAdapter = imeAdapter;
+ mAttachedView = attachedView;
+ mCursorAnchorInfoController = cursorAnchorInfoController;
+ mHighlightSpan = new TextAppearanceSpan(mContext, R.style.SuggestionHighlight);
+
+ createPopupWindow();
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
Changwan Ryu 2017/02/14 00:58:01 could you add a comment on the effect of this line
+ }
+ 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);
+ }
+
+ protected void createPopupWindow() {
Changwan Ryu 2017/02/14 00:58:01 no need to use protected
+ 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);
+ }
+
+ protected void initContentView() {
Changwan Ryu 2017/02/14 00:58:01 no need to use protected
+ 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);
+
+ 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.deleteSuggestionHighlight();
+ mDismissedByItemTap = true;
+ mPopupWindow.dismiss();
+ }
+ });
+ }
+
+ public void updatePosition() {
+ if (!mPopupWindow.isShowing()) {
+ return;
+ }
+ show();
+ }
+
+ public boolean isShowing() {
+ return mPopupWindow.isShowing();
+ }
+
+ private class SuggestionAdapter extends BaseAdapter {
+ private LayoutInflater mInflater =
+ (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ @Override
+ public int getCount() {
+ return mSuggestionInfos.length;
+ }
+ @Override
+ public Object getItem(int position) {
+ return mSuggestionInfos[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 SuggestionInfo suggestionInfo = mSuggestionInfos[position];
+ SpannableString textToSet = new SpannableString(
+ suggestionInfo.mPrefix + suggestionInfo.mText + suggestionInfo.mSuffix);
+ textToSet.setSpan(mHighlightSpan, suggestionInfo.mPrefix.length(),
+ suggestionInfo.mPrefix.length() + suggestionInfo.mText.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ textView.setText(textToSet);
+ return textView;
+ }
+ }
+
+ protected void measureContent() {
Changwan Ryu 2017/02/14 00:58:01 no need to use protected
+ 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 < mSuggestionInfos.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());
+ 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 setSuggestionInfos(SuggestionInfo[] suggestionInfos) {
+ mSuggestionInfos = suggestionInfos;
+ }
+
+ public void onCursorAnchorInfoUpdate(CursorAnchorInfo info) {
+ mLastCursorAnchorInfo = info;
+ if (mFinishShowingPopupOnCursorAnchorInfoUpdate) {
+ mFinishShowingPopupOnCursorAnchorInfoUpdate = false;
+ finishShow();
+ }
+ }
+
+ public void show() {
+ if (!mListeningForCursorAnchorInfoUpdates) {
+ mCursorAnchorInfoController.addListener(this, mAttachedView);
+ mListeningForCursorAnchorInfoUpdates = true;
+ }
+ mFinishShowingPopupOnCursorAnchorInfoUpdate = true;
+ }
+
+ // Called asynchronously after show() returns once we get an up-to-date
+ // CursorAnchorInfo back
+ private void finishShow() {
+ mSuggestionsAdapter.notifyDataSetChanged();
+ // We get the insertion point coords relative to the WebView bounds.
+ // We need to render the popup relative to the display
+ int[] embedderViewCoords = new int[2];
+ mAttachedView.getLocationOnScreen(embedderViewCoords);
+
+ final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
+
+ float density = displayMetrics.density;
+
+ measureContent();
+ int width = mContentView.getMeasuredWidth();
+
+ int positionX = 0;
+ int positionY = 0;
+
+ positionX = Math.round(
+ mLastCursorAnchorInfo.getInsertionMarkerHorizontal() * density - width / 2.0f);
+ positionY = (int) Math.round(
+ (embedderViewCoords[1] + mLastCursorAnchorInfo.getInsertionMarkerBottom()
+ - MENU_Y_OFFSET_FROM_INSERTION_MARKER_BOTTOM)
+ * density);
+
+ // 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) {
+ SuggestionInfo suggestionInfo = mSuggestionInfos[position];
+ mImeAdapter.applySuggestionReplacement(
+ suggestionInfo.mDocumentMarkerID, suggestionInfo.mSuggestionIndex);
+ mDismissedByItemTap = true;
+ mPopupWindow.dismiss();
+ }
+
+ @Override
+ public void onDismiss() {
+ if (!mDismissedByItemTap) {
+ mImeAdapter.suggestionMenuClosed();
+ }
+ mDismissedByItemTap = false;
+ mCursorAnchorInfoController.removeListener(this, mAttachedView);
+ mListeningForCursorAnchorInfoUpdates = false;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698