Home | History | Annotate | Download | only in input
      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          */
     43         void paste();
     44     }
     45 
     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      */
     84     public void showAt(int x, int y) {
     85         updateContent();
     86         positionAt(x, y);
     87     }
     88 
     89     /**
     90      * Hides the paste popup.
     91      */
     92     public void hide() {
     93         mContainer.dismiss();
     94     }
     95 
     96     /**
     97      * @return Whether the popup is active and showing.
     98      */
     99     public boolean isShowing() {
    100         return mContainer.isShowing();
    101     }
    102 
    103     @Override
    104     public void onClick(View v) {
    105         paste();
    106         hide();
    107     }
    108 
    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 
    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 
    189     private void paste() {
    190         mDelegate.paste();
    191     }
    192 }
    193