Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / content / public / android / java / src / org / chromium / content / browser / input / PopupTouchHandleDrawable.java
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.graphics.Canvas;
9 import android.graphics.drawable.Drawable;
10 import android.view.MotionEvent;
11 import android.view.View;
12 import android.widget.PopupWindow;
13
14 import org.chromium.base.CalledByNative;
15 import org.chromium.base.JNINamespace;
16 import org.chromium.content.browser.PositionObserver;
17
18 import java.lang.ref.WeakReference;
19
20 /**
21  * View that displays a selection or insertion handle for text editing.
22  *
23  * While a HandleView is logically a child of some other view, it does not exist in that View's
24  * hierarchy.
25  *
26  */
27 @JNINamespace("content")
28 public class PopupTouchHandleDrawable extends View {
29     private Drawable mDrawable;
30     private final PopupWindow mContainer;
31     private final Context mContext;
32     private final PositionObserver.Listener mParentPositionListener;
33
34     // The weak delegate reference allows the PopupTouchHandleDrawable to be owned by a native
35     // object that might have a different lifetime (or a cyclic lifetime) with respect to the
36     // delegate, allowing garbage collection of any Java references.
37     private final WeakReference<PopupTouchHandleDrawableDelegate> mDelegate;
38
39     // The observer reference will only be non-null while it is attached to mParentPositionListener.
40     private PositionObserver mParentPositionObserver;
41
42     // The position of the handle relative to the parent view.
43     private int mPositionX;
44     private int mPositionY;
45
46     // The position of the parent relative to the application's root view.
47     private int mParentPositionX;
48     private int mParentPositionY;
49
50     // The offset from this handles position to the "tip" of the handle.
51     private float mHotspotX;
52     private float mHotspotY;
53
54     private float mAlpha;
55
56     private final int[] mTempScreenCoords = new int[2];
57
58     static final int LEFT = 0;
59     static final int CENTER = 1;
60     static final int RIGHT = 2;
61     private int mOrientation = -1;
62
63     /**
64      * Provides additional interaction behaviors necessary for handle
65      * manipulation and interaction.
66      */
67     public interface PopupTouchHandleDrawableDelegate {
68         /**
69          * @return The parent View of the PopupWindow.
70          */
71         View getParent();
72
73         /**
74          * @return A position observer for the parent View, used to keep the
75          *         absolutely positioned PopupWindow in-sync with the parent.
76          */
77         PositionObserver getParentPositionObserver();
78
79         /**
80          * Should route MotionEvents to the appropriate logic layer for
81          * performing handle manipulation.
82          */
83         boolean onTouchHandleEvent(MotionEvent ev);
84     }
85
86     public PopupTouchHandleDrawable(PopupTouchHandleDrawableDelegate delegate) {
87         super(delegate.getParent().getContext());
88         mContext = delegate.getParent().getContext();
89         mDelegate = new WeakReference<PopupTouchHandleDrawableDelegate>(delegate);
90         mContainer = new PopupWindow(mContext, null, android.R.attr.textSelectHandleWindowStyle);
91         mContainer.setSplitTouchEnabled(true);
92         mContainer.setClippingEnabled(false);
93         mAlpha = 1.f;
94         mParentPositionListener = new PositionObserver.Listener() {
95             @Override
96             public void onPositionChanged(int x, int y) {
97                 updateParentPosition(x, y);
98             }
99         };
100     }
101
102     @Override
103     public boolean onTouchEvent(MotionEvent event) {
104         final PopupTouchHandleDrawableDelegate delegate = mDelegate.get();
105         if (delegate == null) {
106             // If the delegate is gone, we should immediately dispose of the popup.
107             hide();
108             return false;
109         }
110
111         // Convert from PopupWindow local coordinates to
112         // parent view local coordinates prior to forwarding.
113         delegate.getParent().getLocationOnScreen(mTempScreenCoords);
114         final float offsetX = event.getRawX() - event.getX() - mTempScreenCoords[0];
115         final float offsetY = event.getRawY() - event.getY() - mTempScreenCoords[1];
116         final MotionEvent offsetEvent = MotionEvent.obtainNoHistory(event);
117         offsetEvent.offsetLocation(offsetX, offsetY);
118         final boolean handled = delegate.onTouchHandleEvent(offsetEvent);
119         offsetEvent.recycle();
120         return handled;
121     }
122
123     private void setOrientation(int orientation) {
124         assert orientation >= LEFT && orientation <= RIGHT;
125         if (mOrientation == orientation) return;
126
127         final boolean hadValidOrientation = mOrientation != -1;
128         mOrientation = orientation;
129
130         final int oldAdjustedPositionX = getAdjustedPositionX();
131         final int oldAdjustedPositionY = getAdjustedPositionY();
132
133         switch (orientation) {
134             case LEFT: {
135                 mDrawable = HandleViewResources.getLeftHandleDrawable(mContext);
136                 mHotspotX = (mDrawable.getIntrinsicWidth() * 3) / 4f;
137                 break;
138             }
139
140             case RIGHT: {
141                 mDrawable = HandleViewResources.getRightHandleDrawable(mContext);
142                 mHotspotX = mDrawable.getIntrinsicWidth() / 4f;
143                 break;
144             }
145
146             case CENTER:
147             default: {
148                 mDrawable = HandleViewResources.getCenterHandleDrawable(mContext);
149                 mHotspotX = mDrawable.getIntrinsicWidth() / 2f;
150                 break;
151             }
152         }
153         mHotspotY = 0;
154
155         // Force handle repositioning to accommodate the new orientation's hotspot.
156         if (hadValidOrientation) positionAt(oldAdjustedPositionX, oldAdjustedPositionY);
157         mDrawable.setAlpha((int) (255 * mAlpha));
158
159         invalidate();
160     }
161
162     private void updateParentPosition(int parentPositionX, int parentPositionY) {
163         mParentPositionX = parentPositionX;
164         mParentPositionY = parentPositionY;
165         onPositionChanged();
166     }
167
168     private int getContainerPositionX() {
169         return mParentPositionX + mPositionX;
170     }
171
172     private int getContainerPositionY() {
173         return mParentPositionY + mPositionY;
174     }
175
176     private void onPositionChanged() {
177         mContainer.update(getContainerPositionX(), getContainerPositionY(),
178                 getRight() - getLeft(), getBottom() - getTop());
179     }
180
181     // x and y are in physical pixels.
182     private void moveTo(int x, int y) {
183         mPositionX = x;
184         mPositionY = y;
185         onPositionChanged();
186     }
187
188     @Override
189     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
190         if (mDrawable == null) {
191             setMeasuredDimension(0, 0);
192             return;
193         }
194         setMeasuredDimension(mDrawable.getIntrinsicWidth(),
195                 mDrawable.getIntrinsicHeight());
196     }
197
198     @Override
199     protected void onDraw(Canvas c) {
200         if (mDrawable == null) return;
201         mDrawable.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop());
202         mDrawable.draw(c);
203     }
204
205     // x and y are in physical pixels.
206     private void positionAt(int x, int y) {
207         moveTo(x - Math.round(mHotspotX), y - Math.round(mHotspotY));
208     }
209
210     // Returns the x coordinate of the position that the handle appears to be pointing to relative
211     // to the handles "parent" view.
212     private int getAdjustedPositionX() {
213         return mPositionX + Math.round(mHotspotX);
214     }
215
216     // Returns the y coordinate of the position that the handle appears to be pointing to relative
217     // to the handles "parent" view.
218     private int getAdjustedPositionY() {
219         return mPositionY + Math.round(mHotspotY);
220     }
221
222     @CalledByNative
223     private void show() {
224         if (mContainer.isShowing()) return;
225
226         final PopupTouchHandleDrawableDelegate delegate = mDelegate.get();
227         if (delegate == null) {
228             hide();
229             return;
230         }
231
232         mParentPositionObserver = delegate.getParentPositionObserver();
233         assert mParentPositionObserver != null;
234
235         // While hidden, the parent position may have become stale. It must be updated before
236         // checking isPositionVisible().
237         updateParentPosition(mParentPositionObserver.getPositionX(),
238                 mParentPositionObserver.getPositionY());
239         mParentPositionObserver.addListener(mParentPositionListener);
240         mContainer.setContentView(this);
241         mContainer.showAtLocation(delegate.getParent(), 0,
242                 getContainerPositionX(), getContainerPositionY());
243     }
244
245     @CalledByNative
246     private void hide() {
247         mContainer.dismiss();
248         if (mParentPositionObserver != null) {
249             mParentPositionObserver.removeListener(mParentPositionListener);
250             // Clear the strong reference to allow garbage collection.
251             mParentPositionObserver = null;
252         }
253     }
254
255     @CalledByNative
256     private void setRightOrientation() {
257         setOrientation(RIGHT);
258     }
259
260     @CalledByNative
261     private void setLeftOrientation() {
262         setOrientation(LEFT);
263     }
264
265     @CalledByNative
266     private void setCenterOrientation() {
267         setOrientation(CENTER);
268     }
269
270     @CalledByNative
271     private void setOpacity(float alpha) {
272         if (mAlpha == alpha) return;
273         mAlpha = alpha;
274         if (mDrawable != null) mDrawable.setAlpha((int) (255 * mAlpha));
275     }
276
277     @CalledByNative
278     private void setFocus(float x, float y) {
279         positionAt((int) x, (int) y);
280     }
281
282     @CalledByNative
283     private void setVisible(boolean visible) {
284         setVisibility(visible ? VISIBLE : INVISIBLE);
285     }
286
287     @CalledByNative
288     private boolean intersectsWith(float x, float y, float width, float height) {
289         if (mDrawable == null) return false;
290         final int drawableWidth = mDrawable.getIntrinsicWidth();
291         final int drawableHeight = mDrawable.getIntrinsicHeight();
292         return !(x >= mPositionX + drawableWidth
293                 || y >= mPositionY + drawableHeight
294                 || x + width <= mPositionX
295                 || y + height <= mPositionY);
296     }
297 }