PastePopupMenu.java revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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
5package org.chromium.content.browser.input;
6
7import android.content.Context;
8import android.content.res.TypedArray;
9import android.util.TypedValue;
10import android.view.Gravity;
11import android.view.LayoutInflater;
12import android.view.View;
13import android.view.View.OnClickListener;
14import android.view.ViewGroup;
15import android.view.ViewGroup.LayoutParams;
16import android.widget.PopupWindow;
17
18/**
19 * Paste popup implementation based on TextView.PastePopupMenu.
20 */
21public 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[] POPUP_LAYOUT_ATTRS = { android.R.attr.textEditPasteWindowLayout, };
60
61        mPasteView = null;
62        TypedArray attrs = mContext.getTheme().obtainStyledAttributes(POPUP_LAYOUT_ATTRS);
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