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; }
  80 
  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     
 655     static <T> void expandAll(final TreeItem<T> root) {
 656         if (root == null) return;
 657         
 658         root.setExpanded(true);
 659         expandChildren(root);
 660     }
 661     
 662     private static <T> void expandChildren(TreeItem<T> node) {
 663         if (node == null) return;
 664         List<TreeItem<T>> children = node.getChildren();
 665         if (children == null) return;
 666         
 667         for (int i = 0; i < children.size(); i++) {
 668             TreeItem<T> child = children.get(i);
 669             if (child == null || child.isLeaf()) continue;
 670             
 671             child.setExpanded(true);
 672             expandChildren(child);
 673         }
 674     }
 675 
 676     static <T> void collapseRow(final MultipleSelectionModel<TreeItem<T>> sm, final TreeItem<T> root, final boolean isShowRoot) {
 677         if (sm == null) return;
 678         
 679         TreeItem<T> selectedItem = sm.getSelectedItem();
 680         if (selectedItem == null) return;
 681         if (root == null) return;
 682         
 683         // Fix for RT-17233 where we could hide all items in a tree with no visible
 684         // root by pressing the left-arrow key too many times
 685         if (! isShowRoot && ! selectedItem.isExpanded() && root.equals(selectedItem.getParent())) {
 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 }