1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.content.browser.input; 6 7 import android.content.Context; 8 import android.content.res.TypedArray; 9 import android.util.TypedValue; 10 import android.view.Gravity; 11 import android.view.LayoutInflater; 12 import android.view.View; 13 import android.view.View.OnClickListener; 14 import android.view.ViewGroup; 15 import android.view.ViewGroup.LayoutParams; 16 import android.widget.PopupWindow; 17 18 /** 19 * Paste popup implementation based on TextView.PastePopupMenu. 20 */ 21 public class PastePopupMenu implements OnClickListener { 22 private final View mParent; 23 private final PastePopupMenuDelegate mDelegate; 24 private final Context mContext; 25 private final PopupWindow mContainer; 26 private int mRawPositionX; 27 private int mRawPositionY; 28 private int mPositionX; 29 private int mPositionY; 30 private int mStatusBarHeight; 31 private View mPasteView; 32 private final int mPasteViewLayout; 33 private final int mLineOffsetY; 34 private final int mWidthOffsetX; 35 36 /** 37 * Provider of paste functionality for the given popup. 38 */ 39 public interface PastePopupMenuDelegate { 40 /** 41 * Called to initiate a paste after the popup has been tapped. 42 */ paste()43 void paste(); 44 } 45 PastePopupMenu(View parent, PastePopupMenuDelegate delegate)46 public PastePopupMenu(View parent, PastePopupMenuDelegate delegate) { 47 mParent = parent; 48 mDelegate = delegate; 49 mContext = parent.getContext(); 50 mContainer = new PopupWindow(mContext, null, 51 android.R.attr.textSelectHandleWindowStyle); 52 mContainer.setSplitTouchEnabled(true); 53 mContainer.setClippingEnabled(false); 54 mContainer.setAnimationStyle(0); 55 56 mContainer.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT); 57 mContainer.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); 58 59 final int[] popupLayoutAttrs = { android.R.attr.textEditPasteWindowLayout, }; 60 61 mPasteView = null; 62 TypedArray attrs = mContext.getTheme().obtainStyledAttributes(popupLayoutAttrs); 63 mPasteViewLayout = attrs.getResourceId(attrs.getIndex(0), 0); 64 65 attrs.recycle(); 66 67 // Convert line offset dips to pixels. 68 mLineOffsetY = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 69 5.0f, mContext.getResources().getDisplayMetrics()); 70 mWidthOffsetX = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 71 30.0f, mContext.getResources().getDisplayMetrics()); 72 73 final int statusBarHeightResourceId = 74 mContext.getResources().getIdentifier("status_bar_height", "dimen", "android"); 75 if (statusBarHeightResourceId > 0) { 76 mStatusBarHeight = 77 mContext.getResources().getDimensionPixelSize(statusBarHeightResourceId); 78 } 79 } 80 81 /** 82 * Shows the paste popup at an appropriate location relative to the specified position. 83 */ showAt(int x, int y)84 public void showAt(int x, int y) { 85 updateContent(); 86 positionAt(x, y); 87 } 88 89 /** 90 * Hides the paste popup. 91 */ hide()92 public void hide() { 93 mContainer.dismiss(); 94 } 95 96 /** 97 * @return Whether the popup is active and showing. 98 */ isShowing()99 public boolean isShowing() { 100 return mContainer.isShowing(); 101 } 102 103 @Override onClick(View v)104 public void onClick(View v) { 105 paste(); 106 hide(); 107 } 108 positionAt(int x, int y)109 private void positionAt(int x, int y) { 110 if (mRawPositionX == x && mRawPositionY == y && isShowing()) return; 111 mRawPositionX = x; 112 mRawPositionY = y; 113 114 View contentView = mContainer.getContentView(); 115 int width = contentView.getMeasuredWidth(); 116 int height = contentView.getMeasuredHeight(); 117 118 mPositionX = (int) (x - width / 2.0f); 119 mPositionY = y - height - mLineOffsetY; 120 121 final int[] coords = new int[2]; 122 mParent.getLocationInWindow(coords); 123 coords[0] += mPositionX; 124 coords[1] += mPositionY; 125 126 int minOffsetY = 0; 127 if (mParent.getSystemUiVisibility() == View.SYSTEM_UI_FLAG_VISIBLE) { 128 minOffsetY = mStatusBarHeight; 129 } 130 131 final int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels; 132 if (coords[1] < minOffsetY) { 133 // Update dimensions from new view 134 contentView = mContainer.getContentView(); 135 width = contentView.getMeasuredWidth(); 136 height = contentView.getMeasuredHeight(); 137 138 // Vertical clipping, move under edited line and to the side of insertion cursor 139 // TODO bottom clipping in case there is no system bar 140 coords[1] += height; 141 coords[1] += mLineOffsetY; 142 143 // Move to right hand side of insertion cursor by default. TODO RTL text. 144 final int handleHalfWidth = mWidthOffsetX / 2; 145 146 if (x + width < screenWidth) { 147 coords[0] += handleHalfWidth + width / 2; 148 } else { 149 coords[0] -= handleHalfWidth + width / 2; 150 } 151 } else { 152 // Horizontal clipping 153 coords[0] = Math.max(0, coords[0]); 154 coords[0] = Math.min(screenWidth - width, coords[0]); 155 } 156 157 if (!isShowing()) { 158 mContainer.showAtLocation(mParent, Gravity.NO_GRAVITY, coords[0], coords[1]); 159 } else { 160 mContainer.update(coords[0], coords[1], -1, -1); 161 } 162 } 163 updateContent()164 private void updateContent() { 165 if (mPasteView == null) { 166 final int layout = mPasteViewLayout; 167 LayoutInflater inflater = (LayoutInflater) mContext. 168 getSystemService(Context.LAYOUT_INFLATER_SERVICE); 169 if (inflater != null) { 170 mPasteView = inflater.inflate(layout, null); 171 } 172 173 if (mPasteView == null) { 174 throw new IllegalArgumentException("Unable to inflate TextEdit paste window"); 175 } 176 177 final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 178 mPasteView.setLayoutParams( 179 new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 180 ViewGroup.LayoutParams.WRAP_CONTENT)); 181 mPasteView.measure(size, size); 182 183 mPasteView.setOnClickListener(this); 184 } 185 186 mContainer.setContentView(mPasteView); 187 } 188 paste()189 private void paste() { 190 mDelegate.paste(); 191 } 192 } 193