| Index: content/public/android/java/src/org/chromium/content/browser/input/PopupTouchHandleDrawable.java
 | 
| diff --git a/content/public/android/java/src/org/chromium/content/browser/input/PopupTouchHandleDrawable.java b/content/public/android/java/src/org/chromium/content/browser/input/PopupTouchHandleDrawable.java
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..62a80080b27935a8e643c903939601dc51befd57
 | 
| --- /dev/null
 | 
| +++ b/content/public/android/java/src/org/chromium/content/browser/input/PopupTouchHandleDrawable.java
 | 
| @@ -0,0 +1,265 @@
 | 
| +// Copyright 2012 The Chromium Authors. All rights reserved.
 | 
| +// Use of this source code is governed by a BSD-style license that can be
 | 
| +// found in the LICENSE file.
 | 
| +
 | 
| +package org.chromium.content.browser.input;
 | 
| +
 | 
| +import android.content.Context;
 | 
| +import android.graphics.Canvas;
 | 
| +import android.graphics.Rect;
 | 
| +import android.graphics.drawable.Drawable;
 | 
| +import android.view.MotionEvent;
 | 
| +import android.view.View;
 | 
| +import android.widget.PopupWindow;
 | 
| +
 | 
| +import org.chromium.base.CalledByNative;
 | 
| +import org.chromium.base.JNINamespace;
 | 
| +import org.chromium.content.browser.PositionObserver;
 | 
| +
 | 
| +/**
 | 
| + * View that displays a selection or insertion handle for text editing.
 | 
| + *
 | 
| + * While a HandleView is logically a child of some other view, it does not exist in that View's
 | 
| + * hierarchy.
 | 
| + *
 | 
| + */
 | 
| +@JNINamespace("content")
 | 
| +public class PopupTouchHandleDrawable extends View {
 | 
| +    private Drawable mDrawable;
 | 
| +    private final PopupTouchHandleDrawableDelegate mDelegate;
 | 
| +    private final PopupWindow mContainer;
 | 
| +
 | 
| +    // The position of the handle relative to the parent view.
 | 
| +    private int mPositionX;
 | 
| +    private int mPositionY;
 | 
| +
 | 
| +    // The position of the parent relative to the application's root view.
 | 
| +    private int mParentPositionX;
 | 
| +    private int mParentPositionY;
 | 
| +
 | 
| +    // The offset from this handles position to the "tip" of the handle.
 | 
| +    private float mHotspotX;
 | 
| +    private float mHotspotY;
 | 
| +
 | 
| +    private float mTouchToWindowOffsetX;
 | 
| +    private float mTouchToWindowOffsetY;
 | 
| +
 | 
| +    private float mAlpha;
 | 
| +
 | 
| +    private final View mParent;
 | 
| +
 | 
| +    private final Rect mTempRect = new Rect();
 | 
| +    private final int[] mTempScreenCoords = new int[2];
 | 
| +
 | 
| +    static final int LEFT = 0;
 | 
| +    static final int CENTER = 1;
 | 
| +    static final int RIGHT = 2;
 | 
| +    private int mOrientation = -1;
 | 
| +
 | 
| +    private final PositionObserver mParentPositionObserver;
 | 
| +    private final PositionObserver.Listener mParentPositionListener;
 | 
| +
 | 
| +    /**
 | 
| +     * Provides additional interaction behaviors necessary for handle
 | 
| +     * manipulation and interaction.
 | 
| +     */
 | 
| +    public interface PopupTouchHandleDrawableDelegate {
 | 
| +        /**
 | 
| +         * Should route MotionEvents to the appropriate logic layer for
 | 
| +         * performing handle manipulation.
 | 
| +         */
 | 
| +        boolean onTouchHandleEvent(MotionEvent ev);
 | 
| +    }
 | 
| +
 | 
| +    public PopupTouchHandleDrawable(PopupTouchHandleDrawableDelegate delegate, View parent,
 | 
| +            PositionObserver parentPositionObserver) {
 | 
| +        super(parent.getContext());
 | 
| +        mDelegate = delegate;
 | 
| +        mParent = parent;
 | 
| +        Context context = mParent.getContext();
 | 
| +        mContainer = new PopupWindow(context, null, android.R.attr.textSelectHandleWindowStyle);
 | 
| +        mContainer.setSplitTouchEnabled(true);
 | 
| +        mContainer.setClippingEnabled(false);
 | 
| +        mAlpha = 1.f;
 | 
| +
 | 
| +        mParentPositionListener = new PositionObserver.Listener() {
 | 
| +            @Override
 | 
| +            public void onPositionChanged(int x, int y) {
 | 
| +                updateParentPosition(x, y);
 | 
| +            }
 | 
| +        };
 | 
| +        mParentPositionObserver = parentPositionObserver;
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public boolean onTouchEvent(MotionEvent event) {
 | 
| +        // Convert from PopupWindow local coordinates to
 | 
| +        // parent view local coordinates prior to forwarding.
 | 
| +        mParent.getLocationOnScreen(mTempScreenCoords);
 | 
| +        final float offsetX = event.getRawX() - event.getX() - mTempScreenCoords[0];
 | 
| +        final float offsetY = event.getRawY() - event.getY() - mTempScreenCoords[1];
 | 
| +        final MotionEvent offsetEvent = MotionEvent.obtainNoHistory(event);
 | 
| +        offsetEvent.offsetLocation(offsetX, offsetY);
 | 
| +        final boolean handled = mDelegate.onTouchHandleEvent(offsetEvent);
 | 
| +        offsetEvent.recycle();
 | 
| +        return handled;
 | 
| +    }
 | 
| +
 | 
| +    private void setOrientation(int orientation) {
 | 
| +        assert orientation >= LEFT && orientation <= RIGHT;
 | 
| +        if (mOrientation == orientation) return;
 | 
| +
 | 
| +        final boolean hadValidOrientation = mOrientation != -1;
 | 
| +        mOrientation = orientation;
 | 
| +
 | 
| +        final int oldAdjustedPositionX = getAdjustedPositionX();
 | 
| +        final int oldAdjustedPositionY = getAdjustedPositionY();
 | 
| +
 | 
| +        Context context = mParent.getContext();
 | 
| +        switch (orientation) {
 | 
| +            case LEFT: {
 | 
| +                mDrawable = HandleViewResources.getLeftHandleDrawable(context);
 | 
| +                mHotspotX = (mDrawable.getIntrinsicWidth() * 3) / 4f;
 | 
| +                break;
 | 
| +            }
 | 
| +
 | 
| +            case RIGHT: {
 | 
| +                mDrawable = HandleViewResources.getRightHandleDrawable(context);
 | 
| +                mHotspotX = mDrawable.getIntrinsicWidth() / 4f;
 | 
| +                break;
 | 
| +            }
 | 
| +
 | 
| +            case CENTER:
 | 
| +            default: {
 | 
| +                mDrawable = HandleViewResources.getCenterHandleDrawable(context);
 | 
| +                mHotspotX = mDrawable.getIntrinsicWidth() / 2f;
 | 
| +                break;
 | 
| +            }
 | 
| +        }
 | 
| +        assert mDrawable != null;
 | 
| +        mHotspotY = 0;
 | 
| +
 | 
| +        // Force handle repositioning to accommodate the new orientation's hotspot.
 | 
| +        if (hadValidOrientation) positionAt(oldAdjustedPositionX, oldAdjustedPositionY);
 | 
| +        mDrawable.setAlpha((int) (255 * mAlpha));
 | 
| +
 | 
| +        invalidate();
 | 
| +    }
 | 
| +
 | 
| +    private void updateParentPosition(int parentPositionX, int parentPositionY) {
 | 
| +        mTouchToWindowOffsetX += parentPositionX - mParentPositionX;
 | 
| +        mTouchToWindowOffsetY += parentPositionY - mParentPositionY;
 | 
| +        mParentPositionX = parentPositionX;
 | 
| +        mParentPositionY = parentPositionY;
 | 
| +        onPositionChanged();
 | 
| +    }
 | 
| +
 | 
| +    private int getContainerPositionX() {
 | 
| +        return mParentPositionX + mPositionX;
 | 
| +    }
 | 
| +
 | 
| +    private int getContainerPositionY() {
 | 
| +        return mParentPositionY + mPositionY;
 | 
| +    }
 | 
| +
 | 
| +    private void onPositionChanged() {
 | 
| +        mContainer.update(getContainerPositionX(), getContainerPositionY(),
 | 
| +                getRight() - getLeft(), getBottom() - getTop());
 | 
| +    }
 | 
| +
 | 
| +    // x and y are in physical pixels.
 | 
| +    private void moveTo(int x, int y) {
 | 
| +        mPositionX = x;
 | 
| +        mPositionY = y;
 | 
| +        onPositionChanged();
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 | 
| +        if (mDrawable == null) return;
 | 
| +        setMeasuredDimension(mDrawable.getIntrinsicWidth(),
 | 
| +                mDrawable.getIntrinsicHeight());
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    protected void onDraw(Canvas c) {
 | 
| +        if (mDrawable == null) return;
 | 
| +        mDrawable.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop());
 | 
| +        mDrawable.draw(c);
 | 
| +    }
 | 
| +
 | 
| +    // x and y are in physical pixels.
 | 
| +    private void positionAt(int x, int y) {
 | 
| +        moveTo(x - Math.round(mHotspotX), y - Math.round(mHotspotY));
 | 
| +    }
 | 
| +
 | 
| +    // Returns the x coordinate of the position that the handle appears to be pointing to relative
 | 
| +    // to the handles "parent" view.
 | 
| +    private int getAdjustedPositionX() {
 | 
| +        return mPositionX + Math.round(mHotspotX);
 | 
| +    }
 | 
| +
 | 
| +    // Returns the y coordinate of the position that the handle appears to be pointing to relative
 | 
| +    // to the handles "parent" view.
 | 
| +    private int getAdjustedPositionY() {
 | 
| +        return mPositionY + Math.round(mHotspotY);
 | 
| +    }
 | 
| +
 | 
| +    @CalledByNative
 | 
| +    private void show() {
 | 
| +        // While hidden, the parent position may have become stale. It must be updated before
 | 
| +        // checking isPositionVisible().
 | 
| +        updateParentPosition(mParentPositionObserver.getPositionX(),
 | 
| +                mParentPositionObserver.getPositionY());
 | 
| +        mParentPositionObserver.addListener(mParentPositionListener);
 | 
| +        mContainer.setContentView(this);
 | 
| +        mContainer.showAtLocation(mParent, 0, getContainerPositionX(), getContainerPositionY());
 | 
| +    }
 | 
| +
 | 
| +    @CalledByNative
 | 
| +    private void hide() {
 | 
| +        mContainer.dismiss();
 | 
| +        mParentPositionObserver.removeListener(mParentPositionListener);
 | 
| +    }
 | 
| +
 | 
| +    @CalledByNative
 | 
| +    private void setRightOrientation() {
 | 
| +        setOrientation(RIGHT);
 | 
| +    }
 | 
| +
 | 
| +    @CalledByNative
 | 
| +    private void setLeftOrientation() {
 | 
| +        setOrientation(LEFT);
 | 
| +    }
 | 
| +
 | 
| +    @CalledByNative
 | 
| +    private void setCenterOrientation() {
 | 
| +        setOrientation(CENTER);
 | 
| +    }
 | 
| +
 | 
| +    @CalledByNative
 | 
| +    private void setOpacity(float alpha) {
 | 
| +        if (mAlpha == alpha) return;
 | 
| +        mAlpha = alpha;
 | 
| +        if (mDrawable != null) mDrawable.setAlpha((int) (255 * mAlpha));
 | 
| +    }
 | 
| +
 | 
| +    @CalledByNative
 | 
| +    private void setFocus(float x, float y) {
 | 
| +        positionAt((int) x, (int) y);
 | 
| +    }
 | 
| +
 | 
| +    @CalledByNative
 | 
| +    private void setVisible(boolean visible) {
 | 
| +        setVisibility(visible ? VISIBLE : INVISIBLE);
 | 
| +    }
 | 
| +
 | 
| +    @CalledByNative
 | 
| +    private boolean containsPoint(float x, float y) {
 | 
| +        if (mDrawable == null) return false;
 | 
| +        final int width = mDrawable.getIntrinsicWidth();
 | 
| +        final int height = mDrawable.getIntrinsicHeight();
 | 
| +        return x > mPositionX && x < (mPositionX + width)
 | 
| +                && y > mPositionY && y < (mPositionY + height);
 | 
| +    }
 | 
| +}
 | 
| 
 |