diff options
| -rw-r--r-- | core/java/android/widget/Editor.java | 133 | ||||
| -rw-r--r-- | core/java/android/widget/TextView.java | 22 |
2 files changed, 135 insertions, 20 deletions
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index ae9b3c456c9e..eb767882f348 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -231,6 +231,7 @@ public class Editor { boolean mCreatedWithASelection; boolean mDoubleTap = false; + boolean mTripleClick = false; private Runnable mInsertionActionModeRunnable; @@ -770,20 +771,12 @@ public class Editor { return retOffset; } - /** - * Adjusts selection to the word under last touch offset. Return true if the operation was - * successfully performed. - */ - private boolean selectCurrentWord() { - if (!mTextView.canSelectText()) { - return false; - } - + private boolean needsToSelectAllToSelectWordOrParagraph() { if (mTextView.hasPasswordTransformationMethod()) { // Always select all on a password field. // Cut/copy menu entries are not available for passwords, but being able to select all // is however useful to delete or paste to replace the entire content. - return mTextView.selectAllText(); + return true; } int inputType = mTextView.getInputType(); @@ -798,6 +791,21 @@ public class Editor { variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS || variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS || variation == InputType.TYPE_TEXT_VARIATION_FILTER) { + return true; + } + return false; + } + + /** + * Adjusts selection to the word under last touch offset. Return true if the operation was + * successfully performed. + */ + private boolean selectCurrentWord() { + if (!mTextView.canSelectText()) { + return false; + } + + if (needsToSelectAllToSelectWordOrParagraph()) { return mTextView.selectAllText(); } @@ -840,6 +848,63 @@ public class Editor { return selectionEnd > selectionStart; } + /** + * Adjusts selection to the paragraph under last touch offset. Return true if the operation was + * successfully performed. + */ + private boolean selectCurrentParagraph() { + if (!mTextView.canSelectText()) { + return false; + } + + if (needsToSelectAllToSelectWordOrParagraph()) { + return mTextView.selectAllText(); + } + + long lastTouchOffsets = getLastTouchOffsets(); + final int minLastTouchOffset = TextUtils.unpackRangeStartFromLong(lastTouchOffsets); + final int maxLastTouchOffset = TextUtils.unpackRangeEndFromLong(lastTouchOffsets); + + final long paragraphsRange = getParagraphsRange(minLastTouchOffset, maxLastTouchOffset); + final int start = TextUtils.unpackRangeStartFromLong(paragraphsRange); + final int end = TextUtils.unpackRangeEndFromLong(paragraphsRange); + if (start < end) { + Selection.setSelection((Spannable) mTextView.getText(), start, end); + return true; + } + return false; + } + + /** + * Get the minimum range of paragraphs that contains startOffset and endOffset. + */ + private long getParagraphsRange(int startOffset, int endOffset) { + final Layout layout = mTextView.getLayout(); + if (layout == null) { + return TextUtils.packRangeInLong(-1, -1); + } + final CharSequence text = mTextView.getText(); + int minLine = layout.getLineForOffset(startOffset); + // Search paragraph start. + while (minLine > 0) { + final int prevLineEndOffset = layout.getLineEnd(minLine - 1); + if (text.charAt(prevLineEndOffset - 1) == '\n') { + break; + } + minLine--; + } + int maxLine = layout.getLineForOffset(endOffset); + // Search paragraph end. + while (maxLine < layout.getLineCount() - 1) { + final int lineEndOffset = layout.getLineEnd(maxLine); + if (text.charAt(lineEndOffset - 1) == '\n') { + break; + } + maxLine++; + } + return TextUtils.packRangeInLong(layout.getLineStart(minLine), layout.getLineEnd(maxLine)); + } + void onLocaleChanged() { // Will be re-created on demand in getWordIterator with the proper new locale mWordIterator = null; @@ -3911,13 +3976,13 @@ public class Editor { // Cancel the single tap delayed runnable. if (mInsertionActionModeRunnable != null - && (mDoubleTap || isCursorInsideEasyCorrectionSpan())) { + && (mDoubleTap || mTripleClick || isCursorInsideEasyCorrectionSpan())) { mTextView.removeCallbacks(mInsertionActionModeRunnable); } // Prepare and schedule the single tap runnable to run exactly after the double tap // timeout has passed. - if (!mDoubleTap && !isCursorInsideEasyCorrectionSpan() + if (!mDoubleTap && !mTripleClick && !isCursorInsideEasyCorrectionSpan() && (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION)) { if (mTextActionMode == null) { if (mInsertionActionModeRunnable == null) { @@ -4485,6 +4550,8 @@ public class Editor { private static final int DRAG_ACCELERATOR_MODE_CHARACTER = 1; // Word based selection by dragging. Enabled after long pressing or double tapping. private static final int DRAG_ACCELERATOR_MODE_WORD = 2; + // Paragraph based selection by dragging. Enabled after mouse triple click. + private static final int DRAG_ACCELERATOR_MODE_PARAGRAPH = 3; SelectionModifierCursorController() { resetTouchOffsets(); @@ -4569,7 +4636,7 @@ public class Editor { // Double tap detection if (mGestureStayedInTapRegion) { - if (mDoubleTap) { + if (mDoubleTap || mTripleClick) { final float deltaX = eventX - mDownPositionX; final float deltaY = eventY - mDownPositionY; final float distanceSquared = deltaX * deltaX + deltaY * deltaY; @@ -4581,7 +4648,11 @@ public class Editor { distanceSquared < doubleTapSlop * doubleTapSlop; if (stayedInArea && (isMouse || isPositionOnText(eventX, eventY))) { - selectCurrentWordAndStartDrag(); + if (mDoubleTap) { + selectCurrentWordAndStartDrag(); + } else if (mTripleClick) { + selectCurrentParagraphAndStartDrag(); + } mDiscardNextActionUp = true; } } @@ -4690,10 +4761,32 @@ public class Editor { case DRAG_ACCELERATOR_MODE_WORD: updateWordBasedSelection(event); break; + case DRAG_ACCELERATOR_MODE_PARAGRAPH: + updateParagraphBasedSelection(event); + break; } } } + /** + * If the TextView allows text selection, selects the current paragraph and starts a drag. + * + * @return true if the drag was started. + */ + private boolean selectCurrentParagraphAndStartDrag() { + if (mInsertionActionModeRunnable != null) { + mTextView.removeCallbacks(mInsertionActionModeRunnable); + } + if (mTextActionMode != null) { + mTextActionMode.finish(); + } + if (!selectCurrentParagraph()) { + return false; + } + enterDrag(SelectionModifierCursorController.DRAG_ACCELERATOR_MODE_PARAGRAPH); + return true; + } + private void updateCharacterBasedSelection(MotionEvent event) { final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY()); Selection.setSelection((Spannable) mTextView.getText(), mStartOffset, offset); @@ -4754,6 +4847,18 @@ public class Editor { Selection.setSelection((Spannable) mTextView.getText(), startOffset, offset); } + + private void updateParagraphBasedSelection(MotionEvent event) { + final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY()); + + final int start = Math.min(offset, mStartOffset); + final int end = Math.max(offset, mStartOffset); + final long paragraphsRange = getParagraphsRange(start, end); + final int selectionStart = TextUtils.unpackRangeStartFromLong(paragraphsRange); + final int selectionEnd = TextUtils.unpackRangeEndFromLong(paragraphsRange); + Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd); + } + /** * @param event */ diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 94b75b73bb7d..61df7362b090 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -17,7 +17,6 @@ package android.widget; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; - import android.R; import android.annotation.ColorInt; import android.annotation.DrawableRes; @@ -115,6 +114,7 @@ import android.view.Choreographer; import android.view.DragEvent; import android.view.Gravity; import android.view.HapticFeedbackConstants; +import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; @@ -8408,12 +8408,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int action = event.getActionMasked(); if (mEditor != null && action == MotionEvent.ACTION_DOWN) { - // Detect double tap and inform the Editor. - if (mFirstTouch && (SystemClock.uptimeMillis() - mLastTouchUpTime) <= - ViewConfiguration.getDoubleTapTimeout()) { - mEditor.mDoubleTap = true; - mFirstTouch = false; + final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE); + // Detect double tap and triple click and inform the Editor. + if ((mFirstTouch || (mEditor.mDoubleTap && isMouse)) + && (SystemClock.uptimeMillis() - mLastTouchUpTime) <= + ViewConfiguration.getDoubleTapTimeout()) { + if (mFirstTouch) { + mEditor.mTripleClick = false; + mEditor.mDoubleTap = true; + mFirstTouch = false; + } else { + mEditor.mTripleClick = true; + mEditor.mDoubleTap = false; + mFirstTouch = false; + } } else { + mEditor.mTripleClick = false; mEditor.mDoubleTap = false; mFirstTouch = true; } |