Home | History | Annotate | Download | only in method
      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
     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 
     92     @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 
    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      */
    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_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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    360     protected boolean lineEnd(TextView widget, Spannable buffer) {
    361         return false;
    362     }
    363 
    364     /** {@hide} */
    365     protected boolean leftWord(TextView widget, Spannable buffer) {
    366         return false;
    367     }
    368 
    369     /** {@hide} */
    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      */
    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      */
    398     protected boolean end(TextView widget, Spannable buffer) {
    399         return false;
    400     }
    401 
    402     private int getTopLine(TextView widget) {
    403         return widget.getLayout().getLineForVertical(widget.getScrollY());
    404     }
    405 
    406     private int getBottomLine(TextView widget) {
    407         return widget.getLayout().getLineForVertical(widget.getScrollY() + getInnerHeight(widget));
    408     }
    409 
    410     private int getInnerWidth(TextView widget) {
    411         return widget.getWidth() - widget.getTotalPaddingLeft() - widget.getTotalPaddingRight();
    412     }
    413 
    414     private int getInnerHeight(TextView widget) {
    415         return widget.getHeight() - widget.getTotalPaddingTop() - widget.getTotalPaddingBottom();
    416     }
    417 
    418     private int getCharacterWidth(TextView widget) {
    419         return (int) Math.ceil(widget.getPaint().getFontSpacing());
    420     }
    421 
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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