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

Commit 468394fc authored by Gilles Debunne's avatar Gilles Debunne
Browse files

Text Selection: smart word detection. DO NOT MERGE.

Selecting between words selects the spaces instead of the closest word, facilitating
paste insertion vs word replacement.

Pasting adds/removes space before and after if needed so that pasted text is not appended to
existing text.

Cut and copy not available when selection is empty.

Change-Id: I331413bdef341a4eceffe76fa2957f388d540b6e
parent 4c7d3f28
Loading
Loading
Loading
Loading
+87 −11
Original line number Diff line number Diff line
@@ -5817,7 +5817,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
     * Return true iff there is a selection inside this text view.
     */
    public boolean hasSelection() {
        return getSelectionStart() != getSelectionEnd();
        final int selectionStart = getSelectionStart();
        final int selectionEnd = getSelectionEnd();

        return selectionStart >= 0 && selectionStart != selectionEnd;
    }

    /**
@@ -7005,7 +7008,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            return false;
        }

        if (mText.length() > 0 && getSelectionStart() >= 0) {
        if (mText.length() > 0 && hasSelection()) {
            if (mText instanceof Editable && mInput != null) {
                return true;
            }
@@ -7019,7 +7022,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            return false;
        }

        if (mText.length() > 0 && getSelectionStart() >= 0) {
        if (mText.length() > 0 && hasSelection()) {
            return true;
        }

@@ -7140,6 +7143,49 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        int minOffset = selectionModifierCursorController.getMinTouchOffset();
        int maxOffset = selectionModifierCursorController.getMaxTouchOffset();

        if (minOffset == maxOffset) {
            int offset = Math.max(0, Math.min(minOffset, mTransformed.length()));

            // Tolerance, number of charaters around tapped position
            final int range = 1;
            final int max = mTransformed.length() - 1;

            // 'Smart' word selection: detect position between words
            for (int i = -range; i <= range; i++) {
                int index = offset + i;
                if (index >= 0 && index <= max) {
                    if (Character.isSpaceChar(mTransformed.charAt(index))) {
                        // Select current space
                        selectionStart = index;
                        selectionEnd = selectionStart + 1;

                        // Extend selection to maximum space range
                        while (selectionStart > 0 &&
                                Character.isSpaceChar(mTransformed.charAt(selectionStart - 1))) {
                            selectionStart--;
                        }
                        while (selectionEnd < max &&
                                Character.isSpaceChar(mTransformed.charAt(selectionEnd))) {
                            selectionEnd++;
                        }

                        Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
                        return;
                    }
                }
            }

            // 'Smart' word selection: detect position at beginning or end of text.
            if (offset <= range) {
                Selection.setSelection((Spannable) mText, 0, 0);
                return;
            }
            if (offset >= (max - range)) {
                Selection.setSelection((Spannable) mText, max + 1, max + 1);
                return;
            }
        }

        long wordLimits = getWordLimitsAt(minOffset);
        if (wordLimits >= 0) {
            selectionStart = (int) (wordLimits >>> 32);
@@ -7220,21 +7266,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
                     setOnMenuItemClickListener(handler).
                     setAlphabeticShortcut('x');
                added = true;
            }

            if (canCopy()) {
                menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
                     setOnMenuItemClickListener(handler).
                     setAlphabeticShortcut('c');
                added = true;
            }

            if (canPaste()) {
                menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
                     setOnMenuItemClickListener(handler).
                     setAlphabeticShortcut('v');
            }

                added = true;
            }
        } else {
            /*
            if (!isFocused()) {
@@ -7314,11 +7361,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
    private boolean textIsOnlySpaces() {
        final int length = mTransformed.length();
        for (int i = 0; i < length; i++) {
            final char c = mTransformed.charAt(i);
            final int type = Character.getType(c);
            if (type != Character.SPACE_SEPARATOR)
            if (!Character.isSpaceChar(mTransformed.charAt(i))) {
                return false;
            }
        }
        return true;
    }

@@ -7350,7 +7396,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
    /**
     * Called when a context menu option for the text view is selected.  Currently
     * this will be one of: {@link android.R.id#selectAll},
     * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
     * {@link android.R.id#startSelectingText},
     * {@link android.R.id#cut}, {@link android.R.id#copy},
     * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
     * or {@link android.R.id#switchInputMethod}.
@@ -7394,7 +7440,37 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            case ID_PASTE:
                CharSequence paste = clip.getText();

                if (paste != null) {
                if (paste != null && paste.length() > 0) {
                    // Paste adds/removes spaces before or after insertion as needed.

                    if (Character.isSpaceChar(paste.charAt(0))) {
                        if (min > 0 && Character.isSpaceChar(mTransformed.charAt(min - 1))) {
                            // Two spaces at beginning of paste: remove one
                            ((Editable) mText).replace(min - 1, min, "");
                            min = min - 1;
                            max = max - 1;
                        }
                    } else {
                        if (min > 0 && !Character.isSpaceChar(mTransformed.charAt(min - 1))) {
                            // No space at beginning of paste: add one
                            ((Editable) mText).replace(min, min, " ");
                            min = min + 1;
                            max = max + 1;
                        }
                    }

                    if (Character.isSpaceChar(paste.charAt(paste.length() - 1))) {
                        if (max < mText.length() && Character.isSpaceChar(mTransformed.charAt(max))) {
                            // Two spaces at end of paste: remove one
                            ((Editable) mText).replace(max, max + 1, "");
                        }
                    } else {
                        if (max < mText.length() && !Character.isSpaceChar(mTransformed.charAt(max))) {
                            // No space at end of paste: add one
                            ((Editable) mText).replace(max, max, " ");
                        }
                    }

                    Selection.setSelection((Spannable) mText, max);
                    ((Editable) mText).replace(min, max, paste);
                    stopTextSelectionMode();