modules/controls/src/main/java/com/sun/javafx/scene/control/behavior/TreeViewBehavior.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 javafx.beans.value.ChangeListener;
  29 import javafx.beans.value.ObservableValue;
  30 import javafx.beans.value.WeakChangeListener;
  31 import javafx.collections.ListChangeListener;
  32 import javafx.collections.WeakListChangeListener;
  33 import javafx.geometry.NodeOrientation;
  34 import javafx.scene.control.*;
  35 import javafx.scene.input.KeyEvent;
  36 import javafx.scene.input.MouseEvent;
  37 import javafx.util.Callback;

  38 import java.util.ArrayList;
  39 import java.util.List;
  40 import com.sun.javafx.PlatformUtil;
  41 import static javafx.scene.input.KeyCode.A;
  42 import static javafx.scene.input.KeyCode.ADD;
  43 import static javafx.scene.input.KeyCode.DOWN;
  44 import static javafx.scene.input.KeyCode.END;
  45 import static javafx.scene.input.KeyCode.ENTER;
  46 import static javafx.scene.input.KeyCode.ESCAPE;
  47 import static javafx.scene.input.KeyCode.F2;
  48 import static javafx.scene.input.KeyCode.HOME;
  49 import static javafx.scene.input.KeyCode.KP_DOWN;
  50 import static javafx.scene.input.KeyCode.KP_LEFT;
  51 import static javafx.scene.input.KeyCode.KP_RIGHT;
  52 import static javafx.scene.input.KeyCode.KP_UP;
  53 import static javafx.scene.input.KeyCode.LEFT;
  54 import static javafx.scene.input.KeyCode.MULTIPLY;
  55 import static javafx.scene.input.KeyCode.PAGE_DOWN;
  56 import static javafx.scene.input.KeyCode.PAGE_UP;
  57 import static javafx.scene.input.KeyCode.RIGHT;
  58 import static javafx.scene.input.KeyCode.SPACE;
  59 import static javafx.scene.input.KeyCode.SUBTRACT;
  60 import static javafx.scene.input.KeyCode.UP;
  61 
  62 public class TreeViewBehavior<T> extends BehaviorBase<TreeView<T>> {

  63 
  64     /**************************************************************************
  65      *                          Setup KeyBindings                             *
  66      *************************************************************************/
  67     protected static final List<KeyBinding> TREE_VIEW_BINDINGS = new ArrayList<KeyBinding>();
  68 
  69     static {
  70         TREE_VIEW_BINDINGS.add(new KeyBinding(HOME, "SelectFirstRow"));
  71         TREE_VIEW_BINDINGS.add(new KeyBinding(END, "SelectLastRow"));
  72         TREE_VIEW_BINDINGS.add(new KeyBinding(HOME, "SelectAllToFirstRow").shift());
  73         TREE_VIEW_BINDINGS.add(new KeyBinding(END, "SelectAllToLastRow").shift());
  74         TREE_VIEW_BINDINGS.add(new KeyBinding(PAGE_UP, "SelectAllPageUp").shift());
  75         TREE_VIEW_BINDINGS.add(new KeyBinding(PAGE_DOWN, "SelectAllPageDown").shift());
  76 
  77         TREE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "SelectAllToFocus").shift());
  78         TREE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "SelectAllToFocusAndSetAnchor").shortcut().shift());
  79         
  80         TREE_VIEW_BINDINGS.add(new KeyBinding(HOME, "FocusFirstRow").shortcut());
  81         TREE_VIEW_BINDINGS.add(new KeyBinding(END, "FocusLastRow").shortcut());
  82 
  83         TREE_VIEW_BINDINGS.add(new KeyBinding(PAGE_UP, "ScrollUp"));
  84         TREE_VIEW_BINDINGS.add(new KeyBinding(PAGE_DOWN, "ScrollDown"));
  85 
  86         TREE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "toggleFocusOwnerSelection"));
  87         if (PlatformUtil.isMac()) {
  88             TREE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "toggleFocusOwnerSelection").ctrl().shortcut());
  89         } else {
  90             TREE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "toggleFocusOwnerSelection").ctrl());
  91         }
  92 
  93         TREE_VIEW_BINDINGS.add(new KeyBinding(A, "SelectAll").shortcut());
  94         TREE_VIEW_BINDINGS.add(new KeyBinding(PAGE_UP, "FocusPageUp").shortcut());
  95         TREE_VIEW_BINDINGS.add(new KeyBinding(PAGE_DOWN, "FocusPageDown").shortcut());
  96         TREE_VIEW_BINDINGS.add(new KeyBinding(UP, "FocusPreviousRow").shortcut());
  97         TREE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "FocusNextRow").shortcut());
  98         TREE_VIEW_BINDINGS.add(new KeyBinding(UP, "DiscontinuousSelectPreviousRow").shortcut().shift());
  99         TREE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "DiscontinuousSelectNextRow").shortcut().shift());
 100         TREE_VIEW_BINDINGS.add(new KeyBinding(PAGE_UP, "DiscontinuousSelectPageUp").shortcut().shift());
 101         TREE_VIEW_BINDINGS.add(new KeyBinding(PAGE_DOWN, "DiscontinuousSelectPageDown").shortcut().shift());
 102         TREE_VIEW_BINDINGS.add(new KeyBinding(HOME, "DiscontinuousSelectAllToFirstRow").shortcut().shift());
 103         TREE_VIEW_BINDINGS.add(new KeyBinding(END, "DiscontinuousSelectAllToLastRow").shortcut().shift());
 104 
 105         TREE_VIEW_BINDINGS.add(new KeyBinding(LEFT, "CollapseRow"));
 106         TREE_VIEW_BINDINGS.add(new KeyBinding(KP_LEFT, "CollapseRow"));
 107         TREE_VIEW_BINDINGS.add(new KeyBinding(RIGHT, "ExpandRow"));
 108         TREE_VIEW_BINDINGS.add(new KeyBinding(KP_RIGHT, "ExpandRow"));
 109         
 110         TREE_VIEW_BINDINGS.add(new KeyBinding(MULTIPLY, "ExpandAll"));
 111         TREE_VIEW_BINDINGS.add(new KeyBinding(ADD, "ExpandRow"));
 112         TREE_VIEW_BINDINGS.add(new KeyBinding(SUBTRACT, "CollapseRow"));
 113 
 114         TREE_VIEW_BINDINGS.add(new KeyBinding(UP, "SelectPreviousRow"));
 115         TREE_VIEW_BINDINGS.add(new KeyBinding(KP_UP, "SelectPreviousRow"));
 116         TREE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "SelectNextRow"));
 117         TREE_VIEW_BINDINGS.add(new KeyBinding(KP_DOWN, "SelectNextRow"));
 118 
 119         TREE_VIEW_BINDINGS.add(new KeyBinding(UP, "AlsoSelectPreviousRow").shift());
 120         TREE_VIEW_BINDINGS.add(new KeyBinding(KP_UP, "AlsoSelectPreviousRow").shift());
 121         TREE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "AlsoSelectNextRow").shift());
 122         TREE_VIEW_BINDINGS.add(new KeyBinding(KP_DOWN, "AlsoSelectNextRow").shift());
 123 
 124         TREE_VIEW_BINDINGS.add(new KeyBinding(ENTER, "Edit"));
 125         TREE_VIEW_BINDINGS.add(new KeyBinding(F2, "Edit"));
 126         TREE_VIEW_BINDINGS.add(new KeyBinding(ESCAPE, "CancelEdit"));
 127     }
 128 
 129     @Override protected /*final*/ String matchActionForEvent(KeyEvent e) {
 130         String action = super.matchActionForEvent(e);
 131         if (getControl().getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) {
 132             // Rather than switching the result of the action lookup in this way, the preferred
 133             // way to do this according to the current architecture would be to hoist the
 134             // getEffectiveNodeOrientation call up into the key bindings, the same way that ListView
 135             // orientation (horizontal vs. vertical) is handled with the OrientedKeyBinding class.
 136             if ("CollapseRow".equals(action) && (e.getCode() == LEFT || e.getCode() == KP_LEFT)) {
 137                 action = "ExpandRow";
 138             } else if ("ExpandRow".equals(action) && (e.getCode() == RIGHT || e.getCode() == KP_RIGHT)) {
 139                 action = "CollapseRow";
 140             }
 141         }
 142         return action;
 143     }
 144 
 145     @Override protected void callAction(String name) {
 146         if ("SelectPreviousRow".equals(name)) selectPreviousRow();
 147         else if ("SelectNextRow".equals(name)) selectNextRow();
 148         else if ("SelectFirstRow".equals(name)) selectFirstRow();
 149         else if ("SelectLastRow".equals(name)) selectLastRow();
 150         else if ("SelectAllPageUp".equals(name)) selectAllPageUp();
 151         else if ("SelectAllPageDown".equals(name)) selectAllPageDown();
 152         else if ("SelectAllToFirstRow".equals(name)) selectAllToFirstRow();
 153         else if ("SelectAllToLastRow".equals(name)) selectAllToLastRow();
 154         else if ("AlsoSelectNextRow".equals(name)) alsoSelectNextRow();
 155         else if ("AlsoSelectPreviousRow".equals(name)) alsoSelectPreviousRow();
 156         else if ("ClearSelection".equals(name)) clearSelection();
 157         else if("SelectAll".equals(name)) selectAll();
 158         else if ("ScrollUp".equals(name)) scrollUp();
 159         else if ("ScrollDown".equals(name)) scrollDown();
 160         else if ("ExpandRow".equals(name)) expandRow();
 161         else if ("CollapseRow".equals(name)) collapseRow();
 162         else if ("ExpandAll".equals(name)) expandAll();
 163 //        else if ("ExpandOrCollapseRow".equals(name)) expandOrCollapseRow();
 164         else if ("Edit".equals(name)) edit();
 165         else if ("CancelEdit".equals(name)) cancelEdit();
 166         else if ("FocusFirstRow".equals(name)) focusFirstRow();
 167         else if ("FocusLastRow".equals(name)) focusLastRow();
 168         else if ("toggleFocusOwnerSelection".equals(name)) toggleFocusOwnerSelection();
 169 
 170         else if ("SelectAllToFocus".equals(name)) selectAllToFocus(false);
 171         else if ("SelectAllToFocusAndSetAnchor".equals(name)) selectAllToFocus(true);
 172 
 173         else if ("FocusPageUp".equals(name)) focusPageUp();
 174         else if ("FocusPageDown".equals(name)) focusPageDown();
 175         else if ("FocusPreviousRow".equals(name)) focusPreviousRow();
 176         else if ("FocusNextRow".equals(name)) focusNextRow();
 177         else if ("DiscontinuousSelectNextRow".equals(name)) discontinuousSelectNextRow();
 178         else if ("DiscontinuousSelectPreviousRow".equals(name)) discontinuousSelectPreviousRow();
 179         else if ("DiscontinuousSelectPageUp".equals(name)) discontinuousSelectPageUp();
 180         else if ("DiscontinuousSelectPageDown".equals(name)) discontinuousSelectPageDown();
 181         else if ("DiscontinuousSelectAllToLastRow".equals(name)) discontinuousSelectAllToLastRow();
 182         else if ("DiscontinuousSelectAllToFirstRow".equals(name)) discontinuousSelectAllToFirstRow();
 183         else super.callAction(name);
 184     }
 185 
 186     @Override protected void callActionForEvent(KeyEvent e) {
 187         // RT-12751: we want to keep an eye on the user holding down the shift key, 
 188         // so that we know when they enter/leave multiple selection mode. This
 189         // changes what happens when certain key combinations are pressed.
 190         isShiftDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShiftDown();
 191         isShortcutDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShortcutDown();
 192         
 193         super.callActionForEvent(e);
 194     }

 195 
 196     /**************************************************************************
 197      *                         State and Functions                            *
 198      *************************************************************************/
 199 
 200     private boolean isShiftDown = false;
 201     private boolean isShortcutDown = false;
 202     
 203     // Support for RT-13826:
 204     // set when focus is moved by keyboard to allow for proper selection positions
 205 //    private int selectPos = -1;
 206     
 207     private Callback<Boolean, Integer> onScrollPageUp;
 208     public void setOnScrollPageUp(Callback<Boolean, Integer> c) { onScrollPageUp = c; }
 209 
 210     private Callback<Boolean, Integer> onScrollPageDown;
 211     public void setOnScrollPageDown(Callback<Boolean, Integer> c) { onScrollPageDown = c; }
 212 
 213     private Runnable onSelectPreviousRow;
 214     public void setOnSelectPreviousRow(Runnable r) { onSelectPreviousRow = r; }


 216     private Runnable onSelectNextRow;
 217     public void setOnSelectNextRow(Runnable r) { onSelectNextRow = r; }
 218 
 219     private Runnable onMoveToFirstCell;
 220     public void setOnMoveToFirstCell(Runnable r) { onMoveToFirstCell = r; }
 221 
 222     private Runnable onMoveToLastCell;
 223     public void setOnMoveToLastCell(Runnable r) { onMoveToLastCell = r; }
 224     
 225     private Runnable onFocusPreviousRow;
 226     public void setOnFocusPreviousRow(Runnable r) { onFocusPreviousRow = r; }
 227     
 228     private Runnable onFocusNextRow;
 229     public void setOnFocusNextRow(Runnable r) { onFocusNextRow = r; }
 230     
 231     private boolean selectionChanging = false;
 232     
 233     private final ListChangeListener<Integer> selectedIndicesListener = c -> {
 234         while (c.next()) {
 235             if (c.wasReplaced()) {
 236                 if (TreeCellBehavior.hasDefaultAnchor(getControl())) {
 237                     TreeCellBehavior.removeAnchor(getControl());
 238                 }
 239             }
 240 
 241             final int shift = c.wasPermutated() ? c.getTo() - c.getFrom() : 0;
 242 
 243             MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 244 
 245             // there are no selected items, so lets clear out the anchor
 246             if (! selectionChanging) {
 247                 if (sm.isEmpty()) {
 248                     setAnchor(-1);
 249                 } else if (hasAnchor() && ! sm.isSelected(getAnchor() + shift)) {
 250                     setAnchor(-1);
 251                 }
 252             }
 253 
 254             int addedSize = c.getAddedSize();
 255             if (addedSize > 0 && ! hasAnchor()) {
 256                 List<? extends Integer> addedSubList = c.getAddedSubList();
 257                 int index = addedSubList.get(addedSize - 1);
 258                 setAnchor(index);
 259             }
 260         }
 261     };
 262     
 263     private final ChangeListener<MultipleSelectionModel<TreeItem<T>>> selectionModelListener = 
 264             new ChangeListener<MultipleSelectionModel<TreeItem<T>>>() {
 265         @Override public void changed(ObservableValue<? extends MultipleSelectionModel<TreeItem<T>>> observable, 
 266                     MultipleSelectionModel<TreeItem<T>> oldValue, 
 267                     MultipleSelectionModel<TreeItem<T>> newValue) {
 268             if (oldValue != null) {
 269                 oldValue.getSelectedIndices().removeListener(weakSelectedIndicesListener);
 270             }
 271             if (newValue != null) {
 272                 newValue.getSelectedIndices().addListener(weakSelectedIndicesListener);
 273             }
 274         }
 275     };
 276     
 277     private final WeakListChangeListener<Integer> weakSelectedIndicesListener = 
 278             new WeakListChangeListener<Integer>(selectedIndicesListener);
 279     private final WeakChangeListener<MultipleSelectionModel<TreeItem<T>>> weakSelectionModelListener =
 280             new WeakChangeListener<MultipleSelectionModel<TreeItem<T>>>(selectionModelListener);
 281 
 282     public TreeViewBehavior(TreeView<T> control) {
 283         super(control, TREE_VIEW_BINDINGS);

























































































 284         
 285         // Fix for RT-16565
 286         getControl().selectionModelProperty().addListener(weakSelectionModelListener);
 287         if (control.getSelectionModel() != null) {
 288             control.getSelectionModel().getSelectedIndices().addListener(weakSelectedIndicesListener);
 289         }
 290     }
 291     




 292     @Override public void dispose() {
 293         TreeCellBehavior.removeAnchor(getControl());
 294         super.dispose();
 295     }
 296     
 297     private void setAnchor(int anchor) {
 298         TreeCellBehavior.setAnchor(getControl(), anchor < 0 ? null : anchor, false);
 299     }
 300     
 301     private int getAnchor() {
 302         return TreeCellBehavior.getAnchor(getControl(), getControl().getFocusModel().getFocusedIndex());
 303     }
 304     
 305     private boolean hasAnchor() {
 306         return TreeCellBehavior.hasNonDefaultAnchor(getControl());
 307     }
 308 
 309     @Override public void mousePressed(MouseEvent e) {
 310         super.mousePressed(e);
 311         
 312         if (! e.isShiftDown()) {
 313             int index = getControl().getSelectionModel().getSelectedIndex();
 314             setAnchor(index);
 315         }
 316         
 317         if (! getControl().isFocused() && getControl().isFocusTraversable()) {
 318             getControl().requestFocus();
 319         }
 320     }
 321 
 322     private void clearSelection() {
 323         getControl().getSelectionModel().clearSelection();
 324         //select(null);
 325     }
 326 
 327     private void scrollUp() {
 328         int newSelectedIndex = -1;
 329         if (onScrollPageUp != null) {
 330             newSelectedIndex = onScrollPageUp.call(false);
 331         }
 332         if (newSelectedIndex == -1) return;
 333         
 334         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 335         if (sm == null) return;
 336         sm.clearAndSelect(newSelectedIndex);
 337     }
 338 
 339     private void scrollDown() {
 340         int newSelectedIndex = -1;
 341         if (onScrollPageDown != null) {
 342             newSelectedIndex = onScrollPageDown.call(false);
 343         }
 344         if (newSelectedIndex == -1) return;
 345         
 346         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 347         if (sm == null) return;
 348         sm.clearAndSelect(newSelectedIndex);
 349     }
 350     
 351     private void focusFirstRow() {
 352         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 353         if (fm == null) return;
 354         fm.focus(0);
 355         
 356         if (onMoveToFirstCell != null) onMoveToFirstCell.run();
 357     }
 358     
 359     private void focusLastRow() {
 360         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 361         if (fm == null) return;
 362         fm.focus(getControl().getExpandedItemCount() - 1);
 363         
 364         if (onMoveToLastCell != null) onMoveToLastCell.run();
 365     }
 366     
 367     private void focusPreviousRow() {
 368         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 369         if (fm == null) return;
 370         
 371         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 372         if (sm == null) return;
 373         
 374         fm.focusPrevious();
 375         
 376         if (! isShortcutDown || getAnchor() == -1) {
 377             setAnchor(fm.getFocusedIndex());
 378         }
 379         
 380         if (onFocusPreviousRow != null) onFocusPreviousRow.run();
 381     }
 382 
 383     private void focusNextRow() {
 384         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 385         if (fm == null) return;
 386         
 387         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 388         if (sm == null) return;
 389         
 390         fm.focusNext();
 391         
 392         if (! isShortcutDown || getAnchor() == -1) {
 393             setAnchor(fm.getFocusedIndex());
 394         }
 395         
 396         if (onFocusNextRow != null) onFocusNextRow.run();
 397     }
 398     
 399     private void focusPageUp() {
 400         int newFocusIndex = onScrollPageUp.call(true);
 401         
 402         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 403         if (fm == null) return;
 404         fm.focus(newFocusIndex);
 405     }
 406     
 407     private void focusPageDown() {
 408         int newFocusIndex = onScrollPageDown.call(true);
 409         
 410         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 411         if (fm == null) return;
 412         fm.focus(newFocusIndex);
 413     }
 414 
 415     private void alsoSelectPreviousRow() {
 416         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 417         if (fm == null) return;
 418         
 419         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 420         if (sm == null) return;
 421         
 422         if (isShiftDown && getAnchor() != -1) {
 423             int newRow = fm.getFocusedIndex() - 1;
 424             if (newRow < 0) return;
 425             
 426             int anchor = getAnchor();
 427             
 428             if (! hasAnchor()) {
 429                 setAnchor(fm.getFocusedIndex());
 430             }
 431 
 432             if (sm.getSelectedIndices().size() > 1) {
 433                 clearSelectionOutsideRange(anchor, newRow);
 434             }
 435 
 436             if (anchor > newRow) {
 437                 sm.selectRange(anchor, newRow - 1);
 438             } else {
 439                 sm.selectRange(anchor, newRow + 1);
 440             }
 441         } else {
 442             sm.selectPrevious();
 443         }
 444         
 445         onSelectPreviousRow.run();
 446     }
 447 
 448     private void alsoSelectNextRow() {
 449         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 450         if (fm == null) return;
 451         
 452         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 453         if (sm == null) return;
 454         
 455         if (isShiftDown && getAnchor() != -1) {
 456             int newRow = fm.getFocusedIndex() + 1;
 457             int anchor = getAnchor();
 458             
 459             if (! hasAnchor()) {
 460                 setAnchor(fm.getFocusedIndex());
 461             } 
 462 
 463             if (sm.getSelectedIndices().size() > 1) {
 464                 clearSelectionOutsideRange(anchor, newRow);
 465             }
 466 
 467             if (anchor > newRow) {
 468                 sm.selectRange(anchor, newRow - 1);
 469             } else {
 470                 sm.selectRange(anchor, newRow + 1);
 471             }
 472         } else {
 473             sm.selectNext();
 474         }
 475         
 476         onSelectNextRow.run();
 477     }
 478     
 479     private void clearSelectionOutsideRange(int start, int end) {
 480         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 481         if (sm == null) return;
 482         
 483         int min = Math.min(start, end);
 484         int max = Math.max(start, end);
 485         
 486         List<Integer> indices = new ArrayList<Integer>(sm.getSelectedIndices());
 487         
 488         selectionChanging = true;
 489         for (int i = 0; i < indices.size(); i++) {
 490             int index = indices.get(i);
 491             if (index < min || index > max) {
 492                 sm.clearSelection(index);
 493             }
 494         }
 495         selectionChanging = false;
 496     }
 497 
 498     private void selectPreviousRow() {
 499         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 500         if (fm == null) return;
 501 
 502         int focusIndex = fm.getFocusedIndex();
 503         if (focusIndex <= 0) {
 504             return;
 505         }
 506 
 507         setAnchor(focusIndex - 1);
 508         getControl().getSelectionModel().clearAndSelect(focusIndex - 1);
 509         onSelectPreviousRow.run();
 510     }
 511 
 512     private void selectNextRow() {
 513         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 514         if (fm == null) return;
 515 
 516         int focusIndex = fm.getFocusedIndex();
 517         if (focusIndex == getControl().getExpandedItemCount() - 1) {
 518             return;
 519         }
 520 
 521         setAnchor(focusIndex + 1);
 522         getControl().getSelectionModel().clearAndSelect(focusIndex + 1);
 523         onSelectNextRow.run();
 524     }
 525 
 526     private void selectFirstRow() {
 527         if (getControl().getExpandedItemCount() > 0) {
 528             getControl().getSelectionModel().clearAndSelect(0);
 529             if (onMoveToFirstCell != null) onMoveToFirstCell.run();
 530         }
 531     }
 532 
 533     private void selectLastRow() {
 534         getControl().getSelectionModel().clearAndSelect(getControl().getExpandedItemCount() - 1);
 535         onMoveToLastCell.run();
 536     }
 537 
 538     private void selectAllToFirstRow() {
 539         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 540         if (sm == null) return;
 541 
 542         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 543         if (fm == null) return;
 544 
 545         int leadIndex = fm.getFocusedIndex();
 546 
 547         if (isShiftDown) {
 548             leadIndex = hasAnchor() ? getAnchor() : leadIndex;
 549         }
 550 
 551         sm.clearSelection();
 552         sm.selectRange(leadIndex, -1);
 553 
 554         // RT-18413: Focus must go to first row
 555         fm.focus(0);
 556         
 557         if (isShiftDown) {
 558             setAnchor(leadIndex);
 559         }
 560 
 561         if (onMoveToFirstCell != null) onMoveToFirstCell.run();
 562     }
 563 
 564     private void selectAllToLastRow() {
 565         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 566         if (sm == null) return;
 567 
 568         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 569         if (fm == null) return;
 570 
 571         int leadIndex = fm.getFocusedIndex();
 572 
 573         if (isShiftDown) {
 574             leadIndex = hasAnchor() ? getAnchor() : leadIndex;
 575         }
 576 
 577         sm.clearSelection();
 578         sm.selectRange(leadIndex, getControl().getExpandedItemCount());
 579         
 580         if (isShiftDown) {
 581             setAnchor(leadIndex);
 582         }
 583 
 584         if (onMoveToLastCell != null) onMoveToLastCell.run();
 585     }
 586 
 587     private void selectAll() {
 588         getControl().getSelectionModel().selectAll();
 589     }
 590     
 591     private void selectAllPageUp() {
 592         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 593         if (fm == null) return;
 594 
 595         int leadIndex = fm.getFocusedIndex();
 596         if (isShiftDown) {
 597             leadIndex = getAnchor() == -1 ? leadIndex : getAnchor();
 598             setAnchor(leadIndex);
 599         }
 600         
 601         int leadSelectedIndex = onScrollPageUp.call(false);
 602 
 603         // fix for RT-34407
 604         int adjust = leadIndex < leadSelectedIndex ? 1 : -1;
 605         
 606         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 607         if (sm == null) return;
 608         
 609         selectionChanging = true;
 610         if (sm.getSelectionMode() == SelectionMode.SINGLE) {
 611             sm.select(leadSelectedIndex);
 612         } else {
 613             sm.clearSelection();
 614             sm.selectRange(leadIndex, leadSelectedIndex + adjust);
 615         }
 616         selectionChanging = false;
 617     }
 618     
 619     private void selectAllPageDown() {
 620         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 621         if (fm == null) return;
 622         
 623         int leadIndex = fm.getFocusedIndex();
 624         if (isShiftDown) {
 625             leadIndex = getAnchor() == -1 ? leadIndex : getAnchor();
 626             setAnchor(leadIndex);
 627         }
 628         
 629         int leadSelectedIndex = onScrollPageDown.call(false);
 630 
 631         // fix for RT-34407
 632         int adjust = leadIndex < leadSelectedIndex ? 1 : -1;
 633         
 634         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 635         if (sm == null) return;
 636         
 637         selectionChanging = true;
 638         if (sm.getSelectionMode() == SelectionMode.SINGLE) {
 639             sm.select(leadSelectedIndex);
 640         } else {
 641             sm.clearSelection();
 642             sm.selectRange(leadIndex, leadSelectedIndex + adjust);
 643         }
 644         selectionChanging = false;
 645     }
 646     
 647     private void selectAllToFocus(boolean setAnchorToFocusIndex) {
 648         // Fix for RT-31241
 649         final TreeView<T> treeView = getControl();
 650         if (treeView.getEditingItem() != null) return;
 651 
 652         MultipleSelectionModel<TreeItem<T>> sm = treeView.getSelectionModel();
 653         if (sm == null) return;
 654 
 655         FocusModel<TreeItem<T>> fm = treeView.getFocusModel();
 656         if (fm == null) return;
 657 
 658         int focusIndex = fm.getFocusedIndex();
 659         int anchor = getAnchor();
 660 
 661         sm.clearSelection();
 662         int startPos = anchor;
 663         int endPos = anchor > focusIndex ? focusIndex - 1 : focusIndex + 1;
 664         sm.selectRange(startPos, endPos);
 665         setAnchor(setAnchorToFocusIndex ? focusIndex : anchor);
 666     }
 667     
 668     private void expandRow() {
 669         Callback<TreeItem<T>, Integer> getIndex = p -> getControl().getRow(p);
 670         TreeViewBehavior.expandRow(getControl().getSelectionModel(), getIndex);
 671     }
 672     
 673     private void expandAll() {
 674         TreeViewBehavior.expandAll(getControl().getRoot());
 675     }
 676     
 677     private void collapseRow() {
 678         TreeView<T> control = getControl();
 679         TreeViewBehavior.collapseRow(control.getSelectionModel(), control.getRoot(), control.isShowRoot());
 680     }
 681 
 682     static <T> void expandRow(final MultipleSelectionModel<TreeItem<T>> sm, Callback<TreeItem<T>, Integer> getIndex) {
 683         if (sm == null) return;
 684         
 685         TreeItem<T> treeItem = sm.getSelectedItem();
 686         if (treeItem == null || treeItem.isLeaf()) return;
 687         
 688         if (treeItem.isExpanded()) {
 689             // move selection to the first child (RT-17978)
 690             List<TreeItem<T>> children = treeItem.getChildren();
 691             if (! children.isEmpty()) {
 692                 sm.clearAndSelect(getIndex.call(children.get(0)));
 693             }
 694         } else {
 695             treeItem.setExpanded(true);
 696         }
 697     }
 698     


 730             return;
 731         }
 732         
 733         // Fix for RT-17833 where the selection highlight could disappear unexpectedly from
 734         // the root node in certain circumstances
 735         if (root.equals(selectedItem) && (! root.isExpanded() || root.getChildren().isEmpty())) {
 736             return;
 737         }
 738         
 739         // If we're on a leaf or the branch is not expanded, move up to the parent,
 740         // otherwise collapse the branch.
 741         if (selectedItem.isLeaf() || ! selectedItem.isExpanded()) {
 742             sm.clearSelection();
 743             sm.select(selectedItem.getParent());
 744         } else {
 745             selectedItem.setExpanded(false);
 746         }
 747     }
 748     
 749     private void cancelEdit() {
 750         getControl().edit(null);
 751     }
 752 
 753     private void edit() {
 754         TreeItem<T> treeItem = getControl().getSelectionModel().getSelectedItem();
 755         if (treeItem == null) return;
 756 
 757         getControl().edit(treeItem);
 758     }
 759     
 760     private void toggleFocusOwnerSelection() {
 761         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 762         if (sm == null) return;
 763 
 764         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 765         if (fm == null) return;
 766 
 767         int focusedIndex = fm.getFocusedIndex();
 768         
 769         if (sm.isSelected(focusedIndex)) {
 770             sm.clearSelection(focusedIndex);
 771             fm.focus(focusedIndex);
 772         } else {
 773             sm.select(focusedIndex);
 774         }
 775         
 776         setAnchor(focusedIndex);
 777     }
 778     
 779     /**************************************************************************
 780      * Discontinuous Selection                                                *
 781      *************************************************************************/
 782     
 783     private void discontinuousSelectPreviousRow() {
 784         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 785         if (sm == null) return;
 786 
 787         if (sm.getSelectionMode() != SelectionMode.MULTIPLE) {
 788             selectPreviousRow();
 789             return;
 790         }
 791         
 792         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 793         if (fm == null) return;
 794 
 795         int focusIndex = fm.getFocusedIndex();
 796         final int newFocusIndex = focusIndex - 1;
 797         if (newFocusIndex < 0) return;
 798 
 799         int startIndex = focusIndex;
 800         if (isShiftDown) {
 801             startIndex = getAnchor() == -1 ? focusIndex : getAnchor();
 802         }
 803 
 804         sm.selectRange(newFocusIndex, startIndex + 1);
 805         fm.focus(newFocusIndex);
 806 
 807         if (onFocusPreviousRow != null) onFocusPreviousRow.run();
 808     }
 809     
 810     private void discontinuousSelectNextRow() {
 811         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 812         if (sm == null) return;
 813 
 814         if (sm.getSelectionMode() != SelectionMode.MULTIPLE) {
 815             selectNextRow();
 816             return;
 817         }
 818         
 819         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 820         if (fm == null) return;
 821 
 822         int focusIndex = fm.getFocusedIndex();
 823         final int newFocusIndex = focusIndex + 1;
 824         if (newFocusIndex >= getControl().getExpandedItemCount()) return;
 825 
 826         int startIndex = focusIndex;
 827         if (isShiftDown) {
 828             startIndex = getAnchor() == -1 ? focusIndex : getAnchor();
 829         }
 830 
 831         sm.selectRange(startIndex, newFocusIndex + 1);
 832         fm.focus(newFocusIndex);
 833 
 834         if (onFocusNextRow != null) onFocusNextRow.run();
 835     }
 836     
 837     private void discontinuousSelectPageUp() {
 838         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 839         if (sm == null) return;
 840         
 841         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 842         if (fm == null) return;
 843 
 844         int anchor = getAnchor();
 845         int leadSelectedIndex = onScrollPageUp.call(false);
 846         sm.selectRange(anchor, leadSelectedIndex - 1);
 847     }
 848     
 849     private void discontinuousSelectPageDown() {
 850         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 851         if (sm == null) return;
 852         
 853         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 854         if (fm == null) return;
 855         
 856         int anchor = getAnchor();
 857         int leadSelectedIndex = onScrollPageDown.call(false);
 858         sm.selectRange(anchor, leadSelectedIndex + 1);
 859     }
 860     
 861     private void discontinuousSelectAllToFirstRow() {
 862         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 863         if (sm == null) return;
 864         
 865         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 866         if (fm == null) return;
 867 
 868         int index = fm.getFocusedIndex();
 869         sm.selectRange(0, index);
 870         fm.focus(0);
 871 
 872         if (onMoveToFirstCell != null) onMoveToFirstCell.run();
 873     }
 874     
 875     private void discontinuousSelectAllToLastRow() {
 876         MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
 877         if (sm == null) return;
 878         
 879         FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
 880         if (fm == null) return;
 881 
 882         int index = fm.getFocusedIndex() + 1;
 883         sm.selectRange(index, getControl().getExpandedItemCount());
 884 
 885         if (onMoveToLastCell != null) onMoveToLastCell.run();
 886     }
 887 }
   1 /*
   2  * Copyright (c) 2010, 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 
  26 package com.sun.javafx.scene.control.behavior;
  27 
  28 import com.sun.javafx.scene.control.inputmap.InputMap;
  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.WeakListChangeListener;
  34 import javafx.event.EventHandler;
  35 import javafx.scene.control.*;
  36 import javafx.scene.input.*;

  37 import javafx.util.Callback;
  38 
  39 import java.util.ArrayList;
  40 import java.util.List;
  41 import com.sun.javafx.PlatformUtil;
  42 import com.sun.javafx.scene.control.inputmap.KeyBinding;



















  43 
  44 import static javafx.scene.input.KeyCode.*;
  45 import static com.sun.javafx.scene.control.inputmap.InputMap.KeyMapping;
  46 
  47 public class TreeViewBehavior<T> extends BehaviorBase<TreeView<T>> {



  48 
  49     private final InputMap<TreeView<T>> treeViewInputMap;






















  50 
  51     private final EventHandler<KeyEvent> keyEventListener = e -> {
  52         if (!e.isConsumed()) {




























































































  53             // RT-12751: we want to keep an eye on the user holding down the shift key,
  54             // so that we know when they enter/leave multiple selection mode. This
  55             // changes what happens when certain key combinations are pressed.
  56             isShiftDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShiftDown();
  57             isShortcutDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShortcutDown();


  58         }
  59     };
  60 
  61     /**************************************************************************
  62      *                         State and Functions                            *
  63      *************************************************************************/
  64 
  65     private boolean isShiftDown = false;
  66     private boolean isShortcutDown = false;
  67     
  68     // Support for RT-13826:
  69     // set when focus is moved by keyboard to allow for proper selection positions
  70 //    private int selectPos = -1;
  71     
  72     private Callback<Boolean, Integer> onScrollPageUp;
  73     public void setOnScrollPageUp(Callback<Boolean, Integer> c) { onScrollPageUp = c; }
  74 
  75     private Callback<Boolean, Integer> onScrollPageDown;
  76     public void setOnScrollPageDown(Callback<Boolean, Integer> c) { onScrollPageDown = c; }
  77 
  78     private Runnable onSelectPreviousRow;
  79     public void setOnSelectPreviousRow(Runnable r) { onSelectPreviousRow = r; }


  81     private Runnable onSelectNextRow;
  82     public void setOnSelectNextRow(Runnable r) { onSelectNextRow = r; }
  83 
  84     private Runnable onMoveToFirstCell;
  85     public void setOnMoveToFirstCell(Runnable r) { onMoveToFirstCell = r; }
  86 
  87     private Runnable onMoveToLastCell;
  88     public void setOnMoveToLastCell(Runnable r) { onMoveToLastCell = r; }
  89     
  90     private Runnable onFocusPreviousRow;
  91     public void setOnFocusPreviousRow(Runnable r) { onFocusPreviousRow = r; }
  92     
  93     private Runnable onFocusNextRow;
  94     public void setOnFocusNextRow(Runnable r) { onFocusNextRow = r; }
  95     
  96     private boolean selectionChanging = false;
  97     
  98     private final ListChangeListener<Integer> selectedIndicesListener = c -> {
  99         while (c.next()) {
 100             if (c.wasReplaced()) {
 101                 if (TreeCellBehavior.hasDefaultAnchor(getNode())) {
 102                     TreeCellBehavior.removeAnchor(getNode());
 103                 }
 104             }
 105 
 106             final int shift = c.wasPermutated() ? c.getTo() - c.getFrom() : 0;
 107 
 108             MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 109 
 110             // there are no selected items, so lets clear out the anchor
 111             if (! selectionChanging) {
 112                 if (sm.isEmpty()) {
 113                     setAnchor(-1);
 114                 } else if (hasAnchor() && ! sm.isSelected(getAnchor() + shift)) {
 115                     setAnchor(-1);
 116                 }
 117             }
 118 
 119             int addedSize = c.getAddedSize();
 120             if (addedSize > 0 && ! hasAnchor()) {
 121                 List<? extends Integer> addedSubList = c.getAddedSubList();
 122                 int index = addedSubList.get(addedSize - 1);
 123                 setAnchor(index);
 124             }
 125         }
 126     };
 127     
 128     private final ChangeListener<MultipleSelectionModel<TreeItem<T>>> selectionModelListener = 
 129             new ChangeListener<MultipleSelectionModel<TreeItem<T>>>() {
 130         @Override public void changed(ObservableValue<? extends MultipleSelectionModel<TreeItem<T>>> observable, 
 131                     MultipleSelectionModel<TreeItem<T>> oldValue, 
 132                     MultipleSelectionModel<TreeItem<T>> newValue) {
 133             if (oldValue != null) {
 134                 oldValue.getSelectedIndices().removeListener(weakSelectedIndicesListener);
 135             }
 136             if (newValue != null) {
 137                 newValue.getSelectedIndices().addListener(weakSelectedIndicesListener);
 138             }
 139         }
 140     };
 141     
 142     private final WeakListChangeListener<Integer> weakSelectedIndicesListener = 
 143             new WeakListChangeListener<>(selectedIndicesListener);
 144     private final WeakChangeListener<MultipleSelectionModel<TreeItem<T>>> weakSelectionModelListener =
 145             new WeakChangeListener<>(selectionModelListener);
 146 
 147     public TreeViewBehavior(TreeView<T> control) {
 148         super(control);
 149 
 150 //        // Fix for RT-16565
 151 //        getNode().selectionModelProperty().addListener(weakSelectionModelListener);
 152 //        if (control.getSelectionModel() != null) {
 153 //            control.getSelectionModel().getSelectedIndices().addListener(weakSelectedIndicesListener);
 154 //        }
 155 
 156 
 157 
 158         // create a map for treeView-specific mappings
 159         treeViewInputMap = createInputMap();
 160 
 161 //        // add focus traversal mappings
 162 //        addDefaultMapping(treeViewInputMap, FocusTraversalInputMap.getFocusTraversalMappings());
 163         addDefaultMapping(treeViewInputMap,
 164             new KeyMapping(HOME, e -> selectFirstRow()),
 165             new KeyMapping(END, e -> selectLastRow()),
 166             new KeyMapping(new KeyBinding(HOME).shift(), e -> selectAllToFirstRow()),
 167             new KeyMapping(new KeyBinding(END).shift(), e -> selectAllToLastRow()),
 168             new KeyMapping(new KeyBinding(PAGE_UP).shift(), e -> selectAllPageUp()),
 169             new KeyMapping(new KeyBinding(PAGE_DOWN).shift(), e -> selectAllPageDown()),
 170 
 171             new KeyMapping(new KeyBinding(SPACE).shift(), e -> selectAllToFocus(false)),
 172             new KeyMapping(new KeyBinding(SPACE).shortcut().shift(), e -> selectAllToFocus(true)),
 173 
 174             new KeyMapping(new KeyBinding(HOME).shortcut(), e -> focusFirstRow()),
 175             new KeyMapping(new KeyBinding(END).shortcut(), e -> focusLastRow()),
 176 
 177             new KeyMapping(PAGE_UP, e -> scrollUp()),
 178             new KeyMapping(PAGE_DOWN, e -> scrollDown()),
 179 
 180             new KeyMapping(SPACE, e -> toggleFocusOwnerSelection()),
 181 
 182             new KeyMapping(new KeyBinding(A).shortcut(), e -> selectAll()),
 183             new KeyMapping(new KeyBinding(PAGE_UP).shortcut(), e -> focusPageUp()),
 184             new KeyMapping(new KeyBinding(PAGE_DOWN).shortcut(), e -> focusPageDown()),
 185             new KeyMapping(new KeyBinding(UP).shortcut(), e -> focusPreviousRow()),
 186             new KeyMapping(new KeyBinding(DOWN).shortcut(), e -> focusNextRow()),
 187             new KeyMapping(new KeyBinding(UP).shortcut().shift(), e -> discontinuousSelectPreviousRow()),
 188             new KeyMapping(new KeyBinding(DOWN).shortcut().shift(), e -> discontinuousSelectNextRow()),
 189             new KeyMapping(new KeyBinding(PAGE_UP).shortcut().shift(), e -> discontinuousSelectPageUp()),
 190             new KeyMapping(new KeyBinding(PAGE_DOWN).shortcut().shift(), e -> discontinuousSelectPageDown()),
 191             new KeyMapping(new KeyBinding(HOME).shortcut().shift(), e -> discontinuousSelectAllToFirstRow()),
 192             new KeyMapping(new KeyBinding(END).shortcut().shift(), e -> discontinuousSelectAllToLastRow()),
 193 
 194             // these should be read as 'if RTL, use the first method, otherwise use the second'
 195             new KeyMapping(LEFT, e -> rtl(control, this::expandRow, this::collapseRow)),
 196             new KeyMapping(KP_LEFT, e -> rtl(control, this::expandRow, this::collapseRow)),
 197             new KeyMapping(RIGHT, e -> rtl(control, this::collapseRow, this::expandRow)),
 198             new KeyMapping(KP_RIGHT, e -> rtl(control, this::collapseRow, this::expandRow)),
 199 
 200             new KeyMapping(MULTIPLY, e -> expandAll()),
 201             new KeyMapping(ADD, e -> expandRow()),
 202             new KeyMapping(SUBTRACT, e -> collapseRow()),
 203 
 204             new KeyMapping(UP, e -> selectPreviousRow()),
 205             new KeyMapping(KP_UP, e -> selectPreviousRow()),
 206             new KeyMapping(DOWN, e -> selectNextRow()),
 207             new KeyMapping(KP_DOWN, e -> selectNextRow()),
 208 
 209             new KeyMapping(new KeyBinding(UP).shift(), e -> alsoSelectPreviousRow()),
 210             new KeyMapping(new KeyBinding(KP_UP).shift(), e -> alsoSelectPreviousRow()),
 211             new KeyMapping(new KeyBinding(DOWN).shift(), e -> alsoSelectNextRow()),
 212             new KeyMapping(new KeyBinding(KP_DOWN).shift(), e -> alsoSelectNextRow()),
 213 
 214             new KeyMapping(ENTER, e -> edit()),
 215             new KeyMapping(F2, e -> edit()),
 216             new KeyMapping(ESCAPE, e -> cancelEdit()),
 217 
 218             new InputMap.MouseMapping(MouseEvent.MOUSE_PRESSED, this::mousePressed)
 219         );
 220 
 221         // create OS-specific child mappings
 222         // --- mac OS
 223         InputMap<TreeView<T>> macInputMap = new InputMap<>(control);
 224         macInputMap.setInterceptor(event -> !PlatformUtil.isMac());
 225         addDefaultMapping(macInputMap, new KeyMapping(new KeyBinding(SPACE).shortcut().ctrl(), e -> toggleFocusOwnerSelection()));
 226         addDefaultChildMap(treeViewInputMap, macInputMap);
 227 
 228         // --- all other platforms
 229         InputMap<TreeView<T>> otherOsInputMap = new InputMap<>(control);
 230         otherOsInputMap.setInterceptor(event -> PlatformUtil.isMac());
 231         addDefaultMapping(otherOsInputMap, new KeyMapping(new KeyBinding(SPACE).ctrl(), e -> toggleFocusOwnerSelection()));
 232         addDefaultChildMap(treeViewInputMap, otherOsInputMap);
 233 
 234         // set up other listeners
 235         // We make this an event _filter_ so that we can determine the state
 236         // of the shift key before the event handlers get a shot at the event.
 237         control.addEventFilter(KeyEvent.ANY, keyEventListener);
 238 
 239         // Fix for RT-16565
 240         control.selectionModelProperty().addListener(weakSelectionModelListener);
 241         if (control.getSelectionModel() != null) {
 242             control.getSelectionModel().getSelectedIndices().addListener(weakSelectedIndicesListener);
 243         }
 244     }
 245 
 246     @Override public InputMap<TreeView<T>> getInputMap() {
 247         return treeViewInputMap;
 248     }
 249 
 250     @Override public void dispose() {
 251         TreeCellBehavior.removeAnchor(getNode());
 252         super.dispose();
 253     }
 254     
 255     private void setAnchor(int anchor) {
 256         TreeCellBehavior.setAnchor(getNode(), anchor < 0 ? null : anchor, false);
 257     }
 258     
 259     private int getAnchor() {
 260         return TreeCellBehavior.getAnchor(getNode(), getNode().getFocusModel().getFocusedIndex());
 261     }
 262     
 263     private boolean hasAnchor() {
 264         return TreeCellBehavior.hasNonDefaultAnchor(getNode());
 265     }
 266 
 267     public void mousePressed(MouseEvent e) {


 268         if (! e.isShiftDown()) {
 269             int index = getNode().getSelectionModel().getSelectedIndex();
 270             setAnchor(index);
 271         }
 272         
 273         if (! getNode().isFocused() && getNode().isFocusTraversable()) {
 274             getNode().requestFocus();
 275         }
 276     }
 277 
 278     private void clearSelection() {
 279         getNode().getSelectionModel().clearSelection();
 280         //select(null);
 281     }
 282 
 283     private void scrollUp() {
 284         int newSelectedIndex = -1;
 285         if (onScrollPageUp != null) {
 286             newSelectedIndex = onScrollPageUp.call(false);
 287         }
 288         if (newSelectedIndex == -1) return;
 289         
 290         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 291         if (sm == null) return;
 292         sm.clearAndSelect(newSelectedIndex);
 293     }
 294 
 295     private void scrollDown() {
 296         int newSelectedIndex = -1;
 297         if (onScrollPageDown != null) {
 298             newSelectedIndex = onScrollPageDown.call(false);
 299         }
 300         if (newSelectedIndex == -1) return;
 301         
 302         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 303         if (sm == null) return;
 304         sm.clearAndSelect(newSelectedIndex);
 305     }
 306     
 307     private void focusFirstRow() {
 308         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 309         if (fm == null) return;
 310         fm.focus(0);
 311         
 312         if (onMoveToFirstCell != null) onMoveToFirstCell.run();
 313     }
 314     
 315     private void focusLastRow() {
 316         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 317         if (fm == null) return;
 318         fm.focus(getNode().getExpandedItemCount() - 1);
 319         
 320         if (onMoveToLastCell != null) onMoveToLastCell.run();
 321     }
 322     
 323     private void focusPreviousRow() {
 324         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 325         if (fm == null) return;
 326         
 327         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 328         if (sm == null) return;
 329         
 330         fm.focusPrevious();
 331         
 332         if (! isShortcutDown || getAnchor() == -1) {
 333             setAnchor(fm.getFocusedIndex());
 334         }
 335         
 336         if (onFocusPreviousRow != null) onFocusPreviousRow.run();
 337     }
 338 
 339     private void focusNextRow() {
 340         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 341         if (fm == null) return;
 342         
 343         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 344         if (sm == null) return;
 345         
 346         fm.focusNext();
 347         
 348         if (! isShortcutDown || getAnchor() == -1) {
 349             setAnchor(fm.getFocusedIndex());
 350         }
 351         
 352         if (onFocusNextRow != null) onFocusNextRow.run();
 353     }
 354     
 355     private void focusPageUp() {
 356         int newFocusIndex = onScrollPageUp.call(true);
 357         
 358         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 359         if (fm == null) return;
 360         fm.focus(newFocusIndex);
 361     }
 362     
 363     private void focusPageDown() {
 364         int newFocusIndex = onScrollPageDown.call(true);
 365         
 366         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 367         if (fm == null) return;
 368         fm.focus(newFocusIndex);
 369     }
 370 
 371     private void alsoSelectPreviousRow() {
 372         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 373         if (fm == null) return;
 374         
 375         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 376         if (sm == null) return;
 377         
 378         if (isShiftDown && getAnchor() != -1) {
 379             int newRow = fm.getFocusedIndex() - 1;
 380             if (newRow < 0) return;
 381             
 382             int anchor = getAnchor();
 383             
 384             if (! hasAnchor()) {
 385                 setAnchor(fm.getFocusedIndex());
 386             }
 387 
 388             if (sm.getSelectedIndices().size() > 1) {
 389                 clearSelectionOutsideRange(anchor, newRow);
 390             }
 391 
 392             if (anchor > newRow) {
 393                 sm.selectRange(anchor, newRow - 1);
 394             } else {
 395                 sm.selectRange(anchor, newRow + 1);
 396             }
 397         } else {
 398             sm.selectPrevious();
 399         }
 400         
 401         onSelectPreviousRow.run();
 402     }
 403 
 404     private void alsoSelectNextRow() {
 405         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 406         if (fm == null) return;
 407         
 408         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 409         if (sm == null) return;
 410         
 411         if (isShiftDown && getAnchor() != -1) {
 412             int newRow = fm.getFocusedIndex() + 1;
 413             int anchor = getAnchor();
 414             
 415             if (! hasAnchor()) {
 416                 setAnchor(fm.getFocusedIndex());
 417             } 
 418 
 419             if (sm.getSelectedIndices().size() > 1) {
 420                 clearSelectionOutsideRange(anchor, newRow);
 421             }
 422 
 423             if (anchor > newRow) {
 424                 sm.selectRange(anchor, newRow - 1);
 425             } else {
 426                 sm.selectRange(anchor, newRow + 1);
 427             }
 428         } else {
 429             sm.selectNext();
 430         }
 431         
 432         onSelectNextRow.run();
 433     }
 434     
 435     private void clearSelectionOutsideRange(int start, int end) {
 436         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 437         if (sm == null) return;
 438         
 439         int min = Math.min(start, end);
 440         int max = Math.max(start, end);
 441         
 442         List<Integer> indices = new ArrayList<Integer>(sm.getSelectedIndices());
 443         
 444         selectionChanging = true;
 445         for (int i = 0; i < indices.size(); i++) {
 446             int index = indices.get(i);
 447             if (index < min || index > max) {
 448                 sm.clearSelection(index);
 449             }
 450         }
 451         selectionChanging = false;
 452     }
 453 
 454     private void selectPreviousRow() {
 455         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 456         if (fm == null) return;
 457 
 458         int focusIndex = fm.getFocusedIndex();
 459         if (focusIndex <= 0) {
 460             return;
 461         }
 462 
 463         setAnchor(focusIndex - 1);
 464         getNode().getSelectionModel().clearAndSelect(focusIndex - 1);
 465         onSelectPreviousRow.run();
 466     }
 467 
 468     private void selectNextRow() {
 469         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 470         if (fm == null) return;
 471 
 472         int focusIndex = fm.getFocusedIndex();
 473         if (focusIndex == getNode().getExpandedItemCount() - 1) {
 474             return;
 475         }
 476 
 477         setAnchor(focusIndex + 1);
 478         getNode().getSelectionModel().clearAndSelect(focusIndex + 1);
 479         onSelectNextRow.run();
 480     }
 481 
 482     private void selectFirstRow() {
 483         if (getNode().getExpandedItemCount() > 0) {
 484             getNode().getSelectionModel().clearAndSelect(0);
 485             if (onMoveToFirstCell != null) onMoveToFirstCell.run();
 486         }
 487     }
 488 
 489     private void selectLastRow() {
 490         getNode().getSelectionModel().clearAndSelect(getNode().getExpandedItemCount() - 1);
 491         onMoveToLastCell.run();
 492     }
 493 
 494     private void selectAllToFirstRow() {
 495         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 496         if (sm == null) return;
 497 
 498         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 499         if (fm == null) return;
 500 
 501         int leadIndex = fm.getFocusedIndex();
 502 
 503         if (isShiftDown) {
 504             leadIndex = hasAnchor() ? getAnchor() : leadIndex;
 505         }
 506 
 507         sm.clearSelection();
 508         sm.selectRange(leadIndex, -1);
 509 
 510         // RT-18413: Focus must go to first row
 511         fm.focus(0);
 512         
 513         if (isShiftDown) {
 514             setAnchor(leadIndex);
 515         }
 516 
 517         if (onMoveToFirstCell != null) onMoveToFirstCell.run();
 518     }
 519 
 520     private void selectAllToLastRow() {
 521         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 522         if (sm == null) return;
 523 
 524         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 525         if (fm == null) return;
 526 
 527         int leadIndex = fm.getFocusedIndex();
 528 
 529         if (isShiftDown) {
 530             leadIndex = hasAnchor() ? getAnchor() : leadIndex;
 531         }
 532 
 533         sm.clearSelection();
 534         sm.selectRange(leadIndex, getNode().getExpandedItemCount());
 535         
 536         if (isShiftDown) {
 537             setAnchor(leadIndex);
 538         }
 539 
 540         if (onMoveToLastCell != null) onMoveToLastCell.run();
 541     }
 542 
 543     private void selectAll() {
 544         getNode().getSelectionModel().selectAll();
 545     }
 546     
 547     private void selectAllPageUp() {
 548         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 549         if (fm == null) return;
 550 
 551         int leadIndex = fm.getFocusedIndex();
 552         if (isShiftDown) {
 553             leadIndex = getAnchor() == -1 ? leadIndex : getAnchor();
 554             setAnchor(leadIndex);
 555         }
 556         
 557         int leadSelectedIndex = onScrollPageUp.call(false);
 558 
 559         // fix for RT-34407
 560         int adjust = leadIndex < leadSelectedIndex ? 1 : -1;
 561         
 562         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 563         if (sm == null) return;
 564         
 565         selectionChanging = true;
 566         if (sm.getSelectionMode() == SelectionMode.SINGLE) {
 567             sm.select(leadSelectedIndex);
 568         } else {
 569             sm.clearSelection();
 570             sm.selectRange(leadIndex, leadSelectedIndex + adjust);
 571         }
 572         selectionChanging = false;
 573     }
 574     
 575     private void selectAllPageDown() {
 576         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 577         if (fm == null) return;
 578         
 579         int leadIndex = fm.getFocusedIndex();
 580         if (isShiftDown) {
 581             leadIndex = getAnchor() == -1 ? leadIndex : getAnchor();
 582             setAnchor(leadIndex);
 583         }
 584         
 585         int leadSelectedIndex = onScrollPageDown.call(false);
 586 
 587         // fix for RT-34407
 588         int adjust = leadIndex < leadSelectedIndex ? 1 : -1;
 589         
 590         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 591         if (sm == null) return;
 592         
 593         selectionChanging = true;
 594         if (sm.getSelectionMode() == SelectionMode.SINGLE) {
 595             sm.select(leadSelectedIndex);
 596         } else {
 597             sm.clearSelection();
 598             sm.selectRange(leadIndex, leadSelectedIndex + adjust);
 599         }
 600         selectionChanging = false;
 601     }
 602     
 603     private void selectAllToFocus(boolean setAnchorToFocusIndex) {
 604         // Fix for RT-31241
 605         final TreeView<T> treeView = getNode();
 606         if (treeView.getEditingItem() != null) return;
 607 
 608         MultipleSelectionModel<TreeItem<T>> sm = treeView.getSelectionModel();
 609         if (sm == null) return;
 610 
 611         FocusModel<TreeItem<T>> fm = treeView.getFocusModel();
 612         if (fm == null) return;
 613 
 614         int focusIndex = fm.getFocusedIndex();
 615         int anchor = getAnchor();
 616 
 617         sm.clearSelection();
 618         int startPos = anchor;
 619         int endPos = anchor > focusIndex ? focusIndex - 1 : focusIndex + 1;
 620         sm.selectRange(startPos, endPos);
 621         setAnchor(setAnchorToFocusIndex ? focusIndex : anchor);
 622     }
 623     
 624     private void expandRow() {
 625         Callback<TreeItem<T>, Integer> getIndex = p -> getNode().getRow(p);
 626         TreeViewBehavior.expandRow(getNode().getSelectionModel(), getIndex);
 627     }
 628     
 629     private void expandAll() {
 630         TreeViewBehavior.expandAll(getNode().getRoot());
 631     }
 632     
 633     private void collapseRow() {
 634         TreeView<T> control = getNode();
 635         TreeViewBehavior.collapseRow(control.getSelectionModel(), control.getRoot(), control.isShowRoot());
 636     }
 637 
 638     static <T> void expandRow(final MultipleSelectionModel<TreeItem<T>> sm, Callback<TreeItem<T>, Integer> getIndex) {
 639         if (sm == null) return;
 640         
 641         TreeItem<T> treeItem = sm.getSelectedItem();
 642         if (treeItem == null || treeItem.isLeaf()) return;
 643         
 644         if (treeItem.isExpanded()) {
 645             // move selection to the first child (RT-17978)
 646             List<TreeItem<T>> children = treeItem.getChildren();
 647             if (! children.isEmpty()) {
 648                 sm.clearAndSelect(getIndex.call(children.get(0)));
 649             }
 650         } else {
 651             treeItem.setExpanded(true);
 652         }
 653     }
 654     


 686             return;
 687         }
 688         
 689         // Fix for RT-17833 where the selection highlight could disappear unexpectedly from
 690         // the root node in certain circumstances
 691         if (root.equals(selectedItem) && (! root.isExpanded() || root.getChildren().isEmpty())) {
 692             return;
 693         }
 694         
 695         // If we're on a leaf or the branch is not expanded, move up to the parent,
 696         // otherwise collapse the branch.
 697         if (selectedItem.isLeaf() || ! selectedItem.isExpanded()) {
 698             sm.clearSelection();
 699             sm.select(selectedItem.getParent());
 700         } else {
 701             selectedItem.setExpanded(false);
 702         }
 703     }
 704     
 705     private void cancelEdit() {
 706         getNode().edit(null);
 707     }
 708 
 709     private void edit() {
 710         TreeItem<T> treeItem = getNode().getSelectionModel().getSelectedItem();
 711         if (treeItem == null) return;
 712 
 713         getNode().edit(treeItem);
 714     }
 715     
 716     private void toggleFocusOwnerSelection() {
 717         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 718         if (sm == null) return;
 719 
 720         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 721         if (fm == null) return;
 722 
 723         int focusedIndex = fm.getFocusedIndex();
 724         
 725         if (sm.isSelected(focusedIndex)) {
 726             sm.clearSelection(focusedIndex);
 727             fm.focus(focusedIndex);
 728         } else {
 729             sm.select(focusedIndex);
 730         }
 731         
 732         setAnchor(focusedIndex);
 733     }
 734     
 735     /**************************************************************************
 736      * Discontinuous Selection                                                *
 737      *************************************************************************/
 738     
 739     private void discontinuousSelectPreviousRow() {
 740         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 741         if (sm == null) return;
 742 
 743         if (sm.getSelectionMode() != SelectionMode.MULTIPLE) {
 744             selectPreviousRow();
 745             return;
 746         }
 747         
 748         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 749         if (fm == null) return;
 750 
 751         int focusIndex = fm.getFocusedIndex();
 752         final int newFocusIndex = focusIndex - 1;
 753         if (newFocusIndex < 0) return;
 754 
 755         int startIndex = focusIndex;
 756         if (isShiftDown) {
 757             startIndex = getAnchor() == -1 ? focusIndex : getAnchor();
 758         }
 759 
 760         sm.selectRange(newFocusIndex, startIndex + 1);
 761         fm.focus(newFocusIndex);
 762 
 763         if (onFocusPreviousRow != null) onFocusPreviousRow.run();
 764     }
 765     
 766     private void discontinuousSelectNextRow() {
 767         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 768         if (sm == null) return;
 769 
 770         if (sm.getSelectionMode() != SelectionMode.MULTIPLE) {
 771             selectNextRow();
 772             return;
 773         }
 774         
 775         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 776         if (fm == null) return;
 777 
 778         int focusIndex = fm.getFocusedIndex();
 779         final int newFocusIndex = focusIndex + 1;
 780         if (newFocusIndex >= getNode().getExpandedItemCount()) return;
 781 
 782         int startIndex = focusIndex;
 783         if (isShiftDown) {
 784             startIndex = getAnchor() == -1 ? focusIndex : getAnchor();
 785         }
 786 
 787         sm.selectRange(startIndex, newFocusIndex + 1);
 788         fm.focus(newFocusIndex);
 789 
 790         if (onFocusNextRow != null) onFocusNextRow.run();
 791     }
 792     
 793     private void discontinuousSelectPageUp() {
 794         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 795         if (sm == null) return;
 796         
 797         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 798         if (fm == null) return;
 799 
 800         int anchor = getAnchor();
 801         int leadSelectedIndex = onScrollPageUp.call(false);
 802         sm.selectRange(anchor, leadSelectedIndex - 1);
 803     }
 804     
 805     private void discontinuousSelectPageDown() {
 806         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 807         if (sm == null) return;
 808         
 809         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 810         if (fm == null) return;
 811         
 812         int anchor = getAnchor();
 813         int leadSelectedIndex = onScrollPageDown.call(false);
 814         sm.selectRange(anchor, leadSelectedIndex + 1);
 815     }
 816     
 817     private void discontinuousSelectAllToFirstRow() {
 818         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 819         if (sm == null) return;
 820         
 821         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 822         if (fm == null) return;
 823 
 824         int index = fm.getFocusedIndex();
 825         sm.selectRange(0, index);
 826         fm.focus(0);
 827 
 828         if (onMoveToFirstCell != null) onMoveToFirstCell.run();
 829     }
 830     
 831     private void discontinuousSelectAllToLastRow() {
 832         MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
 833         if (sm == null) return;
 834         
 835         FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
 836         if (fm == null) return;
 837 
 838         int index = fm.getFocusedIndex() + 1;
 839         sm.selectRange(index, getNode().getExpandedItemCount());
 840 
 841         if (onMoveToLastCell != null) onMoveToLastCell.run();
 842     }
 843 }