1 /*
   2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package com.sun.javafx.scene.control.behavior;
  26 
  27 import com.sun.javafx.PlatformUtil;
  28 import com.sun.javafx.scene.control.skin.Utils;
  29 import javafx.beans.value.ChangeListener;
  30 import javafx.beans.value.ObservableValue;
  31 import javafx.beans.value.WeakChangeListener;
  32 import javafx.collections.ListChangeListener;
  33 import javafx.collections.ObservableList;
  34 import javafx.collections.WeakListChangeListener;
  35 import javafx.event.EventHandler;
  36 import javafx.geometry.Orientation;
  37 import javafx.scene.control.FocusModel;
  38 import javafx.scene.control.ListView;
  39 import javafx.scene.control.MultipleSelectionModel;
  40 import javafx.scene.control.SelectionMode;
  41 import com.sun.javafx.scene.control.inputmap.InputMap;
  42 import com.sun.javafx.scene.control.inputmap.KeyBinding;
  43 import javafx.scene.input.KeyEvent;
  44 import javafx.scene.input.MouseEvent;
  45 import javafx.util.Callback;
  46 
  47 import java.util.ArrayList;
  48 import java.util.List;
  49 
  50 import static com.sun.javafx.scene.control.inputmap.InputMap.*;
  51 import static javafx.scene.input.KeyCode.*;
  52 
  53 public class ListViewBehavior<T> extends BehaviorBase<ListView<T>> {
  54     private final InputMap<ListView<T>> listViewInputMap;
  55 
  56     /**
  57      * Indicates that a keyboard key has been pressed which represents the
  58      * event (this could be space bar for example). As long as keyDown is true,
  59      * we are also armed, and will ignore mouse events related to arming.
  60      * Note this is made package private solely for the sake of testing.
  61      */
  62     private boolean keyDown;
  63 
  64     private final EventHandler<KeyEvent> keyEventListener = e -> {
  65         if (!e.isConsumed()) {
  66             // RT-12751: we want to keep an eye on the user holding down the shift key,
  67             // so that we know when they enter/leave multiple selection mode. This
  68             // changes what happens when certain key combinations are pressed.
  69             isShiftDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShiftDown();
  70             isShortcutDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShortcutDown();
  71         }
  72     };
  73 
  74 
  75 
  76     /***************************************************************************
  77      *                                                                         *
  78      * Constructors                                                            *
  79      *                                                                         *
  80      **************************************************************************/
  81 
  82     public ListViewBehavior(ListView<T> control) {
  83         super(control);
  84 
  85         // create a map for listView-specific mappings
  86         listViewInputMap = createInputMap();
  87 
  88         // add focus traversal mappings
  89         addDefaultMapping(listViewInputMap, FocusTraversalInputMap.getFocusTraversalMappings());
  90         addDefaultMapping(listViewInputMap,
  91             new KeyMapping(HOME, e -> selectFirstRow()),
  92             new KeyMapping(END, e -> selectLastRow()),
  93             new KeyMapping(new KeyBinding(HOME).shift(), e -> selectAllToFirstRow()),
  94             new KeyMapping(new KeyBinding(END).shift(), e -> selectAllToLastRow()),
  95             new KeyMapping(new KeyBinding(PAGE_UP).shift(), e -> selectAllPageUp()),
  96             new KeyMapping(new KeyBinding(PAGE_DOWN).shift(), e -> selectAllPageDown()),
  97 
  98             new KeyMapping(new KeyBinding(SPACE).shift(), e -> selectAllToFocus(false)),
  99             new KeyMapping(new KeyBinding(SPACE).shortcut().shift(), e -> selectAllToFocus(true)),
 100 
 101             new KeyMapping(PAGE_UP, e -> scrollPageUp()),
 102             new KeyMapping(PAGE_DOWN, e -> scrollPageDown()),
 103 
 104             new KeyMapping(ENTER, e -> activate()),
 105             new KeyMapping(SPACE, e -> activate()),
 106             new KeyMapping(F2, e -> activate()),
 107             new KeyMapping(ESCAPE, e -> cancelEdit()),
 108 
 109             new KeyMapping(new KeyBinding(A).shortcut(), e -> selectAll()),
 110             new KeyMapping(new KeyBinding(HOME).shortcut(), e -> focusFirstRow()),
 111             new KeyMapping(new KeyBinding(END).shortcut(), e -> focusLastRow()),
 112             new KeyMapping(new KeyBinding(PAGE_UP).shortcut(), e -> focusPageUp()),
 113             new KeyMapping(new KeyBinding(PAGE_DOWN).shortcut(), e -> focusPageDown()),
 114 
 115             new KeyMapping(new KeyBinding(BACK_SLASH).shortcut(), e -> clearSelection()),
 116 
 117             new MouseMapping(MouseEvent.MOUSE_PRESSED, this::mousePressed)
 118         );
 119 
 120         // create OS-specific child mappings
 121         // --- mac OS
 122         InputMap<ListView<T>> macInputMap = new InputMap<>(control);
 123         macInputMap.setInterceptor(event -> !PlatformUtil.isMac());
 124         addDefaultMapping(macInputMap, new KeyMapping(new KeyBinding(SPACE).shortcut().ctrl(), e -> toggleFocusOwnerSelection()));
 125         addDefaultChildMap(listViewInputMap, macInputMap);
 126 
 127         // --- all other platforms
 128         InputMap<ListView<T>> otherOsInputMap = new InputMap<>(control);
 129         otherOsInputMap.setInterceptor(event -> PlatformUtil.isMac());
 130         addDefaultMapping(otherOsInputMap, new KeyMapping(new KeyBinding(SPACE).ctrl(), e -> toggleFocusOwnerSelection()));
 131         addDefaultChildMap(listViewInputMap, otherOsInputMap);
 132 
 133         // create two more child maps, one for vertical listview and one for horizontal listview
 134         // --- vertical listview
 135         InputMap<ListView<T>> verticalListInputMap = new InputMap<>(control);
 136         verticalListInputMap.setInterceptor(event -> control.getOrientation() != Orientation.VERTICAL);
 137 
 138         addDefaultMapping(verticalListInputMap,
 139             new KeyMapping(UP, e -> selectPreviousRow()),
 140             new KeyMapping(KP_UP, e -> selectPreviousRow()),
 141             new KeyMapping(DOWN, e -> selectNextRow()),
 142             new KeyMapping(KP_DOWN, e -> selectNextRow()),
 143 
 144             new KeyMapping(new KeyBinding(UP).shift(), e -> alsoSelectPreviousRow()),
 145             new KeyMapping(new KeyBinding(KP_UP).shift(), e -> alsoSelectPreviousRow()),
 146             new KeyMapping(new KeyBinding(DOWN).shift(), e -> alsoSelectNextRow()),
 147             new KeyMapping(new KeyBinding(KP_DOWN).shift(), e -> alsoSelectNextRow()),
 148 
 149             new KeyMapping(new KeyBinding(UP).shortcut(), e -> focusPreviousRow()),
 150             new KeyMapping(new KeyBinding(DOWN).shortcut(), e -> focusNextRow()),
 151 
 152             new KeyMapping(new KeyBinding(UP).shortcut().shift(), e -> discontinuousSelectPreviousRow()),
 153             new KeyMapping(new KeyBinding(DOWN).shortcut().shift(), e -> discontinuousSelectNextRow()),
 154             new KeyMapping(new KeyBinding(PAGE_UP).shortcut().shift(), e -> discontinuousSelectPageUp()),
 155             new KeyMapping(new KeyBinding(PAGE_DOWN).shortcut().shift(), e -> discontinuousSelectPageDown()),
 156             new KeyMapping(new KeyBinding(HOME).shortcut().shift(), e -> discontinuousSelectAllToFirstRow()),
 157             new KeyMapping(new KeyBinding(END).shortcut().shift(), e -> discontinuousSelectAllToLastRow())
 158         );
 159 
 160         addDefaultChildMap(listViewInputMap, verticalListInputMap);
 161 
 162         // --- horizontal listview
 163         InputMap<ListView<T>> horizontalListInputMap = new InputMap<>(control);
 164         horizontalListInputMap.setInterceptor(event -> control.getOrientation() != Orientation.HORIZONTAL);
 165 
 166         addDefaultMapping(horizontalListInputMap,
 167             new KeyMapping(LEFT, e -> selectPreviousRow()),
 168             new KeyMapping(KP_LEFT, e -> selectPreviousRow()),
 169             new KeyMapping(RIGHT, e -> selectNextRow()),
 170             new KeyMapping(KP_RIGHT, e -> selectNextRow()),
 171 
 172             new KeyMapping(new KeyBinding(LEFT).shift(), e -> alsoSelectPreviousRow()),
 173             new KeyMapping(new KeyBinding(KP_LEFT).shift(), e -> alsoSelectPreviousRow()),
 174             new KeyMapping(new KeyBinding(RIGHT).shift(), e -> alsoSelectNextRow()),
 175             new KeyMapping(new KeyBinding(KP_RIGHT).shift(), e -> alsoSelectNextRow()),
 176 
 177             new KeyMapping(new KeyBinding(LEFT).shortcut(), e -> focusPreviousRow()),
 178             new KeyMapping(new KeyBinding(RIGHT).shortcut(), e -> focusNextRow()),
 179 
 180             new KeyMapping(new KeyBinding(LEFT).shortcut().shift(), e -> discontinuousSelectPreviousRow()),
 181             new KeyMapping(new KeyBinding(RIGHT).shortcut().shift(), e -> discontinuousSelectNextRow())
 182         );
 183 
 184         addDefaultChildMap(listViewInputMap, horizontalListInputMap);
 185 
 186         // set up other listeners
 187         // We make this an event _filter_ so that we can determine the state
 188         // of the shift key before the event handlers get a shot at the event.
 189         control.addEventFilter(KeyEvent.ANY, keyEventListener);
 190 
 191         control.itemsProperty().addListener(weakItemsListener);
 192         if (control.getItems() != null) {
 193             control.getItems().addListener(weakItemsListListener);
 194         }
 195 
 196         // Fix for RT-16565
 197         control.selectionModelProperty().addListener(weakSelectionModelListener);
 198         if (control.getSelectionModel() != null) {
 199             control.getSelectionModel().getSelectedIndices().addListener(weakSelectedIndicesListener);
 200         }
 201 
 202         // Only add this if we're on an embedded platform that supports 5-button navigation
 203         if (Utils.isTwoLevelFocus()) {
 204             tlFocus = new TwoLevelFocusListBehavior(control); // needs to be last.
 205         }
 206     }
 207 
 208 
 209 
 210     /***************************************************************************
 211      *                                                                         *
 212      * Implementation of BehaviorBase API                                      *
 213      *                                                                         *
 214      **************************************************************************/
 215 
 216     @Override public InputMap<ListView<T>> getInputMap() {
 217         return listViewInputMap;
 218     }
 219 
 220     @Override public void dispose() {
 221         ListView<T> control = getNode();
 222 
 223         ListCellBehavior.removeAnchor(control);
 224         if (tlFocus != null) tlFocus.dispose();
 225         super.dispose();
 226 
 227         control.removeEventHandler(KeyEvent.ANY, keyEventListener);
 228     }
 229 
 230 
 231 
 232 
 233 
 234     /**************************************************************************
 235      *                         State and Functions                            *
 236      *************************************************************************/
 237 
 238     private boolean isShiftDown = false;
 239     private boolean isShortcutDown = false;
 240 
 241     private Callback<Boolean, Integer> onScrollPageUp;
 242     private Callback<Boolean, Integer> onScrollPageDown;
 243     private Runnable onFocusPreviousRow;
 244     private Runnable onFocusNextRow;
 245     private Runnable onSelectPreviousRow;
 246     private Runnable onSelectNextRow;
 247     private Runnable onMoveToFirstCell;
 248     private Runnable onMoveToLastCell;
 249 
 250     public void setOnScrollPageUp(Callback<Boolean, Integer> c) { onScrollPageUp = c; }
 251     public void setOnScrollPageDown(Callback<Boolean, Integer> c) { onScrollPageDown = c; }
 252     public void setOnFocusPreviousRow(Runnable r) { onFocusPreviousRow = r; }
 253     public void setOnFocusNextRow(Runnable r) { onFocusNextRow = r; }
 254     public void setOnSelectPreviousRow(Runnable r) { onSelectPreviousRow = r; }
 255     public void setOnSelectNextRow(Runnable r) { onSelectNextRow = r; }
 256     public void setOnMoveToFirstCell(Runnable r) { onMoveToFirstCell = r; }
 257     public void setOnMoveToLastCell(Runnable r) { onMoveToLastCell = r; }
 258 
 259     private boolean selectionChanging = false;
 260 
 261     private final ListChangeListener<Integer> selectedIndicesListener = c -> {
 262         while (c.next()) {
 263             if (c.wasReplaced()) {
 264                 if (ListCellBehavior.hasDefaultAnchor(getNode())) {
 265                     ListCellBehavior.removeAnchor(getNode());
 266                 }
 267             }
 268 
 269             final int shift = c.wasPermutated() ? c.getTo() - c.getFrom() : 0;
 270 
 271             MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 272 
 273             // there are no selected items, so lets clear out the anchor
 274             if (! selectionChanging) {
 275                 if (sm.isEmpty()) {
 276                     setAnchor(-1);
 277                 } else if (hasAnchor() && ! sm.isSelected(getAnchor() + shift)) {
 278                     setAnchor(-1);
 279                 }
 280             }
 281 
 282             int addedSize = c.getAddedSize();
 283             if (addedSize > 0 && ! hasAnchor()) {
 284                 List<? extends Integer> addedSubList = c.getAddedSubList();
 285                 int index = addedSubList.get(addedSize - 1);
 286                 setAnchor(index);
 287             }
 288         }
 289     };
 290 
 291     private final ListChangeListener<T> itemsListListener = c -> {
 292         while (c.next()) {
 293             if (c.wasAdded() && c.getFrom() <= getAnchor()) {
 294                 setAnchor(getAnchor() + c.getAddedSize());
 295             } else if (c.wasRemoved() && c.getFrom() <= getAnchor()) {
 296                 setAnchor(getAnchor() - c.getRemovedSize());
 297             }
 298         }
 299     };
 300 
 301     private final ChangeListener<ObservableList<T>> itemsListener = new ChangeListener<ObservableList<T>>() {
 302         @Override
 303         public void changed(
 304                 ObservableValue<? extends ObservableList<T>> observable,
 305                 ObservableList<T> oldValue, ObservableList<T> newValue) {
 306             if (oldValue != null) {
 307                 oldValue.removeListener(weakItemsListListener);
 308             } if (newValue != null) {
 309                 newValue.addListener(weakItemsListListener);
 310             }
 311         }
 312     };
 313 
 314     private final ChangeListener<MultipleSelectionModel<T>> selectionModelListener = new ChangeListener<MultipleSelectionModel<T>>() {
 315         @Override public void changed(
 316                 ObservableValue<? extends MultipleSelectionModel<T>> observable,
 317                 MultipleSelectionModel<T> oldValue,
 318                 MultipleSelectionModel<T> newValue) {
 319             if (oldValue != null) {
 320                 oldValue.getSelectedIndices().removeListener(weakSelectedIndicesListener);
 321             }
 322             if (newValue != null) {
 323                 newValue.getSelectedIndices().addListener(weakSelectedIndicesListener);
 324             }
 325         }
 326     };
 327 
 328     private final WeakChangeListener<ObservableList<T>> weakItemsListener =
 329             new WeakChangeListener<ObservableList<T>>(itemsListener);
 330     private final WeakListChangeListener<Integer> weakSelectedIndicesListener =
 331             new WeakListChangeListener<Integer>(selectedIndicesListener);
 332     private final WeakListChangeListener<T> weakItemsListListener =
 333             new WeakListChangeListener<>(itemsListListener);
 334     private final WeakChangeListener<MultipleSelectionModel<T>> weakSelectionModelListener =
 335             new WeakChangeListener<MultipleSelectionModel<T>>(selectionModelListener);
 336 
 337     private TwoLevelFocusListBehavior tlFocus;
 338 
 339     private void setAnchor(int anchor) {
 340         ListCellBehavior.setAnchor(getNode(), anchor < 0 ? null : anchor, false);
 341     }
 342 
 343     private int getAnchor() {
 344         return ListCellBehavior.getAnchor(getNode(), getNode().getFocusModel().getFocusedIndex());
 345     }
 346 
 347     private boolean hasAnchor() {
 348         return ListCellBehavior.hasNonDefaultAnchor(getNode());
 349     }
 350 
 351     private void mousePressed(MouseEvent e) {
 352         if (! e.isShiftDown() && ! e.isSynthesized()) {
 353             int index = getNode().getSelectionModel().getSelectedIndex();
 354             setAnchor(index);
 355         }
 356 
 357         if (! getNode().isFocused() && getNode().isFocusTraversable()) {
 358             getNode().requestFocus();
 359         }
 360     }
 361 
 362     private int getRowCount() {
 363         return getNode().getItems() == null ? 0 : getNode().getItems().size();
 364     }
 365 
 366     private void clearSelection() {
 367         getNode().getSelectionModel().clearSelection();
 368     }
 369 
 370     private void scrollPageUp() {
 371         int newSelectedIndex = -1;
 372         if (onScrollPageUp != null) {
 373             newSelectedIndex = onScrollPageUp.call(false);
 374         }
 375         if (newSelectedIndex == -1) return;
 376 
 377         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 378         if (sm == null) return;
 379         sm.clearAndSelect(newSelectedIndex);
 380     }
 381 
 382     private void scrollPageDown() {
 383         int newSelectedIndex = -1;
 384         if (onScrollPageDown != null) {
 385             newSelectedIndex = onScrollPageDown.call(false);
 386         }
 387         if (newSelectedIndex == -1) return;
 388 
 389         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 390         if (sm == null) return;
 391         sm.clearAndSelect(newSelectedIndex);
 392     }
 393 
 394     private void focusFirstRow() {
 395         FocusModel<T> fm = getNode().getFocusModel();
 396         if (fm == null) return;
 397         fm.focus(0);
 398 
 399         if (onMoveToFirstCell != null) onMoveToFirstCell.run();
 400     }
 401 
 402     private void focusLastRow() {
 403         FocusModel<T> fm = getNode().getFocusModel();
 404         if (fm == null) return;
 405         fm.focus(getRowCount() - 1);
 406 
 407         if (onMoveToLastCell != null) onMoveToLastCell.run();
 408     }
 409 
 410     private void focusPreviousRow() {
 411         FocusModel<T> fm = getNode().getFocusModel();
 412         if (fm == null) return;
 413 
 414         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 415         if (sm == null) return;
 416 
 417         fm.focusPrevious();
 418 
 419         if (! isShortcutDown || getAnchor() == -1) {
 420             setAnchor(fm.getFocusedIndex());
 421         }
 422 
 423         if (onFocusPreviousRow != null) onFocusPreviousRow.run();
 424     }
 425 
 426     private void focusNextRow() {
 427         FocusModel<T> fm = getNode().getFocusModel();
 428         if (fm == null) return;
 429 
 430         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 431         if (sm == null) return;
 432 
 433         fm.focusNext();
 434 
 435         if (! isShortcutDown || getAnchor() == -1) {
 436             setAnchor(fm.getFocusedIndex());
 437         }
 438 
 439         if (onFocusNextRow != null) onFocusNextRow.run();
 440     }
 441 
 442     private void focusPageUp() {
 443         int newFocusIndex = onScrollPageUp.call(true);
 444 
 445         FocusModel<T> fm = getNode().getFocusModel();
 446         if (fm == null) return;
 447         fm.focus(newFocusIndex);
 448     }
 449 
 450     private void focusPageDown() {
 451         int newFocusIndex = onScrollPageDown.call(true);
 452 
 453         FocusModel<T> fm = getNode().getFocusModel();
 454         if (fm == null) return;
 455         fm.focus(newFocusIndex);
 456     }
 457 
 458     private void alsoSelectPreviousRow() {
 459         FocusModel<T> fm = getNode().getFocusModel();
 460         if (fm == null) return;
 461 
 462         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 463         if (sm == null) return;
 464 
 465         if (isShiftDown && getAnchor() != -1) {
 466             int newRow = fm.getFocusedIndex() - 1;
 467             if (newRow < 0) return;
 468 
 469             int anchor = getAnchor();
 470 
 471             if (! hasAnchor()) {
 472                 setAnchor(fm.getFocusedIndex());
 473             }
 474 
 475             if (sm.getSelectedIndices().size() > 1) {
 476                 clearSelectionOutsideRange(anchor, newRow);
 477             }
 478 
 479             if (anchor > newRow) {
 480                 sm.selectRange(anchor, newRow - 1);
 481             } else {
 482                 sm.selectRange(anchor, newRow + 1);
 483             }
 484         } else {
 485             sm.selectPrevious();
 486         }
 487 
 488         onSelectPreviousRow.run();
 489     }
 490 
 491     private void alsoSelectNextRow() {
 492         FocusModel<T> fm = getNode().getFocusModel();
 493         if (fm == null) return;
 494 
 495         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 496         if (sm == null) return;
 497 
 498         if (isShiftDown && getAnchor() != -1) {
 499             int newRow = fm.getFocusedIndex() + 1;
 500             int anchor = getAnchor();
 501 
 502             if (! hasAnchor()) {
 503                 setAnchor(fm.getFocusedIndex());
 504             }
 505 
 506             if (sm.getSelectedIndices().size() > 1) {
 507                 clearSelectionOutsideRange(anchor, newRow);
 508             }
 509 
 510             if (anchor > newRow) {
 511                 sm.selectRange(anchor, newRow - 1);
 512             } else {
 513                 sm.selectRange(anchor, newRow + 1);
 514             }
 515         } else {
 516             sm.selectNext();
 517         }
 518 
 519         onSelectNextRow.run();
 520     }
 521 
 522     private void clearSelectionOutsideRange(int start, int end) {
 523         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 524         if (sm == null) return;
 525 
 526         int min = Math.min(start, end);
 527         int max = Math.max(start, end);
 528 
 529         List<Integer> indices = new ArrayList<>(sm.getSelectedIndices());
 530 
 531         selectionChanging = true;
 532         for (int i = 0; i < indices.size(); i++) {
 533             int index = indices.get(i);
 534             if (index < min || index > max) {
 535                 sm.clearSelection(index);
 536             }
 537         }
 538         selectionChanging = false;
 539     }
 540 
 541     private void selectPreviousRow() {
 542         FocusModel<T> fm = getNode().getFocusModel();
 543         if (fm == null) return;
 544 
 545         int focusIndex = fm.getFocusedIndex();
 546         if (focusIndex <= 0) {
 547             return;
 548         }
 549 
 550         setAnchor(focusIndex - 1);
 551         getNode().getSelectionModel().clearAndSelect(focusIndex - 1);
 552         onSelectPreviousRow.run();
 553     }
 554 
 555     private void selectNextRow() {
 556         ListView<T> listView = getNode();
 557         FocusModel<T> fm = listView.getFocusModel();
 558         if (fm == null) return;
 559 
 560         int focusIndex = fm.getFocusedIndex();
 561         if (focusIndex == getRowCount() - 1) {
 562             return;
 563         }
 564 
 565         MultipleSelectionModel<T> sm = listView.getSelectionModel();
 566         if (sm == null) return;
 567 
 568         setAnchor(focusIndex + 1);
 569         sm.clearAndSelect(focusIndex + 1);
 570         if (onSelectNextRow != null) onSelectNextRow.run();
 571     }
 572 
 573     private void selectFirstRow() {
 574         if (getRowCount() > 0) {
 575             getNode().getSelectionModel().clearAndSelect(0);
 576             if (onMoveToFirstCell != null) onMoveToFirstCell.run();
 577         }
 578     }
 579 
 580     private void selectLastRow() {
 581         getNode().getSelectionModel().clearAndSelect(getRowCount() - 1);
 582         if (onMoveToLastCell != null) onMoveToLastCell.run();
 583     }
 584 
 585     private void selectAllPageUp() {
 586         FocusModel<T> fm = getNode().getFocusModel();
 587         if (fm == null) return;
 588 
 589         int leadIndex = fm.getFocusedIndex();
 590         if (isShiftDown) {
 591             leadIndex = getAnchor() == -1 ? leadIndex : getAnchor();
 592             setAnchor(leadIndex);
 593         }
 594 
 595         int leadSelectedIndex = onScrollPageUp.call(false);
 596 
 597         // fix for RT-34407
 598         int adjust = leadIndex < leadSelectedIndex ? 1 : -1;
 599 
 600         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 601         if (sm == null) return;
 602 
 603         selectionChanging = true;
 604         if (sm.getSelectionMode() == SelectionMode.SINGLE) {
 605             sm.select(leadSelectedIndex);
 606         } else {
 607             sm.clearSelection();
 608             sm.selectRange(leadIndex, leadSelectedIndex + adjust);
 609         }
 610         selectionChanging = false;
 611     }
 612 
 613     private void selectAllPageDown() {
 614         FocusModel<T> fm = getNode().getFocusModel();
 615         if (fm == null) return;
 616 
 617         int leadIndex = fm.getFocusedIndex();
 618         if (isShiftDown) {
 619             leadIndex = getAnchor() == -1 ? leadIndex : getAnchor();
 620             setAnchor(leadIndex);
 621         }
 622 
 623         int leadSelectedIndex = onScrollPageDown.call(false);
 624 
 625         // fix for RT-34407
 626         int adjust = leadIndex < leadSelectedIndex ? 1 : -1;
 627 
 628         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 629         if (sm == null) return;
 630 
 631         selectionChanging = true;
 632         if (sm.getSelectionMode() == SelectionMode.SINGLE) {
 633             sm.select(leadSelectedIndex);
 634         } else {
 635             sm.clearSelection();
 636             sm.selectRange(leadIndex, leadSelectedIndex + adjust);
 637         }
 638         selectionChanging = false;
 639     }
 640 
 641     private void selectAllToFirstRow() {
 642         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 643         if (sm == null) return;
 644 
 645         FocusModel<T> fm = getNode().getFocusModel();
 646         if (fm == null) return;
 647 
 648         int leadIndex = fm.getFocusedIndex();
 649 
 650         if (isShiftDown) {
 651             leadIndex = hasAnchor() ? getAnchor() : leadIndex;
 652         }
 653 
 654         sm.clearSelection();
 655         sm.selectRange(leadIndex, -1);
 656 
 657         // RT-18413: Focus must go to first row
 658         fm.focus(0);
 659 
 660         if (isShiftDown) {
 661             setAnchor(leadIndex);
 662         }
 663 
 664         if (onMoveToFirstCell != null) onMoveToFirstCell.run();
 665     }
 666 
 667     private void selectAllToLastRow() {
 668         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 669         if (sm == null) return;
 670 
 671         FocusModel<T> fm = getNode().getFocusModel();
 672         if (fm == null) return;
 673 
 674         int leadIndex = fm.getFocusedIndex();
 675 
 676         System.out.println("isShiftDown: " + isShiftDown);
 677         if (isShiftDown) {
 678             leadIndex = hasAnchor() ? getAnchor() : leadIndex;
 679         }
 680 
 681         sm.clearSelection();
 682         sm.selectRange(leadIndex, getRowCount());
 683 
 684         if (isShiftDown) {
 685             setAnchor(leadIndex);
 686         }
 687 
 688         if (onMoveToLastCell != null) onMoveToLastCell.run();
 689     }
 690 
 691     private void selectAll() {
 692         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 693         if (sm == null) return;
 694         sm.selectAll();
 695     }
 696 
 697     private void selectAllToFocus(boolean setAnchorToFocusIndex) {
 698         // Fix for RT-31241
 699         final ListView<T> listView = getNode();
 700         if (listView.getEditingIndex() >= 0) return;
 701 
 702         MultipleSelectionModel<T> sm = listView.getSelectionModel();
 703         if (sm == null) return;
 704 
 705         FocusModel<T> fm = listView.getFocusModel();
 706         if (fm == null) return;
 707 
 708         int focusIndex = fm.getFocusedIndex();
 709         int anchor = getAnchor();
 710 
 711         sm.clearSelection();
 712         int startPos = anchor;
 713         int endPos = anchor > focusIndex ? focusIndex - 1 : focusIndex + 1;
 714         sm.selectRange(startPos, endPos);
 715         setAnchor(setAnchorToFocusIndex ? focusIndex : anchor);
 716     }
 717 
 718     private void cancelEdit() {
 719         getNode().edit(-1);
 720     }
 721 
 722     private void activate() {
 723         int focusedIndex = getNode().getFocusModel().getFocusedIndex();
 724         getNode().getSelectionModel().select(focusedIndex);
 725         setAnchor(focusedIndex);
 726 
 727         // edit this row also
 728         if (focusedIndex >= 0) {
 729             getNode().edit(focusedIndex);
 730         }
 731     }
 732 
 733     private void toggleFocusOwnerSelection() {
 734         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 735         if (sm == null) return;
 736 
 737         FocusModel<T> fm = getNode().getFocusModel();
 738         if (fm == null) return;
 739 
 740         int focusedIndex = fm.getFocusedIndex();
 741 
 742         if (sm.isSelected(focusedIndex)) {
 743             sm.clearSelection(focusedIndex);
 744             fm.focus(focusedIndex);
 745         } else {
 746             sm.select(focusedIndex);
 747         }
 748 
 749         setAnchor(focusedIndex);
 750     }
 751 
 752     /**************************************************************************
 753      * Discontinuous Selection                                                *
 754      *************************************************************************/
 755 
 756     private void discontinuousSelectPreviousRow() {
 757         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 758         if (sm == null) return;
 759 
 760         if (sm.getSelectionMode() != SelectionMode.MULTIPLE) {
 761             selectPreviousRow();
 762             return;
 763         }
 764 
 765         FocusModel<T> fm = getNode().getFocusModel();
 766         if (fm == null) return;
 767 
 768         int focusIndex = fm.getFocusedIndex();
 769         final int newFocusIndex = focusIndex - 1;
 770         if (newFocusIndex < 0) return;
 771 
 772         int startIndex = focusIndex;
 773         if (isShiftDown) {
 774             startIndex = getAnchor() == -1 ? focusIndex : getAnchor();
 775         }
 776 
 777         sm.selectRange(newFocusIndex, startIndex + 1);
 778         fm.focus(newFocusIndex);
 779 
 780         if (onFocusPreviousRow != null) onFocusPreviousRow.run();
 781     }
 782 
 783     private void discontinuousSelectNextRow() {
 784         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 785         if (sm == null) return;
 786 
 787         if (sm.getSelectionMode() != SelectionMode.MULTIPLE) {
 788             selectNextRow();
 789             return;
 790         }
 791 
 792         FocusModel<T> fm = getNode().getFocusModel();
 793         if (fm == null) return;
 794 
 795         int focusIndex = fm.getFocusedIndex();
 796         final int newFocusIndex = focusIndex + 1;
 797         if (newFocusIndex >= getRowCount()) return;
 798 
 799         int startIndex = focusIndex;
 800         if (isShiftDown) {
 801             startIndex = getAnchor() == -1 ? focusIndex : getAnchor();
 802         }
 803 
 804         sm.selectRange(startIndex, newFocusIndex + 1);
 805         fm.focus(newFocusIndex);
 806 
 807         if (onFocusNextRow != null) onFocusNextRow.run();
 808     }
 809 
 810     private void discontinuousSelectPageUp() {
 811         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 812         if (sm == null) return;
 813 
 814         FocusModel<T> fm = getNode().getFocusModel();
 815         if (fm == null) return;
 816 
 817         int anchor = getAnchor();
 818         int leadSelectedIndex = onScrollPageUp.call(false);
 819         sm.selectRange(anchor, leadSelectedIndex - 1);
 820     }
 821 
 822     private void discontinuousSelectPageDown() {
 823         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 824         if (sm == null) return;
 825 
 826         FocusModel<T> fm = getNode().getFocusModel();
 827         if (fm == null) return;
 828 
 829         int anchor = getAnchor();
 830         int leadSelectedIndex = onScrollPageDown.call(false);
 831         sm.selectRange(anchor, leadSelectedIndex + 1);
 832     }
 833 
 834     private void discontinuousSelectAllToFirstRow() {
 835         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 836         if (sm == null) return;
 837 
 838         FocusModel<T> fm = getNode().getFocusModel();
 839         if (fm == null) return;
 840 
 841         int index = fm.getFocusedIndex();
 842         sm.selectRange(0, index);
 843         fm.focus(0);
 844 
 845         if (onMoveToFirstCell != null) onMoveToFirstCell.run();
 846     }
 847 
 848     private void discontinuousSelectAllToLastRow() {
 849         MultipleSelectionModel<T> sm = getNode().getSelectionModel();
 850         if (sm == null) return;
 851 
 852         FocusModel<T> fm = getNode().getFocusModel();
 853         if (fm == null) return;
 854 
 855         int index = fm.getFocusedIndex() + 1;
 856         sm.selectRange(index, getRowCount());
 857 
 858         if (onMoveToLastCell != null) onMoveToLastCell.run();
 859     }
 860 }