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 }