ArrowKeyMovementMethod.java revision 1c9aefd471cec85f905bea4099f4a641f347e0a0
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.util.Log;
20import android.view.KeyEvent;
21import android.graphics.Rect;
22import android.text.*;
23import android.widget.TextView;
24import android.view.View;
25import android.view.MotionEvent;
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
32ArrowKeyMovementMethod
33implements MovementMethod
34{
35    private boolean up(TextView widget, Spannable buffer) {
36        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
37                        KeyEvent.META_SHIFT_ON) == 1) ||
38                      (MetaKeyKeyListener.getMetaState(buffer,
39                        MetaKeyKeyListener.META_SELECTING) != 0);
40        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
41                        KeyEvent.META_ALT_ON) == 1;
42        Layout layout = widget.getLayout();
43
44        if (cap) {
45            if (alt) {
46                Selection.extendSelection(buffer, 0);
47                return true;
48            } else {
49                return Selection.extendUp(buffer, layout);
50            }
51        } else {
52            if (alt) {
53                Selection.setSelection(buffer, 0);
54                return true;
55            } else {
56                return Selection.moveUp(buffer, layout);
57            }
58        }
59    }
60
61    private boolean down(TextView widget, Spannable buffer) {
62        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
63                        KeyEvent.META_SHIFT_ON) == 1) ||
64                      (MetaKeyKeyListener.getMetaState(buffer,
65                        MetaKeyKeyListener.META_SELECTING) != 0);
66        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
67                        KeyEvent.META_ALT_ON) == 1;
68        Layout layout = widget.getLayout();
69
70        if (cap) {
71            if (alt) {
72                Selection.extendSelection(buffer, buffer.length());
73                return true;
74            } else {
75                return Selection.extendDown(buffer, layout);
76            }
77        } else {
78            if (alt) {
79                Selection.setSelection(buffer, buffer.length());
80                return true;
81            } else {
82                return Selection.moveDown(buffer, layout);
83            }
84        }
85    }
86
87    private boolean left(TextView widget, Spannable buffer) {
88        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
89                        KeyEvent.META_SHIFT_ON) == 1) ||
90                      (MetaKeyKeyListener.getMetaState(buffer,
91                        MetaKeyKeyListener.META_SELECTING) != 0);
92        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
93                        KeyEvent.META_ALT_ON) == 1;
94        Layout layout = widget.getLayout();
95
96        if (cap) {
97            if (alt) {
98                return Selection.extendToLeftEdge(buffer, layout);
99            } else {
100                return Selection.extendLeft(buffer, layout);
101            }
102        } else {
103            if (alt) {
104                return Selection.moveToLeftEdge(buffer, layout);
105            } else {
106                return Selection.moveLeft(buffer, layout);
107            }
108        }
109    }
110
111    private boolean right(TextView widget, Spannable buffer) {
112        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
113                        KeyEvent.META_SHIFT_ON) == 1) ||
114                      (MetaKeyKeyListener.getMetaState(buffer,
115                        MetaKeyKeyListener.META_SELECTING) != 0);
116        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
117                        KeyEvent.META_ALT_ON) == 1;
118        Layout layout = widget.getLayout();
119
120        if (cap) {
121            if (alt) {
122                return Selection.extendToRightEdge(buffer, layout);
123            } else {
124                return Selection.extendRight(buffer, layout);
125            }
126        } else {
127            if (alt) {
128                return Selection.moveToRightEdge(buffer, layout);
129            } else {
130                return Selection.moveRight(buffer, layout);
131            }
132        }
133    }
134
135    public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
136        if (executeDown(widget, buffer, keyCode)) {
137            MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
138            MetaKeyKeyListener.resetLockedMeta(buffer);
139            return true;
140        }
141
142        return false;
143    }
144
145    private boolean executeDown(TextView widget, Spannable buffer, int keyCode) {
146        boolean handled = false;
147
148        switch (keyCode) {
149        case KeyEvent.KEYCODE_DPAD_UP:
150            handled |= up(widget, buffer);
151            break;
152
153        case KeyEvent.KEYCODE_DPAD_DOWN:
154            handled |= down(widget, buffer);
155            break;
156
157        case KeyEvent.KEYCODE_DPAD_LEFT:
158            handled |= left(widget, buffer);
159            break;
160
161        case KeyEvent.KEYCODE_DPAD_RIGHT:
162            handled |= right(widget, buffer);
163            break;
164
165        case KeyEvent.KEYCODE_DPAD_CENTER:
166            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
167                if (widget.showContextMenu()) {
168                    handled = true;
169                }
170            }
171        }
172
173        if (handled) {
174            MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
175            MetaKeyKeyListener.resetLockedMeta(buffer);
176        }
177
178        return handled;
179    }
180
181    public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
182        return false;
183    }
184
185    public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
186        int code = event.getKeyCode();
187        if (code != KeyEvent.KEYCODE_UNKNOWN
188                && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
189            int repeat = event.getRepeatCount();
190            boolean handled = false;
191            while ((--repeat) > 0) {
192                handled |= executeDown(view, text, code);
193            }
194            return handled;
195        }
196        return false;
197    }
198
199    public boolean onTrackballEvent(TextView widget, Spannable text,
200            MotionEvent event) {
201        return false;
202    }
203
204    public boolean onTouchEvent(TextView widget, Spannable buffer,
205                                MotionEvent event) {
206        int initialScrollX = -1, initialScrollY = -1;
207        if (event.getAction() == MotionEvent.ACTION_UP) {
208            initialScrollX = Touch.getInitialScrollX(widget, buffer);
209            initialScrollY = Touch.getInitialScrollY(widget, buffer);
210        }
211
212        boolean handled = Touch.onTouchEvent(widget, buffer, event);
213
214        if (widget.isFocused() && !widget.didTouchFocusSelect()) {
215            if (event.getAction() == MotionEvent.ACTION_UP) {
216                // If we have scrolled, then the up shouldn't move the cursor,
217                // but we do need to make sure the cursor is still visible at
218                // the current scroll offset to avoid the scroll jumping later
219                // to show it.
220                if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) ||
221                        (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
222                    widget.moveCursorToVisibleOffset();
223                    return true;
224                }
225
226                int x = (int) event.getX();
227                int y = (int) event.getY();
228
229                x -= widget.getTotalPaddingLeft();
230                y -= widget.getTotalPaddingTop();
231
232                // Clamp the position to inside of the view.
233                if (x < 0) {
234                    x = 0;
235                } else if (x >= (widget.getWidth()-widget.getTotalPaddingRight())) {
236                    x = widget.getWidth()-widget.getTotalPaddingRight() - 1;
237                }
238                if (y < 0) {
239                    y = 0;
240                } else if (y >= (widget.getHeight()-widget.getTotalPaddingBottom())) {
241                    y = widget.getHeight()-widget.getTotalPaddingBottom() - 1;
242                }
243
244                x += widget.getScrollX();
245                y += widget.getScrollY();
246
247                Layout layout = widget.getLayout();
248                int line = layout.getLineForVertical(y);
249
250                int off = layout.getOffsetForHorizontal(line, x);
251
252                // XXX should do the same adjust for x as we do for the line.
253
254                boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
255                                KeyEvent.META_SHIFT_ON) == 1) ||
256                              (MetaKeyKeyListener.getMetaState(buffer,
257                                MetaKeyKeyListener.META_SELECTING) != 0);
258
259                if (cap) {
260                    Selection.extendSelection(buffer, off);
261                } else {
262                    Selection.setSelection(buffer, off);
263                }
264
265                MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
266                MetaKeyKeyListener.resetLockedMeta(buffer);
267
268                return true;
269            }
270        }
271
272        return handled;
273    }
274
275    public boolean canSelectArbitrarily() {
276        return true;
277    }
278
279    public void initialize(TextView widget, Spannable text) {
280        Selection.setSelection(text, 0);
281    }
282
283    public void onTakeFocus(TextView view, Spannable text, int dir) {
284        if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
285            Layout layout = view.getLayout();
286
287            if (layout == null) {
288                /*
289                 * This shouldn't be null, but do something sensible if it is.
290                 */
291                Selection.setSelection(text, text.length());
292            } else {
293                /*
294                 * Put the cursor at the end of the first line, which is
295                 * either the last offset if there is only one line, or the
296                 * offset before the first character of the second line
297                 * if there is more than one line.
298                 */
299                if (layout.getLineCount() == 1) {
300                    Selection.setSelection(text, text.length());
301                } else {
302                    Selection.setSelection(text, layout.getLineStart(1) - 1);
303                }
304            }
305        } else {
306            Selection.setSelection(text, text.length());
307        }
308    }
309
310    public static MovementMethod getInstance() {
311        if (sInstance == null)
312            sInstance = new ArrowKeyMovementMethod();
313
314        return sInstance;
315    }
316
317    private static ArrowKeyMovementMethod sInstance;
318}
319