diff options
author | Mady Mellor <madym@google.com> | 2015-04-15 14:23:26 -0700 |
---|---|---|
committer | Mady Mellor <madym@google.com> | 2015-05-07 21:05:36 +0000 |
commit | 6c7b4ad690fe5c22c01ad79a232e567e835f676d (patch) | |
tree | c2e26b42897542c29c2ade0243fe75c348237460 /core | |
parent | 98ae4dfbbc2bf5d61786674dd75127eb4ca238e9 (diff) | |
download | frameworks_base-6c7b4ad690fe5c22c01ad79a232e567e835f676d.zip frameworks_base-6c7b4ad690fe5c22c01ad79a232e567e835f676d.tar.gz frameworks_base-6c7b4ad690fe5c22c01ad79a232e567e835f676d.tar.bz2 |
Consider punctuation treatment when selecting text.
This treats punctuation as "words", so groups of punctuation will be
selected together, i.e. "Hmm... yay!!!" would be treated as four words:
Hmm-...-yay-!!!
Bug: 19950062
Change-Id: Ic9514c524092734fad1abdf19a736bfa1406157b
Diffstat (limited to 'core')
-rw-r--r-- | core/java/android/text/method/WordIterator.java | 39 | ||||
-rw-r--r-- | core/java/android/widget/Editor.java | 124 |
2 files changed, 129 insertions, 34 deletions
diff --git a/core/java/android/text/method/WordIterator.java b/core/java/android/text/method/WordIterator.java index 11226a9..c4dc5ed 100644 --- a/core/java/android/text/method/WordIterator.java +++ b/core/java/android/text/method/WordIterator.java @@ -95,6 +95,45 @@ public class WordIterator implements Selection.PositionIterator { } while (true); } + /** {@inheritDoc} */ + public boolean isBoundary(int offset) { + int shiftedOffset = offset - mOffsetShift; + checkOffsetIsValid(shiftedOffset); + return mIterator.isBoundary(shiftedOffset); + } + + /** + * Returns the position of next boundary after the given offset. Returns + * {@code DONE} if there is no boundary after the given offset. + * + * @param offset the given start position to search from. + * @return the position of the last boundary preceding the given offset. + */ + public int nextBoundary(int offset) { + int shiftedOffset = offset - mOffsetShift; + shiftedOffset = mIterator.following(shiftedOffset); + if (shiftedOffset == BreakIterator.DONE) { + return BreakIterator.DONE; + } + return shiftedOffset + mOffsetShift; + } + + /** + * Returns the position of boundary preceding the given offset or + * {@code DONE} if the given offset specifies the starting position. + * + * @param offset the given start position to search from. + * @return the position of the last boundary preceding the given offset. + */ + public int prevBoundary(int offset) { + int shiftedOffset = offset - mOffsetShift; + shiftedOffset = mIterator.preceding(shiftedOffset); + if (shiftedOffset == BreakIterator.DONE) { + return BreakIterator.DONE; + } + return shiftedOffset + mOffsetShift; + } + /** If <code>offset</code> is within a word, returns the index of the first character of that * word, otherwise returns BreakIterator.DONE. * diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 712fdba..4a7d616 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -688,44 +688,101 @@ public class Editor { private int getWordStart(int offset) { // FIXME - For this and similar methods we're not doing anything to check if there's // a LocaleSpan in the text, this may be something we should try handling or checking for. - int retOffset = getWordIteratorWithText().getBeginning(offset); - if (retOffset == BreakIterator.DONE) retOffset = offset; + int retOffset = getWordIteratorWithText().prevBoundary(offset); + if (isPunctBoundaryBehind(retOffset, true /* isStart */)) { + // If we're on a punctuation boundary we should continue to get the + // previous offset until we're not longer on a punctuation boundary. + retOffset = getWordIteratorWithText().prevBoundary(retOffset); + while (!isPunctBoundaryBehind(retOffset, false /* isStart */) + && retOffset != BreakIterator.DONE) { + retOffset = getWordIteratorWithText().prevBoundary(retOffset); + } + } + if (retOffset == BreakIterator.DONE) { + return offset; + } return retOffset; } - private int getWordEnd(int offset, boolean includePunctuation) { - int retOffset = getWordIteratorWithText().getEnd(offset); + private int getWordEnd(int offset) { + int retOffset = getWordIteratorWithText().nextBoundary(offset); + if (isPunctBoundaryForward(retOffset, true /* isStart */)) { + // If we're on a punctuation boundary we should continue to get the + // next offset until we're no longer on a punctuation boundary. + retOffset = getWordIteratorWithText().nextBoundary(retOffset); + while (!isPunctBoundaryForward(retOffset, false /* isStart */) + && retOffset != BreakIterator.DONE) { + retOffset = getWordIteratorWithText().nextBoundary(retOffset); + } + } if (retOffset == BreakIterator.DONE) { - retOffset = offset; - } else if (includePunctuation) { - retOffset = handlePunctuation(retOffset); + return offset; } return retOffset; } - private boolean isEndBoundary(int offset) { - int thisEnd = getWordEnd(offset, false); - return offset == thisEnd; - } + /** + * Checks for punctuation boundaries for the provided offset and the + * previous character. + * + * @param offset The offset to check from. + * @param isStart Whether the boundary being checked for is at the start or + * end of a punctuation sequence. + * @return Whether this is a punctuation boundary. + */ + private boolean isPunctBoundaryBehind(int offset, boolean isStart) { + CharSequence text = mTextView.getText(); + if (offset == BreakIterator.DONE || offset > text.length() || offset == 0) { + return false; + } + int cp = Character.codePointAt(text, offset); + int prevCp = Character.codePointBefore(text, offset); - private boolean isStartBoundary(int offset) { - int thisStart = getWordStart(offset); - return thisStart == offset; + if (isPunctuation(cp)) { + // If it's the start, the current cp and the prev cp are + // punctuation. If it's at the end of a punctuation sequence the + // current is punctuation and the prev is not. + return isStart ? isPunctuation(prevCp) : !isPunctuation(prevCp); + } + return false; } - private int handlePunctuation(int offset) { - // FIXME - Check with UX how repeated ending punctuation should be handled. - // FIXME - Check with UX if / how we would handle non sentence ending characters. - // FIXME - Consider punctuation in different languages. + /** + * Checks for punctuation boundaries for the provided offset and the next + * character. + * + * @param offset The offset to check from. + * @param isStart Whether the boundary being checked for is at the start or + * end of a punctuation sequence. + * @return Whether this is a punctuation boundary. + */ + private boolean isPunctBoundaryForward(int offset, boolean isStart) { CharSequence text = mTextView.getText(); - if (offset < text.length()) { - int c = Character.codePointAt(text, offset); - if (c == 0x002e /* period */|| c == 0x003f /* question mark */ - || c == 0x0021 /* exclamation mark */) { - offset = Character.offsetByCodePoints(text, offset, 1); - } + if (offset == BreakIterator.DONE || offset > text.length() || offset == 0) { + return false; + } + int cp = Character.codePointBefore(text, offset); + int nextCpOffset = Math.min(offset + Character.charCount(cp), text.length() - 1); + int nextCp = Character.codePointBefore(text, nextCpOffset); + + if (isPunctuation(cp)) { + // If it's the start, the current cp and the next cp are + // punctuation. If it's at the end of a punctuation sequence the + // current is punctuation and the next is not. + return isStart ? isPunctuation(nextCp) : !isPunctuation(nextCp); } - return offset; + return false; + } + + private boolean isPunctuation(int cp) { + int type = Character.getType(cp); + return (type == Character.CONNECTOR_PUNCTUATION || + type == Character.DASH_PUNCTUATION || + type == Character.END_PUNCTUATION || + type == Character.FINAL_QUOTE_PUNCTUATION || + type == Character.INITIAL_QUOTE_PUNCTUATION || + type == Character.OTHER_PUNCTUATION || + type == Character.START_PUNCTUATION); } /** @@ -3909,7 +3966,7 @@ public class Editor { boolean positionCursor = false; int offset = trueOffset; - int end = getWordEnd(offset, true); + int end = getWordEnd(offset); int start = getWordStart(offset); if (offset < mPreviousOffset) { @@ -3925,7 +3982,7 @@ public class Editor { } } mTouchWordOffset = Math.max(trueOffset - offset, 0); - mInWord = !isStartBoundary(offset); + mInWord = !getWordIteratorWithText().isBoundary(offset); positionCursor = true; } else if (offset - mTouchWordOffset > mPreviousOffset) { // User is shrinking the selection. @@ -3934,7 +3991,7 @@ public class Editor { offset = end; } offset -= mTouchWordOffset; - mInWord = !isEndBoundary(offset); + mInWord = !getWordIteratorWithText().isBoundary(offset); positionCursor = true; } @@ -4011,8 +4068,7 @@ public class Editor { int offset = trueOffset; boolean positionCursor = false; - - int end = getWordEnd(offset, true); + int end = getWordEnd(offset); int start = getWordStart(offset); if (offset > mPreviousOffset) { @@ -4028,7 +4084,7 @@ public class Editor { } } mTouchWordOffset = Math.max(offset - trueOffset, 0); - mInWord = !isEndBoundary(offset); + mInWord = !getWordIteratorWithText().isBoundary(offset); positionCursor = true; } else if (offset + mTouchWordOffset < mPreviousOffset) { // User is shrinking the selection. @@ -4038,7 +4094,7 @@ public class Editor { } offset += mTouchWordOffset; positionCursor = true; - mInWord = !isStartBoundary(offset); + mInWord = !getWordIteratorWithText().isBoundary(offset); } if (positionCursor) { @@ -4318,7 +4374,7 @@ public class Editor { // We don't start "dragging" until the user is past the initial word that // gets selected on long press. int firstWordStart = getWordStart(mStartOffset); - int firstWordEnd = getWordEnd(mStartOffset, false); + int firstWordEnd = getWordEnd(mStartOffset); if (offset > firstWordEnd || offset < firstWordStart) { // Basically the goal in the below code is to have the highlight be @@ -4365,7 +4421,7 @@ public class Editor { // Need to adjust start offset based on direction of movement. int newStart = mStartOffset < offset ? getWordStart(mStartOffset) - : getWordEnd(mStartOffset, true); + : getWordEnd(mStartOffset); Selection.setSelection((Spannable) mTextView.getText(), newStart, offset); } |