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 -> {
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 }
|
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 -> {
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 }
|