OSDN Git Service

Text selection anchors changed to use windows
authorAdam Powell <adamp@google.com>
Mon, 20 Sep 2010 18:23:56 +0000 (11:23 -0700)
committerAdam Powell <adamp@google.com>
Wed, 22 Sep 2010 00:11:41 +0000 (17:11 -0700)
Change-Id: I14f138039f5e3175a8c07f21985715b8447708e5

12 files changed:
api/current.xml
core/java/android/text/method/ArrowKeyMovementMethod.java
core/java/android/view/View.java
core/java/android/view/ViewGroup.java
core/java/android/view/ViewParent.java
core/java/android/view/ViewRoot.java
core/java/android/widget/PopupWindow.java
core/java/android/widget/TextView.java
core/res/res/values/attrs.xml
core/res/res/values/public.xml
core/res/res/values/styles.xml
core/res/res/values/themes.xml

index f2114e4..975ec7d 100644 (file)
  visibility="public"
 >
 </field>
-<field name="kraken_resource_pad57"
- type="int"
- transient="false"
- volatile="false"
- value="16843464"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 <field name="kraken_resource_pad6"
  type="int"
  transient="false"
  visibility="public"
 >
 </field>
+<field name="textSelectHandleWindowStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843464"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="textSize"
  type="int"
  transient="false"
 <parameter name="event" type="android.view.MotionEvent">
 </parameter>
 </method>
-<method name="setCursorController"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="cursorController" type="android.widget.TextView.CursorController">
-</parameter>
-</method>
 </class>
 <class name="BaseKeyListener"
  extends="android.text.method.MetaKeyKeyListener"
 >
 </method>
 </class>
-<interface name="TextView.CursorController"
- abstract="true"
- static="true"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<method name="draw"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="canvas" type="android.graphics.Canvas">
-</parameter>
-</method>
-<method name="getOffsetX"
- return="float"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="getOffsetY"
- return="float"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="hide"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="isShowing"
- return="boolean"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="onTouchEvent"
- return="boolean"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="event" type="android.view.MotionEvent">
-</parameter>
-</method>
-<method name="show"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="updatePosition"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="x" type="int">
-</parameter>
-<parameter name="y" type="int">
-</parameter>
-</method>
-<field name="FADE_OUT_DURATION"
- type="int"
- transient="false"
- volatile="false"
- value="400"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-</interface>
 <interface name="TextView.OnEditorActionListener"
  abstract="true"
  static="true"
index 0408673..733b535 100644 (file)
@@ -314,6 +314,8 @@ public class ArrowKeyMovementMethod implements MovementMethod {
      * {@link MotionEvent#ACTION_CANCEL} event), the controller is reset to null.
      *
      * @param cursorController A cursor controller implementation
+     *
+     * @hide
      */
     public void setCursorController(CursorController cursorController) {
         mCursorController = cursorController;
index 6e395c8..b794a6a 100644 (file)
@@ -1551,12 +1551,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
     private static final int AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000;
 
     /**
-     * Indicates that this view has a visible/touchable overlay.
-     * @hide
-     */
-    static final int HAS_OVERLAY = 0x10000000;
-
-    /**
      * Always allow a user to overscroll this view, provided it is a
      * view that can scroll.
      *
@@ -2843,57 +2837,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
         resetPressedState();
     }
 
-    /**
-     * Enable or disable drawing overlays after a full drawing pass. This enables a view to
-     * draw on a topmost overlay layer after normal drawing completes and get right of first
-     * refusal for touch events in the window.
-     * 
-     * <em>Warning:</em> Views that use this feature should take care to disable/enable overlay
-     * appropriately when they are attached/detached from their window. All overlays should be
-     * disabled when detached.
-     * 
-     * @param enabled true if overlay drawing should be enabled for this view, false otherwise
-     * 
-     * @see #onDrawOverlay(Canvas)
-     * 
-     * @hide
-     */
-    protected void setOverlayEnabled(boolean enabled) {
-        final boolean oldValue = (mPrivateFlags & HAS_OVERLAY) == HAS_OVERLAY;
-        mPrivateFlags = (mPrivateFlags & ~HAS_OVERLAY) | (enabled ? HAS_OVERLAY : 0);
-        if (enabled != oldValue) {
-            final ViewParent parent = getParent();
-            if (parent != null) {
-                try {
-                    parent.childOverlayStateChanged(this);
-                } catch (AbstractMethodError e) {
-                    Log.e(VIEW_LOG_TAG, "Could not propagate hasOverlay state", e);
-                }
-            }
-        }
-    }
-
-    /**
-     * @return true if this View has an overlay enabled.
-     * 
-     * @see #setOverlayEnabled(boolean)
-     * @see #onDrawOverlay(Canvas)
-     * 
-     * @hide
-     */
-    public boolean isOverlayEnabled() {
-        return (mPrivateFlags & HAS_OVERLAY) == HAS_OVERLAY;
-    }
-
-    /**
-     * Override this method to draw on an overlay layer above all other views in the window
-     * after the standard drawing pass is complete. This allows a view to draw outside its
-     * normal boundaries.
-     * @hide
-     */
-    public void onDrawOverlay(Canvas canvas) {
-    }
-
     private void resetPressedState() {
         if ((mViewFlags & ENABLED_MASK) == DISABLED) {
             return;
index deba70c..b9864ba 100644 (file)
@@ -228,11 +228,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
     protected static final int FLAG_DISALLOW_INTERCEPT = 0x80000;
 
     /**
-     * When set, at least one child of this ViewGroup will return true from hasOverlay.
-     */
-    private static final int FLAG_CHILD_HAS_OVERLAY = 0x100000;
-
-    /**
      * Indicates which types of drawing caches are to be kept in memory.
      * This field should be made private, so it is hidden from the SDK.
      * {@hide}
@@ -860,33 +855,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
                 final View[] children = mChildren;
                 final int count = mChildrenCount;
 
-                // Check for children with overlays first. They don't rely on hit rects to determine
-                // if they can accept a new touch event.
-                if ((mGroupFlags & FLAG_CHILD_HAS_OVERLAY) == FLAG_CHILD_HAS_OVERLAY) {
-                    for (int i = count - 1; i >= 0; i--) {
-                        final View child = children[i];
-                        // Don't let children respond to events as an overlay during an animation.
-                        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
-                                && child.getAnimation() == null
-                                && child.isOverlayEnabled()) {
-                            // offset the event to the view's coordinate system
-                            final float xc = scrolledXFloat - child.mLeft;
-                            final float yc = scrolledYFloat - child.mTop;
-                            ev.setLocation(xc, yc);
-                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
-                            if (child.dispatchTouchEvent(ev))  {
-                                // Event handled, we have a target now.
-                                mMotionTarget = child;
-                                return true;
-                            }
-                            // The event didn't get handled, try the next view.
-                            // Don't reset the event's location, it's not
-                            // necessary here.
-                        }
-                    }
-                }
-
-                // Now check views normally.
                 for (int i = count - 1; i >= 0; i--) {
                     final View child = children[i];
                     if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
@@ -2345,8 +2313,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
         if (clearChildFocus != null) {
             clearChildFocus(clearChildFocus);
         }
-
-        mGroupFlags &= ~FLAG_CHILD_HAS_OVERLAY;
     }
 
     /**
@@ -2569,8 +2535,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
                 final int left = mLeft;
                 final int top = mTop;
 
-                if ((mGroupFlags & FLAG_CHILD_HAS_OVERLAY) == FLAG_CHILD_HAS_OVERLAY ||
-                        dirty.intersect(0, 0, mRight - left, mBottom - top) ||
+                if (dirty.intersect(0, 0, mRight - left, mBottom - top) ||
                         (mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION) {
                     mPrivateFlags &= ~DRAWING_CACHE_VALID;
 
@@ -3489,69 +3454,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
     }
 
     /**
-     * Called when a child's overlay state changes between enabled/disabled.
-     * @param child Child view whose state has changed or null
-     * @hide
-     */
-    public void childOverlayStateChanged(View child) {
-        boolean childHasOverlay = false;
-        if (child != null) {
-            childHasOverlay = child.isOverlayEnabled();
-        } else {
-            final int childCount = getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                if (childHasOverlay |= getChildAt(i).isOverlayEnabled()) {
-                    break;
-                }
-            }
-        }
-        
-        final boolean hasChildWithOverlay = childHasOverlay ||
-                (mGroupFlags & FLAG_CHILD_HAS_OVERLAY) == FLAG_CHILD_HAS_OVERLAY;
-
-        final boolean oldValue = isOverlayEnabled();
-        mGroupFlags = (mGroupFlags & ~FLAG_CHILD_HAS_OVERLAY) |
-                (hasChildWithOverlay ? FLAG_CHILD_HAS_OVERLAY : 0);
-        if (isOverlayEnabled() != oldValue) {
-            final ViewParent parent = getParent();
-            if (parent != null) {
-                try {
-                    parent.childOverlayStateChanged(this);
-                } catch (AbstractMethodError e) {
-                    Log.e("ViewGroup", "Could not propagate hasOverlay state", e);
-                }
-            }
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public boolean isOverlayEnabled() {
-        return super.isOverlayEnabled() ||
-                ((mGroupFlags & FLAG_CHILD_HAS_OVERLAY) == FLAG_CHILD_HAS_OVERLAY);
-    }
-
-    /**
-     * @hide
-     */
-    @Override
-    public void onDrawOverlay(Canvas canvas) {
-        if ((mGroupFlags & FLAG_CHILD_HAS_OVERLAY) == FLAG_CHILD_HAS_OVERLAY) {
-            final int childCount = getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                final View child = getChildAt(i);
-                if (child.isOverlayEnabled()) {
-                    canvas.translate(child.mLeft - child.mScrollX, child.mTop - child.mScrollY);
-                    child.onDrawOverlay(canvas);
-                    canvas.translate(-(child.mLeft - child.mScrollX),
-                            -(child.mTop - child.mScrollY));
-                }
-            }
-        }
-    }
-
-    /**
      * LayoutParams are used by views to tell their parents how they want to be
      * laid out. See
      * {@link android.R.styleable#ViewGroup_Layout ViewGroup Layout Attributes}
index a0d3618..b456c5d 100644 (file)
@@ -208,11 +208,4 @@ public interface ViewParent {
      */
     public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
             boolean immediate);
-
-    /**
-     * Called when a child view's overlay state changes between enabled/disabled.
-     * @param child Child view whose state changed or null.
-     * @hide
-     */
-    public void childOverlayStateChanged(View child);
 }
index acec476..59980ef 100644 (file)
@@ -221,8 +221,6 @@ public final class ViewRoot extends Handler implements ViewParent,
     AudioManager mAudioManager;
 
     private final int mDensity;
-    
-    private boolean mHasOverlay;
 
     public static IWindowSession getWindowSession(Looper mainLooper) {
         synchronized (mStaticInit) {
@@ -1520,9 +1518,6 @@ public final class ViewRoot extends Handler implements ViewParent,
                         canvas.setScreenDensity(scalingRequired
                                 ? DisplayMetrics.DENSITY_DEVICE : 0);
                         mView.draw(canvas);
-                        if (mHasOverlay) {
-                            mView.onDrawOverlay(canvas);
-                        }
                     } finally {
                         mAttachInfo.mIgnoreDirtyState = false;
                         canvas.restoreToCount(saveCount);
@@ -2919,19 +2914,6 @@ public final class ViewRoot extends Handler implements ViewParent,
         return scrollToRectOrFocus(rectangle, immediate);
     }
 
-    /**
-     * @hide
-     */
-    public void childOverlayStateChanged(View child) {
-        final boolean oldState = mHasOverlay;
-        mHasOverlay = child.isOverlayEnabled();
-        // Invalidate the whole thing when we change overlay states just in case
-        // something left chunks of data drawn someplace it shouldn't have.
-        if (mHasOverlay != oldState) {
-            child.invalidate();
-        }
-    }
-
     class TakenSurfaceHolder extends BaseSurfaceHolder {
         @Override
         public boolean onAllowLockCanvas() {
index 0378328..a10d647 100644 (file)
@@ -165,6 +165,7 @@ public class PopupWindow {
                 attrs, com.android.internal.R.styleable.PopupWindow, defStyle, 0);
 
         mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
+        mAnimationStyle = a.getResourceId(R.styleable.PopupWindow_windowAnimationStyle, -1);
 
         // If this is a StateListDrawable, try to find and store the drawable to be
         // used when the drop-down is placed above its anchor view, and the one to be
index a09b0c8..034c617 100644 (file)
@@ -194,6 +194,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
     
     private static int PRIORITY = 100;
 
+    private final int[] mTempCoords = new int[2];
+
     private ColorStateList mTextColor;
     private int mCurTextColor;
     private ColorStateList mHintTextColor;
@@ -3749,8 +3751,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
             showError();
             mShowErrorAfterAttach = false;
         }
-        
-        updateOverlay();
     }
 
     @Override
@@ -3768,8 +3768,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
         if (mError != null) {
             hideError();
         }
-        
-        setOverlayEnabled(false);
     }
 
     @Override
@@ -4122,19 +4120,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
         canvas.restore();
     }
 
-    /**
-     * @hide
-     */
-    @Override
-    public void onDrawOverlay(Canvas canvas) {
-        if (mInsertionPointCursorController != null) {
-            mInsertionPointCursorController.draw(canvas);
-        }
-        if (mSelectionModifierCursorController != null) {
-            mSelectionModifierCursorController.draw(canvas);
-        }
-    }
-
     @Override
     public void getFocusedRect(Rect r) {
         if (mLayout == null) {
@@ -6705,31 +6690,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
     public boolean onTouchEvent(MotionEvent event) {
         final int action = event.getActionMasked();
         if (action == MotionEvent.ACTION_DOWN) {
-            // Check to see if we're testing for our anchor overlay.
-            boolean handled = false;
-            final float x = event.getX();
-            final float y = event.getY();
-            if (x < 0 || x >= mRight - mLeft || y < 0 || y >= mBottom - mTop) {
-                if (mInsertionPointCursorController != null) {
-                    handled |= mInsertionPointCursorController.onTouchEvent(event);
-                }
-                if (mSelectionModifierCursorController != null) {
-                    handled |= mSelectionModifierCursorController.onTouchEvent(event);
-                }
-
-                if (!handled) {
-                    return false;
-                }
+            if (mInsertionPointCursorController != null) {
+                mInsertionPointCursorController.onTouchEvent(event);
+            }
+            if (mSelectionModifierCursorController != null) {
+                mSelectionModifierCursorController.onTouchEvent(event);
             }
 
             // Reset this state; it will be re-set if super.onTouchEvent
             // causes focus to move to the view.
             mTouchFocusSelected = false;
             mScrolled = false;
-
-            if (handled) {
-                return true;
-            }
         }
 
         final boolean superResult = super.onTouchEvent(event);
@@ -6745,7 +6716,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
         }
 
         if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) {
-
             if (mInsertionPointCursorController != null) {
                 mInsertionPointCursorController.onTouchEvent(event);
             }
@@ -6758,12 +6728,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
             // Save previous selection, in case this event is used to show the IME.
             int oldSelStart = getSelectionStart();
             int oldSelEnd = getSelectionEnd();
+
+            final int oldScrollX = mScrollX;
+            final int oldScrollY = mScrollY;
             
             if (mMovement != null) {
                 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
             }
 
             if (isTextEditable()) {
+                if (mScrollX != oldScrollX || mScrollY != oldScrollY) {
+                    // Hide insertion anchor while scrolling. Leave selection.
+                    hideInsertionPointCursorController();
+                    if (mSelectionModifierCursorController != null &&
+                            mSelectionModifierCursorController.isShowing()) {
+                        mSelectionModifierCursorController.updatePosition();
+                    }
+                }
                 if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
                     InputMethodManager imm = (InputMethodManager)
                           getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -7618,22 +7599,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
         }
     }
 
-    private void updateOverlay() {
-        boolean enableOverlay = false;
-        if (mSelectionModifierCursorController != null) {
-            enableOverlay |= mSelectionModifierCursorController.isShowing();
-        }
-        if (mInsertionPointCursorController != null) {
-            enableOverlay |= mInsertionPointCursorController.isShowing();
-        }
-        setOverlayEnabled(enableOverlay);
-    }
-
     /**
      * A CursorController instance can be used to control a cursor in the text.
      *
      * It can be passed to an {@link ArrowKeyMovementMethod} which can intercepts events
      * and send them to this object instead of the cursor.
+     *
+     * @hide
      */
     public interface CursorController {
         /* Cursor fade-out animation duration, in milliseconds. */
@@ -7661,6 +7633,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
          */
         public void updatePosition(int x, int y);
 
+        public void updatePosition();
+
         /**
          * The controller and the cursor's positions can be link by a fixed offset,
          * computed when the controller is touched, and then maintained as it moves
@@ -7679,154 +7653,192 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
          * @param event The touch event
          */
         public boolean onTouchEvent(MotionEvent event);
-
-        /**
-         * Draws a visual representation of the controller on the canvas.
-         *
-         * Called at the end of {@link #draw(Canvas)}, in the content coordinates system.
-         * @param canvas The Canvas used by this TextView.
-         */
-        public void draw(Canvas canvas);
     }
 
-    private class Handle {
-        Drawable mDrawable;
-        // Vertical extension of the touch region
-        int mTopExtension, mBottomExtension;
-        // Position of the virtual finger position on screen
-        int mHotSpotVerticalPosition;
+    private class HandleView extends View {
+        private boolean mPositionOnTop = false;
+        private Drawable mDrawable;
+        private PopupWindow mContainer;
+        private int mPositionX;
+        private int mPositionY;
+        private CursorController mController;
+        private boolean mIsDragging;
 
-        Handle(Drawable drawable) {
-            mDrawable = drawable;
+        public HandleView(CursorController controller, Drawable handle) {
+            super(TextView.this.mContext);
+            mController = controller;
+            mDrawable = handle;
+            mContainer = new PopupWindow(TextView.this.mContext, null,
+                    com.android.internal.R.attr.textSelectHandleWindowStyle);
         }
 
-        void positionAtCursor(final int offset, boolean bottom) {
-            final int drawableWidth = mDrawable.getIntrinsicWidth();
-            final int drawableHeight = mDrawable.getIntrinsicHeight();
-            final int line = mLayout.getLineForOffset(offset);
-            final int lineTop = mLayout.getLineTop(line);
-            final int lineBottom = mLayout.getLineBottom(line);
+        @Override
+        public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            setMeasuredDimension(mDrawable.getIntrinsicWidth(),
+                    mDrawable.getIntrinsicHeight());
+        }
 
-            mHotSpotVerticalPosition = lineTop;
+        public void show() {
+            if (!isPositionInBounds()) {
+                hide();
+                return;
+            }
+            mContainer.setContentView(this);
+            final int[] coords = mTempCoords;
+            TextView.this.getLocationOnScreen(coords);
+            coords[0] += mPositionX;
+            coords[1] += mPositionY;
+            mContainer.showAtLocation(TextView.this, 0, coords[0], coords[1]);
+        }
 
-            final Rect bounds = sCursorControllerTempRect;
-            bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - drawableWidth / 2.0)
-                + mScrollX;
-            bounds.top = (bottom ? lineBottom : lineTop) + mScrollY;
+        public void hide() {
+            mIsDragging = false;
+            mContainer.dismiss();
+        }
 
-            mTopExtension = bottom ? 0 : drawableHeight / 2;
-            mBottomExtension = 0; //drawableHeight / 4;
+        public boolean isShowing() {
+            return mContainer.isShowing();
+        }
+
+        private boolean isPositionInBounds() {
+            final int extendedPaddingTop = getExtendedPaddingTop();
+            final int extendedPaddingBottom = getExtendedPaddingBottom();
+            final int compoundPaddingLeft = getCompoundPaddingLeft();
+            final int compoundPaddingRight = getCompoundPaddingRight();
+
+            final TextView hostView = TextView.this;
+            final int right = hostView.mRight;
+            final int left = hostView.mLeft;
+            final int bottom = hostView.mBottom;
+            final int top = hostView.mTop;
+
+            final int clipLeft = left + compoundPaddingLeft;
+            final int clipTop = top + extendedPaddingTop;
+            final int clipRight = right - compoundPaddingRight;
+            final int clipBottom = bottom - extendedPaddingBottom;
+
+            final int handleWidth = mDrawable.getIntrinsicWidth();
+            return mPositionX >= clipLeft - handleWidth * 0.75f &&
+                    mPositionX <= clipRight + handleWidth * 0.25f &&
+                    mPositionY >= clipTop && mPositionY <= clipBottom;
+        }
+
+        private void moveTo(int x, int y) {
+            mPositionX = x - TextView.this.mScrollX;
+            mPositionY = y - TextView.this.mScrollY;
+            if (isPositionInBounds()) {
+                if (mContainer.isShowing()){
+                    final int[] coords = mTempCoords;
+                    TextView.this.getLocationOnScreen(coords);
+                    coords[0] += mPositionX;
+                    coords[1] += mPositionY;
+                    mContainer.update(coords[0], coords[1], mRight - mLeft, mBottom - mTop);
+                } else {
+                    show();
+                }
+            } else {
+                hide();
+            }
+        }
 
-            // Extend touch region up when editing the last line of text (or a single line) so that
-            // it is easier to grab.
-            if (line == mLayout.getLineCount() - 1) {
-                mTopExtension = (lineBottom - lineTop) - drawableHeight / 2;
+        @Override
+        public void onDraw(Canvas c) {
+            mDrawable.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
+            if (mPositionOnTop) {
+                c.save();
+                c.rotate(180, (mRight - mLeft) / 2, (mBottom - mTop) / 2);
+                mDrawable.draw(c);
+                c.restore();
+            } else {
+                mDrawable.draw(c);
             }
+        }
 
-            bounds.right = bounds.left + drawableWidth;
-            bounds.bottom = bounds.top + drawableHeight;
+        @Override
+        public boolean onTouchEvent(MotionEvent ev) {
+            switch (ev.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                mIsDragging = true;
+                break;
 
-            convertFromViewportToContentCoordinates(bounds);
-            invalidate();
-            mDrawable.setBounds(bounds);
-            invalidate();
-        }
+            case MotionEvent.ACTION_MOVE:
+                final float rawX = ev.getRawX();
+                final float rawY = ev.getRawY();
+                final int[] coords = mTempCoords;
+                TextView.this.getLocationOnScreen(coords);
+                final int x = (int) (rawX - coords[0] + 0.5f);
+                final int y = (int) (rawY - coords[1] + 0.5f);
+                mController.updatePosition(x, y);
+                break;
 
-        boolean hasFingerOn(float x, float y) {
-            // Simulate a 'fat finger' to ease grabbing of the controller.
-            // Expands according to controller image size instead of using dip distance.
-            // Assumes controller imager has a sensible size, proportionnal to screen density.
-            final int drawableWidth = mDrawable.getIntrinsicWidth();
-            final Rect fingerRect = sCursorControllerTempRect;
-            fingerRect.set((int) (x - drawableWidth / 2.0),
-                           (int) (y - mBottomExtension),
-                           (int) (x + drawableWidth / 2.0),
-                           (int) (y + mTopExtension));
-            fingerRect.offset(mScrollX, mScrollY);
-            return Rect.intersects(mDrawable.getBounds(), fingerRect);
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                mIsDragging = false;
+            }
+            return true;
         }
 
-        void invalidate() {
-            final Rect bounds = mDrawable.getBounds();
-            TextView.this.invalidate(bounds.left, bounds.top,
-                    bounds.right, bounds.bottom);
+        public boolean isDragging() {
+            return mIsDragging;
         }
 
-        void postInvalidate() {
-            final Rect bounds = mDrawable.getBounds();
-            TextView.this.postInvalidate(bounds.left, bounds.top,
-                    bounds.right, bounds.bottom);
-        }
+        void positionAtCursor(final int offset, boolean bottom) {
+            final int width = mDrawable.getIntrinsicWidth();
+            final int height = mDrawable.getIntrinsicHeight();
+            final int line = mLayout.getLineForOffset(offset);
+            final int lineTop = mLayout.getLineTop(line);
+            final int lineBottom = mLayout.getLineBottom(line);
+
+            final Rect bounds = sCursorControllerTempRect;
+            bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - width / 2.0)
+                + TextView.this.mScrollX;
+            bounds.top = (bottom ? lineBottom : lineTop) + TextView.this.mScrollY;
 
-        void postInvalidateDelayed(long delay) {
-            final Rect bounds = mDrawable.getBounds();
-            TextView.this.postInvalidateDelayed(delay, bounds.left, bounds.top,
-                                                       bounds.right, bounds.bottom);
+            bounds.right = bounds.left + width;
+            bounds.bottom = bounds.top + height;
+
+            convertFromViewportToContentCoordinates(bounds);
+            moveTo(bounds.left, bounds.top);
         }
     }
 
     class InsertionPointCursorController implements CursorController {
-        private static final int DELAY_BEFORE_FADE_OUT = 2100;
+        private static final int DELAY_BEFORE_FADE_OUT = 4100;
 
-        // Whether or not the cursor control is currently visible
-        private boolean mIsVisible = false;
-        // Starting time of the fade timer
-        private long mFadeOutTimerStart;
         // The cursor controller image
-        private final Handle mHandle;
-        // Used to detect a tap (vs drag) on the controller
-        private long mOnDownTimerStart;
+        private final HandleView mHandle;
         // Offset between finger hot point on cursor controller and actual cursor
         private float mOffsetX, mOffsetY;
 
+        private final Runnable mHider = new Runnable() {
+            public void run() {
+                hide();
+            }
+        };
+
         InsertionPointCursorController() {
             Resources res = mContext.getResources();
-            mHandle = new Handle(res.getDrawable(mTextSelectHandleRes));
+            mHandle = new HandleView(this, res.getDrawable(mTextSelectHandleRes));
         }
 
         public void show() {
-            updateDrawablePosition();
-            // Has to be done after updateDrawablePosition, so that previous position invalidate
-            // in only done if necessary.
-            mIsVisible = true;
-            updateOverlay();
+            updatePosition();
+            mHandle.show();
+            hideDelayed(DELAY_BEFORE_FADE_OUT);
         }
 
         public void hide() {
-            if (mIsVisible) {
-                long time = System.currentTimeMillis();
-                // Start fading out, only if not already in progress
-                if (time - mFadeOutTimerStart < DELAY_BEFORE_FADE_OUT) {
-                    mFadeOutTimerStart = time - DELAY_BEFORE_FADE_OUT;
-                    mHandle.postInvalidate();
-                }
-            }
+            mHandle.hide();
+            TextView.this.removeCallbacks(mHider);
         }
 
-        public boolean isShowing() {
-            return mIsVisible;
+        private void hideDelayed(int msec) {
+            TextView.this.removeCallbacks(mHider);
+            TextView.this.postDelayed(mHider, msec);
         }
 
-        public void draw(Canvas canvas) {
-            if (mIsVisible) {
-                int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart);
-                if (time <= DELAY_BEFORE_FADE_OUT) {
-                    mHandle.postInvalidateDelayed(DELAY_BEFORE_FADE_OUT - time);
-                } else {
-                    time -= DELAY_BEFORE_FADE_OUT;
-                    if (time <= FADE_OUT_DURATION) {
-                        final int alpha = (int)
-                                         ((255.0 * (FADE_OUT_DURATION - time)) / FADE_OUT_DURATION);
-                        mHandle.mDrawable.setAlpha(alpha);
-                        mHandle.postInvalidateDelayed(30);
-                    } else {
-                        mHandle.mDrawable.setAlpha(0);
-                        mIsVisible = false;
-                        updateOverlay();
-                    }
-                }
-                mHandle.mDrawable.draw(canvas);
-            }
+        public boolean isShowing() {
+            return mHandle.isShowing();
         }
 
         public void updatePosition(int x, int y) {
@@ -7835,81 +7847,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
 
             if (offset != previousOffset) {
                 Selection.setSelection((Spannable) mText, offset);
-                updateDrawablePosition();
+                updatePosition();
             }
+            hideDelayed(DELAY_BEFORE_FADE_OUT);
         }
 
-        private void updateDrawablePosition() {
-            if (mIsVisible) {
-                // Clear previous cursor controller before bounds are updated
-                mHandle.postInvalidate();
-            }
-
+        public void updatePosition() {
             final int offset = getSelectionStart();
 
             if (offset < 0) {
                 // Should never happen, safety check.
                 Log.w(LOG_TAG, "Update cursor controller position called with no cursor");
-                mIsVisible = false;
-                updateOverlay();
+                hide();
                 return;
             }
 
             mHandle.positionAtCursor(offset, true);
-
-            mFadeOutTimerStart = System.currentTimeMillis();
-            mHandle.mDrawable.setAlpha(255);
-        }
-
-        public boolean onTouchEvent(MotionEvent event) {
-            if (isFocused() && isTextEditable() && mIsVisible) {
-                switch (event.getActionMasked()) {
-                    case MotionEvent.ACTION_DOWN : {
-                        final float x = event.getX();
-                        final float y = event.getY();
-
-                        if (mHandle.hasFingerOn(x, y)) {
-                            show();
-
-                            if (mMovement instanceof ArrowKeyMovementMethod) {
-                                ((ArrowKeyMovementMethod)mMovement).setCursorController(this);
-                            }
-
-                            if (mParent != null) {
-                                // Prevent possible scrollView parent from scrolling, so that
-                                // we can use auto-scrolling.
-                                mParent.requestDisallowInterceptTouchEvent(true);
-                            }
-
-                            final Rect bounds = mHandle.mDrawable.getBounds();
-                            mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
-                            mOffsetY = mHandle.mHotSpotVerticalPosition - y;
-
-                            mOffsetX += viewportToContentHorizontalOffset();
-                            mOffsetY += viewportToContentVerticalOffset();
-
-                            mOnDownTimerStart = event.getEventTime();
-                            return true;
-                        }
-                        return false;
-                    }
-
-                    case MotionEvent.ACTION_UP : {
-                        int time = (int) (event.getEventTime() - mOnDownTimerStart);
-
-                        if (time <= ViewConfiguration.getTapTimeout()) {
-                            // A tap on the controller (not a drag) will move the cursor
-                            int offset = getOffset((int) event.getX(), (int) event.getY());
-                            Selection.setSelection((Spannable) mText, offset);
-
-                            // Modified by cancelLongPress and prevents the cursor from changing
-                            mScrolled = false;
-                        }
-                        break;
-                    }
-                }
-            }
-            return false;
         }
 
         public float getOffsetX() {
@@ -7919,90 +7872,59 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
         public float getOffsetY() {
             return mOffsetY;
         }
+
+        public boolean onTouchEvent(MotionEvent ev) {
+            return false;
+        }
     }
 
     class SelectionModifierCursorController implements CursorController {
-        // Whether or not the selection controls are currently visible
-        private boolean mIsVisible = false;
-        // Whether that start or the end of selection controller is dragged
-        private boolean mStartIsDragged = false;
-        // Starting time of the fade timer
-        private long mFadeOutTimerStart;
-        // Used to detect a tap (vs drag) on the controller
-        private long mOnDownTimerStart;
         // The cursor controller images
-        private final Handle mStartHandle, mEndHandle;
+        private HandleView mStartHandle, mEndHandle;
         // Offset between finger hot point on active cursor controller and actual cursor
         private float mOffsetX, mOffsetY;
         // The offsets of that last touch down event. Remembered to start selection there.
         private int mMinTouchOffset, mMaxTouchOffset;
+        // Whether selection anchors are active
+        private boolean mIsShowing;
 
         SelectionModifierCursorController() {
             Resources res = mContext.getResources();
-            mStartHandle = new Handle(res.getDrawable(mTextSelectHandleLeftRes));
-            mEndHandle = new Handle(res.getDrawable(mTextSelectHandleRightRes));
+            mStartHandle = new HandleView(this, res.getDrawable(mTextSelectHandleLeftRes));
+            mEndHandle = new HandleView(this, res.getDrawable(mTextSelectHandleRightRes));
         }
 
         public void show() {
-            updateDrawablesPositions();
-            // Has to be done after updateDrawablePositions, so that previous position invalidate
-            // in only done if necessary.
-            mIsVisible = true;
-            updateOverlay();
-            mFadeOutTimerStart = -1;
+            mIsShowing = true;
+            updatePosition();
+            mStartHandle.show();
+            mEndHandle.show();
             hideInsertionPointCursorController();
         }
 
         public void hide() {
-            if (mIsVisible && (mFadeOutTimerStart < 0)) {
-                mFadeOutTimerStart = System.currentTimeMillis();
-                mStartHandle.postInvalidate();
-                mEndHandle.postInvalidate();
-            }
+            mStartHandle.hide();
+            mEndHandle.hide();
+            mIsShowing = false;
         }
 
         public boolean isShowing() {
-            return mIsVisible;
+            return mIsShowing;
         }
 
         public void cancelFadeOutAnimation() {
-            mIsVisible = false;
-            updateOverlay();
-            mStartHandle.postInvalidate();
-            mEndHandle.postInvalidate();
-        }
-
-        public void draw(Canvas canvas) {
-            if (mIsVisible) {
-                if (mFadeOutTimerStart >= 0) {
-                    int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart);
-                    if (time <= FADE_OUT_DURATION) {
-                        final int alpha = 255 * (FADE_OUT_DURATION - time) / FADE_OUT_DURATION;
-                        mStartHandle.mDrawable.setAlpha(alpha);
-                        mEndHandle.mDrawable.setAlpha(alpha);
-                        mStartHandle.postInvalidateDelayed(30);
-                        mEndHandle.postInvalidateDelayed(30);
-                    } else {
-                        mStartHandle.mDrawable.setAlpha(0);
-                        mEndHandle.mDrawable.setAlpha(0);
-                        mIsVisible = false;
-                        updateOverlay();
-                    }
-                }
-                mStartHandle.mDrawable.draw(canvas);
-                mEndHandle.mDrawable.draw(canvas);
-            }
+            hide();
         }
 
         public void updatePosition(int x, int y) {
             int selectionStart = getSelectionStart();
             int selectionEnd = getSelectionEnd();
 
-            final int previousOffset = mStartIsDragged ? selectionStart : selectionEnd;
+            final int previousOffset = mStartHandle.isDragging() ? selectionStart : selectionEnd;
             int offset = getHysteresisOffset(x, y, previousOffset);
 
             // Handle the case where start and end are swapped, making sure start <= end
-            if (mStartIsDragged) {
+            if (mStartHandle.isDragging()) {
                 if (offset <= selectionEnd) {
                     if (selectionStart == offset) {
                         return; // no change, no need to redraw;
@@ -8011,7 +7933,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                 } else {
                     selectionStart = selectionEnd;
                     selectionEnd = offset;
-                    mStartIsDragged = false;
+                    HandleView temp = mStartHandle;
+                    mStartHandle = mEndHandle;
+                    mEndHandle = temp;
                 }
             } else {
                 if (offset >= selectionStart) {
@@ -8022,43 +7946,35 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                 } else {
                     selectionEnd = selectionStart;
                     selectionStart = offset;
-                    mStartIsDragged = true;
+                    HandleView temp = mStartHandle;
+                    mStartHandle = mEndHandle;
+                    mEndHandle = temp;
                 }
             }
 
             Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
-            updateDrawablesPositions();
+            updatePosition();
         }
 
-        private void updateDrawablesPositions() {
-            if (mIsVisible) {
-                // Clear previous cursor controller before bounds are updated
-                mStartHandle.postInvalidate();
-                mEndHandle.postInvalidate();
-            }
-
+        public void updatePosition() {
             final int selectionStart = getSelectionStart();
             final int selectionEnd = getSelectionEnd();
 
             if ((selectionStart < 0) || (selectionEnd < 0)) {
                 // Should never happen, safety check.
                 Log.w(LOG_TAG, "Update selection controller position called with no cursor");
-                mIsVisible = false;
-                updateOverlay();
+                hide();
                 return;
             }
 
             boolean oneLineSelection = mLayout.getLineForOffset(selectionStart) ==
-                mLayout.getLineForOffset(selectionEnd);
+                    mLayout.getLineForOffset(selectionEnd);
             mStartHandle.positionAtCursor(selectionStart, oneLineSelection);
             mEndHandle.positionAtCursor(selectionEnd, true);
-
-            mStartHandle.mDrawable.setAlpha(255);
-            mEndHandle.mDrawable.setAlpha(255);
         }
 
         public boolean onTouchEvent(MotionEvent event) {
-            if (isTextEditable()) {
+            if (isFocused() && isTextEditable()) {
                 switch (event.getActionMasked()) {
                     case MotionEvent.ACTION_DOWN:
                         final int x = (int) event.getX();
@@ -8067,44 +7983,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                         // Remember finger down position, to be able to start selection from there
                         mMinTouchOffset = mMaxTouchOffset = mLastTouchOffset = getOffset(x, y);
 
-                        if (mIsVisible) {
-                            if (mMovement instanceof ArrowKeyMovementMethod) {
-                                boolean isOnStart = mStartHandle.hasFingerOn(x, y);
-                                boolean isOnEnd = mEndHandle.hasFingerOn(x, y);
-                                if (isOnStart || isOnEnd) {
-                                    if (mParent != null) {
-                                        // Prevent possible scrollView parent from scrolling, so
-                                        // that we can use auto-scrolling.
-                                        mParent.requestDisallowInterceptTouchEvent(true);
-                                    }
-
-                                    // In case both controllers are under finger (very small
-                                    // selection region), arbitrarily pick end controller.
-                                    mStartIsDragged = !isOnEnd;
-                                    final Handle draggedHandle =
-                                        mStartIsDragged ? mStartHandle : mEndHandle;
-                                    final Rect bounds = draggedHandle.mDrawable.getBounds();
-                                    mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
-                                    mOffsetY = draggedHandle.mHotSpotVerticalPosition - y;
-
-                                    mOffsetX += viewportToContentHorizontalOffset();
-                                    mOffsetY += viewportToContentVerticalOffset();
-
-                                    mOnDownTimerStart = event.getEventTime();
-                                    ((ArrowKeyMovementMethod)mMovement).setCursorController(this);
-                                    return true;
-                                }
-                            }
-                        }
-                        break;
-
-                    case MotionEvent.ACTION_UP:
-                        int time = (int) (event.getEventTime() - mOnDownTimerStart);
-
-                        if (time <= ViewConfiguration.getTapTimeout()) {
-                            // A tap on the controller (not a drag) opens the contextual Copy menu
-                            showContextMenu();
-                        }
                         break;
 
                     case MotionEvent.ACTION_POINTER_DOWN:
@@ -8155,7 +8033,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
          * @return true iff this controller is currently used to move the selection start.
          */
         public boolean isSelectionStartDragged() {
-            return mIsVisible && mStartIsDragged;
+            return mStartHandle.isDragging();
         }
     }
 
index ac1c0dd..7b0a502 100755 (executable)
         <attr name="quickContactBadgeStyleSmallWindowMedium" format="reference" />
         <!-- Default quickcontact badge style with large quickcontact window. -->
         <attr name="quickContactBadgeStyleSmallWindowLarge" format="reference" />
+        <!-- Reference to a style that will be used for the window containing a text
+             selection anchor. -->
+        <attr name="textSelectHandleWindowStyle" format="reference" />
 
         <!-- =================== -->
         <!-- Preference styles   -->
     </declare-styleable>
     <declare-styleable name="PopupWindow">
         <attr name="popupBackground" format="reference|color" />
+        <attr name="windowAnimationStyle" />
     </declare-styleable>
     <declare-styleable name="ViewAnimator">
         <attr name="inAnimation" format="reference" />
index 2c1e91d..3fc93e7 100644 (file)
   <public type="attr" name="textSelectHandleLeft" id="0x010102c5" />
   <public type="attr" name="textSelectHandleRight" id="0x010102c6" />
   <public type="attr" name="textSelectHandle" id="0x010102c7" />
+  <public type="attr" name="textSelectHandleWindowStyle" id="0x010102c8" />
 
   <public-padding type="attr" name="kraken_resource_pad" end="0x01010300" />
   
index e93b570..61f6ba3 100644 (file)
         <item name="android:paddingBottom">1dip</item>
         <item name="android:background">@android:drawable/bottom_bar</item>
     </style>
+
+    <!-- Style for the small popup windows that contain text selection anchors. -->
+    <style name="Widget.TextSelectHandle">
+        <item name="android:windowAnimationStyle">@android:style/Animation.TextSelectHandle</item>
+    </style>
+
+    <!-- Style for animating text selection handles. -->
+    <style name="Animation.TextSelectHandle">
+        <item name="windowEnterAnimation">@android:anim/fade_in</item>
+        <item name="windowExitAnimation">@android:anim/fade_out</item>
+    </style>
 </resources>
index 7d6ca06..e7cb593 100644 (file)
         <item name="textSelectHandleLeft">@android:drawable/text_select_handle_middle</item>
         <item name="textSelectHandleRight">@android:drawable/text_select_handle_middle</item>
         <item name="textSelectHandle">@android:drawable/text_select_handle_middle</item>
+        <item name="textSelectHandleWindowStyle">@android:style/Widget.TextSelectHandle</item>
 
         <!-- Widget styles -->
         <item name="absListViewStyle">@android:style/Widget.AbsListView</item>