15f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved. 25f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)// found in the LICENSE file. 45f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 55f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)package org.chromium.content.browser.input; 65f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 75f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import android.content.Context; 85f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import android.content.res.TypedArray; 95f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import android.util.TypedValue; 105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import android.view.Gravity; 115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import android.view.LayoutInflater; 125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import android.view.View; 135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import android.view.View.OnClickListener; 145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import android.view.ViewGroup; 155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import android.view.ViewGroup.LayoutParams; 165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import android.widget.PopupWindow; 175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)/** 195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * Paste popup implementation based on TextView.PastePopupMenu. 205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */ 215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)public class PastePopupMenu implements OnClickListener { 225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) private final View mParent; 235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) private final PastePopupMenuDelegate mDelegate; 245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) private final Context mContext; 255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) private final PopupWindow mContainer; 265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) private int mRawPositionX; 275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) private int mRawPositionY; 285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) private int mPositionX; 295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) private int mPositionY; 305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) private int mStatusBarHeight; 316e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) private View mPasteView; 326e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) private final int mPasteViewLayout; 335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) private final int mLineOffsetY; 345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) private final int mWidthOffsetX; 355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) /** 375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * Provider of paste functionality for the given popup. 385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */ 395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) public interface PastePopupMenuDelegate { 405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) /** 415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * Called to initiate a paste after the popup has been tapped. 425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */ 435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) void paste(); 445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) public PastePopupMenu(View parent, PastePopupMenuDelegate delegate) { 475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mParent = parent; 485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mDelegate = delegate; 495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mContext = parent.getContext(); 505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mContainer = new PopupWindow(mContext, null, 515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) android.R.attr.textSelectHandleWindowStyle); 525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mContainer.setSplitTouchEnabled(true); 535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mContainer.setClippingEnabled(false); 545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mContainer.setAnimationStyle(0); 555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mContainer.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT); 575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mContainer.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); 585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci final int[] popupLayoutAttrs = { android.R.attr.textEditPasteWindowLayout, }; 605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 616e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) mPasteView = null; 621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci TypedArray attrs = mContext.getTheme().obtainStyledAttributes(popupLayoutAttrs); 636e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) mPasteViewLayout = attrs.getResourceId(attrs.getIndex(0), 0); 646e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) 655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) attrs.recycle(); 665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) // Convert line offset dips to pixels. 685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mLineOffsetY = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 5.0f, mContext.getResources().getDisplayMetrics()); 705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mWidthOffsetX = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 30.0f, mContext.getResources().getDisplayMetrics()); 725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) final int statusBarHeightResourceId = 745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mContext.getResources().getIdentifier("status_bar_height", "dimen", "android"); 755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if (statusBarHeightResourceId > 0) { 765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mStatusBarHeight = 775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mContext.getResources().getDimensionPixelSize(statusBarHeightResourceId); 785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 795f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 815f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) /** 825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * Shows the paste popup at an appropriate location relative to the specified position. 835f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */ 845f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) public void showAt(int x, int y) { 856e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) updateContent(); 865f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) positionAt(x, y); 875f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 885f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 895f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) /** 905f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * Hides the paste popup. 915f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */ 925f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) public void hide() { 935f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mContainer.dismiss(); 945f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 955f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 965f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) /** 975f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @return Whether the popup is active and showing. 985f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */ 995f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) public boolean isShowing() { 1005f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) return mContainer.isShowing(); 1015f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 1025f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1035f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) @Override 1045f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) public void onClick(View v) { 1056e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) paste(); 1065f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) hide(); 1075f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 1085f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1095f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) private void positionAt(int x, int y) { 1105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if (mRawPositionX == x && mRawPositionY == y && isShowing()) return; 1115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mRawPositionX = x; 1125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mRawPositionY = y; 1135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) View contentView = mContainer.getContentView(); 1155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) int width = contentView.getMeasuredWidth(); 1165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) int height = contentView.getMeasuredHeight(); 1175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mPositionX = (int) (x - width / 2.0f); 1195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mPositionY = y - height - mLineOffsetY; 1205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) final int[] coords = new int[2]; 1225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mParent.getLocationInWindow(coords); 1235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) coords[0] += mPositionX; 1245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) coords[1] += mPositionY; 1255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) int minOffsetY = 0; 1275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if (mParent.getSystemUiVisibility() == View.SYSTEM_UI_FLAG_VISIBLE) { 1285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) minOffsetY = mStatusBarHeight; 1295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 1305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) final int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels; 1325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if (coords[1] < minOffsetY) { 1335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) // Update dimensions from new view 1345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) contentView = mContainer.getContentView(); 1355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) width = contentView.getMeasuredWidth(); 1365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) height = contentView.getMeasuredHeight(); 1375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) // Vertical clipping, move under edited line and to the side of insertion cursor 1395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) // TODO bottom clipping in case there is no system bar 1405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) coords[1] += height; 1415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) coords[1] += mLineOffsetY; 1425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) // Move to right hand side of insertion cursor by default. TODO RTL text. 1445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) final int handleHalfWidth = mWidthOffsetX / 2; 1455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if (x + width < screenWidth) { 1475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) coords[0] += handleHalfWidth + width / 2; 1485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } else { 1495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) coords[0] -= handleHalfWidth + width / 2; 1505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 1515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } else { 1525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) // Horizontal clipping 1535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) coords[0] = Math.max(0, coords[0]); 1545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) coords[0] = Math.min(screenWidth - width, coords[0]); 1555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 1565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1576e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) if (!isShowing()) { 1586e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) mContainer.showAtLocation(mParent, Gravity.NO_GRAVITY, coords[0], coords[1]); 1596e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) } else { 1606e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) mContainer.update(coords[0], coords[1], -1, -1); 1616e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) } 1625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 1635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1646e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) private void updateContent() { 1656e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) if (mPasteView == null) { 1666e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) final int layout = mPasteViewLayout; 1675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) LayoutInflater inflater = (LayoutInflater) mContext. 1685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) getSystemService(Context.LAYOUT_INFLATER_SERVICE); 1695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if (inflater != null) { 1706e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) mPasteView = inflater.inflate(layout, null); 1715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 1725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1736e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) if (mPasteView == null) { 1745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) throw new IllegalArgumentException("Unable to inflate TextEdit paste window"); 1755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 1765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 1786e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) mPasteView.setLayoutParams( 1796e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 1806e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) ViewGroup.LayoutParams.WRAP_CONTENT)); 1816e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) mPasteView.measure(size, size); 1825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1836e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) mPasteView.setOnClickListener(this); 1845f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 1855f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1866e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) mContainer.setContentView(mPasteView); 1875f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 1885f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1895f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) private void paste() { 1905f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) mDelegate.paste(); 1915f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 1925f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)} 193