diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TableHeaderRow.java b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TableHeaderRow.java index e90c68ee389..48a251980c5 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TableHeaderRow.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TableHeaderRow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -88,7 +88,7 @@ public class TableHeaderRow extends StackPane { private final VirtualFlow flow; final TableViewSkinBase tableSkin; - private Map columnMenuItems = new HashMap<>(); + private Map columnMenuItems; private double scrollX; private double tableWidth; private Rectangle clip; @@ -210,12 +210,6 @@ public TableHeaderRow(final TableViewSkinBase skin) { tableSkin.getSkinnable().paddingProperty().addListener(weakTablePaddingListener); TableSkinUtils.getVisibleLeafColumns(skin).addListener(weakVisibleLeafColumnsListener); - // popup menu for hiding/showing columns - columnPopupMenu = new ContextMenu(); - updateTableColumnListeners(TableSkinUtils.getColumns(tableSkin), Collections.>emptyList()); - TableSkinUtils.getVisibleLeafColumns(skin).addListener(weakTableColumnsListener); - TableSkinUtils.getColumns(tableSkin).addListener(weakTableColumnsListener); - // drag header region. Used to indicate the current column being reordered dragHeader = new StackPane(); dragHeader.setVisible(false); @@ -264,6 +258,14 @@ public TableHeaderRow(final TableViewSkinBase skin) { } cornerRegion.setOnMousePressed(me -> { + if (columnPopupMenu == null) { + columnPopupMenu = new ContextMenu(); + columnMenuItems = new HashMap<>(); + + TableSkinUtils.getVisibleLeafColumns(skin).addListener(weakTableColumnsListener); + TableSkinUtils.getColumns(tableSkin).addListener(weakTableColumnsListener); + updateTableColumnListeners(TableSkinUtils.getColumns(tableSkin), List.of()); + } // show a popupMenu which lists all columns columnPopupMenu.show(cornerRegion, Side.BOTTOM, 0, 0); me.consume(); @@ -696,4 +698,12 @@ private void updateCornerPadding() { cornerPadding.set(padding); } + // testing only + Pane getCornerRegion() { + return cornerRegion; + } + + ContextMenu getColumnPopupMenu() { + return columnPopupMenu; + } } diff --git a/modules/javafx.controls/src/shims/java/javafx/scene/control/skin/TableHeaderRowShim.java b/modules/javafx.controls/src/shims/java/javafx/scene/control/skin/TableHeaderRowShim.java index 079e840a75d..204df83dfd4 100644 --- a/modules/javafx.controls/src/shims/java/javafx/scene/control/skin/TableHeaderRowShim.java +++ b/modules/javafx.controls/src/shims/java/javafx/scene/control/skin/TableHeaderRowShim.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023 Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,8 @@ */ package javafx.scene.control.skin; +import javafx.scene.control.ContextMenu; +import javafx.scene.layout.Pane; import javafx.scene.control.TableColumnBase; public class TableHeaderRowShim { @@ -32,4 +34,11 @@ public static TableColumnHeader getColumnHeaderFor(TableHeaderRow tr, final Tabl return tr.getColumnHeaderFor(col); } + public static ContextMenu getColumnPopupMenu(TableHeaderRow tableHeaderRow) { + return tableHeaderRow.getColumnPopupMenu(); + } + + public static Pane getCornerRegion(TableHeaderRow tableHeaderRow) { + return tableHeaderRow.getCornerRegion(); + } } diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/TableViewTableHeaderRowTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/TableViewTableHeaderRowTest.java new file mode 100644 index 00000000000..dea216db382 --- /dev/null +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/TableViewTableHeaderRowTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package test.javafx.scene.control.skin; + +import javafx.beans.property.SimpleBooleanProperty; +import javafx.scene.control.CheckMenuItem; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.skin.TableHeaderRow; +import javafx.scene.control.skin.TableHeaderRowShim; +import javafx.scene.layout.Pane; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import test.com.sun.javafx.scene.control.infrastructure.MouseEventFirer; +import test.com.sun.javafx.scene.control.infrastructure.StageLoader; +import test.com.sun.javafx.scene.control.infrastructure.VirtualFlowTestUtils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests for the {@link TableHeaderRow} of a {@link TableView}. + */ +public class TableViewTableHeaderRowTest { + + private TableView tableView; + private TableHeaderRow tableHeaderRow; + private Pane cornerRegion; + + private StageLoader stageLoader; + + @BeforeEach + void setup() { + tableView = new TableView<>(); + tableView.setTableMenuButtonVisible(true); + + for (int index = 0; index < 10; index++) { + tableView.getColumns().addAll(new TableColumn("Column" + index)); + } + + stageLoader = new StageLoader(tableView); + + tableHeaderRow = VirtualFlowTestUtils.getTableHeaderRow(tableView); + cornerRegion = TableHeaderRowShim.getCornerRegion(tableHeaderRow); + } + + @AfterEach + void cleanup() { + if (stageLoader != null) { + stageLoader.dispose(); + } + } + + @Test + void testTableMenuButtonVisibility() { + assertTrue(cornerRegion.isVisible()); + + tableView.setTableMenuButtonVisible(false); + + assertFalse(cornerRegion.isVisible()); + } + + @Test + void testColumnPopupMenuInitializing() { + ContextMenu columnPopupMenu = TableHeaderRowShim.getColumnPopupMenu(tableHeaderRow); + assertNull(columnPopupMenu); + + MouseEventFirer mouseEventFirer = new MouseEventFirer(cornerRegion); + mouseEventFirer.fireMousePressed(); + + columnPopupMenu = TableHeaderRowShim.getColumnPopupMenu(tableHeaderRow); + assertNotNull(columnPopupMenu); + + assertEquals(tableView.getColumns().size(), columnPopupMenu.getItems().size()); + + for (int index = 0; index < 10; index++) { + String columnText = tableView.getColumns().get(index).getText(); + String columnPopupItemText = columnPopupMenu.getItems().get(index).getText(); + assertEquals(columnText, columnPopupItemText); + } + } + + @Test + void testColumnPopupMenuColumnTextChanged() { + MouseEventFirer mouseEventFirer = new MouseEventFirer(cornerRegion); + mouseEventFirer.fireMousePressed(); + + ContextMenu columnPopupMenu = TableHeaderRowShim.getColumnPopupMenu(tableHeaderRow); + + TableColumn firstColumn = tableView.getColumns().get(0); + MenuItem firstMenuItem = columnPopupMenu.getItems().get(0); + assertEquals(firstColumn.getText(), firstMenuItem.getText()); + + String newColumnText = "MyNewColumnText"; + assertNotEquals(newColumnText, firstMenuItem.getText()); + + firstColumn.setText(newColumnText); + assertEquals(newColumnText, firstMenuItem.getText()); + } + + @Test + void testColumnPopupMenuColumnVisibility() { + MouseEventFirer mouseEventFirer = new MouseEventFirer(cornerRegion); + mouseEventFirer.fireMousePressed(); + + ContextMenu columnPopupMenu = TableHeaderRowShim.getColumnPopupMenu(tableHeaderRow); + + TableColumn firstColumn = tableView.getColumns().get(0); + CheckMenuItem firstMenuItem = (CheckMenuItem) columnPopupMenu.getItems().get(0); + assertEquals(firstColumn.isVisible(), firstMenuItem.isSelected()); + assertTrue(firstMenuItem.isSelected()); + + firstColumn.setVisible(false); + + assertEquals(firstColumn.isVisible(), firstMenuItem.isSelected()); + assertFalse(firstMenuItem.isSelected()); + + firstMenuItem.setSelected(true); + + assertEquals(firstColumn.isVisible(), firstMenuItem.isSelected()); + assertTrue(firstColumn.isVisible()); + } + + @Test + void testColumnPopupMenuColumnVisibilityBound() { + MouseEventFirer mouseEventFirer = new MouseEventFirer(cornerRegion); + mouseEventFirer.fireMousePressed(); + + ContextMenu columnPopupMenu = TableHeaderRowShim.getColumnPopupMenu(tableHeaderRow); + + TableColumn firstColumn = tableView.getColumns().get(0); + MenuItem firstMenuItem = columnPopupMenu.getItems().get(0); + assertFalse(firstMenuItem.isDisable()); + + SimpleBooleanProperty visibilityBinding = new SimpleBooleanProperty(false); + firstColumn.visibleProperty().bind(visibilityBinding); + + // Add a column to trigger the column popup menu rebuild. + tableView.getColumns().add(new TableColumn<>("new")); + + firstMenuItem = columnPopupMenu.getItems().get(0); + assertTrue(firstMenuItem.isDisable()); + } + + @Test + void testColumnPopupMenuColumnsAdded() { + MouseEventFirer mouseEventFirer = new MouseEventFirer(cornerRegion); + mouseEventFirer.fireMousePressed(); + + ContextMenu columnPopupMenu = TableHeaderRowShim.getColumnPopupMenu(tableHeaderRow); + + int itemSize = columnPopupMenu.getItems().size(); + assertEquals(tableView.getColumns().size(), columnPopupMenu.getItems().size()); + + tableView.getColumns().addAll(new TableColumn<>("new1"), new TableColumn<>("new2"), new TableColumn<>("new3")); + + assertEquals(tableView.getColumns().size(), columnPopupMenu.getItems().size()); + assertTrue(columnPopupMenu.getItems().size() > itemSize); + } + + @Test + void testColumnPopupMenuColumnsRemoved() { + MouseEventFirer mouseEventFirer = new MouseEventFirer(cornerRegion); + mouseEventFirer.fireMousePressed(); + + ContextMenu columnPopupMenu = TableHeaderRowShim.getColumnPopupMenu(tableHeaderRow); + + int itemSize = columnPopupMenu.getItems().size(); + assertEquals(tableView.getColumns().size(), columnPopupMenu.getItems().size()); + + tableView.getColumns().remove(tableView.getColumns().size() - 3, tableView.getColumns().size()); + + assertEquals(tableView.getColumns().size(), columnPopupMenu.getItems().size()); + assertTrue(columnPopupMenu.getItems().size() < itemSize); + } + +} diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/TreeTableViewTableHeaderRowTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/TreeTableViewTableHeaderRowTest.java new file mode 100644 index 00000000000..c28e58b3560 --- /dev/null +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/TreeTableViewTableHeaderRowTest.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package test.javafx.scene.control.skin; + +import javafx.beans.property.SimpleBooleanProperty; +import javafx.scene.control.CheckMenuItem; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TreeTableColumn; +import javafx.scene.control.TreeTableView; +import javafx.scene.control.skin.TableHeaderRow; +import javafx.scene.control.skin.TableHeaderRowShim; +import javafx.scene.layout.Pane; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import test.com.sun.javafx.scene.control.infrastructure.MouseEventFirer; +import test.com.sun.javafx.scene.control.infrastructure.StageLoader; +import test.com.sun.javafx.scene.control.infrastructure.VirtualFlowTestUtils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests for the {@link TableHeaderRow} of a {@link TreeTableView}. + */ +public class TreeTableViewTableHeaderRowTest { + + private TreeTableView treeTableView; + private TableHeaderRow tableHeaderRow; + private Pane cornerRegion; + + private StageLoader stageLoader; + + @BeforeEach + void setup() { + treeTableView = new TreeTableView<>(); + treeTableView.setTableMenuButtonVisible(true); + + for (int index = 0; index < 10; index++) { + treeTableView.getColumns().addAll(new TreeTableColumn("Column" + index)); + } + + stageLoader = new StageLoader(treeTableView); + + tableHeaderRow = VirtualFlowTestUtils.getTableHeaderRow(treeTableView); + cornerRegion = TableHeaderRowShim.getCornerRegion(tableHeaderRow); + } + + @AfterEach + void cleanup() { + if (stageLoader != null) { + stageLoader.dispose(); + } + } + + @Test + void testTableMenuButtonVisibility() { + assertTrue(cornerRegion.isVisible()); + + treeTableView.setTableMenuButtonVisible(false); + + assertFalse(cornerRegion.isVisible()); + } + + @Test + void testColumnPopupMenuInitializing() { + ContextMenu columnPopupMenu = TableHeaderRowShim.getColumnPopupMenu(tableHeaderRow); + assertNull(columnPopupMenu); + + MouseEventFirer mouseEventFirer = new MouseEventFirer(cornerRegion); + mouseEventFirer.fireMousePressed(); + + columnPopupMenu = TableHeaderRowShim.getColumnPopupMenu(tableHeaderRow); + assertNotNull(columnPopupMenu); + + assertEquals(treeTableView.getColumns().size(), columnPopupMenu.getItems().size()); + + for (int index = 0; index < 10; index++) { + String columnText = treeTableView.getColumns().get(index).getText(); + String columnPopupItemText = columnPopupMenu.getItems().get(index).getText(); + assertEquals(columnText, columnPopupItemText); + } + } + + @Test + void testColumnPopupMenuColumnTextChanged() { + MouseEventFirer mouseEventFirer = new MouseEventFirer(cornerRegion); + mouseEventFirer.fireMousePressed(); + + ContextMenu columnPopupMenu = TableHeaderRowShim.getColumnPopupMenu(tableHeaderRow); + + TreeTableColumn firstColumn = treeTableView.getColumns().get(0); + MenuItem firstMenuItem = columnPopupMenu.getItems().get(0); + assertEquals(firstColumn.getText(), firstMenuItem.getText()); + + String newColumnText = "MyNewColumnText"; + assertNotEquals(newColumnText, firstMenuItem.getText()); + + firstColumn.setText(newColumnText); + assertEquals(newColumnText, firstMenuItem.getText()); + } + + @Test + void testColumnPopupMenuColumnVisibility() { + MouseEventFirer mouseEventFirer = new MouseEventFirer(cornerRegion); + mouseEventFirer.fireMousePressed(); + + ContextMenu columnPopupMenu = TableHeaderRowShim.getColumnPopupMenu(tableHeaderRow); + + TreeTableColumn firstColumn = treeTableView.getColumns().get(0); + CheckMenuItem firstMenuItem = (CheckMenuItem) columnPopupMenu.getItems().get(0); + assertEquals(firstColumn.isVisible(), firstMenuItem.isSelected()); + assertTrue(firstMenuItem.isSelected()); + + firstColumn.setVisible(false); + + assertEquals(firstColumn.isVisible(), firstMenuItem.isSelected()); + assertFalse(firstMenuItem.isSelected()); + + firstMenuItem.setSelected(true); + + assertEquals(firstColumn.isVisible(), firstMenuItem.isSelected()); + assertTrue(firstColumn.isVisible()); + } + + @Test + void testColumnPopupMenuColumnVisibilityBound() { + MouseEventFirer mouseEventFirer = new MouseEventFirer(cornerRegion); + mouseEventFirer.fireMousePressed(); + + ContextMenu columnPopupMenu = TableHeaderRowShim.getColumnPopupMenu(tableHeaderRow); + + TreeTableColumn firstColumn = treeTableView.getColumns().get(0); + MenuItem firstMenuItem = columnPopupMenu.getItems().get(0); + assertFalse(firstMenuItem.isDisable()); + + SimpleBooleanProperty visibilityBinding = new SimpleBooleanProperty(false); + firstColumn.visibleProperty().bind(visibilityBinding); + + // Add a column to trigger the column popup menu rebuild. + treeTableView.getColumns().add(new TreeTableColumn<>("new")); + + firstMenuItem = columnPopupMenu.getItems().get(0); + assertTrue(firstMenuItem.isDisable()); + } + + @Test + void testColumnPopupMenuColumnsAdded() { + MouseEventFirer mouseEventFirer = new MouseEventFirer(cornerRegion); + mouseEventFirer.fireMousePressed(); + + ContextMenu columnPopupMenu = TableHeaderRowShim.getColumnPopupMenu(tableHeaderRow); + + int itemSize = columnPopupMenu.getItems().size(); + assertEquals(treeTableView.getColumns().size(), columnPopupMenu.getItems().size()); + + treeTableView.getColumns().addAll(new TreeTableColumn<>("new1"), new TreeTableColumn<>("new2"), + new TreeTableColumn<>("new3")); + + assertEquals(treeTableView.getColumns().size(), columnPopupMenu.getItems().size()); + assertTrue(columnPopupMenu.getItems().size() > itemSize); + } + + @Test + void testColumnPopupMenuColumnsRemoved() { + MouseEventFirer mouseEventFirer = new MouseEventFirer(cornerRegion); + mouseEventFirer.fireMousePressed(); + + ContextMenu columnPopupMenu = TableHeaderRowShim.getColumnPopupMenu(tableHeaderRow); + + int itemSize = columnPopupMenu.getItems().size(); + assertEquals(treeTableView.getColumns().size(), columnPopupMenu.getItems().size()); + + treeTableView.getColumns().remove(treeTableView.getColumns().size() - 3, treeTableView.getColumns().size()); + + assertEquals(treeTableView.getColumns().size(), columnPopupMenu.getItems().size()); + assertTrue(columnPopupMenu.getItems().size() < itemSize); + } + +}