1 /*
2 * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package com.sun.javafx.scene.control.behavior;
27
28 import javafx.beans.value.ChangeListener;
29 import javafx.beans.value.ObservableValue;
30 import javafx.beans.value.WeakChangeListener;
31 import javafx.collections.ListChangeListener;
32 import javafx.collections.WeakListChangeListener;
33 import javafx.geometry.NodeOrientation;
34 import javafx.scene.control.*;
35 import javafx.scene.input.KeyEvent;
36 import javafx.scene.input.MouseEvent;
37 import javafx.util.Callback;
38 import java.util.ArrayList;
39 import java.util.List;
40 import com.sun.javafx.PlatformUtil;
41 import static javafx.scene.input.KeyCode.A;
42 import static javafx.scene.input.KeyCode.ADD;
43 import static javafx.scene.input.KeyCode.DOWN;
44 import static javafx.scene.input.KeyCode.END;
45 import static javafx.scene.input.KeyCode.ENTER;
46 import static javafx.scene.input.KeyCode.ESCAPE;
47 import static javafx.scene.input.KeyCode.F2;
48 import static javafx.scene.input.KeyCode.HOME;
49 import static javafx.scene.input.KeyCode.KP_DOWN;
50 import static javafx.scene.input.KeyCode.KP_LEFT;
51 import static javafx.scene.input.KeyCode.KP_RIGHT;
52 import static javafx.scene.input.KeyCode.KP_UP;
53 import static javafx.scene.input.KeyCode.LEFT;
54 import static javafx.scene.input.KeyCode.MULTIPLY;
55 import static javafx.scene.input.KeyCode.PAGE_DOWN;
56 import static javafx.scene.input.KeyCode.PAGE_UP;
57 import static javafx.scene.input.KeyCode.RIGHT;
58 import static javafx.scene.input.KeyCode.SPACE;
59 import static javafx.scene.input.KeyCode.SUBTRACT;
60 import static javafx.scene.input.KeyCode.UP;
61
62 public class TreeViewBehavior<T> extends BehaviorBase<TreeView<T>> {
63
64 /**************************************************************************
65 * Setup KeyBindings *
66 *************************************************************************/
67 protected static final List<KeyBinding> TREE_VIEW_BINDINGS = new ArrayList<KeyBinding>();
68
69 static {
70 TREE_VIEW_BINDINGS.add(new KeyBinding(HOME, "SelectFirstRow"));
71 TREE_VIEW_BINDINGS.add(new KeyBinding(END, "SelectLastRow"));
72 TREE_VIEW_BINDINGS.add(new KeyBinding(HOME, "SelectAllToFirstRow").shift());
73 TREE_VIEW_BINDINGS.add(new KeyBinding(END, "SelectAllToLastRow").shift());
74 TREE_VIEW_BINDINGS.add(new KeyBinding(PAGE_UP, "SelectAllPageUp").shift());
75 TREE_VIEW_BINDINGS.add(new KeyBinding(PAGE_DOWN, "SelectAllPageDown").shift());
76
77 TREE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "SelectAllToFocus").shift());
78 TREE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "SelectAllToFocusAndSetAnchor").shortcut().shift());
79
80 TREE_VIEW_BINDINGS.add(new KeyBinding(HOME, "FocusFirstRow").shortcut());
81 TREE_VIEW_BINDINGS.add(new KeyBinding(END, "FocusLastRow").shortcut());
82
83 TREE_VIEW_BINDINGS.add(new KeyBinding(PAGE_UP, "ScrollUp"));
84 TREE_VIEW_BINDINGS.add(new KeyBinding(PAGE_DOWN, "ScrollDown"));
85
86 TREE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "toggleFocusOwnerSelection"));
87 if (PlatformUtil.isMac()) {
88 TREE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "toggleFocusOwnerSelection").ctrl().shortcut());
89 } else {
90 TREE_VIEW_BINDINGS.add(new KeyBinding(SPACE, "toggleFocusOwnerSelection").ctrl());
91 }
92
93 TREE_VIEW_BINDINGS.add(new KeyBinding(A, "SelectAll").shortcut());
94 TREE_VIEW_BINDINGS.add(new KeyBinding(PAGE_UP, "FocusPageUp").shortcut());
95 TREE_VIEW_BINDINGS.add(new KeyBinding(PAGE_DOWN, "FocusPageDown").shortcut());
96 TREE_VIEW_BINDINGS.add(new KeyBinding(UP, "FocusPreviousRow").shortcut());
97 TREE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "FocusNextRow").shortcut());
98 TREE_VIEW_BINDINGS.add(new KeyBinding(UP, "DiscontinuousSelectPreviousRow").shortcut().shift());
99 TREE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "DiscontinuousSelectNextRow").shortcut().shift());
100 TREE_VIEW_BINDINGS.add(new KeyBinding(PAGE_UP, "DiscontinuousSelectPageUp").shortcut().shift());
101 TREE_VIEW_BINDINGS.add(new KeyBinding(PAGE_DOWN, "DiscontinuousSelectPageDown").shortcut().shift());
102 TREE_VIEW_BINDINGS.add(new KeyBinding(HOME, "DiscontinuousSelectAllToFirstRow").shortcut().shift());
103 TREE_VIEW_BINDINGS.add(new KeyBinding(END, "DiscontinuousSelectAllToLastRow").shortcut().shift());
104
105 TREE_VIEW_BINDINGS.add(new KeyBinding(LEFT, "CollapseRow"));
106 TREE_VIEW_BINDINGS.add(new KeyBinding(KP_LEFT, "CollapseRow"));
107 TREE_VIEW_BINDINGS.add(new KeyBinding(RIGHT, "ExpandRow"));
108 TREE_VIEW_BINDINGS.add(new KeyBinding(KP_RIGHT, "ExpandRow"));
109
110 TREE_VIEW_BINDINGS.add(new KeyBinding(MULTIPLY, "ExpandAll"));
111 TREE_VIEW_BINDINGS.add(new KeyBinding(ADD, "ExpandRow"));
112 TREE_VIEW_BINDINGS.add(new KeyBinding(SUBTRACT, "CollapseRow"));
113
114 TREE_VIEW_BINDINGS.add(new KeyBinding(UP, "SelectPreviousRow"));
115 TREE_VIEW_BINDINGS.add(new KeyBinding(KP_UP, "SelectPreviousRow"));
116 TREE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "SelectNextRow"));
117 TREE_VIEW_BINDINGS.add(new KeyBinding(KP_DOWN, "SelectNextRow"));
118
119 TREE_VIEW_BINDINGS.add(new KeyBinding(UP, "AlsoSelectPreviousRow").shift());
120 TREE_VIEW_BINDINGS.add(new KeyBinding(KP_UP, "AlsoSelectPreviousRow").shift());
121 TREE_VIEW_BINDINGS.add(new KeyBinding(DOWN, "AlsoSelectNextRow").shift());
122 TREE_VIEW_BINDINGS.add(new KeyBinding(KP_DOWN, "AlsoSelectNextRow").shift());
123
124 TREE_VIEW_BINDINGS.add(new KeyBinding(ENTER, "Edit"));
125 TREE_VIEW_BINDINGS.add(new KeyBinding(F2, "Edit"));
126 TREE_VIEW_BINDINGS.add(new KeyBinding(ESCAPE, "CancelEdit"));
127 }
128
129 @Override protected /*final*/ String matchActionForEvent(KeyEvent e) {
130 String action = super.matchActionForEvent(e);
131 if (getControl().getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) {
132 // Rather than switching the result of the action lookup in this way, the preferred
133 // way to do this according to the current architecture would be to hoist the
134 // getEffectiveNodeOrientation call up into the key bindings, the same way that ListView
135 // orientation (horizontal vs. vertical) is handled with the OrientedKeyBinding class.
136 if ("CollapseRow".equals(action) && (e.getCode() == LEFT || e.getCode() == KP_LEFT)) {
137 action = "ExpandRow";
138 } else if ("ExpandRow".equals(action) && (e.getCode() == RIGHT || e.getCode() == KP_RIGHT)) {
139 action = "CollapseRow";
140 }
141 }
142 return action;
143 }
144
145 @Override protected void callAction(String name) {
146 if ("SelectPreviousRow".equals(name)) selectPreviousRow();
147 else if ("SelectNextRow".equals(name)) selectNextRow();
148 else if ("SelectFirstRow".equals(name)) selectFirstRow();
149 else if ("SelectLastRow".equals(name)) selectLastRow();
150 else if ("SelectAllPageUp".equals(name)) selectAllPageUp();
151 else if ("SelectAllPageDown".equals(name)) selectAllPageDown();
152 else if ("SelectAllToFirstRow".equals(name)) selectAllToFirstRow();
153 else if ("SelectAllToLastRow".equals(name)) selectAllToLastRow();
154 else if ("AlsoSelectNextRow".equals(name)) alsoSelectNextRow();
155 else if ("AlsoSelectPreviousRow".equals(name)) alsoSelectPreviousRow();
156 else if ("ClearSelection".equals(name)) clearSelection();
157 else if("SelectAll".equals(name)) selectAll();
158 else if ("ScrollUp".equals(name)) scrollUp();
159 else if ("ScrollDown".equals(name)) scrollDown();
160 else if ("ExpandRow".equals(name)) expandRow();
161 else if ("CollapseRow".equals(name)) collapseRow();
162 else if ("ExpandAll".equals(name)) expandAll();
163 // else if ("ExpandOrCollapseRow".equals(name)) expandOrCollapseRow();
164 else if ("Edit".equals(name)) edit();
165 else if ("CancelEdit".equals(name)) cancelEdit();
166 else if ("FocusFirstRow".equals(name)) focusFirstRow();
167 else if ("FocusLastRow".equals(name)) focusLastRow();
168 else if ("toggleFocusOwnerSelection".equals(name)) toggleFocusOwnerSelection();
169
170 else if ("SelectAllToFocus".equals(name)) selectAllToFocus(false);
171 else if ("SelectAllToFocusAndSetAnchor".equals(name)) selectAllToFocus(true);
172
173 else if ("FocusPageUp".equals(name)) focusPageUp();
174 else if ("FocusPageDown".equals(name)) focusPageDown();
175 else if ("FocusPreviousRow".equals(name)) focusPreviousRow();
176 else if ("FocusNextRow".equals(name)) focusNextRow();
177 else if ("DiscontinuousSelectNextRow".equals(name)) discontinuousSelectNextRow();
178 else if ("DiscontinuousSelectPreviousRow".equals(name)) discontinuousSelectPreviousRow();
179 else if ("DiscontinuousSelectPageUp".equals(name)) discontinuousSelectPageUp();
180 else if ("DiscontinuousSelectPageDown".equals(name)) discontinuousSelectPageDown();
181 else if ("DiscontinuousSelectAllToLastRow".equals(name)) discontinuousSelectAllToLastRow();
182 else if ("DiscontinuousSelectAllToFirstRow".equals(name)) discontinuousSelectAllToFirstRow();
183 else super.callAction(name);
184 }
185
186 @Override protected void callActionForEvent(KeyEvent e) {
187 // RT-12751: we want to keep an eye on the user holding down the shift key,
188 // so that we know when they enter/leave multiple selection mode. This
189 // changes what happens when certain key combinations are pressed.
190 isShiftDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShiftDown();
191 isShortcutDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShortcutDown();
192
193 super.callActionForEvent(e);
194 }
195
196 /**************************************************************************
197 * State and Functions *
198 *************************************************************************/
199
200 private boolean isShiftDown = false;
201 private boolean isShortcutDown = false;
202
203 // Support for RT-13826:
204 // set when focus is moved by keyboard to allow for proper selection positions
205 // private int selectPos = -1;
206
207 private Callback<Boolean, Integer> onScrollPageUp;
208 public void setOnScrollPageUp(Callback<Boolean, Integer> c) { onScrollPageUp = c; }
209
210 private Callback<Boolean, Integer> onScrollPageDown;
211 public void setOnScrollPageDown(Callback<Boolean, Integer> c) { onScrollPageDown = c; }
212
213 private Runnable onSelectPreviousRow;
214 public void setOnSelectPreviousRow(Runnable r) { onSelectPreviousRow = r; }
216 private Runnable onSelectNextRow;
217 public void setOnSelectNextRow(Runnable r) { onSelectNextRow = r; }
218
219 private Runnable onMoveToFirstCell;
220 public void setOnMoveToFirstCell(Runnable r) { onMoveToFirstCell = r; }
221
222 private Runnable onMoveToLastCell;
223 public void setOnMoveToLastCell(Runnable r) { onMoveToLastCell = r; }
224
225 private Runnable onFocusPreviousRow;
226 public void setOnFocusPreviousRow(Runnable r) { onFocusPreviousRow = r; }
227
228 private Runnable onFocusNextRow;
229 public void setOnFocusNextRow(Runnable r) { onFocusNextRow = r; }
230
231 private boolean selectionChanging = false;
232
233 private final ListChangeListener<Integer> selectedIndicesListener = c -> {
234 while (c.next()) {
235 if (c.wasReplaced()) {
236 if (TreeCellBehavior.hasDefaultAnchor(getControl())) {
237 TreeCellBehavior.removeAnchor(getControl());
238 }
239 }
240
241 final int shift = c.wasPermutated() ? c.getTo() - c.getFrom() : 0;
242
243 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
244
245 // there are no selected items, so lets clear out the anchor
246 if (! selectionChanging) {
247 if (sm.isEmpty()) {
248 setAnchor(-1);
249 } else if (hasAnchor() && ! sm.isSelected(getAnchor() + shift)) {
250 setAnchor(-1);
251 }
252 }
253
254 int addedSize = c.getAddedSize();
255 if (addedSize > 0 && ! hasAnchor()) {
256 List<? extends Integer> addedSubList = c.getAddedSubList();
257 int index = addedSubList.get(addedSize - 1);
258 setAnchor(index);
259 }
260 }
261 };
262
263 private final ChangeListener<MultipleSelectionModel<TreeItem<T>>> selectionModelListener =
264 new ChangeListener<MultipleSelectionModel<TreeItem<T>>>() {
265 @Override public void changed(ObservableValue<? extends MultipleSelectionModel<TreeItem<T>>> observable,
266 MultipleSelectionModel<TreeItem<T>> oldValue,
267 MultipleSelectionModel<TreeItem<T>> newValue) {
268 if (oldValue != null) {
269 oldValue.getSelectedIndices().removeListener(weakSelectedIndicesListener);
270 }
271 if (newValue != null) {
272 newValue.getSelectedIndices().addListener(weakSelectedIndicesListener);
273 }
274 }
275 };
276
277 private final WeakListChangeListener<Integer> weakSelectedIndicesListener =
278 new WeakListChangeListener<Integer>(selectedIndicesListener);
279 private final WeakChangeListener<MultipleSelectionModel<TreeItem<T>>> weakSelectionModelListener =
280 new WeakChangeListener<MultipleSelectionModel<TreeItem<T>>>(selectionModelListener);
281
282 public TreeViewBehavior(TreeView<T> control) {
283 super(control, TREE_VIEW_BINDINGS);
284
285 // Fix for RT-16565
286 getControl().selectionModelProperty().addListener(weakSelectionModelListener);
287 if (control.getSelectionModel() != null) {
288 control.getSelectionModel().getSelectedIndices().addListener(weakSelectedIndicesListener);
289 }
290 }
291
292 @Override public void dispose() {
293 TreeCellBehavior.removeAnchor(getControl());
294 super.dispose();
295 }
296
297 private void setAnchor(int anchor) {
298 TreeCellBehavior.setAnchor(getControl(), anchor < 0 ? null : anchor, false);
299 }
300
301 private int getAnchor() {
302 return TreeCellBehavior.getAnchor(getControl(), getControl().getFocusModel().getFocusedIndex());
303 }
304
305 private boolean hasAnchor() {
306 return TreeCellBehavior.hasNonDefaultAnchor(getControl());
307 }
308
309 @Override public void mousePressed(MouseEvent e) {
310 super.mousePressed(e);
311
312 if (! e.isShiftDown()) {
313 int index = getControl().getSelectionModel().getSelectedIndex();
314 setAnchor(index);
315 }
316
317 if (! getControl().isFocused() && getControl().isFocusTraversable()) {
318 getControl().requestFocus();
319 }
320 }
321
322 private void clearSelection() {
323 getControl().getSelectionModel().clearSelection();
324 //select(null);
325 }
326
327 private void scrollUp() {
328 int newSelectedIndex = -1;
329 if (onScrollPageUp != null) {
330 newSelectedIndex = onScrollPageUp.call(false);
331 }
332 if (newSelectedIndex == -1) return;
333
334 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
335 if (sm == null) return;
336 sm.clearAndSelect(newSelectedIndex);
337 }
338
339 private void scrollDown() {
340 int newSelectedIndex = -1;
341 if (onScrollPageDown != null) {
342 newSelectedIndex = onScrollPageDown.call(false);
343 }
344 if (newSelectedIndex == -1) return;
345
346 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
347 if (sm == null) return;
348 sm.clearAndSelect(newSelectedIndex);
349 }
350
351 private void focusFirstRow() {
352 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
353 if (fm == null) return;
354 fm.focus(0);
355
356 if (onMoveToFirstCell != null) onMoveToFirstCell.run();
357 }
358
359 private void focusLastRow() {
360 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
361 if (fm == null) return;
362 fm.focus(getControl().getExpandedItemCount() - 1);
363
364 if (onMoveToLastCell != null) onMoveToLastCell.run();
365 }
366
367 private void focusPreviousRow() {
368 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
369 if (fm == null) return;
370
371 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
372 if (sm == null) return;
373
374 fm.focusPrevious();
375
376 if (! isShortcutDown || getAnchor() == -1) {
377 setAnchor(fm.getFocusedIndex());
378 }
379
380 if (onFocusPreviousRow != null) onFocusPreviousRow.run();
381 }
382
383 private void focusNextRow() {
384 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
385 if (fm == null) return;
386
387 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
388 if (sm == null) return;
389
390 fm.focusNext();
391
392 if (! isShortcutDown || getAnchor() == -1) {
393 setAnchor(fm.getFocusedIndex());
394 }
395
396 if (onFocusNextRow != null) onFocusNextRow.run();
397 }
398
399 private void focusPageUp() {
400 int newFocusIndex = onScrollPageUp.call(true);
401
402 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
403 if (fm == null) return;
404 fm.focus(newFocusIndex);
405 }
406
407 private void focusPageDown() {
408 int newFocusIndex = onScrollPageDown.call(true);
409
410 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
411 if (fm == null) return;
412 fm.focus(newFocusIndex);
413 }
414
415 private void alsoSelectPreviousRow() {
416 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
417 if (fm == null) return;
418
419 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
420 if (sm == null) return;
421
422 if (isShiftDown && getAnchor() != -1) {
423 int newRow = fm.getFocusedIndex() - 1;
424 if (newRow < 0) return;
425
426 int anchor = getAnchor();
427
428 if (! hasAnchor()) {
429 setAnchor(fm.getFocusedIndex());
430 }
431
432 if (sm.getSelectedIndices().size() > 1) {
433 clearSelectionOutsideRange(anchor, newRow);
434 }
435
436 if (anchor > newRow) {
437 sm.selectRange(anchor, newRow - 1);
438 } else {
439 sm.selectRange(anchor, newRow + 1);
440 }
441 } else {
442 sm.selectPrevious();
443 }
444
445 onSelectPreviousRow.run();
446 }
447
448 private void alsoSelectNextRow() {
449 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
450 if (fm == null) return;
451
452 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
453 if (sm == null) return;
454
455 if (isShiftDown && getAnchor() != -1) {
456 int newRow = fm.getFocusedIndex() + 1;
457 int anchor = getAnchor();
458
459 if (! hasAnchor()) {
460 setAnchor(fm.getFocusedIndex());
461 }
462
463 if (sm.getSelectedIndices().size() > 1) {
464 clearSelectionOutsideRange(anchor, newRow);
465 }
466
467 if (anchor > newRow) {
468 sm.selectRange(anchor, newRow - 1);
469 } else {
470 sm.selectRange(anchor, newRow + 1);
471 }
472 } else {
473 sm.selectNext();
474 }
475
476 onSelectNextRow.run();
477 }
478
479 private void clearSelectionOutsideRange(int start, int end) {
480 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
481 if (sm == null) return;
482
483 int min = Math.min(start, end);
484 int max = Math.max(start, end);
485
486 List<Integer> indices = new ArrayList<Integer>(sm.getSelectedIndices());
487
488 selectionChanging = true;
489 for (int i = 0; i < indices.size(); i++) {
490 int index = indices.get(i);
491 if (index < min || index > max) {
492 sm.clearSelection(index);
493 }
494 }
495 selectionChanging = false;
496 }
497
498 private void selectPreviousRow() {
499 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
500 if (fm == null) return;
501
502 int focusIndex = fm.getFocusedIndex();
503 if (focusIndex <= 0) {
504 return;
505 }
506
507 setAnchor(focusIndex - 1);
508 getControl().getSelectionModel().clearAndSelect(focusIndex - 1);
509 onSelectPreviousRow.run();
510 }
511
512 private void selectNextRow() {
513 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
514 if (fm == null) return;
515
516 int focusIndex = fm.getFocusedIndex();
517 if (focusIndex == getControl().getExpandedItemCount() - 1) {
518 return;
519 }
520
521 setAnchor(focusIndex + 1);
522 getControl().getSelectionModel().clearAndSelect(focusIndex + 1);
523 onSelectNextRow.run();
524 }
525
526 private void selectFirstRow() {
527 if (getControl().getExpandedItemCount() > 0) {
528 getControl().getSelectionModel().clearAndSelect(0);
529 if (onMoveToFirstCell != null) onMoveToFirstCell.run();
530 }
531 }
532
533 private void selectLastRow() {
534 getControl().getSelectionModel().clearAndSelect(getControl().getExpandedItemCount() - 1);
535 onMoveToLastCell.run();
536 }
537
538 private void selectAllToFirstRow() {
539 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
540 if (sm == null) return;
541
542 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
543 if (fm == null) return;
544
545 int leadIndex = fm.getFocusedIndex();
546
547 if (isShiftDown) {
548 leadIndex = hasAnchor() ? getAnchor() : leadIndex;
549 }
550
551 sm.clearSelection();
552 sm.selectRange(leadIndex, -1);
553
554 // RT-18413: Focus must go to first row
555 fm.focus(0);
556
557 if (isShiftDown) {
558 setAnchor(leadIndex);
559 }
560
561 if (onMoveToFirstCell != null) onMoveToFirstCell.run();
562 }
563
564 private void selectAllToLastRow() {
565 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
566 if (sm == null) return;
567
568 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
569 if (fm == null) return;
570
571 int leadIndex = fm.getFocusedIndex();
572
573 if (isShiftDown) {
574 leadIndex = hasAnchor() ? getAnchor() : leadIndex;
575 }
576
577 sm.clearSelection();
578 sm.selectRange(leadIndex, getControl().getExpandedItemCount());
579
580 if (isShiftDown) {
581 setAnchor(leadIndex);
582 }
583
584 if (onMoveToLastCell != null) onMoveToLastCell.run();
585 }
586
587 private void selectAll() {
588 getControl().getSelectionModel().selectAll();
589 }
590
591 private void selectAllPageUp() {
592 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
593 if (fm == null) return;
594
595 int leadIndex = fm.getFocusedIndex();
596 if (isShiftDown) {
597 leadIndex = getAnchor() == -1 ? leadIndex : getAnchor();
598 setAnchor(leadIndex);
599 }
600
601 int leadSelectedIndex = onScrollPageUp.call(false);
602
603 // fix for RT-34407
604 int adjust = leadIndex < leadSelectedIndex ? 1 : -1;
605
606 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
607 if (sm == null) return;
608
609 selectionChanging = true;
610 if (sm.getSelectionMode() == SelectionMode.SINGLE) {
611 sm.select(leadSelectedIndex);
612 } else {
613 sm.clearSelection();
614 sm.selectRange(leadIndex, leadSelectedIndex + adjust);
615 }
616 selectionChanging = false;
617 }
618
619 private void selectAllPageDown() {
620 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
621 if (fm == null) return;
622
623 int leadIndex = fm.getFocusedIndex();
624 if (isShiftDown) {
625 leadIndex = getAnchor() == -1 ? leadIndex : getAnchor();
626 setAnchor(leadIndex);
627 }
628
629 int leadSelectedIndex = onScrollPageDown.call(false);
630
631 // fix for RT-34407
632 int adjust = leadIndex < leadSelectedIndex ? 1 : -1;
633
634 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
635 if (sm == null) return;
636
637 selectionChanging = true;
638 if (sm.getSelectionMode() == SelectionMode.SINGLE) {
639 sm.select(leadSelectedIndex);
640 } else {
641 sm.clearSelection();
642 sm.selectRange(leadIndex, leadSelectedIndex + adjust);
643 }
644 selectionChanging = false;
645 }
646
647 private void selectAllToFocus(boolean setAnchorToFocusIndex) {
648 // Fix for RT-31241
649 final TreeView<T> treeView = getControl();
650 if (treeView.getEditingItem() != null) return;
651
652 MultipleSelectionModel<TreeItem<T>> sm = treeView.getSelectionModel();
653 if (sm == null) return;
654
655 FocusModel<TreeItem<T>> fm = treeView.getFocusModel();
656 if (fm == null) return;
657
658 int focusIndex = fm.getFocusedIndex();
659 int anchor = getAnchor();
660
661 sm.clearSelection();
662 int startPos = anchor;
663 int endPos = anchor > focusIndex ? focusIndex - 1 : focusIndex + 1;
664 sm.selectRange(startPos, endPos);
665 setAnchor(setAnchorToFocusIndex ? focusIndex : anchor);
666 }
667
668 private void expandRow() {
669 Callback<TreeItem<T>, Integer> getIndex = p -> getControl().getRow(p);
670 TreeViewBehavior.expandRow(getControl().getSelectionModel(), getIndex);
671 }
672
673 private void expandAll() {
674 TreeViewBehavior.expandAll(getControl().getRoot());
675 }
676
677 private void collapseRow() {
678 TreeView<T> control = getControl();
679 TreeViewBehavior.collapseRow(control.getSelectionModel(), control.getRoot(), control.isShowRoot());
680 }
681
682 static <T> void expandRow(final MultipleSelectionModel<TreeItem<T>> sm, Callback<TreeItem<T>, Integer> getIndex) {
683 if (sm == null) return;
684
685 TreeItem<T> treeItem = sm.getSelectedItem();
686 if (treeItem == null || treeItem.isLeaf()) return;
687
688 if (treeItem.isExpanded()) {
689 // move selection to the first child (RT-17978)
690 List<TreeItem<T>> children = treeItem.getChildren();
691 if (! children.isEmpty()) {
692 sm.clearAndSelect(getIndex.call(children.get(0)));
693 }
694 } else {
695 treeItem.setExpanded(true);
696 }
697 }
698
730 return;
731 }
732
733 // Fix for RT-17833 where the selection highlight could disappear unexpectedly from
734 // the root node in certain circumstances
735 if (root.equals(selectedItem) && (! root.isExpanded() || root.getChildren().isEmpty())) {
736 return;
737 }
738
739 // If we're on a leaf or the branch is not expanded, move up to the parent,
740 // otherwise collapse the branch.
741 if (selectedItem.isLeaf() || ! selectedItem.isExpanded()) {
742 sm.clearSelection();
743 sm.select(selectedItem.getParent());
744 } else {
745 selectedItem.setExpanded(false);
746 }
747 }
748
749 private void cancelEdit() {
750 getControl().edit(null);
751 }
752
753 private void edit() {
754 TreeItem<T> treeItem = getControl().getSelectionModel().getSelectedItem();
755 if (treeItem == null) return;
756
757 getControl().edit(treeItem);
758 }
759
760 private void toggleFocusOwnerSelection() {
761 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
762 if (sm == null) return;
763
764 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
765 if (fm == null) return;
766
767 int focusedIndex = fm.getFocusedIndex();
768
769 if (sm.isSelected(focusedIndex)) {
770 sm.clearSelection(focusedIndex);
771 fm.focus(focusedIndex);
772 } else {
773 sm.select(focusedIndex);
774 }
775
776 setAnchor(focusedIndex);
777 }
778
779 /**************************************************************************
780 * Discontinuous Selection *
781 *************************************************************************/
782
783 private void discontinuousSelectPreviousRow() {
784 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
785 if (sm == null) return;
786
787 if (sm.getSelectionMode() != SelectionMode.MULTIPLE) {
788 selectPreviousRow();
789 return;
790 }
791
792 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
793 if (fm == null) return;
794
795 int focusIndex = fm.getFocusedIndex();
796 final int newFocusIndex = focusIndex - 1;
797 if (newFocusIndex < 0) return;
798
799 int startIndex = focusIndex;
800 if (isShiftDown) {
801 startIndex = getAnchor() == -1 ? focusIndex : getAnchor();
802 }
803
804 sm.selectRange(newFocusIndex, startIndex + 1);
805 fm.focus(newFocusIndex);
806
807 if (onFocusPreviousRow != null) onFocusPreviousRow.run();
808 }
809
810 private void discontinuousSelectNextRow() {
811 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
812 if (sm == null) return;
813
814 if (sm.getSelectionMode() != SelectionMode.MULTIPLE) {
815 selectNextRow();
816 return;
817 }
818
819 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
820 if (fm == null) return;
821
822 int focusIndex = fm.getFocusedIndex();
823 final int newFocusIndex = focusIndex + 1;
824 if (newFocusIndex >= getControl().getExpandedItemCount()) return;
825
826 int startIndex = focusIndex;
827 if (isShiftDown) {
828 startIndex = getAnchor() == -1 ? focusIndex : getAnchor();
829 }
830
831 sm.selectRange(startIndex, newFocusIndex + 1);
832 fm.focus(newFocusIndex);
833
834 if (onFocusNextRow != null) onFocusNextRow.run();
835 }
836
837 private void discontinuousSelectPageUp() {
838 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
839 if (sm == null) return;
840
841 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
842 if (fm == null) return;
843
844 int anchor = getAnchor();
845 int leadSelectedIndex = onScrollPageUp.call(false);
846 sm.selectRange(anchor, leadSelectedIndex - 1);
847 }
848
849 private void discontinuousSelectPageDown() {
850 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
851 if (sm == null) return;
852
853 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
854 if (fm == null) return;
855
856 int anchor = getAnchor();
857 int leadSelectedIndex = onScrollPageDown.call(false);
858 sm.selectRange(anchor, leadSelectedIndex + 1);
859 }
860
861 private void discontinuousSelectAllToFirstRow() {
862 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
863 if (sm == null) return;
864
865 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
866 if (fm == null) return;
867
868 int index = fm.getFocusedIndex();
869 sm.selectRange(0, index);
870 fm.focus(0);
871
872 if (onMoveToFirstCell != null) onMoveToFirstCell.run();
873 }
874
875 private void discontinuousSelectAllToLastRow() {
876 MultipleSelectionModel<TreeItem<T>> sm = getControl().getSelectionModel();
877 if (sm == null) return;
878
879 FocusModel<TreeItem<T>> fm = getControl().getFocusModel();
880 if (fm == null) return;
881
882 int index = fm.getFocusedIndex() + 1;
883 sm.selectRange(index, getControl().getExpandedItemCount());
884
885 if (onMoveToLastCell != null) onMoveToLastCell.run();
886 }
887 }
|
1 /*
2 * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package com.sun.javafx.scene.control.behavior;
27
28 import com.sun.javafx.scene.control.inputmap.InputMap;
29 import javafx.beans.value.ChangeListener;
30 import javafx.beans.value.ObservableValue;
31 import javafx.beans.value.WeakChangeListener;
32 import javafx.collections.ListChangeListener;
33 import javafx.collections.WeakListChangeListener;
34 import javafx.event.EventHandler;
35 import javafx.scene.control.*;
36 import javafx.scene.input.*;
37 import javafx.util.Callback;
38
39 import java.util.ArrayList;
40 import java.util.List;
41 import com.sun.javafx.PlatformUtil;
42 import com.sun.javafx.scene.control.inputmap.KeyBinding;
43
44 import static javafx.scene.input.KeyCode.*;
45 import static com.sun.javafx.scene.control.inputmap.InputMap.KeyMapping;
46
47 public class TreeViewBehavior<T> extends BehaviorBase<TreeView<T>> {
48
49 private final InputMap<TreeView<T>> treeViewInputMap;
50
51 private final EventHandler<KeyEvent> keyEventListener = e -> {
52 if (!e.isConsumed()) {
53 // RT-12751: we want to keep an eye on the user holding down the shift key,
54 // so that we know when they enter/leave multiple selection mode. This
55 // changes what happens when certain key combinations are pressed.
56 isShiftDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShiftDown();
57 isShortcutDown = e.getEventType() == KeyEvent.KEY_PRESSED && e.isShortcutDown();
58 }
59 };
60
61 /**************************************************************************
62 * State and Functions *
63 *************************************************************************/
64
65 private boolean isShiftDown = false;
66 private boolean isShortcutDown = false;
67
68 // Support for RT-13826:
69 // set when focus is moved by keyboard to allow for proper selection positions
70 // private int selectPos = -1;
71
72 private Callback<Boolean, Integer> onScrollPageUp;
73 public void setOnScrollPageUp(Callback<Boolean, Integer> c) { onScrollPageUp = c; }
74
75 private Callback<Boolean, Integer> onScrollPageDown;
76 public void setOnScrollPageDown(Callback<Boolean, Integer> c) { onScrollPageDown = c; }
77
78 private Runnable onSelectPreviousRow;
79 public void setOnSelectPreviousRow(Runnable r) { onSelectPreviousRow = r; }
81 private Runnable onSelectNextRow;
82 public void setOnSelectNextRow(Runnable r) { onSelectNextRow = r; }
83
84 private Runnable onMoveToFirstCell;
85 public void setOnMoveToFirstCell(Runnable r) { onMoveToFirstCell = r; }
86
87 private Runnable onMoveToLastCell;
88 public void setOnMoveToLastCell(Runnable r) { onMoveToLastCell = r; }
89
90 private Runnable onFocusPreviousRow;
91 public void setOnFocusPreviousRow(Runnable r) { onFocusPreviousRow = r; }
92
93 private Runnable onFocusNextRow;
94 public void setOnFocusNextRow(Runnable r) { onFocusNextRow = r; }
95
96 private boolean selectionChanging = false;
97
98 private final ListChangeListener<Integer> selectedIndicesListener = c -> {
99 while (c.next()) {
100 if (c.wasReplaced()) {
101 if (TreeCellBehavior.hasDefaultAnchor(getNode())) {
102 TreeCellBehavior.removeAnchor(getNode());
103 }
104 }
105
106 final int shift = c.wasPermutated() ? c.getTo() - c.getFrom() : 0;
107
108 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
109
110 // there are no selected items, so lets clear out the anchor
111 if (! selectionChanging) {
112 if (sm.isEmpty()) {
113 setAnchor(-1);
114 } else if (hasAnchor() && ! sm.isSelected(getAnchor() + shift)) {
115 setAnchor(-1);
116 }
117 }
118
119 int addedSize = c.getAddedSize();
120 if (addedSize > 0 && ! hasAnchor()) {
121 List<? extends Integer> addedSubList = c.getAddedSubList();
122 int index = addedSubList.get(addedSize - 1);
123 setAnchor(index);
124 }
125 }
126 };
127
128 private final ChangeListener<MultipleSelectionModel<TreeItem<T>>> selectionModelListener =
129 new ChangeListener<MultipleSelectionModel<TreeItem<T>>>() {
130 @Override public void changed(ObservableValue<? extends MultipleSelectionModel<TreeItem<T>>> observable,
131 MultipleSelectionModel<TreeItem<T>> oldValue,
132 MultipleSelectionModel<TreeItem<T>> newValue) {
133 if (oldValue != null) {
134 oldValue.getSelectedIndices().removeListener(weakSelectedIndicesListener);
135 }
136 if (newValue != null) {
137 newValue.getSelectedIndices().addListener(weakSelectedIndicesListener);
138 }
139 }
140 };
141
142 private final WeakListChangeListener<Integer> weakSelectedIndicesListener =
143 new WeakListChangeListener<>(selectedIndicesListener);
144 private final WeakChangeListener<MultipleSelectionModel<TreeItem<T>>> weakSelectionModelListener =
145 new WeakChangeListener<>(selectionModelListener);
146
147 public TreeViewBehavior(TreeView<T> control) {
148 super(control);
149
150 // // Fix for RT-16565
151 // getNode().selectionModelProperty().addListener(weakSelectionModelListener);
152 // if (control.getSelectionModel() != null) {
153 // control.getSelectionModel().getSelectedIndices().addListener(weakSelectedIndicesListener);
154 // }
155
156
157
158 // create a map for treeView-specific mappings
159 treeViewInputMap = createInputMap();
160
161 // // add focus traversal mappings
162 // addDefaultMapping(treeViewInputMap, FocusTraversalInputMap.getFocusTraversalMappings());
163 addDefaultMapping(treeViewInputMap,
164 new KeyMapping(HOME, e -> selectFirstRow()),
165 new KeyMapping(END, e -> selectLastRow()),
166 new KeyMapping(new KeyBinding(HOME).shift(), e -> selectAllToFirstRow()),
167 new KeyMapping(new KeyBinding(END).shift(), e -> selectAllToLastRow()),
168 new KeyMapping(new KeyBinding(PAGE_UP).shift(), e -> selectAllPageUp()),
169 new KeyMapping(new KeyBinding(PAGE_DOWN).shift(), e -> selectAllPageDown()),
170
171 new KeyMapping(new KeyBinding(SPACE).shift(), e -> selectAllToFocus(false)),
172 new KeyMapping(new KeyBinding(SPACE).shortcut().shift(), e -> selectAllToFocus(true)),
173
174 new KeyMapping(new KeyBinding(HOME).shortcut(), e -> focusFirstRow()),
175 new KeyMapping(new KeyBinding(END).shortcut(), e -> focusLastRow()),
176
177 new KeyMapping(PAGE_UP, e -> scrollUp()),
178 new KeyMapping(PAGE_DOWN, e -> scrollDown()),
179
180 new KeyMapping(SPACE, e -> toggleFocusOwnerSelection()),
181
182 new KeyMapping(new KeyBinding(A).shortcut(), e -> selectAll()),
183 new KeyMapping(new KeyBinding(PAGE_UP).shortcut(), e -> focusPageUp()),
184 new KeyMapping(new KeyBinding(PAGE_DOWN).shortcut(), e -> focusPageDown()),
185 new KeyMapping(new KeyBinding(UP).shortcut(), e -> focusPreviousRow()),
186 new KeyMapping(new KeyBinding(DOWN).shortcut(), e -> focusNextRow()),
187 new KeyMapping(new KeyBinding(UP).shortcut().shift(), e -> discontinuousSelectPreviousRow()),
188 new KeyMapping(new KeyBinding(DOWN).shortcut().shift(), e -> discontinuousSelectNextRow()),
189 new KeyMapping(new KeyBinding(PAGE_UP).shortcut().shift(), e -> discontinuousSelectPageUp()),
190 new KeyMapping(new KeyBinding(PAGE_DOWN).shortcut().shift(), e -> discontinuousSelectPageDown()),
191 new KeyMapping(new KeyBinding(HOME).shortcut().shift(), e -> discontinuousSelectAllToFirstRow()),
192 new KeyMapping(new KeyBinding(END).shortcut().shift(), e -> discontinuousSelectAllToLastRow()),
193
194 // these should be read as 'if RTL, use the first method, otherwise use the second'
195 new KeyMapping(LEFT, e -> rtl(control, this::expandRow, this::collapseRow)),
196 new KeyMapping(KP_LEFT, e -> rtl(control, this::expandRow, this::collapseRow)),
197 new KeyMapping(RIGHT, e -> rtl(control, this::collapseRow, this::expandRow)),
198 new KeyMapping(KP_RIGHT, e -> rtl(control, this::collapseRow, this::expandRow)),
199
200 new KeyMapping(MULTIPLY, e -> expandAll()),
201 new KeyMapping(ADD, e -> expandRow()),
202 new KeyMapping(SUBTRACT, e -> collapseRow()),
203
204 new KeyMapping(UP, e -> selectPreviousRow()),
205 new KeyMapping(KP_UP, e -> selectPreviousRow()),
206 new KeyMapping(DOWN, e -> selectNextRow()),
207 new KeyMapping(KP_DOWN, e -> selectNextRow()),
208
209 new KeyMapping(new KeyBinding(UP).shift(), e -> alsoSelectPreviousRow()),
210 new KeyMapping(new KeyBinding(KP_UP).shift(), e -> alsoSelectPreviousRow()),
211 new KeyMapping(new KeyBinding(DOWN).shift(), e -> alsoSelectNextRow()),
212 new KeyMapping(new KeyBinding(KP_DOWN).shift(), e -> alsoSelectNextRow()),
213
214 new KeyMapping(ENTER, e -> edit()),
215 new KeyMapping(F2, e -> edit()),
216 new KeyMapping(ESCAPE, e -> cancelEdit()),
217
218 new InputMap.MouseMapping(MouseEvent.MOUSE_PRESSED, this::mousePressed)
219 );
220
221 // create OS-specific child mappings
222 // --- mac OS
223 InputMap<TreeView<T>> macInputMap = new InputMap<>(control);
224 macInputMap.setInterceptor(event -> !PlatformUtil.isMac());
225 addDefaultMapping(macInputMap, new KeyMapping(new KeyBinding(SPACE).shortcut().ctrl(), e -> toggleFocusOwnerSelection()));
226 addDefaultChildMap(treeViewInputMap, macInputMap);
227
228 // --- all other platforms
229 InputMap<TreeView<T>> otherOsInputMap = new InputMap<>(control);
230 otherOsInputMap.setInterceptor(event -> PlatformUtil.isMac());
231 addDefaultMapping(otherOsInputMap, new KeyMapping(new KeyBinding(SPACE).ctrl(), e -> toggleFocusOwnerSelection()));
232 addDefaultChildMap(treeViewInputMap, otherOsInputMap);
233
234 // set up other listeners
235 // We make this an event _filter_ so that we can determine the state
236 // of the shift key before the event handlers get a shot at the event.
237 control.addEventFilter(KeyEvent.ANY, keyEventListener);
238
239 // Fix for RT-16565
240 control.selectionModelProperty().addListener(weakSelectionModelListener);
241 if (control.getSelectionModel() != null) {
242 control.getSelectionModel().getSelectedIndices().addListener(weakSelectedIndicesListener);
243 }
244 }
245
246 @Override public InputMap<TreeView<T>> getInputMap() {
247 return treeViewInputMap;
248 }
249
250 @Override public void dispose() {
251 TreeCellBehavior.removeAnchor(getNode());
252 super.dispose();
253 }
254
255 private void setAnchor(int anchor) {
256 TreeCellBehavior.setAnchor(getNode(), anchor < 0 ? null : anchor, false);
257 }
258
259 private int getAnchor() {
260 return TreeCellBehavior.getAnchor(getNode(), getNode().getFocusModel().getFocusedIndex());
261 }
262
263 private boolean hasAnchor() {
264 return TreeCellBehavior.hasNonDefaultAnchor(getNode());
265 }
266
267 public void mousePressed(MouseEvent e) {
268 if (! e.isShiftDown()) {
269 int index = getNode().getSelectionModel().getSelectedIndex();
270 setAnchor(index);
271 }
272
273 if (! getNode().isFocused() && getNode().isFocusTraversable()) {
274 getNode().requestFocus();
275 }
276 }
277
278 private void clearSelection() {
279 getNode().getSelectionModel().clearSelection();
280 //select(null);
281 }
282
283 private void scrollUp() {
284 int newSelectedIndex = -1;
285 if (onScrollPageUp != null) {
286 newSelectedIndex = onScrollPageUp.call(false);
287 }
288 if (newSelectedIndex == -1) return;
289
290 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
291 if (sm == null) return;
292 sm.clearAndSelect(newSelectedIndex);
293 }
294
295 private void scrollDown() {
296 int newSelectedIndex = -1;
297 if (onScrollPageDown != null) {
298 newSelectedIndex = onScrollPageDown.call(false);
299 }
300 if (newSelectedIndex == -1) return;
301
302 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
303 if (sm == null) return;
304 sm.clearAndSelect(newSelectedIndex);
305 }
306
307 private void focusFirstRow() {
308 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
309 if (fm == null) return;
310 fm.focus(0);
311
312 if (onMoveToFirstCell != null) onMoveToFirstCell.run();
313 }
314
315 private void focusLastRow() {
316 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
317 if (fm == null) return;
318 fm.focus(getNode().getExpandedItemCount() - 1);
319
320 if (onMoveToLastCell != null) onMoveToLastCell.run();
321 }
322
323 private void focusPreviousRow() {
324 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
325 if (fm == null) return;
326
327 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
328 if (sm == null) return;
329
330 fm.focusPrevious();
331
332 if (! isShortcutDown || getAnchor() == -1) {
333 setAnchor(fm.getFocusedIndex());
334 }
335
336 if (onFocusPreviousRow != null) onFocusPreviousRow.run();
337 }
338
339 private void focusNextRow() {
340 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
341 if (fm == null) return;
342
343 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
344 if (sm == null) return;
345
346 fm.focusNext();
347
348 if (! isShortcutDown || getAnchor() == -1) {
349 setAnchor(fm.getFocusedIndex());
350 }
351
352 if (onFocusNextRow != null) onFocusNextRow.run();
353 }
354
355 private void focusPageUp() {
356 int newFocusIndex = onScrollPageUp.call(true);
357
358 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
359 if (fm == null) return;
360 fm.focus(newFocusIndex);
361 }
362
363 private void focusPageDown() {
364 int newFocusIndex = onScrollPageDown.call(true);
365
366 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
367 if (fm == null) return;
368 fm.focus(newFocusIndex);
369 }
370
371 private void alsoSelectPreviousRow() {
372 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
373 if (fm == null) return;
374
375 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
376 if (sm == null) return;
377
378 if (isShiftDown && getAnchor() != -1) {
379 int newRow = fm.getFocusedIndex() - 1;
380 if (newRow < 0) return;
381
382 int anchor = getAnchor();
383
384 if (! hasAnchor()) {
385 setAnchor(fm.getFocusedIndex());
386 }
387
388 if (sm.getSelectedIndices().size() > 1) {
389 clearSelectionOutsideRange(anchor, newRow);
390 }
391
392 if (anchor > newRow) {
393 sm.selectRange(anchor, newRow - 1);
394 } else {
395 sm.selectRange(anchor, newRow + 1);
396 }
397 } else {
398 sm.selectPrevious();
399 }
400
401 onSelectPreviousRow.run();
402 }
403
404 private void alsoSelectNextRow() {
405 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
406 if (fm == null) return;
407
408 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
409 if (sm == null) return;
410
411 if (isShiftDown && getAnchor() != -1) {
412 int newRow = fm.getFocusedIndex() + 1;
413 int anchor = getAnchor();
414
415 if (! hasAnchor()) {
416 setAnchor(fm.getFocusedIndex());
417 }
418
419 if (sm.getSelectedIndices().size() > 1) {
420 clearSelectionOutsideRange(anchor, newRow);
421 }
422
423 if (anchor > newRow) {
424 sm.selectRange(anchor, newRow - 1);
425 } else {
426 sm.selectRange(anchor, newRow + 1);
427 }
428 } else {
429 sm.selectNext();
430 }
431
432 onSelectNextRow.run();
433 }
434
435 private void clearSelectionOutsideRange(int start, int end) {
436 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
437 if (sm == null) return;
438
439 int min = Math.min(start, end);
440 int max = Math.max(start, end);
441
442 List<Integer> indices = new ArrayList<Integer>(sm.getSelectedIndices());
443
444 selectionChanging = true;
445 for (int i = 0; i < indices.size(); i++) {
446 int index = indices.get(i);
447 if (index < min || index > max) {
448 sm.clearSelection(index);
449 }
450 }
451 selectionChanging = false;
452 }
453
454 private void selectPreviousRow() {
455 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
456 if (fm == null) return;
457
458 int focusIndex = fm.getFocusedIndex();
459 if (focusIndex <= 0) {
460 return;
461 }
462
463 setAnchor(focusIndex - 1);
464 getNode().getSelectionModel().clearAndSelect(focusIndex - 1);
465 onSelectPreviousRow.run();
466 }
467
468 private void selectNextRow() {
469 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
470 if (fm == null) return;
471
472 int focusIndex = fm.getFocusedIndex();
473 if (focusIndex == getNode().getExpandedItemCount() - 1) {
474 return;
475 }
476
477 setAnchor(focusIndex + 1);
478 getNode().getSelectionModel().clearAndSelect(focusIndex + 1);
479 onSelectNextRow.run();
480 }
481
482 private void selectFirstRow() {
483 if (getNode().getExpandedItemCount() > 0) {
484 getNode().getSelectionModel().clearAndSelect(0);
485 if (onMoveToFirstCell != null) onMoveToFirstCell.run();
486 }
487 }
488
489 private void selectLastRow() {
490 getNode().getSelectionModel().clearAndSelect(getNode().getExpandedItemCount() - 1);
491 onMoveToLastCell.run();
492 }
493
494 private void selectAllToFirstRow() {
495 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
496 if (sm == null) return;
497
498 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
499 if (fm == null) return;
500
501 int leadIndex = fm.getFocusedIndex();
502
503 if (isShiftDown) {
504 leadIndex = hasAnchor() ? getAnchor() : leadIndex;
505 }
506
507 sm.clearSelection();
508 sm.selectRange(leadIndex, -1);
509
510 // RT-18413: Focus must go to first row
511 fm.focus(0);
512
513 if (isShiftDown) {
514 setAnchor(leadIndex);
515 }
516
517 if (onMoveToFirstCell != null) onMoveToFirstCell.run();
518 }
519
520 private void selectAllToLastRow() {
521 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
522 if (sm == null) return;
523
524 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
525 if (fm == null) return;
526
527 int leadIndex = fm.getFocusedIndex();
528
529 if (isShiftDown) {
530 leadIndex = hasAnchor() ? getAnchor() : leadIndex;
531 }
532
533 sm.clearSelection();
534 sm.selectRange(leadIndex, getNode().getExpandedItemCount());
535
536 if (isShiftDown) {
537 setAnchor(leadIndex);
538 }
539
540 if (onMoveToLastCell != null) onMoveToLastCell.run();
541 }
542
543 private void selectAll() {
544 getNode().getSelectionModel().selectAll();
545 }
546
547 private void selectAllPageUp() {
548 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
549 if (fm == null) return;
550
551 int leadIndex = fm.getFocusedIndex();
552 if (isShiftDown) {
553 leadIndex = getAnchor() == -1 ? leadIndex : getAnchor();
554 setAnchor(leadIndex);
555 }
556
557 int leadSelectedIndex = onScrollPageUp.call(false);
558
559 // fix for RT-34407
560 int adjust = leadIndex < leadSelectedIndex ? 1 : -1;
561
562 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
563 if (sm == null) return;
564
565 selectionChanging = true;
566 if (sm.getSelectionMode() == SelectionMode.SINGLE) {
567 sm.select(leadSelectedIndex);
568 } else {
569 sm.clearSelection();
570 sm.selectRange(leadIndex, leadSelectedIndex + adjust);
571 }
572 selectionChanging = false;
573 }
574
575 private void selectAllPageDown() {
576 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
577 if (fm == null) return;
578
579 int leadIndex = fm.getFocusedIndex();
580 if (isShiftDown) {
581 leadIndex = getAnchor() == -1 ? leadIndex : getAnchor();
582 setAnchor(leadIndex);
583 }
584
585 int leadSelectedIndex = onScrollPageDown.call(false);
586
587 // fix for RT-34407
588 int adjust = leadIndex < leadSelectedIndex ? 1 : -1;
589
590 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
591 if (sm == null) return;
592
593 selectionChanging = true;
594 if (sm.getSelectionMode() == SelectionMode.SINGLE) {
595 sm.select(leadSelectedIndex);
596 } else {
597 sm.clearSelection();
598 sm.selectRange(leadIndex, leadSelectedIndex + adjust);
599 }
600 selectionChanging = false;
601 }
602
603 private void selectAllToFocus(boolean setAnchorToFocusIndex) {
604 // Fix for RT-31241
605 final TreeView<T> treeView = getNode();
606 if (treeView.getEditingItem() != null) return;
607
608 MultipleSelectionModel<TreeItem<T>> sm = treeView.getSelectionModel();
609 if (sm == null) return;
610
611 FocusModel<TreeItem<T>> fm = treeView.getFocusModel();
612 if (fm == null) return;
613
614 int focusIndex = fm.getFocusedIndex();
615 int anchor = getAnchor();
616
617 sm.clearSelection();
618 int startPos = anchor;
619 int endPos = anchor > focusIndex ? focusIndex - 1 : focusIndex + 1;
620 sm.selectRange(startPos, endPos);
621 setAnchor(setAnchorToFocusIndex ? focusIndex : anchor);
622 }
623
624 private void expandRow() {
625 Callback<TreeItem<T>, Integer> getIndex = p -> getNode().getRow(p);
626 TreeViewBehavior.expandRow(getNode().getSelectionModel(), getIndex);
627 }
628
629 private void expandAll() {
630 TreeViewBehavior.expandAll(getNode().getRoot());
631 }
632
633 private void collapseRow() {
634 TreeView<T> control = getNode();
635 TreeViewBehavior.collapseRow(control.getSelectionModel(), control.getRoot(), control.isShowRoot());
636 }
637
638 static <T> void expandRow(final MultipleSelectionModel<TreeItem<T>> sm, Callback<TreeItem<T>, Integer> getIndex) {
639 if (sm == null) return;
640
641 TreeItem<T> treeItem = sm.getSelectedItem();
642 if (treeItem == null || treeItem.isLeaf()) return;
643
644 if (treeItem.isExpanded()) {
645 // move selection to the first child (RT-17978)
646 List<TreeItem<T>> children = treeItem.getChildren();
647 if (! children.isEmpty()) {
648 sm.clearAndSelect(getIndex.call(children.get(0)));
649 }
650 } else {
651 treeItem.setExpanded(true);
652 }
653 }
654
686 return;
687 }
688
689 // Fix for RT-17833 where the selection highlight could disappear unexpectedly from
690 // the root node in certain circumstances
691 if (root.equals(selectedItem) && (! root.isExpanded() || root.getChildren().isEmpty())) {
692 return;
693 }
694
695 // If we're on a leaf or the branch is not expanded, move up to the parent,
696 // otherwise collapse the branch.
697 if (selectedItem.isLeaf() || ! selectedItem.isExpanded()) {
698 sm.clearSelection();
699 sm.select(selectedItem.getParent());
700 } else {
701 selectedItem.setExpanded(false);
702 }
703 }
704
705 private void cancelEdit() {
706 getNode().edit(null);
707 }
708
709 private void edit() {
710 TreeItem<T> treeItem = getNode().getSelectionModel().getSelectedItem();
711 if (treeItem == null) return;
712
713 getNode().edit(treeItem);
714 }
715
716 private void toggleFocusOwnerSelection() {
717 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
718 if (sm == null) return;
719
720 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
721 if (fm == null) return;
722
723 int focusedIndex = fm.getFocusedIndex();
724
725 if (sm.isSelected(focusedIndex)) {
726 sm.clearSelection(focusedIndex);
727 fm.focus(focusedIndex);
728 } else {
729 sm.select(focusedIndex);
730 }
731
732 setAnchor(focusedIndex);
733 }
734
735 /**************************************************************************
736 * Discontinuous Selection *
737 *************************************************************************/
738
739 private void discontinuousSelectPreviousRow() {
740 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
741 if (sm == null) return;
742
743 if (sm.getSelectionMode() != SelectionMode.MULTIPLE) {
744 selectPreviousRow();
745 return;
746 }
747
748 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
749 if (fm == null) return;
750
751 int focusIndex = fm.getFocusedIndex();
752 final int newFocusIndex = focusIndex - 1;
753 if (newFocusIndex < 0) return;
754
755 int startIndex = focusIndex;
756 if (isShiftDown) {
757 startIndex = getAnchor() == -1 ? focusIndex : getAnchor();
758 }
759
760 sm.selectRange(newFocusIndex, startIndex + 1);
761 fm.focus(newFocusIndex);
762
763 if (onFocusPreviousRow != null) onFocusPreviousRow.run();
764 }
765
766 private void discontinuousSelectNextRow() {
767 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
768 if (sm == null) return;
769
770 if (sm.getSelectionMode() != SelectionMode.MULTIPLE) {
771 selectNextRow();
772 return;
773 }
774
775 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
776 if (fm == null) return;
777
778 int focusIndex = fm.getFocusedIndex();
779 final int newFocusIndex = focusIndex + 1;
780 if (newFocusIndex >= getNode().getExpandedItemCount()) return;
781
782 int startIndex = focusIndex;
783 if (isShiftDown) {
784 startIndex = getAnchor() == -1 ? focusIndex : getAnchor();
785 }
786
787 sm.selectRange(startIndex, newFocusIndex + 1);
788 fm.focus(newFocusIndex);
789
790 if (onFocusNextRow != null) onFocusNextRow.run();
791 }
792
793 private void discontinuousSelectPageUp() {
794 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
795 if (sm == null) return;
796
797 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
798 if (fm == null) return;
799
800 int anchor = getAnchor();
801 int leadSelectedIndex = onScrollPageUp.call(false);
802 sm.selectRange(anchor, leadSelectedIndex - 1);
803 }
804
805 private void discontinuousSelectPageDown() {
806 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
807 if (sm == null) return;
808
809 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
810 if (fm == null) return;
811
812 int anchor = getAnchor();
813 int leadSelectedIndex = onScrollPageDown.call(false);
814 sm.selectRange(anchor, leadSelectedIndex + 1);
815 }
816
817 private void discontinuousSelectAllToFirstRow() {
818 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
819 if (sm == null) return;
820
821 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
822 if (fm == null) return;
823
824 int index = fm.getFocusedIndex();
825 sm.selectRange(0, index);
826 fm.focus(0);
827
828 if (onMoveToFirstCell != null) onMoveToFirstCell.run();
829 }
830
831 private void discontinuousSelectAllToLastRow() {
832 MultipleSelectionModel<TreeItem<T>> sm = getNode().getSelectionModel();
833 if (sm == null) return;
834
835 FocusModel<TreeItem<T>> fm = getNode().getFocusModel();
836 if (fm == null) return;
837
838 int index = fm.getFocusedIndex() + 1;
839 sm.selectRange(index, getNode().getExpandedItemCount());
840
841 if (onMoveToLastCell != null) onMoveToLastCell.run();
842 }
843 }
|