Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 40329b85 authored by Keisuke Kuroyanagi's avatar Keisuke Kuroyanagi Committed by Android (Google) Code Review
Browse files

Merge "Move mouse selection handling logic to Editor."

parents 880677bd 97af673e
Loading
Loading
Loading
Loading
+5 −21
Original line number Original line Diff line number Diff line
@@ -20,7 +20,6 @@ import android.graphics.Rect;
import android.text.Layout;
import android.text.Layout;
import android.text.Selection;
import android.text.Selection;
import android.text.Spannable;
import android.text.Spannable;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View;
@@ -222,34 +221,26 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
        return lineEnd(widget, buffer);
        return lineEnd(widget, buffer);
    }
    }


    private static boolean isTouchSelecting(boolean isMouse, Spannable buffer) {
        return isMouse ? Touch.isActivelySelecting(buffer) : isSelecting(buffer);
    }

    @Override
    @Override
    public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
    public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
        int initialScrollX = -1;
        int initialScrollX = -1;
        int initialScrollY = -1;
        int initialScrollY = -1;
        final int action = event.getAction();
        final int action = event.getAction();
        final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);


        if (action == MotionEvent.ACTION_UP) {
        if (action == MotionEvent.ACTION_UP) {
            initialScrollX = Touch.getInitialScrollX(widget, buffer);
            initialScrollX = Touch.getInitialScrollX(widget, buffer);
            initialScrollY = Touch.getInitialScrollY(widget, buffer);
            initialScrollY = Touch.getInitialScrollY(widget, buffer);
        }
        }


        boolean wasTouchSelecting = isTouchSelecting(isMouse, buffer);
        boolean wasTouchSelecting = isSelecting(buffer);
        boolean handled = Touch.onTouchEvent(widget, buffer, event);
        boolean handled = Touch.onTouchEvent(widget, buffer, event);


        if (widget.didTouchFocusSelect() && !isMouse) {
        if (widget.didTouchFocusSelect()) {
            return handled;
            return handled;
        }
        }
        if (action == MotionEvent.ACTION_DOWN) {
        if (action == MotionEvent.ACTION_DOWN) {
            // Capture the mouse pointer down location to ensure selection starts
            // right under the mouse (and is not influenced by cursor location).
            // The code below needs to run for mouse events.
            // For touch events, the code should run only when selection is active.
            // For touch events, the code should run only when selection is active.
            if (isMouse || isTouchSelecting(isMouse, buffer)) {
            if (isSelecting(buffer)) {
                if (!widget.isFocused()) {
                if (!widget.isFocused()) {
                    if (!widget.requestFocus()) {
                    if (!widget.requestFocus()) {
                        return handled;
                        return handled;
@@ -265,15 +256,8 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
            }
            }
        } else if (widget.isFocused()) {
        } else if (widget.isFocused()) {
            if (action == MotionEvent.ACTION_MOVE) {
            if (action == MotionEvent.ACTION_MOVE) {
                // Cursor can be active at any location in the text while mouse pointer can start
                if (isSelecting(buffer) && handled) {
                // selection from a totally different location. Use LAST_TAP_DOWN span to ensure
                // text selection will start from mouse pointer location.
                    final int startOffset = buffer.getSpanStart(LAST_TAP_DOWN);
                    final int startOffset = buffer.getSpanStart(LAST_TAP_DOWN);
                if (isMouse && Touch.isSelectionStarted(buffer)) {
                    Selection.setSelection(buffer, startOffset);
                }

                if (isTouchSelecting(isMouse, buffer) && handled) {
                    // Before selecting, make sure we've moved out of the "slop".
                    // Before selecting, make sure we've moved out of the "slop".
                    // handled will be true, if we're in select mode AND we're
                    // handled will be true, if we're in select mode AND we're
                    // OUT of the slop
                    // OUT of the slop
+1 −49
Original line number Original line Diff line number Diff line
@@ -119,18 +119,12 @@ public class Touch {
            ds = buffer.getSpans(0, buffer.length(), DragState.class);
            ds = buffer.getSpans(0, buffer.length(), DragState.class);


            if (ds.length > 0) {
            if (ds.length > 0) {
                ds[0].mIsSelectionStarted = false;

                if (ds[0].mFarEnough == false) {
                if (ds[0].mFarEnough == false) {
                    int slop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop();
                    int slop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop();


                    if (Math.abs(event.getX() - ds[0].mX) >= slop ||
                    if (Math.abs(event.getX() - ds[0].mX) >= slop ||
                        Math.abs(event.getY() - ds[0].mY) >= slop) {
                        Math.abs(event.getY() - ds[0].mY) >= slop) {
                        ds[0].mFarEnough = true;
                        ds[0].mFarEnough = true;
                        if (event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
                            ds[0].mIsActivelySelecting = true;
                            ds[0].mIsSelectionStarted = true;
                        }
                    }
                    }
                }
                }


@@ -142,13 +136,9 @@ public class Touch {
                            || MetaKeyKeyListener.getMetaState(buffer,
                            || MetaKeyKeyListener.getMetaState(buffer,
                                    MetaKeyKeyListener.META_SELECTING) != 0;
                                    MetaKeyKeyListener.META_SELECTING) != 0;


                    if (!event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
                        ds[0].mIsActivelySelecting = false;
                    }

                    float dx;
                    float dx;
                    float dy;
                    float dy;
                    if (cap && event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
                    if (cap) {
                        // if we're selecting, we want the scroll to go in
                        // if we're selecting, we want the scroll to go in
                        // the direction of the drag
                        // the direction of the drag
                        dx = event.getX() - ds[0].mX;
                        dx = event.getX() - ds[0].mX;
@@ -160,7 +150,6 @@ public class Touch {
                    ds[0].mX = event.getX();
                    ds[0].mX = event.getX();
                    ds[0].mY = event.getY();
                    ds[0].mY = event.getY();


                    int nx = widget.getScrollX() + (int) dx;
                    int ny = widget.getScrollY() + (int) dy;
                    int ny = widget.getScrollY() + (int) dy;


                    int padding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom();
                    int padding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom();
@@ -172,10 +161,6 @@ public class Touch {
                    int oldX = widget.getScrollX();
                    int oldX = widget.getScrollX();
                    int oldY = widget.getScrollY();
                    int oldY = widget.getScrollY();


                    if (!event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
                        scrollTo(widget, layout, nx, ny);
                    }

                    // If we actually scrolled, then cancel the up action.
                    // If we actually scrolled, then cancel the up action.
                    if (oldX != widget.getScrollX() || oldY != widget.getScrollY()) {
                    if (oldX != widget.getScrollX() || oldY != widget.getScrollY()) {
                        widget.cancelLongPress();
                        widget.cancelLongPress();
@@ -207,37 +192,6 @@ public class Touch {
        return ds.length > 0 ? ds[0].mScrollY : -1;
        return ds.length > 0 ? ds[0].mScrollY : -1;
    }
    }


    /**
     * Checks if selection is still active.
     * This is useful for extending Selection span on buffer.
     * @param buffer The text buffer.
     * @return true if buffer has been marked for selection.
     *
     * @hide
     */
    static boolean isActivelySelecting(Spannable buffer) {
        DragState[] ds;
        ds = buffer.getSpans(0, buffer.length(), DragState.class);

        return ds.length > 0 && ds[0].mIsActivelySelecting;
    }

    /**
     * Checks if selection has begun (are we out of slop?).
     * Note: DragState.mIsSelectionStarted goes back to false with the very next event.
     * This is useful for starting Selection span on buffer.
     * @param buffer The text buffer.
     * @return true if selection has started on the buffer.
     *
     * @hide
     */
    static boolean isSelectionStarted(Spannable buffer) {
        DragState[] ds;
        ds = buffer.getSpans(0, buffer.length(), DragState.class);

        return ds.length > 0 && ds[0].mIsSelectionStarted;
    }

    private static class DragState implements NoCopySpan {
    private static class DragState implements NoCopySpan {
        public float mX;
        public float mX;
        public float mY;
        public float mY;
@@ -245,8 +199,6 @@ public class Touch {
        public int mScrollY;
        public int mScrollY;
        public boolean mFarEnough;
        public boolean mFarEnough;
        public boolean mUsed;
        public boolean mUsed;
        public boolean mIsActivelySelecting;
        public boolean mIsSelectionStarted;


        public DragState(float x, float y, int scrollX, int scrollY) {
        public DragState(float x, float y, int scrollX, int scrollY) {
            mX = x;
            mX = x;
+139 −82
Original line number Original line Diff line number Diff line
@@ -75,6 +75,7 @@ import android.view.ActionMode.Callback;
import android.view.DisplayListCanvas;
import android.view.DisplayListCanvas;
import android.view.DragEvent;
import android.view.DragEvent;
import android.view.Gravity;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.LayoutInflater;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem;
@@ -805,8 +806,8 @@ public class Editor {
        final int maxOffset = TextUtils.unpackRangeEndFromLong(lastTouchOffsets);
        final int maxOffset = TextUtils.unpackRangeEndFromLong(lastTouchOffsets);


        // Safety check in case standard touch event handling has been bypassed
        // Safety check in case standard touch event handling has been bypassed
        if (minOffset < 0 || minOffset >= mTextView.getText().length()) return false;
        if (minOffset < 0 || minOffset > mTextView.getText().length()) return false;
        if (maxOffset < 0 || maxOffset >= mTextView.getText().length()) return false;
        if (maxOffset < 0 || maxOffset > mTextView.getText().length()) return false;


        int selectionStart, selectionEnd;
        int selectionStart, selectionEnd;


@@ -1773,7 +1774,8 @@ public class Editor {
        stopTextActionMode();
        stopTextActionMode();
        mPreserveDetachedSelection = false;
        mPreserveDetachedSelection = false;


        getSelectionController().enterDrag();
        getSelectionController().enterDrag(
                SelectionModifierCursorController.DRAG_ACCELERATOR_MODE_WORD);
        return true;
        return true;
    }
    }


@@ -4492,14 +4494,22 @@ public class Editor {


        // Where the user first starts the drag motion.
        // Where the user first starts the drag motion.
        private int mStartOffset = -1;
        private int mStartOffset = -1;
        // Indicates whether the user is selecting text and using the drag accelerator.

        private boolean mDragAcceleratorActive;
        private boolean mHaventMovedEnoughToStartDrag;
        private boolean mHaventMovedEnoughToStartDrag;
        // The line that a selection happened most recently with the drag accelerator.
        // The line that a selection happened most recently with the drag accelerator.
        private int mLineSelectionIsOn = -1;
        private int mLineSelectionIsOn = -1;
        // Whether the drag accelerator has selected past the initial line.
        // Whether the drag accelerator has selected past the initial line.
        private boolean mSwitchedLines = false;
        private boolean mSwitchedLines = false;


        // Indicates the drag accelerator mode that the user is currently using.
        private int mDragAcceleratorMode = DRAG_ACCELERATOR_MODE_INACTIVE;
        // Drag accelerator is inactive.
        private static final int DRAG_ACCELERATOR_MODE_INACTIVE = 0;
        // Character based selection by dragging. Only for mouse.
        private static final int DRAG_ACCELERATOR_MODE_CHARACTER = 1;
        // Word based selection by dragging. Enabled after long pressing or double tapping.
        private static final int DRAG_ACCELERATOR_MODE_WORD = 2;

        SelectionModifierCursorController() {
        SelectionModifierCursorController() {
            resetTouchOffsets();
            resetTouchOffsets();
        }
        }
@@ -4510,7 +4520,6 @@ public class Editor {
            }
            }
            initDrawables();
            initDrawables();
            initHandles();
            initHandles();
            hideInsertionPointCursorController();
        }
        }


        private void initDrawables() {
        private void initDrawables() {
@@ -4548,10 +4557,10 @@ public class Editor {
            if (mEndHandle != null) mEndHandle.hide();
            if (mEndHandle != null) mEndHandle.hide();
        }
        }


        public void enterDrag() {
        public void enterDrag(int dragAcceleratorMode) {
            // Just need to init the handles / hide insertion cursor.
            // Just need to init the handles / hide insertion cursor.
            show();
            show();
            mDragAcceleratorActive = true;
            mDragAcceleratorMode = dragAcceleratorMode;
            // Start location of selection.
            // Start location of selection.
            mStartOffset = mTextView.getOffsetForPosition(mLastDownPositionX,
            mStartOffset = mTextView.getOffsetForPosition(mLastDownPositionX,
                    mLastDownPositionY);
                    mLastDownPositionY);
@@ -4563,6 +4572,7 @@ public class Editor {
            // the user to continue dragging across the screen to select text; TextView will
            // the user to continue dragging across the screen to select text; TextView will
            // scroll as necessary.
            // scroll as necessary.
            mTextView.getParent().requestDisallowInterceptTouchEvent(true);
            mTextView.getParent().requestDisallowInterceptTouchEvent(true);
            mTextView.cancelLongPress();
        }
        }


        public void onTouchEvent(MotionEvent event) {
        public void onTouchEvent(MotionEvent event) {
@@ -4570,6 +4580,7 @@ public class Editor {
            // selection and tap can move cursor from this tap position.
            // selection and tap can move cursor from this tap position.
            final float eventX = event.getX();
            final float eventX = event.getX();
            final float eventY = event.getY();
            final float eventY = event.getY();
            final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
            switch (event.getActionMasked()) {
            switch (event.getActionMasked()) {
                case MotionEvent.ACTION_DOWN:
                case MotionEvent.ACTION_DOWN:
                    if (extractedTextModeWillBeStarted()) {
                    if (extractedTextModeWillBeStarted()) {
@@ -4593,7 +4604,7 @@ public class Editor {
                                boolean stayedInArea =
                                boolean stayedInArea =
                                        distanceSquared < doubleTapSlop * doubleTapSlop;
                                        distanceSquared < doubleTapSlop * doubleTapSlop;


                                if (stayedInArea && isPositionOnText(eventX, eventY)) {
                                if (stayedInArea && (isMouse || isPositionOnText(eventX, eventY))) {
                                    selectCurrentWordAndStartDrag();
                                    selectCurrentWordAndStartDrag();
                                    mDiscardNextActionUp = true;
                                    mDiscardNextActionUp = true;
                                }
                                }
@@ -4639,36 +4650,118 @@ public class Editor {
                        }
                        }
                    }
                    }


                    if (isMouse && !isDragAcceleratorActive()) {
                        final int offset = mTextView.getOffsetForPosition(eventX, eventY);
                        if (mStartOffset != offset) {
                            // Start character based drag accelerator.
                            if (mTextActionMode != null) {
                                mTextActionMode.finish();
                            }
                            enterDrag(DRAG_ACCELERATOR_MODE_CHARACTER);
                            mDiscardNextActionUp = true;
                            mHaventMovedEnoughToStartDrag = false;
                        }
                    }

                    if (mStartHandle != null && mStartHandle.isShowing()) {
                    if (mStartHandle != null && mStartHandle.isShowing()) {
                        // Don't do the drag if the handles are showing already.
                        // Don't do the drag if the handles are showing already.
                        break;
                        break;
                    }
                    }


                    if (mStartOffset != -1 && mTextView.getLayout() != null) {
                    updateSelection(event);
                        if (!mHaventMovedEnoughToStartDrag) {
                    break;

                case MotionEvent.ACTION_UP:
                    if (!isDragAcceleratorActive()) {
                        break;
                    }
                    updateSelection(event);

                    // No longer dragging to select text, let the parent intercept events.
                    mTextView.getParent().requestDisallowInterceptTouchEvent(false);

                    int startOffset = mTextView.getSelectionStart();
                    int endOffset = mTextView.getSelectionEnd();

                    // Since we don't let drag handles pass once they're visible, we need to
                    // make sure the start / end locations are correct because the user *can*
                    // switch directions during the initial drag.
                    if (endOffset < startOffset) {
                        int tmp = endOffset;
                        endOffset = startOffset;
                        startOffset = tmp;

                        // Also update the selection with the right offsets in this case.
                        Selection.setSelection((Spannable) mTextView.getText(),
                                startOffset, endOffset);
                    }
                    if (startOffset != endOffset) {
                        mStartHandle.showAtLocation(startOffset);
                        mEndHandle.showAtLocation(endOffset);
                        startSelectionActionMode();
                    }

                    // No longer the first dragging motion, reset.
                    resetDragAcceleratorState();
                    break;
            }
        }

        private void updateSelection(MotionEvent event) {
            if (mTextView.getLayout() != null) {
                switch (mDragAcceleratorMode) {
                    case DRAG_ACCELERATOR_MODE_CHARACTER:
                        updateCharacterBasedSelection(event);
                        break;
                    case DRAG_ACCELERATOR_MODE_WORD:
                        updateWordBasedSelection(event);
                        break;
                }
            }
        }

        private void updateCharacterBasedSelection(MotionEvent event) {
            final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY());
            Selection.setSelection((Spannable) mTextView.getText(), mStartOffset, offset);
        }


        private void updateWordBasedSelection(MotionEvent event) {
            if (mHaventMovedEnoughToStartDrag) {
                return;
            }
            final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
            final ViewConfiguration viewConfig = ViewConfiguration.get(
                    mTextView.getContext());
            final float eventX = event.getX();
            final float eventY = event.getY();
            final int currLine;
            if (isMouse) {
                // No need to offset the y coordinate for mouse input.
                currLine = mTextView.getLineAtCoordinate(eventY);
            } else {
                float y = eventY;
                float y = eventY;
                if (mSwitchedLines) {
                if (mSwitchedLines) {
                    // Offset the finger by the same vertical offset as the handles.
                    // Offset the finger by the same vertical offset as the handles.
                    // This improves visibility of the content being selected by
                    // This improves visibility of the content being selected by
                    // shifting the finger below the content, this is applied once
                    // shifting the finger below the content, this is applied once
                    // the user has switched lines.
                    // the user has switched lines.
                    final int touchSlop = viewConfig.getScaledTouchSlop();
                    final float fingerOffset = (mStartHandle != null)
                    final float fingerOffset = (mStartHandle != null)
                            ? mStartHandle.getIdealVerticalOffset()
                            ? mStartHandle.getIdealVerticalOffset()
                            : touchSlop;
                            : touchSlop;
                    y = eventY - fingerOffset;
                    y = eventY - fingerOffset;
                }
                }


                            final int currLine = getCurrentLineAdjustedForSlop(
                currLine = getCurrentLineAdjustedForSlop(mTextView.getLayout(), mLineSelectionIsOn,
                                    mTextView.getLayout(),
                        y);
                                    mLineSelectionIsOn, y);
                if (!mSwitchedLines && currLine != mLineSelectionIsOn) {
                if (!mSwitchedLines && currLine != mLineSelectionIsOn) {
                    // Break early here, we want to offset the finger position from
                    // Break early here, we want to offset the finger position from
                    // the selection highlight, once the user moved their finger
                    // the selection highlight, once the user moved their finger
                    // to a different line we should apply the offset and *not* switch
                    // to a different line we should apply the offset and *not* switch
                    // lines until recomputing the position with the finger offset.
                    // lines until recomputing the position with the finger offset.
                    mSwitchedLines = true;
                    mSwitchedLines = true;
                                break;
                    return;
                }
            }
            }


            int startOffset;
            int startOffset;
@@ -4687,46 +4780,6 @@ public class Editor {
            Selection.setSelection((Spannable) mTextView.getText(),
            Selection.setSelection((Spannable) mTextView.getText(),
                    startOffset, offset);
                    startOffset, offset);
        }
        }
                    }
                    break;

                case MotionEvent.ACTION_UP:
                    if (mDragAcceleratorActive) {
                        // No longer dragging to select text, let the parent intercept events.
                        mTextView.getParent().requestDisallowInterceptTouchEvent(false);

                        show();
                        int startOffset = mTextView.getSelectionStart();
                        int endOffset = mTextView.getSelectionEnd();

                        // Since we don't let drag handles pass once they're visible, we need to
                        // make sure the start / end locations are correct because the user *can*
                        // switch directions during the initial drag.
                        if (endOffset < startOffset) {
                            int tmp = endOffset;
                            endOffset = startOffset;
                            startOffset = tmp;

                            // Also update the selection with the right offsets in this case.
                            Selection.setSelection((Spannable) mTextView.getText(),
                                    startOffset, endOffset);
                        }

                        // Need to do this to display the handles.
                        mStartHandle.showAtLocation(startOffset);
                        mEndHandle.showAtLocation(endOffset);

                        // No longer the first dragging motion, reset.
                        startSelectionActionMode();

                        mDragAcceleratorActive = false;
                        mStartOffset = -1;
                        mSwitchedLines = false;
                    }
                    break;
            }
        }

        /**
        /**
         * @param event
         * @param event
         */
         */
@@ -4749,8 +4802,12 @@ public class Editor {


        public void resetTouchOffsets() {
        public void resetTouchOffsets() {
            mMinTouchOffset = mMaxTouchOffset = -1;
            mMinTouchOffset = mMaxTouchOffset = -1;
            resetDragAcceleratorState();
        }

        private void resetDragAcceleratorState() {
            mStartOffset = -1;
            mStartOffset = -1;
            mDragAcceleratorActive = false;
            mDragAcceleratorMode = DRAG_ACCELERATOR_MODE_INACTIVE;
            mSwitchedLines = false;
            mSwitchedLines = false;
        }
        }


@@ -4765,7 +4822,7 @@ public class Editor {
         * @return true if the user is selecting text using the drag accelerator.
         * @return true if the user is selecting text using the drag accelerator.
         */
         */
        public boolean isDragAcceleratorActive() {
        public boolean isDragAcceleratorActive() {
            return mDragAcceleratorActive;
            return mDragAcceleratorMode != DRAG_ACCELERATOR_MODE_INACTIVE;
        }
        }


        public void onTouchModeChanged(boolean isInTouchMode) {
        public void onTouchModeChanged(boolean isInTouchMode) {