Merge "Support Ctrl-based EditText movement."
This commit is contained in:
95
core/java/android/text/CharSequenceIterator.java
Normal file
95
core/java/android/text/CharSequenceIterator.java
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -38,7 +43,7 @@ public class Selection {
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the offset of the selection edge or cursor, or -1 if
|
||||
* there is no selection or cursor.
|
||||
@@ -57,7 +62,7 @@ public class Selection {
|
||||
// private static int pin(int value, int min, int max) {
|
||||
// return value < min ? 0 : (value > max ? max : value);
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Set the selection anchor to <code>start</code> and the selection edge
|
||||
* to <code>stop</code>.
|
||||
@@ -69,7 +74,7 @@ public class Selection {
|
||||
|
||||
int ostart = getSelectionStart(text);
|
||||
int oend = getSelectionEnd(text);
|
||||
|
||||
|
||||
if (ostart != start || oend != stop) {
|
||||
text.setSpan(SELECTION_START, start, start,
|
||||
Spanned.SPAN_POINT_POINT|Spanned.SPAN_INTERMEDIATE);
|
||||
@@ -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);
|
||||
@@ -419,7 +460,7 @@ public class Selection {
|
||||
|
||||
private static final class START implements NoCopySpan { }
|
||||
private static final class END implements NoCopySpan { }
|
||||
|
||||
|
||||
/*
|
||||
* Public constants
|
||||
*/
|
||||
|
||||
@@ -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) {
|
||||
@@ -220,7 +244,7 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
|
||||
boolean cap = isSelecting(buffer);
|
||||
if (cap) {
|
||||
int offset = widget.getOffset((int) event.getX(), (int) event.getY());
|
||||
|
||||
|
||||
buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
|
||||
|
||||
// Disallow intercepting of the touch events, so that
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user