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

Commit 67bcd82b authored by Jeff Sharkey's avatar Jeff Sharkey Committed by Android (Google) Code Review
Browse files

Merge "Support Ctrl-based EditText movement."

parents 571a24bf e982dfc1
Loading
Loading
Loading
Loading
+95 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.text;

import android.util.MathUtils;

import java.text.CharacterIterator;

/** {@hide} */
public class CharSequenceIterator implements CharacterIterator {
    private final CharSequence mValue;

    private final int mStart;
    private final int mEnd;
    private int mIndex;

    public CharSequenceIterator(CharSequence value) {
        mValue = value;
        mStart = 0;
        mEnd = value.length();
        mIndex = 0;
    }

    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(e);
        }
    }

    /** {@inheritDoc} */
    public char current() {
        if (mIndex == mEnd) {
            return DONE;
        }
        return mValue.charAt(mIndex);
    }

    /** {@inheritDoc} */
    public int getBeginIndex() {
        return mStart;
    }

    /** {@inheritDoc} */
    public int getEndIndex() {
        return mEnd;
    }

    /** {@inheritDoc} */
    public int getIndex() {
        return mIndex;
    }

    /** {@inheritDoc} */
    public char first() {
        return setIndex(mStart);
    }

    /** {@inheritDoc} */
    public char last() {
        return setIndex(mEnd - 1);
    }

    /** {@inheritDoc} */
    public char next() {
        return setIndex(mIndex + 1);
    }

    /** {@inheritDoc} */
    public char previous() {
        return setIndex(mIndex - 1);
    }

    /** {@inheritDoc} */
    public char setIndex(int index) {
        mIndex = MathUtils.constrain(index, mStart, mEnd);
        return current();
    }
}
+45 −4
Original line number Diff line number Diff line
@@ -16,6 +16,11 @@

package android.text;

import android.util.Log;

import java.text.BreakIterator;
import java.text.CharacterIterator;


/**
 * Utility class for manipulating cursors and selections in CharSequences.
@@ -357,6 +362,42 @@ public class Selection {
        return true;
    }

    /** {@hide} */
    public static interface PositionIterator {
        public static final int DONE = BreakIterator.DONE;

        public int preceding(int position);
        public int following(int position);
    }

    /** {@hide} */
    public static boolean moveToPreceding(
            Spannable text, PositionIterator iter, boolean extendSelection) {
        final int offset = iter.preceding(getSelectionEnd(text));
        if (offset != PositionIterator.DONE) {
            if (extendSelection) {
                extendSelection(text, offset);
            } else {
                setSelection(text, offset);
            }
        }
        return true;
    }

    /** {@hide} */
    public static boolean moveToFollowing(
            Spannable text, PositionIterator iter, boolean extendSelection) {
        final int offset = iter.following(getSelectionEnd(text));
        if (offset != PositionIterator.DONE) {
            if (extendSelection) {
                extendSelection(text, offset);
            } else {
                setSelection(text, offset);
            }
        }
        return true;
    }

    private static int findEdge(Spannable text, Layout layout, int dir) {
        int pt = getSelectionEnd(text);
        int line = layout.getLineForOffset(pt);
+123 −2
Original line number Diff line number Diff line
@@ -17,14 +17,23 @@
package android.text.method;

import android.graphics.Rect;
import android.text.CharSequenceIterator;
import android.text.Editable;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.Spanned;
import android.text.TextWatcher;
import android.util.Log;
import android.util.MathUtils;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;

import java.text.BreakIterator;
import java.text.CharacterIterator;

/**
 * A movement method that provides cursor movement and selection.
 * Supports displaying the context menu on DPad Center.
@@ -193,6 +202,20 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
        }
    }

    /** {@hide} */
    @Override
    protected boolean leftWord(TextView widget, Spannable buffer) {
        mWordIterator.setCharSequence(buffer);
        return Selection.moveToPreceding(buffer, mWordIterator, isSelecting(buffer));
    }

    /** {@hide} */
    @Override
    protected boolean rightWord(TextView widget, Spannable buffer) {
        mWordIterator.setCharSequence(buffer);
        return Selection.moveToFollowing(buffer, mWordIterator, isSelecting(buffer));
    }

    @Override
    protected boolean home(TextView widget, Spannable buffer) {
        return lineStart(widget, buffer);
@@ -205,7 +228,8 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme

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

        if (action == MotionEvent.ACTION_UP) {
@@ -308,6 +332,103 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
        return sInstance;
    }

    /**
     * Walks through cursor positions at word boundaries. Internally uses
     * {@link BreakIterator#getWordInstance()}, and caches {@link CharSequence}
     * for performance reasons.
     */
    private static class WordIterator implements Selection.PositionIterator {
        private CharSequence mCurrent;
        private boolean mCurrentDirty = false;

        private BreakIterator mIterator;

        private TextWatcher mWatcher = new TextWatcher() {
            /** {@inheritDoc} */
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                // ignored
            }

            /** {@inheritDoc} */
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                mCurrentDirty = true;
            }

            /** {@inheritDoc} */
            public void afterTextChanged(Editable s) {
                // ignored
            }
        };

        public void setCharSequence(CharSequence incoming) {
            if (mIterator == null) {
                mIterator = BreakIterator.getWordInstance();
            }

            // when incoming is different object, move listeners to new sequence
            // and mark as dirty so we reload contents.
            if (mCurrent != incoming) {
                if (mCurrent instanceof Editable) {
                    ((Editable) mCurrent).removeSpan(mWatcher);
                }

                if (incoming instanceof Editable) {
                    ((Editable) incoming).setSpan(
                            mWatcher, 0, incoming.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
                }

                mCurrent = incoming;
                mCurrentDirty = true;
            }

            if (mCurrentDirty) {
                final CharacterIterator charIterator = new CharSequenceIterator(mCurrent);
                mIterator.setText(charIterator);

                mCurrentDirty = false;
            }
        }

        private boolean isValidOffset(int offset) {
            return offset >= 0 && offset < mCurrent.length();
        }

        private boolean isLetterOrDigit(int offset) {
            if (isValidOffset(offset)) {
                return Character.isLetterOrDigit(mCurrent.charAt(offset));
            } else {
                return false;
            }
        }

        /** {@inheritDoc} */
        public int preceding(int offset) {
            // always round cursor index into valid string index
            offset = MathUtils.constrain(offset, 0, mCurrent.length() - 1);

            do {
                offset = mIterator.preceding(offset);
                if (isLetterOrDigit(offset)) break;
            } while (isValidOffset(offset));

            return offset;
        }

        /** {@inheritDoc} */
        public int following(int offset) {
            // always round cursor index into valid string index
            offset = MathUtils.constrain(offset, 0, mCurrent.length() - 1);

            do {
                offset = mIterator.following(offset);
                if (isLetterOrDigit(offset - 1)) break;
            } while (isValidOffset(offset));

            return offset;
        }
    }

    private WordIterator mWordIterator = new WordIterator();

    private static final Object LAST_TAP_DOWN = new Object();
    private static ArrowKeyMovementMethod sInstance;
+22 −0
Original line number Diff line number Diff line
@@ -163,6 +163,9 @@ public class BaseMovementMethod implements MovementMethod {
            case KeyEvent.KEYCODE_DPAD_LEFT:
                if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
                    return left(widget, buffer);
                } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
                        KeyEvent.META_CTRL_ON)) {
                    return leftWord(widget, buffer);
                } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
                        KeyEvent.META_ALT_ON)) {
                    return lineStart(widget, buffer);
@@ -172,6 +175,9 @@ public class BaseMovementMethod implements MovementMethod {
            case KeyEvent.KEYCODE_DPAD_RIGHT:
                if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
                    return right(widget, buffer);
                } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
                        KeyEvent.META_CTRL_ON)) {
                    return rightWord(widget, buffer);
                } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
                        KeyEvent.META_ALT_ON)) {
                    return lineEnd(widget, buffer);
@@ -217,12 +223,18 @@ public class BaseMovementMethod implements MovementMethod {
            case KeyEvent.KEYCODE_MOVE_HOME:
                if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
                    return home(widget, buffer);
                } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
                        KeyEvent.META_CTRL_ON)) {
                    return top(widget, buffer);
                }
                break;

            case KeyEvent.KEYCODE_MOVE_END:
                if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
                    return end(widget, buffer);
                } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
                        KeyEvent.META_CTRL_ON)) {
                    return bottom(widget, buffer);
                }
                break;
        }
@@ -349,6 +361,16 @@ public class BaseMovementMethod implements MovementMethod {
        return false;
    }

    /** {@hide} */
    protected boolean leftWord(TextView widget, Spannable buffer) {
        return false;
    }

    /** {@hide} */
    protected boolean rightWord(TextView widget, Spannable buffer) {
        return false;
    }

    /**
     * Performs a home movement action.
     * Moves the cursor or scrolls to the start of the line or to the top of the