modules/controls/src/main/java/com/sun/javafx/scene/control/behavior/ListViewBehavior.java

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization
   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 -> {


 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 }
   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 -> {


 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 }