Add support for mouse-based text selection.

Incorporate patch from Logitech (donated under AOSP license) to the
framework to add mouse-based text selection to ArrowKeyMovementMethod.

Bug: 14652753

Change-Id: Iab264bb954b72ccedfada763eba8f13ef37a4578
This commit is contained in:
Sujith Ramakrishnan
2014-05-19 15:32:13 -07:00
parent 7de699fb68
commit cc32bd83e4
4 changed files with 98 additions and 17 deletions

View File

@@ -32297,6 +32297,7 @@ package android.view {
method public final float getY();
method public final float getY(int);
method public final float getYPrecision();
method public final boolean isButtonPressed(int);
method public static android.view.MotionEvent obtain(long, long, int, int, android.view.MotionEvent.PointerProperties[], android.view.MotionEvent.PointerCoords[], int, int, float, float, int, int, int, int);
method public static deprecated android.view.MotionEvent obtain(long, long, int, int, int[], android.view.MotionEvent.PointerCoords[], int, float, float, int, int, int, int);
method public static android.view.MotionEvent obtain(long, long, int, float, float, float, float, int, float, float, int, int);

View File

@@ -20,6 +20,7 @@ import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -221,11 +222,16 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
return lineEnd(widget, buffer);
}
private static boolean isTouchSelecting(boolean isMouse, Spannable buffer) {
return isMouse ? Touch.isActivelySelecting(buffer) : isSelecting(buffer);
}
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
int initialScrollX = -1;
int initialScrollY = -1;
final int action = event.getAction();
final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
if (action == MotionEvent.ACTION_UP) {
initialScrollX = Touch.getInitialScrollX(widget, buffer);
@@ -236,19 +242,30 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
if (widget.isFocused() && !widget.didTouchFocusSelect()) {
if (action == MotionEvent.ACTION_DOWN) {
if (isSelecting(buffer)) {
int offset = widget.getOffsetForPosition(event.getX(), event.getY());
buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
// Disallow intercepting of the touch events, so that
// users can scroll and select at the same time.
// without this, users would get booted out of select
// mode once the view detected it needed to scroll.
widget.getParent().requestDisallowInterceptTouchEvent(true);
}
// Capture the mouse pointer down location to ensure selection starts
// right under the mouse (and is not influenced by cursor location).
// The code below needs to run for mouse events.
// For touch events, the code should run only when selection is active.
if (isMouse || isTouchSelecting(isMouse, buffer)) {
int offset = widget.getOffsetForPosition(event.getX(), event.getY());
buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
// Disallow intercepting of the touch events, so that
// users can scroll and select at the same time.
// without this, users would get booted out of select
// mode once the view detected it needed to scroll.
widget.getParent().requestDisallowInterceptTouchEvent(true);
}
} else if (action == MotionEvent.ACTION_MOVE) {
if (isSelecting(buffer) && handled) {
// Cursor can be active at any location in the text while mouse pointer can start
// selection from a totally different location. Use LAST_TAP_DOWN span to ensure
// text selection will start from mouse pointer location.
if (isMouse && Touch.isSelectionStarted(buffer)) {
int offset = buffer.getSpanStart(LAST_TAP_DOWN);
Selection.setSelection(buffer, offset);
}
if (isTouchSelecting(isMouse, buffer) && handled) {
// Before selecting, make sure we've moved out of the "slop".
// handled will be true, if we're in select mode AND we're
// OUT of the slop
@@ -277,7 +294,7 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
}
int offset = widget.getOffsetForPosition(event.getX(), event.getY());
if (isSelecting(buffer)) {
if (isTouchSelecting(isMouse, buffer)) {
buffer.removeSpan(LAST_TAP_DOWN);
Selection.extendSelection(buffer, offset);
}
@@ -288,7 +305,6 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
return true;
}
}
return handled;
}

View File

@@ -119,12 +119,18 @@ public class Touch {
ds = buffer.getSpans(0, buffer.length(), DragState.class);
if (ds.length > 0) {
ds[0].mIsSelectionStarted = false;
if (ds[0].mFarEnough == false) {
int slop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop();
if (Math.abs(event.getX() - ds[0].mX) >= slop ||
Math.abs(event.getY() - ds[0].mY) >= slop) {
ds[0].mFarEnough = true;
if (event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
ds[0].mIsActivelySelecting = true;
ds[0].mIsSelectionStarted = true;
}
}
}
@@ -135,9 +141,14 @@ public class Touch {
MetaKeyKeyListener.META_SHIFT_ON) == 1
|| MetaKeyKeyListener.getMetaState(buffer,
MetaKeyKeyListener.META_SELECTING) != 0;
if (!event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
ds[0].mIsActivelySelecting = false;
}
float dx;
float dy;
if (cap) {
if (cap && event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
// if we're selecting, we want the scroll to go in
// the direction of the drag
dx = event.getX() - ds[0].mX;
@@ -157,11 +168,13 @@ public class Touch {
ny = Math.min(ny, layout.getHeight() - (widget.getHeight() - padding));
ny = Math.max(ny, 0);
int oldX = widget.getScrollX();
int oldY = widget.getScrollY();
scrollTo(widget, layout, nx, ny);
if (!event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
scrollTo(widget, layout, nx, ny);
}
// If we actually scrolled, then cancel the up action.
if (oldX != widget.getScrollX() || oldY != widget.getScrollY()) {
@@ -194,6 +207,37 @@ public class Touch {
return ds.length > 0 ? ds[0].mScrollY : -1;
}
/**
* Checks if selection is still active.
* This is useful for extending Selection span on buffer.
* @param buffer The text buffer.
* @return true if buffer has been marked for selection.
*
* @hide
*/
static boolean isActivelySelecting(Spannable buffer) {
DragState[] ds;
ds = buffer.getSpans(0, buffer.length(), DragState.class);
return ds.length > 0 && ds[0].mIsActivelySelecting;
}
/**
* Checks if selection has begun (are we out of slop?).
* Note: DragState.mIsSelectionStarted goes back to false with the very next event.
* This is useful for starting Selection span on buffer.
* @param buffer The text buffer.
* @return true if selection has started on the buffer.
*
* @hide
*/
static boolean isSelectionStarted(Spannable buffer) {
DragState[] ds;
ds = buffer.getSpans(0, buffer.length(), DragState.class);
return ds.length > 0 && ds[0].mIsSelectionStarted;
}
private static class DragState implements NoCopySpan {
public float mX;
public float mY;
@@ -201,6 +245,8 @@ public class Touch {
public int mScrollY;
public boolean mFarEnough;
public boolean mUsed;
public boolean mIsActivelySelecting;
public boolean mIsSelectionStarted;
public DragState(float x, float y, int scrollX, int scrollY) {
mX = x;

View File

@@ -3131,6 +3131,24 @@ public final class MotionEvent extends InputEvent implements Parcelable {
return symbolicName != null ? symbolicName : Integer.toString(toolType);
}
/**
* Checks if a mouse or stylus button (or combination of buttons) is pressed.
* @param button Button (or combination of buttons).
* @return True if specified buttons are pressed.
*
* @see #BUTTON_PRIMARY
* @see #BUTTON_SECONDARY
* @see #BUTTON_TERTIARY
* @see #BUTTON_FORWARD
* @see #BUTTON_BACK
*/
public final boolean isButtonPressed(int button) {
if (button == 0) {
return false;
}
return (getButtonState() & button) == button;
}
public static final Parcelable.Creator<MotionEvent> CREATOR
= new Parcelable.Creator<MotionEvent>() {
public MotionEvent createFromParcel(Parcel in) {