Loading core/java/android/text/CharSequenceIterator.java 0 → 100644 +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(); } } core/java/android/text/Selection.java +45 −4 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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); Loading core/java/android/text/method/ArrowKeyMovementMethod.java +123 −2 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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); Loading @@ -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) { Loading Loading @@ -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; Loading core/java/android/text/method/BaseMovementMethod.java +22 −0 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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); Loading Loading @@ -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; } Loading Loading @@ -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 Loading Loading
core/java/android/text/CharSequenceIterator.java 0 → 100644 +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(); } }
core/java/android/text/Selection.java +45 −4 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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); Loading
core/java/android/text/method/ArrowKeyMovementMethod.java +123 −2 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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); Loading @@ -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) { Loading Loading @@ -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; Loading
core/java/android/text/method/BaseMovementMethod.java +22 −0 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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); Loading Loading @@ -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; } Loading Loading @@ -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 Loading