diff --git a/modules/javafx.base/src/main/java/com/sun/javafx/event/EventUtil.java b/modules/javafx.base/src/main/java/com/sun/javafx/event/EventUtil.java index 10f9fa05a2b..f0c02172a37 100644 --- a/modules/javafx.base/src/main/java/com/sun/javafx/event/EventUtil.java +++ b/modules/javafx.base/src/main/java/com/sun/javafx/event/EventUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -39,19 +39,34 @@ public final class EventUtil { new AtomicBoolean(); public static Event fireEvent(EventTarget eventTarget, Event event) { + return fireEvent(eventTarget, null, event); + } + + /** + * Dispatches an event to the specified {@code eventTarget}, optionally extending the event + * dispatch chain up to the {@code delegateTarget}. The delegate target must be a descendant + * of the {@code eventTarget}. + * + * @param eventTarget the event target, not {@code null} + * @param delegateTarget the delegate event target, may be {@code null} + * @param event the event + * @return the event after processing, or {@code null} + */ + public static Event fireEvent(EventTarget eventTarget, EventTarget delegateTarget, Event event) { if (event.getTarget() != eventTarget) { event = event.copyFor(event.getSource(), eventTarget); } + EventTarget actualTarget = delegateTarget != null ? delegateTarget : eventTarget; + if (eventDispatchChainInUse.getAndSet(true)) { // the member event dispatch chain is in use currently, we need to // create a new instance for this call - return fireEventImpl(new EventDispatchChainImpl(), - eventTarget, event); + return fireEventImpl(new EventDispatchChainImpl(), actualTarget, event); } try { - return fireEventImpl(eventDispatchChain, eventTarget, event); + return fireEventImpl(eventDispatchChain, actualTarget, event); } finally { // need to do reset after use to remove references to event // dispatchers from the chain diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/SpinnerBehavior.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/SpinnerBehavior.java index fed927a8af2..9c8670bd623 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/SpinnerBehavior.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/SpinnerBehavior.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -31,14 +31,11 @@ import javafx.scene.control.Spinner; import javafx.scene.control.SpinnerValueFactory; import com.sun.javafx.scene.control.inputmap.InputMap; +import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.util.Duration; - import java.util.List; -import static javafx.scene.input.KeyCode.*; -import static com.sun.javafx.scene.control.inputmap.InputMap.KeyMapping; - public class SpinnerBehavior extends BehaviorBase> { // this specifies how long the mouse has to be pressed on a button @@ -69,7 +66,20 @@ public class SpinnerBehavior extends BehaviorBase> { } }; + private final EventHandler spinnerKeyHandler = e -> { + boolean arrowsAreVertical = arrowsAreVertical(); + KeyCode increment = arrowsAreVertical ? KeyCode.UP : KeyCode.RIGHT; + KeyCode decrement = arrowsAreVertical ? KeyCode.DOWN : KeyCode.LEFT; + if (e.getCode() == increment) { + increment(1); + e.consume(); + } + else if (e.getCode() == decrement) { + decrement(1); + e.consume(); + } + }; /*************************************************************************** * * @@ -84,24 +94,14 @@ public SpinnerBehavior(Spinner spinner) { // InputMap installed on the control, if it is non-null, allowing us to pick up any user-specified mappings) spinnerInputMap = createInputMap(); - // then spinner-specific mappings for key and mouse input - addDefaultMapping(spinnerInputMap, - new KeyMapping(UP, KeyEvent.KEY_PRESSED, e -> { - if (arrowsAreVertical()) increment(1); else FocusTraversalInputMap.traverseUp(e); - }), - new KeyMapping(RIGHT, KeyEvent.KEY_PRESSED, e -> { - if (! arrowsAreVertical()) increment(1); else FocusTraversalInputMap.traverseRight(e); - }), - new KeyMapping(LEFT, KeyEvent.KEY_PRESSED, e -> { - if (! arrowsAreVertical()) decrement(1); else FocusTraversalInputMap.traverseLeft(e); - }), - new KeyMapping(DOWN, KeyEvent.KEY_PRESSED, e -> { - if (arrowsAreVertical()) decrement(1); else FocusTraversalInputMap.traverseDown(e); - }) - ); + spinner.addEventFilter(KeyEvent.KEY_PRESSED, spinnerKeyHandler); } - + @Override + public void dispose() { + getNode().removeEventFilter(KeyEvent.KEY_PRESSED, spinnerKeyHandler); + super.dispose(); + } /*************************************************************************** * * diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/Control.java b/modules/javafx.controls/src/main/java/javafx/scene/control/Control.java index 1c0b796b63d..c71a36cc13d 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/Control.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/Control.java @@ -595,6 +595,11 @@ protected Control() { } } + @Override + protected boolean isFocusScope() { + return true; + } + /* ************************************************************************* * Implementation of layout bounds for the Control. We want to preserve * * the lazy semantics of layout bounds. So whenever the width/height * @@ -634,6 +639,15 @@ protected Skin createDefaultSkin() { return null; } + @Override + protected Node getFocusDelegate(Node hoistingNode) { + if (skinBase != null) { + return skinBase.getFocusDelegate(hoistingNode); + } + + return null; + } + /* ************************************************************************* * * * Package API for SkinBase * diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java b/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java index cd21a42cd70..0be430c842e 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java @@ -43,6 +43,7 @@ import javafx.scene.AccessibleAction; import javafx.scene.AccessibleAttribute; import javafx.scene.Node; +import javafx.scene.Parent; import javafx.scene.input.MouseEvent; import javafx.scene.layout.Region; @@ -190,6 +191,18 @@ public final ObservableList getChildren() { return children; } + /** + * Overriding this method allows skins to specify the {@link Parent#getFocusDelegate(Node)}. + * + * @param hoistingNode the descendant of this skin that hoisted the focus request + * (not necessarily the focus delegate), or {@code null} + * @return the focus delegate + * @since 24 + */ + protected Node getFocusDelegate(Node hoistingNode) { + return null; + } + /** * Called during the layout pass of the scenegraph. * @param contentX the x position diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/Spinner.java b/modules/javafx.controls/src/main/java/javafx/scene/control/Spinner.java index bc2a42a95cc..95a32da52bc 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/Spinner.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/Spinner.java @@ -24,7 +24,6 @@ */ package javafx.scene.control; -import com.sun.javafx.scene.control.FakeFocusTextField; import javafx.beans.property.StringProperty; import javafx.scene.control.skin.SpinnerSkin; import javafx.beans.NamedArg; @@ -37,7 +36,6 @@ import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.beans.value.WritableValue; -import javafx.collections.MapChangeListener; import javafx.collections.ObservableList; import javafx.scene.AccessibleAction; import javafx.scene.AccessibleAttribute; @@ -153,17 +151,6 @@ public Spinner() { value.addListener((o, oldValue, newValue) -> setText(newValue)); - // Fix for JDK-8115009 - getProperties().addListener((MapChangeListener) change -> { - if (change.wasAdded()) { - if (change.getKey() == "FOCUSED") { - setFocused((Boolean)change.getValueAdded()); - getProperties().remove("FOCUSED"); - } - } - }); - // End of fix for JDK-8115009 - focusedProperty().addListener(o -> { if (!isFocused()) { try { @@ -610,7 +597,7 @@ public final BooleanProperty editableProperty() { public final ReadOnlyObjectProperty editorProperty() { if (editor == null) { editor = new ReadOnlyObjectWrapper<>(this, "editor"); - textField = new FakeFocusTextField(); + textField = new TextField(); textField.tooltipProperty().bind(tooltipProperty()); editor.set(textField); } 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..602122dbcf5 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 @@ -42,7 +42,6 @@ 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; @@ -106,6 +105,7 @@ public SpinnerSkin(Spinner control) { behavior = new SpinnerBehavior<>(control); textField = control.getEditor(); + textField.setHoistFocus(true); ListenerHelper lh = ListenerHelper.get(this); @@ -116,6 +116,7 @@ public SpinnerSkin(Spinner control) { // increment / decrement arrows incrementArrow = new Region(); + incrementArrow.setHoistFocus(true); incrementArrow.setFocusTraversable(false); incrementArrow.getStyleClass().setAll("increment-arrow"); incrementArrow.setMaxWidth(Region.USE_PREF_SIZE); @@ -132,6 +133,7 @@ public void executeAccessibleAction(AccessibleAction action, Object... parameter } }; incrementArrowButton.setAccessibleRole(AccessibleRole.INCREMENT_BUTTON); + incrementArrowButton.setHoistFocus(true); incrementArrowButton.setFocusTraversable(false); incrementArrowButton.getStyleClass().setAll("increment-arrow-button"); incrementArrowButton.getChildren().add(incrementArrow); @@ -142,6 +144,7 @@ public void executeAccessibleAction(AccessibleAction action, Object... parameter incrementArrowButton.setOnMouseReleased(e -> behavior.stopSpinning()); decrementArrow = new Region(); + decrementArrow.setHoistFocus(true); decrementArrow.setFocusTraversable(false); decrementArrow.getStyleClass().setAll("decrement-arrow"); decrementArrow.setMaxWidth(Region.USE_PREF_SIZE); @@ -158,6 +161,7 @@ public void executeAccessibleAction(AccessibleAction action, Object... parameter } }; decrementArrowButton.setAccessibleRole(AccessibleRole.DECREMENT_BUTTON); + decrementArrowButton.setHoistFocus(true); decrementArrowButton.setFocusTraversable(false); decrementArrowButton.getStyleClass().setAll("decrement-arrow-button"); decrementArrowButton.getChildren().add(decrementArrow); @@ -169,56 +173,8 @@ public void executeAccessibleAction(AccessibleAction action, Object... parameter getChildren().addAll(incrementArrowButton, decrementArrowButton); - // Fixes in the same vein as ComboBoxListViewSkin - - // move fake focus in to the textfield if the spinner is editable - lh.addChangeListener(control.focusedProperty(), (op) -> { - // Fix for the regression noted in a comment in JDK-8115009. - ((FakeFocusTextField)textField).setFakeFocus(control.isFocused()); - }); - - lh.addEventFilter(control, KeyEvent.ANY, (ke) -> { - if (control.isEditable()) { - // This prevents a stack overflow from our rebroadcasting of the - // event to the textfield that occurs in the final else statement - // of the conditions below. - if (ke.getTarget().equals(textField)) return; - - // Fix for JDK-8095537 which led to a stack overflow - if (ke.getCode() == KeyCode.ESCAPE) return; - - // This and the additional check of isIncDecKeyEvent in - // textField's event filter fix JDK-8185937. - if (isIncDecKeyEvent(ke)) return; - - // Fix for the regression noted in a comment in JDK-8115009. - // This forwards the event down into the TextField when - // the key event is actually received by the Spinner. - textField.fireEvent(ke.copyFor(textField, textField)); - - if (ke.getCode() == KeyCode.ENTER) return; - - ke.consume(); - } - }); - - // This event filter is to enable keyboard events being delivered to the - // spinner when the user has mouse clicked into the TextField area of the - // Spinner control. Without this the up/down/left/right arrow keys don't - // work when you click inside the TextField area (but they do in the case - // of tabbing in). - lh.addEventFilter(textField, KeyEvent.ANY, (ke) -> { - if (! control.isEditable() || isIncDecKeyEvent(ke)) { - control.fireEvent(ke.copyFor(control, control)); - ke.consume(); - } - }); - lh.addChangeListener(textField.focusedProperty(), (op) -> { boolean hasFocus = textField.isFocused(); - // Fix for JDK-8115009 - control.getProperties().put("FOCUSED", hasFocus); - // --- end of JDK-8115009 // JDK-8120120 starts here if (! hasFocus) { @@ -229,8 +185,6 @@ public void executeAccessibleAction(AccessibleAction action, Object... parameter // --- end of JDK-8120120 }); - // end of comboBox-esque fixes - textField.focusTraversableProperty().bind(control.editableProperty()); // Following code borrowed from ComboBoxPopupControl, to resolve the @@ -292,6 +246,11 @@ public void dispose() { super.dispose(); } + @Override + protected Node getFocusDelegate(Node hoistingNode) { + return textField; + } + /** {@inheritDoc} */ @Override protected void layoutChildren(final double x, final double y, final double w, final double h) { 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 57dcc6835bf..8811373803c 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Node.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Node.java @@ -107,6 +107,7 @@ import javafx.stage.Window; import javafx.util.Callback; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -634,7 +635,7 @@ public Map, List