Index: content/public/android/java/src/org/chromium/content/browser/HandleView.java |
diff --git a/content/public/android/java/src/org/chromium/content/browser/HandleView.java b/content/public/android/java/src/org/chromium/content/browser/HandleView.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..bf36b56863445275fcd87442987749813d3bcaf8 |
--- /dev/null |
+++ b/content/public/android/java/src/org/chromium/content/browser/HandleView.java |
@@ -0,0 +1,370 @@ |
+// Copyright (c) 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; |
+ |
+import android.content.Context; |
+import android.content.res.TypedArray; |
+import android.graphics.Canvas; |
+import android.graphics.Rect; |
+import android.graphics.drawable.Drawable; |
+import android.os.SystemClock; |
+import android.util.TypedValue; |
+import android.view.Gravity; |
+import android.view.LayoutInflater; |
+import android.view.MotionEvent; |
+import android.view.View; |
+import android.view.ViewConfiguration; |
+import android.view.ViewGroup; |
+import android.view.ViewParent; |
+import android.view.WindowManager; |
+import android.view.View.OnClickListener; |
+import android.view.ViewGroup.LayoutParams; |
+import android.widget.PopupWindow; |
+import android.widget.TextView; |
+ |
+/** |
+ * View that displays a selection or insertion handle for text editing. |
+ */ |
+class HandleView extends View { |
+ private Drawable mDrawable; |
+ private final PopupWindow mContainer; |
+ private int mPositionX; |
+ private int mPositionY; |
+ private final CursorController mController; |
+ private boolean mIsDragging; |
+ private float mTouchToWindowOffsetX; |
+ private float mTouchToWindowOffsetY; |
+ private float mHotspotX; |
+ private float mHotspotY; |
+ private int mLineOffsetY; |
+ private int mHeight; |
+ private int mLastParentX; |
+ private int mLastParentY; |
+ private float mDownPositionX, mDownPositionY; |
+ private int mContainerPositionX, mContainerPositionY; |
+ private long mTouchTimer; |
+ private boolean mIsInsertionHandle = false; |
+ private Runnable mLongPressCallback; |
+ |
+ private View mParent; |
+ private InsertionHandleController.PastePopupMenu mPastePopupWindow; |
+ |
+ private final int mTextSelectHandleLeftRes; |
+ private final int mTextSelectHandleRightRes; |
+ private final int mTextSelectHandleRes; |
+ |
+ private Drawable mSelectHandleLeft; |
+ private Drawable mSelectHandleRight; |
+ private Drawable mSelectHandleCenter; |
+ |
+ private final int[] mTempCoords = new int[2]; |
+ private final Rect mTempRect = new Rect(); |
+ |
+ static final int LEFT = 0; |
+ static final int CENTER = 1; |
+ static final int RIGHT = 2; |
+ |
+ // Number of dips to subtract from the handle's y position to give a suitable |
+ // y coordinate for the corresponding text position. This is to compensate for the fact |
+ // that the handle position is at the base of the line of text. |
+ private static final float LINE_OFFSET_Y_DIP = 5.0f; |
+ |
+ private static final int[] TEXT_VIEW_HANDLE_ATTRS = { |
+ android.R.attr.textSelectHandleLeft, |
+ android.R.attr.textSelectHandle, |
+ android.R.attr.textSelectHandleRight, |
+ }; |
+ |
+ HandleView(CursorController controller, int pos, View parent) { |
+ super(parent.getContext()); |
+ Context context = parent.getContext(); |
+ mParent = parent; |
+ mController = controller; |
+ mContainer = new PopupWindow(context, null, android.R.attr.textSelectHandleWindowStyle); |
+ mContainer.setSplitTouchEnabled(true); |
+ mContainer.setClippingEnabled(false); |
+ |
+ TypedArray a = context.obtainStyledAttributes(TEXT_VIEW_HANDLE_ATTRS); |
+ mTextSelectHandleLeftRes = a.getResourceId(a.getIndex(LEFT), 0); |
+ mTextSelectHandleRes = a.getResourceId(a.getIndex(CENTER), 0); |
+ mTextSelectHandleRightRes = a.getResourceId(a.getIndex(RIGHT), 0); |
+ a.recycle(); |
+ |
+ setOrientation(pos); |
+ |
+ // Convert line offset dips to pixels. |
+ mLineOffsetY = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, |
+ LINE_OFFSET_Y_DIP, context.getResources().getDisplayMetrics()); |
+ } |
+ |
+ void setOrientation(int pos) { |
+ int handleWidth; |
+ switch (pos) { |
+ case LEFT: { |
+ if (mSelectHandleLeft == null) { |
+ mSelectHandleLeft = getContext().getResources().getDrawable( |
+ mTextSelectHandleLeftRes); |
+ } |
+ mDrawable = mSelectHandleLeft; |
+ handleWidth = mDrawable.getIntrinsicWidth(); |
+ mHotspotX = (handleWidth * 3) / 4; |
+ break; |
+ } |
+ |
+ case RIGHT: { |
+ if (mSelectHandleRight == null) { |
+ mSelectHandleRight = getContext().getResources().getDrawable( |
+ mTextSelectHandleRightRes); |
+ } |
+ mDrawable = mSelectHandleRight; |
+ handleWidth = mDrawable.getIntrinsicWidth(); |
+ mHotspotX = handleWidth / 4; |
+ break; |
+ } |
+ |
+ case CENTER: |
+ default: { |
+ if (mSelectHandleCenter == null) { |
+ mSelectHandleCenter = getContext().getResources().getDrawable( |
+ mTextSelectHandleRes); |
+ } |
+ mDrawable = mSelectHandleCenter; |
+ handleWidth = mDrawable.getIntrinsicWidth(); |
+ mHotspotX = handleWidth / 2; |
+ mIsInsertionHandle = true; |
+ break; |
+ } |
+ } |
+ |
+ final int handleHeight = mDrawable.getIntrinsicHeight(); |
+ mHotspotY = 0; |
+ mHeight = handleHeight; |
+ invalidate(); |
+ } |
+ |
+ @Override |
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
+ setMeasuredDimension(mDrawable.getIntrinsicWidth(), |
+ mDrawable.getIntrinsicHeight()); |
+ } |
+ |
+ void show() { |
+ if (!isPositionVisible()) { |
+ hide(); |
+ return; |
+ } |
+ mContainer.setContentView(this); |
+ final int[] coords = mTempCoords; |
+ mParent.getLocationInWindow(coords); |
+ mContainerPositionX = coords[0] + mPositionX; |
+ mContainerPositionY = coords[1] + mPositionY; |
+ mContainer.showAtLocation(mParent, 0, mContainerPositionX, mContainerPositionY); |
+ |
+ // Hide paste view when handle is moved on screen. |
+ if (mPastePopupWindow != null) { |
+ mPastePopupWindow.hide(); |
+ } |
+ } |
+ |
+ void hide() { |
+ mIsDragging = false; |
+ mContainer.dismiss(); |
+ if (mPastePopupWindow != null) { |
+ mPastePopupWindow.hide(); |
+ } |
+ } |
+ |
+ boolean isShowing() { |
+ return mContainer.isShowing(); |
+ } |
+ |
+ private boolean isPositionVisible() { |
+ // Always show a dragging handle. |
+ if (mIsDragging) { |
+ return true; |
+ } |
+ |
+ final Rect clip = mTempRect; |
+ clip.left = 0; |
+ clip.top = 0; |
+ clip.right = mParent.getWidth(); |
+ clip.bottom = mParent.getHeight(); |
+ |
+ final ViewParent parent = mParent.getParent(); |
+ if (parent == null || !parent.getChildVisibleRect(mParent, clip, null)) { |
+ return false; |
+ } |
+ |
+ final int[] coords = mTempCoords; |
+ mParent.getLocationInWindow(coords); |
+ final int posX = coords[0] + mPositionX + (int) mHotspotX; |
+ final int posY = coords[1] + mPositionY + (int) mHotspotY; |
+ |
+ return posX >= clip.left && posX <= clip.right && |
+ posY >= clip.top && posY <= clip.bottom; |
+ } |
+ |
+ void moveTo(int x, int y) { |
+ mPositionX = x; |
+ mPositionY = y; |
+ if (isPositionVisible()) { |
+ int[] coords = null; |
+ if (mContainer.isShowing()) { |
+ coords = mTempCoords; |
+ mParent.getLocationInWindow(coords); |
+ final int containerPositionX = coords[0] + mPositionX; |
+ final int containerPositionY = coords[1] + mPositionY; |
+ |
+ if (containerPositionX != mContainerPositionX || |
+ containerPositionY != mContainerPositionY) { |
+ mContainerPositionX = containerPositionX; |
+ mContainerPositionY = containerPositionY; |
+ |
+ mContainer.update(mContainerPositionX, mContainerPositionY, |
+ getRight() - getLeft(), getBottom() - getTop()); |
+ |
+ // Hide paste popup window as soon as a scroll occurs. |
+ if (mPastePopupWindow != null) { |
+ mPastePopupWindow.hide(); |
+ } |
+ } |
+ } else { |
+ show(); |
+ } |
+ |
+ if (mIsDragging) { |
+ if (coords == null) { |
+ coords = mTempCoords; |
+ mParent.getLocationInWindow(coords); |
+ } |
+ if (coords[0] != mLastParentX || coords[1] != mLastParentY) { |
+ mTouchToWindowOffsetX += coords[0] - mLastParentX; |
+ mTouchToWindowOffsetY += coords[1] - mLastParentY; |
+ mLastParentX = coords[0]; |
+ mLastParentY = coords[1]; |
+ } |
+ // Hide paste popup window as soon as the handle is dragged. |
+ if (mPastePopupWindow != null) { |
+ mPastePopupWindow.hide(); |
+ } |
+ } |
+ } else { |
+ hide(); |
+ } |
+ } |
+ |
+ @Override |
+ protected void onDraw(Canvas c) { |
+ mDrawable.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop()); |
+ mDrawable.draw(c); |
+ } |
+ |
+ @Override |
+ public boolean onTouchEvent(MotionEvent ev) { |
+ switch (ev.getActionMasked()) { |
+ case MotionEvent.ACTION_DOWN: { |
+ mDownPositionX = ev.getRawX(); |
+ mDownPositionY = ev.getRawY(); |
+ mTouchToWindowOffsetX = mDownPositionX - mPositionX; |
+ mTouchToWindowOffsetY = mDownPositionY - mPositionY; |
+ final int[] coords = mTempCoords; |
+ mParent.getLocationInWindow(coords); |
+ mLastParentX = coords[0]; |
+ mLastParentY = coords[1]; |
+ mIsDragging = true; |
+ mController.beforeStartUpdatingPosition(this); |
+ mTouchTimer = SystemClock.uptimeMillis(); |
+ break; |
+ } |
+ |
+ case MotionEvent.ACTION_MOVE: { |
+ updatePosition(ev.getRawX(), ev.getRawY()); |
+ break; |
+ } |
+ |
+ case MotionEvent.ACTION_UP: |
+ if (mIsInsertionHandle) { |
+ long delay = SystemClock.uptimeMillis() - mTouchTimer; |
+ if (delay < ViewConfiguration.getTapTimeout()) { |
+ if (mPastePopupWindow != null && mPastePopupWindow.isShowing()) { |
+ // Tapping on the handle dismisses the displayed paste view, |
+ mPastePopupWindow.hide(); |
+ } else { |
+ showPastePopupWindow(); |
+ } |
+ } |
+ } |
+ mIsDragging = false; |
+ break; |
+ |
+ case MotionEvent.ACTION_CANCEL: |
+ mIsDragging = false; |
+ break; |
+ } |
+ return true; |
+ } |
+ |
+ boolean isDragging() { |
+ return mIsDragging; |
+ } |
+ |
+ /** |
+ * @return Returns the x position of the handle |
+ */ |
+ int getPositionX() { |
+ return mPositionX; |
+ } |
+ |
+ /** |
+ * @return Returns the y position of the handle |
+ */ |
+ int getPositionY() { |
+ return mPositionY; |
+ } |
+ |
+ private void updatePosition(float rawX, float rawY) { |
+ final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX; |
+ final float newPosY = rawY - mTouchToWindowOffsetY + mHotspotY - mLineOffsetY; |
+ |
+ mController.updatePosition(this, Math.round(newPosX), Math.round(newPosY)); |
+ } |
+ |
+ void positionAt(int x, int y) { |
+ moveTo((int)(x - mHotspotX), (int)(y - mHotspotY)); |
+ } |
+ |
+ // Returns the x coordinate of the position that the handle appears to be pointing to. |
+ int getAdjustedPositionX() { |
+ return (int) (mPositionX + mHotspotX); |
+ } |
+ |
+ // Returns the y coordinate of the position that the handle appears to be pointing to. |
+ int getAdjustedPositionY() { |
+ return (int) (mPositionY + mHotspotY); |
+ } |
+ |
+ // Returns a suitable y coordinate for the text position corresponding to the handle. |
+ // As the handle points to a position on the base of the line of text, this method |
+ // returns a coordinate a small number of pixels higher (i.e. a slightly smaller number) |
+ // than getAdjustedPositionY. |
+ int getLineAdjustedPositionY() { |
+ return (int) (mPositionY + mHotspotY - mLineOffsetY); |
+ } |
+ |
+ Drawable getDrawable() { |
+ return mDrawable; |
+ } |
+ |
+ void showPastePopupWindow() { |
+ InsertionHandleController ihc = (InsertionHandleController) mController; |
+ if (mIsInsertionHandle && ihc.canPaste()) { |
+ if (mPastePopupWindow == null) { |
+ // Lazy initialization: create when actually shown only. |
+ mPastePopupWindow = ihc.new PastePopupMenu(); |
+ } |
+ mPastePopupWindow.show(); |
+ } |
+ } |
+} |