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 }