Skip to content

Commit 19a855e

Browse files
author
Jose Pereda
committed
8286261: Selection of non-expanded non-leaf treeItem grows unexpectedly when adding two-level descendants
Reviewed-by: aghaisas
1 parent 18b2366 commit 19a855e

File tree

5 files changed

+179
-8
lines changed

5 files changed

+179
-8
lines changed

modules/javafx.controls/src/main/java/javafx/scene/control/ControlUtils.java

+14
Original file line numberDiff line numberDiff line change
@@ -232,4 +232,18 @@ public static <S> int getIndexOfChildWithDescendant(TreeItem<S> parent, TreeItem
232232
}
233233
return -1;
234234
}
235+
236+
public static <S> boolean isTreeItemIncludingAncestorsExpanded(TreeItem<S> item) {
237+
if (item == null || !item.isExpanded()) {
238+
return false;
239+
}
240+
TreeItem<S> ancestor = item.getParent();
241+
while (ancestor != null) {
242+
if (!ancestor.isExpanded()) {
243+
return false;
244+
}
245+
ancestor = ancestor.getParent();
246+
}
247+
return true;
248+
}
235249
}

modules/javafx.controls/src/main/java/javafx/scene/control/TreeTableView.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -2611,7 +2611,7 @@ private void updateTreeEventListener(TreeItem<S> oldRoot, TreeItem<S> newRoot) {
26112611
}
26122612
} else if (e.wasAdded()) {
26132613
// shuffle selection by the number of added items
2614-
shift += treeItem.isExpanded() ? addedSize : 0;
2614+
shift += ControlUtils.isTreeItemIncludingAncestorsExpanded(treeItem) ? addedSize : 0;
26152615

26162616
// RT-32963: We were taking the startRow from the TreeItem
26172617
// in which the children were added, rather than from the
@@ -2647,7 +2647,7 @@ private void updateTreeEventListener(TreeItem<S> oldRoot, TreeItem<S> newRoot) {
26472647

26482648
// shuffle selection by the number of removed items
26492649
// only if removed items are before the current selection.
2650-
if (treeItem.isExpanded()) {
2650+
if (ControlUtils.isTreeItemIncludingAncestorsExpanded(treeItem)) {
26512651
int lastSelectedSiblingIndex = selectedItems.stream()
26522652
.map(item -> ControlUtils.getIndexOfChildWithDescendant(treeItem, item))
26532653
.max(Comparator.naturalOrder())
@@ -3491,7 +3491,7 @@ private void updateTreeEventListener(TreeItem<S> oldRoot, TreeItem<S> newRoot) {
34913491
// get the TreeItem the event occurred on - we only need to
34923492
// shift if the tree item is expanded
34933493
TreeItem<S> eventTreeItem = e.getTreeItem();
3494-
if (eventTreeItem.isExpanded()) {
3494+
if (ControlUtils.isTreeItemIncludingAncestorsExpanded(eventTreeItem)) {
34953495
for (int i = 0; i < e.getAddedChildren().size(); i++) {
34963496
// get the added item and determine the row it is in
34973497
TreeItem<S> item = e.getAddedChildren().get(i);
@@ -3513,7 +3513,7 @@ private void updateTreeEventListener(TreeItem<S> oldRoot, TreeItem<S> newRoot) {
35133513
}
35143514
}
35153515

3516-
if (e.getTreeItem().isExpanded()) {
3516+
if (ControlUtils.isTreeItemIncludingAncestorsExpanded(e.getTreeItem())) {
35173517
int focusedSiblingRow = ControlUtils.getIndexOfChildWithDescendant(e.getTreeItem(), getFocusedItem());
35183518
if (e.getFrom() <= focusedSiblingRow) {
35193519
// shuffle selection by the number of removed items

modules/javafx.controls/src/main/java/javafx/scene/control/TreeView.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -1406,7 +1406,7 @@ private void updateTreeEventListener(TreeItem<T> oldRoot, TreeItem<T> newRoot) {
14061406
// no-op
14071407
} else if (e.wasAdded()) {
14081408
// shuffle selection by the number of added items
1409-
shift += treeItem.isExpanded() ? addedSize : 0;
1409+
shift += ControlUtils.isTreeItemIncludingAncestorsExpanded(treeItem) ? addedSize : 0;
14101410

14111411
// RT-32963: We were taking the startRow from the TreeItem
14121412
// in which the children were added, rather than from the
@@ -1434,7 +1434,7 @@ private void updateTreeEventListener(TreeItem<T> oldRoot, TreeItem<T> newRoot) {
14341434

14351435
// shuffle selection by the number of removed items
14361436
// only if removed items are before the current selection.
1437-
if (treeItem.isExpanded()) {
1437+
if (ControlUtils.isTreeItemIncludingAncestorsExpanded(treeItem)) {
14381438
int lastSelectedSiblingIndex = selectedItems.stream()
14391439
.map(item -> ControlUtils.getIndexOfChildWithDescendant(treeItem, item))
14401440
.max(Comparator.naturalOrder())
@@ -1683,7 +1683,7 @@ private void updateTreeEventListener(TreeItem<T> oldRoot, TreeItem<T> newRoot) {
16831683
// get the TreeItem the event occurred on - we only need to
16841684
// shift if the tree item is expanded
16851685
TreeItem<T> eventTreeItem = e.getTreeItem();
1686-
if (eventTreeItem.isExpanded()) {
1686+
if (ControlUtils.isTreeItemIncludingAncestorsExpanded(eventTreeItem)) {
16871687
for (int i = 0; i < e.getAddedChildren().size(); i++) {
16881688
// get the added item and determine the row it is in
16891689
TreeItem<T> item = e.getAddedChildren().get(i);
@@ -1705,7 +1705,7 @@ private void updateTreeEventListener(TreeItem<T> oldRoot, TreeItem<T> newRoot) {
17051705
}
17061706
}
17071707

1708-
if (e.getTreeItem().isExpanded()) {
1708+
if (ControlUtils.isTreeItemIncludingAncestorsExpanded(e.getTreeItem())) {
17091709
int focusedSiblingRow = ControlUtils.getIndexOfChildWithDescendant(e.getTreeItem(), getFocusedItem());
17101710
if (e.getFrom() <= focusedSiblingRow) {
17111711
// shuffle selection by the number of removed items

modules/javafx.controls/src/test/java/test/javafx/scene/control/TreeTableViewTest.java

+83
Original file line numberDiff line numberDiff line change
@@ -6831,4 +6831,87 @@ public void test_ChangeToStringMouseMultipleSelectionCellMode() {
68316831
Thread.currentThread().setUncaughtExceptionHandler(exceptionHandler);
68326832
}
68336833

6834+
// JDK-8286261
6835+
@Test
6836+
public void testAddTreeItemToCollapsedAncestorKeepsSelectedItem() {
6837+
TreeItem<String> rootNode = new TreeItem<>("Root");
6838+
rootNode.setExpanded(true);
6839+
TreeItem<String> level1 = new TreeItem<>("Node 0");
6840+
level1.setExpanded(false);
6841+
TreeItem<String> level2 = new TreeItem<>("Node 1");
6842+
level2.getChildren().add(new TreeItem<>("Node 2"));
6843+
level2.setExpanded(true);
6844+
6845+
rootNode.getChildren().add(level1);
6846+
rootNode.getChildren().add(new TreeItem<>("Node 3"));
6847+
6848+
level1.getChildren().add(level2);
6849+
6850+
TreeTableColumn<String, String> column = new TreeTableColumn<>("Nodes");
6851+
column.setCellValueFactory(p -> new ReadOnlyStringWrapper(p.getValue().getValue()));
6852+
column.setPrefWidth(200);
6853+
6854+
TreeTableView<String> table = new TreeTableView<>(rootNode);
6855+
table.setShowRoot(false);
6856+
table.getColumns().add(column);
6857+
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
6858+
6859+
table.getSelectionModel().select(level1);
6860+
assertEquals(0, table.getSelectionModel().getSelectedIndex());
6861+
assertEquals("Node 0", table.getSelectionModel().getSelectedItem().getValue());
6862+
assertEquals(0, table.getFocusModel().getFocusedIndex());
6863+
assertEquals("Node 0", table.getFocusModel().getFocusedItem().getValue());
6864+
6865+
// add new node at level 3, that has a collapsed ancestor
6866+
level2.getChildren().add(new TreeItem<>("Node 4"));
6867+
6868+
// selection and focus remain at level1
6869+
assertEquals(0, table.getSelectionModel().getSelectedIndex());
6870+
assertEquals("Node 0", table.getSelectionModel().getSelectedItem().getValue());
6871+
assertEquals(0, table.getFocusModel().getFocusedIndex());
6872+
assertEquals("Node 0", table.getFocusModel().getFocusedItem().getValue());
6873+
}
6874+
6875+
// JDK-8286261
6876+
@Test
6877+
public void testRemoveTreeItemFromCollapsedAncestorKeepsSelectedItem() {
6878+
TreeItem<String> rootNode = new TreeItem<>("Root");
6879+
rootNode.setExpanded(true);
6880+
TreeItem<String> level1 = new TreeItem<>("Node 0");
6881+
level1.setExpanded(false);
6882+
TreeItem<String> level2 = new TreeItem<>("Node 1");
6883+
level2.getChildren().add(new TreeItem<>("Node 2"));
6884+
level2.getChildren().add(new TreeItem<>("Node 3"));
6885+
6886+
level2.setExpanded(true);
6887+
6888+
rootNode.getChildren().add(level1);
6889+
rootNode.getChildren().add(new TreeItem<>("Node 4"));
6890+
6891+
level1.getChildren().add(level2);
6892+
6893+
TreeTableColumn<String, String> column = new TreeTableColumn<>("Nodes");
6894+
column.setCellValueFactory(p -> new ReadOnlyStringWrapper(p.getValue().getValue()));
6895+
column.setPrefWidth(200);
6896+
6897+
TreeTableView<String> table = new TreeTableView<>(rootNode);
6898+
table.setShowRoot(false);
6899+
table.getColumns().add(column);
6900+
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
6901+
6902+
table.getSelectionModel().select(level1);
6903+
assertEquals(0, table.getSelectionModel().getSelectedIndex());
6904+
assertEquals("Node 0", table.getSelectionModel().getSelectedItem().getValue());
6905+
assertEquals(0, table.getFocusModel().getFocusedIndex());
6906+
assertEquals("Node 0", table.getFocusModel().getFocusedItem().getValue());
6907+
6908+
// remove Node 2 at level 3, that has a collapsed ancestor
6909+
level2.getChildren().remove(0);
6910+
6911+
// selection and focus remain at level1
6912+
assertEquals(0, table.getSelectionModel().getSelectedIndex());
6913+
assertEquals("Node 0", table.getSelectionModel().getSelectedItem().getValue());
6914+
assertEquals(0, table.getFocusModel().getFocusedIndex());
6915+
assertEquals("Node 0", table.getFocusModel().getFocusedItem().getValue());
6916+
}
68346917
}

modules/javafx.controls/src/test/java/test/javafx/scene/control/TreeViewTest.java

+74
Original file line numberDiff line numberDiff line change
@@ -3833,6 +3833,80 @@ public void testRemoveTreeItemChangesSelectedItem() {
38333833
assertEquals("Node 1", table.getSelectionModel().getSelectedItem().getValue());
38343834
}
38353835

3836+
// JDK-8286261
3837+
@Test
3838+
public void testAddTreeItemToCollapsedAncestorKeepsSelectedItem() {
3839+
TreeItem<String> rootNode = new TreeItem<>("Root");
3840+
rootNode.setExpanded(true);
3841+
TreeItem<String> level1 = new TreeItem<>("Node 0");
3842+
level1.setExpanded(false);
3843+
TreeItem<String> level2 = new TreeItem<>("Node 1");
3844+
level2.getChildren().add(new TreeItem<>("Node 2"));
3845+
level2.setExpanded(true);
3846+
3847+
rootNode.getChildren().add(level1);
3848+
rootNode.getChildren().add(new TreeItem<>("Node 3"));
3849+
3850+
level1.getChildren().add(level2);
3851+
3852+
TreeView<String> table = new TreeView<>(rootNode);
3853+
table.setShowRoot(false);
3854+
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
3855+
3856+
table.getSelectionModel().select(level1);
3857+
assertEquals(0, table.getSelectionModel().getSelectedIndex());
3858+
assertEquals("Node 0", table.getSelectionModel().getSelectedItem().getValue());
3859+
assertEquals(0, table.getFocusModel().getFocusedIndex());
3860+
assertEquals("Node 0", table.getFocusModel().getFocusedItem().getValue());
3861+
3862+
// add new node at level 3, that has a collapsed ancestor
3863+
level2.getChildren().add(new TreeItem<>("Node 4"));
3864+
3865+
// selection and focus remain at level1
3866+
assertEquals(0, table.getSelectionModel().getSelectedIndex());
3867+
assertEquals("Node 0", table.getSelectionModel().getSelectedItem().getValue());
3868+
assertEquals(0, table.getFocusModel().getFocusedIndex());
3869+
assertEquals("Node 0", table.getFocusModel().getFocusedItem().getValue());
3870+
}
3871+
3872+
// JDK-8286261
3873+
@Test
3874+
public void testRemoveTreeItemFromCollapsedAncestorKeepsSelectedItem() {
3875+
TreeItem<String> rootNode = new TreeItem<>("Root");
3876+
rootNode.setExpanded(true);
3877+
TreeItem<String> level1 = new TreeItem<>("Node 0");
3878+
level1.setExpanded(false);
3879+
TreeItem<String> level2 = new TreeItem<>("Node 1");
3880+
level2.getChildren().add(new TreeItem<>("Node 2"));
3881+
level2.getChildren().add(new TreeItem<>("Node 3"));
3882+
3883+
level2.setExpanded(true);
3884+
3885+
rootNode.getChildren().add(level1);
3886+
rootNode.getChildren().add(new TreeItem<>("Node 4"));
3887+
3888+
level1.getChildren().add(level2);
3889+
3890+
TreeView<String> table = new TreeView<>(rootNode);
3891+
table.setShowRoot(false);
3892+
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
3893+
3894+
table.getSelectionModel().select(level1);
3895+
assertEquals(0, table.getSelectionModel().getSelectedIndex());
3896+
assertEquals("Node 0", table.getSelectionModel().getSelectedItem().getValue());
3897+
assertEquals(0, table.getFocusModel().getFocusedIndex());
3898+
assertEquals("Node 0", table.getFocusModel().getFocusedItem().getValue());
3899+
3900+
// remove Node 2 at level 3, that has a collapsed ancestor
3901+
level2.getChildren().remove(0);
3902+
3903+
// selection and focus remain at level1
3904+
assertEquals(0, table.getSelectionModel().getSelectedIndex());
3905+
assertEquals("Node 0", table.getSelectionModel().getSelectedItem().getValue());
3906+
assertEquals(0, table.getFocusModel().getFocusedIndex());
3907+
assertEquals("Node 0", table.getFocusModel().getFocusedItem().getValue());
3908+
}
3909+
38363910
public static class MisbehavingOnCancelTreeCell<S> extends TreeCell<S> {
38373911

38383912
@Override

0 commit comments

Comments
 (0)