ArrowKeyMovementMethod.java revision 01ce2e9eee41cc0c24b0d16465710a28ea337d5d
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;
26
27// XXX this doesn't extend MetaKeyKeyListener because the signatures
28// don't match.  Need to figure that out.  Meanwhile the meta keys
29// won't work in fields that don't take input.
30
31public class ArrowKeyMovementMethod implements MovementMethod {
32    private boolean isCap(Spannable buffer) {
33        return ((MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_SHIFT_ON) == 1) ||
34                (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0));
35    }
36
37    private boolean isAlt(Spannable buffer) {
38        return MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1;
39    }
40
41    private boolean up(TextView widget, Spannable buffer) {
42        boolean cap = isCap(buffer);
43        boolean alt = isAlt(buffer);
44        Layout layout = widget.getLayout();
45
46        if (cap) {
47            if (alt) {
48                Selection.extendSelection(buffer, 0);
49                return true;
50            } else {
51                return Selection.extendUp(buffer, layout);
52            }
53        } else {
54            if (alt) {
55                Selection.setSelection(buffer, 0);
56                return true;
57            } else {
58                return Selection.moveUp(buffer, layout);
59            }
60        }
61    }
62
63    private boolean down(TextView widget, Spannable buffer) {
64        boolean cap = isCap(buffer);
65        boolean alt = isAlt(buffer);
66        Layout layout = widget.getLayout();
67
68        if (cap) {
69            if (alt) {
70                Selection.extendSelection(buffer, buffer.length());
71                return true;
72            } else {
73                return Selection.extendDown(buffer, layout);
74            }
75        } else {
76            if (alt) {
77                Selection.setSelection(buffer, buffer.length());
78                return true;
79            } else {
80                return Selection.moveDown(buffer, layout);
81            }
82        }
83    }
84
85    private boolean left(TextView widget, Spannable buffer) {
86        boolean cap = isCap(buffer);
87        boolean alt = isAlt(buffer);
88        Layout layout = widget.getLayout();
89
90        if (cap) {
91            if (alt) {
92                return Selection.extendToLeftEdge(buffer, layout);
93            } else {
94                return Selection.extendLeft(buffer, layout);
95            }
96        } else {
97            if (alt) {
98                return Selection.moveToLeftEdge(buffer, layout);
99            } else {
100                return Selection.moveLeft(buffer, layout);
101            }
102        }
103    }
104
105    private boolean right(TextView widget, Spannable buffer) {
106        boolean cap = isCap(buffer);
107        boolean alt = isAlt(buffer);
108        Layout layout = widget.getLayout();
109
110        if (cap) {
111            if (alt) {
112                return Selection.extendToRightEdge(buffer, layout);
113            } else {
114                return Selection.extendRight(buffer, layout);
115            }
116        } else {
117            if (alt) {
118                return Selection.moveToRightEdge(buffer, layout);
119            } else {
120                return Selection.moveRight(buffer, layout);
121            }
122        }
123    }
124
125    public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
126        if (executeDown(widget, buffer, keyCode)) {
127            MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
128            MetaKeyKeyListener.resetLockedMeta(buffer);
129            return true;
130        }
131
132        return false;
133    }
134
135    private boolean executeDown(TextView widget, Spannable buffer, int keyCode) {
136        boolean handled = false;
137
138        switch (keyCode) {
139        case KeyEvent.KEYCODE_DPAD_UP:
140            handled |= up(widget, buffer);
141            break;
142
143        case KeyEvent.KEYCODE_DPAD_DOWN:
144            handled |= down(widget, buffer);
145            break;
146
147        case KeyEvent.KEYCODE_DPAD_LEFT:
148            handled |= left(widget, buffer);
149            break;
150
151        case KeyEvent.KEYCODE_DPAD_RIGHT:
152            handled |= right(widget, buffer);
153            break;
154
155        case KeyEvent.KEYCODE_DPAD_CENTER:
156            if ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) &&
157                (widget.showContextMenu())) {
158                    handled = true;
159            }
160        }
161
162        if (handled) {
163            MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
164            MetaKeyKeyListener.resetLockedMeta(buffer);
165        }
166
167        return handled;
168    }
169
170    public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
171        return false;
172    }
173
174    public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
175        int code = event.getKeyCode();
176        if (code != KeyEvent.KEYCODE_UNKNOWN && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
177            int repeat = event.getRepeatCount();
178            boolean handled = false;
179            while ((--repeat) > 0) {
180                handled |= executeDown(view, text, code);
181            }
182            return handled;
183        }
184        return false;
185    }
186
187    public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) {
188        return false;
189    }
190
191    public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
192        int initialScrollX = -1, initialScrollY = -1;
193        if (event.getAction() == MotionEvent.ACTION_UP) {
194            initialScrollX = Touch.getInitialScrollX(widget, buffer);
195            initialScrollY = Touch.getInitialScrollY(widget, buffer);
196        }
197
198        boolean handled = Touch.onTouchEvent(widget, buffer, event);
199
200        if (widget.isFocused() && !widget.didTouchFocusSelect()) {
201            if (event.getAction() == MotionEvent.ACTION_DOWN) {
202              boolean cap = isCap(buffer);
203              if (cap) {
204                  int offset = widget.getOffset((int) event.getX(), (int) event.getY());
205
206                  buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
207
208                  // Disallow intercepting of the touch events, so that
209                  // users can scroll and select at the same time.
210                  // without this, users would get booted out of select
211                  // mode once the view detected it needed to scroll.
212                  widget.getParent().requestDisallowInterceptTouchEvent(true);
213              }
214            } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
215                boolean cap = isCap(buffer);
216
217                if (cap && handled) {
218                    // Before selecting, make sure we've moved out of the "slop".
219                    // handled will be true, if we're in select mode AND we're
220                    // OUT of the slop
221
222                    // Turn long press off while we're selecting. User needs to
223                    // re-tap on the selection to enable long press
224                    widget.cancelLongPress();
225
226                    // Update selection as we're moving the selection area.
227
228                    // Get the current touch position
229                    int offset = widget.getOffset((int) event.getX(), (int) event.getY());
230
231                    Selection.extendSelection(buffer, offset);
232                    return true;
233                }
234            } else if (event.getAction() == MotionEvent.ACTION_UP) {
235                // If we have scrolled, then the up shouldn't move the cursor,
236                // but we do need to make sure the cursor is still visible at
237                // the current scroll offset to avoid the scroll jumping later
238                // to show it.
239                if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) ||
240                    (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
241                    widget.moveCursorToVisibleOffset();
242                    return true;
243                }
244
245                int offset = widget.getOffset((int) event.getX(), (int) event.getY());
246                if (isCap(buffer)) {
247                    buffer.removeSpan(LAST_TAP_DOWN);
248                    Selection.extendSelection(buffer, offset);
249                } else {
250                    Selection.setSelection(buffer, offset);
251                }
252
253                MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
254                MetaKeyKeyListener.resetLockedMeta(buffer);
255
256                return true;
257            }
258        }
259
260        return handled;
261    }
262
263    public boolean canSelectArbitrarily() {
264        return true;
265    }
266
267    public void initialize(TextView widget, Spannable text) {
268        Selection.setSelection(text, 0);
269    }
270
271    public void onTakeFocus(TextView view, Spannable text, int dir) {
272        if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
273            if (view.getLayout() == null) {
274                // This shouldn't be null, but do something sensible if it is.
275                Selection.setSelection(text, text.length());
276            }
277        } else {
278            Selection.setSelection(text, text.length());
279        }
280    }
281
282    public static MovementMethod getInstance() {
283        if (sInstance == null) {
284            sInstance = new ArrowKeyMovementMethod();
285        }
286
287        return sInstance;
288    }
289
290
291    private static final Object LAST_TAP_DOWN = new Object();
292    private static ArrowKeyMovementMethod sInstance;
293}
294