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