summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorMady Mellor <madym@google.com>2015-04-15 14:23:26 -0700
committerMady Mellor <madym@google.com>2015-05-07 21:05:36 +0000
commit6c7b4ad690fe5c22c01ad79a232e567e835f676d (patch)
treec2e26b42897542c29c2ade0243fe75c348237460 /core
parent98ae4dfbbc2bf5d61786674dd75127eb4ca238e9 (diff)
downloadframeworks_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.java39
-rw-r--r--core/java/android/widget/Editor.java124
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);
}