1 /* 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 17 package android.text.method; 18 19 import android.text.Layout; 20 import android.text.Spannable; 21 import android.view.InputDevice; 22 import android.view.KeyEvent; 23 import android.view.MotionEvent; 24 import android.widget.TextView; 25 26 /** 27 * Base classes for movement methods. 28 */ 29 public class BaseMovementMethod implements MovementMethod { 30 @Override canSelectArbitrarily()31 public boolean canSelectArbitrarily() { 32 return false; 33 } 34 35 @Override initialize(TextView widget, Spannable text)36 public void initialize(TextView widget, Spannable text) { 37 } 38 39 @Override onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event)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 onKeyOther(TextView widget, Spannable text, KeyEvent event)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 onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event)74 public boolean onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event) { 75 return false; 76 } 77 78 @Override onTakeFocus(TextView widget, Spannable text, int direction)79 public void onTakeFocus(TextView widget, Spannable text, int direction) { 80 } 81 82 @Override onTouchEvent(TextView widget, Spannable text, MotionEvent event)83 public boolean onTouchEvent(TextView widget, Spannable text, MotionEvent event) { 84 return false; 85 } 86 87 @Override onTrackballEvent(TextView widget, Spannable text, MotionEvent event)88 public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) { 89 return false; 90 } 91 92 @Override onGenericMotionEvent(TextView widget, Spannable text, MotionEvent event)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 125 /** 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 */ getMovementMetaState(Spannable buffer, KeyEvent event)136 protected int getMovementMetaState(Spannable buffer, KeyEvent event) { 137 // We ignore locked modifiers and SHIFT. 138 int metaState = MetaKeyKeyListener.getMetaState(buffer, event) 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 */ handleMovementKey(TextView widget, Spannable buffer, int keyCode, int movementMetaState, KeyEvent event)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_CTRL_ON)) { 168 return leftWord(widget, buffer); 169 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 170 KeyEvent.META_ALT_ON)) { 171 return lineStart(widget, buffer); 172 } 173 break; 174 175 case KeyEvent.KEYCODE_DPAD_RIGHT: 176 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { 177 return right(widget, buffer); 178 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 179 KeyEvent.META_CTRL_ON)) { 180 return rightWord(widget, buffer); 181 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 182 KeyEvent.META_ALT_ON)) { 183 return lineEnd(widget, buffer); 184 } 185 break; 186 187 case KeyEvent.KEYCODE_DPAD_UP: 188 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { 189 return up(widget, buffer); 190 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 191 KeyEvent.META_ALT_ON)) { 192 return top(widget, buffer); 193 } 194 break; 195 196 case KeyEvent.KEYCODE_DPAD_DOWN: 197 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { 198 return down(widget, buffer); 199 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 200 KeyEvent.META_ALT_ON)) { 201 return bottom(widget, buffer); 202 } 203 break; 204 205 case KeyEvent.KEYCODE_PAGE_UP: 206 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { 207 return pageUp(widget, buffer); 208 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 209 KeyEvent.META_ALT_ON)) { 210 return top(widget, buffer); 211 } 212 break; 213 214 case KeyEvent.KEYCODE_PAGE_DOWN: 215 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { 216 return pageDown(widget, buffer); 217 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 218 KeyEvent.META_ALT_ON)) { 219 return bottom(widget, buffer); 220 } 221 break; 222 223 case KeyEvent.KEYCODE_MOVE_HOME: 224 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { 225 return home(widget, buffer); 226 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 227 KeyEvent.META_CTRL_ON)) { 228 return top(widget, buffer); 229 } 230 break; 231 232 case KeyEvent.KEYCODE_MOVE_END: 233 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { 234 return end(widget, buffer); 235 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 236 KeyEvent.META_CTRL_ON)) { 237 return bottom(widget, buffer); 238 } 239 break; 240 } 241 return false; 242 } 243 244 /** 245 * Performs a left movement action. 246 * Moves the cursor or scrolls left 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 */ left(TextView widget, Spannable buffer)252 protected boolean left(TextView widget, Spannable buffer) { 253 return false; 254 } 255 256 /** 257 * Performs a right movement action. 258 * Moves the cursor or scrolls right by one character. 259 * 260 * @param widget The text view. 261 * @param buffer The text buffer. 262 * @return True if the event was handled. 263 */ right(TextView widget, Spannable buffer)264 protected boolean right(TextView widget, Spannable buffer) { 265 return false; 266 } 267 268 /** 269 * Performs an up movement action. 270 * Moves the cursor or scrolls up 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 */ up(TextView widget, Spannable buffer)276 protected boolean up(TextView widget, Spannable buffer) { 277 return false; 278 } 279 280 /** 281 * Performs a down movement action. 282 * Moves the cursor or scrolls down by one line. 283 * 284 * @param widget The text view. 285 * @param buffer The text buffer. 286 * @return True if the event was handled. 287 */ down(TextView widget, Spannable buffer)288 protected boolean down(TextView widget, Spannable buffer) { 289 return false; 290 } 291 292 /** 293 * Performs a page-up movement action. 294 * Moves the cursor or scrolls up 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 */ pageUp(TextView widget, Spannable buffer)300 protected boolean pageUp(TextView widget, Spannable buffer) { 301 return false; 302 } 303 304 /** 305 * Performs a page-down movement action. 306 * Moves the cursor or scrolls down by one page. 307 * 308 * @param widget The text view. 309 * @param buffer The text buffer. 310 * @return True if the event was handled. 311 */ pageDown(TextView widget, Spannable buffer)312 protected boolean pageDown(TextView widget, Spannable buffer) { 313 return false; 314 } 315 316 /** 317 * Performs a top movement action. 318 * Moves the cursor or scrolls to the top 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 */ top(TextView widget, Spannable buffer)324 protected boolean top(TextView widget, Spannable buffer) { 325 return false; 326 } 327 328 /** 329 * Performs a bottom movement action. 330 * Moves the cursor or scrolls to the bottom of the buffer. 331 * 332 * @param widget The text view. 333 * @param buffer The text buffer. 334 * @return True if the event was handled. 335 */ bottom(TextView widget, Spannable buffer)336 protected boolean bottom(TextView widget, Spannable buffer) { 337 return false; 338 } 339 340 /** 341 * Performs a line-start movement action. 342 * Moves the cursor or scrolls to the start 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 */ lineStart(TextView widget, Spannable buffer)348 protected boolean lineStart(TextView widget, Spannable buffer) { 349 return false; 350 } 351 352 /** 353 * Performs a line-end movement action. 354 * Moves the cursor or scrolls to the end of the line. 355 * 356 * @param widget The text view. 357 * @param buffer The text buffer. 358 * @return True if the event was handled. 359 */ lineEnd(TextView widget, Spannable buffer)360 protected boolean lineEnd(TextView widget, Spannable buffer) { 361 return false; 362 } 363 364 /** {@hide} */ leftWord(TextView widget, Spannable buffer)365 protected boolean leftWord(TextView widget, Spannable buffer) { 366 return false; 367 } 368 369 /** {@hide} */ rightWord(TextView widget, Spannable buffer)370 protected boolean rightWord(TextView widget, Spannable buffer) { 371 return false; 372 } 373 374 /** 375 * Performs a home movement action. 376 * Moves the cursor or scrolls to the start of the line or to the top of the 377 * document depending on whether the insertion point is being moved or 378 * the document is being scrolled. 379 * 380 * @param widget The text view. 381 * @param buffer The text buffer. 382 * @return True if the event was handled. 383 */ home(TextView widget, Spannable buffer)384 protected boolean home(TextView widget, Spannable buffer) { 385 return false; 386 } 387 388 /** 389 * Performs an end movement action. 390 * Moves the cursor or scrolls to the start of the line or to the top of the 391 * document depending on whether the insertion point is being moved or 392 * the document is being scrolled. 393 * 394 * @param widget The text view. 395 * @param buffer The text buffer. 396 * @return True if the event was handled. 397 */ end(TextView widget, Spannable buffer)398 protected boolean end(TextView widget, Spannable buffer) { 399 return false; 400 } 401 getTopLine(TextView widget)402 private int getTopLine(TextView widget) { 403 return widget.getLayout().getLineForVertical(widget.getScrollY()); 404 } 405 getBottomLine(TextView widget)406 private int getBottomLine(TextView widget) { 407 return widget.getLayout().getLineForVertical(widget.getScrollY() + getInnerHeight(widget)); 408 } 409 getInnerWidth(TextView widget)410 private int getInnerWidth(TextView widget) { 411 return widget.getWidth() - widget.getTotalPaddingLeft() - widget.getTotalPaddingRight(); 412 } 413 getInnerHeight(TextView widget)414 private int getInnerHeight(TextView widget) { 415 return widget.getHeight() - widget.getTotalPaddingTop() - widget.getTotalPaddingBottom(); 416 } 417 getCharacterWidth(TextView widget)418 private int getCharacterWidth(TextView widget) { 419 return (int) Math.ceil(widget.getPaint().getFontSpacing()); 420 } 421 getScrollBoundsLeft(TextView widget)422 private int getScrollBoundsLeft(TextView widget) { 423 final Layout layout = widget.getLayout(); 424 final int topLine = getTopLine(widget); 425 final int bottomLine = getBottomLine(widget); 426 if (topLine > bottomLine) { 427 return 0; 428 } 429 int left = Integer.MAX_VALUE; 430 for (int line = topLine; line <= bottomLine; line++) { 431 final int lineLeft = (int) Math.floor(layout.getLineLeft(line)); 432 if (lineLeft < left) { 433 left = lineLeft; 434 } 435 } 436 return left; 437 } 438 getScrollBoundsRight(TextView widget)439 private int getScrollBoundsRight(TextView widget) { 440 final Layout layout = widget.getLayout(); 441 final int topLine = getTopLine(widget); 442 final int bottomLine = getBottomLine(widget); 443 if (topLine > bottomLine) { 444 return 0; 445 } 446 int right = Integer.MIN_VALUE; 447 for (int line = topLine; line <= bottomLine; line++) { 448 final int lineRight = (int) Math.ceil(layout.getLineRight(line)); 449 if (lineRight > right) { 450 right = lineRight; 451 } 452 } 453 return right; 454 } 455 456 /** 457 * Performs a scroll left action. 458 * Scrolls left by the specified number of characters. 459 * 460 * @param widget The text view. 461 * @param buffer The text buffer. 462 * @param amount The number of characters to scroll by. Must be at least 1. 463 * @return True if the event was handled. 464 * @hide 465 */ scrollLeft(TextView widget, Spannable buffer, int amount)466 protected boolean scrollLeft(TextView widget, Spannable buffer, int amount) { 467 final int minScrollX = getScrollBoundsLeft(widget); 468 int scrollX = widget.getScrollX(); 469 if (scrollX > minScrollX) { 470 scrollX = Math.max(scrollX - getCharacterWidth(widget) * amount, minScrollX); 471 widget.scrollTo(scrollX, widget.getScrollY()); 472 return true; 473 } 474 return false; 475 } 476 477 /** 478 * Performs a scroll right action. 479 * Scrolls right by the specified number of characters. 480 * 481 * @param widget The text view. 482 * @param buffer The text buffer. 483 * @param amount The number of characters to scroll by. Must be at least 1. 484 * @return True if the event was handled. 485 * @hide 486 */ scrollRight(TextView widget, Spannable buffer, int amount)487 protected boolean scrollRight(TextView widget, Spannable buffer, int amount) { 488 final int maxScrollX = getScrollBoundsRight(widget) - getInnerWidth(widget); 489 int scrollX = widget.getScrollX(); 490 if (scrollX < maxScrollX) { 491 scrollX = Math.min(scrollX + getCharacterWidth(widget) * amount, maxScrollX); 492 widget.scrollTo(scrollX, widget.getScrollY()); 493 return true; 494 } 495 return false; 496 } 497 498 /** 499 * Performs a scroll up action. 500 * Scrolls up by the specified number of lines. 501 * 502 * @param widget The text view. 503 * @param buffer The text buffer. 504 * @param amount The number of lines to scroll by. Must be at least 1. 505 * @return True if the event was handled. 506 * @hide 507 */ scrollUp(TextView widget, Spannable buffer, int amount)508 protected boolean scrollUp(TextView widget, Spannable buffer, int amount) { 509 final Layout layout = widget.getLayout(); 510 final int top = widget.getScrollY(); 511 int topLine = layout.getLineForVertical(top); 512 if (layout.getLineTop(topLine) == top) { 513 // If the top line is partially visible, bring it all the way 514 // into view; otherwise, bring the previous line into view. 515 topLine -= 1; 516 } 517 if (topLine >= 0) { 518 topLine = Math.max(topLine - amount + 1, 0); 519 Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(topLine)); 520 return true; 521 } 522 return false; 523 } 524 525 /** 526 * Performs a scroll down action. 527 * Scrolls down by the specified number of lines. 528 * 529 * @param widget The text view. 530 * @param buffer The text buffer. 531 * @param amount The number of lines to scroll by. Must be at least 1. 532 * @return True if the event was handled. 533 * @hide 534 */ scrollDown(TextView widget, Spannable buffer, int amount)535 protected boolean scrollDown(TextView widget, Spannable buffer, int amount) { 536 final Layout layout = widget.getLayout(); 537 final int innerHeight = getInnerHeight(widget); 538 final int bottom = widget.getScrollY() + innerHeight; 539 int bottomLine = layout.getLineForVertical(bottom); 540 if (layout.getLineTop(bottomLine + 1) < bottom + 1) { 541 // Less than a pixel of this line is out of view, 542 // so we must have tried to make it entirely in view 543 // and now want the next line to be in view instead. 544 bottomLine += 1; 545 } 546 final int limit = layout.getLineCount() - 1; 547 if (bottomLine <= limit) { 548 bottomLine = Math.min(bottomLine + amount - 1, limit); 549 Touch.scrollTo(widget, layout, widget.getScrollX(), 550 layout.getLineTop(bottomLine + 1) - innerHeight); 551 return true; 552 } 553 return false; 554 } 555 556 /** 557 * Performs a scroll page up action. 558 * Scrolls up by one page. 559 * 560 * @param widget The text view. 561 * @param buffer The text buffer. 562 * @return True if the event was handled. 563 * @hide 564 */ scrollPageUp(TextView widget, Spannable buffer)565 protected boolean scrollPageUp(TextView widget, Spannable buffer) { 566 final Layout layout = widget.getLayout(); 567 final int top = widget.getScrollY() - getInnerHeight(widget); 568 int topLine = layout.getLineForVertical(top); 569 if (topLine >= 0) { 570 Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(topLine)); 571 return true; 572 } 573 return false; 574 } 575 576 /** 577 * Performs a scroll page up action. 578 * Scrolls down by one page. 579 * 580 * @param widget The text view. 581 * @param buffer The text buffer. 582 * @return True if the event was handled. 583 * @hide 584 */ scrollPageDown(TextView widget, Spannable buffer)585 protected boolean scrollPageDown(TextView widget, Spannable buffer) { 586 final Layout layout = widget.getLayout(); 587 final int innerHeight = getInnerHeight(widget); 588 final int bottom = widget.getScrollY() + innerHeight + innerHeight; 589 int bottomLine = layout.getLineForVertical(bottom); 590 if (bottomLine <= layout.getLineCount() - 1) { 591 Touch.scrollTo(widget, layout, widget.getScrollX(), 592 layout.getLineTop(bottomLine + 1) - innerHeight); 593 return true; 594 } 595 return false; 596 } 597 598 /** 599 * Performs a scroll to top action. 600 * Scrolls to the top of the document. 601 * 602 * @param widget The text view. 603 * @param buffer The text buffer. 604 * @return True if the event was handled. 605 * @hide 606 */ scrollTop(TextView widget, Spannable buffer)607 protected boolean scrollTop(TextView widget, Spannable buffer) { 608 final Layout layout = widget.getLayout(); 609 if (getTopLine(widget) >= 0) { 610 Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(0)); 611 return true; 612 } 613 return false; 614 } 615 616 /** 617 * Performs a scroll to bottom action. 618 * Scrolls to the bottom of the document. 619 * 620 * @param widget The text view. 621 * @param buffer The text buffer. 622 * @return True if the event was handled. 623 * @hide 624 */ scrollBottom(TextView widget, Spannable buffer)625 protected boolean scrollBottom(TextView widget, Spannable buffer) { 626 final Layout layout = widget.getLayout(); 627 final int lineCount = layout.getLineCount(); 628 if (getBottomLine(widget) <= lineCount - 1) { 629 Touch.scrollTo(widget, layout, widget.getScrollX(), 630 layout.getLineTop(lineCount) - getInnerHeight(widget)); 631 return true; 632 } 633 return false; 634 } 635 636 /** 637 * Performs a scroll to line start action. 638 * Scrolls to the start of the line. 639 * 640 * @param widget The text view. 641 * @param buffer The text buffer. 642 * @return True if the event was handled. 643 * @hide 644 */ scrollLineStart(TextView widget, Spannable buffer)645 protected boolean scrollLineStart(TextView widget, Spannable buffer) { 646 final int minScrollX = getScrollBoundsLeft(widget); 647 int scrollX = widget.getScrollX(); 648 if (scrollX > minScrollX) { 649 widget.scrollTo(minScrollX, widget.getScrollY()); 650 return true; 651 } 652 return false; 653 } 654 655 /** 656 * Performs a scroll to line end action. 657 * Scrolls to the end of the line. 658 * 659 * @param widget The text view. 660 * @param buffer The text buffer. 661 * @return True if the event was handled. 662 * @hide 663 */ scrollLineEnd(TextView widget, Spannable buffer)664 protected boolean scrollLineEnd(TextView widget, Spannable buffer) { 665 final int maxScrollX = getScrollBoundsRight(widget) - getInnerWidth(widget); 666 int scrollX = widget.getScrollX(); 667 if (scrollX < maxScrollX) { 668 widget.scrollTo(maxScrollX, widget.getScrollY()); 669 return true; 670 } 671 return false; 672 } 673 } 674