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.
5 package org.chromium.content.browser.input;
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;
14 import org.chromium.base.CalledByNative;
15 import org.chromium.base.JNINamespace;
16 import org.chromium.content.browser.PositionObserver;
18 import java.lang.ref.WeakReference;
21 * View that displays a selection or insertion handle for text editing.
23 * While a HandleView is logically a child of some other view, it does not exist in that View's
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;
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;
39 // The observer reference will only be non-null while it is attached to mParentPositionListener.
40 private PositionObserver mParentPositionObserver;
42 // The position of the handle relative to the parent view.
43 private int mPositionX;
44 private int mPositionY;
46 // The position of the parent relative to the application's root view.
47 private int mParentPositionX;
48 private int mParentPositionY;
50 // The offset from this handles position to the "tip" of the handle.
51 private float mHotspotX;
52 private float mHotspotY;
56 private final int[] mTempScreenCoords = new int[2];
58 static final int LEFT = 0;
59 static final int CENTER = 1;
60 static final int RIGHT = 2;
61 private int mOrientation = -1;
64 * Provides additional interaction behaviors necessary for handle
65 * manipulation and interaction.
67 public interface PopupTouchHandleDrawableDelegate {
69 * @return The parent View of the PopupWindow.
74 * @return A position observer for the parent View, used to keep the
75 * absolutely positioned PopupWindow in-sync with the parent.
77 PositionObserver getParentPositionObserver();
80 * Should route MotionEvents to the appropriate logic layer for
81 * performing handle manipulation.
83 boolean onTouchHandleEvent(MotionEvent ev);
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);
94 mParentPositionListener = new PositionObserver.Listener() {
96 public void onPositionChanged(int x, int y) {
97 updateParentPosition(x, y);
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.
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();
123 private void setOrientation(int orientation) {
124 assert orientation >= LEFT && orientation <= RIGHT;
125 if (mOrientation == orientation) return;
127 final boolean hadValidOrientation = mOrientation != -1;
128 mOrientation = orientation;
130 final int oldAdjustedPositionX = getAdjustedPositionX();
131 final int oldAdjustedPositionY = getAdjustedPositionY();
133 switch (orientation) {
135 mDrawable = HandleViewResources.getLeftHandleDrawable(mContext);
136 mHotspotX = (mDrawable.getIntrinsicWidth() * 3) / 4f;
141 mDrawable = HandleViewResources.getRightHandleDrawable(mContext);
142 mHotspotX = mDrawable.getIntrinsicWidth() / 4f;
148 mDrawable = HandleViewResources.getCenterHandleDrawable(mContext);
149 mHotspotX = mDrawable.getIntrinsicWidth() / 2f;
155 // Force handle repositioning to accommodate the new orientation's hotspot.
156 if (hadValidOrientation) positionAt(oldAdjustedPositionX, oldAdjustedPositionY);
157 mDrawable.setAlpha((int) (255 * mAlpha));
162 private void updateParentPosition(int parentPositionX, int parentPositionY) {
163 mParentPositionX = parentPositionX;
164 mParentPositionY = parentPositionY;
168 private int getContainerPositionX() {
169 return mParentPositionX + mPositionX;
172 private int getContainerPositionY() {
173 return mParentPositionY + mPositionY;
176 private void onPositionChanged() {
177 mContainer.update(getContainerPositionX(), getContainerPositionY(),
178 getRight() - getLeft(), getBottom() - getTop());
181 // x and y are in physical pixels.
182 private void moveTo(int x, int y) {
189 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
190 if (mDrawable == null) {
191 setMeasuredDimension(0, 0);
194 setMeasuredDimension(mDrawable.getIntrinsicWidth(),
195 mDrawable.getIntrinsicHeight());
199 protected void onDraw(Canvas c) {
200 if (mDrawable == null) return;
201 mDrawable.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop());
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));
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);
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);
223 private void show() {
224 if (mContainer.isShowing()) return;
226 final PopupTouchHandleDrawableDelegate delegate = mDelegate.get();
227 if (delegate == null) {
232 mParentPositionObserver = delegate.getParentPositionObserver();
233 assert mParentPositionObserver != null;
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());
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;
256 private void setRightOrientation() {
257 setOrientation(RIGHT);
261 private void setLeftOrientation() {
262 setOrientation(LEFT);
266 private void setCenterOrientation() {
267 setOrientation(CENTER);
271 private void setOpacity(float alpha) {
272 if (mAlpha == alpha) return;
274 if (mDrawable != null) mDrawable.setAlpha((int) (255 * mAlpha));
278 private void setFocus(float x, float y) {
279 positionAt((int) x, (int) y);
283 private void setVisible(boolean visible) {
284 setVisibility(visible ? VISIBLE : INVISIBLE);
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);