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; }
 215 
 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     
 699     static <T> void expandAll(final TreeItem<T> root) {
 700         if (root == null) return;
 701         
 702         root.setExpanded(true);
 703         expandChildren(root);
 704     }
 705     
 706     private static <T> void expandChildren(TreeItem<T> node) {
 707         if (node == null) return;
 708         List<TreeItem<T>> children = node.getChildren();
 709         if (children == null) return;
 710         
 711         for (int i = 0; i < children.size(); i++) {
 712             TreeItem<T> child = children.get(i);
 713             if (child == null || child.isLeaf()) continue;
 714             
 715             child.setExpanded(true);
 716             expandChildren(child);
 717         }
 718     }
 719 
 720     static <T> void collapseRow(final MultipleSelectionModel<TreeItem<T>> sm, final TreeItem<T> root, final boolean isShowRoot) {
 721         if (sm == null) return;
 722         
 723         TreeItem<T> selectedItem = sm.getSelectedItem();
 724         if (selectedItem == null) return;
 725         if (root == null) return;
 726         
 727         // Fix for RT-17233 where we could hide all items in a tree with no visible
 728         // root by pressing the left-arrow key too many times
 729         if (! isShowRoot && ! selectedItem.isExpanded() && root.equals(selectedItem.getParent())) {
 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 }