ArrowKeyMovementMethod.java revision 49271c941ed965c20ed834c5efc52b07ed616e34
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.text.method;
18
19import android.text.Layout;
20import android.text.Selection;
21import android.text.Spannable;
22import android.view.KeyEvent;
23import android.view.MotionEvent;
24import android.view.View;
25import android.widget.TextView;
26import android.widget.TextView.CursorController;
27
28// XXX this doesn't extend MetaKeyKeyListener because the signatures
29// don't match.  Need to figure that out.  Meanwhile the meta keys
30// won't work in fields that don't take input.
31
32public class ArrowKeyMovementMethod implements MovementMethod {
33    /**
34     * An optional controller for the cursor.
35     * Use {@link #setCursorController(CursorController)} to set this field.
36     */
37    private CursorController mCursorController;
38
39    private boolean isCap(Spannable buffer) {
40        return ((MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_SHIFT_ON) == 1) ||
41                (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0));
42    }
43
44    private boolean isAlt(Spannable buffer) {
45        return MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1;
46    }
47
48    private boolean up(TextView widget, Spannable buffer) {
49        boolean cap = isCap(buffer);
50        boolean alt = isAlt(buffer);
51        Layout layout = widget.getLayout();
52
53        if (cap) {
54            if (alt) {
55                Selection.extendSelection(buffer, 0);
56                return true;
57            } else {
58                return Selection.extendUp(buffer, layout);
59            }
60        } else {
61            if (alt) {
62                Selection.setSelection(buffer, 0);
63                return true;
64            } else {
65                return Selection.moveUp(buffer, layout);
66            }
67        }
68    }
69
70    private boolean down(TextView widget, Spannable buffer) {
71        boolean cap = isCap(buffer);
72        boolean alt = isAlt(buffer);
73        Layout layout = widget.getLayout();
74
75        if (cap) {
76            if (alt) {
77                Selection.extendSelection(buffer, buffer.length());
78                return true;
79            } else {
80                return Selection.extendDown(buffer, layout);
81            }
82        } else {
83            if (alt) {
84                Selection.setSelection(buffer, buffer.length());
85                return true;
86            } else {
87                return Selection.moveDown(buffer, layout);
88            }
89        }
90    }
91
92    private boolean left(TextView widget, Spannable buffer) {
93        boolean cap = isCap(buffer);
94        boolean alt = isAlt(buffer);
95        Layout layout = widget.getLayout();
96
97        if (cap) {
98            if (alt) {
99                return Selection.extendToLeftEdge(buffer, layout);
100            } else {
101                return Selection.extendLeft(buffer, layout);
102            }
103        } else {
104            if (alt) {
105                return Selection.moveToLeftEdge(buffer, layout);
106            } else {
107                return Selection.moveLeft(buffer, layout);
108            }
109        }
110    }
111
112    private boolean right(TextView widget, Spannable buffer) {
113        boolean cap = isCap(buffer);
114        boolean alt = isAlt(buffer);
115        Layout layout = widget.getLayout();
116
117        if (cap) {
118            if (alt) {
119                return Selection.extendToRightEdge(buffer, layout);
120            } else {
121                return Selection.extendRight(buffer, layout);
122            }
123        } else {
124            if (alt) {
125                return Selection.moveToRightEdge(buffer, layout);
126            } else {
127                return Selection.moveRight(buffer, layout);
128            }
129        }
130    }
131
132    public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
133        if (executeDown(widget, buffer, keyCode)) {
134            MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
135            MetaKeyKeyListener.resetLockedMeta(buffer);
136            return true;
137        }
138
139        return false;
140    }
141
142    private boolean executeDown(TextView widget, Spannable buffer, int keyCode) {
143        boolean handled = false;
144
145        switch (keyCode) {
146        case KeyEvent.KEYCODE_DPAD_UP:
147            handled |= up(widget, buffer);
148            break;
149
150        case KeyEvent.KEYCODE_DPAD_DOWN:
151            handled |= down(widget, buffer);
152            break;
153
154        case KeyEvent.KEYCODE_DPAD_LEFT:
155            handled |= left(widget, buffer);
156            break;
157
158        case KeyEvent.KEYCODE_DPAD_RIGHT:
159            handled |= right(widget, buffer);
160            break;
161
162        case KeyEvent.KEYCODE_DPAD_CENTER:
163            if ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) &&
164                (widget.showContextMenu())) {
165                    handled = true;
166            }
167        }
168
169        if (handled) {
170            MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
171            MetaKeyKeyListener.resetLockedMeta(buffer);
172        }
173
174        return handled;
175    }
176
177    public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
178        return false;
179    }
180
181    public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
182        int code = event.getKeyCode();
183        if (code != KeyEvent.KEYCODE_UNKNOWN && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
184            int repeat = event.getRepeatCount();
185            boolean handled = false;
186            while ((--repeat) > 0) {
187                handled |= executeDown(view, text, code);
188            }
189            return handled;
190        }
191        return false;
192    }
193
194    public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) {
195        if (mCursorController != null) {
196            mCursorController.hide();
197        }
198        return false;
199    }
200
201    public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
202        if (mCursorController != null) {
203            return onTouchEventCursor(widget, buffer, event);
204        } else {
205            return onTouchEventStandard(widget, buffer, event);
206        }
207    }
208
209    private boolean onTouchEventStandard(TextView widget, Spannable buffer, MotionEvent event) {
210        int initialScrollX = -1, initialScrollY = -1;
211        if (event.getAction() == MotionEvent.ACTION_UP) {
212            initialScrollX = Touch.getInitialScrollX(widget, buffer);
213            initialScrollY = Touch.getInitialScrollY(widget, buffer);
214        }
215
216        boolean handled = Touch.onTouchEvent(widget, buffer, event);
217
218        if (widget.isFocused() && !widget.didTouchFocusSelect()) {
219            if (event.getAction() == MotionEvent.ACTION_DOWN) {
220              boolean cap = isCap(buffer);
221              if (cap) {
222                  int offset = widget.getOffset((int) event.getX(), (int) event.getY());
223
224                  buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
225
226                  // Disallow intercepting of the touch events, so that
227                  // users can scroll and select at the same time.
228                  // without this, users would get booted out of select
229                  // mode once the view detected it needed to scroll.
230                  widget.getParent().requestDisallowInterceptTouchEvent(true);
231              }
232            } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
233                boolean cap = isCap(buffer);
234
235                if (cap && handled) {
236                    // Before selecting, make sure we've moved out of the "slop".
237                    // handled will be true, if we're in select mode AND we're
238                    // OUT of the slop
239
240                    // Turn long press off while we're selecting. User needs to
241                    // re-tap on the selection to enable long press
242                    widget.cancelLongPress();
243
244                    // Update selection as we're moving the selection area.
245
246                    // Get the current touch position
247                    int offset = widget.getOffset((int) event.getX(), (int) event.getY());
248
249                    Selection.extendSelection(buffer, offset);
250                    return true;
251                }
252            } else if (event.getAction() == MotionEvent.ACTION_UP) {
253                // If we have scrolled, then the up shouldn't move the cursor,
254                // but we do need to make sure the cursor is still visible at
255                // the current scroll offset to avoid the scroll jumping later
256                // to show it.
257                if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) ||
258                    (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
259                    widget.moveCursorToVisibleOffset();
260                    return true;
261                }
262
263                int offset = widget.getOffset((int) event.getX(), (int) event.getY());
264                if (isCap(buffer)) {
265                    buffer.removeSpan(LAST_TAP_DOWN);
266                    Selection.extendSelection(buffer, offset);
267                } else {
268                    Selection.setSelection(buffer, offset);
269                }
270
271                MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
272                MetaKeyKeyListener.resetLockedMeta(buffer);
273
274                return true;
275            }
276        }
277
278        return handled;
279    }
280
281    private boolean onTouchEventCursor(TextView widget, Spannable buffer, MotionEvent event) {
282        if (widget.isFocused() && !widget.didTouchFocusSelect()) {
283            switch (event.getActionMasked()) {
284                case MotionEvent.ACTION_MOVE:
285                    widget.cancelLongPress();
286
287                    // Offset the current touch position (from controller to cursor)
288                    final float x = event.getX() + mCursorController.getOffsetX();
289                    final float y = event.getY() + mCursorController.getOffsetY();
290                    mCursorController.updatePosition((int) x, (int) y);
291                    return true;
292
293                case MotionEvent.ACTION_UP:
294                case MotionEvent.ACTION_CANCEL:
295                    mCursorController = null;
296                    return true;
297            }
298        }
299        return false;
300    }
301
302    /**
303     * Defines the cursor controller.
304     *
305     * When set, this object can be used to handle touch events, that can be translated into cursor
306     * updates.
307     *
308     * {@link MotionEvent#ACTION_MOVE} events will call back the
309     * {@link CursorController#updatePosition(int, int)} controller's method, passing the current
310     * finger coordinates (offset by {@link CursorController#getOffsetX()} and
311     * {@link CursorController#getOffsetY()}) as parameters.
312     *
313     * When the gesture is finished (on a {@link MotionEvent#ACTION_UP} or
314     * {@link MotionEvent#ACTION_CANCEL} event), the controller is reset to null.
315     *
316     * @param cursorController A cursor controller implementation
317     */
318    public void setCursorController(CursorController cursorController) {
319        mCursorController = cursorController;
320    }
321
322    public boolean canSelectArbitrarily() {
323        return true;
324    }
325
326    public void initialize(TextView widget, Spannable text) {
327        Selection.setSelection(text, 0);
328    }
329
330    public void onTakeFocus(TextView view, Spannable text, int dir) {
331        if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
332            if (view.getLayout() == null) {
333                // This shouldn't be null, but do something sensible if it is.
334                Selection.setSelection(text, text.length());
335            }
336        } else {
337            Selection.setSelection(text, text.length());
338        }
339    }
340
341    public static MovementMethod getInstance() {
342        if (sInstance == null) {
343            sInstance = new ArrowKeyMovementMethod();
344        }
345
346        return sInstance;
347    }
348
349
350    private static final Object LAST_TAP_DOWN = new Object();
351    private static ArrowKeyMovementMethod sInstance;
352}
353