summaryrefslogtreecommitdiffstats
path: root/core/java/android/text/method
diff options
context:
space:
mode:
authorJeff Brown <jeffbrown@google.com>2010-12-17 18:33:02 -0800
committerJeff Brown <jeffbrown@google.com>2010-12-21 18:03:08 -0800
commit67b6ab72ae96a9f2be929de2c32c110df5390fdd (patch)
tree010e0fe89f0659721e24562ec2a346801222f8d0 /core/java/android/text/method
parentd1e8e94368d8b6ac245fdcee227c6349654446ff (diff)
downloadframeworks_base-67b6ab72ae96a9f2be929de2c32c110df5390fdd.zip
frameworks_base-67b6ab72ae96a9f2be929de2c32c110df5390fdd.tar.gz
frameworks_base-67b6ab72ae96a9f2be929de2c32c110df5390fdd.tar.bz2
Add TextView support for Home, End, PageUp, PageDown.
Change-Id: If8aa2a63b5fc33528d54eef68e695082a129acce
Diffstat (limited to 'core/java/android/text/method')
-rw-r--r--core/java/android/text/method/ArrowKeyMovementMethod.java267
-rw-r--r--core/java/android/text/method/BaseMovementMethod.java345
-rw-r--r--core/java/android/text/method/LinkMovementMethod.java41
-rw-r--r--core/java/android/text/method/MovementMethod.java24
-rw-r--r--core/java/android/text/method/ScrollingMovementMethod.java295
5 files changed, 677 insertions, 295 deletions
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index d724320..a61ff13 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -16,6 +16,7 @@
package android.text.method;
+import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
@@ -24,170 +25,185 @@ import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
-// 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 {
- private boolean isCap(Spannable buffer) {
+/**
+ * A movement method that provides cursor movement and selection.
+ * Supports displaying the context menu on DPad Center.
+ */
+public class ArrowKeyMovementMethod extends BaseMovementMethod implements MovementMethod {
+ private static boolean isSelecting(Spannable buffer) {
return ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SHIFT_ON) == 1) ||
(MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0));
}
- private boolean isAlt(Spannable buffer) {
- return MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_ALT_ON) == 1;
+ private int getCurrentLineTop(Spannable buffer, Layout layout) {
+ return layout.getLineTop(layout.getLineForOffset(Selection.getSelectionEnd(buffer)));
}
- private boolean up(TextView widget, Spannable buffer) {
- boolean cap = isCap(buffer);
- boolean alt = isAlt(buffer);
- Layout layout = widget.getLayout();
+ private int getPageHeight(TextView widget) {
+ // This calculation does not take into account the view transformations that
+ // may have been applied to the child or its containers. In case of scaling or
+ // rotation, the calculated page height may be incorrect.
+ final Rect rect = new Rect();
+ return widget.getGlobalVisibleRect(rect) ? rect.height() : 0;
+ }
- if (cap) {
- if (alt) {
- Selection.extendSelection(buffer, 0);
- return true;
- } else {
- return Selection.extendUp(buffer, layout);
- }
+ @Override
+ protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode,
+ int movementMetaState, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getRepeatCount() == 0
+ && MetaKeyKeyListener.getMetaState(buffer,
+ MetaKeyKeyListener.META_SELECTING) != 0) {
+ return widget.showContextMenu();
+ }
+ }
+ break;
+ }
+ return super.handleMovementKey(widget, buffer, keyCode, movementMetaState, event);
+ }
+
+ @Override
+ protected boolean left(TextView widget, Spannable buffer) {
+ final Layout layout = widget.getLayout();
+ if (isSelecting(buffer)) {
+ return Selection.extendLeft(buffer, layout);
} else {
- if (alt) {
- Selection.setSelection(buffer, 0);
- return true;
- } else {
- return Selection.moveUp(buffer, layout);
- }
+ return Selection.moveLeft(buffer, layout);
}
}
- private boolean down(TextView widget, Spannable buffer) {
- boolean cap = isCap(buffer);
- boolean alt = isAlt(buffer);
- Layout layout = widget.getLayout();
+ @Override
+ protected boolean right(TextView widget, Spannable buffer) {
+ final Layout layout = widget.getLayout();
+ if (isSelecting(buffer)) {
+ return Selection.extendRight(buffer, layout);
+ } else {
+ return Selection.moveRight(buffer, layout);
+ }
+ }
- if (cap) {
- if (alt) {
- Selection.extendSelection(buffer, buffer.length());
- return true;
- } else {
- return Selection.extendDown(buffer, layout);
- }
+ @Override
+ protected boolean up(TextView widget, Spannable buffer) {
+ final Layout layout = widget.getLayout();
+ if (isSelecting(buffer)) {
+ return Selection.extendUp(buffer, layout);
} else {
- if (alt) {
- Selection.setSelection(buffer, buffer.length());
- return true;
- } else {
- return Selection.moveDown(buffer, layout);
- }
+ return Selection.moveUp(buffer, layout);
}
}
- private boolean left(TextView widget, Spannable buffer) {
- boolean cap = isCap(buffer);
- boolean alt = isAlt(buffer);
- Layout layout = widget.getLayout();
+ @Override
+ protected boolean down(TextView widget, Spannable buffer) {
+ final Layout layout = widget.getLayout();
+ if (isSelecting(buffer)) {
+ return Selection.extendDown(buffer, layout);
+ } else {
+ return Selection.moveDown(buffer, layout);
+ }
+ }
- if (cap) {
- if (alt) {
- return Selection.extendToLeftEdge(buffer, layout);
+ @Override
+ protected boolean pageUp(TextView widget, Spannable buffer) {
+ final Layout layout = widget.getLayout();
+ final boolean selecting = isSelecting(buffer);
+ final int targetY = getCurrentLineTop(buffer, layout) - getPageHeight(widget);
+ boolean handled = false;
+ for (;;) {
+ final int previousSelectionEnd = Selection.getSelectionEnd(buffer);
+ if (selecting) {
+ Selection.extendUp(buffer, layout);
} else {
- return Selection.extendLeft(buffer, layout);
+ Selection.moveUp(buffer, layout);
}
- } else {
- if (alt) {
- return Selection.moveToLeftEdge(buffer, layout);
- } else {
- return Selection.moveLeft(buffer, layout);
+ if (Selection.getSelectionEnd(buffer) == previousSelectionEnd) {
+ break;
+ }
+ handled = true;
+ if (getCurrentLineTop(buffer, layout) <= targetY) {
+ break;
}
}
+ return handled;
}
- 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);
+ @Override
+ protected boolean pageDown(TextView widget, Spannable buffer) {
+ final Layout layout = widget.getLayout();
+ final boolean selecting = isSelecting(buffer);
+ final int targetY = getCurrentLineTop(buffer, layout) + getPageHeight(widget);
+ boolean handled = false;
+ for (;;) {
+ final int previousSelectionEnd = Selection.getSelectionEnd(buffer);
+ if (selecting) {
+ Selection.extendDown(buffer, layout);
} else {
- return Selection.extendRight(buffer, layout);
+ Selection.moveDown(buffer, layout);
}
- } else {
- if (alt) {
- return Selection.moveToRightEdge(buffer, layout);
- } else {
- return Selection.moveRight(buffer, layout);
+ if (Selection.getSelectionEnd(buffer) == previousSelectionEnd) {
+ break;
+ }
+ handled = true;
+ if (getCurrentLineTop(buffer, layout) >= targetY) {
+ break;
}
}
+ return handled;
}
- public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
- if (executeDown(widget, buffer, keyCode)) {
- MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
- MetaKeyKeyListener.resetLockedMeta(buffer);
- return true;
+ @Override
+ protected boolean top(TextView widget, Spannable buffer) {
+ if (isSelecting(buffer)) {
+ Selection.extendSelection(buffer, 0);
+ } else {
+ Selection.setSelection(buffer, 0);
}
-
- return false;
+ return true;
}
- 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;
- }
+ @Override
+ protected boolean bottom(TextView widget, Spannable buffer) {
+ if (isSelecting(buffer)) {
+ Selection.extendSelection(buffer, buffer.length());
+ } else {
+ Selection.setSelection(buffer, buffer.length());
}
+ return true;
+ }
- if (handled) {
- MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
- MetaKeyKeyListener.resetLockedMeta(buffer);
+ @Override
+ protected boolean lineStart(TextView widget, Spannable buffer) {
+ final Layout layout = widget.getLayout();
+ if (isSelecting(buffer)) {
+ return Selection.extendToLeftEdge(buffer, layout);
+ } else {
+ return Selection.moveToLeftEdge(buffer, layout);
}
-
- return handled;
}
- public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
- return false;
+ @Override
+ protected boolean lineEnd(TextView widget, Spannable buffer) {
+ final Layout layout = widget.getLayout();
+ if (isSelecting(buffer)) {
+ return Selection.extendToRightEdge(buffer, layout);
+ } else {
+ return Selection.moveToRightEdge(buffer, layout);
+ }
}
- 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;
+ @Override
+ protected boolean home(TextView widget, Spannable buffer) {
+ return lineStart(widget, buffer);
}
- public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) {
- return false;
+ @Override
+ protected boolean end(TextView widget, Spannable buffer) {
+ return lineEnd(widget, buffer);
}
+ @Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
int initialScrollX = -1, initialScrollY = -1;
final int action = event.getAction();
@@ -201,7 +217,7 @@ public class ArrowKeyMovementMethod implements MovementMethod {
if (widget.isFocused() && !widget.didTouchFocusSelect()) {
if (action == MotionEvent.ACTION_DOWN) {
- boolean cap = isCap(buffer);
+ boolean cap = isSelecting(buffer);
if (cap) {
int offset = widget.getOffset((int) event.getX(), (int) event.getY());
@@ -214,7 +230,7 @@ public class ArrowKeyMovementMethod implements MovementMethod {
widget.getParent().requestDisallowInterceptTouchEvent(true);
}
} else if (action == MotionEvent.ACTION_MOVE) {
- boolean cap = isCap(buffer);
+ boolean cap = isSelecting(buffer);
if (cap && handled) {
// Before selecting, make sure we've moved out of the "slop".
@@ -245,7 +261,7 @@ public class ArrowKeyMovementMethod implements MovementMethod {
}
int offset = widget.getOffset((int) event.getX(), (int) event.getY());
- if (isCap(buffer)) {
+ if (isSelecting(buffer)) {
buffer.removeSpan(LAST_TAP_DOWN);
Selection.extendSelection(buffer, offset);
} else {
@@ -262,14 +278,17 @@ public class ArrowKeyMovementMethod implements MovementMethod {
return handled;
}
+ @Override
public boolean canSelectArbitrarily() {
return true;
}
+ @Override
public void initialize(TextView widget, Spannable text) {
Selection.setSelection(text, 0);
}
+ @Override
public void onTakeFocus(TextView view, Spannable text, int dir) {
if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
if (view.getLayout() == null) {
diff --git a/core/java/android/text/method/BaseMovementMethod.java b/core/java/android/text/method/BaseMovementMethod.java
new file mode 100644
index 0000000..2be18d6
--- /dev/null
+++ b/core/java/android/text/method/BaseMovementMethod.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2010 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.Spannable;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.widget.TextView;
+
+/**
+ * Base classes for movement methods.
+ */
+public class BaseMovementMethod implements MovementMethod {
+ @Override
+ public boolean canSelectArbitrarily() {
+ return false;
+ }
+
+ @Override
+ public void initialize(TextView widget, Spannable text) {
+ }
+
+ @Override
+ public boolean onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event) {
+ final int movementMetaState = getMovementMetaState(text, event);
+ boolean handled = handleMovementKey(widget, text, keyCode, movementMetaState, event);
+ if (handled) {
+ MetaKeyKeyListener.adjustMetaAfterKeypress(text);
+ MetaKeyKeyListener.resetLockedMeta(text);
+ }
+ return handled;
+ }
+
+ @Override
+ public boolean onKeyOther(TextView widget, Spannable text, KeyEvent event) {
+ final int movementMetaState = getMovementMetaState(text, event);
+ final int keyCode = event.getKeyCode();
+ if (keyCode != KeyEvent.KEYCODE_UNKNOWN
+ && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
+ final int repeat = event.getRepeatCount();
+ boolean handled = false;
+ for (int i = 0; i < repeat; i++) {
+ if (!handleMovementKey(widget, text, keyCode, movementMetaState, event)) {
+ break;
+ }
+ handled = true;
+ }
+ if (handled) {
+ MetaKeyKeyListener.adjustMetaAfterKeypress(text);
+ MetaKeyKeyListener.resetLockedMeta(text);
+ }
+ return handled;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public void onTakeFocus(TextView widget, Spannable text, int direction) {
+ }
+
+ @Override
+ public boolean onTouchEvent(TextView widget, Spannable text, MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Gets the meta state used for movement using the modifiers tracked by the text
+ * buffer as well as those present in the key event.
+ *
+ * The movement meta state excludes the state of locked modifiers or the SHIFT key
+ * since they are not used by movement actions (but they may be used for selection).
+ *
+ * @param buffer The text buffer.
+ * @param event The key event.
+ * @return The keyboard meta states used for movement.
+ */
+ protected int getMovementMetaState(Spannable buffer, KeyEvent event) {
+ // We ignore locked modifiers and SHIFT.
+ int metaState = (event.getMetaState() | MetaKeyKeyListener.getMetaState(buffer))
+ & ~(MetaKeyKeyListener.META_ALT_LOCKED | MetaKeyKeyListener.META_SYM_LOCKED);
+ return KeyEvent.normalizeMetaState(metaState) & ~KeyEvent.META_SHIFT_MASK;
+ }
+
+ /**
+ * Performs a movement key action.
+ * The default implementation decodes the key down and invokes movement actions
+ * such as {@link #down} and {@link #up}.
+ * {@link #onKeyDown(TextView, Spannable, int, KeyEvent)} calls this method once
+ * to handle an {@link KeyEvent#ACTION_DOWN}.
+ * {@link #onKeyOther(TextView, Spannable, KeyEvent)} calls this method repeatedly
+ * to handle each repetition of an {@link KeyEvent#ACTION_MULTIPLE}.
+ *
+ * @param widget The text view.
+ * @param buffer The text buffer.
+ * @param event The key event.
+ * @param keyCode The key code.
+ * @param movementMetaState The keyboard meta states used for movement.
+ * @param event The key event.
+ * @return True if the event was handled.
+ */
+ protected boolean handleMovementKey(TextView widget, Spannable buffer,
+ int keyCode, int movementMetaState, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
+ return left(widget, buffer);
+ } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
+ KeyEvent.META_ALT_ON)) {
+ return lineStart(widget, buffer);
+ }
+ break;
+
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
+ return right(widget, buffer);
+ } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
+ KeyEvent.META_ALT_ON)) {
+ return lineEnd(widget, buffer);
+ }
+ break;
+
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
+ return up(widget, buffer);
+ } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
+ KeyEvent.META_ALT_ON)) {
+ return top(widget, buffer);
+ }
+ break;
+
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
+ return down(widget, buffer);
+ } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
+ KeyEvent.META_ALT_ON)) {
+ return bottom(widget, buffer);
+ }
+ break;
+
+ case KeyEvent.KEYCODE_PAGE_UP:
+ if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
+ return pageUp(widget, buffer);
+ } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
+ KeyEvent.META_ALT_ON)) {
+ return top(widget, buffer);
+ }
+ break;
+
+ case KeyEvent.KEYCODE_PAGE_DOWN:
+ if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
+ return pageDown(widget, buffer);
+ } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
+ KeyEvent.META_ALT_ON)) {
+ return bottom(widget, buffer);
+ }
+ break;
+
+ case KeyEvent.KEYCODE_MOVE_HOME:
+ if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
+ return home(widget, buffer);
+ }
+ break;
+
+ case KeyEvent.KEYCODE_MOVE_END:
+ if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
+ return end(widget, buffer);
+ }
+ break;
+ }
+ return false;
+ }
+
+ /**
+ * Performs a left movement action.
+ * Moves the cursor or scrolls left by one character.
+ *
+ * @param widget The text view.
+ * @param buffer The text buffer.
+ * @return True if the event was handled.
+ */
+ protected boolean left(TextView widget, Spannable buffer) {
+ return false;
+ }
+
+ /**
+ * Performs a right movement action.
+ * Moves the cursor or scrolls right by one character.
+ *
+ * @param widget The text view.
+ * @param buffer The text buffer.
+ * @return True if the event was handled.
+ */
+ protected boolean right(TextView widget, Spannable buffer) {
+ return false;
+ }
+
+ /**
+ * Performs an up movement action.
+ * Moves the cursor or scrolls up by one line.
+ *
+ * @param widget The text view.
+ * @param buffer The text buffer.
+ * @return True if the event was handled.
+ */
+ protected boolean up(TextView widget, Spannable buffer) {
+ return false;
+ }
+
+ /**
+ * Performs a down movement action.
+ * Moves the cursor or scrolls down by one line.
+ *
+ * @param widget The text view.
+ * @param buffer The text buffer.
+ * @return True if the event was handled.
+ */
+ protected boolean down(TextView widget, Spannable buffer) {
+ return false;
+ }
+
+ /**
+ * Performs a page-up movement action.
+ * Moves the cursor or scrolls up by one page.
+ *
+ * @param widget The text view.
+ * @param buffer The text buffer.
+ * @return True if the event was handled.
+ */
+ protected boolean pageUp(TextView widget, Spannable buffer) {
+ return false;
+ }
+
+ /**
+ * Performs a page-down movement action.
+ * Moves the cursor or scrolls down by one page.
+ *
+ * @param widget The text view.
+ * @param buffer The text buffer.
+ * @return True if the event was handled.
+ */
+ protected boolean pageDown(TextView widget, Spannable buffer) {
+ return false;
+ }
+
+ /**
+ * Performs a top movement action.
+ * Moves the cursor or scrolls to the top of the buffer.
+ *
+ * @param widget The text view.
+ * @param buffer The text buffer.
+ * @return True if the event was handled.
+ */
+ protected boolean top(TextView widget, Spannable buffer) {
+ return false;
+ }
+
+ /**
+ * Performs a bottom movement action.
+ * Moves the cursor or scrolls to the bottom of the buffer.
+ *
+ * @param widget The text view.
+ * @param buffer The text buffer.
+ * @return True if the event was handled.
+ */
+ protected boolean bottom(TextView widget, Spannable buffer) {
+ return false;
+ }
+
+ /**
+ * Performs a line-start movement action.
+ * Moves the cursor or scrolls to the start of the line.
+ *
+ * @param widget The text view.
+ * @param buffer The text buffer.
+ * @return True if the event was handled.
+ */
+ protected boolean lineStart(TextView widget, Spannable buffer) {
+ return false;
+ }
+
+ /**
+ * Performs an line-end movement action.
+ * Moves the cursor or scrolls to the end of the line.
+ *
+ * @param widget The text view.
+ * @param buffer The text buffer.
+ * @return True if the event was handled.
+ */
+ protected boolean lineEnd(TextView widget, Spannable buffer) {
+ return false;
+ }
+
+ /**
+ * Performs a home movement action.
+ * Moves the cursor or scrolls to the start of the line or to the top of the
+ * document depending on whether the insertion point is being moved or
+ * the document is being scrolled.
+ *
+ * @param widget The text view.
+ * @param buffer The text buffer.
+ * @return True if the event was handled.
+ */
+ protected boolean home(TextView widget, Spannable buffer) {
+ return false;
+ }
+
+ /**
+ * Performs an end movement action.
+ * Moves the cursor or scrolls to the start of the line or to the top of the
+ * document depending on whether the insertion point is being moved or
+ * the document is being scrolled.
+ *
+ * @param widget The text view.
+ * @param buffer The text buffer.
+ * @return True if the event was handled.
+ */
+ protected boolean end(TextView widget, Spannable buffer) {
+ return false;
+ }
+}
diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java
index 22e9cc6..80c8169 100644
--- a/core/java/android/text/method/LinkMovementMethod.java
+++ b/core/java/android/text/method/LinkMovementMethod.java
@@ -16,8 +16,6 @@
package android.text.method;
-import android.content.Intent;
-import android.net.Uri;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.text.*;
@@ -25,28 +23,30 @@ import android.text.style.*;
import android.view.View;
import android.widget.TextView;
-public class
-LinkMovementMethod
-extends ScrollingMovementMethod
-{
+/**
+ * A movement method that traverses links in the text buffer and scrolls if necessary.
+ * Supports clicking on links with DPad Center or Enter.
+ */
+public class LinkMovementMethod extends ScrollingMovementMethod {
private static final int CLICK = 1;
private static final int UP = 2;
private static final int DOWN = 3;
@Override
- public boolean onKeyDown(TextView widget, Spannable buffer,
- int keyCode, KeyEvent event) {
+ protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode,
+ int movementMetaState, KeyEvent event) {
switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_CENTER:
- case KeyEvent.KEYCODE_ENTER:
- if (event.getRepeatCount() == 0) {
- if (action(CLICK, widget, buffer)) {
- return true;
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_ENTER:
+ if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getRepeatCount() == 0) {
+ return action(CLICK, widget, buffer);
+ }
}
- }
+ break;
}
-
- return super.onKeyDown(widget, buffer, keyCode, event);
+ return super.handleMovementKey(widget, buffer, keyCode, movementMetaState, event);
}
@Override
@@ -86,8 +86,6 @@ extends ScrollingMovementMethod
}
private boolean action(int what, TextView widget, Spannable buffer) {
- boolean handled = false;
-
Layout layout = widget.getLayout();
int padding = widget.getTotalPaddingTop() +
@@ -184,11 +182,6 @@ extends ScrollingMovementMethod
return false;
}
- public boolean onKeyUp(TextView widget, Spannable buffer,
- int keyCode, KeyEvent event) {
- return false;
- }
-
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
@@ -229,11 +222,13 @@ extends ScrollingMovementMethod
return super.onTouchEvent(widget, buffer, event);
}
+ @Override
public void initialize(TextView widget, Spannable text) {
Selection.removeSelection(text);
text.removeSpan(FROM_BELOW);
}
+ @Override
public void onTakeFocus(TextView view, Spannable text, int dir) {
Selection.removeSelection(text);
diff --git a/core/java/android/text/method/MovementMethod.java b/core/java/android/text/method/MovementMethod.java
index 29f67a1..9167676 100644
--- a/core/java/android/text/method/MovementMethod.java
+++ b/core/java/android/text/method/MovementMethod.java
@@ -21,24 +21,32 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.text.*;
-public interface MovementMethod
-{
+/**
+ * Provides cursor positioning, scrolling and text selection functionality in a {@link TextView}.
+ * <p>
+ * The {@link TextView} delegates handling of key events, trackball motions and touches to
+ * the movement method for purposes of content navigation. The framework automatically
+ * selects an appropriate movement method based on the content of the {@link TextView}.
+ * </p><p>
+ * This interface is intended for use by the framework; it should not be implemented
+ * directly by applications.
+ * </p>
+ */
+public interface MovementMethod {
public void initialize(TextView widget, Spannable text);
public boolean onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event);
public boolean onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event);
-
+
/**
* If the key listener wants to other kinds of key events, return true,
* otherwise return false and the caller (i.e. the widget host)
* will handle the key.
*/
public boolean onKeyOther(TextView view, Spannable text, KeyEvent event);
-
+
public void onTakeFocus(TextView widget, Spannable text, int direction);
- public boolean onTrackballEvent(TextView widget, Spannable text,
- MotionEvent event);
- public boolean onTouchEvent(TextView widget, Spannable text,
- MotionEvent event);
+ public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event);
+ public boolean onTouchEvent(TextView widget, Spannable text, MotionEvent event);
/**
* Returns true if this movement method allows arbitrary selection
diff --git a/core/java/android/text/method/ScrollingMovementMethod.java b/core/java/android/text/method/ScrollingMovementMethod.java
index 563ceed..194ecc1 100644
--- a/core/java/android/text/method/ScrollingMovementMethod.java
+++ b/core/java/android/text/method/ScrollingMovementMethod.java
@@ -16,201 +16,216 @@
package android.text.method;
-import android.util.Log;
-import android.view.KeyEvent;
import android.view.MotionEvent;
import android.text.*;
import android.widget.TextView;
import android.view.View;
-public class
-ScrollingMovementMethod
-implements MovementMethod
-{
- /**
- * Scrolls the text to the left if possible.
- */
- protected boolean left(TextView widget, Spannable buffer) {
- Layout layout = widget.getLayout();
-
- int scrolly = widget.getScrollY();
- int scr = widget.getScrollX();
- int em = Math.round(layout.getPaint().getFontSpacing());
-
- int padding = widget.getTotalPaddingTop() +
- widget.getTotalPaddingBottom();
- int top = layout.getLineForVertical(scrolly);
- int bottom = layout.getLineForVertical(scrolly + widget.getHeight() -
- padding);
- int left = Integer.MAX_VALUE;
-
- for (int i = top; i <= bottom; i++) {
- left = (int) Math.min(left, layout.getLineLeft(i));
- }
+/**
+ * A movement method that interprets movement keys by scrolling the text buffer.
+ */
+public class ScrollingMovementMethod extends BaseMovementMethod implements MovementMethod {
+ private int getTopLine(TextView widget) {
+ return widget.getLayout().getLineForVertical(widget.getScrollY());
+ }
- if (scr > left) {
- int s = Math.max(scr - em, left);
- widget.scrollTo(s, widget.getScrollY());
- return true;
- }
+ private int getBottomLine(TextView widget) {
+ return widget.getLayout().getLineForVertical(widget.getScrollY() + getInnerHeight(widget));
+ }
- return false;
+ private int getInnerWidth(TextView widget) {
+ return widget.getWidth() - widget.getTotalPaddingLeft() - widget.getTotalPaddingRight();
}
- /**
- * Scrolls the text to the right if possible.
- */
- protected boolean right(TextView widget, Spannable buffer) {
- Layout layout = widget.getLayout();
+ private int getInnerHeight(TextView widget) {
+ return widget.getHeight() - widget.getTotalPaddingTop() - widget.getTotalPaddingBottom();
+ }
- int scrolly = widget.getScrollY();
- int scr = widget.getScrollX();
- int em = Math.round(layout.getPaint().getFontSpacing());
+ private int getCharacterWidth(TextView widget) {
+ return (int) Math.ceil(widget.getPaint().getFontSpacing());
+ }
- int padding = widget.getTotalPaddingTop() +
- widget.getTotalPaddingBottom();
- int top = layout.getLineForVertical(scrolly);
- int bottom = layout.getLineForVertical(scrolly + widget.getHeight() -
- padding);
- int right = 0;
+ private int getScrollBoundsLeft(TextView widget) {
+ final Layout layout = widget.getLayout();
+ final int topLine = getTopLine(widget);
+ final int bottomLine = getBottomLine(widget);
+ if (topLine > bottomLine) {
+ return 0;
+ }
+ int left = Integer.MAX_VALUE;
+ for (int line = topLine; line <= bottomLine; line++) {
+ final int lineLeft = (int) Math.floor(layout.getLineLeft(line));
+ if (lineLeft < left) {
+ left = lineLeft;
+ }
+ }
+ return left;
+ }
- for (int i = top; i <= bottom; i++) {
- right = (int) Math.max(right, layout.getLineRight(i));
+ private int getScrollBoundsRight(TextView widget) {
+ final Layout layout = widget.getLayout();
+ final int topLine = getTopLine(widget);
+ final int bottomLine = getBottomLine(widget);
+ if (topLine > bottomLine) {
+ return 0;
+ }
+ int right = Integer.MIN_VALUE;
+ for (int line = topLine; line <= bottomLine; line++) {
+ final int lineRight = (int) Math.ceil(layout.getLineRight(line));
+ if (lineRight > right) {
+ right = lineRight;
+ }
}
+ return right;
+ }
- padding = widget.getTotalPaddingLeft() + widget.getTotalPaddingRight();
- if (scr < right - (widget.getWidth() - padding)) {
- int s = Math.min(scr + em, right - (widget.getWidth() - padding));
- widget.scrollTo(s, widget.getScrollY());
+ @Override
+ protected boolean left(TextView widget, Spannable buffer) {
+ final int minScrollX = getScrollBoundsLeft(widget);
+ int scrollX = widget.getScrollX();
+ if (scrollX > minScrollX) {
+ scrollX = Math.max(scrollX - getCharacterWidth(widget), minScrollX);
+ widget.scrollTo(scrollX, widget.getScrollY());
return true;
}
+ return false;
+ }
+ @Override
+ protected boolean right(TextView widget, Spannable buffer) {
+ final int maxScrollX = getScrollBoundsRight(widget) - getInnerWidth(widget);
+ int scrollX = widget.getScrollX();
+ if (scrollX < maxScrollX) {
+ scrollX = Math.min(scrollX + getCharacterWidth(widget), maxScrollX);
+ widget.scrollTo(scrollX, widget.getScrollY());
+ return true;
+ }
return false;
}
- /**
- * Scrolls the text up if possible.
- */
+ @Override
protected boolean up(TextView widget, Spannable buffer) {
- Layout layout = widget.getLayout();
-
- int areatop = widget.getScrollY();
- int line = layout.getLineForVertical(areatop);
- int linetop = layout.getLineTop(line);
-
- // If the top line is partially visible, bring it all the way
- // into view; otherwise, bring the previous line into view.
- if (areatop == linetop)
- line--;
-
- if (line >= 0) {
- Touch.scrollTo(widget, layout,
- widget.getScrollX(), layout.getLineTop(line));
+ final Layout layout = widget.getLayout();
+ final int top = widget.getScrollY();
+ int topLine = layout.getLineForVertical(top);
+ if (layout.getLineTop(topLine) == top) {
+ // If the top line is partially visible, bring it all the way
+ // into view; otherwise, bring the previous line into view.
+ topLine -= 1;
+ }
+ if (topLine >= 0) {
+ Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(topLine));
return true;
}
-
return false;
}
- /**
- * Scrolls the text down if possible.
- */
+ @Override
protected boolean down(TextView widget, Spannable buffer) {
- Layout layout = widget.getLayout();
-
- int padding = widget.getTotalPaddingTop() +
- widget.getTotalPaddingBottom();
-
- int areabot = widget.getScrollY() + widget.getHeight() - padding;
- int line = layout.getLineForVertical(areabot);
-
- if (layout.getLineTop(line+1) < areabot + 1) {
+ final Layout layout = widget.getLayout();
+ final int innerHeight = getInnerHeight(widget);
+ final int bottom = widget.getScrollY() + innerHeight;
+ int bottomLine = layout.getLineForVertical(bottom);
+ if (layout.getLineTop(bottomLine + 1) < bottom + 1) {
// Less than a pixel of this line is out of view,
// so we must have tried to make it entirely in view
// and now want the next line to be in view instead.
-
- line++;
+ bottomLine += 1;
}
-
- if (line <= layout.getLineCount() - 1) {
- widget.scrollTo(widget.getScrollX(), layout.getLineTop(line+1) -
- (widget.getHeight() - padding));
- Touch.scrollTo(widget, layout,
- widget.getScrollX(), widget.getScrollY());
+ if (bottomLine <= layout.getLineCount() - 1) {
+ Touch.scrollTo(widget, layout, widget.getScrollX(),
+ layout.getLineTop(bottomLine + 1) - innerHeight);
return true;
}
-
return false;
}
- public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
- return executeDown(widget, buffer, keyCode);
+ @Override
+ protected boolean pageUp(TextView widget, Spannable buffer) {
+ final Layout layout = widget.getLayout();
+ final int top = widget.getScrollY() - getInnerHeight(widget);
+ int topLine = layout.getLineForVertical(top);
+ if (topLine >= 0) {
+ Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(topLine));
+ return true;
+ }
+ return false;
}
- private boolean executeDown(TextView widget, Spannable buffer, int keyCode) {
- boolean handled = false;
-
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- handled |= left(widget, buffer);
- break;
-
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- handled |= right(widget, buffer);
- break;
-
- case KeyEvent.KEYCODE_DPAD_UP:
- handled |= up(widget, buffer);
- break;
-
- case KeyEvent.KEYCODE_DPAD_DOWN:
- handled |= down(widget, buffer);
- break;
+ @Override
+ protected boolean pageDown(TextView widget, Spannable buffer) {
+ final Layout layout = widget.getLayout();
+ final int innerHeight = getInnerHeight(widget);
+ final int bottom = widget.getScrollY() + innerHeight + innerHeight;
+ int bottomLine = layout.getLineForVertical(bottom);
+ if (bottomLine <= layout.getLineCount() - 1) {
+ Touch.scrollTo(widget, layout, widget.getScrollX(),
+ layout.getLineTop(bottomLine + 1) - innerHeight);
+ return true;
}
+ return false;
+ }
- return handled;
+ @Override
+ protected boolean top(TextView widget, Spannable buffer) {
+ final Layout layout = widget.getLayout();
+ if (getTopLine(widget) >= 0) {
+ Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(0));
+ return true;
+ }
+ return false;
}
- public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
+ @Override
+ protected boolean bottom(TextView widget, Spannable buffer) {
+ final Layout layout = widget.getLayout();
+ final int lineCount = layout.getLineCount();
+ if (getBottomLine(widget) <= lineCount - 1) {
+ Touch.scrollTo(widget, layout, widget.getScrollX(),
+ layout.getLineTop(lineCount) - getInnerHeight(widget));
+ return true;
+ }
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 first = true;
- boolean handled = false;
- while ((--repeat) > 0) {
- if (first && executeDown(view, text, code)) {
- handled = true;
- MetaKeyKeyListener.adjustMetaAfterKeypress(text);
- MetaKeyKeyListener.resetLockedMeta(text);
- }
- first = false;
- }
- return handled;
+ @Override
+ protected boolean lineStart(TextView widget, Spannable buffer) {
+ final int minScrollX = getScrollBoundsLeft(widget);
+ int scrollX = widget.getScrollX();
+ if (scrollX > minScrollX) {
+ widget.scrollTo(minScrollX, widget.getScrollY());
+ return true;
}
return false;
}
-
- public boolean onTrackballEvent(TextView widget, Spannable text,
- MotionEvent event) {
+
+ @Override
+ protected boolean lineEnd(TextView widget, Spannable buffer) {
+ final int maxScrollX = getScrollBoundsRight(widget) - getInnerWidth(widget);
+ int scrollX = widget.getScrollX();
+ if (scrollX < maxScrollX) {
+ widget.scrollTo(maxScrollX, widget.getScrollY());
+ return true;
+ }
return false;
}
-
- public boolean onTouchEvent(TextView widget, Spannable buffer,
- MotionEvent event) {
- return Touch.onTouchEvent(widget, buffer, event);
+
+ @Override
+ protected boolean home(TextView widget, Spannable buffer) {
+ return top(widget, buffer);
}
- public void initialize(TextView widget, Spannable text) { }
+ @Override
+ protected boolean end(TextView widget, Spannable buffer) {
+ return bottom(widget, buffer);
+ }
- public boolean canSelectArbitrarily() {
- return false;
+ @Override
+ public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
+ return Touch.onTouchEvent(widget, buffer, event);
}
+ @Override
public void onTakeFocus(TextView widget, Spannable text, int dir) {
Layout layout = widget.getLayout();