1 /* 2 * Copyright (c) 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 package com.sun.javafx.scene.control.behavior; 26 27 import com.sun.javafx.PlatformUtil; 28 import com.sun.javafx.scene.control.skin.Utils; 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.ObservableList; 34 import javafx.collections.WeakListChangeListener; 35 import javafx.event.EventHandler; 36 import javafx.geometry.Orientation; 37 import javafx.scene.control.FocusModel; 38 import javafx.scene.control.ListView; 39 import javafx.scene.control.MultipleSelectionModel; 40 import javafx.scene.control.SelectionMode; 41 import com.sun.javafx.scene.control.inputmap.InputMap; 42 import com.sun.javafx.scene.control.inputmap.KeyBinding; 43 import javafx.scene.input.KeyEvent; 44 import javafx.scene.input.MouseEvent; 45 import javafx.util.Callback; 46 47 import java.util.ArrayList; 48 import java.util.List; 49 50 import static com.sun.javafx.scene.control.inputmap.InputMap.*; 51 import static javafx.scene.input.KeyCode.*; 52 53 public class ListViewBehavior<T> extends BehaviorBase<ListView<T>> { 54 private final InputMap<ListView<T>> listViewInputMap; 55 56 /** 57 * Indicates that a keyboard key has been pressed which represents the 58 * event (this could be space bar for example). As long as keyDown is true, 59 * we are also armed, and will ignore mouse events related to arming. 60 * Note this is made package private solely for the sake of testing. 61 */ 62 private boolean keyDown; 63 64 private final EventHandler<KeyEvent> keyEventListener = e -> { 65 if (!e.isConsumed()) { 66 // RT-12751: we want to keep an eye on the user holding down the shift key, 67 // so that we know when they enter/leave multiple selection mode. This 68 // changes what happens when certain key combinations are pressed. 69 isShiftDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShiftDown(); 70 isShortcutDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShortcutDown(); 71 } 72 }; 73 74 75 76 /*************************************************************************** 77 * * 78 * Constructors * 79 * * 80 **************************************************************************/ 81 82 public ListViewBehavior(ListView<T> control) { 83 super(control); 84 85 // create a map for listView-specific mappings 86 listViewInputMap = createInputMap(); 87 88 // add focus traversal mappings 89 addDefaultMapping(listViewInputMap, FocusTraversalInputMap.getFocusTraversalMappings()); 90 addDefaultMapping(listViewInputMap, 91 new KeyMapping(HOME, e -> selectFirstRow()), 92 new KeyMapping(END, e -> selectLastRow()), 93 new KeyMapping(new KeyBinding(HOME).shift(), e -> selectAllToFirstRow()), 94 new KeyMapping(new KeyBinding(END).shift(), e -> selectAllToLastRow()), 95 new KeyMapping(new KeyBinding(PAGE_UP).shift(), e -> selectAllPageUp()), 96 new KeyMapping(new KeyBinding(PAGE_DOWN).shift(), e -> selectAllPageDown()), 97 98 new KeyMapping(new KeyBinding(SPACE).shift(), e -> selectAllToFocus(false)), 99 new KeyMapping(new KeyBinding(SPACE).shortcut().shift(), e -> selectAllToFocus(true)), 100 101 new KeyMapping(PAGE_UP, e -> scrollPageUp()), 102 new KeyMapping(PAGE_DOWN, e -> scrollPageDown()), 103 104 new KeyMapping(ENTER, e -> activate()), 105 new KeyMapping(SPACE, e -> activate()), 106 new KeyMapping(F2, e -> activate()), 107 new KeyMapping(ESCAPE, e -> cancelEdit()), 108 109 new KeyMapping(new KeyBinding(A).shortcut(), e -> selectAll()), 110 new KeyMapping(new KeyBinding(HOME).shortcut(), e -> focusFirstRow()), 111 new KeyMapping(new KeyBinding(END).shortcut(), e -> focusLastRow()), 112 new KeyMapping(new KeyBinding(PAGE_UP).shortcut(), e -> focusPageUp()), 113 new KeyMapping(new KeyBinding(PAGE_DOWN).shortcut(), e -> focusPageDown()), 114 115 new KeyMapping(new KeyBinding(BACK_SLASH).shortcut(), e -> clearSelection()), 116 117 new MouseMapping(MouseEvent.MOUSE_PRESSED, this::mousePressed) 118 ); 119 120 // create OS-specific child mappings 121 // --- mac OS 122 InputMap<ListView<T>> macInputMap = new InputMap<>(control); 123 macInputMap.setInterceptor(event -> !PlatformUtil.isMac()); 124 addDefaultMapping(macInputMap, new KeyMapping(new KeyBinding(SPACE).shortcut().ctrl(), e -> toggleFocusOwnerSelection())); 125 addDefaultChildMap(listViewInputMap, macInputMap); 126 127 // --- all other platforms 128 InputMap<ListView<T>> otherOsInputMap = new InputMap<>(control); 129 otherOsInputMap.setInterceptor(event -> PlatformUtil.isMac()); 130 addDefaultMapping(otherOsInputMap, new KeyMapping(new KeyBinding(SPACE).ctrl(), e -> toggleFocusOwnerSelection())); 131 addDefaultChildMap(listViewInputMap, otherOsInputMap); 132 133 // create two more child maps, one for vertical listview and one for horizontal listview 134 // --- vertical listview 135 InputMap<ListView<T>> verticalListInputMap = new InputMap<>(control); 136 verticalListInputMap.setInterceptor(event -> control.getOrientation() != Orientation.VERTICAL); 137 138 addDefaultMapping(verticalListInputMap, 139 new KeyMapping(UP, e -> selectPreviousRow()), 140 new KeyMapping(KP_UP, e -> selectPreviousRow()), 141 new KeyMapping(DOWN, e -> selectNextRow()), 142 new KeyMapping(KP_DOWN, e -> selectNextRow()), 143 144 new KeyMapping(new KeyBinding(UP).shift(), e -> alsoSelectPreviousRow()), 145 new KeyMapping(new KeyBinding(KP_UP).shift(), e -> alsoSelectPreviousRow()), 146 new KeyMapping(new KeyBinding(DOWN).shift(), e -> alsoSelectNextRow()), 147 new KeyMapping(new KeyBinding(KP_DOWN).shift(), e -> alsoSelectNextRow()), 148 149 new KeyMapping(new KeyBinding(UP).shortcut(), e -> focusPreviousRow()), 150 new KeyMapping(new KeyBinding(DOWN).shortcut(), e -> focusNextRow()), 151 152 new KeyMapping(new KeyBinding(UP).shortcut().shift(), e -> discontinuousSelectPreviousRow()), 153 new KeyMapping(new KeyBinding(DOWN).shortcut().shift(), e -> discontinuousSelectNextRow()), 154 new KeyMapping(new KeyBinding(PAGE_UP).shortcut().shift(), e -> discontinuousSelectPageUp()), 155 new KeyMapping(new KeyBinding(PAGE_DOWN).shortcut().shift(), e -> discontinuousSelectPageDown()), 156 new KeyMapping(new KeyBinding(HOME).shortcut().shift(), e -> discontinuousSelectAllToFirstRow()), 157 new KeyMapping(new KeyBinding(END).shortcut().shift(), e -> discontinuousSelectAllToLastRow()) 158 ); 159 160 addDefaultChildMap(listViewInputMap, verticalListInputMap); 161 162 // --- horizontal listview 163 InputMap<ListView<T>> horizontalListInputMap = new InputMap<>(control); 164 horizontalListInputMap.setInterceptor(event -> control.getOrientation() != Orientation.HORIZONTAL); 165 166 addDefaultMapping(horizontalListInputMap, 167 new KeyMapping(LEFT, e -> selectPreviousRow()), 168 new KeyMapping(KP_LEFT, e -> selectPreviousRow()), 169 new KeyMapping(RIGHT, e -> selectNextRow()), 170 new KeyMapping(KP_RIGHT, e -> selectNextRow()), 171 172 new KeyMapping(new KeyBinding(LEFT).shift(), e -> alsoSelectPreviousRow()), 173 new KeyMapping(new KeyBinding(KP_LEFT).shift(), e -> alsoSelectPreviousRow()), 174 new KeyMapping(new KeyBinding(RIGHT).shift(), e -> alsoSelectNextRow()), 175 new KeyMapping(new KeyBinding(KP_RIGHT).shift(), e -> alsoSelectNextRow()), 176 177 new KeyMapping(new KeyBinding(LEFT).shortcut(), e -> focusPreviousRow()), 178 new KeyMapping(new KeyBinding(RIGHT).shortcut(), e -> focusNextRow()), 179 180 new KeyMapping(new KeyBinding(LEFT).shortcut().shift(), e -> discontinuousSelectPreviousRow()), 181 new KeyMapping(new KeyBinding(RIGHT).shortcut().shift(), e -> discontinuousSelectNextRow()) 182 ); 183 184 addDefaultChildMap(listViewInputMap, horizontalListInputMap); 185 186 // set up other listeners 187 // We make this an event _filter_ so that we can determine the state 188 // of the shift key before the event handlers get a shot at the event. 189 control.addEventFilter(KeyEvent.ANY, keyEventListener); 190 191 control.itemsProperty().addListener(weakItemsListener); 192 if (control.getItems() != null) { 193 control.getItems().addListener(weakItemsListListener); 194 } 195 196 // Fix for RT-16565 197 control.selectionModelProperty().addListener(weakSelectionModelListener); 198 if (control.getSelectionModel() != null) { 199 control.getSelectionModel().getSelectedIndices().addListener(weakSelectedIndicesListener); 200 } 201 202 // Only add this if we're on an embedded platform that supports 5-button navigation 203 if (Utils.isTwoLevelFocus()) { 204 tlFocus = new TwoLevelFocusListBehavior(control); // needs to be last. 205 } 206 } 207 208 209 210 /*************************************************************************** 211 * * 212 * Implementation of BehaviorBase API * 213 * * 214 **************************************************************************/ 215 216 @Override public InputMap<ListView<T>> getInputMap() { 217 return listViewInputMap; 218 } 219 220 @Override public void dispose() { 221 ListView<T> control = getNode(); 222 223 ListCellBehavior.removeAnchor(control); 224 if (tlFocus != null) tlFocus.dispose(); 225 super.dispose(); 226 227 control.removeEventHandler(KeyEvent.ANY, keyEventListener); 228 } 229 230 231 232 233 234 /************************************************************************** 235 * State and Functions * 236 *************************************************************************/ 237 238 private boolean isShiftDown = false; 239 private boolean isShortcutDown = false; 240 241 private Callback<Boolean, Integer> onScrollPageUp; 242 private Callback<Boolean, Integer> onScrollPageDown; 243 private Runnable onFocusPreviousRow; 244 private Runnable onFocusNextRow; 245 private Runnable onSelectPreviousRow; 246 private Runnable onSelectNextRow; 247 private Runnable onMoveToFirstCell; 248 private Runnable onMoveToLastCell; 249 250 public void setOnScrollPageUp(Callback<Boolean, Integer> c) { onScrollPageUp = c; } 251 public void setOnScrollPageDown(Callback<Boolean, Integer> c) { onScrollPageDown = c; } 252 public void setOnFocusPreviousRow(Runnable r) { onFocusPreviousRow = r; } 253 public void setOnFocusNextRow(Runnable r) { onFocusNextRow = r; } 254 public void setOnSelectPreviousRow(Runnable r) { onSelectPreviousRow = r; } 255 public void setOnSelectNextRow(Runnable r) { onSelectNextRow = r; } 256 public void setOnMoveToFirstCell(Runnable r) { onMoveToFirstCell = r; } 257 public void setOnMoveToLastCell(Runnable r) { onMoveToLastCell = r; } 258 259 private boolean selectionChanging = false; 260 261 private final ListChangeListener<Integer> selectedIndicesListener = c -> { 262 while (c.next()) { 263 if (c.wasReplaced()) { 264 if (ListCellBehavior.hasDefaultAnchor(getNode())) { 265 ListCellBehavior.removeAnchor(getNode()); 266 } 267 } 268 269 final int shift = c.wasPermutated() ? c.getTo() - c.getFrom() : 0; 270 271 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 272 273 // there are no selected items, so lets clear out the anchor 274 if (! selectionChanging) { 275 if (sm.isEmpty()) { 276 setAnchor(-1); 277 } else if (hasAnchor() && ! sm.isSelected(getAnchor() + shift)) { 278 setAnchor(-1); 279 } 280 } 281 282 int addedSize = c.getAddedSize(); 283 if (addedSize > 0 && ! hasAnchor()) { 284 List<? extends Integer> addedSubList = c.getAddedSubList(); 285 int index = addedSubList.get(addedSize - 1); 286 setAnchor(index); 287 } 288 } 289 }; 290 291 private final ListChangeListener<T> itemsListListener = c -> { 292 while (c.next()) { 293 if (c.wasAdded() && c.getFrom() <= getAnchor()) { 294 setAnchor(getAnchor() + c.getAddedSize()); 295 } else if (c.wasRemoved() && c.getFrom() <= getAnchor()) { 296 setAnchor(getAnchor() - c.getRemovedSize()); 297 } 298 } 299 }; 300 301 private final ChangeListener<ObservableList<T>> itemsListener = new ChangeListener<ObservableList<T>>() { 302 @Override 303 public void changed( 304 ObservableValue<? extends ObservableList<T>> observable, 305 ObservableList<T> oldValue, ObservableList<T> newValue) { 306 if (oldValue != null) { 307 oldValue.removeListener(weakItemsListListener); 308 } if (newValue != null) { 309 newValue.addListener(weakItemsListListener); 310 } 311 } 312 }; 313 314 private final ChangeListener<MultipleSelectionModel<T>> selectionModelListener = new ChangeListener<MultipleSelectionModel<T>>() { 315 @Override public void changed( 316 ObservableValue<? extends MultipleSelectionModel<T>> observable, 317 MultipleSelectionModel<T> oldValue, 318 MultipleSelectionModel<T> newValue) { 319 if (oldValue != null) { 320 oldValue.getSelectedIndices().removeListener(weakSelectedIndicesListener); 321 } 322 if (newValue != null) { 323 newValue.getSelectedIndices().addListener(weakSelectedIndicesListener); 324 } 325 } 326 }; 327 328 private final WeakChangeListener<ObservableList<T>> weakItemsListener = 329 new WeakChangeListener<ObservableList<T>>(itemsListener); 330 private final WeakListChangeListener<Integer> weakSelectedIndicesListener = 331 new WeakListChangeListener<Integer>(selectedIndicesListener); 332 private final WeakListChangeListener<T> weakItemsListListener = 333 new WeakListChangeListener<>(itemsListListener); 334 private final WeakChangeListener<MultipleSelectionModel<T>> weakSelectionModelListener = 335 new WeakChangeListener<MultipleSelectionModel<T>>(selectionModelListener); 336 337 private TwoLevelFocusListBehavior tlFocus; 338 339 private void setAnchor(int anchor) { 340 ListCellBehavior.setAnchor(getNode(), anchor < 0 ? null : anchor, false); 341 } 342 343 private int getAnchor() { 344 return ListCellBehavior.getAnchor(getNode(), getNode().getFocusModel().getFocusedIndex()); 345 } 346 347 private boolean hasAnchor() { 348 return ListCellBehavior.hasNonDefaultAnchor(getNode()); 349 } 350 351 private void mousePressed(MouseEvent e) { 352 if (! e.isShiftDown() && ! e.isSynthesized()) { 353 int index = getNode().getSelectionModel().getSelectedIndex(); 354 setAnchor(index); 355 } 356 357 if (! getNode().isFocused() && getNode().isFocusTraversable()) { 358 getNode().requestFocus(); 359 } 360 } 361 362 private int getRowCount() { 363 return getNode().getItems() == null ? 0 : getNode().getItems().size(); 364 } 365 366 private void clearSelection() { 367 getNode().getSelectionModel().clearSelection(); 368 } 369 370 private void scrollPageUp() { 371 int newSelectedIndex = -1; 372 if (onScrollPageUp != null) { 373 newSelectedIndex = onScrollPageUp.call(false); 374 } 375 if (newSelectedIndex == -1) return; 376 377 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 378 if (sm == null) return; 379 sm.clearAndSelect(newSelectedIndex); 380 } 381 382 private void scrollPageDown() { 383 int newSelectedIndex = -1; 384 if (onScrollPageDown != null) { 385 newSelectedIndex = onScrollPageDown.call(false); 386 } 387 if (newSelectedIndex == -1) return; 388 389 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 390 if (sm == null) return; 391 sm.clearAndSelect(newSelectedIndex); 392 } 393 394 private void focusFirstRow() { 395 FocusModel<T> fm = getNode().getFocusModel(); 396 if (fm == null) return; 397 fm.focus(0); 398 399 if (onMoveToFirstCell != null) onMoveToFirstCell.run(); 400 } 401 402 private void focusLastRow() { 403 FocusModel<T> fm = getNode().getFocusModel(); 404 if (fm == null) return; 405 fm.focus(getRowCount() - 1); 406 407 if (onMoveToLastCell != null) onMoveToLastCell.run(); 408 } 409 410 private void focusPreviousRow() { 411 FocusModel<T> fm = getNode().getFocusModel(); 412 if (fm == null) return; 413 414 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 415 if (sm == null) return; 416 417 fm.focusPrevious(); 418 419 if (! isShortcutDown || getAnchor() == -1) { 420 setAnchor(fm.getFocusedIndex()); 421 } 422 423 if (onFocusPreviousRow != null) onFocusPreviousRow.run(); 424 } 425 426 private void focusNextRow() { 427 FocusModel<T> fm = getNode().getFocusModel(); 428 if (fm == null) return; 429 430 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 431 if (sm == null) return; 432 433 fm.focusNext(); 434 435 if (! isShortcutDown || getAnchor() == -1) { 436 setAnchor(fm.getFocusedIndex()); 437 } 438 439 if (onFocusNextRow != null) onFocusNextRow.run(); 440 } 441 442 private void focusPageUp() { 443 int newFocusIndex = onScrollPageUp.call(true); 444 445 FocusModel<T> fm = getNode().getFocusModel(); 446 if (fm == null) return; 447 fm.focus(newFocusIndex); 448 } 449 450 private void focusPageDown() { 451 int newFocusIndex = onScrollPageDown.call(true); 452 453 FocusModel<T> fm = getNode().getFocusModel(); 454 if (fm == null) return; 455 fm.focus(newFocusIndex); 456 } 457 458 private void alsoSelectPreviousRow() { 459 FocusModel<T> fm = getNode().getFocusModel(); 460 if (fm == null) return; 461 462 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 463 if (sm == null) return; 464 465 if (isShiftDown && getAnchor() != -1) { 466 int newRow = fm.getFocusedIndex() - 1; 467 if (newRow < 0) return; 468 469 int anchor = getAnchor(); 470 471 if (! hasAnchor()) { 472 setAnchor(fm.getFocusedIndex()); 473 } 474 475 if (sm.getSelectedIndices().size() > 1) { 476 clearSelectionOutsideRange(anchor, newRow); 477 } 478 479 if (anchor > newRow) { 480 sm.selectRange(anchor, newRow - 1); 481 } else { 482 sm.selectRange(anchor, newRow + 1); 483 } 484 } else { 485 sm.selectPrevious(); 486 } 487 488 onSelectPreviousRow.run(); 489 } 490 491 private void alsoSelectNextRow() { 492 FocusModel<T> fm = getNode().getFocusModel(); 493 if (fm == null) return; 494 495 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 496 if (sm == null) return; 497 498 if (isShiftDown && getAnchor() != -1) { 499 int newRow = fm.getFocusedIndex() + 1; 500 int anchor = getAnchor(); 501 502 if (! hasAnchor()) { 503 setAnchor(fm.getFocusedIndex()); 504 } 505 506 if (sm.getSelectedIndices().size() > 1) { 507 clearSelectionOutsideRange(anchor, newRow); 508 } 509 510 if (anchor > newRow) { 511 sm.selectRange(anchor, newRow - 1); 512 } else { 513 sm.selectRange(anchor, newRow + 1); 514 } 515 } else { 516 sm.selectNext(); 517 } 518 519 onSelectNextRow.run(); 520 } 521 522 private void clearSelectionOutsideRange(int start, int end) { 523 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 524 if (sm == null) return; 525 526 int min = Math.min(start, end); 527 int max = Math.max(start, end); 528 529 List<Integer> indices = new ArrayList<>(sm.getSelectedIndices()); 530 531 selectionChanging = true; 532 for (int i = 0; i < indices.size(); i++) { 533 int index = indices.get(i); 534 if (index < min || index > max) { 535 sm.clearSelection(index); 536 } 537 } 538 selectionChanging = false; 539 } 540 541 private void selectPreviousRow() { 542 FocusModel<T> fm = getNode().getFocusModel(); 543 if (fm == null) return; 544 545 int focusIndex = fm.getFocusedIndex(); 546 if (focusIndex <= 0) { 547 return; 548 } 549 550 setAnchor(focusIndex - 1); 551 getNode().getSelectionModel().clearAndSelect(focusIndex - 1); 552 onSelectPreviousRow.run(); 553 } 554 555 private void selectNextRow() { 556 ListView<T> listView = getNode(); 557 FocusModel<T> fm = listView.getFocusModel(); 558 if (fm == null) return; 559 560 int focusIndex = fm.getFocusedIndex(); 561 if (focusIndex == getRowCount() - 1) { 562 return; 563 } 564 565 MultipleSelectionModel<T> sm = listView.getSelectionModel(); 566 if (sm == null) return; 567 568 setAnchor(focusIndex + 1); 569 sm.clearAndSelect(focusIndex + 1); 570 if (onSelectNextRow != null) onSelectNextRow.run(); 571 } 572 573 private void selectFirstRow() { 574 if (getRowCount() > 0) { 575 getNode().getSelectionModel().clearAndSelect(0); 576 if (onMoveToFirstCell != null) onMoveToFirstCell.run(); 577 } 578 } 579 580 private void selectLastRow() { 581 getNode().getSelectionModel().clearAndSelect(getRowCount() - 1); 582 if (onMoveToLastCell != null) onMoveToLastCell.run(); 583 } 584 585 private void selectAllPageUp() { 586 FocusModel<T> fm = getNode().getFocusModel(); 587 if (fm == null) return; 588 589 int leadIndex = fm.getFocusedIndex(); 590 if (isShiftDown) { 591 leadIndex = getAnchor() == -1 ? leadIndex : getAnchor(); 592 setAnchor(leadIndex); 593 } 594 595 int leadSelectedIndex = onScrollPageUp.call(false); 596 597 // fix for RT-34407 598 int adjust = leadIndex < leadSelectedIndex ? 1 : -1; 599 600 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 601 if (sm == null) return; 602 603 selectionChanging = true; 604 if (sm.getSelectionMode() == SelectionMode.SINGLE) { 605 sm.select(leadSelectedIndex); 606 } else { 607 sm.clearSelection(); 608 sm.selectRange(leadIndex, leadSelectedIndex + adjust); 609 } 610 selectionChanging = false; 611 } 612 613 private void selectAllPageDown() { 614 FocusModel<T> fm = getNode().getFocusModel(); 615 if (fm == null) return; 616 617 int leadIndex = fm.getFocusedIndex(); 618 if (isShiftDown) { 619 leadIndex = getAnchor() == -1 ? leadIndex : getAnchor(); 620 setAnchor(leadIndex); 621 } 622 623 int leadSelectedIndex = onScrollPageDown.call(false); 624 625 // fix for RT-34407 626 int adjust = leadIndex < leadSelectedIndex ? 1 : -1; 627 628 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 629 if (sm == null) return; 630 631 selectionChanging = true; 632 if (sm.getSelectionMode() == SelectionMode.SINGLE) { 633 sm.select(leadSelectedIndex); 634 } else { 635 sm.clearSelection(); 636 sm.selectRange(leadIndex, leadSelectedIndex + adjust); 637 } 638 selectionChanging = false; 639 } 640 641 private void selectAllToFirstRow() { 642 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 643 if (sm == null) return; 644 645 FocusModel<T> fm = getNode().getFocusModel(); 646 if (fm == null) return; 647 648 int leadIndex = fm.getFocusedIndex(); 649 650 if (isShiftDown) { 651 leadIndex = hasAnchor() ? getAnchor() : leadIndex; 652 } 653 654 sm.clearSelection(); 655 sm.selectRange(leadIndex, -1); 656 657 // RT-18413: Focus must go to first row 658 fm.focus(0); 659 660 if (isShiftDown) { 661 setAnchor(leadIndex); 662 } 663 664 if (onMoveToFirstCell != null) onMoveToFirstCell.run(); 665 } 666 667 private void selectAllToLastRow() { 668 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 669 if (sm == null) return; 670 671 FocusModel<T> fm = getNode().getFocusModel(); 672 if (fm == null) return; 673 674 int leadIndex = fm.getFocusedIndex(); 675 676 System.out.println("isShiftDown: " + isShiftDown); 677 if (isShiftDown) { 678 leadIndex = hasAnchor() ? getAnchor() : leadIndex; 679 } 680 681 sm.clearSelection(); 682 sm.selectRange(leadIndex, getRowCount()); 683 684 if (isShiftDown) { 685 setAnchor(leadIndex); 686 } 687 688 if (onMoveToLastCell != null) onMoveToLastCell.run(); 689 } 690 691 private void selectAll() { 692 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 693 if (sm == null) return; 694 sm.selectAll(); 695 } 696 697 private void selectAllToFocus(boolean setAnchorToFocusIndex) { 698 // Fix for RT-31241 699 final ListView<T> listView = getNode(); 700 if (listView.getEditingIndex() >= 0) return; 701 702 MultipleSelectionModel<T> sm = listView.getSelectionModel(); 703 if (sm == null) return; 704 705 FocusModel<T> fm = listView.getFocusModel(); 706 if (fm == null) return; 707 708 int focusIndex = fm.getFocusedIndex(); 709 int anchor = getAnchor(); 710 711 sm.clearSelection(); 712 int startPos = anchor; 713 int endPos = anchor > focusIndex ? focusIndex - 1 : focusIndex + 1; 714 sm.selectRange(startPos, endPos); 715 setAnchor(setAnchorToFocusIndex ? focusIndex : anchor); 716 } 717 718 private void cancelEdit() { 719 getNode().edit(-1); 720 } 721 722 private void activate() { 723 int focusedIndex = getNode().getFocusModel().getFocusedIndex(); 724 getNode().getSelectionModel().select(focusedIndex); 725 setAnchor(focusedIndex); 726 727 // edit this row also 728 if (focusedIndex >= 0) { 729 getNode().edit(focusedIndex); 730 } 731 } 732 733 private void toggleFocusOwnerSelection() { 734 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 735 if (sm == null) return; 736 737 FocusModel<T> fm = getNode().getFocusModel(); 738 if (fm == null) return; 739 740 int focusedIndex = fm.getFocusedIndex(); 741 742 if (sm.isSelected(focusedIndex)) { 743 sm.clearSelection(focusedIndex); 744 fm.focus(focusedIndex); 745 } else { 746 sm.select(focusedIndex); 747 } 748 749 setAnchor(focusedIndex); 750 } 751 752 /************************************************************************** 753 * Discontinuous Selection * 754 *************************************************************************/ 755 756 private void discontinuousSelectPreviousRow() { 757 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 758 if (sm == null) return; 759 760 if (sm.getSelectionMode() != SelectionMode.MULTIPLE) { 761 selectPreviousRow(); 762 return; 763 } 764 765 FocusModel<T> fm = getNode().getFocusModel(); 766 if (fm == null) return; 767 768 int focusIndex = fm.getFocusedIndex(); 769 final int newFocusIndex = focusIndex - 1; 770 if (newFocusIndex < 0) return; 771 772 int startIndex = focusIndex; 773 if (isShiftDown) { 774 startIndex = getAnchor() == -1 ? focusIndex : getAnchor(); 775 } 776 777 sm.selectRange(newFocusIndex, startIndex + 1); 778 fm.focus(newFocusIndex); 779 780 if (onFocusPreviousRow != null) onFocusPreviousRow.run(); 781 } 782 783 private void discontinuousSelectNextRow() { 784 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 785 if (sm == null) return; 786 787 if (sm.getSelectionMode() != SelectionMode.MULTIPLE) { 788 selectNextRow(); 789 return; 790 } 791 792 FocusModel<T> fm = getNode().getFocusModel(); 793 if (fm == null) return; 794 795 int focusIndex = fm.getFocusedIndex(); 796 final int newFocusIndex = focusIndex + 1; 797 if (newFocusIndex >= getRowCount()) return; 798 799 int startIndex = focusIndex; 800 if (isShiftDown) { 801 startIndex = getAnchor() == -1 ? focusIndex : getAnchor(); 802 } 803 804 sm.selectRange(startIndex, newFocusIndex + 1); 805 fm.focus(newFocusIndex); 806 807 if (onFocusNextRow != null) onFocusNextRow.run(); 808 } 809 810 private void discontinuousSelectPageUp() { 811 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 812 if (sm == null) return; 813 814 FocusModel<T> fm = getNode().getFocusModel(); 815 if (fm == null) return; 816 817 int anchor = getAnchor(); 818 int leadSelectedIndex = onScrollPageUp.call(false); 819 sm.selectRange(anchor, leadSelectedIndex - 1); 820 } 821 822 private void discontinuousSelectPageDown() { 823 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 824 if (sm == null) return; 825 826 FocusModel<T> fm = getNode().getFocusModel(); 827 if (fm == null) return; 828 829 int anchor = getAnchor(); 830 int leadSelectedIndex = onScrollPageDown.call(false); 831 sm.selectRange(anchor, leadSelectedIndex + 1); 832 } 833 834 private void discontinuousSelectAllToFirstRow() { 835 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 836 if (sm == null) return; 837 838 FocusModel<T> fm = getNode().getFocusModel(); 839 if (fm == null) return; 840 841 int index = fm.getFocusedIndex(); 842 sm.selectRange(0, index); 843 fm.focus(0); 844 845 if (onMoveToFirstCell != null) onMoveToFirstCell.run(); 846 } 847 848 private void discontinuousSelectAllToLastRow() { 849 MultipleSelectionModel<T> sm = getNode().getSelectionModel(); 850 if (sm == null) return; 851 852 FocusModel<T> fm = getNode().getFocusModel(); 853 if (fm == null) return; 854 855 int index = fm.getFocusedIndex() + 1; 856 sm.selectRange(index, getRowCount()); 857 858 if (onMoveToLastCell != null) onMoveToLastCell.run(); 859 } 860 }