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