blob: 94c6ed08c1e1f55091459b8649b981c6342ac90d [file] [log] [blame]
Jeff Brown67b6ab72010-12-17 18:33:02 -08001/*
2 * Copyright (C) 2010 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.Spannable;
Jeff Brown8f345672011-02-26 13:29:53 -080021import android.view.InputDevice;
Jeff Brown67b6ab72010-12-17 18:33:02 -080022import android.view.KeyEvent;
23import android.view.MotionEvent;
24import android.widget.TextView;
25
26/**
27 * Base classes for movement methods.
28 */
29public class BaseMovementMethod implements MovementMethod {
30 @Override
31 public boolean canSelectArbitrarily() {
32 return false;
33 }
34
35 @Override
36 public void initialize(TextView widget, Spannable text) {
37 }
38
39 @Override
40 public boolean onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event) {
41 final int movementMetaState = getMovementMetaState(text, event);
42 boolean handled = handleMovementKey(widget, text, keyCode, movementMetaState, event);
43 if (handled) {
44 MetaKeyKeyListener.adjustMetaAfterKeypress(text);
45 MetaKeyKeyListener.resetLockedMeta(text);
46 }
47 return handled;
48 }
49
50 @Override
51 public boolean onKeyOther(TextView widget, Spannable text, KeyEvent event) {
52 final int movementMetaState = getMovementMetaState(text, event);
53 final int keyCode = event.getKeyCode();
54 if (keyCode != KeyEvent.KEYCODE_UNKNOWN
55 && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
56 final int repeat = event.getRepeatCount();
57 boolean handled = false;
58 for (int i = 0; i < repeat; i++) {
59 if (!handleMovementKey(widget, text, keyCode, movementMetaState, event)) {
60 break;
61 }
62 handled = true;
63 }
64 if (handled) {
65 MetaKeyKeyListener.adjustMetaAfterKeypress(text);
66 MetaKeyKeyListener.resetLockedMeta(text);
67 }
68 return handled;
69 }
70 return false;
71 }
72
73 @Override
74 public boolean onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event) {
75 return false;
76 }
77
78 @Override
79 public void onTakeFocus(TextView widget, Spannable text, int direction) {
80 }
81
82 @Override
83 public boolean onTouchEvent(TextView widget, Spannable text, MotionEvent event) {
84 return false;
85 }
86
87 @Override
88 public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) {
89 return false;
90 }
91
Jeff Brown8f345672011-02-26 13:29:53 -080092 @Override
93 public boolean onGenericMotionEvent(TextView widget, Spannable text, MotionEvent event) {
94 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
95 switch (event.getAction()) {
96 case MotionEvent.ACTION_SCROLL: {
97 final float vscroll;
98 final float hscroll;
99 if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
100 vscroll = 0;
101 hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
102 } else {
103 vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
104 hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
105 }
106
107 boolean handled = false;
108 if (hscroll < 0) {
109 handled |= scrollLeft(widget, text, (int)Math.ceil(-hscroll));
110 } else if (hscroll > 0) {
111 handled |= scrollRight(widget, text, (int)Math.ceil(hscroll));
112 }
113 if (vscroll < 0) {
114 handled |= scrollUp(widget, text, (int)Math.ceil(-vscroll));
115 } else if (vscroll > 0) {
116 handled |= scrollDown(widget, text, (int)Math.ceil(vscroll));
117 }
118 return handled;
119 }
120 }
121 }
122 return false;
123 }
124
Jeff Brown67b6ab72010-12-17 18:33:02 -0800125 /**
126 * Gets the meta state used for movement using the modifiers tracked by the text
127 * buffer as well as those present in the key event.
128 *
129 * The movement meta state excludes the state of locked modifiers or the SHIFT key
130 * since they are not used by movement actions (but they may be used for selection).
131 *
132 * @param buffer The text buffer.
133 * @param event The key event.
134 * @return The keyboard meta states used for movement.
135 */
136 protected int getMovementMetaState(Spannable buffer, KeyEvent event) {
137 // We ignore locked modifiers and SHIFT.
138 int metaState = (event.getMetaState() | MetaKeyKeyListener.getMetaState(buffer))
139 & ~(MetaKeyKeyListener.META_ALT_LOCKED | MetaKeyKeyListener.META_SYM_LOCKED);
140 return KeyEvent.normalizeMetaState(metaState) & ~KeyEvent.META_SHIFT_MASK;
141 }
142
143 /**
144 * Performs a movement key action.
145 * The default implementation decodes the key down and invokes movement actions
146 * such as {@link #down} and {@link #up}.
147 * {@link #onKeyDown(TextView, Spannable, int, KeyEvent)} calls this method once
148 * to handle an {@link KeyEvent#ACTION_DOWN}.
149 * {@link #onKeyOther(TextView, Spannable, KeyEvent)} calls this method repeatedly
150 * to handle each repetition of an {@link KeyEvent#ACTION_MULTIPLE}.
151 *
152 * @param widget The text view.
153 * @param buffer The text buffer.
154 * @param event The key event.
155 * @param keyCode The key code.
156 * @param movementMetaState The keyboard meta states used for movement.
157 * @param event The key event.
158 * @return True if the event was handled.
159 */
160 protected boolean handleMovementKey(TextView widget, Spannable buffer,
161 int keyCode, int movementMetaState, KeyEvent event) {
162 switch (keyCode) {
163 case KeyEvent.KEYCODE_DPAD_LEFT:
164 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
165 return left(widget, buffer);
166 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
167 KeyEvent.META_ALT_ON)) {
168 return lineStart(widget, buffer);
169 }
170 break;
171
172 case KeyEvent.KEYCODE_DPAD_RIGHT:
173 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
174 return right(widget, buffer);
175 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
176 KeyEvent.META_ALT_ON)) {
177 return lineEnd(widget, buffer);
178 }
179 break;
180
181 case KeyEvent.KEYCODE_DPAD_UP:
182 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
183 return up(widget, buffer);
184 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
185 KeyEvent.META_ALT_ON)) {
186 return top(widget, buffer);
187 }
188 break;
189
190 case KeyEvent.KEYCODE_DPAD_DOWN:
191 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
192 return down(widget, buffer);
193 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
194 KeyEvent.META_ALT_ON)) {
195 return bottom(widget, buffer);
196 }
197 break;
198
199 case KeyEvent.KEYCODE_PAGE_UP:
200 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
201 return pageUp(widget, buffer);
202 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
203 KeyEvent.META_ALT_ON)) {
204 return top(widget, buffer);
205 }
206 break;
207
208 case KeyEvent.KEYCODE_PAGE_DOWN:
209 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
210 return pageDown(widget, buffer);
211 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
212 KeyEvent.META_ALT_ON)) {
213 return bottom(widget, buffer);
214 }
215 break;
216
217 case KeyEvent.KEYCODE_MOVE_HOME:
218 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
219 return home(widget, buffer);
220 }
221 break;
222
223 case KeyEvent.KEYCODE_MOVE_END:
224 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
225 return end(widget, buffer);
226 }
227 break;
228 }
229 return false;
230 }
231
232 /**
233 * Performs a left movement action.
234 * Moves the cursor or scrolls left by one character.
235 *
236 * @param widget The text view.
237 * @param buffer The text buffer.
238 * @return True if the event was handled.
239 */
240 protected boolean left(TextView widget, Spannable buffer) {
241 return false;
242 }
243
244 /**
245 * Performs a right movement action.
246 * Moves the cursor or scrolls right by one character.
247 *
248 * @param widget The text view.
249 * @param buffer The text buffer.
250 * @return True if the event was handled.
251 */
252 protected boolean right(TextView widget, Spannable buffer) {
253 return false;
254 }
255
256 /**
257 * Performs an up movement action.
258 * Moves the cursor or scrolls up by one line.
259 *
260 * @param widget The text view.
261 * @param buffer The text buffer.
262 * @return True if the event was handled.
263 */
264 protected boolean up(TextView widget, Spannable buffer) {
265 return false;
266 }
267
268 /**
269 * Performs a down movement action.
270 * Moves the cursor or scrolls down by one line.
271 *
272 * @param widget The text view.
273 * @param buffer The text buffer.
274 * @return True if the event was handled.
275 */
276 protected boolean down(TextView widget, Spannable buffer) {
277 return false;
278 }
279
280 /**
281 * Performs a page-up movement action.
282 * Moves the cursor or scrolls up by one page.
283 *
284 * @param widget The text view.
285 * @param buffer The text buffer.
286 * @return True if the event was handled.
287 */
288 protected boolean pageUp(TextView widget, Spannable buffer) {
289 return false;
290 }
291
292 /**
293 * Performs a page-down movement action.
294 * Moves the cursor or scrolls down by one page.
295 *
296 * @param widget The text view.
297 * @param buffer The text buffer.
298 * @return True if the event was handled.
299 */
300 protected boolean pageDown(TextView widget, Spannable buffer) {
301 return false;
302 }
303
304 /**
305 * Performs a top movement action.
306 * Moves the cursor or scrolls to the top of the buffer.
307 *
308 * @param widget The text view.
309 * @param buffer The text buffer.
310 * @return True if the event was handled.
311 */
312 protected boolean top(TextView widget, Spannable buffer) {
313 return false;
314 }
315
316 /**
317 * Performs a bottom movement action.
318 * Moves the cursor or scrolls to the bottom of the buffer.
319 *
320 * @param widget The text view.
321 * @param buffer The text buffer.
322 * @return True if the event was handled.
323 */
324 protected boolean bottom(TextView widget, Spannable buffer) {
325 return false;
326 }
327
328 /**
329 * Performs a line-start movement action.
330 * Moves the cursor or scrolls to the start of the line.
331 *
332 * @param widget The text view.
333 * @param buffer The text buffer.
334 * @return True if the event was handled.
335 */
336 protected boolean lineStart(TextView widget, Spannable buffer) {
337 return false;
338 }
339
340 /**
341 * Performs an line-end movement action.
342 * Moves the cursor or scrolls to the end of the line.
343 *
344 * @param widget The text view.
345 * @param buffer The text buffer.
346 * @return True if the event was handled.
347 */
348 protected boolean lineEnd(TextView widget, Spannable buffer) {
349 return false;
350 }
351
352 /**
353 * Performs a home movement action.
354 * Moves the cursor or scrolls to the start of the line or to the top of the
355 * document depending on whether the insertion point is being moved or
356 * the document is being scrolled.
357 *
358 * @param widget The text view.
359 * @param buffer The text buffer.
360 * @return True if the event was handled.
361 */
362 protected boolean home(TextView widget, Spannable buffer) {
363 return false;
364 }
365
366 /**
367 * Performs an end movement action.
368 * Moves the cursor or scrolls to the start of the line or to the top of the
369 * document depending on whether the insertion point is being moved or
370 * the document is being scrolled.
371 *
372 * @param widget The text view.
373 * @param buffer The text buffer.
374 * @return True if the event was handled.
375 */
376 protected boolean end(TextView widget, Spannable buffer) {
377 return false;
378 }
Jeff Brown8f345672011-02-26 13:29:53 -0800379
380 private int getTopLine(TextView widget) {
381 return widget.getLayout().getLineForVertical(widget.getScrollY());
382 }
383
384 private int getBottomLine(TextView widget) {
385 return widget.getLayout().getLineForVertical(widget.getScrollY() + getInnerHeight(widget));
386 }
387
388 private int getInnerWidth(TextView widget) {
389 return widget.getWidth() - widget.getTotalPaddingLeft() - widget.getTotalPaddingRight();
390 }
391
392 private int getInnerHeight(TextView widget) {
393 return widget.getHeight() - widget.getTotalPaddingTop() - widget.getTotalPaddingBottom();
394 }
395
396 private int getCharacterWidth(TextView widget) {
397 return (int) Math.ceil(widget.getPaint().getFontSpacing());
398 }
399
400 private int getScrollBoundsLeft(TextView widget) {
401 final Layout layout = widget.getLayout();
402 final int topLine = getTopLine(widget);
403 final int bottomLine = getBottomLine(widget);
404 if (topLine > bottomLine) {
405 return 0;
406 }
407 int left = Integer.MAX_VALUE;
408 for (int line = topLine; line <= bottomLine; line++) {
409 final int lineLeft = (int) Math.floor(layout.getLineLeft(line));
410 if (lineLeft < left) {
411 left = lineLeft;
412 }
413 }
414 return left;
415 }
416
417 private int getScrollBoundsRight(TextView widget) {
418 final Layout layout = widget.getLayout();
419 final int topLine = getTopLine(widget);
420 final int bottomLine = getBottomLine(widget);
421 if (topLine > bottomLine) {
422 return 0;
423 }
424 int right = Integer.MIN_VALUE;
425 for (int line = topLine; line <= bottomLine; line++) {
426 final int lineRight = (int) Math.ceil(layout.getLineRight(line));
427 if (lineRight > right) {
428 right = lineRight;
429 }
430 }
431 return right;
432 }
433
434 /**
435 * Performs a scroll left action.
436 * Scrolls left by the specified number of characters.
437 *
438 * @param widget The text view.
439 * @param buffer The text buffer.
440 * @param amount The number of characters to scroll by. Must be at least 1.
441 * @return True if the event was handled.
442 * @hide
443 */
444 protected boolean scrollLeft(TextView widget, Spannable buffer, int amount) {
445 final int minScrollX = getScrollBoundsLeft(widget);
446 int scrollX = widget.getScrollX();
447 if (scrollX > minScrollX) {
448 scrollX = Math.max(scrollX - getCharacterWidth(widget) * amount, minScrollX);
449 widget.scrollTo(scrollX, widget.getScrollY());
450 return true;
451 }
452 return false;
453 }
454
455 /**
456 * Performs a scroll right action.
457 * Scrolls right by the specified number of characters.
458 *
459 * @param widget The text view.
460 * @param buffer The text buffer.
461 * @param amount The number of characters to scroll by. Must be at least 1.
462 * @return True if the event was handled.
463 * @hide
464 */
465 protected boolean scrollRight(TextView widget, Spannable buffer, int amount) {
466 final int maxScrollX = getScrollBoundsRight(widget) - getInnerWidth(widget);
467 int scrollX = widget.getScrollX();
468 if (scrollX < maxScrollX) {
469 scrollX = Math.min(scrollX + getCharacterWidth(widget) * amount, maxScrollX);
470 widget.scrollTo(scrollX, widget.getScrollY());
471 return true;
472 }
473 return false;
474 }
475
476 /**
477 * Performs a scroll up action.
478 * Scrolls up by the specified number of lines.
479 *
480 * @param widget The text view.
481 * @param buffer The text buffer.
482 * @param amount The number of lines to scroll by. Must be at least 1.
483 * @return True if the event was handled.
484 * @hide
485 */
486 protected boolean scrollUp(TextView widget, Spannable buffer, int amount) {
487 final Layout layout = widget.getLayout();
488 final int top = widget.getScrollY();
489 int topLine = layout.getLineForVertical(top);
490 if (layout.getLineTop(topLine) == top) {
491 // If the top line is partially visible, bring it all the way
492 // into view; otherwise, bring the previous line into view.
493 topLine -= 1;
494 }
495 if (topLine >= 0) {
496 topLine = Math.max(topLine - amount + 1, 0);
497 Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(topLine));
498 return true;
499 }
500 return false;
501 }
502
503 /**
504 * Performs a scroll down action.
505 * Scrolls down by the specified number of lines.
506 *
507 * @param widget The text view.
508 * @param buffer The text buffer.
509 * @param amount The number of lines to scroll by. Must be at least 1.
510 * @return True if the event was handled.
511 * @hide
512 */
513 protected boolean scrollDown(TextView widget, Spannable buffer, int amount) {
514 final Layout layout = widget.getLayout();
515 final int innerHeight = getInnerHeight(widget);
516 final int bottom = widget.getScrollY() + innerHeight;
517 int bottomLine = layout.getLineForVertical(bottom);
518 if (layout.getLineTop(bottomLine + 1) < bottom + 1) {
519 // Less than a pixel of this line is out of view,
520 // so we must have tried to make it entirely in view
521 // and now want the next line to be in view instead.
522 bottomLine += 1;
523 }
524 final int limit = layout.getLineCount() - 1;
525 if (bottomLine <= limit) {
526 bottomLine = Math.min(bottomLine + amount - 1, limit);
527 Touch.scrollTo(widget, layout, widget.getScrollX(),
528 layout.getLineTop(bottomLine + 1) - innerHeight);
529 return true;
530 }
531 return false;
532 }
533
534 /**
535 * Performs a scroll page up action.
536 * Scrolls up by one page.
537 *
538 * @param widget The text view.
539 * @param buffer The text buffer.
540 * @return True if the event was handled.
541 * @hide
542 */
543 protected boolean scrollPageUp(TextView widget, Spannable buffer) {
544 final Layout layout = widget.getLayout();
545 final int top = widget.getScrollY() - getInnerHeight(widget);
546 int topLine = layout.getLineForVertical(top);
547 if (topLine >= 0) {
548 Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(topLine));
549 return true;
550 }
551 return false;
552 }
553
554 /**
555 * Performs a scroll page up action.
556 * Scrolls down by one page.
557 *
558 * @param widget The text view.
559 * @param buffer The text buffer.
560 * @return True if the event was handled.
561 * @hide
562 */
563 protected boolean scrollPageDown(TextView widget, Spannable buffer) {
564 final Layout layout = widget.getLayout();
565 final int innerHeight = getInnerHeight(widget);
566 final int bottom = widget.getScrollY() + innerHeight + innerHeight;
567 int bottomLine = layout.getLineForVertical(bottom);
568 if (bottomLine <= layout.getLineCount() - 1) {
569 Touch.scrollTo(widget, layout, widget.getScrollX(),
570 layout.getLineTop(bottomLine + 1) - innerHeight);
571 return true;
572 }
573 return false;
574 }
575
576 /**
577 * Performs a scroll to top action.
578 * Scrolls to the top of the document.
579 *
580 * @param widget The text view.
581 * @param buffer The text buffer.
582 * @return True if the event was handled.
583 * @hide
584 */
585 protected boolean scrollTop(TextView widget, Spannable buffer) {
586 final Layout layout = widget.getLayout();
587 if (getTopLine(widget) >= 0) {
588 Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(0));
589 return true;
590 }
591 return false;
592 }
593
594 /**
595 * Performs a scroll to bottom action.
596 * Scrolls to the bottom of the document.
597 *
598 * @param widget The text view.
599 * @param buffer The text buffer.
600 * @return True if the event was handled.
601 * @hide
602 */
603 protected boolean scrollBottom(TextView widget, Spannable buffer) {
604 final Layout layout = widget.getLayout();
605 final int lineCount = layout.getLineCount();
606 if (getBottomLine(widget) <= lineCount - 1) {
607 Touch.scrollTo(widget, layout, widget.getScrollX(),
608 layout.getLineTop(lineCount) - getInnerHeight(widget));
609 return true;
610 }
611 return false;
612 }
613
614 /**
615 * Performs a scroll to line start action.
616 * Scrolls to the start of the line.
617 *
618 * @param widget The text view.
619 * @param buffer The text buffer.
620 * @return True if the event was handled.
621 * @hide
622 */
623 protected boolean scrollLineStart(TextView widget, Spannable buffer) {
624 final int minScrollX = getScrollBoundsLeft(widget);
625 int scrollX = widget.getScrollX();
626 if (scrollX > minScrollX) {
627 widget.scrollTo(minScrollX, widget.getScrollY());
628 return true;
629 }
630 return false;
631 }
632
633 /**
634 * Performs a scroll to line end action.
635 * Scrolls to the end of the line.
636 *
637 * @param widget The text view.
638 * @param buffer The text buffer.
639 * @return True if the event was handled.
640 * @hide
641 */
642 protected boolean scrollLineEnd(TextView widget, Spannable buffer) {
643 final int maxScrollX = getScrollBoundsRight(widget) - getInnerWidth(widget);
644 int scrollX = widget.getScrollX();
645 if (scrollX < maxScrollX) {
646 widget.scrollTo(maxScrollX, widget.getScrollY());
647 return true;
648 }
649 return false;
650 }
Jeff Brown67b6ab72010-12-17 18:33:02 -0800651}