From 616c3a4e6a362546360c661841b39becca5d4b5f Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Fri, 8 Mar 2024 10:54:10 -0800 Subject: [PATCH 01/28] 8314968: Public InputMap --- .../control/behavior/ColorPickerBehavior.java | 11 +- .../behavior/ComboBoxBaseBehavior.java | 151 ++-- .../behavior/ComboBoxListViewBehavior.java | 36 +- .../control/behavior/DatePickerBehavior.java | 15 +- .../behavior/FocusTraversalInputMap.java | 16 + .../behavior/PasswordFieldBehavior.java | 40 +- .../control/behavior/TabPaneBehavior.java | 186 +++-- .../control/behavior/TextAreaBehavior.java | 255 +++---- .../control/behavior/TextFieldBehavior.java | 109 +-- .../behavior/TextInputControlBehavior.java | 690 ++++++++--------- .../control/input/EventHandlerPriority.java | 52 ++ .../scene/control/input/KeyEventMapper.java | 68 ++ .../javafx/scene/control/input/PHList.java | 225 ++++++ .../scene/control/inputmap/InputMap.java | 1 + .../scene/control/inputmap/KeyBinding.java | 3 + .../java/javafx/scene/control/ComboBox.java | 21 +- .../javafx/scene/control/ComboBoxBase.java | 11 +- .../java/javafx/scene/control/Control.java | 63 +- .../javafx/scene/control/PopupControl.java | 1 + .../main/java/javafx/scene/control/Skin.java | 1 + .../java/javafx/scene/control/SkinBase.java | 16 +- .../java/javafx/scene/control/TabPane.java | 51 +- .../java/javafx/scene/control/TextArea.java | 46 ++ .../scene/control/TextInputControl.java | 61 +- .../scene/control/input/BehaviorBase.java | 244 ++++++ .../scene/control/input/EventCriteria.java | 49 ++ .../scene/control/input/FunctionHandler.java | 54 ++ .../input/FunctionHandlerConditional.java | 45 ++ .../scene/control/input/FunctionTag.java | 37 + .../javafx/scene/control/input/InputMap.java | 458 +++++++++++ .../scene/control/input/KeyBinding.java | 722 ++++++++++++++++++ .../scene/control/input/SkinInputMap.java | 316 ++++++++ .../scene/control/input/package-info.java | 31 + .../scene/control/skin/ColorPickerSkin.java | 22 +- .../scene/control/skin/ComboBoxBaseSkin.java | 4 +- .../control/skin/ComboBoxListViewSkin.java | 20 +- .../scene/control/skin/DatePickerSkin.java | 20 +- .../scene/control/skin/TabPaneSkin.java | 74 +- .../scene/control/skin/TextAreaSkin.java | 48 +- .../scene/control/skin/TextFieldSkin.java | 48 +- .../control/skin/TextInputControlSkin.java | 23 +- .../src/main/java/module-info.java | 1 + modules/javafx.controls/src/test/addExports | 1 + .../control/behavior/BehaviorCleanupTest.java | 143 ++-- .../behavior/BehaviorMemoryLeakTest.java | 44 +- .../infrastructure/ControlSkinFactory.java | 46 +- .../scene/control/behavior/TestInputMap.java | 182 +++++ .../scene/control/behavior/TestPHList.java | 162 ++++ .../behavior/TextAreaBehaviorRobotTest.java | 3 + 49 files changed, 3928 insertions(+), 998 deletions(-) create mode 100644 modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/input/EventHandlerPriority.java create mode 100644 modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/input/KeyEventMapper.java create mode 100644 modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/input/PHList.java create mode 100644 modules/javafx.controls/src/main/java/javafx/scene/control/input/BehaviorBase.java create mode 100644 modules/javafx.controls/src/main/java/javafx/scene/control/input/EventCriteria.java create mode 100644 modules/javafx.controls/src/main/java/javafx/scene/control/input/FunctionHandler.java create mode 100644 modules/javafx.controls/src/main/java/javafx/scene/control/input/FunctionHandlerConditional.java create mode 100644 modules/javafx.controls/src/main/java/javafx/scene/control/input/FunctionTag.java create mode 100644 modules/javafx.controls/src/main/java/javafx/scene/control/input/InputMap.java create mode 100644 modules/javafx.controls/src/main/java/javafx/scene/control/input/KeyBinding.java create mode 100644 modules/javafx.controls/src/main/java/javafx/scene/control/input/SkinInputMap.java create mode 100644 modules/javafx.controls/src/main/java/javafx/scene/control/input/package-info.java create mode 100644 modules/javafx.controls/src/test/java/test/javafx/scene/control/behavior/TestInputMap.java create mode 100644 modules/javafx.controls/src/test/java/test/javafx/scene/control/behavior/TestPHList.java diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ColorPickerBehavior.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ColorPickerBehavior.java index be6cde212f4..2c05913b9b5 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ColorPickerBehavior.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ColorPickerBehavior.java @@ -27,7 +27,6 @@ import javafx.scene.control.ColorPicker; import javafx.scene.control.PopupControl; - import javafx.scene.paint.Color; public class ColorPickerBehavior extends ComboBoxBaseBehavior { @@ -41,8 +40,8 @@ public class ColorPickerBehavior extends ComboBoxBaseBehavior { /** * */ - public ColorPickerBehavior(final ColorPicker colorPicker) { - super(colorPicker); + public ColorPickerBehavior(ColorPicker c) { + super(c); } /************************************************************************** @@ -54,14 +53,14 @@ public ColorPickerBehavior(final ColorPicker colorPicker) { @Override public void onAutoHide(PopupControl popup) { // when we click on some non interactive part of the // Color Palette - we do not want to hide. - if (!popup.isShowing() && getNode().isShowing()) { + if (!popup.isShowing() && getControl().isShowing()) { // Popup was dismissed. Maybe user clicked outside or typed ESCAPE. // Make sure DatePicker button is in sync. - getNode().hide(); + getControl().hide(); } // if the ColorPicker is no longer showing, then invoke the super method // to keep its show/hide state in sync. - if (!getNode().isShowing()) { + if (!getControl().isShowing()) { super.onAutoHide(popup); } } diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ComboBoxBaseBehavior.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ComboBoxBaseBehavior.java index f1fee7be22f..a447394ad27 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ComboBoxBaseBehavior.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ComboBoxBaseBehavior.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020, 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,11 +25,8 @@ package com.sun.javafx.scene.control.behavior; -import com.sun.javafx.scene.control.inputmap.InputMap; - import javafx.beans.InvalidationListener; import javafx.beans.Observable; -import javafx.event.EventHandler; import javafx.event.EventTarget; import javafx.scene.Node; import javafx.scene.control.ComboBox; @@ -37,18 +34,16 @@ import javafx.scene.control.DatePicker; import javafx.scene.control.PopupControl; import javafx.scene.control.TextField; +import javafx.scene.control.input.BehaviorBase; +import javafx.scene.control.input.KeyBinding; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; import com.sun.javafx.scene.control.skin.Utils; -import javafx.scene.input.*; -import com.sun.javafx.scene.control.inputmap.KeyBinding; - -import static javafx.scene.input.KeyCode.*; -import static javafx.scene.input.KeyEvent.*; -import static com.sun.javafx.scene.control.inputmap.InputMap.KeyMapping; -import static com.sun.javafx.scene.control.inputmap.InputMap.MouseMapping; public class ComboBoxBaseBehavior extends BehaviorBase> { - private final InputMap> inputMap; private InvalidationListener focusListener = this::focusChanged; /*************************************************************************** @@ -62,65 +57,47 @@ public class ComboBoxBaseBehavior extends BehaviorBase> { /** * */ - public ComboBoxBaseBehavior(final ComboBoxBase comboBox) { - super(comboBox); - - // create a map for comboBox-specific mappings (this reuses the default - // InputMap installed on the control, if it is non-null, allowing us to pick up any user-specified mappings) - inputMap = createInputMap(); - - final EventHandler togglePopup = e -> { - // If popup is shown, KeyEvent causes popup to close - showPopupOnMouseRelease = true; - - if (getNode().isShowing()) hide(); - else show(); - }; - - // comboBox-specific mappings for key and mouse input - KeyMapping enterPressed, enterReleased; - addDefaultMapping(inputMap, - new KeyMapping(F4, KEY_RELEASED, togglePopup), - new KeyMapping(new KeyBinding(UP).alt(), togglePopup), - new KeyMapping(new KeyBinding(DOWN).alt(), togglePopup), + public ComboBoxBaseBehavior(ComboBoxBase c) { + super(c); + } - new KeyMapping(SPACE, KEY_PRESSED, this::keyPressed), - new KeyMapping(SPACE, KEY_RELEASED, this::keyReleased), + @Override + protected void populateSkinInputMap() { + // ComboBoxBase also cares about focus + getControl().focusedProperty().addListener(focusListener); - enterPressed = new KeyMapping(ENTER, KEY_PRESSED, this::keyPressed), - enterReleased = new KeyMapping(ENTER, KEY_RELEASED, this::keyReleased), + // Only add this if we're on an embedded platform that supports 5-button navigation + if (Utils.isTwoLevelFocus()) { + tlFocus = new TwoLevelFocusComboBehavior(getControl()); // needs to be last. + } - // The following keys are forwarded to the parent container - new KeyMapping(ESCAPE, KEY_PRESSED, this::cancelEdit), - new KeyMapping(F10, KEY_PRESSED, this::forwardToParent), + registerFunction(ComboBoxBase.TOGGLE_POPUP, this::togglePopup); - new MouseMapping(MouseEvent.MOUSE_PRESSED, this::mousePressed), - new MouseMapping(MouseEvent.MOUSE_RELEASED, this::mouseReleased), - new MouseMapping(MouseEvent.MOUSE_ENTERED, this::mouseEntered), - new MouseMapping(MouseEvent.MOUSE_EXITED, this::mouseExited) - ); + registerKey(KeyBinding.with(KeyCode.F4).onKeyReleased().build(), ComboBoxBase.TOGGLE_POPUP); + registerKey(KeyBinding.alt(KeyCode.DOWN), ComboBoxBase.TOGGLE_POPUP); + registerKey(KeyBinding.alt(KeyCode.UP), ComboBoxBase.TOGGLE_POPUP); - // we don't want to consume events on enter press - let them carry on through - enterPressed.setAutoConsume(false); - enterReleased.setAutoConsume(false); + addHandler(KeyBinding.of(KeyCode.SPACE), true, this::keyPressed); + addHandler(KeyBinding.with(KeyCode.SPACE).onKeyReleased().build(), true, this::keyReleased); - // ComboBoxBase also cares about focus - comboBox.focusedProperty().addListener(focusListener); + // these two should not consume the event + addHandler(KeyBinding.of(KeyCode.ENTER), false, this::keyPressed); + addHandler(KeyBinding.with(KeyCode.ENTER).onKeyReleased().build(), false, this::keyReleased); - // Only add this if we're on an embedded platform that supports 5-button navigation - if (Utils.isTwoLevelFocus()) { - tlFocus = new TwoLevelFocusComboBehavior(comboBox); // needs to be last. - } - } + addHandler(KeyBinding.of(KeyCode.ESCAPE), true, this::cancelEdit); + addHandler(KeyBinding.of(KeyCode.F10), true, this::forwardToParent); - @Override public void dispose() { - if (tlFocus != null) tlFocus.dispose(); - getNode().focusedProperty().removeListener(focusListener); - super.dispose(); + addHandler(MouseEvent.MOUSE_PRESSED, true, this::mousePressed); + addHandler(MouseEvent.MOUSE_RELEASED, true, this::mouseReleased); + addHandler(MouseEvent.MOUSE_ENTERED, true, this::mouseEntered); + addHandler(MouseEvent.MOUSE_EXITED, true, this::mouseExited); } - @Override public InputMap> getInputMap() { - return inputMap; + public void dispose() { + if (tlFocus != null) { + tlFocus.dispose(); + } + getControl().focusedProperty().removeListener(focusListener); } /*************************************************************************** @@ -132,7 +109,7 @@ public ComboBoxBaseBehavior(final ComboBoxBase comboBox) { protected void focusChanged(Observable o) { // If we did have the key down, but are now not focused, then we must // disarm the box. - final ComboBoxBase box = getNode(); + final ComboBoxBase box = getControl(); if (keyDown && !box.isFocused()) { keyDown = false; box.disarm(); @@ -169,9 +146,9 @@ private void keyPressed(KeyEvent e) { } } else { - if (! getNode().isPressed() && ! getNode().isArmed()) { + if (! getControl().isPressed() && ! getControl().isArmed()) { keyDown = true; - getNode().arm(); + getControl().arm(); } } } @@ -187,16 +164,16 @@ private void keyReleased(KeyEvent e) { if (!Utils.isTwoLevelFocus()) { if (keyDown) { keyDown = false; - if (getNode().isArmed()) { - getNode().disarm(); + if (getControl().isArmed()) { + getControl().disarm(); } } } } private void forwardToParent(KeyEvent event) { - if (getNode().getParent() != null) { - getNode().getParent().fireEvent(event); + if (getControl().getParent() != null) { + getControl().getParent().fireEvent(event); } } @@ -205,7 +182,7 @@ private void cancelEdit(KeyEvent event) { * This can be cleaned up if the editor property is moved up * to ComboBoxBase. */ - ComboBoxBase comboBoxBase = getNode(); + ComboBoxBase comboBoxBase = getControl(); TextField textField = null; if (comboBoxBase instanceof DatePicker) { textField = ((DatePicker)comboBoxBase).getEditor(); @@ -248,7 +225,7 @@ public void mouseReleased(MouseEvent e) { } public void mouseEntered(MouseEvent e) { - if (!getNode().isEditable()) { + if (!getControl().isEditable()) { mouseInsideButton = true; } else { // This is strongly tied to ComboBoxBaseSkin @@ -274,23 +251,23 @@ private void arm(MouseEvent e) { ! (e.isMiddleButtonDown() || e.isSecondaryButtonDown() || e.isShiftDown() || e.isControlDown() || e.isAltDown() || e.isMetaDown())); - if (! getNode().isArmed() && valid) { - getNode().arm(); + if (! getControl().isArmed() && valid) { + getControl().arm(); } } public void show() { - if (! getNode().isShowing()) { - if (getNode().isFocusTraversable()) { - getNode().requestFocus(); + if (! getControl().isShowing()) { + if (getControl().isFocusTraversable()) { + getControl().requestFocus(); } - getNode().show(); + getControl().show(); } } public void hide() { - if (getNode().isShowing()) { - getNode().hide(); + if (getControl().isShowing()) { + getControl().hide(); } } @@ -307,15 +284,25 @@ public void onAutoHide(PopupControl popup) { } public void arm() { - if (getNode().isPressed()) { - getNode().arm(); + if (getControl().isPressed()) { + getControl().arm(); } } public void disarm() { - if (! keyDown && getNode().isArmed()) { - getNode().disarm(); + if (! keyDown && getControl().isArmed()) { + getControl().disarm(); } } + private void togglePopup(ComboBoxBase c) { + // If popup is shown, KeyEvent causes popup to close + showPopupOnMouseRelease = true; + + if (c.isShowing()) { + hide(); + } else { + show(); + } + } } diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ComboBoxListViewBehavior.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ComboBoxListViewBehavior.java index 3fb031bd3c2..0eb4c5075e2 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ComboBoxListViewBehavior.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ComboBoxListViewBehavior.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2021, 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 @@ -28,10 +28,7 @@ import javafx.scene.control.ComboBox; import javafx.scene.control.ComboBoxBase; import javafx.scene.control.SelectionModel; -import com.sun.javafx.scene.control.inputmap.InputMap; - -import static javafx.scene.input.KeyCode.DOWN; -import static javafx.scene.input.KeyCode.UP; +import javafx.scene.input.KeyCode; public class ComboBoxListViewBehavior extends ComboBoxBaseBehavior { @@ -44,16 +41,19 @@ public class ComboBoxListViewBehavior extends ComboBoxBaseBehavior { /** * */ - public ComboBoxListViewBehavior(final ComboBox comboBox) { - super(comboBox); + public ComboBoxListViewBehavior(ComboBoxBase c) { + super(c); + } + + @Override + protected void populateSkinInputMap() { + super.populateSkinInputMap(); + + registerFunction(ComboBox.SELECT_PREV, this::selectPrevious); + registerFunction(ComboBox.SELECT_NEXT, this::selectNext); - // Add these bindings as a child input map, so they take precedence - InputMap> comboBoxListViewInputMap = new InputMap<>(comboBox); - comboBoxListViewInputMap.getMappings().addAll( - new InputMap.KeyMapping(UP, e -> selectPrevious()), - new InputMap.KeyMapping(DOWN, e -> selectNext()) - ); - addDefaultChildMap(getInputMap(), comboBoxListViewInputMap); + registerKey(KeyCode.UP, ComboBox.SELECT_PREV); + registerKey(KeyCode.DOWN, ComboBox.SELECT_NEXT); } /*************************************************************************** @@ -63,16 +63,18 @@ public ComboBoxListViewBehavior(final ComboBox comboBox) { **************************************************************************/ private ComboBox getComboBox() { - return (ComboBox) getNode(); + return (ComboBox) getControl(); } - private void selectPrevious() { + private void selectPrevious(ComboBoxBase c) { + // NOTE: ComboBoxBase does not have getSelectionModel(). design problem? SelectionModel sm = getComboBox().getSelectionModel(); if (sm == null) return; sm.selectPrevious(); } - private void selectNext() { + private void selectNext(ComboBoxBase c) { + // NOTE: ComboBoxBase does not have getSelectionModel(). design problem? SelectionModel sm = getComboBox().getSelectionModel(); if (sm == null) return; sm.selectNext(); diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/DatePickerBehavior.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/DatePickerBehavior.java index 709d3d15e5b..1b25cdc030f 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/DatePickerBehavior.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/DatePickerBehavior.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,11 +25,10 @@ package com.sun.javafx.scene.control.behavior; +import java.time.LocalDate; import javafx.scene.control.DatePicker; import javafx.scene.control.PopupControl; -import java.time.LocalDate; - public class DatePickerBehavior extends ComboBoxBaseBehavior { @@ -42,8 +41,8 @@ public class DatePickerBehavior extends ComboBoxBaseBehavior { /** * */ - public DatePickerBehavior(final DatePicker datePicker) { - super(datePicker); + public DatePickerBehavior(DatePicker c) { + super(c); } /*************************************************************************** @@ -56,14 +55,14 @@ public DatePickerBehavior(final DatePicker datePicker) { @Override public void onAutoHide(PopupControl popup) { // when we click on some non-interactive part of the // calendar - we do not want to hide. - if (!popup.isShowing() && getNode().isShowing()) { + if (!popup.isShowing() && getControl().isShowing()) { // Popup was dismissed. Maybe user clicked outside or typed ESCAPE. // Make sure DatePicker button is in sync. - getNode().hide(); + getControl().hide(); } // if the DatePicker is no longer showing, then invoke the super method // to keep its show/hide state in sync. - if (!getNode().isShowing()) { + if (!getControl().isShowing()) { super.onAutoHide(popup); } } 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..da6c8a6f9c5 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 @@ -137,6 +137,14 @@ public static final void traverseNext(KeyEvent e) { traverse(getNode(e), com.sun.javafx.scene.traversal.Direction.NEXT, TraversalMethod.KEY); } + /** + * Calls the focus traversal engine and indicates that traversal should + * go the next focusTraversable Node in the focus traversal cycle. + */ + public static final void traverseNext(Node n) { + traverse(n, com.sun.javafx.scene.traversal.Direction.NEXT, TraversalMethod.KEY); + } + /** * Calls the focus traversal engine and indicates that traversal should * go the previous focusTraversable Node in the focus traversal cycle. @@ -145,6 +153,14 @@ public static final void traversePrevious(KeyEvent e) { traverse(getNode(e), com.sun.javafx.scene.traversal.Direction.PREVIOUS, TraversalMethod.KEY); } + /** + * Calls the focus traversal engine and indicates that traversal should + * go the previous focusTraversable Node in the focus traversal cycle. + */ + public static final void traversePrevious(Node n) { + traverse(n, com.sun.javafx.scene.traversal.Direction.PREVIOUS, TraversalMethod.KEY); + } + private static Node getNode(KeyEvent e) { EventTarget target = e.getTarget(); if (target instanceof Node) { diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/PasswordFieldBehavior.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/PasswordFieldBehavior.java index 57960f8cb0d..daa10ddee1c 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/PasswordFieldBehavior.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/PasswordFieldBehavior.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, 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 @@ -26,6 +26,8 @@ package com.sun.javafx.scene.control.behavior; import javafx.scene.control.PasswordField; +import javafx.scene.control.TextField; +import javafx.scene.control.skin.TextFieldSkin; import javafx.scene.text.HitInfo; /** @@ -33,31 +35,43 @@ */ public class PasswordFieldBehavior extends TextFieldBehavior { - public PasswordFieldBehavior(PasswordField passwordField) { - super(passwordField); + public PasswordFieldBehavior(PasswordField c, TextFieldSkin skin) { + super(c, skin); } // RT-18711 & RT-18854: Stub out word based navigation and editing // for security reasons. @Override - protected void deletePreviousWord() { } + protected void deletePreviousWord(TextField c) { + } + @Override - protected void deleteNextWord() { } + protected void deleteNextWord(TextField c) { + } + @Override - protected void selectPreviousWord() { } + protected void selectPreviousWord(TextField c) { + } + @Override - public void selectNextWord() { } + public void selectNextWord(TextField c) { + } + @Override - protected void previousWord() { } + protected void previousWord(TextField c) { + } + @Override - protected void nextWord() { } + protected void nextWord(TextField c) { + } + @Override - protected void selectWord() { - selectAll(); + protected void selectWord(TextField c) { + selectAll(c); } + @Override protected void mouseDoubleClick(HitInfo hit) { - getNode().selectAll(); + getControl().selectAll(); } - } diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TabPaneBehavior.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TabPaneBehavior.java index 785aab9487b..c05294203ea 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TabPaneBehavior.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TabPaneBehavior.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2016, 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 @@ -25,75 +25,77 @@ package com.sun.javafx.scene.control.behavior; -import com.sun.javafx.scene.control.inputmap.InputMap; +import java.util.List; import javafx.event.Event; +import javafx.geometry.NodeOrientation; +import javafx.scene.Node; import javafx.scene.control.SelectionModel; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; -import javafx.scene.input.*; -import com.sun.javafx.scene.control.inputmap.KeyBinding; - -import java.util.List; - -import static javafx.scene.input.KeyCode.*; -import static com.sun.javafx.scene.control.inputmap.InputMap.KeyMapping; -import static com.sun.javafx.scene.control.inputmap.InputMap.MouseMapping; - -public class TabPaneBehavior extends BehaviorBase { - - private final InputMap tabPaneInputMap; - - public TabPaneBehavior(TabPane tabPane) { - super(tabPane); +import javafx.scene.control.input.KeyBinding; +import javafx.scene.control.input.SkinInputMap; +import javafx.scene.input.KeyCode; +import javafx.scene.input.MouseEvent; - // create a map for TabPane-specific mappings (this reuses the default - // InputMap installed on the control, if it is non-null, allowing us to pick up any user-specified mappings) - tabPaneInputMap = createInputMap(); - - // TabPane-specific mappings for key and mouse input - addDefaultMapping(tabPaneInputMap, - new KeyMapping(UP, e -> selectPreviousTab()), - new KeyMapping(DOWN, e -> selectNextTab()), - new KeyMapping(LEFT, e -> rtl(tabPane, this::selectNextTab, this::selectPreviousTab)), - new KeyMapping(RIGHT, e -> rtl(tabPane, this::selectPreviousTab, this::selectNextTab)), - new KeyMapping(HOME, e -> { - if (getNode().isFocused()) { - moveSelection(-1, 1); - } - }), - new KeyMapping(END, e -> { - if (getNode().isFocused()) { - moveSelection(getNode().getTabs().size(), -1); - } - }), - new KeyMapping(new KeyBinding(PAGE_UP).ctrl(), e -> selectPreviousTab()), - new KeyMapping(new KeyBinding(PAGE_DOWN).ctrl(), e -> selectNextTab()), - new KeyMapping(new KeyBinding(TAB).ctrl(), e -> selectNextTab()), - new KeyMapping(new KeyBinding(TAB).ctrl().shift(), e -> selectPreviousTab()), - new MouseMapping(MouseEvent.MOUSE_PRESSED, e -> getNode().requestFocus()) - ); +/** + * The TabPaneBehavior is stateless. + */ +// The amount of memory saved with stateless (static) behaviors would likely not justify +// more extensive changes that are required to convert the legacy behavior to a stateless one: +// now we have to manually drag the control instance everywhere. +// I don't think this is worth it. +public class TabPaneBehavior { + private static final SkinInputMap inputMap = createInputMap(); + + // stateless behavior: one SkinInputMap for all TabPanes + private TabPaneBehavior() { } + + private static SkinInputMap createInputMap() { + SkinInputMap m = new SkinInputMap<>(); + + m.registerFunction(TabPane.Tag.SELECT_FIRST_TAB, TabPaneBehavior::selectFirstTab); + m.registerFunction(TabPane.Tag.SELECT_LAST_TAB, TabPaneBehavior::selectLastTab); + m.registerFunction(TabPane.Tag.SELECT_LEFT_TAB, TabPaneBehavior::selectLeftTab); + m.registerFunction(TabPane.Tag.SELECT_NEXT_TAB, TabPaneBehavior::selectNextTab); + m.registerFunction(TabPane.Tag.SELECT_PREV_TAB, TabPaneBehavior::selectPreviousTab); + m.registerFunction(TabPane.Tag.SELECT_RIGHT_TAB, TabPaneBehavior::selectRightTab); + + m.registerKey(KeyBinding.of(KeyCode.DOWN), TabPane.Tag.SELECT_NEXT_TAB); + m.registerKey(KeyBinding.of(KeyCode.HOME), TabPane.Tag.SELECT_FIRST_TAB); + m.registerKey(KeyBinding.of(KeyCode.END), TabPane.Tag.SELECT_LAST_TAB); + m.registerKey(KeyBinding.of(KeyCode.LEFT), TabPane.Tag.SELECT_LEFT_TAB); + m.registerKey(KeyBinding.of(KeyCode.RIGHT), TabPane.Tag.SELECT_RIGHT_TAB); + m.registerKey(KeyBinding.of(KeyCode.UP), TabPane.Tag.SELECT_PREV_TAB); + + m.registerKey(KeyBinding.ctrl(KeyCode.PAGE_DOWN), TabPane.Tag.SELECT_NEXT_TAB); + m.registerKey(KeyBinding.ctrl(KeyCode.PAGE_UP), TabPane.Tag.SELECT_PREV_TAB); + m.registerKey(KeyBinding.ctrl(KeyCode.TAB), TabPane.Tag.SELECT_NEXT_TAB); + m.registerKey(KeyBinding.ctrlShift(KeyCode.TAB), TabPane.Tag.SELECT_PREV_TAB); + + m.addHandler(MouseEvent.MOUSE_PRESSED, true, TabPaneBehavior::requestFocus); + + return m; } - @Override public InputMap getInputMap() { - return tabPaneInputMap; + public static void install(TabPane control) { + control.getInputMap().setSkinInputMap(inputMap); } - public void selectTab(Tab tab) { - getNode().getSelectionModel().select(tab); + public static void selectTab(TabPane c, Tab tab) { + c.getSelectionModel().select(tab); } - public boolean canCloseTab(Tab tab) { - Event event = new Event(tab,tab,Tab.TAB_CLOSE_REQUEST_EVENT); - Event.fireEvent(tab, event); - return ! event.isConsumed(); + public static boolean canCloseTab(Tab tab) { + Event ev = new Event(tab, tab, Tab.TAB_CLOSE_REQUEST_EVENT); + Event.fireEvent(tab, ev); + return !ev.isConsumed(); } - public void closeTab(Tab tab) { - TabPane tabPane = getNode(); + public static void closeTab(TabPane c, Tab tab) { // only switch to another tab if the selected tab is the one we're closing - int index = tabPane.getTabs().indexOf(tab); + int index = c.getTabs().indexOf(tab); if (index != -1) { - tabPane.getTabs().remove(index); + c.getTabs().remove(index); } if (tab.getOnClosed() != null) { Event.fireEvent(tab, new Event(Tab.CLOSED_EVENT)); @@ -102,34 +104,72 @@ public void closeTab(Tab tab) { // Find a tab after the currently selected that is not disabled. Loop around // if no tabs are found after currently selected tab. - public void selectNextTab() { - moveSelection(1); + public static void selectNextTab(TabPane c) { + moveSelection(c, 1); } // Find a tab before the currently selected that is not disabled. - public void selectPreviousTab() { - moveSelection(-1); + public static void selectPreviousTab(TabPane c) { + moveSelection(c, -1); + } + + private static void selectLeftTab(TabPane c) { + if (isRTL(c)) { + selectNextTab(c); + } else { + selectPreviousTab(c); + } + } + + private static void selectRightTab(TabPane c) { + if (isRTL(c)) { + selectPreviousTab(c); + } else { + selectNextTab(c); + } } - private void moveSelection(int delta) { - moveSelection(getNode().getSelectionModel().getSelectedIndex(), delta); + private static boolean isRTL(TabPane c) { + return c.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT; } - private void moveSelection(int startIndex, int delta) { - final TabPane tabPane = getNode(); - if (tabPane.getTabs().isEmpty()) return; + // TODO a bit of controversy: should this method return boolean to avoid consuming the key event + // when the control is not focused? + private static void selectFirstTab(TabPane c) { + if (c.isFocused()) { + moveSelection(c, -1, 1); + } + } + + // TODO a bit of controversy: should this method return boolean to avoid consuming the key event + // when the control is not focused? + private static void selectLastTab(TabPane c) { + if (c.isFocused()) { + int sz = c.getTabs().size(); + moveSelection(c, sz, -1); + } + } - int tabIndex = findValidTab(startIndex, delta); + private static void moveSelection(TabPane c, int delta) { + int ix = c.getSelectionModel().getSelectedIndex(); + moveSelection(c, ix, delta); + } + + private static void moveSelection(TabPane c, int startIndex, int delta) { + if (c.getTabs().isEmpty()) { + return; + } + + int tabIndex = findValidTab(c, startIndex, delta); if (tabIndex > -1) { - final SelectionModel selectionModel = tabPane.getSelectionModel(); + final SelectionModel selectionModel = c.getSelectionModel(); selectionModel.select(tabIndex); } - tabPane.requestFocus(); + c.requestFocus(); } - private int findValidTab(int startIndex, int delta) { - final TabPane tabPane = getNode(); - final List tabs = tabPane.getTabs(); + private static int findValidTab(TabPane c, int startIndex, int delta) { + final List tabs = c.getTabs(); final int max = tabs.size(); int index = startIndex; @@ -144,7 +184,7 @@ private int findValidTab(int startIndex, int delta) { return -1; } - private int nextIndex(int value, int max) { + private static int nextIndex(int value, int max) { final int min = 0; int r = value % max; if (r > min && max < min) { @@ -154,4 +194,8 @@ private int nextIndex(int value, int max) { } return r; } + + private static void requestFocus(MouseEvent ev) { + ((Node)ev.getSource()).requestFocus(); + } } diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TextAreaBehavior.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TextAreaBehavior.java index f36ba23144e..f25aeed70c3 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TextAreaBehavior.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/TextAreaBehavior.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, 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 @@ -25,130 +25,132 @@ package com.sun.javafx.scene.control.behavior; -import com.sun.javafx.PlatformUtil; -import com.sun.javafx.scene.control.Properties; -import javafx.scene.control.skin.TextAreaSkin; import javafx.beans.value.ChangeListener; import javafx.geometry.Point2D; import javafx.geometry.Rectangle2D; import javafx.scene.Scene; import javafx.scene.control.TextArea; -import com.sun.javafx.scene.control.skin.Utils; +import javafx.scene.control.input.KeyBinding; +import javafx.scene.control.skin.TextAreaSkin; +import javafx.scene.control.skin.TextInputControlSkin.Direction; +import javafx.scene.control.skin.TextInputControlSkin.TextUnit; import javafx.scene.input.ContextMenuEvent; -import com.sun.javafx.scene.control.inputmap.InputMap; -import com.sun.javafx.scene.control.inputmap.KeyBinding; -import javafx.scene.input.KeyEvent; +import javafx.scene.input.KeyCode; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; import javafx.scene.text.HitInfo; import javafx.stage.Screen; import javafx.stage.Window; - -import java.util.function.Predicate; - -import static com.sun.javafx.PlatformUtil.isMac; -import static com.sun.javafx.PlatformUtil.isWindows; -import static javafx.scene.control.skin.TextInputControlSkin.TextUnit; -import static javafx.scene.control.skin.TextInputControlSkin.Direction; -import static javafx.scene.input.KeyCode.*; +import com.sun.javafx.PlatformUtil; +import com.sun.javafx.scene.control.Properties; +import com.sun.javafx.scene.control.skin.Utils; /** * Text area behavior. */ public class TextAreaBehavior extends TextInputControlBehavior