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 Diff line number Diff line
@@ -20,7 +20,6 @@ import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -222,34 +221,26 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
        return lineEnd(widget, buffer);
    }

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

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

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

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

        if (widget.didTouchFocusSelect() && !isMouse) {
        if (widget.didTouchFocusSelect()) {
            return handled;
        }
        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.
            if (isMouse || isTouchSelecting(isMouse, buffer)) {
            if (isSelecting(buffer)) {
                if (!widget.isFocused()) {
                    if (!widget.requestFocus()) {
                        return handled;
@@ -265,15 +256,8 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
            }
        } else if (widget.isFocused()) {
            if (action == MotionEvent.ACTION_MOVE) {
                // Cursor can be active at any location in the text while mouse pointer can start
                // selection from a totally different location. Use LAST_TAP_DOWN span to ensure
                // text selection will start from mouse pointer location.
                if (isSelecting(buffer) && handled) {
                    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".
                    // handled will be true, if we're in select mode AND we're
                    // OUT of the slop
+1 −49
Original line number Diff line number Diff line
@@ -119,18 +119,12 @@ public class Touch {
            ds = buffer.getSpans(0, buffer.length(), DragState.class);

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

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

                    if (Math.abs(event.getX() - ds[0].mX) >= slop ||
                        Math.abs(event.getY() - ds[0].mY) >= slop) {
                        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.META_SELECTING) != 0;

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

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

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

                    int padding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom();
@@ -172,10 +161,6 @@ public class Touch {
                    int oldX = widget.getScrollX();
                    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 (oldX != widget.getScrollX() || oldY != widget.getScrollY()) {
                        widget.cancelLongPress();
@@ -207,37 +192,6 @@ public class Touch {
        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 {
        public float mX;
        public float mY;
@@ -245,8 +199,6 @@ public class Touch {
        public int mScrollY;
        public boolean mFarEnough;
        public boolean mUsed;
        public boolean mIsActivelySelecting;
        public boolean mIsSelectionStarted;

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

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

        int selectionStart, selectionEnd;

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

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

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

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

        private boolean mHaventMovedEnoughToStartDrag;
        // The line that a selection happened most recently with the drag accelerator.
        private int mLineSelectionIsOn = -1;
        // Whether the drag accelerator has selected past the initial line.
        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() {
            resetTouchOffsets();
        }
@@ -4510,7 +4520,6 @@ public class Editor {
            }
            initDrawables();
            initHandles();
            hideInsertionPointCursorController();
        }

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

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

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

                                if (stayedInArea && isPositionOnText(eventX, eventY)) {
                                if (stayedInArea && (isMouse || isPositionOnText(eventX, eventY))) {
                                    selectCurrentWordAndStartDrag();
                                    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()) {
                        // Don't do the drag if the handles are showing already.
                        break;
                    }

                    if (mStartOffset != -1 && mTextView.getLayout() != null) {
                        if (!mHaventMovedEnoughToStartDrag) {
                    updateSelection(event);
                    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;
                if (mSwitchedLines) {
                    // Offset the finger by the same vertical offset as the handles.
                    // This improves visibility of the content being selected by
                    // shifting the finger below the content, this is applied once
                    // the user has switched lines.
                    final int touchSlop = viewConfig.getScaledTouchSlop();
                    final float fingerOffset = (mStartHandle != null)
                            ? mStartHandle.getIdealVerticalOffset()
                            : touchSlop;
                    y = eventY - fingerOffset;
                }

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

            int startOffset;
@@ -4687,46 +4780,6 @@ public class Editor {
            Selection.setSelection((Spannable) mTextView.getText(),
                    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
         */
@@ -4749,8 +4802,12 @@ public class Editor {

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

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

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

        public void onTouchModeChanged(boolean isInTouchMode) {