diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ContextMenuContent.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ContextMenuContent.java index b281cdd84a9..d65e6f552c5 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ContextMenuContent.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ContextMenuContent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, 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 @@ -54,6 +54,7 @@ import javafx.scene.AccessibleRole; import javafx.scene.Node; import javafx.scene.Parent; +import javafx.scene.TraversalDirection; import javafx.scene.control.CheckMenuItem; import javafx.scene.control.ContextMenu; import javafx.scene.control.CustomMenuItem; @@ -75,7 +76,7 @@ import com.sun.javafx.scene.NodeHelper; import com.sun.javafx.scene.control.behavior.TwoLevelFocusPopupBehavior; import com.sun.javafx.scene.control.skin.Utils; -import com.sun.javafx.scene.traversal.Direction; +import com.sun.javafx.scene.traversal.TraversalUtils; /** * This is a the SkinBase for ContextMenu based controls so that the CSS parts @@ -540,12 +541,12 @@ private void initialize() { break; case DOWN: // move to the next sibling - move(Direction.NEXT); + move(TraversalDirection.NEXT); ke.consume(); break; case UP: // move to previous sibling - move(Direction.PREVIOUS); + move(TraversalDirection.PREVIOUS); ke.consume(); break; case SPACE: @@ -707,23 +708,23 @@ private void selectMenuItem() { }); } - private void move(Direction dir) { + private void move(TraversalDirection dir) { int startIndex = currentFocusedIndex != -1 ? currentFocusedIndex : itemsContainer.getChildren().size(); requestFocusOnIndex(findSibling(dir, startIndex)); } - private int findSibling(final Direction dir, final int startIndex) { + private int findSibling(final TraversalDirection dir, final int startIndex) { final int childCount = itemsContainer.getChildren().size(); int i = startIndex; do { - if (dir.isForward() && i >= childCount - 1) { + if (TraversalUtils.isForward(dir) && i >= childCount - 1) { // loop to zero i = 0; - } else if (!dir.isForward() && i == 0) { + } else if (!TraversalUtils.isForward(dir) && i == 0) { // loop to end i = childCount - 1; } else { - i += (dir.isForward() ? 1 : -1); + i += (TraversalUtils.isForward(dir) ? 1 : -1); } Node n = itemsContainer.getChildren().get(i); diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/DatePickerContent.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/DatePickerContent.java index c9dc6b2eebe..9ef498bfd4d 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/DatePickerContent.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/DatePickerContent.java @@ -25,24 +25,26 @@ package com.sun.javafx.scene.control; +import static com.sun.javafx.PlatformUtil.isMac; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.MONTHS; +import static java.time.temporal.ChronoUnit.WEEKS; +import static java.time.temporal.ChronoUnit.YEARS; import java.time.DateTimeException; import java.time.LocalDate; +import java.time.YearMonth; +import java.time.chrono.ChronoLocalDate; +import java.time.chrono.Chronology; import java.time.format.DateTimeFormatter; import java.time.format.DecimalStyle; -import java.time.chrono.Chronology; -import java.time.chrono.ChronoLocalDate; import java.time.temporal.ChronoUnit; import java.time.temporal.ValueRange; import java.time.temporal.WeekFields; -import java.time.YearMonth; import java.util.ArrayList; import java.util.List; import java.util.Locale; - -import static java.time.temporal.ChronoField.*; -import static java.time.temporal.ChronoUnit.*; - -import com.sun.javafx.scene.control.skin.*; import javafx.application.Platform; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -50,9 +52,10 @@ import javafx.event.EventHandler; import javafx.scene.Node; import javafx.scene.Scene; +import javafx.scene.TraversalDirection; import javafx.scene.control.Button; -import javafx.scene.control.DatePicker; import javafx.scene.control.DateCell; +import javafx.scene.control.DatePicker; import javafx.scene.control.Label; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseButton; @@ -61,15 +64,11 @@ import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; -import javafx.scene.layout.VBox; import javafx.scene.layout.StackPane; - +import javafx.scene.layout.VBox; +import com.sun.javafx.scene.control.skin.Utils; import com.sun.javafx.scene.control.skin.resources.ControlResources; -import com.sun.javafx.scene.traversal.Direction; -import com.sun.javafx.scene.traversal.TraversalMethod; - -import static com.sun.javafx.PlatformUtil.*; -import com.sun.javafx.scene.NodeHelper; +import com.sun.javafx.scene.traversal.TraversalUtils; /** * The full content for the DatePicker popup. This class could @@ -171,7 +170,7 @@ public DatePickerContent(final DatePicker datePicker) { if (newFocusOwner == gridPane) { if (oldFocusOwner instanceof DateCell) { // Backwards traversal, skip gridPane. - NodeHelper.traverse(gridPane, Direction.PREVIOUS, TraversalMethod.DEFAULT); + TraversalUtils.traverse(gridPane, TraversalDirection.PREVIOUS, false); } else { // Forwards traversal, pass focus to day cell. if (lastFocusedDayCell != null) { diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index 1575a3a14c7..39e1fe52444 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, 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 @@ -45,6 +45,10 @@ import javafx.event.EventHandler; import javafx.event.EventTarget; import javafx.event.EventType; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; import javafx.scene.control.SkinBase; /** @@ -444,4 +448,60 @@ public void handle(T ev) { } } } + + /** + * Adds a disonnectable machinery to listen to {@code control}'s Scene and Scene.focusOwner properties, + * for the purpose of invoking the supplied callback when focused node is in the {@code control}'s hierarchy + * or is the control itself. + * + * @param control the control + * @param callback the callback + */ + public void addSceneFocusOwnerListener(Control control, Consumer callback) { + items.add(new ChLi() { + private ChangeListener focusListener = (s, p, n) -> { + if (isParent(control, n)) { + callback.accept(n); + } + }; + + { + control.sceneProperty().addListener(this); + Scene scene = control.getScene(); + if (scene != null) { + scene.focusOwnerProperty().addListener(focusListener); + } + } + + private static boolean isParent(Node parent, Node n) { + while (n != null) { + if (parent == n) { + return true; + } + n = n.getParent(); + } + return false; + } + + @Override + public void disconnect() { + control.sceneProperty().removeListener(this); + Scene scene = control.getScene(); + if (scene != null) { + scene.focusOwnerProperty().removeListener(focusListener); + } + focusListener = null; + } + + @Override + public void changed(ObservableValue p, Scene old, Scene scene) { + if (old != null) { + old.focusOwnerProperty().removeListener(focusListener); + } + if (scene != null) { + scene.focusOwnerProperty().addListener(focusListener); + } + } + }); + } } diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/DateCellBehavior.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/DateCellBehavior.java index a5c1fbe70c1..4584ed4c295 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/DateCellBehavior.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/DateCellBehavior.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, 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 @@ -25,23 +25,20 @@ package com.sun.javafx.scene.control.behavior; -import com.sun.javafx.scene.traversal.Direction; -import javafx.geometry.NodeOrientation; -import javafx.scene.Node; -import javafx.scene.control.DateCell; - -import com.sun.javafx.scene.control.DatePickerContent; -import com.sun.javafx.scene.control.inputmap.InputMap; - -import java.time.temporal.ChronoUnit; - import static javafx.scene.input.KeyCode.DOWN; import static javafx.scene.input.KeyCode.ENTER; import static javafx.scene.input.KeyCode.LEFT; import static javafx.scene.input.KeyCode.RIGHT; import static javafx.scene.input.KeyCode.SPACE; import static javafx.scene.input.KeyCode.UP; -import static javafx.scene.input.KeyEvent.*; +import static javafx.scene.input.KeyEvent.KEY_RELEASED; +import java.time.temporal.ChronoUnit; +import javafx.geometry.NodeOrientation; +import javafx.scene.Node; +import javafx.scene.TraversalDirection; +import javafx.scene.control.DateCell; +import com.sun.javafx.scene.control.DatePickerContent; +import com.sun.javafx.scene.control.inputmap.InputMap; /** * Behaviors for LocalDate based cells types. Simply defines methods @@ -57,10 +54,10 @@ public DateCellBehavior(DateCell dateCell) { inputMap = createInputMap(); addDefaultMapping(inputMap, - new InputMap.KeyMapping(UP, e -> traverse(dateCell, Direction.UP)), - new InputMap.KeyMapping(DOWN, e -> traverse(dateCell, Direction.DOWN)), - new InputMap.KeyMapping(LEFT, e -> traverse(dateCell, Direction.LEFT)), - new InputMap.KeyMapping(RIGHT, e -> traverse(dateCell, Direction.RIGHT)), + new InputMap.KeyMapping(UP, e -> traverse(dateCell, TraversalDirection.UP)), + new InputMap.KeyMapping(DOWN, e -> traverse(dateCell, TraversalDirection.DOWN)), + new InputMap.KeyMapping(LEFT, e -> traverse(dateCell, TraversalDirection.LEFT)), + new InputMap.KeyMapping(RIGHT, e -> traverse(dateCell, TraversalDirection.RIGHT)), new InputMap.KeyMapping(ENTER, KEY_RELEASED, e -> selectDate()), new InputMap.KeyMapping(SPACE, KEY_RELEASED, e -> selectDate()) ); @@ -76,7 +73,7 @@ private void selectDate() { dpc.selectDayCell(cell); } - public void traverse(final DateCell cell, final Direction dir) { + public void traverse(final DateCell cell, final TraversalDirection dir) { boolean rtl = (cell.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT); DatePickerContent dpc = findDatePickerContent(cell); if (dpc != null) { diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/FocusTraversalInputMap.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/FocusTraversalInputMap.java index 903c43d59fb..26124640573 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/FocusTraversalInputMap.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/FocusTraversalInputMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, 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,23 +24,20 @@ */ package com.sun.javafx.scene.control.behavior; -import com.sun.javafx.scene.NodeHelper; -import com.sun.javafx.scene.traversal.Direction; -import com.sun.javafx.scene.traversal.TraversalMethod; -import javafx.event.EventTarget; -import javafx.scene.Node; -import com.sun.javafx.scene.control.inputmap.InputMap; -import com.sun.javafx.scene.control.inputmap.KeyBinding; -import javafx.scene.input.KeyEvent; - -import java.util.List; - -import static com.sun.javafx.scene.control.inputmap.InputMap.*; import static javafx.scene.input.KeyCode.DOWN; import static javafx.scene.input.KeyCode.LEFT; import static javafx.scene.input.KeyCode.RIGHT; import static javafx.scene.input.KeyCode.TAB; import static javafx.scene.input.KeyCode.UP; +import java.util.List; +import javafx.event.EventTarget; +import javafx.scene.Node; +import javafx.scene.TraversalDirection; +import javafx.scene.input.KeyEvent; +import com.sun.javafx.scene.control.inputmap.InputMap; +import com.sun.javafx.scene.control.inputmap.InputMap.KeyMapping; +import com.sun.javafx.scene.control.inputmap.KeyBinding; +import com.sun.javafx.scene.traversal.TraversalUtils; public class FocusTraversalInputMap { @@ -87,14 +84,14 @@ public static InputMap createInputMap(N node) { * * @param node The node to traverse on * @param dir The direction to traverse - * @param method The focus traversal method + * @param focusVisible whether the focused Node should visibly indicate focus */ - public static void traverse(final Node node, final Direction dir, TraversalMethod method) { + public static void traverse(final Node node, final TraversalDirection dir, boolean focusVisible) { if (node == null) { throw new IllegalArgumentException("Attempting to traverse on a null Node. " + "Most probably a KeyEvent has been fired with a null target specified."); } - NodeHelper.traverse(node, dir, method); + TraversalUtils.traverse(node, dir, focusVisible); } /** @@ -102,7 +99,7 @@ public static void traverse(final Node node, final Direction dir, TraversalMetho * go the next focusTraversable Node above the current one. */ public static final void traverseUp(KeyEvent e) { - traverse(getNode(e), com.sun.javafx.scene.traversal.Direction.UP, TraversalMethod.KEY); + traverse(getNode(e), TraversalDirection.UP, true); } /** @@ -110,7 +107,7 @@ public static final void traverseUp(KeyEvent e) { * go the next focusTraversable Node below the current one. */ public static final void traverseDown(KeyEvent e) { - traverse(getNode(e), com.sun.javafx.scene.traversal.Direction.DOWN, TraversalMethod.KEY); + traverse(getNode(e), TraversalDirection.DOWN, true); } /** @@ -118,7 +115,7 @@ public static final void traverseDown(KeyEvent e) { * go the next focusTraversable Node left of the current one. */ public static final void traverseLeft(KeyEvent e) { - traverse(getNode(e), com.sun.javafx.scene.traversal.Direction.LEFT, TraversalMethod.KEY); + traverse(getNode(e), TraversalDirection.LEFT, true); } /** @@ -126,7 +123,7 @@ public static final void traverseLeft(KeyEvent e) { * go the next focusTraversable Node right of the current one. */ public static final void traverseRight(KeyEvent e) { - traverse(getNode(e), com.sun.javafx.scene.traversal.Direction.RIGHT, TraversalMethod.KEY); + traverse(getNode(e), TraversalDirection.RIGHT, true); } /** @@ -134,7 +131,7 @@ public static final void traverseRight(KeyEvent e) { * go the next focusTraversable Node in the focus traversal cycle. */ public static final void traverseNext(KeyEvent e) { - traverse(getNode(e), com.sun.javafx.scene.traversal.Direction.NEXT, TraversalMethod.KEY); + traverse(getNode(e), TraversalDirection.NEXT, true); } /** @@ -142,7 +139,7 @@ public static final void traverseNext(KeyEvent e) { * go the previous focusTraversable Node in the focus traversal cycle. */ public static final void traversePrevious(KeyEvent e) { - traverse(getNode(e), com.sun.javafx.scene.traversal.Direction.PREVIOUS, TraversalMethod.KEY); + traverse(getNode(e), TraversalDirection.PREVIOUS, true); } private static Node getNode(KeyEvent e) { diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ToggleButtonBehavior.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ToggleButtonBehavior.java index a0404218798..58736c44f74 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ToggleButtonBehavior.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ToggleButtonBehavior.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, 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,20 +24,24 @@ */ package com.sun.javafx.scene.control.behavior; -import com.sun.javafx.scene.control.skin.Utils; +import static javafx.scene.input.KeyCode.DOWN; +import static javafx.scene.input.KeyCode.LEFT; +import static javafx.scene.input.KeyCode.RIGHT; +import static javafx.scene.input.KeyCode.UP; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.geometry.NodeOrientation; import javafx.scene.Node; +import javafx.scene.TraversalDirection; import javafx.scene.control.Control; import javafx.scene.control.Toggle; import javafx.scene.control.ToggleButton; import javafx.scene.control.ToggleGroup; -import com.sun.javafx.scene.control.inputmap.InputMap; import javafx.scene.input.KeyEvent; - -import static com.sun.javafx.scene.control.inputmap.InputMap.*; -import static javafx.scene.input.KeyCode.*; +import com.sun.javafx.scene.control.inputmap.InputMap; +import com.sun.javafx.scene.control.inputmap.InputMap.KeyMapping; +import com.sun.javafx.scene.control.inputmap.InputMap.Mapping; +import com.sun.javafx.scene.control.skin.Utils; public class ToggleButtonBehavior extends ButtonBehavior{ @@ -45,10 +49,10 @@ public ToggleButtonBehavior(C button) { super(button); ObservableList> mappings = FXCollections.observableArrayList( - new KeyMapping(RIGHT, e -> traverse(e, "ToggleNext-Right")), - new KeyMapping(LEFT, e -> traverse(e, "TogglePrevious-Left")), - new KeyMapping(DOWN, e -> traverse(e, "ToggleNext-Down")), - new KeyMapping(UP, e -> traverse(e, "TogglePrevious-Up")) + new KeyMapping(RIGHT, e -> traverse(e, TraversalDirection.RIGHT)), + new KeyMapping(LEFT, e -> traverse(e, TraversalDirection.LEFT)), + new KeyMapping(DOWN, e -> traverse(e, TraversalDirection.DOWN)), + new KeyMapping(UP, e -> traverse(e, TraversalDirection.UP)) ); // we disable auto-consuming, so that unconsumed events work their way @@ -91,7 +95,7 @@ private int previousToggleIndex(final ObservableList toggles, final int return i; } - private void traverse(KeyEvent e, String name) { + private final void traverse(KeyEvent e, TraversalDirection dir) { ToggleButton toggleButton = getNode(); final ToggleGroup toggleGroup = toggleButton.getToggleGroup(); // A ToggleButton does not have to be in a group. @@ -102,7 +106,7 @@ private void traverse(KeyEvent e, String name) { ObservableList toggles = toggleGroup.getToggles(); final int currentToggleIdx = toggles.indexOf(toggleButton); - boolean traversingToNext = traversingToNext(name, toggleButton.getEffectiveNodeOrientation()); + boolean traversingToNext = traversingToNext(dir, toggleButton.getEffectiveNodeOrientation()); if (Utils.isTwoLevelFocus()) { // Because we don't auto-consume (see mapping definitions above), we // can simply return here and have the traversal handled by another @@ -134,16 +138,16 @@ private void traverse(KeyEvent e, String name) { } } - private boolean traversingToNext(String name, NodeOrientation effectiveNodeOrientation) { + private boolean traversingToNext(TraversalDirection dir, NodeOrientation effectiveNodeOrientation) { boolean rtl = effectiveNodeOrientation == NodeOrientation.RIGHT_TO_LEFT; - switch (name) { - case "ToggleNext-Right": + switch (dir) { + case RIGHT: return rtl ? false : true; - case "ToggleNext-Down": + case DOWN: return true; - case "TogglePrevious-Left": + case LEFT: return rtl ? true : false; - case "TogglePrevious-Up": + case UP: return false; default: throw new IllegalArgumentException("Not a toggle action"); diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TwoLevelFocusBehavior.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TwoLevelFocusBehavior.java index d84b9141cbd..7a123f5a05a 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TwoLevelFocusBehavior.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TwoLevelFocusBehavior.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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 @@ -25,21 +25,19 @@ package com.sun.javafx.scene.control.behavior; -import com.sun.javafx.scene.NodeHelper; -import com.sun.javafx.scene.traversal.TraversalMethod; +import javafx.beans.value.ChangeListener; import javafx.css.PseudoClass; +import javafx.event.Event; +import javafx.event.EventDispatcher; +import javafx.event.EventHandler; import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.TraversalDirection; import javafx.scene.control.Control; import javafx.scene.control.PopupControl; - -import javafx.scene.Scene; import javafx.scene.input.KeyEvent; - -import javafx.beans.value.ChangeListener; -import javafx.event.Event; -import javafx.event.EventDispatcher; -import javafx.event.EventHandler; import javafx.scene.input.MouseEvent; +import com.sun.javafx.scene.traversal.TraversalUtils; /** * A two level focus handler allows a Control to behave as if it @@ -93,51 +91,53 @@ public void dispose() { final EventDispatcher preemptiveEventDispatcher = (event, tail) -> { // block the event from being passed down to children - if (event instanceof KeyEvent && event.getEventType() == KeyEvent.KEY_PRESSED) { - if (!((KeyEvent)event).isMetaDown() && !((KeyEvent)event).isControlDown() && !((KeyEvent)event).isAltDown()) { + if (event instanceof KeyEvent ev && event.getEventType() == KeyEvent.KEY_PRESSED) { + if ( + !ev.isMetaDown() && + !ev.isControlDown() && + !ev.isAltDown() + ) { if (isExternalFocus()) { // // don't let the behaviour leak any navigation keys when // we're not in blocking mode.... // Object obj = event.getTarget(); - - switch (((KeyEvent)event).getCode()) { - case TAB : - if (((KeyEvent)event).isShiftDown()) { - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.PREVIOUS, TraversalMethod.KEY); - } - else { - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.NEXT, TraversalMethod.KEY); - } - event.consume(); - break; - case UP : - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.UP, TraversalMethod.KEY); - event.consume(); - break; - case DOWN : - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.DOWN, TraversalMethod.KEY); - event.consume(); - break; - case LEFT : - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.LEFT, TraversalMethod.KEY); - event.consume(); - break; - case RIGHT : - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.RIGHT, TraversalMethod.KEY); - event.consume(); - break; - case ENTER : - setExternalFocus(false); - event.consume(); - break; - default : - // this'll kill mnemonics.... unless! - Scene s = tlNode.getScene(); - Event.fireEvent(s, event); - event.consume(); - break; + switch (ev.getCode()) { + case TAB: + if (ev.isShiftDown()) { + TraversalUtils.traverse((Node)obj, TraversalDirection.PREVIOUS, true); + } else { + TraversalUtils.traverse((Node)obj, TraversalDirection.NEXT, true); + } + event.consume(); + break; + case UP: + TraversalUtils.traverse((Node)obj, TraversalDirection.UP, true); + event.consume(); + break; + case DOWN: + TraversalUtils.traverse((Node)obj, TraversalDirection.DOWN, true); + event.consume(); + break; + case LEFT: + TraversalUtils.traverse((Node)obj, TraversalDirection.LEFT, true); + event.consume(); + break; + case RIGHT: + TraversalUtils.traverse((Node)obj, TraversalDirection.RIGHT, true); + event.consume(); + break; + case ENTER: + setExternalFocus(false); + event.consume(); + break; + default: + // this'll kill mnemonics.... unless! + Scene s = tlNode.getScene(); + Event.fireEvent(s, event); + event.consume(); + break; } } } diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TwoLevelFocusComboBehavior.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TwoLevelFocusComboBehavior.java index ea7750c07c2..f7a7f737bcd 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TwoLevelFocusComboBehavior.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TwoLevelFocusComboBehavior.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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 @@ -25,19 +25,16 @@ package com.sun.javafx.scene.control.behavior; -import com.sun.javafx.scene.NodeHelper; -import com.sun.javafx.scene.traversal.TraversalMethod; -import javafx.scene.Node; - -import javafx.scene.Scene; -import javafx.scene.input.KeyEvent; - import javafx.beans.value.ChangeListener; import javafx.event.Event; import javafx.event.EventDispatcher; import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.TraversalDirection; +import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseEvent; - +import com.sun.javafx.scene.traversal.TraversalUtils; public class TwoLevelFocusComboBehavior extends TwoLevelFocusBehavior { @@ -74,51 +71,53 @@ public void dispose() { final EventDispatcher preemptiveEventDispatcher = (event, tail) -> { // block the event from being passed down to children - if (event instanceof KeyEvent && event.getEventType() == KeyEvent.KEY_PRESSED) { - if (!((KeyEvent)event).isMetaDown() && !((KeyEvent)event).isControlDown() && !((KeyEvent)event).isAltDown()) { + if (event instanceof KeyEvent ev && event.getEventType() == KeyEvent.KEY_PRESSED) { + if ( + !ev.isMetaDown() && + !ev.isControlDown() && + !ev.isAltDown()) + { if (isExternalFocus()) { // // don't let the behaviour leak any navigation keys when // we're not in blocking mode.... // Object obj = event.getTarget(); - - switch (((KeyEvent)event).getCode()) { - case TAB : - if (((KeyEvent)event).isShiftDown()) { - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.PREVIOUS, TraversalMethod.KEY); - } - else { - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.NEXT, TraversalMethod.KEY); - } - event.consume(); - break; - case UP : - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.UP, TraversalMethod.KEY); - event.consume(); - break; - case DOWN : - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.DOWN, TraversalMethod.KEY); - event.consume(); - break; - case LEFT : - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.LEFT, TraversalMethod.KEY); - event.consume(); - break; - case RIGHT : - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.RIGHT, TraversalMethod.KEY); - event.consume(); - break; - case ENTER : - setExternalFocus(false); - origEventDispatcher.dispatchEvent(event, tail); - break; - default : - // this'll kill mnemonics.... unless! - Scene s = tlNode.getScene(); - Event.fireEvent(s, event); - event.consume(); - break; + switch (ev.getCode()) { + case TAB: + if (ev.isShiftDown()) { + TraversalUtils.traverse((Node)obj, TraversalDirection.PREVIOUS, true); + } else { + TraversalUtils.traverse((Node)obj, TraversalDirection.NEXT, true); + } + event.consume(); + break; + case UP: + TraversalUtils.traverse((Node)obj, TraversalDirection.UP, true); + event.consume(); + break; + case DOWN: + TraversalUtils.traverse((Node)obj, TraversalDirection.DOWN, true); + event.consume(); + break; + case LEFT: + TraversalUtils.traverse((Node)obj, TraversalDirection.LEFT, true); + event.consume(); + break; + case RIGHT: + TraversalUtils.traverse((Node)obj, TraversalDirection.RIGHT, true); + event.consume(); + break; + case ENTER: + setExternalFocus(false); + origEventDispatcher.dispatchEvent(event, tail); + break; + default: + // this'll kill mnemonics.... unless! + Scene s = tlNode.getScene(); + Event.fireEvent(s, event); + event.consume(); + break; } } } diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TwoLevelFocusListBehavior.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TwoLevelFocusListBehavior.java index 1249f59a3df..5db73561eeb 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TwoLevelFocusListBehavior.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TwoLevelFocusListBehavior.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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 @@ -25,20 +25,18 @@ package com.sun.javafx.scene.control.behavior; -import com.sun.javafx.scene.NodeHelper; -import com.sun.javafx.scene.control.Properties; -import com.sun.javafx.scene.traversal.TraversalMethod; -import javafx.scene.Node; -import javafx.scene.Parent; - -import javafx.scene.Scene; -import javafx.scene.input.KeyEvent; - import javafx.beans.value.ChangeListener; import javafx.event.Event; import javafx.event.EventDispatcher; import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.TraversalDirection; +import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseEvent; +import com.sun.javafx.scene.control.Properties; +import com.sun.javafx.scene.traversal.TraversalUtils; public class TwoLevelFocusListBehavior extends TwoLevelFocusBehavior { @@ -75,51 +73,53 @@ public void dispose() { final EventDispatcher preemptiveEventDispatcher = (event, tail) -> { // block the event from being passed down to children - if (event instanceof KeyEvent && event.getEventType() == KeyEvent.KEY_PRESSED) { - if (!((KeyEvent)event).isMetaDown() && !((KeyEvent)event).isControlDown() && !((KeyEvent)event).isAltDown()) { + if (event instanceof KeyEvent ev && event.getEventType() == KeyEvent.KEY_PRESSED) { + if ( + !ev.isMetaDown() && + !ev.isControlDown() && + !ev.isAltDown() + ) { if (isExternalFocus()) { // // don't let the behaviour leak any navigation keys when // we're not in blocking mode.... // Object obj = event.getTarget(); - switch (((KeyEvent)event).getCode()) { - case TAB : - if (((KeyEvent)event).isShiftDown()) { - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.PREVIOUS, TraversalMethod.KEY); - } - else { - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.NEXT, TraversalMethod.KEY); - } - event.consume(); - break; - case UP : - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.UP, TraversalMethod.KEY); - event.consume(); - break; - case DOWN : - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.DOWN, TraversalMethod.KEY); - event.consume(); - break; - case LEFT : - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.LEFT, TraversalMethod.KEY); - event.consume(); - break; - case RIGHT : - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.RIGHT, TraversalMethod.KEY); - event.consume(); - break; - case ENTER : - setExternalFocus(false); - event.consume(); - break; - default : - // this'll kill mnemonics.... unless! - Scene s = tlNode.getScene(); - Event.fireEvent(s, event); - event.consume(); - break; + case TAB: + if (ev.isShiftDown()) { + TraversalUtils.traverse((Node)obj, TraversalDirection.PREVIOUS, true); + } else { + TraversalUtils.traverse((Node)obj, TraversalDirection.NEXT, true); + } + event.consume(); + break; + case UP: + TraversalUtils.traverse((Node)obj, TraversalDirection.UP, true); + event.consume(); + break; + case DOWN: + TraversalUtils.traverse((Node)obj, TraversalDirection.DOWN, true); + event.consume(); + break; + case LEFT: + TraversalUtils.traverse((Node)obj, TraversalDirection.LEFT, true); + event.consume(); + break; + case RIGHT: + TraversalUtils.traverse((Node)obj, TraversalDirection.RIGHT, true); + event.consume(); + break; + case ENTER: + setExternalFocus(false); + event.consume(); + break; + default: + // this'll kill mnemonics.... unless! + Scene s = tlNode.getScene(); + Event.fireEvent(s, event); + event.consume(); + break; } } } diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TwoLevelFocusPopupBehavior.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TwoLevelFocusPopupBehavior.java index 484fafcf99b..7a188a193ed 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TwoLevelFocusPopupBehavior.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TwoLevelFocusPopupBehavior.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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 @@ -25,20 +25,17 @@ package com.sun.javafx.scene.control.behavior; -import com.sun.javafx.scene.NodeHelper; -import com.sun.javafx.scene.traversal.TraversalMethod; -import javafx.scene.Node; -import javafx.scene.control.PopupControl; - -import javafx.scene.Scene; -import javafx.scene.input.KeyEvent; - import javafx.beans.value.ChangeListener; import javafx.event.Event; import javafx.event.EventDispatcher; import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.TraversalDirection; +import javafx.scene.control.PopupControl; +import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseEvent; - +import com.sun.javafx.scene.traversal.TraversalUtils; public class TwoLevelFocusPopupBehavior extends TwoLevelFocusBehavior { @@ -95,51 +92,53 @@ public void dispose() { final EventDispatcher preemptiveEventDispatcher = (event, tail) -> { // block the event from being passed down to children - if (event instanceof KeyEvent && event.getEventType() == KeyEvent.KEY_PRESSED) { - if (!((KeyEvent)event).isMetaDown() && !((KeyEvent)event).isControlDown() && !((KeyEvent)event).isAltDown()) { + if (event instanceof KeyEvent ev && event.getEventType() == KeyEvent.KEY_PRESSED) { + if ( + !ev.isMetaDown() && + !ev.isControlDown() && + !ev.isAltDown()) + { if (isExternalFocus()) { // // don't let the behaviour leak any navigation keys when // we're not in blocking mode.... // Object obj = event.getTarget(); - - switch (((KeyEvent)event).getCode()) { - case TAB : - if (((KeyEvent)event).isShiftDown()) { - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.PREVIOUS, TraversalMethod.KEY); - } - else { - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.NEXT, TraversalMethod.KEY); - } - event.consume(); - break; - case UP : - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.UP, TraversalMethod.KEY); - event.consume(); - break; - case DOWN : - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.DOWN, TraversalMethod.KEY); - event.consume(); - break; - case LEFT : - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.LEFT, TraversalMethod.KEY); - event.consume(); - break; - case RIGHT : - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.RIGHT, TraversalMethod.KEY); - event.consume(); - break; - case ENTER : - setExternalFocus(false); - event.consume(); - break; - default : - // this'll kill mnemonics.... unless! - Scene s = tlNode.getScene(); - Event.fireEvent(s, event); - event.consume(); - break; + switch (ev.getCode()) { + case TAB: + if (ev.isShiftDown()) { + TraversalUtils.traverse((Node)obj, TraversalDirection.PREVIOUS, true); + } else { + TraversalUtils.traverse((Node)obj, TraversalDirection.NEXT, true); + } + event.consume(); + break; + case UP: + TraversalUtils.traverse((Node)obj, TraversalDirection.UP, true); + event.consume(); + break; + case DOWN: + TraversalUtils.traverse((Node)obj, TraversalDirection.DOWN, true); + event.consume(); + break; + case LEFT: + TraversalUtils.traverse((Node)obj, TraversalDirection.LEFT, true); + event.consume(); + break; + case RIGHT: + TraversalUtils.traverse((Node)obj, TraversalDirection.RIGHT, true); + event.consume(); + break; + case ENTER: + setExternalFocus(false); + event.consume(); + break; + default: + // this'll kill mnemonics.... unless! + Scene s = tlNode.getScene(); + Event.fireEvent(s, event); + event.consume(); + break; } } } @@ -149,8 +148,12 @@ public void dispose() { final EventDispatcher preemptivePopupEventDispatcher = (event, tail) -> { // block the event from being passed down to children - if (event instanceof KeyEvent && event.getEventType() == KeyEvent.KEY_PRESSED) { - if (!((KeyEvent)event).isMetaDown() && !((KeyEvent)event).isControlDown() && !((KeyEvent)event).isAltDown()) { + if (event instanceof KeyEvent ev && event.getEventType() == KeyEvent.KEY_PRESSED) { + if ( + !ev.isMetaDown() && + !ev.isControlDown() && + !ev.isAltDown()) + { if (!isExternalFocus()) { // // don't let the behaviour leak any navigation keys when @@ -158,49 +161,47 @@ public void dispose() { // Object obj = event.getTarget(); switch (((KeyEvent)event).getCode()) { - case TAB : - case ENTER : - event.consume(); - break; - case UP : - case DOWN : - break; - case LEFT : - if (obj instanceof Node) { - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.LEFT, TraversalMethod.KEY); - event.consume(); - } - else if (obj instanceof Scene) { - Node node = ((Scene)obj).getFocusOwner(); - if (node != null) { - NodeHelper.traverse(node, com.sun.javafx.scene.traversal.Direction.LEFT, TraversalMethod.KEY); - event.consume(); - } - } - break; - case RIGHT : - if (obj instanceof Node) { - NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.RIGHT, TraversalMethod.KEY); - event.consume(); - } - else if (obj instanceof Scene) { - Node node = ((Scene)obj).getFocusOwner(); - if (node != null) { - NodeHelper.traverse(node, com.sun.javafx.scene.traversal.Direction.RIGHT, TraversalMethod.KEY); - event.consume(); - } - } - break; - - default : - // this'll kill mnemonics.... unless! - Scene s = null; - if (tlNode != null) { - s = tlNode.getScene(); - Event.fireEvent(s, event); - } - event.consume(); - break; + case TAB: + case ENTER: + event.consume(); + break; + case UP: + case DOWN: + break; + case LEFT: + if (obj instanceof Node n) { + TraversalUtils.traverse((Node)obj, TraversalDirection.LEFT, true); + event.consume(); + } else if (obj instanceof Scene sc) { + Node node = sc.getFocusOwner(); + if (node != null) { + TraversalUtils.traverse((Node)obj, TraversalDirection.LEFT, true); + event.consume(); + } + } + break; + case RIGHT: + if (obj instanceof Node n) { + TraversalUtils.traverse((Node)obj, TraversalDirection.RIGHT, true); + event.consume(); + } else if (obj instanceof Scene sc) { + Node node = sc.getFocusOwner(); + if (node != null) { + TraversalUtils.traverse((Node)obj, TraversalDirection.RIGHT, true); + event.consume(); + } + } + break; + + default: + // this'll kill mnemonics.... unless! + Scene s = null; + if (tlNode != null) { + s = tlNode.getScene(); + Event.fireEvent(s, event); + } + event.consume(); + break; } } } diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/ToggleButton.java b/modules/javafx.controls/src/main/java/javafx/scene/control/ToggleButton.java index dfd7f559347..2fd18921822 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/ToggleButton.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/ToggleButton.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2024, 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 @@ -25,25 +25,21 @@ package javafx.scene.control; -import com.sun.javafx.scene.ParentHelper; -import com.sun.javafx.scene.traversal.ParentTraversalEngine; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanPropertyBase; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectPropertyBase; import javafx.beans.value.ChangeListener; import javafx.beans.value.WritableValue; +import javafx.css.PseudoClass; +import javafx.css.StyleableProperty; import javafx.event.ActionEvent; import javafx.geometry.Pos; import javafx.scene.AccessibleAttribute; import javafx.scene.AccessibleRole; import javafx.scene.Node; -import javafx.css.PseudoClass; - import javafx.scene.control.skin.ToggleButtonSkin; - -import javafx.css.StyleableProperty; +import com.sun.javafx.scene.traversal.OverridableTraversalPolicy; /** * A {@code ToggleButton} is a specialized control which has the ability to be @@ -210,9 +206,11 @@ public final ToggleGroup getToggleGroup() { public final ObjectProperty toggleGroupProperty() { if (toggleGroup == null) { toggleGroup = new ObjectPropertyBase<>() { + private final OverridableTraversalPolicy policy = new OverridableTraversalPolicy(); private ToggleGroup old; - private ChangeListener listener = (o, oV, nV) -> - ParentHelper.getTraversalEngine(ToggleButton.this).setOverriddenFocusTraversability(nV != null ? isSelected() : null); + private ChangeListener listener = (o, oV, nV) -> { + policy.setOverriddenFocusTraversability(nV != null ? isSelected() : null); + }; @Override protected void invalidated() { final ToggleGroup tg = get(); @@ -221,15 +219,14 @@ public final ObjectProperty toggleGroupProperty() { old.getToggles().remove(ToggleButton.this); } tg.getToggles().add(ToggleButton.this); - final ParentTraversalEngine parentTraversalEngine = new ParentTraversalEngine(ToggleButton.this); - ParentHelper.setTraversalEngine(ToggleButton.this, parentTraversalEngine); + setTraversalPolicy(policy); // If there's no toggle selected, do not override - parentTraversalEngine.setOverriddenFocusTraversability(tg.getSelectedToggle() != null ? isSelected() : null); + policy.setOverriddenFocusTraversability(tg.getSelectedToggle() != null ? isSelected() : null); tg.selectedToggleProperty().addListener(listener); } else if (tg == null) { old.selectedToggleProperty().removeListener(listener); old.getToggles().remove(ToggleButton.this); - ParentHelper.setTraversalEngine(ToggleButton.this, null); + setTraversalPolicy(null); } old = tg; diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ColorPalette.java b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ColorPalette.java index 70e5a8b5fc1..d511e294457 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ColorPalette.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ColorPalette.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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 @@ -25,14 +25,8 @@ package javafx.scene.control.skin; -import com.sun.javafx.scene.NodeHelper; -import com.sun.javafx.scene.ParentHelper; -import com.sun.javafx.scene.control.CustomColorDialog; -import com.sun.javafx.scene.control.skin.Utils; -import com.sun.javafx.scene.traversal.Algorithm; -import com.sun.javafx.scene.traversal.Direction; -import com.sun.javafx.scene.traversal.ParentTraversalEngine; -import com.sun.javafx.scene.traversal.TraversalContext; +import static com.sun.javafx.scene.control.Properties.getColorPickerString; +import java.util.List; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; @@ -44,6 +38,9 @@ import javafx.geometry.Pos; import javafx.geometry.Side; import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.TraversalDirection; +import javafx.scene.TraversalPolicy; import javafx.scene.control.ColorPicker; import javafx.scene.control.ContextMenu; import javafx.scene.control.Hyperlink; @@ -63,10 +60,10 @@ import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.shape.StrokeType; - -import java.util.List; - -import static com.sun.javafx.scene.control.Properties.getColorPickerString; +import com.sun.javafx.scene.NodeHelper; +import com.sun.javafx.scene.control.CustomColorDialog; +import com.sun.javafx.scene.control.skin.Utils; +import com.sun.javafx.scene.traversal.TraversalUtils; // Not public API - this is (presently) an implementation detail only class ColorPalette extends Region { @@ -299,10 +296,10 @@ private void initNavigation() { } }); - ParentHelper.setTraversalEngine(this, new ParentTraversalEngine(this, new Algorithm() { + setTraversalPolicy(new TraversalPolicy() { @Override - public Node select(Node owner, Direction dir, TraversalContext context) { - final Node subsequentNode = context.selectInSubtree(context.getRoot(), owner, dir); + public Node select(Parent root, Node owner, TraversalDirection dir) { + final Node subsequentNode = TraversalPolicy.getDefault().select(root, owner, dir); switch (dir) { case NEXT: case NEXT_IN_LINE: @@ -326,7 +323,7 @@ public Node select(Node owner, Direction dir, TraversalContext context) { return null; } - private Node processArrow(ColorSquare owner, Direction dir) { + private Node processArrow(ColorSquare owner, TraversalDirection dir) { int row = 0; int column = 0; @@ -339,7 +336,7 @@ private Node processArrow(ColorSquare owner, Direction dir) { } // Adjust the direction according to color picker orientation - dir = dir.getDirectionForNodeOrientation(colorPicker.getEffectiveNodeOrientation()); + dir = TraversalUtils.getDirectionForNodeOrientation(dir, colorPicker.getEffectiveNodeOrientation()); // This returns true for all the cases which we need to override if (isAtBorder(dir, row, column, (owner.colorType == ColorType.CUSTOM))) { // There's no other node in the direction from the square, so we need to continue on some other row @@ -355,15 +352,15 @@ private Node processArrow(ColorSquare owner, Direction dir) { // might have different number of columns if (owner.colorType == ColorType.STANDARD) { subsequentRow = 0; - subsequentColumn = (dir == Direction.LEFT)? NUM_OF_COLUMNS - 1 : 0; + subsequentColumn = (dir == TraversalDirection.LEFT)? NUM_OF_COLUMNS - 1 : 0; } else if (owner.colorType == ColorType.CUSTOM) { - subsequentRow = Math.floorMod(dir == Direction.LEFT ? row - 1 : row + 1, customColorRows); - subsequentColumn = dir == Direction.LEFT ? subsequentRow == customColorRows - 1 ? + subsequentRow = Math.floorMod(dir == TraversalDirection.LEFT ? row - 1 : row + 1, customColorRows); + subsequentColumn = dir == TraversalDirection.LEFT ? subsequentRow == customColorRows - 1 ? customColorLastRowLength - 1 : NUM_OF_COLUMNS - 1 : 0; } else { - subsequentRow = Math.floorMod(dir == Direction.LEFT ? row - 1 : row + 1, NUM_OF_ROWS); - subsequentColumn = dir == Direction.LEFT ? NUM_OF_COLUMNS - 1 : 0; + subsequentRow = Math.floorMod(dir == TraversalDirection.LEFT ? row - 1 : row + 1, NUM_OF_ROWS); + subsequentColumn = dir == TraversalDirection.LEFT ? NUM_OF_COLUMNS - 1 : 0; } break; case UP: // custom color are not handled here @@ -393,7 +390,7 @@ else if (owner.colorType == ColorType.CUSTOM) { return null; } - private boolean isAtBorder(Direction dir, int row, int column, boolean custom) { + private boolean isAtBorder(TraversalDirection dir, int row, int column, boolean custom) { switch (dir) { case LEFT: return column == 0; @@ -409,15 +406,15 @@ private boolean isAtBorder(Direction dir, int row, int column, boolean custom) { } @Override - public Node selectFirst(TraversalContext context) { + public Node selectFirst(Parent root) { return standardColorGrid.getChildren().get(0); } @Override - public Node selectLast(TraversalContext context) { + public Node selectLast(Parent root) { return customColorLink; } - })); + }); } private void processSelectKey(KeyEvent ke) { diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ComboBoxPopupControl.java b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ComboBoxPopupControl.java index 9ee5803694f..c811720e7f4 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ComboBoxPopupControl.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ComboBoxPopupControl.java @@ -47,16 +47,12 @@ import javafx.scene.layout.Region; import javafx.stage.WindowEvent; import javafx.util.StringConverter; -import com.sun.javafx.scene.ParentHelper; import com.sun.javafx.scene.control.FakeFocusTextField; import com.sun.javafx.scene.control.ListenerHelper; import com.sun.javafx.scene.control.Properties; import com.sun.javafx.scene.control.behavior.TextInputControlBehavior; import com.sun.javafx.scene.input.ExtendedInputMethodRequests; -import com.sun.javafx.scene.traversal.Algorithm; -import com.sun.javafx.scene.traversal.Direction; -import com.sun.javafx.scene.traversal.ParentTraversalEngine; -import com.sun.javafx.scene.traversal.TraversalContext; +import com.sun.javafx.scene.traversal.TraversalUtils; import com.sun.javafx.tk.Toolkit; /** @@ -191,22 +187,8 @@ public void install() { comboBoxBase.setOnInputMethodTextChanged(inputMethodTextChangedHandler); } - // Fix for JDK-8094715, where focus traversal was getting stuck inside the ComboBox - ParentHelper.setTraversalEngine(comboBoxBase, - new ParentTraversalEngine(comboBoxBase, new Algorithm() { - - @Override public Node select(Node owner, Direction dir, TraversalContext context) { - return null; - } - - @Override public Node selectFirst(TraversalContext context) { - return null; - } - - @Override public Node selectLast(TraversalContext context) { - return null; - } - })); + // Fix for RT-36902, where focus traversal was getting stuck inside the ComboBox + comboBoxBase.setTraversalPolicy(TraversalUtils.EMPTY_POLICY); updateEditable(); } diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/MenuBarSkin.java b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/MenuBarSkin.java index 7c674ac7b28..0be4390cffd 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/MenuBarSkin.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/MenuBarSkin.java @@ -60,6 +60,7 @@ import javafx.scene.AccessibleAttribute; import javafx.scene.Node; import javafx.scene.Scene; +import javafx.scene.TraversalDirection; import javafx.scene.control.Control; import javafx.scene.control.CustomMenuItem; import javafx.scene.control.Menu; @@ -79,14 +80,12 @@ import javafx.util.Pair; import javafx.util.Subscription; import com.sun.javafx.menu.MenuBase; -import com.sun.javafx.scene.ParentHelper; import com.sun.javafx.scene.SceneHelper; import com.sun.javafx.scene.control.GlobalMenuAdapter; import com.sun.javafx.scene.control.IDisconnectable; import com.sun.javafx.scene.control.ListenerHelper; import com.sun.javafx.scene.control.MenuBarButton; -import com.sun.javafx.scene.traversal.Direction; -import com.sun.javafx.scene.traversal.ParentTraversalEngine; +import com.sun.javafx.scene.traversal.TraversalUtils; import com.sun.javafx.tk.Toolkit; /** @@ -281,12 +280,12 @@ public MenuBarSkin(final MenuBar control) { acceleratorKeyCombo = KeyCombination.keyCombination("F10"); } - ParentTraversalEngine engine = new ParentTraversalEngine(getSkinnable()); - engine.addTraverseListener((node, bounds) -> { - if (openMenu != null) openMenu.hide(); + lh.addSceneFocusOwnerListener(control, (n) -> { + if (openMenu != null) { + openMenu.hide(); + } setFocusedMenuIndex(0); }); - ParentHelper.setTraversalEngine(getSkinnable(), engine); lh.addChangeListener(control.sceneProperty(), true, (scene) -> { if (sceneListenerHelper != null) { @@ -307,17 +306,17 @@ public MenuBarSkin(final MenuBar control) { if (control.getScene().getWindow().isFocused()) { if (openMenu != null && !openMenu.isShowing()) { if (isRTL) { - moveToMenu(Direction.NEXT, false); // just move the selection bar + moveToMenu(TraversalDirection.NEXT, false); // just move the selection bar } else { - moveToMenu(Direction.PREVIOUS, false); // just move the selection bar + moveToMenu(TraversalDirection.PREVIOUS, false); // just move the selection bar } ev.consume(); return; } if (isRTL) { - moveToMenu(Direction.NEXT, true); + moveToMenu(TraversalDirection.NEXT, true); } else { - moveToMenu(Direction.PREVIOUS, true); + moveToMenu(TraversalDirection.PREVIOUS, true); } } ev.consume(); @@ -328,17 +327,17 @@ public MenuBarSkin(final MenuBar control) { if (control.getScene().getWindow().isFocused()) { if (openMenu != null && !openMenu.isShowing()) { if (isRTL) { - moveToMenu(Direction.PREVIOUS, false); // just move the selection bar + moveToMenu(TraversalDirection.PREVIOUS, false); // just move the selection bar } else { - moveToMenu(Direction.NEXT, false); // just move the selection bar + moveToMenu(TraversalDirection.NEXT, false); // just move the selection bar } ev.consume(); return; } if (isRTL) { - moveToMenu(Direction.PREVIOUS, true); + moveToMenu(TraversalDirection.PREVIOUS, true); } else { - moveToMenu(Direction.NEXT, true); + moveToMenu(TraversalDirection.NEXT, true); } } ev.consume(); @@ -1099,7 +1098,7 @@ private void menuModeEnd() { setFocusedMenuIndex(-1); } - private void moveToMenu(Direction dir, boolean doShow) { + private void moveToMenu(TraversalDirection dir, boolean doShow) { Menu focusedMenu = menuBarButtonAt(focusedMenuIndex).menu; boolean showNextMenu = doShow && focusedMenu.isShowing(); findSibling(dir, focusedMenuIndex).ifPresent(p -> { @@ -1112,7 +1111,7 @@ private void moveToMenu(Direction dir, boolean doShow) { }); } - private Optional> findSibling(Direction dir, int startIndex) { + private Optional> findSibling(TraversalDirection dir, int startIndex) { if (startIndex == -1) { return Optional.empty(); } @@ -1127,7 +1126,7 @@ private Optional> findSibling(Direction dir, int startIndex) while (i < totalMenus) { i++; - nextIndex = (startIndex + (dir.isForward() ? 1 : -1)) % totalMenus; + nextIndex = (startIndex + (TraversalUtils.isForward(dir) ? 1 : -1)) % totalMenus; if (nextIndex == -1) { // loop backwards to end diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ScrollPaneSkin.java b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ScrollPaneSkin.java index f2b176eb271..7da49db6c16 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ScrollPaneSkin.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ScrollPaneSkin.java @@ -26,7 +26,6 @@ package javafx.scene.control.skin; import static com.sun.javafx.scene.control.skin.Utils.boundedSize; - import javafx.animation.Animation.Status; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; @@ -59,14 +58,12 @@ import javafx.scene.layout.StackPane; import javafx.scene.shape.Rectangle; import javafx.util.Duration; - import com.sun.javafx.scene.NodeHelper; -import com.sun.javafx.scene.ParentHelper; import com.sun.javafx.scene.control.ListenerHelper; import com.sun.javafx.scene.control.Properties; import com.sun.javafx.scene.control.behavior.BehaviorBase; import com.sun.javafx.scene.control.behavior.ScrollPaneBehavior; -import com.sun.javafx.scene.traversal.ParentTraversalEngine; +import com.sun.javafx.scene.traversal.TraversalUtils; import com.sun.javafx.util.Utils; /** @@ -632,13 +629,6 @@ private void initialize() { ScrollPane control = getSkinnable(); scrollNode = control.getContent(); - ParentTraversalEngine traversalEngine = new ParentTraversalEngine(getSkinnable()); - traversalEngine.addTraverseListener((node, bounds) -> { - // auto-scroll so node is within (0,0),(contentWidth,contentHeight) - scrollBoundsIntoView(bounds); - }); - ParentHelper.setTraversalEngine(getSkinnable(), traversalEngine); - if (scrollNode != null) { scrollNode.layoutBoundsProperty().addListener(weakNodeListener); scrollNode.layoutBoundsProperty().addListener(weakBoundsChangeListener); @@ -670,6 +660,14 @@ private void initialize() { ListenerHelper lh = ListenerHelper.get(this); + lh.addSceneFocusOwnerListener(control, (n) -> { + // auto-scroll node to view + Bounds b = TraversalUtils.getLayoutBounds(n, control); + if (b != null) { + scrollBoundsIntoView(b); + } + }); + lh.addEventFilter(hsb, MouseEvent.MOUSE_PRESSED, barHandler); lh.addEventFilter(vsb, MouseEvent.MOUSE_PRESSED, barHandler); diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/SpinnerSkin.java b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/SpinnerSkin.java index 57ec53793aa..6b0757d94d9 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/SpinnerSkin.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/SpinnerSkin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, 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 @@ -25,13 +25,11 @@ package javafx.scene.control.skin; import java.util.List; - import javafx.css.PseudoClass; import javafx.geometry.HPos; import javafx.geometry.VPos; import javafx.scene.AccessibleAction; import javafx.scene.AccessibleRole; -import javafx.scene.Node; import javafx.scene.control.Control; import javafx.scene.control.SkinBase; import javafx.scene.control.Spinner; @@ -40,15 +38,10 @@ import javafx.scene.input.KeyEvent; import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; - -import com.sun.javafx.scene.ParentHelper; import com.sun.javafx.scene.control.FakeFocusTextField; import com.sun.javafx.scene.control.ListenerHelper; import com.sun.javafx.scene.control.behavior.SpinnerBehavior; -import com.sun.javafx.scene.traversal.Algorithm; -import com.sun.javafx.scene.traversal.Direction; -import com.sun.javafx.scene.traversal.ParentTraversalEngine; -import com.sun.javafx.scene.traversal.TraversalContext; +import com.sun.javafx.scene.traversal.TraversalUtils; /** * Default skin implementation for the {@link Spinner} control. @@ -233,25 +226,6 @@ public void executeAccessibleAction(AccessibleAction action, Object... parameter textField.focusTraversableProperty().bind(control.editableProperty()); - // Following code borrowed from ComboBoxPopupControl, to resolve the - // issue initially identified in JDK-8094715, but specifically (for Spinner) - // identified in JDK-8092584 - ParentHelper.setTraversalEngine(control, - new ParentTraversalEngine(control, new Algorithm() { - - @Override public Node select(Node owner, Direction dir, TraversalContext context) { - return null; - } - - @Override public Node selectFirst(TraversalContext context) { - return null; - } - - @Override public Node selectLast(TraversalContext context) { - return null; - } - })); - lh.addChangeListener(control.sceneProperty(), (op) -> { // Stop spinning when sceneProperty is modified behavior.stopSpinning(); @@ -274,6 +248,11 @@ public void install() { // when replacing the skin, the textField (which comes from the control), must first be uninstalled // by the old skin in its dispose(), followed by (re-)adding it here. getChildren().add(textField); + + // Following code borrowed from ComboBoxPopupControl, to resolve the + // issue initially identified in RT-36902, but specifically (for Spinner) + // identified in RT-40625 + getSkinnable().setTraversalPolicy(TraversalUtils.EMPTY_POLICY); } /** {@inheritDoc} */ diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ToolBarSkin.java b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ToolBarSkin.java index 13dc8ab4d4f..37626423806 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ToolBarSkin.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ToolBarSkin.java @@ -25,25 +25,25 @@ package javafx.scene.control.skin; -import com.sun.javafx.scene.NodeHelper; -import com.sun.javafx.scene.ParentHelper; +import static com.sun.javafx.scene.control.skin.resources.ControlResources.getString; import java.util.ArrayList; import java.util.Collections; import java.util.List; - -import com.sun.javafx.scene.control.behavior.BehaviorBase; -import com.sun.javafx.scene.traversal.Algorithm; -import com.sun.javafx.scene.traversal.ParentTraversalEngine; -import com.sun.javafx.scene.traversal.TraversalContext; - import javafx.beans.binding.Bindings; -import javafx.beans.property.ObjectProperty; import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; import javafx.beans.value.WritableValue; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.collections.SetChangeListener; +import javafx.css.CssMetaData; import javafx.css.PseudoClass; +import javafx.css.Styleable; +import javafx.css.StyleableDoubleProperty; +import javafx.css.StyleableObjectProperty; +import javafx.css.StyleableProperty; +import javafx.css.converter.EnumConverter; +import javafx.css.converter.SizeConverter; import javafx.geometry.HPos; import javafx.geometry.Orientation; import javafx.geometry.Pos; @@ -54,10 +54,12 @@ import javafx.scene.AccessibleRole; import javafx.scene.Node; import javafx.scene.Parent; +import javafx.scene.TraversalDirection; +import javafx.scene.TraversalPolicy; import javafx.scene.control.ContextMenu; import javafx.scene.control.Control; -import javafx.scene.control.MenuItem; import javafx.scene.control.CustomMenuItem; +import javafx.scene.control.MenuItem; import javafx.scene.control.Separator; import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.SkinBase; @@ -67,20 +69,11 @@ import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; -import javafx.css.StyleableDoubleProperty; -import javafx.css.StyleableObjectProperty; -import javafx.css.StyleableProperty; -import javafx.css.CssMetaData; - -import javafx.css.converter.EnumConverter; -import javafx.css.converter.SizeConverter; -import com.sun.javafx.scene.control.behavior.ToolBarBehavior; -import com.sun.javafx.scene.traversal.Direction; - -import javafx.css.Styleable; import javafx.stage.WindowEvent; - -import static com.sun.javafx.scene.control.skin.resources.ControlResources.getString; +import com.sun.javafx.scene.NodeHelper; +import com.sun.javafx.scene.control.behavior.BehaviorBase; +import com.sun.javafx.scene.control.behavior.ToolBarBehavior; +import com.sun.javafx.scene.traversal.TraversalUtils; /** * Default skin implementation for the {@link ToolBar} control. @@ -111,7 +104,6 @@ public class ToolBarSkin extends SkinBase { private double savedPrefWidth = 0; private double savedPrefHeight = 0; private boolean needsUpdate = false; - private final ParentTraversalEngine engine; private final BehaviorBase behavior; private ListChangeListener itemsListener; @@ -139,14 +131,13 @@ public ToolBarSkin(ToolBar control) { initialize(); registerChangeListener(control.orientationProperty(), e -> initialize()); - engine = new ParentTraversalEngine(getSkinnable(), new Algorithm() { - - private Node selectPrev(int from, TraversalContext context) { + getSkinnable().setTraversalPolicy(new TraversalPolicy() { + private Node selectPrev(int from, Parent root) { for (int i = from; i >= 0; --i) { Node n = box.getChildren().get(i); if (n.isDisabled() || !NodeHelper.isTreeShowing(n)) continue; - if (n instanceof Parent) { - Node selected = context.selectLastInParent((Parent)n); + if (n instanceof Parent p) { + Node selected = TraversalPolicy.getDefault().selectLast(p); if (selected != null) return selected; } if (n.isFocusTraversable() ) { @@ -156,15 +147,15 @@ private Node selectPrev(int from, TraversalContext context) { return null; } - private Node selectNext(int from, TraversalContext context) { + private Node selectNext(int from, Parent root) { for (int i = from, max = box.getChildren().size(); i < max; ++i) { Node n = box.getChildren().get(i); if (n.isDisabled() || !NodeHelper.isTreeShowing(n)) continue; if (n.isFocusTraversable()) { return n; } - if (n instanceof Parent) { - Node selected = context.selectFirstInParent((Parent)n); + if (n instanceof Parent p) { + Node selected = TraversalPolicy.getDefault().selectFirst(p); if (selected != null) return selected; } } @@ -172,16 +163,16 @@ private Node selectNext(int from, TraversalContext context) { } @Override - public Node select(Node owner, Direction dir, TraversalContext context) { + public Node select(Parent root, Node owner, TraversalDirection dir) { - dir = dir.getDirectionForNodeOrientation(control.getEffectiveNodeOrientation()); + dir = TraversalUtils.getDirectionForNodeOrientation(dir, control.getEffectiveNodeOrientation()); final ObservableList boxChildren = box.getChildren(); if (owner == overflowMenu) { - if (dir.isForward()) { + if (TraversalUtils.isForward(dir)) { return null; } else { - Node selected = selectPrev(boxChildren.size() - 1, context); + Node selected = selectPrev(boxChildren.size() - 1, root); if (selected != null) return selected; } } @@ -197,22 +188,24 @@ public Node select(Node owner, Direction dir, TraversalContext context) { } item = item.getParent(); } - Node selected = context.selectInSubtree(item, owner, dir); + Node selected = TraversalPolicy.getDefault().select(item, owner, dir); if (selected != null) return selected; idx = boxChildren.indexOf(item); - if (dir == Direction.NEXT) dir = Direction.NEXT_IN_LINE; + if (dir == TraversalDirection.NEXT) { + dir = TraversalDirection.NEXT_IN_LINE; + } } if (idx >= 0) { - if (dir.isForward()) { - Node selected = selectNext(idx + 1, context); + if (TraversalUtils.isForward(dir)) { + Node selected = selectNext(idx + 1, root); if (selected != null) return selected; if (overflow) { overflowMenu.requestFocus(); return overflowMenu; } } else { - Node selected = selectPrev(idx - 1, context); + Node selected = selectPrev(idx - 1, root); if (selected != null) return selected; } } @@ -220,8 +213,8 @@ public Node select(Node owner, Direction dir, TraversalContext context) { } @Override - public Node selectFirst(TraversalContext context) { - Node selected = selectNext(0, context); + public Node selectFirst(Parent root) { + Node selected = selectNext(0, root); if (selected != null) return selected; if (overflow) { return overflowMenu; @@ -230,14 +223,13 @@ public Node selectFirst(TraversalContext context) { } @Override - public Node selectLast(TraversalContext context) { + public Node selectLast(Parent root) { if (overflow) { return overflowMenu; } - return selectPrev(box.getChildren().size() - 1, context); + return selectPrev(box.getChildren().size() - 1, root); } }); - ParentHelper.setTraversalEngine(getSkinnable(), engine); registerChangeListener(control.focusedProperty(), ov -> { if (getSkinnable().isFocused()) { @@ -639,10 +631,7 @@ private void organizeOverflow(double length) { overflowBox.getChildren().add(node); if (node.isFocused()) { if (!box.getChildren().isEmpty()) { - Node last = engine.selectLast(); - if (last != null) { - last.requestFocus(); - } + selectAndFocusLast(); } else { overflowMenu.requestFocus(); } @@ -654,13 +643,20 @@ private void organizeOverflow(double length) { overflow = !overflowBox.getChildren().isEmpty(); overflowNodeIndex = newOverflowNodeIndex; if (!overflow && overflowMenu.isFocused()) { - Node last = engine.selectLast(); + selectAndFocusLast(); + } + overflowMenu.setVisible(overflow); + overflowMenu.setManaged(overflow); + } + + private void selectAndFocusLast() { + TraversalPolicy p = getSkinnable().getTraversalPolicy(); + if (p != null) { + Node last = p.selectLast(getSkinnable()); if (last != null) { last.requestFocus(); } } - overflowMenu.setVisible(overflow); - overflowMenu.setManaged(overflow); } private void addNodesToToolBar() { diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/VirtualFlow.java b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/VirtualFlow.java index 6cf4667129c..d15e4a5a7c6 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/VirtualFlow.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/VirtualFlow.java @@ -25,15 +25,10 @@ package javafx.scene.control.skin; -import com.sun.javafx.scene.ParentHelper; -import com.sun.javafx.scene.control.Logging; -import com.sun.javafx.scene.control.Properties; -import com.sun.javafx.scene.control.VirtualScrollBar; -import com.sun.javafx.scene.control.skin.Utils; -import com.sun.javafx.scene.traversal.Algorithm; -import com.sun.javafx.scene.traversal.Direction; -import com.sun.javafx.scene.traversal.ParentTraversalEngine; -import com.sun.javafx.scene.traversal.TraversalContext; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.beans.InvalidationListener; @@ -57,6 +52,8 @@ import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.Scene; +import javafx.scene.TraversalDirection; +import javafx.scene.TraversalPolicy; import javafx.scene.control.Cell; import javafx.scene.control.IndexedCell; import javafx.scene.control.ScrollBar; @@ -68,11 +65,10 @@ import javafx.util.Callback; import javafx.util.Duration; import com.sun.javafx.logging.PlatformLogger; - -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.List; +import com.sun.javafx.scene.control.Logging; +import com.sun.javafx.scene.control.Properties; +import com.sun.javafx.scene.control.VirtualScrollBar; +import com.sun.javafx.scene.control.skin.Utils; /** * Implementation of a virtualized container using a cell based mechanism. This @@ -637,15 +633,14 @@ public void handle(MouseEvent e) { startSBReleasedAnimation(); }); - ParentHelper.setTraversalEngine(this, new ParentTraversalEngine(this, new Algorithm() { - - Node selectNextAfterIndex(int index, TraversalContext context) { + setTraversalPolicy(new TraversalPolicy() { + Node selectNextAfterIndex(int index, Parent root) { T nextCell; while ((nextCell = getVisibleCell(++index)) != null) { if (nextCell.isFocusTraversable()) { return nextCell; } - Node n = context.selectFirstInParent(nextCell); + Node n = TraversalPolicy.getDefault().selectFirst(nextCell); if (n != null) { return n; } @@ -653,10 +648,10 @@ Node selectNextAfterIndex(int index, TraversalContext context) { return null; } - Node selectPreviousBeforeIndex(int index, TraversalContext context) { + Node selectPreviousBeforeIndex(int index, Parent root) { T prevCell; while ((prevCell = getVisibleCell(--index)) != null) { - Node prev = context.selectLastInParent(prevCell); + Node prev = TraversalPolicy.getDefault().selectLast(prevCell); if (prev != null) { return prev; } @@ -668,31 +663,31 @@ Node selectPreviousBeforeIndex(int index, TraversalContext context) { } @Override - public Node select(Node owner, Direction dir, TraversalContext context) { + public Node select(Parent root, Node owner, TraversalDirection dir) { T cell; if (cells.isEmpty()) return null; if (cells.contains(owner)) { cell = (T) owner; } else { cell = findOwnerCell(owner); - Node next = context.selectInSubtree(cell, owner, dir); + Node next = TraversalPolicy.getDefault().select(cell, owner, dir); if (next != null) { return next; } - if (dir == Direction.NEXT) dir = Direction.NEXT_IN_LINE; + if (dir == TraversalDirection.NEXT) dir = TraversalDirection.NEXT_IN_LINE; } int cellIndex = cell.getIndex(); switch(dir) { case PREVIOUS: - return selectPreviousBeforeIndex(cellIndex, context); + return selectPreviousBeforeIndex(cellIndex, root); case NEXT: - Node n = context.selectFirstInParent(cell); + Node n = TraversalPolicy.getDefault().selectFirst(cell); if (n != null) { return n; } // Intentional fall-through case NEXT_IN_LINE: - return selectNextAfterIndex(cellIndex, context); + return selectNextAfterIndex(cellIndex, root); } return null; } @@ -706,29 +701,29 @@ private T findOwnerCell(Node owner) { } @Override - public Node selectFirst(TraversalContext context) { + public Node selectFirst(Parent root) { T firstCell = cells.getFirst(); if (firstCell == null) return null; if (firstCell.isFocusTraversable()) return firstCell; - Node n = context.selectFirstInParent(firstCell); + Node n = TraversalPolicy.getDefault().selectFirst(firstCell); if (n != null) { return n; } - return selectNextAfterIndex(firstCell.getIndex(), context); + return selectNextAfterIndex(firstCell.getIndex(), root); } @Override - public Node selectLast(TraversalContext context) { + public Node selectLast(Parent root) { T lastCell = cells.getLast(); if (lastCell == null) return null; - Node p = context.selectLastInParent(lastCell); + Node p = TraversalPolicy.getDefault().selectLast(lastCell); if (p != null) { return p; } if (lastCell.isFocusTraversable()) return lastCell; - return selectPreviousBeforeIndex(lastCell.getIndex(), context); + return selectPreviousBeforeIndex(lastCell.getIndex(), root); } - })); + }); } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/embed/EmbeddedSceneInterface.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/embed/EmbeddedSceneInterface.java index 7df04518a0d..31ec2fd477f 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/embed/EmbeddedSceneInterface.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/embed/EmbeddedSceneInterface.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, 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 @@ -26,10 +26,9 @@ package com.sun.javafx.embed; import java.nio.IntBuffer; - -import com.sun.javafx.scene.traversal.Direction; import javafx.collections.ObservableList; import javafx.event.EventType; +import javafx.scene.TraversalDirection; import javafx.scene.image.PixelFormat; import javafx.scene.input.InputMethodEvent; import javafx.scene.input.InputMethodRequests; @@ -112,7 +111,7 @@ public void swipeEvent(final int type, final double x, final double y, final dou */ public void menuEvent(int x, int y, int xAbs, int yAbs, boolean isKeyboardTrigger); - public boolean traverseOut(Direction dir); + public boolean traverseOut(TraversalDirection dir); public void setDragStartListener(HostDragStartListener l); diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/KeyboardShortcutsHandler.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/KeyboardShortcutsHandler.java index d52a3537d61..2bd97704ed0 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/KeyboardShortcutsHandler.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/KeyboardShortcutsHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, 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 @@ -33,22 +33,20 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; - -import com.sun.javafx.scene.traversal.TraversalMethod; import javafx.collections.ObservableList; import javafx.collections.ObservableMap; import javafx.event.Event; import javafx.scene.Node; +import javafx.scene.TraversalDirection; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.input.Mnemonic; - import com.sun.javafx.PlatformUtil; import com.sun.javafx.collections.ObservableListWrapper; import com.sun.javafx.collections.ObservableMapWrapper; import com.sun.javafx.event.BasicEventDispatcher; -import com.sun.javafx.scene.traversal.Direction; +import com.sun.javafx.scene.traversal.TraversalUtils; public final class KeyboardShortcutsHandler extends BasicEventDispatcher { private ObservableMap accelerators; @@ -99,8 +97,8 @@ public ObservableMap getAccelerators() { return accelerators; } - private void traverse(Event event, Node node, Direction dir) { - if (NodeHelper.traverse(node, dir, TraversalMethod.KEY)) { + private void traverse(Event event, Node node, TraversalDirection dir) { + if (TraversalUtils.traverse(node, dir, true)) { event.consume(); } } @@ -118,23 +116,23 @@ public void processTraversal(Event event) { switch (keyEvent.getCode()) { case TAB : if (keyEvent.isShiftDown()) { - traverse(event, node, Direction.PREVIOUS); + traverse(event, node, TraversalDirection.PREVIOUS); } else { - traverse(event, node, Direction.NEXT); + traverse(event, node, TraversalDirection.NEXT); } break; case UP : - traverse(event, node, Direction.UP); + traverse(event, node, TraversalDirection.UP); break; case DOWN : - traverse(event, node, Direction.DOWN); + traverse(event, node, TraversalDirection.DOWN); break; case LEFT : - traverse(event, node, Direction.LEFT); + traverse(event, node, TraversalDirection.LEFT); break; case RIGHT : - traverse(event, node, Direction.RIGHT); + traverse(event, node, TraversalDirection.RIGHT); break; default : break; diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/NodeHelper.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/NodeHelper.java index 2e879cf1bb5..4bb236dc3ab 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/NodeHelper.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/NodeHelper.java @@ -25,18 +25,6 @@ package com.sun.javafx.scene; -import com.sun.glass.ui.Accessible; -import com.sun.javafx.css.TransitionDefinition; -import com.sun.javafx.css.TransitionTimer; -import com.sun.javafx.css.media.MediaQueryContext; -import com.sun.javafx.geom.BaseBounds; -import com.sun.javafx.geom.PickRay; -import com.sun.javafx.geom.transform.BaseTransform; -import com.sun.javafx.scene.input.PickResultChooser; -import com.sun.javafx.scene.traversal.Direction; -import com.sun.javafx.scene.traversal.TraversalMethod; -import com.sun.javafx.sg.prism.NGNode; -import com.sun.javafx.util.Utils; import java.util.List; import java.util.Map; import javafx.beans.binding.BooleanExpression; @@ -51,6 +39,16 @@ import javafx.scene.Scene; import javafx.scene.SubScene; import javafx.scene.text.Font; +import com.sun.glass.ui.Accessible; +import com.sun.javafx.css.TransitionDefinition; +import com.sun.javafx.css.TransitionTimer; +import com.sun.javafx.css.media.MediaQueryContext; +import com.sun.javafx.geom.BaseBounds; +import com.sun.javafx.geom.PickRay; +import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.javafx.scene.input.PickResultChooser; +import com.sun.javafx.sg.prism.NGNode; +import com.sun.javafx.util.Utils; /** * Used to access internal methods of Node. @@ -233,10 +231,6 @@ public static BooleanProperty showMnemonicsProperty(Node node) { return nodeAccessor.showMnemonicsProperty(node); } - public static boolean traverse(Node node, Direction direction, TraversalMethod method) { - return nodeAccessor.traverse(node, direction, method); - } - public static double getPivotX(Node node) { return nodeAccessor.getPivotX(node); } @@ -386,7 +380,6 @@ boolean doComputeIntersects(Node node, PickRay pickRay, void setShowMnemonics(Node node, boolean value); boolean isShowMnemonics(Node node); BooleanProperty showMnemonicsProperty(Node node); - boolean traverse(Node node, Direction direction, TraversalMethod method); double getPivotX(Node node); double getPivotY(Node node); double getPivotZ(Node node); diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/ParentHelper.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/ParentHelper.java index b737393d488..8d313698b20 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/ParentHelper.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/ParentHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2024, 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 @@ -25,16 +25,15 @@ package com.sun.javafx.scene; +import java.util.List; +import javafx.scene.Node; +import javafx.scene.Parent; import com.sun.javafx.geom.BaseBounds; import com.sun.javafx.geom.PickRay; import com.sun.javafx.geom.transform.BaseTransform; import com.sun.javafx.scene.input.PickResultChooser; -import com.sun.javafx.scene.traversal.ParentTraversalEngine; import com.sun.javafx.sg.prism.NGNode; import com.sun.javafx.util.Utils; -import java.util.List; -import javafx.scene.Node; -import javafx.scene.Parent; /* * Used to access internal methods of Parent. @@ -113,14 +112,6 @@ public static boolean pickChildrenNode(Parent parent, PickRay pickRay, return parentAccessor.pickChildrenNode(parent, pickRay, result); } - public static void setTraversalEngine(Parent parent, ParentTraversalEngine value) { - parentAccessor.setTraversalEngine(parent, value); - } - - public static ParentTraversalEngine getTraversalEngine(Parent parent) { - return parentAccessor.getTraversalEngine(parent); - } - public static void setParentAccessor(final ParentAccessor newAccessor) { if (parentAccessor != null) { throw new IllegalStateException(); @@ -137,9 +128,6 @@ public interface ParentAccessor { void doProcessCSS(Node node); void doPickNodeLocal(Node node, PickRay localPickRay, PickResultChooser result); boolean pickChildrenNode(Parent parent, PickRay pickRay, PickResultChooser result); - void setTraversalEngine(Parent parent, ParentTraversalEngine value); - ParentTraversalEngine getTraversalEngine(Parent parent); List doGetAllParentStylesheets(Parent parent); } - } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/SceneHelper.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/SceneHelper.java index 8ac7e33d6cd..963dfe5a6ae 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/SceneHelper.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/SceneHelper.java @@ -25,9 +25,6 @@ package com.sun.javafx.scene; -import com.sun.glass.ui.Accessible; -import com.sun.javafx.tk.TKScene; -import com.sun.javafx.util.Utils; import javafx.scene.Camera; import javafx.scene.Node; import javafx.scene.Parent; @@ -35,6 +32,9 @@ import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseEvent; import javafx.stage.Window; +import com.sun.glass.ui.Accessible; +import com.sun.javafx.tk.TKScene; +import com.sun.javafx.util.Utils; /** * Used to access internal scene methods. @@ -156,5 +156,4 @@ public interface SceneAccessor { Accessible getAccessible(Scene scene); } - } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/Algorithm.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/Algorithm.java deleted file mode 100644 index ab59018e4cc..00000000000 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/Algorithm.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2010, 2014, 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 com.sun.javafx.scene.traversal; - -import javafx.scene.Node; - -/** - * An algorithm to be used in a traversal engine. - * - * Note that in order to avoid cycles or dead-ends in traversal the algorithms should respect the following order: - * * for NEXT: node -> node's subtree -> node siblings (first sibling then it's subtree) -> NEXT_IN_LINE for node's parent - * * for NEXT_IN_LINE: node -> node siblings (first sibling then it's subtree) -> NEXT_IN_LINE for node's parent - * * for PREVIOUS: node -> node siblings ( ! first subtree then the node itself ! ) -> PREVIOUS for node's parent - * - * Basically it ensures that next direction will traverse the same nodes as previous, in the opposite order. - * - */ -public interface Algorithm { - - /** - * Traverse from owner, in direction dir. - * Return a the new target Node or null if no suitable target is found. - * - * Typically, the implementation of override algorithm handles only parent's direct children and looks like this: - * 1) Find the nearest parent of the "owner" that is handled by this algorithm (i.e. it's a direct child of the root). - * 2) select the next node within this direct child using the context.selectInSubtree() and return it - * 2a) if no such node exists, move to the next direct child in the direction (this is where the different order of direct children is defined) - * or if direct children are not traversable, the select the first node in the next direct child - */ - public Node select(Node owner, Direction dir, TraversalContext context); - - /** - * Return the first traversable node for the specified context (root). - * @param context the context that contains the root - * @return the first node - */ - public Node selectFirst(TraversalContext context); - - /** - * Return the last traversable node for the specified context (root). - * @param context the context that contains the root - * @return the last node - */ - public Node selectLast(TraversalContext context); - -} diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/ContainerTabOrder.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/ContainerTabOrder.java index af7fe3293b8..081be91afad 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/ContainerTabOrder.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/ContainerTabOrder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, 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 @@ -28,29 +28,30 @@ import java.util.List; import javafx.geometry.Bounds; import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.TraversalDirection; +import javafx.scene.TraversalPolicy; -import static com.sun.javafx.scene.traversal.Direction.*; - -public class ContainerTabOrder implements Algorithm { +public class ContainerTabOrder extends TraversalPolicy { ContainerTabOrder() { } @Override - public Node select(Node node, Direction dir, TraversalContext context) { + public Node select(Parent root, Node node, TraversalDirection dir) { switch (dir) { case NEXT: + return findNextFocusableNode(root, node); case NEXT_IN_LINE: - return TabOrderHelper.findNextFocusablePeer(node, context.getRoot(), dir == NEXT); + return findNextInLineFocusableNode(root, node); case PREVIOUS: - return TabOrderHelper.findPreviousFocusablePeer(node, context.getRoot()); + return findPreviousFocusableNode(root, node); case UP: case DOWN: case LEFT: case RIGHT: - List nodes = context.getAllTargetNodes(); - - int target = trav2D(context.getSceneLayoutBounds(node), dir, nodes, context); + List nodes = TraversalUtils.getAllTargetNodes(root); + int target = trav2D(TraversalUtils.getLayoutBoundsInSceneCoordinates(node), dir, nodes, root); if (target != -1) { return nodes.get(target); } @@ -59,23 +60,23 @@ public Node select(Node node, Direction dir, TraversalContext context) { } @Override - public Node selectFirst(TraversalContext context) { - return TabOrderHelper.getFirstTargetNode(context.getRoot()); + public Node selectFirst(Parent root) { + return TabOrderHelper.getFirstTargetNode(root); } @Override - public Node selectLast(TraversalContext context) { - return TabOrderHelper.getLastTargetNode(context.getRoot()); + public Node selectLast(Parent root) { + return TabOrderHelper.getLastTargetNode(root); } - private int trav2D(Bounds origin, Direction dir, List peers, TraversalContext context) { + private int trav2D(Bounds origin, TraversalDirection dir, List peers, Parent root) { Bounds bestBounds = null; double bestMetric = 0.0; int bestIndex = -1; for (int i = 0; i < peers.size(); i++) { - final Bounds targetBounds = context.getSceneLayoutBounds(peers.get(i)); + final Bounds targetBounds = TraversalUtils.getLayoutBoundsInSceneCoordinates(peers.get(i)); final double outd = outDistance(dir, origin, targetBounds); final double metric; @@ -101,11 +102,11 @@ private int trav2D(Bounds origin, Direction dir, List peers, TraversalCont return bestIndex; } - private boolean isOnAxis(Direction dir, Bounds cur, Bounds tgt) { + private boolean isOnAxis(TraversalDirection dir, Bounds cur, Bounds tgt) { final double cmin, cmax, tmin, tmax; - if (dir == UP || dir == DOWN) { + if (dir == TraversalDirection.UP || dir == TraversalDirection.DOWN) { cmin = cur.getMinX(); cmax = cur.getMaxX(); tmin = tgt.getMinX(); @@ -125,17 +126,17 @@ private boolean isOnAxis(Direction dir, Bounds cur, Bounds tgt) { * Compute the out-distance to the near edge of the target in the * traversal direction. Negative means the near edge is "behind". */ - private double outDistance(Direction dir, Bounds cur, Bounds tgt) { + private double outDistance(TraversalDirection dir, Bounds cur, Bounds tgt) { final double distance; - if (dir == UP) { + if (dir == TraversalDirection.UP) { distance = cur.getMinY() - tgt.getMaxY(); } - else if (dir == DOWN) { + else if (dir == TraversalDirection.DOWN) { distance = tgt.getMinY() - cur.getMaxY(); } - else if (dir == LEFT) { + else if (dir == TraversalDirection.LEFT) { distance = cur.getMinX() - tgt.getMaxX(); } else { // dir == RIGHT @@ -149,12 +150,12 @@ else if (dir == LEFT) { * Computes the side distance from current center to target center. * Always positive. This is only used for on-axis nodes. */ - private double centerSideDistance(Direction dir, Bounds cur, Bounds tgt) { + private double centerSideDistance(TraversalDirection dir, Bounds cur, Bounds tgt) { final double cc; // current center final double tc; // target center - if (dir == UP || dir == DOWN) { + if (dir == TraversalDirection.UP || dir == TraversalDirection.DOWN) { cc = cur.getMinX() + cur.getWidth() / 2.0f; tc = tgt.getMinX() + tgt.getWidth() / 2.0f; } @@ -171,11 +172,11 @@ private double centerSideDistance(Direction dir, Bounds cur, Bounds tgt) { * Computes the side distance between the closest corners of the current * and target. Always positive. This is only used for off-axis nodes. */ - private double cornerSideDistance(Direction dir, Bounds cur, Bounds tgt) { + private double cornerSideDistance(TraversalDirection dir, Bounds cur, Bounds tgt) { final double distance; - if (dir == UP || dir == DOWN) { + if (dir == TraversalDirection.UP || dir == TraversalDirection.DOWN) { if (tgt.getMinX() > cur.getMaxX()) { // on the right diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/Direction.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/Direction.java deleted file mode 100644 index 32a0894b198..00000000000 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/Direction.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2010, 2024, 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 com.sun.javafx.scene.traversal; - -import javafx.geometry.NodeOrientation; -import javafx.scene.TraversalDirection; - -/** - * Specifies the direction of traversal. - */ -public enum Direction { - - UP(false), - DOWN(true), - LEFT(false), - RIGHT(true), - NEXT(true), - NEXT_IN_LINE(true), // Like NEXT, but does not traverse into the current parent - PREVIOUS(false); - private final boolean forward; - - Direction(boolean forward) { - this.forward = forward; - } - - public boolean isForward() { - return forward; - } - - /** - * Returns the direction with respect to the node's orientation. It affect's only arrow keys however, so it's not - * an error to ignore this call if handling only next/previous traversal. - * @param orientation - * @return - */ - public Direction getDirectionForNodeOrientation(NodeOrientation orientation) { - if (orientation == NodeOrientation.RIGHT_TO_LEFT) { - switch (this) { - case LEFT: - return RIGHT; - case RIGHT: - return LEFT; - } - } - return this; - } - - public static Direction of(TraversalDirection d) { - return switch (d) { - case DOWN -> DOWN; - case LEFT -> LEFT; - case NEXT -> NEXT; - case PREVIOUS -> PREVIOUS; - case RIGHT -> RIGHT; - case UP -> UP; - }; - } -} diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/Heuristic2D.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/Heuristic2D.java index 1f1ef0858d6..5f37286a84d 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/Heuristic2D.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/Heuristic2D.java @@ -25,13 +25,13 @@ package com.sun.javafx.scene.traversal; -import static com.sun.javafx.scene.traversal.Direction.DOWN; -import static com.sun.javafx.scene.traversal.Direction.LEFT; -import static com.sun.javafx.scene.traversal.Direction.NEXT; -import static com.sun.javafx.scene.traversal.Direction.NEXT_IN_LINE; -import static com.sun.javafx.scene.traversal.Direction.PREVIOUS; -import static com.sun.javafx.scene.traversal.Direction.RIGHT; -import static com.sun.javafx.scene.traversal.Direction.UP; +import static javafx.scene.TraversalDirection.DOWN; +import static javafx.scene.TraversalDirection.LEFT; +import static javafx.scene.TraversalDirection.NEXT; +import static javafx.scene.TraversalDirection.NEXT_IN_LINE; +import static javafx.scene.TraversalDirection.PREVIOUS; +import static javafx.scene.TraversalDirection.RIGHT; +import static javafx.scene.TraversalDirection.UP; import java.util.List; import java.util.Stack; import java.util.function.Function; @@ -39,23 +39,28 @@ import javafx.geometry.Bounds; import javafx.geometry.Point2D; import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.TraversalDirection; +import javafx.scene.TraversalPolicy; -public class Heuristic2D implements Algorithm { +public class Heuristic2D extends TraversalPolicy { Heuristic2D() { } @Override - public Node select(Node node, Direction dir, TraversalContext context) { + public Node select(Parent root, Node node, TraversalDirection dir) { + System.out.println(dir + " node=" + node); Node newNode = null; cacheTraversal(node, dir); - if (NEXT.equals(dir) || NEXT_IN_LINE.equals(dir)) { - newNode = TabOrderHelper.findNextFocusablePeer(node, context.getRoot(), dir == NEXT); - } - else if (PREVIOUS.equals(dir)) { - newNode = TabOrderHelper.findPreviousFocusablePeer(node, context.getRoot()); + if (NEXT.equals(dir)) { + newNode = findNextFocusableNode(root, node); + } else if (NEXT_IN_LINE.equals(dir)) { + newNode = findNextInLineFocusableNode(root, node); + } else if (PREVIOUS.equals(dir)) { + newNode = findPreviousFocusableNode(root, node); } else if (UP.equals(dir) || DOWN.equals(dir) || LEFT.equals(dir) || RIGHT.equals(dir) ) { /* @@ -77,11 +82,11 @@ else if (UP.equals(dir) || DOWN.equals(dir) || LEFT.equals(dir) || RIGHT.equals( switch (dir) { case UP: case DOWN: - newNode = getNearestNodeUpOrDown(currentB, cachedB, context, dir); + newNode = getNearestNodeUpOrDown(currentB, cachedB, root, dir); break; case LEFT: case RIGHT: - newNode = getNearestNodeLeftOrRight(currentB, cachedB, context, dir); + newNode = getNearestNodeLeftOrRight(currentB, cachedB, root, dir); break; default: break; @@ -105,16 +110,16 @@ else if (UP.equals(dir) || DOWN.equals(dir) || LEFT.equals(dir) || RIGHT.equals( } @Override - public Node selectFirst(TraversalContext context) { - return TabOrderHelper.getFirstTargetNode(context.getRoot()); + public Node selectFirst(Parent root) { + return TabOrderHelper.getFirstTargetNode(root); } @Override - public Node selectLast(TraversalContext context) { - return TabOrderHelper.getLastTargetNode(context.getRoot()); + public Node selectLast(Parent root) { + return TabOrderHelper.getLastTargetNode(root); } - private boolean isOnAxis(Direction dir, Bounds cur, Bounds tgt) { + private boolean isOnAxis(TraversalDirection dir, Bounds cur, Bounds tgt) { final double cmin, cmax, tmin, tmax; @@ -138,7 +143,7 @@ private boolean isOnAxis(Direction dir, Bounds cur, Bounds tgt) { * Compute the out-distance to the near edge of the target in the * traversal direction. Negative means the near edge is "behind". */ - private double outDistance(Direction dir, Bounds cur, Bounds tgt) { + private double outDistance(TraversalDirection dir, Bounds cur, Bounds tgt) { final double distance; if (dir == UP) { @@ -160,7 +165,7 @@ else if (dir == LEFT) { * Computes the side distance from current center to target center. * Always positive. This is only used for on-axis nodes. */ - private double centerSideDistance(Direction dir, Bounds cur, Bounds tgt) { + private double centerSideDistance(TraversalDirection dir, Bounds cur, Bounds tgt) { final double cc; // current center final double tc; // target center @@ -179,7 +184,7 @@ private double centerSideDistance(Direction dir, Bounds cur, Bounds tgt) { * Computes the side distance between the closest corners of the current * and target. Always positive. This is only used for off-axis nodes. */ - private double cornerSideDistance(Direction dir, Bounds cur, Bounds tgt) { + private double cornerSideDistance(TraversalDirection dir, Bounds cur, Bounds tgt) { final double distance; @@ -208,12 +213,12 @@ private double cornerSideDistance(Direction dir, Bounds cur, Bounds tgt) { } protected Node cacheStartTraversalNode = null; - protected Direction cacheStartTraversalDirection = null; + protected TraversalDirection cacheStartTraversalDirection = null; protected boolean reverseDirection = false; protected Node cacheLastTraversalNode = null; protected Stack traversalNodeStack = new Stack(); - private void cacheTraversal(Node node, Direction dir) { + private void cacheTraversal(Node node, TraversalDirection dir) { if (!traversalNodeStack.empty() && node != cacheLastTraversalNode) { /* ** we didn't get here by arrow key, @@ -224,7 +229,7 @@ private void cacheTraversal(Node node, Direction dir) { /* ** Next or Previous cancels the row caching */ - if (dir == Direction.NEXT || dir == Direction.PREVIOUS) { + if (dir == TraversalDirection.NEXT || dir == TraversalDirection.PREVIOUS) { traversalNodeStack.clear(); reverseDirection = false; } else { @@ -260,9 +265,9 @@ private void cacheTraversal(Node node, Direction dir) { private static final Function BOUNDS_BOTTOM_SIDE = t -> t.getMaxY(); - protected Node getNearestNodeUpOrDown(Bounds currentB, Bounds originB, TraversalContext context, Direction dir) { + protected Node getNearestNodeUpOrDown(Bounds currentB, Bounds originB, Parent root, TraversalDirection dir) { - List nodes = context.getAllTargetNodes(); + List nodes = TraversalUtils.getAllTargetNodes(root); Function ySideInDirection = dir == DOWN ? BOUNDS_BOTTOM_SIDE : BOUNDS_TOP_SIDE; Function ySideInOpositeDirection = dir == DOWN ? BOUNDS_TOP_SIDE : BOUNDS_BOTTOM_SIDE; @@ -554,9 +559,9 @@ protected Node getNearestNodeUpOrDown(Bounds currentB, Bounds originB, Traversal private static final Function BOUNDS_RIGHT_SIDE = t -> t.getMaxX(); - protected Node getNearestNodeLeftOrRight(Bounds currentB, Bounds originB, TraversalContext context, Direction dir) { + protected Node getNearestNodeLeftOrRight(Bounds currentB, Bounds originB, Parent root, TraversalDirection dir) { - List nodes = context.getAllTargetNodes(); + List nodes = TraversalUtils.getAllTargetNodes(root); Function xSideInDirection = dir == LEFT ? BOUNDS_LEFT_SIDE : BOUNDS_RIGHT_SIDE; Function xSideInOpositeDirection = dir == LEFT ? BOUNDS_RIGHT_SIDE : BOUNDS_LEFT_SIDE; diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/ParentTraversalEngine.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/OverridableTraversalPolicy.java similarity index 61% rename from modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/ParentTraversalEngine.java rename to modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/OverridableTraversalPolicy.java index 1307012c427..614a9be4f01 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/ParentTraversalEngine.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/OverridableTraversalPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, 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 @@ -22,31 +22,35 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ - package com.sun.javafx.scene.traversal; +import javafx.scene.Node; import javafx.scene.Parent; +import javafx.scene.TraversalDirection; +import javafx.scene.TraversalPolicy; /** - * This traversal engine can be used to change algorithm for some specific parent/control that needs different traversal. - * This can be achieved by setting such engine using {@link Parent#setImpl_traversalEngine(ParentTraversalEngine)} - * and providing a special Algorithm implementation. - * - * Alternatively, the traversal engine can be w/o an algorithm and used just for listening to focus changes inside the specified parent. + * Non-traversable policy which allows for overriding of {@link #isParentTraversable(Parent)}. */ -public final class ParentTraversalEngine extends TraversalEngine{ - - private final Parent root; +public class OverridableTraversalPolicy extends TraversalPolicy { private Boolean overridenTraversability; - public ParentTraversalEngine(Parent root, Algorithm algorithm) { - super(algorithm); - this.root = root; + public OverridableTraversalPolicy() { } - public ParentTraversalEngine(Parent root) { - super(); - this.root = root; + @Override + public Node select(Parent root, Node owner, TraversalDirection dir) { + return null; + } + + @Override + public Node selectFirst(Parent root) { + return null; + } + + @Override + public Node selectLast(Parent root) { + return null; } /** @@ -57,14 +61,11 @@ public void setOverriddenFocusTraversability(Boolean value) { } @Override - protected Parent getRoot() { - return root; - } - - public boolean isParentTraversable() { + public boolean isParentTraversable(Parent root) { // This means the traversability can be overriden only for traversable root. // If user explicitly disabled traversability, we don't set it back to true - return overridenTraversability != null ? root.isFocusTraversable() && overridenTraversability : root.isFocusTraversable(); + return overridenTraversability != null ? + root.isFocusTraversable() && overridenTraversability : + root.isFocusTraversable(); } - } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/SceneTraversalEngine.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/SceneTraversalEngine.java deleted file mode 100644 index 1c7b7e5c33c..00000000000 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/SceneTraversalEngine.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2011, 2022, 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 com.sun.javafx.scene.traversal; - -import javafx.scene.Parent; -import javafx.scene.Scene; - -/** - * Traversal engine for Scene. - */ -public final class SceneTraversalEngine extends TopMostTraversalEngine{ - - private final Scene scene; - - public SceneTraversalEngine(Scene scene) { - this.scene = scene; - } - - @Override - protected Parent getRoot() { - return scene.getRoot(); - } -} diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/SubSceneTraversalEngine.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/SubSceneTraversalEngine.java deleted file mode 100644 index 9b977178bbb..00000000000 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/SubSceneTraversalEngine.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2011, 2022, 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 com.sun.javafx.scene.traversal; - -import javafx.scene.Parent; -import javafx.scene.SubScene; - -/** - * Traversal engine for subscene - */ -public final class SubSceneTraversalEngine extends TopMostTraversalEngine{ - - private final SubScene subScene; - - public SubSceneTraversalEngine(SubScene scene) { - this.subScene = scene; - } - - @Override - protected Parent getRoot() { - return subScene.getRoot(); - } -} diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TabOrderHelper.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TabOrderHelper.java index 9dac1b9ca14..f48302f3c31 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TabOrderHelper.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TabOrderHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, 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 @@ -25,30 +25,33 @@ package com.sun.javafx.scene.traversal; -import com.sun.javafx.scene.NodeHelper; -import com.sun.javafx.scene.ParentHelper; +import java.util.List; import javafx.collections.ObservableList; import javafx.scene.Node; import javafx.scene.Parent; - -import java.util.List; +import javafx.scene.TraversalPolicy; +import com.sun.javafx.scene.NodeHelper; final class TabOrderHelper { private static Node findPreviousFocusableInList(List nodeList, int startIndex) { for (int i = startIndex ; i >= 0 ; i--) { Node prevNode = nodeList.get(i); - // ParentTraverEngine can override traversability, so we need to check it first - if (isDisabledOrInvisible(prevNode)) continue; - final ParentTraversalEngine traversalEngine = prevNode instanceof Parent - ? ParentHelper.getTraversalEngine((Parent) prevNode) : null; - if (prevNode instanceof Parent) { - if (traversalEngine != null && traversalEngine.canTraverse()) { - Node selected = traversalEngine.selectLast(); + // TraversalPolicy can override traversability, so we need to check it first + if (isDisabledOrInvisible(prevNode)) { + continue; + } + TraversalPolicy policy = prevNode instanceof Parent p ? p.getTraversalPolicy() : null; + if (prevNode instanceof Parent p) { + if (policy != null) { + Node selected = policy.selectLast(p); if (selected != null) { return selected; } + if (policy.isParentTraversable(p)) { + return prevNode; + } } else { - List prevNodesList = ((Parent) prevNode).getChildrenUnmodifiable(); + List prevNodesList = p.getChildrenUnmodifiable(); if (prevNodesList.size() > 0) { Node newNode = findPreviousFocusableInList(prevNodesList, prevNodesList.size() - 1); if (newNode != null) { @@ -57,9 +60,7 @@ private static Node findPreviousFocusableInList(List nodeList, int startIn } } } - if (traversalEngine != null - ? traversalEngine.isParentTraversable() - : prevNode.isFocusTraversable()) { + if (prevNode.isFocusTraversable()) { return prevNode; } } @@ -97,9 +98,8 @@ public static Node findPreviousFocusablePeer(Node node, Parent root) { Parent parent = startNode.getParent(); if (parent != null) { // If the parent itself is traversable, select it - final ParentTraversalEngine parentEngine - = ParentHelper.getTraversalEngine(parent); - if (parentEngine != null ? parentEngine.isParentTraversable() : parent.isFocusTraversable()) { + TraversalPolicy policy = parent.getTraversalPolicy(); + if (policy != null ? policy.isParentTraversable(parent) : parent.isFocusTraversable()) { newNode = parent; } else { peerNodes = findPeers(parent); @@ -130,18 +130,16 @@ private static List findPeers(Node node) { private static Node findNextFocusableInList(List nodeList, int startIndex) { for (int i = startIndex ; i < nodeList.size() ; i++) { Node nextNode = nodeList.get(i); - if (isDisabledOrInvisible(nextNode)) continue; - final ParentTraversalEngine traversalEngine = nextNode instanceof Parent - ? ParentHelper.getTraversalEngine((Parent) nextNode) : null; - // ParentTraverEngine can override traversability, so we need to check it first - if (traversalEngine != null - ? traversalEngine.isParentTraversable() - : nextNode.isFocusTraversable()) { - return nextNode; + if (isDisabledOrInvisible(nextNode)) { + continue; } - else if (nextNode instanceof Parent) { - if (traversalEngine!= null && traversalEngine.canTraverse()) { - Node selected = traversalEngine.selectFirst(); + // TraversalPolicy can override traversability, so we need to check it first + if (isParentTraversable(nextNode)) { + return nextNode; + } else if (nextNode instanceof Parent p) { + TraversalPolicy policy = p.getTraversalPolicy(); + if (policy != null) { + Node selected = policy.selectFirst(p); if (selected != null) { return selected; } else { @@ -149,7 +147,7 @@ else if (nextNode instanceof Parent) { continue; } } - List nextNodesList = ((Parent)nextNode).getChildrenUnmodifiable(); + List nextNodesList = p.getChildrenUnmodifiable(); if (nextNodesList.size() > 0) { Node newNode = findNextFocusableInList(nextNodesList, 0); if (newNode != null) { @@ -166,8 +164,8 @@ public static Node findNextFocusablePeer(Node node, Parent root, boolean travers Node newNode = null; // First, try to find next peer among the node children - if (traverseIntoCurrent && node instanceof Parent) { - newNode = findNextFocusableInList(((Parent)node).getChildrenUnmodifiable(), 0); + if (traverseIntoCurrent && node instanceof Parent p) { + newNode = findNextFocusableInList(p.getChildrenUnmodifiable(), 0); } // Next step is to select the siblings "to the right" @@ -204,57 +202,72 @@ public static Node findNextFocusablePeer(Node node, Parent root, boolean travers return newNode; } - public static Node getFirstTargetNode(Parent p) { - if (p == null || isDisabledOrInvisible(p)) return null; - final ParentTraversalEngine traversalEngine - = ParentHelper.getTraversalEngine(p); - if (traversalEngine!= null && traversalEngine.canTraverse()) { - Node selected = traversalEngine.selectFirst(); + public static Node getFirstTargetNode(Parent parent) { + if (parent == null || isDisabledOrInvisible(parent)) { + return null; + } + + TraversalPolicy policy = parent.getTraversalPolicy(); + if (policy != null) { + Node selected = policy.selectFirst(parent); if (selected != null) { return selected; } } - List parentsNodes = p.getChildrenUnmodifiable(); + + List parentsNodes = parent.getChildrenUnmodifiable(); for (Node n : parentsNodes) { - if (isDisabledOrInvisible(n)) continue; - final ParentTraversalEngine parentEngine = n instanceof Parent - ? ParentHelper.getTraversalEngine((Parent)n) : null; - if (parentEngine != null ? parentEngine.isParentTraversable() : n.isFocusTraversable()) { + if (isDisabledOrInvisible(n)) { + continue; + } + if (isParentTraversable(n)) { return n; } - if (n instanceof Parent) { - Node result = getFirstTargetNode((Parent)n); - if (result != null) return result; + if (n instanceof Parent p) { + Node result = getFirstTargetNode(p); + if (result != null) + return result; } } return null; } - public static Node getLastTargetNode(Parent p) { - if (p == null || isDisabledOrInvisible(p)) return null; - final ParentTraversalEngine traversalEngine - = ParentHelper.getTraversalEngine(p); - if (traversalEngine!= null && traversalEngine.canTraverse()) { - Node selected = traversalEngine.selectLast(); + public static Node getLastTargetNode(Parent parent) { + if (parent == null || isDisabledOrInvisible(parent)) return null; + TraversalPolicy policy = parent.getTraversalPolicy(); + if (policy != null) { + Node selected = policy.selectLast(parent); if (selected != null) { return selected; } } - List parentsNodes = p.getChildrenUnmodifiable(); + + List parentsNodes = parent.getChildrenUnmodifiable(); for (int i = parentsNodes.size() - 1; i >= 0; --i) { Node n = parentsNodes.get(i); - if (isDisabledOrInvisible(n)) continue; - - if (n instanceof Parent) { - Node result = getLastTargetNode((Parent) n); - if (result != null) return result; + if (isDisabledOrInvisible(n)) { + continue; + } + if (n instanceof Parent p) { + Node result = getLastTargetNode(p); + if (result != null) { + return result; + } } - final ParentTraversalEngine parentEngine = n instanceof Parent - ? ParentHelper.getTraversalEngine((Parent) n) : null; - if (parentEngine != null ? parentEngine.isParentTraversable() : n.isFocusTraversable()) { + if (isParentTraversable(n)) { return n; } } return null; } + + private static boolean isParentTraversable(Node n) { + if (n instanceof Parent p) { + TraversalPolicy policy = p.getTraversalPolicy(); + if (policy != null) { + return policy.isParentTraversable(p); + } + } + return n.isFocusTraversable(); + } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TopMostTraversalEngine.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TopMostTraversalEngine.java index 9d3b6d3f538..81a8292c7d1 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TopMostTraversalEngine.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TopMostTraversalEngine.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, 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 @@ -25,58 +25,39 @@ package com.sun.javafx.scene.traversal; -import com.sun.javafx.scene.NodeHelper; -import com.sun.javafx.scene.ParentHelper; import javafx.scene.Node; import javafx.scene.Parent; +import javafx.scene.TraversalDirection; +import javafx.scene.TraversalPolicy; +import com.sun.javafx.scene.NodeHelper; /** * This is the class for all top-level traversal engines in scenes and subscenes. * These traversal engines are created automatically and can only have the default algorithm. * - * These engines should be used by calling {@link #trav(javafx.scene.Node, Direction)}, {@link #traverseToFirst()} and + * These engines should be used by calling {@link #trav(javafx.scene.Node, TraversalDirection)}, {@link #traverseToFirst()} and * {@link #traverseToLast()} methods. These methods do the actual traversal - selecting the Node that's should be focused next and * focusing it. Also, listener calls are handled by top-most traversal engines. * select* methods can be used as well, but will *not* transfer the focus to the result, they are just query methods. */ -public abstract class TopMostTraversalEngine extends TraversalEngine{ - - protected TopMostTraversalEngine() { - /* - * for 2d behaviour from TAB use : - * algorithm = new WeightedClosestCorner(); - * for Container sequence TAB behaviour and 2d arrow behaviour use : - * algorithm = new ContainerTabOrder(); - * for 2D arrow behaviour with a target bias and a stack use : - * algorithm = new Biased2DWithStack(); - */ - super(DEFAULT_ALGORITHM); - } - - /** - * For testing purposes only! - */ - TopMostTraversalEngine(Algorithm algorithm) { - super(algorithm); - } - +public final class TopMostTraversalEngine { /** * Traverse the focus to the next node in the specified direction. * * @param node The starting node to traverse from * @param dir the traversal direction - * @param method the traversal method + * @param focusVisible whether the focused Node should visible indicate focus * @return the new focus owner or null if none found (in that case old focus owner is still valid) */ - public final Node trav(Node node, Direction dir, TraversalMethod method) { + public static final Node trav(Parent root, Node node, TraversalDirection dir, boolean focusVisible) { Node newNode = null; Parent p = node.getParent(); Node traverseNode = node; while (p != null) { - // First find the nearest traversal engine override (i.e. a ParentTraversalEngine that is traversable) - ParentTraversalEngine engine = ParentHelper.getTraversalEngine(p); - if (engine != null && engine.canTraverse()) { - newNode = engine.select(node, dir); + // First find the nearest traversal policy override + TraversalPolicy policy = p.getTraversalPolicy(); + if (policy != null) { + newNode = policy.select(p, node, dir); if (newNode != null) { break; } else { @@ -84,8 +65,8 @@ public final Node trav(Node node, Direction dir, TraversalMethod method) { // So now we try to traverse from the whole parent (associated with that traversal engine) // by a traversal engine that's higher in the hierarchy traverseNode = p; - if (dir == Direction.NEXT) { - dir = Direction.NEXT_IN_LINE; + if (dir == TraversalDirection.NEXT) { + dir = TraversalDirection.NEXT_IN_LINE; } } } @@ -93,60 +74,38 @@ public final Node trav(Node node, Direction dir, TraversalMethod method) { } // No engine override was able to find the Node in the specified direction, so if (newNode == null) { - newNode = select(traverseNode, dir); + newNode = TraversalPolicy.getDefault().select(root, traverseNode, dir); } if (newNode == null) { - if (dir == Direction.NEXT || dir == Direction.NEXT_IN_LINE) { - newNode = selectFirst(); - } else if (dir == Direction.PREVIOUS) { - newNode = selectLast(); + if (dir == TraversalDirection.NEXT || dir == TraversalDirection.NEXT_IN_LINE) { + newNode = TraversalPolicy.getDefault().selectFirst(root); + } else if (dir == TraversalDirection.PREVIOUS) { + newNode = TraversalPolicy.getDefault().selectLast(root); } } if (newNode != null) { - focusAndNotify(newNode, method); + focusAndNotify(root, newNode, focusVisible); } return newNode; } - private void focusAndNotify(Node newNode, TraversalMethod method) { - if (method == TraversalMethod.KEY) { - NodeHelper.requestFocusVisible(newNode); + private static void focusAndNotify(Parent root, Node n, boolean focusVisible) { + if (focusVisible) { + NodeHelper.requestFocusVisible(n); } else { - newNode.requestFocus(); - } - - notifyTreeTraversedTo(newNode); - } - - private void notifyTreeTraversedTo(Node newNode) { - Parent p = newNode.getParent(); - while (p != null) { - final ParentTraversalEngine traversalEngine = ParentHelper.getTraversalEngine(p); - if (traversalEngine != null) { - traversalEngine.notifyTraversedTo(newNode); - } - p = p.getParent(); + n.requestFocus(); } - notifyTraversedTo(newNode); } /** * Set focus on the first Node in this context (if any) * @return the first node or null if there's none */ - public final Node traverseToFirst() { - Node n = selectFirst(); - if (n != null) focusAndNotify(n, TraversalMethod.DEFAULT); - return n; - } - - /** - * Set focus on the last Node in this context (if any) - * @return the last node or null if there's none - */ - public final Node traverseToLast() { - Node n = selectLast(); - if (n != null) focusAndNotify(n, TraversalMethod.DEFAULT); + public static final Node traverseToFirst(Parent root) { + Node n = TraversalPolicy.getDefault().selectFirst(root); + if (n != null) { + focusAndNotify(root, n, false); + } return n; } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TraversalContext.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TraversalContext.java deleted file mode 100644 index d7fc04e67e6..00000000000 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TraversalContext.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2011, 2014, 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 com.sun.javafx.scene.traversal; - -import javafx.geometry.Bounds; -import javafx.scene.Node; -import javafx.scene.Parent; - -import java.util.List; - -public interface TraversalContext { - - /** - * Returns all possible targets within the context - */ - List getAllTargetNodes(); - - /** - * Returns layout bounds of the Node in the relevant (Sub)Scene. Note that these bounds are the most important for traversal - * as they define the final position within the scene. - */ - Bounds getSceneLayoutBounds(Node node); - - /** - * The root for this context, Traversal should be done only within the root - */ - Parent getRoot(); - - /** - * If the TraversalEngine does not want to handle traversal inside some inner child (Parent), it can use this method to apply - * default algorithm inside that Parent and return the first Node - */ - Node selectFirstInParent(Parent parent); - - /** - * If the TraversalEngine does not want to handle traversal inside some inner child (Parent), it can use this method to apply - * default algorithm inside that Parent and return the last Node - */ - Node selectLastInParent(Parent parent); - - /** - * If the TraversalEngine does not want to handle traversal inside some inner child (Parent), it can use this method to apply - * default algorithm inside that Parent and return the next Node within the Parent or null if there's no successor. - * @param subTreeRoot this will be used as a root of the traversal. Should be a Node that is still handled by the current TraversalEngine, - * but it's content is not. - */ - Node selectInSubtree(Parent subTreeRoot, Node from, Direction dir); -} diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TraversalEngine.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TraversalEngine.java deleted file mode 100644 index 6eec69bf36b..00000000000 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TraversalEngine.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (c) 2010, 2025, 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 com.sun.javafx.scene.traversal; - -import com.sun.javafx.application.PlatformImpl; -import com.sun.javafx.scene.NodeHelper; -import javafx.geometry.BoundingBox; -import javafx.geometry.Bounds; -import javafx.scene.Node; -import javafx.scene.Parent; - -import java.util.ArrayList; -import java.util.List; - -/** - * This is abstract class for a traversal engine. There are 2 types : {@link com.sun.javafx.scene.traversal.ParentTraversalEngine} - * to be used in {@link Parent#setTraversalEngine(ParentTraversalEngine)} to override default behavior - * and {@link com.sun.javafx.scene.traversal.TopMostTraversalEngine} that is the default traversal engine for scene and subscene. - * - * Every engine is basically a wrapper of an algorithm + some specific parent (or scene/subscene), which define engine's root. - */ -public abstract class TraversalEngine{ - - /** - * This is the default algorithm for the running platform. It's the algorithm that's used in TopMostTraversalEngine - */ - static final Algorithm DEFAULT_ALGORITHM = PlatformImpl.isContextual2DNavigation() ? new Heuristic2D() : new ContainerTabOrder(); - - private final TraversalContext context = new EngineContext(); // This is the context used in calls to this engine's algorithm - // This is a special context that's used when invoking select "callbacks" to default algorithm in other contexts - private final TempEngineContext tempEngineContext = new TempEngineContext(); - protected final Algorithm algorithm; - - private final Bounds initialBounds = new BoundingBox(0, 0, 1, 1); - private final ArrayList listeners = new ArrayList<>(); - - /** - * Creates engine with the specified algorithm - * @param algorithm - */ - protected TraversalEngine(Algorithm algorithm) { - this.algorithm = algorithm; - } - - /** - * Creates engine with no algorithm. This makes all the select* calls invalid. - * @see #canTraverse() - */ - protected TraversalEngine() { - this.algorithm = null; - } - - /** - * Add a listener to traversal engine. The listener is notified whenever focus is changed by traversal inside the associated scene or parent. - * This can be used with ParentTraversalEngine that has no algorithm to observe changes to the focus inside the parent. - * @param listener - */ - public final void addTraverseListener(TraverseListener listener) { - listeners.add(listener); - } - - /** - * Fire notifications for listeners. This is called from the TopMostTraversalEngine - * @param newNode the node which has been focused - */ - final void notifyTraversedTo(Node newNode) { - for (TraverseListener l : listeners) { - l.onTraverse(newNode, getLayoutBounds(newNode, getRoot())); - } - } - - /** - * Returns the node that is in the direction {@code dir} starting from the Node {@code from} using the engine's algorithm. - * Null means there is no Node in that direction - * @param from the node to start traversal from - * @param dir the direction of traversal - * @return the subsequent node in the specified direction or null if none - * @throws java.lang.NullPointerException if there is no algorithm - */ - public final Node select(Node from, Direction dir) { - return algorithm.select(from, dir, context); - } - - /** - * Returns the first node in this engine's context (scene/parent) using the engine's algorithm. - * This can be null only if there are no traversable nodes - * @return The first node or null if none exists - * @throws java.lang.NullPointerException if there is no algorithm - */ - public final Node selectFirst() { - return algorithm.selectFirst(context); - } - - /** - * Returns the last node in this engine's context (scene/parent) using the engine's algorithm. - * This can be null only if there are no traversable nodes - * @return The last node or null if none exists - * @throws java.lang.NullPointerException if there is no algorithm - */ - public final Node selectLast() { - return algorithm.selectLast(context); - } - - /** - * The root of this engine's context. This is the node that is the root of the tree that is traversed by this engine. - * @return This engine's root - */ - protected abstract Parent getRoot(); - - /** - * Returns true only if there's specified algorithm for this engine. Otherwise, this engine cannot be used for traversal. - * The engine might be still useful however, e.g. for listening on traversal changes. - * @return - */ - public final boolean canTraverse() { - return algorithm != null; - } - - /** - * Gets the appropriate bounds for the given node, transformed into - * the scene's or the specified node's coordinates. - * @return bounds of node in {@code forParent} coordinates or scene coordinates if {@code forParent} is null - */ - private Bounds getLayoutBounds(Node n, Parent forParent) { - final Bounds bounds; - if (n != null) { - if (forParent == null) { - bounds = n.localToScene(n.getLayoutBounds()); - } else { - bounds = forParent.sceneToLocal(n.localToScene(n.getLayoutBounds())); - } - } else { - bounds = initialBounds; - } - return bounds; - } - - // This is the engine context passed algorithm on select calls - private final class EngineContext extends BaseEngineContext { - @Override - public Parent getRoot() { - return TraversalEngine.this.getRoot(); - } - } - - // This is the engine context passed to algorithm on select callbacks from other contexts. - // It can change the root to the node defined in "selectFirstInParent", "selectLastInParent" or - // "selectInSubtree" methods - private final class TempEngineContext extends BaseEngineContext { - private Parent root; - - @Override - public Parent getRoot() { - return root; - } - - public void setRoot(Parent root) { - this.root = root; - } - } - - /** - * The base class for all engine contexts - */ - private abstract class BaseEngineContext implements TraversalContext { - - /** - * Returns all traversable nodes in the context's (engine's) root - */ - @Override - public List getAllTargetNodes() { - final List targetNodes = new ArrayList<>(); - addFocusableChildrenToList(targetNodes, getRoot()); - return targetNodes; - } - - @Override - public Bounds getSceneLayoutBounds(Node n) { - return getLayoutBounds(n, null); - } - - private void addFocusableChildrenToList(List list, Parent parent) { - List parentsNodes = parent.getChildrenUnmodifiable(); - for (Node n : parentsNodes) { - if (n.isFocusTraversable() && !n.isFocused() && NodeHelper.isTreeVisible(n) && !n.isDisabled()) { - list.add(n); - } - if (n instanceof Parent) { - addFocusableChildrenToList(list, (Parent)n); - } - } - } - - // All of the methods below are callbacks from traversal context to the default algorithm. - // They can be used to obtain "default" result for the specified subtree. - // This is useful when there is some algorithm that overrides behavior for a Parent but parent's children - // should be again traversed by default algorithm. - @Override - public Node selectFirstInParent(Parent parent) { - tempEngineContext.setRoot(parent); - return DEFAULT_ALGORITHM.selectFirst(tempEngineContext); - } - - @Override - public Node selectLastInParent(Parent parent) { - tempEngineContext.setRoot(parent); - return DEFAULT_ALGORITHM.selectLast(tempEngineContext); - } - - @Override - public Node selectInSubtree(Parent subTreeRoot, Node from, Direction dir) { - tempEngineContext.setRoot(subTreeRoot); - return DEFAULT_ALGORITHM.select(from, dir, tempEngineContext); - } - } -} diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TraversalMethod.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TraversalMethod.java deleted file mode 100644 index d00f12a6d48..00000000000 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TraversalMethod.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2022, 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 com.sun.javafx.scene.traversal; - -/** - * Specifies the traversal method. - */ -public enum TraversalMethod { - /** - * Traversal was initiated programmatically or by clicking. - */ - DEFAULT, - - /** - * Traversal was initiated by pressing a key on the keyboard. - */ - KEY -} diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TraversalUtils.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TraversalUtils.java new file mode 100644 index 00000000000..4cb1c621377 --- /dev/null +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TraversalUtils.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2024, 2025, 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 com.sun.javafx.scene.traversal; + +import java.util.ArrayList; +import java.util.List; +import javafx.geometry.BoundingBox; +import javafx.geometry.Bounds; +import javafx.geometry.NodeOrientation; +import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.SubScene; +import javafx.scene.TraversalDirection; +import javafx.scene.TraversalPolicy; +import com.sun.javafx.application.PlatformImpl; +import com.sun.javafx.scene.NodeHelper; + +public final class TraversalUtils { + public static final TraversalPolicy DEFAULT_POLICY = PlatformImpl.isContextual2DNavigation() ? new Heuristic2D() : new ContainerTabOrder(); + public static final TraversalPolicy EMPTY_POLICY = initEmptyTraversablePolicy(); + + private static final Bounds INITIAL_BOUNDS = new BoundingBox(0, 0, 1, 1); + + private TraversalUtils() { + } + + public static TraversalPolicy createDefaultTraversalAlgorithm() { + return PlatformImpl.isContextual2DNavigation() ? new Heuristic2D() : new ContainerTabOrder(); + } + + /** + * Gets the appropriate bounds for the given node, transformed into the specified node's coordinates. + * This method returns {@code null} if {@code n} or {@code forParent} is null + * or the node is not a part of the scene graph. + * @return bounds of node in {@code forParent} coordinates, or null + */ + public static Bounds getLayoutBounds(Node n, Parent forParent) { + if ((n != null) && (forParent != null)) { + Bounds b = n.localToScene(n.getLayoutBounds()); + if (b != null) { + return forParent.sceneToLocal(b); + } + } + return null; + } + + /** + * Gets the appropriate bounds for the given node, transformed into the scene's coordinates. + * @return bounds of node in scene coordinates + */ + public static Bounds getLayoutBoundsInSceneCoordinates(Node n) { + if (n != null) { + return n.localToScene(n.getLayoutBounds()); + } else { + return INITIAL_BOUNDS; + } + } + + private static TraversalPolicy initEmptyTraversablePolicy() { + return new TraversalPolicy() { + @Override + public Node select(Parent root, Node owner, TraversalDirection dir) { + return null; + } + + @Override + public Node selectFirst(Parent root) { + return null; + } + + @Override + public Node selectLast(Parent root) { + return null; + } + }; + } + + /** + * Returns all possible targets within the traversal root. + * + * @param root the traversal root + * @return the List of all possible targets within the traversal root + */ + public static final List getAllTargetNodes(Parent root) { + final List targetNodes = new ArrayList<>(); + addFocusableChildrenToList(targetNodes, root); + return targetNodes; + } + + private static final void addFocusableChildrenToList(List list, Parent parent) { + List parentsNodes = parent.getChildrenUnmodifiable(); + for (Node n : parentsNodes) { + if (n.isFocusTraversable() && !n.isFocused() && NodeHelper.isTreeVisible(n) && !n.isDisabled()) { + list.add(n); + } + if (n instanceof Parent p) { + addFocusableChildrenToList(list, p); + } + } + } + + public static Node findNextFocusableNode(Parent root, Node node, boolean traverseIntoCurrent) { + return TabOrderHelper.findNextFocusablePeer(node, root, traverseIntoCurrent); + } + + public static Node findPreviousFocusableNode(Parent root, Node node) { + return TabOrderHelper.findPreviousFocusablePeer(node, root); + } + + /** + * Returns true if the traversal is considered a forward movement. + * @return true if forward + */ + public static boolean isForward(TraversalDirection d) { + switch (d) { + case UP: + case LEFT: + case PREVIOUS: + return false; + } + return true; + } + + /** + * Returns the direction with respect to the node's orientation. It affect's only arrow keys however, so it's not + * an error to ignore this call if handling only next/previous traversal. + * + * @param orientation the node orientation + * @return the traverse direction + */ + public static TraversalDirection getDirectionForNodeOrientation(TraversalDirection d, NodeOrientation orientation) { + if (orientation == NodeOrientation.RIGHT_TO_LEFT) { + switch (d) { + case LEFT: + return TraversalDirection.RIGHT; + case RIGHT: + return TraversalDirection.LEFT; + } + } + return d; + } + + /** + * Focus traversal. + * @param node + * @param dir + * @param focusVisible + * @return + */ + public static boolean traverse(Node node, TraversalDirection dir, boolean focusVisible) { + if (node != null) { + SubScene ss = NodeHelper.getSubScene(node); + if (ss != null) { + return TopMostTraversalEngine.trav(ss.getRoot(), node, dir, focusVisible) != null; + } + + Scene sc = node.getScene(); + if (sc != null) { + return TopMostTraversalEngine.trav(sc.getRoot(), node, dir, focusVisible) != null; + } + } + return false; + } +} diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TraverseListener.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TraverseListener.java deleted file mode 100644 index a6a8f8d30de..00000000000 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/TraverseListener.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2010, 2013, 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 com.sun.javafx.scene.traversal; - - -import javafx.geometry.Bounds; -import javafx.scene.Node; - - -public interface TraverseListener { - - public void onTraverse(Node node, Bounds bounds); - -} diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/WeightedClosestCorner.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/WeightedClosestCorner.java deleted file mode 100644 index 7228a46b2f0..00000000000 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/scene/traversal/WeightedClosestCorner.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright (c) 2010, 2024, 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 com.sun.javafx.scene.traversal; - -import java.util.List; -import javafx.geometry.Bounds; -import javafx.geometry.Point2D; -import javafx.scene.Node; - -import static com.sun.javafx.scene.traversal.Direction.*; - -/** - * First search for on-axis nodes. A node is on-axis if any part of its - * closest edge lies between the pair of rays cast in the traversal direction - * from the corners of the originating node. - * - * If there are multiple on-axis nodes, the closest is chosen. - * - * If there is a tie for the closest on-axis node, the tie is broken - * by choosing the one whose center is closest to the center of the - * originating node. TODO: investigate alternative tiebreaker algorithms - * (filed as JDK-8090927). - * - * If there are no on-axis nodes, compute the distance OUT (in the traversal - * direction) and the distance to the SIDE (perpendicular to the traversal - * direction) and compute a distance metric OUT + WEIGHT * SIDE. Choose the - * node with the smallest distance metric. - * - * TODO: presumably WEIGHT is greater than one, so that nodes farther OUT - * but close (and not on) axis are chosen in preference to nodes that are - * physically closer but are farther off to the side. Determine WEIGHT - * somehow, possibly empirically (filed as JDK-8091219). - */ - -public class WeightedClosestCorner implements Algorithm { - - - WeightedClosestCorner() { - } - - private boolean isOnAxis(Direction dir, Bounds cur, Bounds tgt) { - - final double cmin, cmax, tmin, tmax; - - if (dir == UP || dir == DOWN) { - cmin = cur.getMinX(); - cmax = cur.getMaxX(); - tmin = tgt.getMinX(); - tmax = tgt.getMaxX(); - } - else { // dir == LEFT || dir == RIGHT - cmin = cur.getMinY(); - cmax = cur.getMaxY(); - tmin = tgt.getMinY(); - tmax = tgt.getMaxY(); - } - - return tmin <= cmax && tmax >= cmin; - } - - /** - * Compute the out-distance to the near edge of the target in the - * traversal direction. Negative means the near edge is "behind". - */ - private double outDistance(Direction dir, Bounds cur, Bounds tgt) { - - final double distance; - - if (dir == UP) { - distance = cur.getMinY() - tgt.getMaxY(); - } - else if (dir == DOWN) { - distance = tgt.getMinY() - cur.getMaxY(); - } - else if (dir == LEFT) { - distance = cur.getMinX() - tgt.getMaxX(); - } - else { // dir == RIGHT - distance = tgt.getMinX() - cur.getMaxX(); - } - - return distance; - } - - /** - * Computes the side distance from current center to target center. - * Always positive. This is only used for on-axis nodes. - */ - private double centerSideDistance(Direction dir, Bounds cur, Bounds tgt) { - - final double cc; // current center - final double tc; // target center - - if (dir == UP || dir == DOWN) { - cc = cur.getMinX() + cur.getWidth() / 2.0f; - tc = tgt.getMinX() + tgt.getWidth() / 2.0f; - } - else { // dir == LEFT || dir == RIGHT - cc = cur.getMinY() + cur.getHeight() / 2.0f; - tc = tgt.getMinY() + tgt.getHeight() / 2.0f; - } - - return Math.abs(tc - cc); - //return (tc > cc) ? tc - cc : cc - tc; - } - - /** - * Computes the side distance between the closest corners of the current - * and target. Always positive. This is only used for off-axis nodes. - */ - private double cornerSideDistance(Direction dir, Bounds cur, Bounds tgt) { - - final double distance; - - if (dir == UP || dir == DOWN) { - - if (tgt.getMinX() > cur.getMaxX()) { - // on the right - distance = tgt.getMinX() - cur.getMaxX(); - } - else { - // on the left - distance = cur.getMinX() - tgt.getMaxX(); - } - } - else { // dir == LEFT or dir == RIGHT - - if (tgt.getMinY() > cur.getMaxY()) { - // below - distance = tgt.getMinY() - cur.getMaxY(); - } - else { - // above - distance = cur.getMinY() - tgt.getMaxY(); - } - } - return distance; - } - - @Override - public Node select(Node node, Direction dir, TraversalContext context) { - Node newNode = null; - List nodes = context.getAllTargetNodes(); - - int target = traverse(context.getSceneLayoutBounds(node), dir, nodes, context); - if (target != -1) { - newNode = nodes.get(target); - } - - return newNode; - } - - @Override - public Node selectFirst(TraversalContext context) { - List nodes = context.getAllTargetNodes(); - Point2D zeroZero = new Point2D(0,0); - - if (nodes.size() > 0) { - int nodeIndex; - Node nearestNode = nodes.get(0); - double nearestDistance = zeroZero.distance(context.getSceneLayoutBounds(nodes.get(0)).getMinX(), - context.getSceneLayoutBounds(nodes.get(0)).getMinY()); - double distance; - - for (nodeIndex = 1; nodeIndex < nodes.size(); nodeIndex++) { - distance = zeroZero.distance(context.getSceneLayoutBounds(nodes.get(nodeIndex)).getMinX(), - context.getSceneLayoutBounds(nodes.get(nodeIndex)).getMinY()); - if (nearestDistance > distance) { - nearestDistance = distance; - nearestNode = nodes.get(nodeIndex); - } - } - return nearestNode; - } - return null; - } - - @Override - public Node selectLast(TraversalContext context) { - return null; - } - - public int traverse(Bounds origin, Direction dir, List targets, TraversalContext context) { - - final int target; - - if (dir == NEXT || dir == NEXT_IN_LINE || dir == PREVIOUS) { - target = trav1D(origin, dir, targets, context); - } else { - target = trav2D(origin, dir, targets, context); - } - - return target; - } - - private int trav2D(Bounds origin, Direction dir, List targets, TraversalContext context) { - - Bounds bestBounds = null; - double bestMetric = 0.0; - int bestIndex = -1; - - for (int i = 0; i < targets.size(); i++) { - final Bounds targetBounds = context.getSceneLayoutBounds(targets.get(i)); - final double outd = outDistance(dir, origin, targetBounds); - final double metric; - - if (isOnAxis(dir, origin, targetBounds)) { - metric = outd + centerSideDistance(dir, origin, targetBounds) / 100; - } - else { - final double cosd = cornerSideDistance(dir, origin, targetBounds); - metric = 100000 + outd*outd + 9*cosd*cosd; - } - - if (outd < 0.0) { - continue; - } - - if (bestBounds == null || metric < bestMetric) { - bestBounds = targetBounds; - bestMetric = metric; - bestIndex = i; - } - } - - return bestIndex; - } - - /* - * Consider focus targets to have a total order using values - * (minY, minX, hashCode). - */ - private int compare1D(Bounds a, Bounds b) { - - int res = 0; - - // the following use the node's center - final double metric1a = (a.getMinY() + a.getMaxY()) / 2; - final double metric1b = (b.getMinY() + b.getMaxY()) / 2; - final double metric2a = (a.getMinX() + a.getMaxX()) / 2; - final double metric2b = (b.getMinX() + b.getMaxX()) / 2; - final double metric3a = a.hashCode(); - final double metric3b = b.hashCode(); - - if (metric1a < metric1b) { - res = -1; - } - else if (metric1a > metric1b) { - res = 1; - } - else if (metric2a < metric2b) { - res = -1; - } - else if (metric2a > metric2b) { - res = 1; - } - else if (metric3a < metric3b) { - res = -1; - } - else if (metric3a > metric3b) { - res = 1; - } - - return res; - } - - - private int compare1D(Bounds a, Bounds b, Direction dir) { - return (dir != PREVIOUS) ? -compare1D(a, b) : compare1D(a, b); - } - - private int trav1D(Bounds origin, Direction dir, List targets, TraversalContext context) { - int bestSoFar = -1; - int leastSoFar = -1; - - for (int i = 0; i < targets.size(); i++) { - if (leastSoFar == -1 || - compare1D(context.getSceneLayoutBounds(targets.get(i)), - context.getSceneLayoutBounds(targets.get(leastSoFar)), dir) < 0) { - leastSoFar = i; - } - - if (compare1D(context.getSceneLayoutBounds(targets.get(i)), origin, dir) < 0) { - continue; - } - - if (bestSoFar == -1 || - compare1D(context.getSceneLayoutBounds(targets.get(i)), context.getSceneLayoutBounds(targets.get(bestSoFar)), dir) < 0) { - bestSoFar = i; - } - } - - return (bestSoFar == -1) ? leastSoFar : bestSoFar; - } - -} diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/EmbeddedScene.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/EmbeddedScene.java index a84d6b736e2..8b7f081e9fb 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/EmbeddedScene.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/EmbeddedScene.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, 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 @@ -25,31 +25,31 @@ package com.sun.javafx.tk.quantum; -import com.sun.javafx.embed.HostDragStartListener; +import java.nio.ByteOrder; +import java.nio.IntBuffer; import javafx.application.Platform; import javafx.collections.ObservableList; import javafx.event.EventType; +import javafx.scene.TraversalDirection; +import javafx.scene.image.PixelFormat; import javafx.scene.input.InputMethodEvent; import javafx.scene.input.InputMethodRequests; import javafx.scene.input.InputMethodTextRun; import javafx.scene.input.KeyCode; import javafx.scene.input.MouseEvent; -import javafx.scene.image.PixelFormat; -import java.nio.IntBuffer; +import com.sun.glass.ui.Pixels; import com.sun.javafx.cursor.CursorFrame; import com.sun.javafx.embed.AbstractEvents; import com.sun.javafx.embed.EmbeddedSceneDTInterface; import com.sun.javafx.embed.EmbeddedSceneInterface; +import com.sun.javafx.embed.HostDragStartListener; import com.sun.javafx.embed.HostInterface; import com.sun.javafx.scene.input.KeyCodeMap; -import com.sun.javafx.scene.traversal.Direction; import com.sun.javafx.sg.prism.NGNode; import com.sun.javafx.tk.TKClipboard; import com.sun.javafx.tk.Toolkit; import com.sun.prism.paint.Color; import com.sun.prism.paint.Paint; -import com.sun.glass.ui.Pixels; -import java.nio.ByteOrder; final class EmbeddedScene extends GlassScene implements EmbeddedSceneInterface { @@ -194,10 +194,10 @@ public void repaint() { } @Override - public boolean traverseOut(Direction dir) { - if (dir == Direction.NEXT) { + public boolean traverseOut(TraversalDirection dir) { + if (dir == TraversalDirection.NEXT) { return host.traverseFocusOut(true); - } else if (dir == Direction.PREVIOUS) { + } else if (dir == TraversalDirection.PREVIOUS) { return host.traverseFocusOut(false); } return false; diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Node.java b/modules/javafx.graphics/src/main/java/javafx/scene/Node.java index 30a52b085e1..0677a67bf44 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Node.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Node.java @@ -25,9 +25,15 @@ package javafx.scene; - -import com.sun.javafx.geometry.BoundsUtils; -import com.sun.javafx.scene.traversal.TraversalMethod; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.Observable; @@ -59,6 +65,8 @@ import javafx.css.CssMetaData; import javafx.css.ParsedValue; import javafx.css.PseudoClass; +import javafx.css.Selector; +import javafx.css.Style; import javafx.css.StyleConverter; import javafx.css.StyleOrigin; import javafx.css.Styleable; @@ -66,6 +74,11 @@ import javafx.css.StyleableDoubleProperty; import javafx.css.StyleableObjectProperty; import javafx.css.StyleableProperty; +import javafx.css.converter.BooleanConverter; +import javafx.css.converter.CursorConverter; +import javafx.css.converter.EffectConverter; +import javafx.css.converter.EnumConverter; +import javafx.css.converter.SizeConverter; import javafx.event.Event; import javafx.event.EventDispatchChain; import javafx.event.EventDispatcher; @@ -101,27 +114,14 @@ import javafx.scene.input.TransferMode; import javafx.scene.input.ZoomEvent; import javafx.scene.shape.Shape; +import javafx.scene.shape.Shape3D; import javafx.scene.text.Font; import javafx.scene.transform.Rotate; import javafx.scene.transform.Transform; import javafx.stage.Window; import javafx.util.Callback; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - import com.sun.glass.ui.Accessible; import com.sun.glass.ui.Application; -import com.sun.javafx.util.Logging; -import com.sun.javafx.util.TempState; -import com.sun.javafx.util.Utils; import com.sun.javafx.beans.IDProperty; import com.sun.javafx.beans.event.AbstractNotifyListener; import com.sun.javafx.collections.TrackableObservableList; @@ -132,13 +132,6 @@ import com.sun.javafx.css.TransitionDefinitionCssMetaData; import com.sun.javafx.css.TransitionTimer; import com.sun.javafx.css.media.MediaQueryContext; -import javafx.css.Selector; -import javafx.css.Style; -import javafx.css.converter.BooleanConverter; -import javafx.css.converter.CursorConverter; -import javafx.css.converter.EffectConverter; -import javafx.css.converter.EnumConverter; -import javafx.css.converter.SizeConverter; import com.sun.javafx.effect.EffectDirtyBits; import com.sun.javafx.geom.BaseBounds; import com.sun.javafx.geom.BoxBounds; @@ -149,6 +142,9 @@ import com.sun.javafx.geom.transform.BaseTransform; import com.sun.javafx.geom.transform.GeneralTransform3D; import com.sun.javafx.geom.transform.NoninvertibleTransformException; +import com.sun.javafx.geometry.BoundsUtils; +import com.sun.javafx.logging.PlatformLogger; +import com.sun.javafx.logging.PlatformLogger.Level; import com.sun.javafx.perf.PerformanceTracker; import com.sun.javafx.scene.AbstractNode; import com.sun.javafx.scene.BoundsAccessor; @@ -164,16 +160,15 @@ import com.sun.javafx.scene.input.PickResultChooser; import com.sun.javafx.scene.transform.TransformHelper; import com.sun.javafx.scene.transform.TransformUtils; -import com.sun.javafx.scene.traversal.Direction; +import com.sun.javafx.scene.traversal.TraversalUtils; import com.sun.javafx.sg.prism.NGNode; import com.sun.javafx.tk.Toolkit; +import com.sun.javafx.util.Logging; +import com.sun.javafx.util.TempState; +import com.sun.javafx.util.Utils; import com.sun.prism.impl.PrismSettings; import com.sun.scenario.effect.EffectHelper; -import javafx.scene.shape.Shape3D; -import com.sun.javafx.logging.PlatformLogger; -import com.sun.javafx.logging.PlatformLogger.Level; - /** * Base class for scene graph nodes. A scene graph is a set of tree data structures * where every item has zero or one parent, and each item is either @@ -544,11 +539,6 @@ public BooleanProperty showMnemonicsProperty(Node node) { return node.showMnemonicsProperty(); } - @Override - public boolean traverse(Node node, Direction direction, TraversalMethod method) { - return node.traverse(direction, method); - } - @Override public double getPivotX(Node node) { return node.getPivotX(); @@ -8552,19 +8542,6 @@ private void requestFocusVisible() { } } - /** - * Traverses from this node in the direction indicated. Note that this - * node need not actually have the focus, nor need it be focusTraversable. - * However, the node must be part of a scene, otherwise this request - * is ignored. - */ - final boolean traverse(Direction dir, TraversalMethod method) { - if (getScene() == null) { - return false; - } - return getScene().traverse(this, dir, method); - } - /** * Requests to move the focus from this {@code Node} in the specified direction. * The {@code Node} serves as a reference point and does not have to be focused or focusable. @@ -8575,11 +8552,10 @@ final boolean traverse(Direction dir, TraversalMethod method) { * * @param direction the direction of focus traversal, non-null * @return {@code true} if traversal was successful - * @since 24 + * @since 999 TODO */ public final boolean requestFocusTraversal(TraversalDirection direction) { - Direction d = Direction.of(direction); - return traverse(d, TraversalMethod.KEY); + return TraversalUtils.traverse(this, direction, true); } //-------------------------- diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Parent.java b/modules/javafx.graphics/src/main/java/javafx/scene/Parent.java index 1839a59feea..1d1084f3098 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Parent.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Parent.java @@ -25,23 +25,23 @@ package javafx.scene; -import com.sun.javafx.scene.traversal.ParentTraversalEngine; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; +import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener.Change; import javafx.collections.ObservableList; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import com.sun.javafx.util.TempState; -import com.sun.javafx.util.Utils; -import com.sun.javafx.collections.TrackableObservableList; -import com.sun.javafx.collections.VetoableListDecorator; import javafx.css.PseudoClass; import javafx.css.Selector; +import javafx.stage.Window; +import com.sun.javafx.collections.TrackableObservableList; +import com.sun.javafx.collections.VetoableListDecorator; import com.sun.javafx.css.StyleManager; import com.sun.javafx.geom.BaseBounds; import com.sun.javafx.geom.PickRay; @@ -51,16 +51,16 @@ import com.sun.javafx.geom.transform.NoninvertibleTransformException; import com.sun.javafx.scene.CssFlags; import com.sun.javafx.scene.DirtyBits; -import com.sun.javafx.scene.input.PickResultChooser; -import com.sun.javafx.sg.prism.NGGroup; -import com.sun.javafx.sg.prism.NGNode; -import com.sun.javafx.tk.Toolkit; import com.sun.javafx.scene.LayoutFlags; import com.sun.javafx.scene.NodeHelper; import com.sun.javafx.scene.ParentHelper; +import com.sun.javafx.scene.input.PickResultChooser; +import com.sun.javafx.sg.prism.NGGroup; +import com.sun.javafx.sg.prism.NGNode; import com.sun.javafx.stage.WindowHelper; -import java.util.Collections; -import javafx.stage.Window; +import com.sun.javafx.tk.Toolkit; +import com.sun.javafx.util.TempState; +import com.sun.javafx.util.Utils; /** * The base class for all nodes that have children in the scene graph. @@ -137,16 +137,6 @@ public boolean pickChildrenNode(Parent parent, PickRay pickRay, PickResultChoose return parent.pickChildrenNode(pickRay, result); } - @Override - public void setTraversalEngine(Parent parent, ParentTraversalEngine value) { - parent.setTraversalEngine(value); - } - - @Override - public ParentTraversalEngine getTraversalEngine(Parent parent) { - return parent.getTraversalEngine(); - } - @Override public List doGetAllParentStylesheets(Parent parent) { return parent.doGetAllParentStylesheets(); @@ -918,14 +908,28 @@ private void doPickNodeLocal(PickRay pickRay, PickResultChooser result) { return results; } - private ParentTraversalEngine traversalEngine; + /** + * The {@link TraversalPolicy} allows for customizing focus traversal within this + * {@code Parent}'s children as well as traversal outside of this {@code Parent}. + * + * @defaultValue null + * @since 999 TODO + */ + private ObjectProperty traversalPolicy; + + public final ObjectProperty traversalPolicyProperty() { + if (this.traversalPolicy == null) { + this.traversalPolicy = new SimpleObjectProperty<>(this, "traversalPolicy", null); + } + return this.traversalPolicy; + } - private final void setTraversalEngine(ParentTraversalEngine value) { - this.traversalEngine = value; + public final void setTraversalPolicy(TraversalPolicy p) { + traversalPolicyProperty().set(p); } - private final ParentTraversalEngine getTraversalEngine() { - return traversalEngine; + public final TraversalPolicy getTraversalPolicy() { + return traversalPolicy == null ? null : traversalPolicy.get(); } /* ********************************************************************* diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java index 0fbf12c802b..168c51ba1aa 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java @@ -25,41 +25,22 @@ package javafx.scene; -import com.sun.glass.ui.Application; -import com.sun.glass.ui.Accessible; -import com.sun.javafx.scene.ScenePreferences; -import com.sun.javafx.scene.traversal.TraversalMethod; -import com.sun.javafx.util.Logging; -import com.sun.javafx.util.Utils; -import com.sun.javafx.application.PlatformImpl; -import com.sun.javafx.collections.TrackableObservableList; -import com.sun.javafx.css.StyleManager; -import com.sun.javafx.css.media.MediaQueryContext; -import com.sun.javafx.cursor.CursorFrame; -import com.sun.javafx.event.EventQueue; -import com.sun.javafx.event.EventUtil; -import com.sun.javafx.geom.PickRay; -import com.sun.javafx.geom.Vec3d; -import com.sun.javafx.geom.transform.BaseTransform; -import com.sun.javafx.perf.PerformanceTracker; -import com.sun.javafx.scene.CssFlags; -import com.sun.javafx.scene.LayoutFlags; -import com.sun.javafx.scene.SceneEventDispatcher; -import com.sun.javafx.scene.SceneHelper; -import com.sun.javafx.scene.InputMethodStateManager; -import com.sun.javafx.scene.input.DragboardHelper; -import com.sun.javafx.scene.input.ExtendedInputMethodRequests; -import com.sun.javafx.scene.input.InputEventUtils; -import com.sun.javafx.scene.input.PickResultChooser; -import com.sun.javafx.scene.traversal.Direction; -import com.sun.javafx.scene.traversal.SceneTraversalEngine; -import com.sun.javafx.scene.traversal.TopMostTraversalEngine; -import com.sun.javafx.stage.EmbeddedWindow; -import com.sun.javafx.sg.prism.NGCamera; -import com.sun.javafx.sg.prism.NGLightBase; -import com.sun.javafx.tk.*; -import com.sun.prism.impl.PrismSettings; - +import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED; +import java.io.File; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Stream; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.application.ColorScheme; @@ -68,7 +49,16 @@ import javafx.beans.DefaultProperty; import javafx.beans.InvalidationListener; import javafx.beans.NamedArg; -import javafx.beans.property.*; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ObjectPropertyBase; +import javafx.beans.property.Property; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyDoubleProperty; +import javafx.beans.property.ReadOnlyDoubleWrapper; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectPropertyBase; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener.Change; import javafx.collections.ObservableList; @@ -77,10 +67,41 @@ import javafx.css.PseudoClass; import javafx.css.StyleableObjectProperty; import javafx.css.Stylesheet; -import javafx.event.*; -import javafx.geometry.*; +import javafx.event.Event; +import javafx.event.EventDispatchChain; +import javafx.event.EventDispatcher; +import javafx.event.EventHandler; +import javafx.event.EventTarget; +import javafx.event.EventType; +import javafx.geometry.Bounds; +import javafx.geometry.NodeOrientation; +import javafx.geometry.Orientation; +import javafx.geometry.Point2D; +import javafx.geometry.Point3D; import javafx.scene.image.WritableImage; -import javafx.scene.input.*; +import javafx.scene.input.ContextMenuEvent; +import javafx.scene.input.DragEvent; +import javafx.scene.input.Dragboard; +import javafx.scene.input.GestureEvent; +import javafx.scene.input.InputEvent; +import javafx.scene.input.InputMethodEvent; +import javafx.scene.input.InputMethodRequests; +import javafx.scene.input.InputMethodTextRun; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.Mnemonic; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseDragEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.input.PickResult; +import javafx.scene.input.RotateEvent; +import javafx.scene.input.ScrollEvent; +import javafx.scene.input.SwipeEvent; +import javafx.scene.input.TouchEvent; +import javafx.scene.input.TouchPoint; +import javafx.scene.input.TransferMode; +import javafx.scene.input.ZoomEvent; import javafx.scene.layout.HeaderBar; import javafx.scene.layout.HeaderButtonType; import javafx.scene.layout.HeaderDragType; @@ -92,22 +113,54 @@ import javafx.stage.Window; import javafx.util.Callback; import javafx.util.Duration; +import com.sun.glass.ui.Accessible; +import com.sun.glass.ui.Application; +import com.sun.javafx.application.PlatformImpl; +import com.sun.javafx.collections.TrackableObservableList; +import com.sun.javafx.css.StyleManager; +import com.sun.javafx.cursor.CursorFrame; +import com.sun.javafx.event.EventQueue; +import com.sun.javafx.event.EventUtil; +import com.sun.javafx.geom.PickRay; +import com.sun.javafx.geom.Vec3d; +import com.sun.javafx.geom.transform.BaseTransform; import com.sun.javafx.logging.PlatformLogger; import com.sun.javafx.logging.PlatformLogger.Level; - -import java.io.File; -import java.util.*; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.stream.Stream; - import com.sun.javafx.logging.PulseLogger; - -import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED; +import com.sun.javafx.perf.PerformanceTracker; +import com.sun.javafx.scene.CssFlags; +import com.sun.javafx.scene.InputMethodStateManager; +import com.sun.javafx.scene.LayoutFlags; import com.sun.javafx.scene.NodeHelper; -import com.sun.javafx.stage.WindowHelper; +import com.sun.javafx.scene.SceneEventDispatcher; +import com.sun.javafx.scene.SceneHelper; +import com.sun.javafx.scene.ScenePreferences; import com.sun.javafx.scene.input.ClipboardHelper; +import com.sun.javafx.scene.input.DragboardHelper; +import com.sun.javafx.scene.input.ExtendedInputMethodRequests; +import com.sun.javafx.scene.input.InputEventUtils; +import com.sun.javafx.scene.input.PickResultChooser; import com.sun.javafx.scene.input.TouchPointHelper; -import java.lang.ref.WeakReference; +import com.sun.javafx.scene.traversal.TopMostTraversalEngine; +import com.sun.javafx.scene.traversal.TraversalUtils; +import com.sun.javafx.sg.prism.NGCamera; +import com.sun.javafx.sg.prism.NGLightBase; +import com.sun.javafx.stage.EmbeddedWindow; +import com.sun.javafx.stage.WindowHelper; +import com.sun.javafx.tk.HeaderAreaType; +import com.sun.javafx.tk.TKClipboard; +import com.sun.javafx.tk.TKDragGestureListener; +import com.sun.javafx.tk.TKDragSourceListener; +import com.sun.javafx.tk.TKDropTargetListener; +import com.sun.javafx.tk.TKPulseListener; +import com.sun.javafx.tk.TKScene; +import com.sun.javafx.tk.TKSceneListener; +import com.sun.javafx.tk.TKScenePaintListener; +import com.sun.javafx.tk.TKStage; +import com.sun.javafx.tk.Toolkit; +import com.sun.javafx.util.Logging; +import com.sun.javafx.util.Utils; +import com.sun.prism.impl.PrismSettings; /** * The JavaFX {@code Scene} class is the container for all content in a scene graph. @@ -2208,25 +2261,13 @@ final boolean isFocusDirty() { return focusDirty; } - private TopMostTraversalEngine traversalEngine = new SceneTraversalEngine(this); - - /** - * Traverses focus from the given node in the given direction. - */ - boolean traverse(Node node, Direction dir, TraversalMethod method) { - if (node.getSubScene() != null) { - return node.getSubScene().traverse(node, dir, method); - } - return traversalEngine.trav(node, dir, method) != null; - } - /** * Moves the focus to a reasonable initial location. Called when a scene's * focus is dirty and there's no current owner, or if the owner has been * removed from the scene. */ private void focusInitial() { - traversalEngine.traverseToFirst(); + TopMostTraversalEngine.traverseToFirst(getRoot()); } /** @@ -2236,7 +2277,7 @@ private void focusInitial() { * function assumes that it is still a member of the same scene. */ private void focusIneligible(Node node) { - traverse(node, Direction.NEXT, TraversalMethod.DEFAULT); + TraversalUtils.traverse(node, TraversalDirection.NEXT, false); } boolean processKeyEvent(KeyEvent e) { diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/SubScene.java b/modules/javafx.graphics/src/main/java/javafx/scene/SubScene.java index 91f35c57f9b..a5330eca0ef 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/SubScene.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/SubScene.java @@ -25,29 +25,28 @@ package javafx.scene; -import com.sun.javafx.css.StyleManager; -import com.sun.javafx.scene.traversal.Direction; -import com.sun.javafx.scene.traversal.SubSceneTraversalEngine; -import com.sun.javafx.scene.traversal.TopMostTraversalEngine; -import com.sun.javafx.scene.traversal.TraversalMethod; +import java.io.File; +import java.util.ArrayList; +import java.util.List; import javafx.application.ConditionalFeature; import javafx.application.Platform; import javafx.beans.NamedArg; -import javafx.beans.property.*; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.DoublePropertyBase; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ObjectPropertyBase; +import javafx.beans.property.SimpleObjectProperty; import javafx.css.PseudoClass; import javafx.css.Stylesheet; import javafx.geometry.NodeOrientation; import javafx.geometry.Point3D; import javafx.scene.input.PickResult; import javafx.scene.paint.Paint; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - +import com.sun.javafx.css.StyleManager; import com.sun.javafx.geom.BaseBounds; import com.sun.javafx.geom.PickRay; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.javafx.logging.PlatformLogger; import com.sun.javafx.scene.CssFlags; import com.sun.javafx.scene.DirtyBits; import com.sun.javafx.scene.NodeHelper; @@ -59,8 +58,6 @@ import com.sun.javafx.sg.prism.NGSubScene; import com.sun.javafx.tk.Toolkit; -import com.sun.javafx.logging.PlatformLogger; - /** * The {@code SubScene} class is the container for content in a scene graph. * {@code SubScene} provides separation of different parts of a scene, each @@ -773,12 +770,6 @@ void layoutPass() { } } - private TopMostTraversalEngine traversalEngine = new SubSceneTraversalEngine(this); - - boolean traverse(Node node, Direction dir, TraversalMethod method) { - return traversalEngine.trav(node, dir, method) != null; - } - private enum SubSceneDirtyBits { SIZE_DIRTY, FILL_DIRTY, diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/TraversalDirection.java b/modules/javafx.graphics/src/main/java/javafx/scene/TraversalDirection.java index 8bf6983b172..68a5e78d6e0 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/TraversalDirection.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/TraversalDirection.java @@ -37,6 +37,8 @@ public enum TraversalDirection { LEFT, /** Indicates a focus change to the next focusable node. */ NEXT, + /** Indicates a focus change to the next focusable node, possibly traversing outside of the current parent. */ + NEXT_IN_LINE, /** Indicates a focus change to the previous focusable node. */ PREVIOUS, /** Indicates a focus change to the node to the right of the currently focused node. */ diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/TraversalPolicy.java b/modules/javafx.graphics/src/main/java/javafx/scene/TraversalPolicy.java new file mode 100644 index 00000000000..d6863f8cf09 --- /dev/null +++ b/modules/javafx.graphics/src/main/java/javafx/scene/TraversalPolicy.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2024, 2025, 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 javafx.scene; + +import com.sun.javafx.scene.traversal.TraversalUtils; + +/** + * The base class for any algorithm that determines focus traversal between elements in the + * JavaFX scene graph. + *

+ * In addition to the default traversal policy, which can be obtained via {@link #getDefault()}, a custom policy + * can be set on via {@link Parent#setTraversalPolicy(TraversalPolicy)}, thus altering the traversal between + * the children of that {@code Parent} and out of it. + *

+ * Note that in order to avoid cycles or dead-ends in traversal the algorithms should respect the following order: + *

    + *
  • For {@link TraversalDirection#NEXT NEXT}: + * node -> + * node subtree -> + * node siblings (first sibling then its subtree) -> + * {@link TraversalDirection#NEXT_IN_LINE NEXT_IN_LINE} for node's parent + *
  • For {@link TraversalDirection#NEXT_IN_LINE NEXT_IN_LINE}: + * node -> + * node siblings (first sibling then its subtree) -> + * {@link TraversalDirection#NEXT_IN_LINE NEXT_IN_LINE} for node's parent + *
  • For {@link TraversalDirection#PREVIOUS PREVIOUS}: + * node -> + * node siblings (first subtree then the node itself) -> + * {@link TraversalDirection#PREVIOUS PREVIOUS} for node's parent + *
+ *

+ * This ensures that the next direction will traverse the same nodes as previous (in the opposite order). + * + * @see TraversalDirection + * @since 24 + */ +public abstract class TraversalPolicy { + /** + * Traverses from the specified {@code node}, in the direction {@code dir}. + * Returns the new {@link javafx.scene.Node#isFocusTraversable() focus traversable} Node + * or null if no suitable target is found. + *

+ * Note: the {@code node} does not have to be focused or focus traversable, as it serves + * only as a reference point. + * + * Typically, the implementation of override TraversalPolicy handles only parent's direct children and looks like this: + *

    + *
  1. Find the nearest parent of the "owner" that is handled by this TraversalPolicy (i.e. it's a direct child of the root). + *
  2. select the next node within this direct child and return it + *
  3. if no such node exists, move to the next direct child in the direction (this is where the different order of direct children is defined) + * or if direct children are not traversable, the select the first node in the next direct child + *
+ * + * @param root the traversal root + * @param node the Node to traverse from + * @param dir the traversal direction + * @return the new focus owner or null if none found (in that case old focus owner is still valid) + */ + public abstract Node select(Parent root, Node node, TraversalDirection dir); + + /** + * Return the first {@link javafx.scene.Node#isFocusTraversable() focus traversable} + * node for the specified root. + * + * @param root the traversal root + * @return the first node + */ + public abstract Node selectFirst(Parent root); + + /** + * Return the last + * {@link javafx.scene.Node#isFocusTraversable() focus traversable} node for the specified root. + * + * @param root the traversal root + * @return the last node + */ + public abstract Node selectLast(Parent root); + + /** + * Constructor for subclasses to call. + */ + protected TraversalPolicy() { + } + + /** + * Determines whether the root is traversable. + * This method can be overridden by a subclass. The base class simply returns the result of calling + * {@code root.isFocusTraversable();} + * + * @param root the traversal root + * @return true if the root is traversable + */ + public boolean isParentTraversable(Parent root) { + return root.isFocusTraversable(); + } + + /** + * Returns the platform's default traversal policy singleton. + * + * @return the default traversal policy + */ + public static final TraversalPolicy getDefault() { + return TraversalUtils.DEFAULT_POLICY; + } + + /** + * Finds the next focusable Node. + * This method is provided to the policy implementation for handling of the {@link TraversalDirection#NEXT} + * case when it needs to consider traversing into the parent's nodes. + *

+ * Example:

     @Override
+     *     public Node select(Parent root, Node owner, TraversalDirection dir) {
+     *         switch(dir) {
+     *         case NEXT:
+     *             return findNextFocusableNode(root, owner);
+     *         ...
+     * 
+ * + * @param root the traversal root + * @param node the Node to traverse from + * @return the new focus owner or null if none found (in that case old focus owner is still valid) + */ + protected final Node findNextFocusableNode(Parent root, Node node) { + return TraversalUtils.findNextFocusableNode(root, node, true); + } + + /** + * Finds the next in line focusable Node. + * This method is provided to the policy implementation for handling of the {@link TraversalDirection#NEXT_IN_LINE} + * case when it needs to consider traversing into the parent's nodes. + *

+ * Example:

     @Override
+     *     public Node select(Parent root, Node owner, TraversalDirection dir) {
+     *         switch(dir) {
+     *         case NEXT_IN_LINE:
+     *             return findNextInLineFocusableNode(root, owner);
+     *         ...
+     * 
+ * + * @param root the traversal root + * @param node the Node to traverse from + * @return the new focus owner or null if none found (in that case old focus owner is still valid) + * @throws IllegalArgumentException if the direction is other than {@code TraversalDirection.NEXT} + * or {@code TraversalDirection.NEXT_IN_LINE} + */ + protected final Node findNextInLineFocusableNode(Parent root, Node node) { + return TraversalUtils.findNextFocusableNode(root, node, false); + } + + /** + * Finds the previous focusable Node. + * This method is provided to the policy implementation for handling of the {@link TraversalDirection#PREVIOUS} + * case when it needs to consider traversing into the parent's nodes. + *

+ * Example:

     @Override
+     *     public Node select(Parent root, Node owner, TraversalDirection dir) {
+     *         switch(dir) {
+     *         case PREVIOUS:
+     *             return findPreviousFocusableNode(root, owner);
+     *         ...
+     * 
+ * + * @param root the traversal root + * @param node the Node to traverse from + * @return the new focus owner or null if none found (in that case old focus owner is still valid) + */ + protected final Node findPreviousFocusableNode(Parent root, Node node) { + return TraversalUtils.findPreviousFocusableNode(root, node); + } +} diff --git a/modules/javafx.graphics/src/shims/java/com/sun/javafx/scene/traversal/TopMostTraversalEngineShim.java b/modules/javafx.graphics/src/shims/java/com/sun/javafx/scene/traversal/TopMostTraversalEngineShim.java deleted file mode 100644 index 10bdc4dd2fe..00000000000 --- a/modules/javafx.graphics/src/shims/java/com/sun/javafx/scene/traversal/TopMostTraversalEngineShim.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2015, 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 com.sun.javafx.scene.traversal; - -public abstract class TopMostTraversalEngineShim extends TopMostTraversalEngine { - - public TopMostTraversalEngineShim(Algorithm algorithm) { - super(algorithm); - } - -} diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/scene/traversal/TopMostTraversalEngineTest.java b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/scene/traversal/TopMostTraversalEngineTest.java index 20b7a2c3982..00c4a365928 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/scene/traversal/TopMostTraversalEngineTest.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/scene/traversal/TopMostTraversalEngineTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, 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 @@ -25,38 +25,29 @@ package test.com.sun.javafx.scene.traversal; -import com.sun.javafx.scene.ParentHelper; -import com.sun.javafx.scene.traversal.Algorithm; -import com.sun.javafx.scene.traversal.ContainerTabOrderShim; -import com.sun.javafx.scene.traversal.Direction; -import com.sun.javafx.scene.traversal.ParentTraversalEngine; -import com.sun.javafx.scene.traversal.TopMostTraversalEngineShim; -import com.sun.javafx.scene.traversal.TraversalContext; -import com.sun.javafx.scene.traversal.TraversalMethod; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.ParentShim; +import javafx.scene.TraversalDirection; +import javafx.scene.TraversalPolicy; import javafx.scene.shape.Rectangle; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; +import com.sun.javafx.scene.traversal.ContainerTabOrderShim; +import com.sun.javafx.scene.traversal.OverridableTraversalPolicy; +import com.sun.javafx.scene.traversal.TopMostTraversalEngine; public class TopMostTraversalEngineTest { - private TopMostTraversalEngineShim engine; + private TraversalPolicy engine; private Group root; @BeforeEach public void setUp() { root = new Group(); - engine = new TopMostTraversalEngineShim(new ContainerTabOrderShim()) { - @Override - protected Parent getRoot() { - return root; - } - }; + engine = new ContainerTabOrderShim(); } @Test @@ -65,7 +56,7 @@ public void selectFirst() { Group g = new Group(focusableNode, createFocusableNode()); ParentShim.getChildren(root).add(g); - assertEquals(focusableNode, engine.selectFirst()); + assertEquals(focusableNode, engine.selectFirst(root)); } @Test @@ -74,55 +65,55 @@ public void selectFirstSkipInvisible() { final Node n2 = createFocusableNode(); ParentShim.getChildren(root).addAll(n1, n2); - assertEquals(n2, engine.selectFirst()); + assertEquals(n2, engine.selectFirst(root)); } @Test public void selectFirstUseParentEngine() { Group g = new Group(createFocusableNode()); - ParentHelper.setTraversalEngine(g, new ParentTraversalEngine(g, new Algorithm() { + g.setTraversalPolicy(new TraversalPolicy() { @Override - public Node select(Node owner, Direction dir, TraversalContext context) { + public Node select(Parent root, Node owner, TraversalDirection dir) { return null; } @Override - public Node selectFirst(TraversalContext context) { + public Node selectFirst(Parent root) { return null; } @Override - public Node selectLast(TraversalContext context) { + public Node selectLast(Parent root) { return null; } - })); + }); g.setDisable(true); ParentShim.getChildren(root).add(g); final Node focusableNode = createFocusableNode(); g = new Group(createFocusableNode(), focusableNode, createFocusableNode()); - ParentHelper.setTraversalEngine(g, new ParentTraversalEngine(g, new Algorithm() { + g.setTraversalPolicy(new TraversalPolicy() { @Override - public Node select(Node owner, Direction dir, TraversalContext context) { + public Node select(Parent root, Node owner, TraversalDirection dir) { fail(); return null; } @Override - public Node selectFirst(TraversalContext context) { + public Node selectFirst(Parent root) { return focusableNode; } @Override - public Node selectLast(TraversalContext context) { + public Node selectLast(Parent root) { fail(); return null; } - })); + }); ParentShim.getChildren(root).add(g); - assertEquals(focusableNode, engine.selectFirst()); + assertEquals(focusableNode, engine.selectFirst(root)); } @Test @@ -131,20 +122,20 @@ public void selectFirstFocusableParent() { g.setFocusTraversable(true); ParentShim.getChildren(root).add(g); - assertEquals(g, engine.selectFirst()); + assertEquals(g, engine.selectFirst(root)); } @Test public void selectFirstTraverseOverride() { Group g = new Group(createFocusableNode(), createFocusableNode()); g.setFocusTraversable(true); - final ParentTraversalEngine pEngine = new ParentTraversalEngine(g); - pEngine.setOverriddenFocusTraversability(false); - ParentHelper.setTraversalEngine(g, pEngine); + OverridableTraversalPolicy policy = new OverridableTraversalPolicy(); + policy.setOverriddenFocusTraversability(false); + g.setTraversalPolicy(policy); ParentShim.getChildren(root).add(g); - assertEquals(ParentShim.getChildren(g).get(0), engine.selectFirst()); + assertEquals(ParentShim.getChildren(g).get(0), engine.selectFirst(root)); } @@ -154,7 +145,7 @@ public void selectLast() { Group g = new Group(createFocusableNode(), focusableNode); ParentShim.getChildren(root).add(g); - assertEquals(focusableNode, engine.selectLast()); + assertEquals(focusableNode, engine.selectLast(root)); } @Test @@ -163,57 +154,57 @@ public void selectLastSkipInvisible() { final Node n2 = createFocusableDisabledNode(); ParentShim.getChildren(root).addAll(n1, n2); - assertEquals(n1, engine.selectFirst()); + assertEquals(n1, engine.selectFirst(root)); } @Test public void selectLastUseParentEngine() { final Node focusableNode = createFocusableNode(); Group g = new Group(createFocusableNode(), focusableNode, createFocusableNode()); - ParentHelper.setTraversalEngine(g, new ParentTraversalEngine(g, new Algorithm() { + g.setTraversalPolicy(new TraversalPolicy() { @Override - public Node select(Node owner, Direction dir, TraversalContext context) { + public Node select(Parent root, Node owner, TraversalDirection dir) { fail(); return null; } @Override - public Node selectFirst(TraversalContext context) { + public Node selectFirst(Parent root) { fail(); return null; } @Override - public Node selectLast(TraversalContext context) { + public Node selectLast(Parent root) { return focusableNode; } - })); + }); ParentShim.getChildren(root).add(g); g = new Group(createFocusableNode()); - ParentHelper.setTraversalEngine(g, new ParentTraversalEngine(g, new Algorithm() { + g.setTraversalPolicy(new TraversalPolicy() { @Override - public Node select(Node owner, Direction dir, TraversalContext context) { + public Node select(Parent root, Node owner, TraversalDirection dir) { return null; } @Override - public Node selectFirst(TraversalContext context) { + public Node selectFirst(Parent root) { return null; } @Override - public Node selectLast(TraversalContext context) { + public Node selectLast(Parent root) { return null; } - })); + }); g.setDisable(true); ParentShim.getChildren(root).add(g); - assertEquals(focusableNode, engine.selectLast()); + assertEquals(focusableNode, engine.selectLast(root)); } @Test @@ -223,7 +214,7 @@ public void selectLastFocusableParent() { g.setFocusTraversable(true); ParentShim.getChildren(root).add(g); - assertEquals(focusableNode, engine.selectLast()); + assertEquals(focusableNode, engine.selectLast(root)); } @Test @@ -232,22 +223,22 @@ public void selectLastFocusableParent_2() { g.setFocusTraversable(true); ParentShim.getChildren(root).add(g); - assertEquals(g, engine.selectLast()); + assertEquals(g, engine.selectLast(root)); } @Test public void selectLastTraverseOverride() { Group g = new Group(); g.setFocusTraversable(true); - final ParentTraversalEngine pEngine = new ParentTraversalEngine(g); - pEngine.setOverriddenFocusTraversability(false); - ParentHelper.setTraversalEngine(g, pEngine); + OverridableTraversalPolicy policy = new OverridableTraversalPolicy(); + policy.setOverriddenFocusTraversability(false); + g.setTraversalPolicy(policy); Node focusableNode = createFocusableNode(); ParentShim.getChildren(root).addAll(focusableNode, g); - assertEquals(focusableNode, engine.selectLast()); + assertEquals(focusableNode, engine.selectLast(root)); } @Test @@ -258,7 +249,7 @@ public void selectNext() { ParentShim.getChildren(root).addAll(g); - assertEquals(n2, engine.trav(n1, Direction.NEXT, TraversalMethod.DEFAULT)); + assertEquals(n2, TopMostTraversalEngine.trav(root, n1, TraversalDirection.NEXT, false)); } @Test @@ -270,7 +261,7 @@ public void selectNextFromParent() { ParentShim.getChildren(root).addAll(g); - assertEquals(ng1, engine.trav(n1, Direction.NEXT, TraversalMethod.DEFAULT)); + assertEquals(ng1, TopMostTraversalEngine.trav(root, n1, TraversalDirection.NEXT, false)); } @Test @@ -281,7 +272,7 @@ public void selectNextFromParent_2() { ParentShim.getChildren(root).addAll(g); - assertEquals(n2, engine.trav(n1, Direction.NEXT, TraversalMethod.DEFAULT)); + assertEquals(n2, TopMostTraversalEngine.trav(root, n1, TraversalDirection.NEXT, false)); } @Test @@ -292,7 +283,7 @@ public void selectNextInParentSibling() { ParentShim.getChildren(root).addAll(createFocusableNode(), new Group(new Group(n1, createFocusableDisabledNode(), createFocusableDisabledNode()), new Group(createFocusableDisabledNode())), new Group(n2)); - assertEquals(n2, engine.trav(n1, Direction.NEXT, TraversalMethod.DEFAULT)); + assertEquals(n2, TopMostTraversalEngine.trav(root, n1, TraversalDirection.NEXT, false)); } @Test @@ -303,7 +294,7 @@ public void selectNextFocusableParent() { ParentShim.getChildren(root).addAll(new Group(createFocusableNode(), n1, createFocusableDisabledNode(), g)); - assertEquals(g, engine.trav(n1, Direction.NEXT, TraversalMethod.DEFAULT)); + assertEquals(g, TopMostTraversalEngine.trav(root, n1, TraversalDirection.NEXT, false)); } @Test @@ -311,29 +302,29 @@ public void selectNextInOverridenAlgorithm() { Node n1 = createFocusableNode(); Node n2 = createFocusableNode(); Group g = new Group(n1, createFocusableNode(), n2); - ParentHelper.setTraversalEngine(g, new ParentTraversalEngine(g, new Algorithm() { + g.setTraversalPolicy(new TraversalPolicy() { @Override - public Node select(Node owner, Direction dir, TraversalContext context) { - assertEquals(Direction.NEXT, dir); + public Node select(Parent root, Node owner, TraversalDirection dir) { + assertEquals(TraversalDirection.NEXT, dir); return n2; } @Override - public Node selectFirst(TraversalContext context) { + public Node selectFirst(Parent root) { fail(); return null; } @Override - public Node selectLast(TraversalContext context) { + public Node selectLast(Parent root) { fail(); return null; } - })); + }); ParentShim.getChildren(root).add(g); - assertEquals(n2, engine.trav(n1, Direction.NEXT, TraversalMethod.DEFAULT)); + assertEquals(n2, TopMostTraversalEngine.trav(root, n1, TraversalDirection.NEXT, false)); } @@ -343,30 +334,30 @@ public void selectNextInOverridenAlgorithm_NothingSelected() { Node n2 = createFocusableNode(); Group g = new Group(n1, createFocusableNode(), n2); g.setFocusTraversable(true); - ParentHelper.setTraversalEngine(g, new ParentTraversalEngine(g, new Algorithm() { + g.setTraversalPolicy(new TraversalPolicy() { @Override - public Node select(Node owner, Direction dir, TraversalContext context) { - assertEquals(Direction.NEXT, dir); + public Node select(Parent root, Node owner, TraversalDirection dir) { + assertEquals(TraversalDirection.NEXT, dir); return null; } @Override - public Node selectFirst(TraversalContext context) { + public Node selectFirst(Parent root) { fail(); return null; } @Override - public Node selectLast(TraversalContext context) { + public Node selectLast(Parent root) { fail(); return null; } - })); + }); final Node n3 = createFocusableNode(); ParentShim.getChildren(root).addAll(g, n3); - assertEquals(n3, engine.trav(n1, Direction.NEXT, TraversalMethod.DEFAULT)); + assertEquals(n3, TopMostTraversalEngine.trav(root, n1, TraversalDirection.NEXT, false)); } @Test @@ -378,7 +369,7 @@ public void selectNextInLine() { ParentShim.getChildren(root).addAll(g); - assertEquals(n2, engine.trav(n1, Direction.NEXT_IN_LINE, TraversalMethod.DEFAULT)); + assertEquals(n2, TopMostTraversalEngine.trav(root, n1, TraversalDirection.NEXT_IN_LINE, false)); } @@ -390,7 +381,7 @@ public void selectPrevious() { ParentShim.getChildren(root).addAll(g); - assertEquals(n2, engine.trav(n1, Direction.PREVIOUS, TraversalMethod.DEFAULT)); + assertEquals(n2, TopMostTraversalEngine.trav(root, n1, TraversalDirection.PREVIOUS, false)); } @Test @@ -402,7 +393,7 @@ public void selectPreviousFromParent() { ParentShim.getChildren(root).addAll(g); - assertEquals(n2, engine.trav(n1, Direction.PREVIOUS, TraversalMethod.DEFAULT)); + assertEquals(n2, TopMostTraversalEngine.trav(root, n1, TraversalDirection.PREVIOUS, false)); } @Test @@ -413,7 +404,7 @@ public void selectPreviousFromParent_2() { ParentShim.getChildren(root).addAll(g); - assertEquals(n2, engine.trav(n1, Direction.PREVIOUS, TraversalMethod.DEFAULT)); + assertEquals(n2, TopMostTraversalEngine.trav(root, n1, TraversalDirection.PREVIOUS, false)); } @Test @@ -425,7 +416,7 @@ public void selectPreviousInParentSibling() { new Group(new Group(createFocusableDisabledNode(), n1, createFocusableDisabledNode())), createFocusableNode()); - assertEquals(n2, engine.trav(n1, Direction.PREVIOUS, TraversalMethod.DEFAULT)); + assertEquals(n2, TopMostTraversalEngine.trav(root, n1, TraversalDirection.PREVIOUS, false)); } @Test @@ -437,7 +428,7 @@ public void selectPreviousFocusableParentsNode() { ParentShim.getChildren(root).addAll(new Group(createFocusableNode(), g, n1, createFocusableDisabledNode())); - assertEquals(n2, engine.trav(n1, Direction.PREVIOUS, TraversalMethod.DEFAULT)); + assertEquals(n2, TopMostTraversalEngine.trav(root, n1, TraversalDirection.PREVIOUS, false)); } @Test @@ -449,7 +440,7 @@ public void selectPreviousFocusableParent() { ParentShim.getChildren(root).addAll(new Group(createFocusableNode(), n1, g, createFocusableDisabledNode())); - assertEquals(g, engine.trav(n2, Direction.PREVIOUS, TraversalMethod.DEFAULT)); + assertEquals(g, TopMostTraversalEngine.trav(root, n2, TraversalDirection.PREVIOUS, false)); } @Test @@ -459,7 +450,7 @@ public void selectNextToLast() { ParentShim.getChildren(root).addAll(new Group(n2), new Group(createFocusableNode(), n1)); - assertEquals(n2, engine.trav(n1, Direction.NEXT, TraversalMethod.DEFAULT)); + assertEquals(n2, TopMostTraversalEngine.trav(root, n1, TraversalDirection.NEXT, false)); } @@ -470,7 +461,7 @@ public void selectPreviousToFirst() { ParentShim.getChildren(root).addAll(new Group(n1, createFocusableNode()), new Group(n2)); - assertEquals(n2, engine.trav(n1, Direction.PREVIOUS, TraversalMethod.DEFAULT)); + assertEquals(n2, TopMostTraversalEngine.trav(root, n1, TraversalDirection.PREVIOUS, false)); } @@ -479,29 +470,29 @@ public void selectPreviousInOverridenAlgorithm() { Node n1 = createFocusableNode(); Node n2 = createFocusableNode(); Group g = new Group(n2, createFocusableNode(), n1); - ParentHelper.setTraversalEngine(g, new ParentTraversalEngine(g, new Algorithm() { + g.setTraversalPolicy(new TraversalPolicy() { @Override - public Node select(Node owner, Direction dir, TraversalContext context) { - assertEquals(Direction.PREVIOUS, dir); + public Node select(Parent root, Node owner, TraversalDirection dir) { + assertEquals(TraversalDirection.PREVIOUS, dir); return n2; } @Override - public Node selectFirst(TraversalContext context) { + public Node selectFirst(Parent root) { fail(); return null; } @Override - public Node selectLast(TraversalContext context) { + public Node selectLast(Parent root) { fail(); return null; } - })); + }); ParentShim.getChildren(root).add(g); - assertEquals(n2, engine.trav(n1, Direction.PREVIOUS, TraversalMethod.DEFAULT)); + assertEquals(n2, TopMostTraversalEngine.trav(root, n1, TraversalDirection.PREVIOUS, false)); } private Node createFocusableNode() { diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/scene/traversal/TraversalPolicyTest.java b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/scene/traversal/TraversalPolicyTest.java new file mode 100644 index 00000000000..e0c6a9aeaf9 --- /dev/null +++ b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/scene/traversal/TraversalPolicyTest.java @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2024, 2025, 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.com.sun.javafx.scene.traversal; + +import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.TraversalDirection; +import javafx.scene.TraversalPolicy; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.shape.Rectangle; +import javafx.stage.Stage; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import com.sun.javafx.scene.traversal.TraversalUtils; +import com.sun.javafx.tk.Toolkit; +import test.com.sun.javafx.pgstub.StubToolkit; + +/** + * Tests TraversalPolicy APIs using the default and a custom traversal policies. + */ +public final class TraversalPolicyTest { + private static StubToolkit tk; + private static Stage stage; + private static Scene scene; + private static GridPane grid; + private static Node t0; + private static Node t1; + private static Node t2; + private static Node t3; + private static Node b00; + private static Node b01; + private static Node b02; + private static Node b10; + private static Node b11; + private static Node b12; + private static Node b20; + private static Node b21; + private static Node b22; + + /** + * [T.0] [T.1] [T.2] [T.3] + * ----------------------- + * [G.0.0] [G.1.0] [G.2.0] + * [G.0.1] [G.1.1] [G.2.1] + * [G.0.2] [G.1.2] [G.2.2] + */ + @BeforeEach + void beforeClass() { + tk = (StubToolkit)Toolkit.getToolkit(); + + t0 = b("T.0"); + t1 = b("T.1"); + t2 = b("T.2"); + t3 = b("T.3"); + + b00 = b("b.0.0"); + b01 = b("b.0.1"); + b02 = b("b.0.2"); + b10 = b("b.1.0"); + b11 = b("b.1.1"); + b12 = b("b.1.2"); + b20 = b("b.2.0"); + b21 = b("b.2.1"); + b22 = b("b.2.2"); + + grid = new GridPane(); + grid.add(b00, 0, 0); + grid.add(b01, 0, 1); + grid.add(b02, 0, 2); + grid.add(b10, 1, 0); + grid.add(b11, 1, 1); + grid.add(b12, 1, 2); + grid.add(b20, 2, 0); + grid.add(b21, 2, 1); + grid.add(b22, 2, 2); + + BorderPane bp = new BorderPane(grid); + bp.setTop(new HBox( + t0, + t1, + t2, + t3 + )); + + scene = new Scene(bp, 500, 400); + stage = new Stage(); + stage.setScene(scene); + stage.show(); + } + + @BeforeEach + void beforeEach() { + grid.setTraversalPolicy(null); + stage.requestFocus(); + firePulse(); + t0.requestFocus(); + firePulse(); + } + + @AfterEach + void afterEach() { + if (stage != null) { + stage.hide(); + stage = null; + } + scene = null; + } + + @AfterAll + static void afterAll() { + if (stage != null) { + stage.hide(); + stage = null; + } + } + + void traverse(Node from, TraversalDirection dir, Node... nodes) { + from.requestFocus(); + firePulse(); + checkFocused(from); + + for (Node n : nodes) { + boolean success = TraversalUtils.traverse(from, dir, false); + Assertions.assertTrue(success, "failed to traverse from node: " + from); + firePulse(); + checkFocused(n); + checkEventNode(n); + from = n; + } + } + + void checkFocused(Node n) { + Assertions.assertTrue(n.isFocused(), "expecting focused node: " + n); + } + + void checkEventNode(Node n) { + Node fromEvent = scene.getFocusOwner(); + Assertions.assertTrue(fromEvent == n, "TraversalEvent.node is wrong, expecting=" + n + ", observed=" + fromEvent); + //System.out.println(fromEvent); + } + + static void setCustomPolicy() { + grid.setTraversalPolicy(customTraversalPolicy( + b00, + b10, + b20, + b01, + b11, + b21, + b02, + b12, + b22 + )); + } + + private static Rectangle b(String text) { + Rectangle b = new Rectangle() { + @Override + public String toString() { + return text; + } + }; + b.setWidth(80); + b.setHeight(40); + b.setFocusTraversable(true); + return b; + } + + private static void firePulse() { + tk.firePulse(); + } + + static TraversalPolicy customTraversalPolicy(Node... nodes) { + // This custom policy differs from default by explicitly specifying the traversal order. + return new TraversalPolicy() { + @Override + public Node select(Parent root, Node owner, TraversalDirection dir) { + int ix = indexOf(owner); + if (ix < 0) { + return null; + } + + switch (dir) { + case NEXT: + if (ix >= (nodes.length - 1)) { + // traversing up the stack from last node + return findNextFocusableNode(root, owner); + } + ix++; + break; + case NEXT_IN_LINE: + if (ix >= (nodes.length - 1)) { + // traversing up the stack from last node + return findNextInLineFocusableNode(root, owner); + } + ix++; + break; + case PREVIOUS: + if (ix <= 0) { + // traversing up the stack from the first node + return findPreviousFocusableNode(root, owner); + } + ix--; + break; + case LEFT: + case UP: + ix--; + break; + case DOWN: + case RIGHT: + default: + ix++; + } + + if (ix < 0) { + return selectLast(root); + } else if (ix >= nodes.length) { + return selectFirst(root); + } + return nodes[ix]; + } + + @Override + public Node selectFirst(Parent root) { + return nodes[0]; + } + + @Override + public Node selectLast(Parent root) { + int ix = nodes.length - 1; + if (ix < 0) { + return null; + } + return nodes[ix]; + } + + private int indexOf(Node n) { + for (int i = nodes.length - 1; i >= 0; --i) { + if (nodes[i] == n) { + return i; + } + } + return -1; + } + }; + } + + // direction: DOWN, default policy + @Test + void testDefaultPolicy_DOWN() { + traverse( + t0, + TraversalDirection.DOWN, + b00, b01, b02 + ); + } + + // direction: DOWN, custom policy + @Test + void testCustomPolicy_DOWN() { + setCustomPolicy(); + traverse( + t0, + TraversalDirection.DOWN, + b00, b10, b20, + b01, b11, b21, + b02, b12, b22, + b00, b10 + ); + } + + // direction: LEFT, default policy + @Test + void testDefaultPolicy_LEFT() { + traverse( + t3, + TraversalDirection.LEFT, + t2, t1, t0 + ); + } + + // direction: LEFT, default policy + @Test + void testDefaultPolicy_LEFT2() { + traverse( + b20, + TraversalDirection.LEFT, + b10, b00 + ); + } + + // direction: LEFT, custom policy, start at B20 + @Test + void testCustomPolicy_LEFT() { + setCustomPolicy(); + traverse( + b20, + TraversalDirection.LEFT, + b10, b00, + b22, b12, b02, + b21, b11, b01, + b20, b10, b00, + b22 + ); + } + + // direction: NEXT, default policy + @Test + void testDefaultPolicy_NEXT() { + traverse( + t0, + TraversalDirection.NEXT, + t1, t2, t3, + b00, b01, b02, + b10, b11, b12, + b20, b21, b22, + t0, t1, t2, t3 + ); + } + + // direction: NEXT, custom policy + @Test + void testCustomPolicy_NEXT() { + setCustomPolicy(); + traverse( + t0, + TraversalDirection.NEXT, + t1, t2, t3, + b00, b10, b20, + b01, b11, b21, + b02, b12, b22, + t0, t1, t2, t3 + ); + } + + // direction: NEXT_IN_LINE, default policy + @Test + void testDefaultPolicy_NEXT_IN_LINE() { + traverse( + t0, + TraversalDirection.NEXT_IN_LINE, + t1, t2, t3, + b00, b01, b02, + b10, b11, b12, + b20, b21, b22, + t0, t1, t2, t3 + ); + } + + // direction: NEXT_IN_LINE, custom policy + @Test + void testCustomPolicy_NEXT_IN_LINE() { + setCustomPolicy(); + traverse( + t0, + TraversalDirection.NEXT_IN_LINE, + t1, t2, t3, + b00, b10, b20, + b01, b11, b21, + b02, b12, b22, + t0, t1, t2, t3 + ); + } + + // direction: PREVIOUS, default policy + @Test + void testDefaultPolicy_PREVIOUS() { + traverse( + t3, + TraversalDirection.PREVIOUS, + t2, t1, t0, + b22 + ); + } + + // direction: PREVIOUS, custom policy + @Test + void testCustomPolicy_PREVIOUS() { + setCustomPolicy(); + traverse( + t3, + TraversalDirection.PREVIOUS, + t2, t1, t0, + b22, b12, b02, + b21, b11, b01, + b20, b10, b00, + t3, t2, t1, t0, + b22 + ); + } + + // direction: RIGHT, default policy + @Test + void testDefaultPolicy_RIGHT() { + traverse( + t0, + TraversalDirection.RIGHT, + t1, t2, t3 + ); + } + + // direction: RIGHT, default policy, start at B00 + @Test + void testDefaultPolicy_RIGHT2() { + traverse( + b00, + TraversalDirection.RIGHT, + b10, b20, + t3 + ); + } + + // direction: RIGHT, custom policy + @Test + void testCustomPolicy_RIGHT() { + setCustomPolicy(); + traverse( + b00, + TraversalDirection.RIGHT, + b10, b20, + b01, b11, b21, + b02, b12, b22, + b00 + ); + } + + // direction: UP, default policy + @Test + void testDefaultPolicy_UP() { + traverse( + b02, + TraversalDirection.UP, + b01, b00, t0 + ); + } + + // direction: UP, custom policy + @Test + void testCustomPolicy_UP() { + setCustomPolicy(); + traverse( + b02, + TraversalDirection.UP, + b21, b11, b01, + b20, b10, b00, + b22, b12, b02 + ); + } +} diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/scene/traversal/TraversalTest.java b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/scene/traversal/TraversalTest.java index 601d7680d8e..6cd408041f4 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/scene/traversal/TraversalTest.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/scene/traversal/TraversalTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, 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 @@ -25,34 +25,23 @@ package test.com.sun.javafx.scene.traversal; -import com.sun.javafx.scene.traversal.Direction; -import com.sun.javafx.scene.traversal.SceneTraversalEngine; -import com.sun.javafx.scene.traversal.TraversalEngine; -import com.sun.javafx.scene.traversal.TraversalMethod; -import com.sun.javafx.scene.traversal.TraverseListener; - +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.stream.Stream; - -import javafx.geometry.Bounds; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.Scene; +import javafx.scene.TraversalDirection; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; +import com.sun.javafx.scene.traversal.TopMostTraversalEngine; /** - * Tests for TraversalEngine with the default ContainerTabOrder algorithm, - * tests if using the WeightedClosestCorner algorithm have been - * left in comments. + * Tests for TraversalEngine with the default ContainerTabOrder policy. */ public final class TraversalTest { private Stage stage; @@ -75,62 +64,45 @@ public final class TraversalTest { * */ private Node[] keypadNodes; - private SceneTraversalEngine traversalEngine; /* - * Parameters: [fromNumber], [direction], [toNumber], [toNumberTransformed] + * Parameters: [fromNumber], [traversalDirection], [toNumber], [toNumberTransformed] */ - public static Stream data() { + private static Stream parameters() { return Stream.of( /* traversal from center */ - Arguments.of( 5, Direction.LEFT, 4, 8 ), - Arguments.of( 5, Direction.RIGHT, 6, 2 ), - Arguments.of( 5, Direction.UP, 2, 4 ), - Arguments.of( 5, Direction.DOWN, 8, 6 ), - - // using WeightedClosestCorner, target varies according to transform - //Arguments.of( 5, Direction.PREVIOUS, 4, 8 ), - //Arguments.of( 5, Direction.NEXT, 6, 2 ), + Arguments.of( 5, TraversalDirection.LEFT, 4, 8 ), + Arguments.of( 5, TraversalDirection.RIGHT, 6, 2 ), + Arguments.of( 5, TraversalDirection.UP, 2, 4 ), + Arguments.of( 5, TraversalDirection.DOWN, 8, 6 ), // using ContainerTabOrder, target is always the same - Arguments.of( 5, Direction.PREVIOUS, 4, 4 ), - Arguments.of( 5, Direction.NEXT, 6, 6 ), + Arguments.of( 5, TraversalDirection.PREVIOUS, 4, 4 ), + Arguments.of( 5, TraversalDirection.NEXT, 6, 6 ), /* traversal from borders (untransformed) */ - Arguments.of( 4, Direction.LEFT, 4, 7 ), - Arguments.of( 6, Direction.RIGHT, 6, 3 ), - Arguments.of( 2, Direction.UP, 2, 1 ), - Arguments.of( 8, Direction.DOWN, 8, 9 ), - - // using WeightedClosestCorner, target varies according to transform - //Arguments.of( 4, Direction.PREVIOUS, 3, 7 ), - //Arguments.of( 1, Direction.PREVIOUS, 9, 4 ), - //Arguments.of( 6, Direction.NEXT, 7, 3 ), - //Arguments.of( 9, Direction.NEXT, 1, 6 ), + Arguments.of( 4, TraversalDirection.LEFT, 4, 7 ), + Arguments.of( 6, TraversalDirection.RIGHT, 6, 3 ), + Arguments.of( 2, TraversalDirection.UP, 2, 1 ), + Arguments.of( 8, TraversalDirection.DOWN, 8, 9 ), // using ContainerTabOrder, target always the same - Arguments.of( 4, Direction.PREVIOUS, 3, 3 ), - Arguments.of( 1, Direction.PREVIOUS, 9, 9 ), - Arguments.of( 6, Direction.NEXT, 7, 7 ), - Arguments.of( 9, Direction.NEXT, 1, 1 ), + Arguments.of( 4, TraversalDirection.PREVIOUS, 3, 3 ), + Arguments.of( 1, TraversalDirection.PREVIOUS, 9, 9 ), + Arguments.of( 6, TraversalDirection.NEXT, 7, 7 ), + Arguments.of( 9, TraversalDirection.NEXT, 1, 1 ), /* traversal from borders (transformed) */ - Arguments.of( 2, Direction.RIGHT, 3, 2 ), - Arguments.of( 8, Direction.LEFT, 7, 8 ), - Arguments.of( 4, Direction.UP, 1, 4 ), - Arguments.of( 6, Direction.DOWN, 9, 6 ), - - // using WeightedClosestCorner, target varies according to transform - //Arguments.of( 8, Direction.PREVIOUS, 7, 1 ), - //Arguments.of( 7, Direction.PREVIOUS, 6, 3 ), - //Arguments.of( 2, Direction.NEXT, 3, 9 ), - //Arguments.of( 3, Direction.NEXT, 4, 7)} + Arguments.of( 2, TraversalDirection.RIGHT, 3, 2 ), + Arguments.of( 8, TraversalDirection.LEFT, 7, 8 ), + Arguments.of( 4, TraversalDirection.UP, 1, 4 ), + Arguments.of( 6, TraversalDirection.DOWN, 9, 6 ), // using ContainerTabOrder, target always the same - Arguments.of( 8, Direction.PREVIOUS, 7, 7 ), - Arguments.of( 7, Direction.PREVIOUS, 6, 6 ), - Arguments.of( 2, Direction.NEXT, 3, 3 ), - Arguments.of( 3, Direction.NEXT, 4, 4) + Arguments.of( 8, TraversalDirection.PREVIOUS, 7, 7 ), + Arguments.of( 7, TraversalDirection.PREVIOUS, 6, 6 ), + Arguments.of( 2, TraversalDirection.NEXT, 3, 3 ), + Arguments.of( 3, TraversalDirection.NEXT, 4, 4) ); } @@ -140,9 +112,7 @@ public void setUp() { scene = new Scene(new Group(), 500, 500); stage.setScene(scene); - traversalEngine = new SceneTraversalEngine(scene); - - keypadNodes = createKeypadNodesInScene(scene, traversalEngine); + keypadNodes = createKeypadNodesInScene(scene); stage.show(); stage.requestFocus(); @@ -150,58 +120,42 @@ public void setUp() { @AfterEach public void tearDown() { + if (stage != null) { + stage.hide(); + } stage = null; scene = null; keypadNodes = null; - traversalEngine = null; } @ParameterizedTest - @MethodSource("data") - public void untransformedTraversalTest(int fromNumber, - Direction direction, - int toNumber, - int toNumberTransformed) { + @MethodSource("parameters") + public void untransformedTraversalTest( + int fromNumber, + TraversalDirection direction, + int toNumber, + int toNumberTransformed) + { keypadNodes[fromNumber - 1].requestFocus(); - traversalEngine.trav(keypadNodes[fromNumber - 1], direction, TraversalMethod.DEFAULT); + TopMostTraversalEngine.trav(scene.getRoot(), keypadNodes[fromNumber - 1], direction, false); assertTrue(keypadNodes[toNumber - 1].isFocused()); } @ParameterizedTest - @MethodSource("data") - public void transformedTraversalTest(int fromNumber, - Direction direction, - int toNumber, - int toNumberTransformed) { + @MethodSource("parameters") + public void transformedTraversalTest( + int fromNumber, + TraversalDirection direction, + int toNumber, + int toNumberTransformed) + { scene.getRoot().setRotate(90); keypadNodes[fromNumber - 1].requestFocus(); - traversalEngine.trav(keypadNodes[fromNumber - 1], direction, TraversalMethod.DEFAULT); + TopMostTraversalEngine.trav(scene.getRoot(), keypadNodes[fromNumber - 1], direction, false); assertTrue(keypadNodes[toNumberTransformed - 1].isFocused()); } - @ParameterizedTest - @MethodSource("data") - public void traverseListenerTest(int fromNumber, - Direction direction, - int toNumber, - int toNumberTransformed) { - final TraverseListenerImpl traverseListener = - new TraverseListenerImpl(); - traversalEngine.addTraverseListener(traverseListener); - keypadNodes[fromNumber - 1].requestFocus(); - traversalEngine.trav(keypadNodes[fromNumber - 1], direction, TraversalMethod.DEFAULT); - if (fromNumber != toNumber) { - assertEquals(1, traverseListener.getCallCounter()); - assertSame(keypadNodes[toNumber - 1], - traverseListener.getLastNode()); - } else { - assertEquals(0, traverseListener.getCallCounter()); - } - } - - private static Node[] createKeypadNodesInScene( - final Scene scene, - final TraversalEngine traversalEngine) { + private static Node[] createKeypadNodesInScene(final Scene scene) { final Node[] keypad = new Node[9]; int index = 0; @@ -219,24 +173,4 @@ private static Node[] createKeypadNodesInScene( return keypad; } - - private static final class TraverseListenerImpl - implements TraverseListener { - private int callCounter; - private Node lastNode; - - public int getCallCounter() { - return callCounter; - } - - public Node getLastNode() { - return lastNode; - } - - @Override - public void onTraverse(final Node node, final Bounds bounds) { - ++callCounter; - lastNode = node; - } - } } diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/scene/traversal/TraverseInvisibleTest.java b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/scene/traversal/TraverseInvisibleTest.java index 3640c32955a..ff3dec435a7 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/scene/traversal/TraverseInvisibleTest.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/scene/traversal/TraverseInvisibleTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, 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 @@ -25,27 +25,20 @@ package test.com.sun.javafx.scene.traversal; -import com.sun.javafx.scene.traversal.Direction; -import com.sun.javafx.scene.traversal.SceneTraversalEngine; -import com.sun.javafx.scene.traversal.TraversalEngine; -import com.sun.javafx.scene.traversal.TraversalMethod; -import com.sun.javafx.scene.traversal.TraverseListener; - +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.stream.Stream; - -import javafx.geometry.Bounds; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.Scene; +import javafx.scene.TraversalDirection; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import static org.junit.jupiter.api.Assertions.assertTrue; +import com.sun.javafx.scene.traversal.TopMostTraversalEngine; /** * Tests TraversalEngine with invisible nodes, using the default ContainerTabOrder algorithm, @@ -54,24 +47,22 @@ public final class TraverseInvisibleTest { private Stage stage; private Scene scene; private Node[] keypadNodes; - private SceneTraversalEngine traversalEngine; /* - ** ** Parameters: [fromNumber], [direction], [invisibleNumber], [toNumber] ** The Grid looks like : ** 0 1 2 ** 3 4 5 ** 6 7 8 */ - public static Stream data() { + private static Stream parameters() { return Stream.of( - Arguments.of( 3, Direction.RIGHT, 4, 5), - Arguments.of( 5, Direction.LEFT, 4, 3), - Arguments.of( 4, Direction.NEXT, 5, 6), - Arguments.of( 6, Direction.PREVIOUS, 5, 4), - Arguments.of( 8, Direction.UP, 5, 2 ), - Arguments.of( 2, Direction.DOWN, 5, 8) + Arguments.of( 3, TraversalDirection.RIGHT, 4, 5), + Arguments.of( 5, TraversalDirection.LEFT, 4, 3), + Arguments.of( 4, TraversalDirection.NEXT, 5, 6), + Arguments.of( 6, TraversalDirection.PREVIOUS, 5, 4), + Arguments.of( 8, TraversalDirection.UP, 5, 2 ), + Arguments.of( 2, TraversalDirection.DOWN, 5, 8) ); } @@ -81,9 +72,7 @@ public void setUp() { scene = new Scene(new Group(), 500, 500); stage.setScene(scene); - traversalEngine = new SceneTraversalEngine(scene); - - keypadNodes = createKeypadNodesInScene(scene, traversalEngine); + keypadNodes = createKeypadNodesInScene(scene); stage.show(); stage.requestFocus(); } @@ -93,29 +82,26 @@ public void tearDown() { stage = null; scene = null; keypadNodes = null; - traversalEngine = null; } @ParameterizedTest - @MethodSource("data") - public void traverseOverInvisible(int fromNumber, - Direction direction, - int invisibleNumber, - int toNumber) { + @MethodSource("parameters") + public void traverseOverInvisible( + int fromNumber, + TraversalDirection direction, + int invisibleNumber, + int toNumber) + { keypadNodes[fromNumber].requestFocus(); keypadNodes[invisibleNumber].setVisible(false); - traversalEngine.trav(keypadNodes[fromNumber], direction, TraversalMethod.DEFAULT); + TopMostTraversalEngine.trav(scene.getRoot(), keypadNodes[fromNumber], direction, false); assertTrue(keypadNodes[toNumber].isFocused()); keypadNodes[invisibleNumber - 1].setVisible(true); } - - - private static Node[] createKeypadNodesInScene( - final Scene scene, - final TraversalEngine traversalEngine) { + private static Node[] createKeypadNodesInScene(final Scene scene) { final Node[] keypad = new Node[9]; int index = 0; @@ -133,24 +119,4 @@ private static Node[] createKeypadNodesInScene( return keypad; } - - private static final class TraverseListenerImpl - implements TraverseListener { - private int callCounter; - private Node lastNode; - - public int getCallCounter() { - return callCounter; - } - - public Node getLastNode() { - return lastNode; - } - - @Override - public void onTraverse(final Node node, final Bounds bounds) { - ++callCounter; - lastNode = node; - } - } } diff --git a/modules/javafx.web/src/main/java/com/sun/javafx/scene/web/behavior/HTMLEditorBehavior.java b/modules/javafx.web/src/main/java/com/sun/javafx/scene/web/behavior/HTMLEditorBehavior.java index bfbb7a17712..f1d46760995 100644 --- a/modules/javafx.web/src/main/java/com/sun/javafx/scene/web/behavior/HTMLEditorBehavior.java +++ b/modules/javafx.web/src/main/java/com/sun/javafx/scene/web/behavior/HTMLEditorBehavior.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, 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 @@ -25,16 +25,18 @@ package com.sun.javafx.scene.web.behavior; -import com.sun.javafx.scene.ParentHelper; -import com.sun.javafx.scene.control.behavior.BehaviorBase; -import com.sun.javafx.scene.control.inputmap.InputMap; -import com.sun.javafx.scene.control.inputmap.KeyBinding; +import static javafx.scene.input.KeyCode.B; +import static javafx.scene.input.KeyCode.F12; +import static javafx.scene.input.KeyCode.I; +import static javafx.scene.input.KeyCode.TAB; +import static javafx.scene.input.KeyCode.U; import javafx.scene.web.HTMLEditor; import javafx.scene.web.HTMLEditorSkin; +import com.sun.javafx.scene.control.behavior.BehaviorBase; import com.sun.javafx.scene.control.behavior.FocusTraversalInputMap; - -import static javafx.scene.input.KeyCode.*; -import static com.sun.javafx.scene.control.inputmap.InputMap.KeyMapping; +import com.sun.javafx.scene.control.inputmap.InputMap; +import com.sun.javafx.scene.control.inputmap.InputMap.KeyMapping; +import com.sun.javafx.scene.control.inputmap.KeyBinding; /** * HTML editor behavior. @@ -51,7 +53,7 @@ public HTMLEditorBehavior(HTMLEditor htmlEditor) { new KeyMapping(new KeyBinding(I).shortcut(), e -> keyboardShortcuts(HTMLEditorSkin.Command.ITALIC)), new KeyMapping(new KeyBinding(U).shortcut(), e -> keyboardShortcuts(HTMLEditorSkin.Command.UNDERLINE)), - new KeyMapping(new KeyBinding(F12), e -> ParentHelper.getTraversalEngine(getNode()).selectFirst().requestFocus()), + new KeyMapping(new KeyBinding(F12), e -> getNode().getTraversalPolicy().selectFirst(getNode()).requestFocus()), new KeyMapping(new KeyBinding(TAB).ctrl(), FocusTraversalInputMap::traverseNext), new KeyMapping(new KeyBinding(TAB).ctrl().shift(), FocusTraversalInputMap::traversePrevious) ); diff --git a/modules/javafx.web/src/main/java/com/sun/javafx/webkit/WebPageClientImpl.java b/modules/javafx.web/src/main/java/com/sun/javafx/webkit/WebPageClientImpl.java index e3ce6edb21d..c5899815ca1 100644 --- a/modules/javafx.web/src/main/java/com/sun/javafx/webkit/WebPageClientImpl.java +++ b/modules/javafx.web/src/main/java/com/sun/javafx/webkit/WebPageClientImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, 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 @@ -25,20 +25,17 @@ package com.sun.javafx.webkit; -import com.sun.javafx.scene.NodeHelper; import java.lang.ref.WeakReference; - -import com.sun.javafx.scene.traversal.Direction; -import com.sun.javafx.scene.traversal.TraversalMethod; import javafx.geometry.Point2D; import javafx.geometry.Rectangle2D; import javafx.scene.Cursor; import javafx.scene.Scene; +import javafx.scene.TraversalDirection; import javafx.scene.control.Tooltip; import javafx.scene.web.WebView; import javafx.stage.Screen; import javafx.stage.Window; - +import com.sun.javafx.scene.traversal.TraversalUtils; import com.sun.javafx.util.Utils; import com.sun.webkit.CursorManager; import com.sun.webkit.WebPageClient; @@ -107,8 +104,9 @@ public WebPageClientImpl(Accessor accessor) { } } - @Override public void transferFocus(boolean forward) { - NodeHelper.traverse(accessor.getView(), forward ? Direction.NEXT : Direction.PREVIOUS, TraversalMethod.DEFAULT); + @Override + public void transferFocus(boolean forward) { + TraversalUtils.traverse(accessor.getView(), forward ? TraversalDirection.NEXT : TraversalDirection.PREVIOUS, false); } @Override public WCRectangle getScreenBounds(boolean available) { diff --git a/modules/javafx.web/src/main/java/javafx/scene/web/HTMLEditorSkin.java b/modules/javafx.web/src/main/java/javafx/scene/web/HTMLEditorSkin.java index 008bf1094e8..89028580abe 100644 --- a/modules/javafx.web/src/main/java/javafx/scene/web/HTMLEditorSkin.java +++ b/modules/javafx.web/src/main/java/javafx/scene/web/HTMLEditorSkin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, 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 @@ -25,31 +25,35 @@ package javafx.scene.web; +import static javafx.geometry.NodeOrientation.RIGHT_TO_LEFT; +import static javafx.scene.web.HTMLEditorSkin.Command.*; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; import java.util.ResourceBundle; - -import com.sun.javafx.application.PlatformImpl; -import com.sun.javafx.scene.ParentHelper; -import com.sun.javafx.scene.traversal.Algorithm; -import com.sun.javafx.scene.traversal.Direction; -import com.sun.javafx.scene.traversal.ParentTraversalEngine; -import com.sun.javafx.scene.traversal.TraversalContext; -import javafx.css.PseudoClass; -import javafx.geometry.Orientation; -import org.w3c.dom.html.HTMLDocument; -import org.w3c.dom.html.HTMLElement; - import javafx.application.ConditionalFeature; import javafx.application.Platform; import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; +import javafx.css.PseudoClass; import javafx.css.StyleableProperty; import javafx.geometry.NodeOrientation; +import javafx.geometry.Orientation; +import javafx.print.PrinterJob; import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.TraversalDirection; +import javafx.scene.TraversalPolicy; import javafx.scene.control.Button; +import javafx.scene.control.ColorPicker; import javafx.scene.control.ComboBox; +import javafx.scene.control.Control; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.control.Separator; +import javafx.scene.control.SkinBase; import javafx.scene.control.TextInputControl; import javafx.scene.control.ToggleButton; import javafx.scene.control.ToggleGroup; @@ -63,26 +67,17 @@ import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.util.Callback; - +import org.w3c.dom.html.HTMLDocument; +import org.w3c.dom.html.HTMLElement; +import com.sun.javafx.application.PlatformImpl; import com.sun.javafx.scene.control.skin.FXVK; import com.sun.javafx.scene.web.behavior.HTMLEditorBehavior; -import com.sun.webkit.WebPage; import com.sun.javafx.webkit.Accessor; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import javafx.scene.Scene; -import javafx.scene.control.*; -import javafx.scene.layout.*; -import javafx.collections.ListChangeListener; - -import static javafx.geometry.NodeOrientation.*; -import javafx.print.PrinterJob; - -import static javafx.scene.web.HTMLEditorSkin.Command.*; +import com.sun.webkit.WebPage; /** * HTML editor skin. @@ -145,8 +140,6 @@ public class HTMLEditorSkin extends SkinBase { private WebView webView; private WebPage webPage; - private ParentTraversalEngine engine; - private boolean resetToolbarState = false; private String cachedHTMLText = ""; private ResourceBundle resources; @@ -458,23 +451,22 @@ public HTMLEditorSkin(HTMLEditor control) { enableToolbar(true); setHTMLText(cachedHTMLText); - engine = new ParentTraversalEngine(getSkinnable(), new Algorithm() { + getSkinnable().setTraversalPolicy(new TraversalPolicy() { @Override - public Node select(Node owner, Direction dir, TraversalContext context) { + public Node select(Parent root, Node owner, TraversalDirection dir) { return cutButton; } @Override - public Node selectFirst(TraversalContext context) { + public Node selectFirst(Parent root) { return cutButton; } @Override - public Node selectLast(TraversalContext context) { + public Node selectLast(Parent root) { return cutButton; } }); - ParentHelper.setTraversalEngine(getSkinnable(), engine); webView.setFocusTraversable(true); gridPane.getChildren().addListener(itemsListener); }