blob: 04086737523cbdc874fc078989da31b5447bded3 [file] [log] [blame]
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.text.method;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import android.widget.TextView.CursorController;
// XXX this doesn't extend MetaKeyKeyListener because the signatures
// don't match. Need to figure that out. Meanwhile the meta keys
// won't work in fields that don't take input.
public class ArrowKeyMovementMethod implements MovementMethod {
/**
* An optional controller for the cursor.
* Use {@link #setCursorController(CursorController)} to set this field.
*/
private CursorController mCursorController;
private boolean isCap(Spannable buffer) {
return ((MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_SHIFT_ON) == 1) ||
(MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0));
}
private boolean isAlt(Spannable buffer) {
return MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1;
}
private boolean up(TextView widget, Spannable buffer) {
boolean cap = isCap(buffer);
boolean alt = isAlt(buffer);
Layout layout = widget.getLayout();
if (cap) {
if (alt) {
Selection.extendSelection(buffer, 0);
return true;
} else {
return Selection.extendUp(buffer, layout);
}
} else {
if (alt) {
Selection.setSelection(buffer, 0);
return true;
} else {
return Selection.moveUp(buffer, layout);
}
}
}
private boolean down(TextView widget, Spannable buffer) {
boolean cap = isCap(buffer);
boolean alt = isAlt(buffer);
Layout layout = widget.getLayout();
if (cap) {
if (alt) {
Selection.extendSelection(buffer, buffer.length());
return true;
} else {
return Selection.extendDown(buffer, layout);
}
} else {
if (alt) {
Selection.setSelection(buffer, buffer.length());
return true;
} else {
return Selection.moveDown(buffer, layout);
}
}
}
private boolean left(TextView widget, Spannable buffer) {
boolean cap = isCap(buffer);
boolean alt = isAlt(buffer);
Layout layout = widget.getLayout();
if (cap) {
if (alt) {
return Selection.extendToLeftEdge(buffer, layout);
} else {
return Selection.extendLeft(buffer, layout);
}
} else {
if (alt) {
return Selection.moveToLeftEdge(buffer, layout);
} else {
return Selection.moveLeft(buffer, layout);
}
}
}
private boolean right(TextView widget, Spannable buffer) {
boolean cap = isCap(buffer);
boolean alt = isAlt(buffer);
Layout layout = widget.getLayout();
if (cap) {
if (alt) {
return Selection.extendToRightEdge(buffer, layout);
} else {
return Selection.extendRight(buffer, layout);
}
} else {
if (alt) {
return Selection.moveToRightEdge(buffer, layout);
} else {
return Selection.moveRight(buffer, layout);
}
}
}
public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
if (executeDown(widget, buffer, keyCode)) {
MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
MetaKeyKeyListener.resetLockedMeta(buffer);
return true;
}
return false;
}
private boolean executeDown(TextView widget, Spannable buffer, int keyCode) {
boolean handled = false;
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
handled |= up(widget, buffer);
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
handled |= down(widget, buffer);
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
handled |= left(widget, buffer);
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
handled |= right(widget, buffer);
break;
case KeyEvent.KEYCODE_DPAD_CENTER:
if ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) &&
(widget.showContextMenu())) {
handled = true;
}
}
if (handled) {
MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
MetaKeyKeyListener.resetLockedMeta(buffer);
}
return handled;
}
public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
return false;
}
public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
int code = event.getKeyCode();
if (code != KeyEvent.KEYCODE_UNKNOWN && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
int repeat = event.getRepeatCount();
boolean handled = false;
while ((--repeat) > 0) {
handled |= executeDown(view, text, code);
}
return handled;
}
return false;
}
public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) {
if (mCursorController != null) {
mCursorController.hide();
}
return false;
}
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
if (mCursorController != null) {
return onTouchEventCursor(widget, buffer, event);
} else {
return onTouchEventStandard(widget, buffer, event);
}
}
private boolean onTouchEventStandard(TextView widget, Spannable buffer, MotionEvent event) {
int initialScrollX = -1, initialScrollY = -1;
if (event.getAction() == MotionEvent.ACTION_UP) {
initialScrollX = Touch.getInitialScrollX(widget, buffer);
initialScrollY = Touch.getInitialScrollY(widget, buffer);
}
boolean handled = Touch.onTouchEvent(widget, buffer, event);
if (widget.isFocused() && !widget.didTouchFocusSelect()) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
boolean cap = isCap(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
// 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 (event.getAction() == MotionEvent.ACTION_MOVE) {
boolean cap = isCap(buffer);
if (cap && 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
// Turn long press off while we're selecting. User needs to
// re-tap on the selection to enable long press
widget.cancelLongPress();
// Update selection as we're moving the selection area.
// Get the current touch position
int offset = widget.getOffset((int) event.getX(), (int) event.getY());
Selection.extendSelection(buffer, offset);
return true;
}
} else if (event.getAction() == MotionEvent.ACTION_UP) {
// If we have scrolled, then the up shouldn't move the cursor,
// but we do need to make sure the cursor is still visible at
// the current scroll offset to avoid the scroll jumping later
// to show it.
if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) ||
(initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
widget.moveCursorToVisibleOffset();
return true;
}
int offset = widget.getOffset((int) event.getX(), (int) event.getY());
if (isCap(buffer)) {
buffer.removeSpan(LAST_TAP_DOWN);
Selection.extendSelection(buffer, offset);
} else {
Selection.setSelection(buffer, offset);
}
MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
MetaKeyKeyListener.resetLockedMeta(buffer);
return true;
}
}
return handled;
}
private boolean onTouchEventCursor(TextView widget, Spannable buffer, MotionEvent event) {
if (widget.isFocused() && !widget.didTouchFocusSelect()) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
widget.cancelLongPress();
// Offset the current touch position (from controller to cursor)
final float x = event.getX() + mCursorController.getOffsetX();
final float y = event.getY() + mCursorController.getOffsetY();
mCursorController.updatePosition((int) x, (int) y);
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mCursorController = null;
return true;
}
}
return false;
}
/**
* Defines the cursor controller.
*
* When set, this object can be used to handle touch events, that can be translated into cursor
* updates.
*
* {@link MotionEvent#ACTION_MOVE} events will call back the
* {@link CursorController#updatePosition(int, int)} controller's method, passing the current
* finger coordinates (offset by {@link CursorController#getOffsetX()} and
* {@link CursorController#getOffsetY()}) as parameters.
*
* When the gesture is finished (on a {@link MotionEvent#ACTION_UP} or
* {@link MotionEvent#ACTION_CANCEL} event), the controller is reset to null.
*
* @param cursorController A cursor controller implementation
*/
public void setCursorController(CursorController cursorController) {
mCursorController = cursorController;
}
public boolean canSelectArbitrarily() {
return true;
}
public void initialize(TextView widget, Spannable text) {
Selection.setSelection(text, 0);
}
public void onTakeFocus(TextView view, Spannable text, int dir) {
if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
if (view.getLayout() == null) {
// This shouldn't be null, but do something sensible if it is.
Selection.setSelection(text, text.length());
}
} else {
Selection.setSelection(text, text.length());
}
}
public static MovementMethod getInstance() {
if (sInstance == null) {
sInstance = new ArrowKeyMovementMethod();
}
return sInstance;
}
private static final Object LAST_TAP_DOWN = new Object();
private static ArrowKeyMovementMethod sInstance;
}