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

Commit 10f92517 authored by Mady Mellor's avatar Mady Mellor Committed by Android (Google) Code Review
Browse files

Merge "Text selection: Fix word boundaries for languages without spaces" into mnc-dev

parents ff81025a e264ac39
Loading
Loading
Loading
Loading
+94 −10
Original line number Diff line number Diff line
@@ -147,11 +147,90 @@ public class WordIterator implements Selection.PositionIterator {
     * @throws IllegalArgumentException is offset is not valid.
     */
    public int getBeginning(int offset) {
        // TODO: Check if usage of this can be updated to getBeginning(offset, true) if
        // so this method can be removed.
        return getBeginning(offset, false);
    }

    /**
     * If <code>offset</code> is within a word, returns the index of the last character of that
     * word plus one, otherwise returns BreakIterator.DONE.
     *
     * The offsets that are considered to be part of a word are the indexes of its characters,
     * <i>as well as</i> the index of its last character plus one.
     * If offset is the index of a low surrogate character, BreakIterator.DONE will be returned.
     *
     * Valid range for offset is [0..textLength] (note the inclusive upper bound).
     * The returned value is within [offset..textLength] or BreakIterator.DONE.
     *
     * @throws IllegalArgumentException is offset is not valid.
     */
    public int getEnd(int offset) {
        // TODO: Check if usage of this can be updated to getEnd(offset, true), if
        // so this method can be removed.
        return getEnd(offset, false);
    }

    /**
     * If the <code>offset</code> is within a word or on a word boundary that can only be
     * considered the start of a word (e.g. _word where "_" is any character that would not
     * be considered part of the word) then this returns the index of the first character of
     * that word.
     *
     * If the offset is on a word boundary that can be considered the start and end of a
     * word, e.g. AABB (where AA and BB are both words) and the offset is the boundary
     * between AA and BB, this would return the start of the previous word, AA.
     *
     * Returns BreakIterator.DONE if there is no previous boundary.
     *
     * @throws IllegalArgumentException is offset is not valid.
     */
    public int getPrevWordBeginningOnTwoWordsBoundary(int offset) {
        return getBeginning(offset, true);
    }

    /**
     * If the <code>offset</code> is within a word or on a word boundary that can only be
     * considered the end of a word (e.g. word_ where "_" is any character that would not
     * be considered part of the word) then this returns the index of the last character
     * plus one of that word.
     *
     * If the offset is on a word boundary that can be considered the start and end of a
     * word, e.g. AABB (where AA and BB are both words) and the offset is the boundary
     * between AA and BB, this would return the end of the next word, BB.
     *
     * Returns BreakIterator.DONE if there is no next boundary.
     *
     * @throws IllegalArgumentException is offset is not valid.
     */
    public int getNextWordEndOnTwoWordBoundary(int offset) {
        return getEnd(offset, true);
    }

    /**
     * If the <code>offset</code> is within a word or on a word boundary that can only be
     * considered the start of a word (e.g. _word where "_" is any character that would not
     * be considered part of the word) then this returns the index of the first character of
     * that word.
     *
     * If the offset is on a word boundary that can be considered the start and end of a
     * word, e.g. AABB (where AA and BB are both words) and the offset is the boundary
     * between AA and BB, and getPrevWordBeginningOnTwoWordsBoundary is true then this would
     * return the start of the previous word, AA. Otherwise it would return the current offset,
     * the start of BB.
     *
     * Returns BreakIterator.DONE if there is no previous boundary.
     *
     * @throws IllegalArgumentException is offset is not valid.
     */
    private int getBeginning(int offset, boolean getPrevWordBeginningOnTwoWordsBoundary) {
        final int shiftedOffset = offset - mOffsetShift;
        checkOffsetIsValid(shiftedOffset);

        if (isOnLetterOrDigit(shiftedOffset)) {
            if (mIterator.isBoundary(shiftedOffset)) {
            if (mIterator.isBoundary(shiftedOffset)
                    && (!isAfterLetterOrDigit(shiftedOffset)
                            || !getPrevWordBeginningOnTwoWordsBoundary)) {
                return shiftedOffset + mOffsetShift;
            } else {
                return mIterator.preceding(shiftedOffset) + mOffsetShift;
@@ -164,24 +243,29 @@ public class WordIterator implements Selection.PositionIterator {
        return BreakIterator.DONE;
    }

    /** If <code>offset</code> is within a word, returns the index of the last character of that
     * word plus one, otherwise returns BreakIterator.DONE.
    /**
     * If the <code>offset</code> is within a word or on a word boundary that can only be
     * considered the end of a word (e.g. word_ where "_" is any character that would not be
     * considered part of the word) then this returns the index of the last character plus one
     * of that word.
     *
     * The offsets that are considered to be part of a word are the indexes of its characters,
     * <i>as well as</i> the index of its last character plus one.
     * If offset is the index of a low surrogate character, BreakIterator.DONE will be returned.
     * If the offset is on a word boundary that can be considered the start and end of a
     * word, e.g. AABB (where AA and BB are both words) and the offset is the boundary
     * between AA and BB, and getNextWordEndOnTwoWordBoundary is true then this would return
     * the end of the next word, BB. Otherwise it would return the current offset, the end
     * of AA.
     *
     * Valid range for offset is [0..textLength] (note the inclusive upper bound).
     * The returned value is within [offset..textLength] or BreakIterator.DONE.
     * Returns BreakIterator.DONE if there is no next boundary.
     *
     * @throws IllegalArgumentException is offset is not valid.
     */
    public int getEnd(int offset) {
    private int getEnd(int offset, boolean getNextWordEndOnTwoWordBoundary) {
        final int shiftedOffset = offset - mOffsetShift;
        checkOffsetIsValid(shiftedOffset);

        if (isAfterLetterOrDigit(shiftedOffset)) {
            if (mIterator.isBoundary(shiftedOffset)) {
            if (mIterator.isBoundary(shiftedOffset)
                    && (!isOnLetterOrDigit(shiftedOffset) || !getNextWordEndOnTwoWordBoundary)) {
                return shiftedOffset + mOffsetShift;
            } else {
                return mIterator.following(shiftedOffset) + mOffsetShift;
+89 −4
Original line number Diff line number Diff line
@@ -123,6 +123,7 @@ public class Editor {
    private static final float[] TEMP_POSITION = new float[2];
    private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
    private static final float LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS = 0.5f;
    private static final int UNSET_X_VALUE = -1;
    // Tag used when the Editor maintains its own separate UndoManager.
    private static final String UNDO_OWNER_TAG = "Editor";

@@ -735,7 +736,7 @@ public class Editor {
            retOffset = getWordIteratorWithText().getPunctuationBeginning(offset);
        } else {
            // Not on a punctuation boundary, find the word start.
            retOffset = getWordIteratorWithText().getBeginning(offset);
            retOffset = getWordIteratorWithText().getPrevWordBeginningOnTwoWordsBoundary(offset);
        }
        if (retOffset == BreakIterator.DONE) {
            return offset;
@@ -750,7 +751,7 @@ public class Editor {
            retOffset = getWordIteratorWithText().getPunctuationEnd(offset);
        } else {
            // Not on a punctuation boundary, find the word end.
            retOffset = getWordIteratorWithText().getEnd(offset);
            retOffset = getWordIteratorWithText().getNextWordEndOnTwoWordBoundary(offset);
        }
        if (retOffset == BreakIterator.DONE) {
            return offset;
@@ -4067,6 +4068,10 @@ public class Editor {
        private boolean mInWord = false;
        // Difference between touch position and word boundary position.
        private float mTouchWordDelta;
        // X value of the previous updatePosition call.
        private float mPrevX;
        // Indicates if the handle has moved a boundary between LTR and RTL text.
        private boolean mLanguageDirectionChanged = false;

        public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) {
            super(drawableLtr, drawableRtl);
@@ -4127,7 +4132,43 @@ public class Editor {
            int end = getWordEnd(offset);
            int start = getWordStart(offset);

            if (offset < mPreviousOffset) {
            if (mPrevX == UNSET_X_VALUE) {
                mPrevX = x;
            }

            final int selectionStart = mTextView.getSelectionStart();
            final boolean selectionStartRtl = layout.isRtlCharAt(selectionStart);
            final boolean atRtl = layout.isRtlCharAt(offset);
            final boolean isLvlBoundary = layout.isLevelBoundary(offset);
            boolean isExpanding;

            // We can't determine if the user is expanding or shrinking the selection if they're
            // on a bi-di boundary, so until they've moved past the boundary we'll just place
            // the cursor at the current position.
            if (isLvlBoundary || (selectionStartRtl && !atRtl) || (!selectionStartRtl && atRtl)) {
                // We're on a boundary or this is the first direction change -- just update
                // to the current position.
                mLanguageDirectionChanged = true;
                mTouchWordDelta = 0.0f;
                positionAtCursorOffset(offset, false);
                return;
            } else if (mLanguageDirectionChanged && !isLvlBoundary) {
                // We've just moved past the boundary so update the position. After this we can
                // figure out if the user is expanding or shrinking to go by word or character.
                positionAtCursorOffset(offset, false);
                mTouchWordDelta = 0.0f;
                mLanguageDirectionChanged = false;
                return;
            } else {
                final float xDiff = x - mPrevX;
                if (atRtl) {
                    isExpanding = xDiff > 0 || currLine > mPrevLine;
                } else {
                    isExpanding = xDiff < 0 || currLine < mPrevLine;
                }
            }

            if (isExpanding) {
                // User is increasing the selection.
                if (!mInWord || currLine < mPrevLine) {
                    // We're not in a word, or we're on a different line so we'll expand by
@@ -4182,6 +4223,7 @@ public class Editor {
                }
                positionAtCursorOffset(offset, false);
            }
            mPrevX = x;
        }

        @Override
@@ -4196,6 +4238,7 @@ public class Editor {
            if (event.getActionMasked() == MotionEvent.ACTION_UP) {
                // Reset the touch word offset when the user has lifted their finger.
                mTouchWordDelta = 0.0f;
                mPrevX = UNSET_X_VALUE;
            }
            return superResult;
        }
@@ -4206,6 +4249,10 @@ public class Editor {
        private boolean mInWord = false;
        // Difference between touch position and word boundary position.
        private float mTouchWordDelta;
        // X value of the previous updatePosition call.
        private float mPrevX;
        // Indicates if the handle has moved a boundary between LTR and RTL text.
        private boolean mLanguageDirectionChanged = false;

        public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) {
            super(drawableLtr, drawableRtl);
@@ -4266,7 +4313,43 @@ public class Editor {
            int end = getWordEnd(offset);
            int start = getWordStart(offset);

            if (offset > mPreviousOffset) {
            if (mPrevX == UNSET_X_VALUE) {
                mPrevX = x;
            }

            final int selectionEnd = mTextView.getSelectionEnd();
            final boolean selectionEndRtl = layout.isRtlCharAt(selectionEnd);
            final boolean atRtl = layout.isRtlCharAt(offset);
            final boolean isLvlBoundary = layout.isLevelBoundary(offset);
            boolean isExpanding;

            // We can't determine if the user is expanding or shrinking the selection if they're
            // on a bi-di boundary, so until they've moved past the boundary we'll just place
            // the cursor at the current position.
            if (isLvlBoundary || (selectionEndRtl && !atRtl) || (!selectionEndRtl && atRtl)) {
                // We're on a boundary or this is the first direction change -- just update
                // to the current position.
                mLanguageDirectionChanged = true;
                mTouchWordDelta = 0.0f;
                positionAtCursorOffset(offset, false);
                return;
            } else if (mLanguageDirectionChanged && !isLvlBoundary) {
                // We've just moved past the boundary so update the position. After this we can
                // figure out if the user is expanding or shrinking to go by word or character.
                positionAtCursorOffset(offset, false);
                mTouchWordDelta = 0.0f;
                mLanguageDirectionChanged = false;
                return;
            } else {
                final float xDiff = x - mPrevX;
                if (atRtl) {
                    isExpanding = xDiff < 0 || currLine < mPrevLine;
                } else {
                    isExpanding = xDiff > 0 || currLine > mPrevLine;
                }
            }

            if (isExpanding) {
                // User is increasing the selection.
                if (!mInWord || currLine > mPrevLine) {
                    // We're not in a word, or we're on a different line so we'll expand by
@@ -4321,6 +4404,7 @@ public class Editor {
                }
                positionAtCursorOffset(offset, false);
            }
            mPrevX = x;
        }

        @Override
@@ -4335,6 +4419,7 @@ public class Editor {
            if (event.getActionMasked() == MotionEvent.ACTION_UP) {
                // Reset the touch word offset when the user has lifted their finger.
                mTouchWordDelta = 0.0f;
                mPrevX = UNSET_X_VALUE;
            }
            return superResult;
        }