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