diff options
-rw-r--r-- | core/java/android/webkit/QuadF.java | 85 | ||||
-rw-r--r-- | core/java/android/webkit/WebViewClassic.java | 205 |
2 files changed, 254 insertions, 36 deletions
diff --git a/core/java/android/webkit/QuadF.java b/core/java/android/webkit/QuadF.java new file mode 100644 index 0000000..e9011e3 --- /dev/null +++ b/core/java/android/webkit/QuadF.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.webkit; + +import android.graphics.PointF; + +/** + * A quadrilateral, determined by four points, clockwise order. Typically + * p1 is "top-left" and p4 is "bottom-left" following webkit's rectangle-to- + * FloatQuad conversion. + */ +class QuadF { + public PointF p1; + public PointF p2; + public PointF p3; + public PointF p4; + + public QuadF() { + p1 = new PointF(); + p2 = new PointF(); + p3 = new PointF(); + p4 = new PointF(); + } + + public void offset(float dx, float dy) { + p1.offset(dx, dy); + p2.offset(dx, dy); + p3.offset(dx, dy); + p4.offset(dx, dy); + } + + /** + * Determines if the quadrilateral contains the given point. This does + * not work if the quadrilateral is self-intersecting or if any inner + * angle is reflex (greater than 180 degrees). + */ + public boolean containsPoint(float x, float y) { + return isPointInTriangle(x, y, p1, p2, p3) || + isPointInTriangle(x, y, p1, p3, p4); + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder("QuadF("); + s.append(p1.x).append(",").append(p1.y); + s.append(" - "); + s.append(p2.x).append(",").append(p2.y); + s.append(" - "); + s.append(p3.x).append(",").append(p3.y); + s.append(" - "); + s.append(p4.x).append(",").append(p4.y); + s.append(")"); + return s.toString(); + } + + private static boolean isPointInTriangle(float x0, float y0, + PointF r1, PointF r2, PointF r3) { + // Use the barycentric technique + float x13 = r1.x - r3.x; + float y13 = r1.y - r3.y; + float x23 = r2.x - r3.x; + float y23 = r2.y - r3.y; + float x03 = x0 - r3.x; + float y03 = y0 - r3.y; + + float determinant = (y23 * x13) - (x23 * y13); + float lambda1 = ((y23 * x03) - (x23 * y03))/determinant; + float lambda2 = ((x13 * y03) - (y13 * x03))/determinant; + float lambda3 = 1 - lambda1 - lambda2; + return lambda1 >= 0.0f && lambda2 >= 0.0f && lambda3 >= 0.0f; + } +} diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java index 5ae2fe0..7ddff8e 100644 --- a/core/java/android/webkit/WebViewClassic.java +++ b/core/java/android/webkit/WebViewClassic.java @@ -43,6 +43,7 @@ import android.graphics.Paint; import android.graphics.PaintFlagsDrawFilter; import android.graphics.Picture; import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; @@ -56,9 +57,7 @@ import android.net.http.SslCertificate; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; -import android.os.Looper; import android.os.Message; -import android.os.StrictMode; import android.os.SystemClock; import android.provider.Settings; import android.security.KeyChain; @@ -758,22 +757,21 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc this.setContentView(mContentView); } - public void show(Rect cursorRect, int windowLeft, int windowTop) { + public void show(Point cursorBottom, Point cursorTop, + int windowLeft, int windowTop) { measureContent(); int width = mContentView.getMeasuredWidth(); int height = mContentView.getMeasuredHeight(); - int y = cursorRect.top - height; + int y = cursorTop.y - height; + int x = cursorTop.x - (width / 2); if (y < windowTop) { // There's not enough room vertically, move it below the // handle. - // The selection handle is vertically offset by 1/4 of the - // line height. ensureSelectionHandles(); - y = cursorRect.bottom - (cursorRect.height() / 4) + - mSelectHandleCenter.getIntrinsicHeight(); + y = cursorBottom.y + mSelectHandleCenter.getIntrinsicHeight(); + x = cursorBottom.x - (width / 2); } - int x = cursorRect.centerX() - (width / 2); if (x < windowLeft) { x = windowLeft; } @@ -1151,12 +1149,18 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private Drawable mSelectHandleLeft; private Drawable mSelectHandleRight; private Drawable mSelectHandleCenter; - private Rect mSelectCursorBase = new Rect(); + private Point mSelectHandleLeftOffset; + private Point mSelectHandleRightOffset; + private Point mSelectHandleCenterOffset; + private Point mSelectCursorBase = new Point(); private int mSelectCursorBaseLayerId; - private Rect mSelectCursorExtent = new Rect(); + private QuadF mSelectCursorBaseTextQuad = new QuadF(); + private Point mSelectCursorExtent = new Point(); private int mSelectCursorExtentLayerId; - private Rect mSelectDraggingCursor; - private Point mSelectDraggingOffset = new Point(); + private QuadF mSelectCursorExtentTextQuad = new QuadF(); + private Point mSelectDraggingCursor; + private Point mSelectDraggingOffset; + private QuadF mSelectDraggingTextQuad; private boolean mIsCaretSelection; static final int HANDLE_ID_START = 0; static final int HANDLE_ID_END = 1; @@ -3894,9 +3898,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc if (mSelectingText) { if (mSelectCursorBaseLayerId == mCurrentScrollingLayerId) { mSelectCursorBase.offset(dx, dy); + mSelectCursorBaseTextQuad.offset(dx, dy); } if (mSelectCursorExtentLayerId == mCurrentScrollingLayerId) { mSelectCursorExtent.offset(dx, dy); + mSelectCursorExtentTextQuad.offset(dx, dy); } } if (mAutoCompletePopup != null && @@ -4774,6 +4780,13 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc com.android.internal.R.drawable.text_select_handle_left); mSelectHandleRight = mContext.getResources().getDrawable( com.android.internal.R.drawable.text_select_handle_right); + mSelectHandleCenterOffset = new Point(0, + -mSelectHandleCenter.getIntrinsicHeight()); + mSelectHandleLeftOffset = new Point(0, + -mSelectHandleLeft.getIntrinsicHeight()); + mSelectHandleRightOffset = new Point( + -mSelectHandleLeft.getIntrinsicWidth() / 2, + -mSelectHandleRight.getIntrinsicHeight()); } } @@ -4813,10 +4826,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * startX, startY, endX, endY */ private void getSelectionHandles(int[] handles) { - handles[0] = mSelectCursorBase.left; - handles[1] = mSelectCursorBase.bottom; - handles[2] = mSelectCursorExtent.left; - handles[3] = mSelectCursorExtent.bottom; + handles[0] = mSelectCursorBase.x; + handles[1] = mSelectCursorBase.y; + handles[2] = mSelectCursorExtent.x; + handles[3] = mSelectCursorExtent.y; if (!nativeIsBaseFirst(mNativeClass)) { int swap = handles[0]; handles[0] = handles[2]; @@ -5332,17 +5345,66 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc ClipboardManager cm = (ClipboardManager)(mContext .getSystemService(Context.CLIPBOARD_SERVICE)); if (cm.hasPrimaryClip()) { - Rect cursorRect = contentToViewRect(mSelectCursorBase); + Point cursorPoint = new Point(contentToViewX(mSelectCursorBase.x), + contentToViewY(mSelectCursorBase.y)); + Point cursorTop = calculateCaretTop(); + cursorTop.set(contentToViewX(cursorTop.x), + contentToViewY(cursorTop.y)); + int[] location = new int[2]; mWebView.getLocationInWindow(location); - cursorRect.offset(location[0] - getScrollX(), location[1] - getScrollY()); + int offsetX = location[0] - getScrollX(); + int offsetY = location[1] - getScrollY(); + cursorPoint.offset(offsetX, offsetY); + cursorTop.offset(offsetX, offsetY); if (mPasteWindow == null) { mPasteWindow = new PastePopupWindow(); } - mPasteWindow.show(cursorRect, location[0], location[1]); + mPasteWindow.show(cursorPoint, cursorTop, location[0], location[1]); } } + /** + * Given segment AB, this finds the point C along AB that is closest to + * point and then returns it scale along AB. The scale factor is AC/AB. + * + * @param x The x coordinate of the point near segment AB that determines + * the scale factor. + * @param y The y coordinate of the point near segment AB that determines + * the scale factor. + * @param a The first point of the line segment. + * @param b The second point of the line segment. + * @return The scale factor AC/AB, where C is the point on AB closest to + * point. + */ + private static float scaleAlongSegment(int x, int y, PointF a, PointF b) { + // The bottom line of the text box is line AB + float abX = b.x - a.x; + float abY = b.y - a.y; + float ab2 = (abX * abX) + (abY * abY); + + // The line from first point in text bounds to bottom is AP + float apX = x - a.x; + float apY = y - a.y; + float abDotAP = (apX * abX) + (apY * abY); + float scale = abDotAP / ab2; + return scale; + } + + /** + * Assuming arbitrary shape of a quadralateral forming text bounds, this + * calculates the top of a caret. + */ + private Point calculateCaretTop() { + float scale = scaleAlongSegment(mSelectCursorBase.x, mSelectCursorBase.y, + mSelectCursorBaseTextQuad.p4, mSelectCursorBaseTextQuad.p3); + int x = Math.round(scaleCoordinate(scale, + mSelectCursorBaseTextQuad.p1.x, mSelectCursorBaseTextQuad.p2.x)); + int y = Math.round(scaleCoordinate(scale, + mSelectCursorBaseTextQuad.p1.y, mSelectCursorBaseTextQuad.p2.y)); + return new Point(x, y); + } + private void hidePasteButton() { if (mPasteWindow != null) { mPasteWindow.hide(); @@ -5351,9 +5413,54 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private void syncSelectionCursors() { mSelectCursorBaseLayerId = - nativeGetHandleLayerId(mNativeClass, HANDLE_ID_BASE, mSelectCursorBase); + nativeGetHandleLayerId(mNativeClass, HANDLE_ID_BASE, + mSelectCursorBase, mSelectCursorBaseTextQuad); mSelectCursorExtentLayerId = - nativeGetHandleLayerId(mNativeClass, HANDLE_ID_EXTENT, mSelectCursorExtent); + nativeGetHandleLayerId(mNativeClass, HANDLE_ID_EXTENT, + mSelectCursorExtent, mSelectCursorExtentTextQuad); + } + + private void adjustSelectionCursors() { + boolean wasDraggingStart = (mSelectDraggingCursor == mSelectCursorBase); + int oldX = mSelectDraggingCursor.x; + int oldY = mSelectDraggingCursor.y; + int oldStartX = mSelectCursorBase.x; + int oldStartY = mSelectCursorBase.y; + int oldEndX = mSelectCursorExtent.x; + int oldEndY = mSelectCursorExtent.y; + + syncSelectionCursors(); + boolean dragChanged = oldX != mSelectDraggingCursor.x || + oldY != mSelectDraggingCursor.y; + if (dragChanged && !mIsCaretSelection) { + boolean draggingStart; + if (wasDraggingStart) { + float endStart = distanceSquared(oldEndX, oldEndY, + mSelectCursorBase); + float endEnd = distanceSquared(oldEndX, oldEndY, + mSelectCursorExtent); + draggingStart = endStart > endEnd; + } else { + float startStart = distanceSquared(oldStartX, oldStartY, + mSelectCursorBase); + float startEnd = distanceSquared(oldStartX, oldStartY, + mSelectCursorExtent); + draggingStart = startStart > startEnd; + } + mSelectDraggingCursor = (draggingStart + ? mSelectCursorBase : mSelectCursorExtent); + mSelectDraggingTextQuad = (draggingStart + ? mSelectCursorBaseTextQuad : mSelectCursorExtentTextQuad); + mSelectDraggingOffset = (draggingStart + ? mSelectHandleLeftOffset : mSelectHandleRightOffset); + } + mSelectDraggingCursor.set(oldX, oldY); + } + + private float distanceSquared(int x, int y, Point p) { + float dx = p.x - x; + float dy = p.y - y; + return (dx * dx) + (dy * dy); } private boolean setupWebkitSelect() { @@ -5370,14 +5477,14 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private void updateWebkitSelection() { int[] handles = null; if (mIsCaretSelection) { - mSelectCursorExtent.set(mSelectCursorBase); + mSelectCursorExtent.set(mSelectCursorBase.x, mSelectCursorBase.y); } if (mSelectingText) { handles = new int[4]; - handles[0] = mSelectCursorBase.centerX(); - handles[1] = mSelectCursorBase.centerY(); - handles[2] = mSelectCursorExtent.centerX(); - handles[3] = mSelectCursorExtent.centerY(); + handles[0] = mSelectCursorBase.x; + handles[1] = mSelectCursorBase.y; + handles[2] = mSelectCursorExtent.x; + handles[3] = mSelectCursorExtent.y; } else { nativeSetTextSelection(mNativeClass, 0); } @@ -5968,12 +6075,15 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } mSelectionStarted = false; if (mSelectingText) { + ensureSelectionHandles(); int shiftedY = y - getTitleHeight() + getScrollY(); int shiftedX = x + getScrollX(); if (mSelectHandleCenter != null && mSelectHandleCenter.getBounds() .contains(shiftedX, shiftedY)) { mSelectionStarted = true; mSelectDraggingCursor = mSelectCursorBase; + mSelectDraggingOffset = mSelectHandleCenterOffset; + mSelectDraggingTextQuad = mSelectCursorBaseTextQuad; mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE); hidePasteButton(); } else if (mSelectHandleLeft != null @@ -5981,19 +6091,18 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc .contains(shiftedX, shiftedY)) { mSelectionStarted = true; mSelectDraggingCursor = mSelectCursorBase; + mSelectDraggingOffset = mSelectHandleLeftOffset; + mSelectDraggingTextQuad = mSelectCursorBaseTextQuad; } else if (mSelectHandleRight != null && mSelectHandleRight.getBounds() .contains(shiftedX, shiftedY)) { mSelectionStarted = true; mSelectDraggingCursor = mSelectCursorExtent; + mSelectDraggingOffset = mSelectHandleRightOffset; + mSelectDraggingTextQuad = mSelectCursorExtentTextQuad; } else if (mIsCaretSelection) { selectionDone(); } - if (mSelectDraggingCursor != null) { - mSelectDraggingOffset.set( - mSelectDraggingCursor.left - contentX, - mSelectDraggingCursor.top - contentY); - } if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "select=" + contentX + "," + contentY); } @@ -6073,9 +6182,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc parent.requestDisallowInterceptTouchEvent(true); } if (deltaX != 0 || deltaY != 0) { - mSelectDraggingCursor.offsetTo( - contentX + mSelectDraggingOffset.x, - contentY + mSelectDraggingOffset.y); + snapDraggingCursor(contentX, contentY); updateWebkitSelection(); mLastTouchX = x; mLastTouchY = y; @@ -6684,6 +6791,30 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mTouchMode = TOUCH_DONE_MODE; } + private void snapDraggingCursor(int x, int y) { + x += viewToContentDimension(mSelectDraggingOffset.x); + y += viewToContentDimension(mSelectDraggingOffset.y); + if (mSelectDraggingTextQuad.containsPoint(x, y)) { + float scale = scaleAlongSegment(x, y, + mSelectDraggingTextQuad.p4, mSelectDraggingTextQuad.p3); + // clamp scale to ensure point is on the bottom segment + scale = Math.max(0.0f, scale); + scale = Math.min(scale, 1.0f); + float newX = scaleCoordinate(scale, + mSelectDraggingTextQuad.p4.x, mSelectDraggingTextQuad.p3.x); + float newY = scaleCoordinate(scale, + mSelectDraggingTextQuad.p4.y, mSelectDraggingTextQuad.p3.y); + mSelectDraggingCursor.set(Math.round(newX), Math.round(newY)); + } else { + mSelectDraggingCursor.set(x, y); + } + } + + private static float scaleCoordinate(float scale, float coord1, float coord2) { + float diff = coord2 - coord1; + return coord1 + (scale * diff); + } + @Override public boolean onGenericMotionEvent(MotionEvent event) { if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { @@ -8735,6 +8866,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc setupWebkitSelect(); } else if (!mSelectionStarted) { syncSelectionCursors(); + } else { + adjustSelectionCursors(); } if (mIsCaretSelection) { resetCaretTimer(); @@ -9398,7 +9531,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private static native void nativeSetPauseDrawing(int instance, boolean pause); private static native void nativeSetTextSelection(int instance, int selection); private static native int nativeGetHandleLayerId(int instance, int handle, - Rect cursorLocation); + Point cursorLocation, QuadF textQuad); private static native boolean nativeIsBaseFirst(int instance); private static native void nativeMapLayerRect(int instance, int layerId, Rect rect); |