diff options
author | Jeff Sharkey <jsharkey@android.com> | 2011-03-21 16:40:23 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2011-03-21 18:20:49 -0700 |
commit | e982dfc1bae36620f67371efc7b0a0f8adc9450d (patch) | |
tree | eacbafbdd7542c0d8bcce9bfafb4ae69bad0dc15 /core/java/android/text/method | |
parent | b57816957a1105ca42e2abca7dbc2203f24b4e2a (diff) | |
download | frameworks_base-e982dfc1bae36620f67371efc7b0a0f8adc9450d.zip frameworks_base-e982dfc1bae36620f67371efc7b0a0f8adc9450d.tar.gz frameworks_base-e982dfc1bae36620f67371efc7b0a0f8adc9450d.tar.bz2 |
Support Ctrl-based EditText movement.
EditText now listens for Ctrl+left/right to jump through text at word
boundaries. It also listens for Ctrl+home/end to move to start/end of
the full text. This emulates behavior found in desktop text editors.
Bug: 4081964
Change-Id: I98bd19c0d8707357847db3466648a83fd774dbaf
Diffstat (limited to 'core/java/android/text/method')
-rw-r--r-- | core/java/android/text/method/ArrowKeyMovementMethod.java | 125 | ||||
-rw-r--r-- | core/java/android/text/method/BaseMovementMethod.java | 22 |
2 files changed, 145 insertions, 2 deletions
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index a61ff13..80c0106 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -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; diff --git a/core/java/android/text/method/BaseMovementMethod.java b/core/java/android/text/method/BaseMovementMethod.java index 94c6ed0..f554b90 100644 --- a/core/java/android/text/method/BaseMovementMethod.java +++ b/core/java/android/text/method/BaseMovementMethod.java @@ -164,6 +164,9 @@ public class BaseMovementMethod implements MovementMethod { 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); } @@ -173,6 +176,9 @@ public class BaseMovementMethod implements MovementMethod { 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 |