• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.text.method;
18 
19 import android.util.Log;
20 import android.view.KeyEvent;
21 import android.graphics.Rect;
22 import android.text.*;
23 import android.widget.TextView;
24 import android.view.View;
25 import android.view.ViewConfiguration;
26 import android.view.MotionEvent;
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 
32 public class
33 ArrowKeyMovementMethod
34 implements MovementMethod
35 {
up(TextView widget, Spannable buffer)36     private boolean up(TextView widget, Spannable buffer) {
37         boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
38                         KeyEvent.META_SHIFT_ON) == 1) ||
39                       (MetaKeyKeyListener.getMetaState(buffer,
40                         MetaKeyKeyListener.META_SELECTING) != 0);
41         boolean alt = MetaKeyKeyListener.getMetaState(buffer,
42                         KeyEvent.META_ALT_ON) == 1;
43         Layout layout = widget.getLayout();
44 
45         if (cap) {
46             if (alt) {
47                 Selection.extendSelection(buffer, 0);
48                 return true;
49             } else {
50                 return Selection.extendUp(buffer, layout);
51             }
52         } else {
53             if (alt) {
54                 Selection.setSelection(buffer, 0);
55                 return true;
56             } else {
57                 return Selection.moveUp(buffer, layout);
58             }
59         }
60     }
61 
down(TextView widget, Spannable buffer)62     private boolean down(TextView widget, Spannable buffer) {
63         boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
64                         KeyEvent.META_SHIFT_ON) == 1) ||
65                       (MetaKeyKeyListener.getMetaState(buffer,
66                         MetaKeyKeyListener.META_SELECTING) != 0);
67         boolean alt = MetaKeyKeyListener.getMetaState(buffer,
68                         KeyEvent.META_ALT_ON) == 1;
69         Layout layout = widget.getLayout();
70 
71         if (cap) {
72             if (alt) {
73                 Selection.extendSelection(buffer, buffer.length());
74                 return true;
75             } else {
76                 return Selection.extendDown(buffer, layout);
77             }
78         } else {
79             if (alt) {
80                 Selection.setSelection(buffer, buffer.length());
81                 return true;
82             } else {
83                 return Selection.moveDown(buffer, layout);
84             }
85         }
86     }
87 
left(TextView widget, Spannable buffer)88     private boolean left(TextView widget, Spannable buffer) {
89         boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
90                         KeyEvent.META_SHIFT_ON) == 1) ||
91                       (MetaKeyKeyListener.getMetaState(buffer,
92                         MetaKeyKeyListener.META_SELECTING) != 0);
93         boolean alt = MetaKeyKeyListener.getMetaState(buffer,
94                         KeyEvent.META_ALT_ON) == 1;
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 
right(TextView widget, Spannable buffer)112     private boolean right(TextView widget, Spannable buffer) {
113         boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
114                         KeyEvent.META_SHIFT_ON) == 1) ||
115                       (MetaKeyKeyListener.getMetaState(buffer,
116                         MetaKeyKeyListener.META_SELECTING) != 0);
117         boolean alt = MetaKeyKeyListener.getMetaState(buffer,
118                         KeyEvent.META_ALT_ON) == 1;
119         Layout layout = widget.getLayout();
120 
121         if (cap) {
122             if (alt) {
123                 return Selection.extendToRightEdge(buffer, layout);
124             } else {
125                 return Selection.extendRight(buffer, layout);
126             }
127         } else {
128             if (alt) {
129                 return Selection.moveToRightEdge(buffer, layout);
130             } else {
131                 return Selection.moveRight(buffer, layout);
132             }
133         }
134     }
135 
onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event)136     public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
137         if (executeDown(widget, buffer, keyCode)) {
138             MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
139             MetaKeyKeyListener.resetLockedMeta(buffer);
140             return true;
141         }
142 
143         return false;
144     }
145 
executeDown(TextView widget, Spannable buffer, int keyCode)146     private boolean executeDown(TextView widget, Spannable buffer, int keyCode) {
147         boolean handled = false;
148 
149         switch (keyCode) {
150         case KeyEvent.KEYCODE_DPAD_UP:
151             handled |= up(widget, buffer);
152             break;
153 
154         case KeyEvent.KEYCODE_DPAD_DOWN:
155             handled |= down(widget, buffer);
156             break;
157 
158         case KeyEvent.KEYCODE_DPAD_LEFT:
159             handled |= left(widget, buffer);
160             break;
161 
162         case KeyEvent.KEYCODE_DPAD_RIGHT:
163             handled |= right(widget, buffer);
164             break;
165 
166         case KeyEvent.KEYCODE_DPAD_CENTER:
167             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
168                 if (widget.showContextMenu()) {
169                     handled = true;
170                 }
171             }
172         }
173 
174         if (handled) {
175             MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
176             MetaKeyKeyListener.resetLockedMeta(buffer);
177         }
178 
179         return handled;
180     }
181 
onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event)182     public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
183         return false;
184     }
185 
onKeyOther(TextView view, Spannable text, KeyEvent event)186     public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
187         int code = event.getKeyCode();
188         if (code != KeyEvent.KEYCODE_UNKNOWN
189                 && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
190             int repeat = event.getRepeatCount();
191             boolean handled = false;
192             while ((--repeat) > 0) {
193                 handled |= executeDown(view, text, code);
194             }
195             return handled;
196         }
197         return false;
198     }
199 
onTrackballEvent(TextView widget, Spannable text, MotionEvent event)200     public boolean onTrackballEvent(TextView widget, Spannable text,
201             MotionEvent event) {
202         return false;
203     }
204 
onTouchEvent(TextView widget, Spannable buffer, MotionEvent event)205     public boolean onTouchEvent(TextView widget, Spannable buffer,
206                                 MotionEvent event) {
207         int initialScrollX = -1, initialScrollY = -1;
208         if (event.getAction() == MotionEvent.ACTION_UP) {
209             initialScrollX = Touch.getInitialScrollX(widget, buffer);
210             initialScrollY = Touch.getInitialScrollY(widget, buffer);
211         }
212 
213         boolean handled = Touch.onTouchEvent(widget, buffer, event);
214 
215         if (widget.isFocused() && !widget.didTouchFocusSelect()) {
216             if (event.getAction() == MotionEvent.ACTION_UP) {
217                 // If we have scrolled, then the up shouldn't move the cursor,
218                 // but we do need to make sure the cursor is still visible at
219                 // the current scroll offset to avoid the scroll jumping later
220                 // to show it.
221                 if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) ||
222                         (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
223                     widget.moveCursorToVisibleOffset();
224                     return true;
225                 }
226 
227                 int x = (int) event.getX();
228                 int y = (int) event.getY();
229 
230                 x -= widget.getTotalPaddingLeft();
231                 y -= widget.getTotalPaddingTop();
232 
233                 // Clamp the position to inside of the view.
234                 if (x < 0) {
235                     x = 0;
236                 } else if (x >= (widget.getWidth()-widget.getTotalPaddingRight())) {
237                     x = widget.getWidth()-widget.getTotalPaddingRight() - 1;
238                 }
239                 if (y < 0) {
240                     y = 0;
241                 } else if (y >= (widget.getHeight()-widget.getTotalPaddingBottom())) {
242                     y = widget.getHeight()-widget.getTotalPaddingBottom() - 1;
243                 }
244 
245                 x += widget.getScrollX();
246                 y += widget.getScrollY();
247 
248                 Layout layout = widget.getLayout();
249                 int line = layout.getLineForVertical(y);
250 
251                 int off = layout.getOffsetForHorizontal(line, x);
252 
253                 // XXX should do the same adjust for x as we do for the line.
254 
255                 boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
256                                 KeyEvent.META_SHIFT_ON) == 1) ||
257                               (MetaKeyKeyListener.getMetaState(buffer,
258                                 MetaKeyKeyListener.META_SELECTING) != 0);
259 
260                 DoubleTapState[] tap = buffer.getSpans(0, buffer.length(),
261                                                        DoubleTapState.class);
262                 boolean doubletap = false;
263 
264                 if (tap.length > 0) {
265                     if (event.getEventTime() - tap[0].mWhen <=
266                         ViewConfiguration.getDoubleTapTimeout()) {
267                         if (sameWord(buffer, off, Selection.getSelectionEnd(buffer))) {
268                             doubletap = true;
269                         }
270                     }
271 
272                     tap[0].mWhen = event.getEventTime();
273                 } else {
274                     DoubleTapState newtap = new DoubleTapState();
275                     newtap.mWhen = event.getEventTime();
276                     buffer.setSpan(newtap, 0, buffer.length(),
277                                    Spannable.SPAN_INCLUSIVE_INCLUSIVE);
278                 }
279 
280                 if (cap) {
281                     Selection.extendSelection(buffer, off);
282                 } else if (doubletap) {
283                     Selection.setSelection(buffer,
284                                            findWordStart(buffer, off),
285                                            findWordEnd(buffer, off));
286                 } else {
287                     Selection.setSelection(buffer, off);
288                 }
289 
290                 MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
291                 MetaKeyKeyListener.resetLockedMeta(buffer);
292 
293                 return true;
294             }
295         }
296 
297         return handled;
298     }
299 
300     private static class DoubleTapState implements NoCopySpan {
301         long mWhen;
302     }
303 
sameWord(CharSequence text, int one, int two)304     private static boolean sameWord(CharSequence text, int one, int two) {
305         int start = findWordStart(text, one);
306         int end = findWordEnd(text, one);
307 
308         if (end == start) {
309             return false;
310         }
311 
312         return start == findWordStart(text, two) &&
313                end == findWordEnd(text, two);
314     }
315 
316     // TODO: Unify with TextView.getWordForDictionary()
findWordStart(CharSequence text, int start)317     private static int findWordStart(CharSequence text, int start) {
318         for (; start > 0; start--) {
319             char c = text.charAt(start - 1);
320             int type = Character.getType(c);
321 
322             if (c != '\'' &&
323                 type != Character.UPPERCASE_LETTER &&
324                 type != Character.LOWERCASE_LETTER &&
325                 type != Character.TITLECASE_LETTER &&
326                 type != Character.MODIFIER_LETTER &&
327                 type != Character.DECIMAL_DIGIT_NUMBER) {
328                 break;
329             }
330         }
331 
332         return start;
333     }
334 
335     // TODO: Unify with TextView.getWordForDictionary()
findWordEnd(CharSequence text, int end)336     private static int findWordEnd(CharSequence text, int end) {
337         int len = text.length();
338 
339         for (; end < len; end++) {
340             char c = text.charAt(end);
341             int type = Character.getType(c);
342 
343             if (c != '\'' &&
344                 type != Character.UPPERCASE_LETTER &&
345                 type != Character.LOWERCASE_LETTER &&
346                 type != Character.TITLECASE_LETTER &&
347                 type != Character.MODIFIER_LETTER &&
348                 type != Character.DECIMAL_DIGIT_NUMBER) {
349                 break;
350             }
351         }
352 
353         return end;
354     }
355 
canSelectArbitrarily()356     public boolean canSelectArbitrarily() {
357         return true;
358     }
359 
initialize(TextView widget, Spannable text)360     public void initialize(TextView widget, Spannable text) {
361         Selection.setSelection(text, 0);
362     }
363 
onTakeFocus(TextView view, Spannable text, int dir)364     public void onTakeFocus(TextView view, Spannable text, int dir) {
365         if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
366             Layout layout = view.getLayout();
367 
368             if (layout == null) {
369                 /*
370                  * This shouldn't be null, but do something sensible if it is.
371                  */
372                 Selection.setSelection(text, text.length());
373             } else {
374                 /*
375                  * Put the cursor at the end of the first line, which is
376                  * either the last offset if there is only one line, or the
377                  * offset before the first character of the second line
378                  * if there is more than one line.
379                  */
380                 if (layout.getLineCount() == 1) {
381                     Selection.setSelection(text, text.length());
382                 } else {
383                     Selection.setSelection(text, layout.getLineStart(1) - 1);
384                 }
385             }
386         } else {
387             Selection.setSelection(text, text.length());
388         }
389     }
390 
getInstance()391     public static MovementMethod getInstance() {
392         if (sInstance == null)
393             sInstance = new ArrowKeyMovementMethod();
394 
395         return sInstance;
396     }
397 
398     private static ArrowKeyMovementMethod sInstance;
399 }
400