--- old/modules/controls/src/main/java/com/sun/javafx/scene/control/behavior/BehaviorBase.java 2015-09-03 14:46:48.355715100 -0700 +++ new/modules/controls/src/main/java/com/sun/javafx/scene/control/behavior/BehaviorBase.java 2015-09-03 14:46:47.785682500 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -22,387 +22,125 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ - package com.sun.javafx.scene.control.behavior; -import javafx.application.ConditionalFeature; -import javafx.application.Platform; -import javafx.beans.InvalidationListener; -import javafx.beans.Observable; -import javafx.event.EventHandler; import javafx.scene.Node; -import javafx.scene.control.Control; -import javafx.scene.input.ContextMenuEvent; -import javafx.scene.input.KeyEvent; -import javafx.scene.input.MouseEvent; +import com.sun.javafx.scene.control.inputmap.InputMap; +import com.sun.javafx.scene.control.inputmap.InputMap.Mapping; + import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import com.sun.javafx.scene.traversal.Direction; -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; - -/** - * A convenient base class from which all our built-in behaviors extend. The - * main functionality in BehaviorBase revolves around infrastructure for - * resolving key events into function calls. The differences between platforms - * can be subtle, and we attempt to build sufficient infrastructure into - * BehaviorBase to minimize the amount of code and the complexity of code - * necessary to support multiple platforms sufficiently well. - * - *

Although BehaviorBase is typically used as a base class, it is not abstract and - * several skins instantiate an instance of BehaviorBase directly.

- * - *

BehaviorBase also implements the hooks for focus traversal. This - * implementation is sufficient for most subclasses of BehaviorBase. The - * following action names are registered in the keyMap for handling focus - * traversal. Subclasses which need to invoke focus traversal using non-standard - * key strokes should map key strokes to these action names:

- * - * - *

Note that by convention, action names are camel case with the first letter - * uppercase, matching class naming conventions.

- */ -public class BehaviorBase { - /** - * A static final reference to whether the platform we are on supports touch. - */ - protected final static boolean IS_TOUCH_SUPPORTED = Platform.isSupported(ConditionalFeature.INPUT_TOUCH); - - /** - * The default key bindings for focus traversal. For many behavior - * implementations, you may be able to use this directly. The built in names - * for these traversal actions are: - * - */ - protected static final List TRAVERSAL_BINDINGS = new ArrayList<>(); - static final String TRAVERSE_UP = "TraverseUp"; - static final String TRAVERSE_DOWN = "TraverseDown"; - static final String TRAVERSE_LEFT = "TraverseLeft"; - static final String TRAVERSE_RIGHT = "TraverseRight"; - static final String TRAVERSE_NEXT = "TraverseNext"; - static final String TRAVERSE_PREVIOUS = "TraversePrevious"; - - static { - TRAVERSAL_BINDINGS.add(new KeyBinding(UP, TRAVERSE_UP)); - TRAVERSAL_BINDINGS.add(new KeyBinding(DOWN, TRAVERSE_DOWN)); - TRAVERSAL_BINDINGS.add(new KeyBinding(LEFT, TRAVERSE_LEFT)); - TRAVERSAL_BINDINGS.add(new KeyBinding(RIGHT, TRAVERSE_RIGHT)); - TRAVERSAL_BINDINGS.add(new KeyBinding(TAB, TRAVERSE_NEXT)); - TRAVERSAL_BINDINGS.add(new KeyBinding(TAB, TRAVERSE_PREVIOUS).shift()); - - TRAVERSAL_BINDINGS.add(new KeyBinding(UP, TRAVERSE_UP).shift().alt().ctrl()); - TRAVERSAL_BINDINGS.add(new KeyBinding(DOWN, TRAVERSE_DOWN).shift().alt().ctrl()); - TRAVERSAL_BINDINGS.add(new KeyBinding(LEFT, TRAVERSE_LEFT).shift().alt().ctrl()); - TRAVERSAL_BINDINGS.add(new KeyBinding(RIGHT, TRAVERSE_RIGHT).shift().alt().ctrl()); - TRAVERSAL_BINDINGS.add(new KeyBinding(TAB, TRAVERSE_NEXT).shift().alt().ctrl()); - TRAVERSAL_BINDINGS.add(new KeyBinding(TAB, TRAVERSE_PREVIOUS).alt().ctrl()); - } - - /** - * The Control with which this Behavior is used. This must be specified in - * the constructor and must not be null. - */ - private final C control; - - /** - * The key bindings for this Behavior. - */ - private final List keyBindings; - - /** - * Listens to any key events on the Control and responds to them - */ - private final EventHandler keyEventListener = e -> { - if (!e.isConsumed()) { - callActionForEvent(e); - } - }; +import java.util.function.Consumer; - /** - * Listens to any focus events on the Control and calls protected methods as a result - */ - private final InvalidationListener focusListener = property -> { - focusChanged(); - }; - - /** - * Create a new BehaviorBase for the given control. The Control must not - * be null. - * - * @param control The control. Must not be null. - * @param keyBindings The key bindings that should be used with this behavior. - * Null is treated as an empty list. - */ - public BehaviorBase(final C control, final List keyBindings) { - // Don't need to explicitly check for null because Collections.unmodifiableList - // will die on null, as will the adding of listeners - this.control = control; - this.keyBindings = keyBindings == null ? Collections.emptyList() - : Collections.unmodifiableList(new ArrayList<>(keyBindings)); - control.addEventHandler(KeyEvent.ANY, keyEventListener); - control.focusedProperty().addListener(focusListener); - } - - /** - * Called by a Skin when the Skin is disposed. This method - * allows a Behavior to implement any logic necessary to clean up itself after - * the Behavior is no longer needed. Calling dispose twice has no effect. This - * method is intended to be overridden by subclasses, although all subclasses - * must call super.dispose() or a potential memory leak will result. - */ - public void dispose() { - control.removeEventHandler(KeyEvent.ANY, keyEventListener); - control.focusedProperty().removeListener(focusListener); +public abstract class BehaviorBase { + + private final N node; + private final List> installedDefaultMappings; + private final List childInputMapDisposalHandlers; + + + public BehaviorBase(N node) { + this.node = node; + this.installedDefaultMappings = new ArrayList<>(); + this.childInputMapDisposalHandlers = new ArrayList<>(); + } + + public abstract InputMap getInputMap(); + + public final N getNode() { + return node; } - /*************************************************************************** - * Implementation of the Behavior "interface" * - * * - * One of the specialized duties of the behavior is to react to key * - * events. The behavior breaks the handling of a key event down into a few * - * distinct stages. First, the BehaviorBase will analyze the key event and * - * find the String name of a matching action to invoke, if any. If an * - * action exists for this event, the name is then fed to * - * callActionForEvent(name), which will then invoke an actual method on * - * the behavior that is the implementation of that action. * - * * - * The reason for returning the intermediate action name as a String is * - * twofold. First, the matching is done by analyzing a set of key bindings * - * which are *statically declared* on each behavior class. The fact that * - * they are static means that we cannot refer to an actual instance method * - * to invoke (such as with lambda's). It is also important that these are * - * static to reduce the memory footprint of a control (since having * - * per-instance key bindings would add a lot to memory footprint). We * - * could have used something other than String as the intermediate token, * - * however String is useful if we ever want to expose to developers a way * - * to alter the action map from a property file or XML file etc. * - * * - **************************************************************************/ - - /** - * Gets the control associated with this behavior. Even after the BehaviorBase is - * disposed, this reference will be non-null. - * - * @return The control for this Behavior. - */ - public final C getControl() { return control; } - - /** - * Invokes the appropriate action for this key event. This is the main entry point where - * key events are passed when they occur. This method is responsible for invoking - * matchActionForEvent, callAction, and consuming the event if it was handled by this control. - * - * @param e The key event. Must not be null. - */ - protected void callActionForEvent(KeyEvent e) { - String action = matchActionForEvent(e); - if (action != null) { - callAction(action); - e.consume(); + public void dispose() { + // when we dispose a behavior, we do NOT want to dispose the InputMap, + // as that can remove input mappings that were not installed by the + // behavior. Instead, we want to only remove mappings that the behavior + // itself installed. This can be done by removing all input mappings that + // were installed via the 'addDefaultMapping' method. + + // remove default mappings only + for (Mapping mapping : installedDefaultMappings) { + getInputMap().getMappings().remove(mapping); + } + + // Remove all default child mappings + for (Runnable r : childInputMapDisposalHandlers) { + r.run(); } + +// InputMap inputMap = getInputMap(); +// if (inputMap != null) { +// inputMap.dispose(); +// } } - /** - * Given a key event, this method will find the matching action name, or null if there - * is not one. - * - * @param e The key event. Must not be null. - * @return The name of the action to invoke, or null if there is not one. - */ - protected String matchActionForEvent(final KeyEvent e) { - if (e == null) throw new NullPointerException("KeyEvent must not be null"); - KeyBinding match = null; - int specificity = 0; - int maxBindings = keyBindings.size(); - for (int i = 0; i < maxBindings; i++) { - KeyBinding binding = keyBindings.get(i); - int s = binding.getSpecificity(control, e); - if (s > specificity) { - specificity = s; - match = binding; - } + protected void addDefaultMapping(List> newMapping) { + addDefaultMapping(getInputMap(), newMapping.toArray(new Mapping[newMapping.size()])); + } + + protected void addDefaultMapping(Mapping... newMapping) { + addDefaultMapping(getInputMap(), newMapping); + } + + protected void addDefaultMapping(InputMap inputMap, Mapping... newMapping) { + // make a copy of the existing mappings, so we only check against those + List> existingMappings = new ArrayList<>(inputMap.getMappings()); + + for (Mapping mapping : newMapping) { + // check if a mapping already exists, and if so, do not add this mapping + // TODO this is insufficient as we need to check entire InputMap hierarchy +// for (Mapping existingMapping : existingMappings) { +// if (existingMapping != null && existingMapping.equals(mapping)) { +// return; +// } +// } + if (existingMappings.contains(mapping)) continue; + + inputMap.getMappings().add(mapping); + installedDefaultMappings.add(mapping); } - String action = null; - if (match != null) { - action = match.getAction(); + } + + protected void addDefaultChildMap(InputMap parentInputMap, InputMap newChildInputMap) { + parentInputMap.getChildInputMaps().add(newChildInputMap); + + childInputMapDisposalHandlers.add(() -> parentInputMap.getChildInputMaps().remove(newChildInputMap)); + } + + protected InputMap createInputMap() { + // TODO re-enable when InputMap moves back to Node / Control +// return node.getInputMap() != null ? +// (InputMap)node.getInputMap() : +// new InputMap<>(node); + return new InputMap<>(node); + } + + protected void removeMapping(Object key) { + InputMap inputMap = getInputMap(); + inputMap.lookupMapping(key).ifPresent(mapping -> { + inputMap.getMappings().remove(mapping); + installedDefaultMappings.remove(mapping); + }); + } + + void rtl(Node node, Runnable rtlMethod, Runnable nonRtlMethod) { + switch(node.getEffectiveNodeOrientation()) { + case RIGHT_TO_LEFT: rtlMethod.run(); break; + default: nonRtlMethod.run(); break; } - return action; } - /** - * Called to invoke the action associated with the given name. - * - *

When a KeyEvent is handled, it is first passed through - * callActionForEvent which resolves which "action" should be executed - * based on the key event. This action is indicated by name. This name is - * then passed to this function which is responsible for invoking the right - * function based on the name.

- */ - protected void callAction(String name) { - switch (name) { - case TRAVERSE_UP: traverseUp(); break; - case TRAVERSE_DOWN: traverseDown(); break; - case TRAVERSE_LEFT: traverseLeft(); break; - case TRAVERSE_RIGHT: traverseRight(); break; - case TRAVERSE_NEXT: traverseNext(); break; - case TRAVERSE_PREVIOUS: traversePrevious(); break; + void rtl(Node node, T object, Consumer rtlMethod, Consumer nonRtlMethod) { + switch(node.getEffectiveNodeOrientation()) { + case RIGHT_TO_LEFT: rtlMethod.accept(object); break; + default: nonRtlMethod.accept(object); break; } } - /*************************************************************************** - * Focus Traversal methods * - **************************************************************************/ - - /** - * Called by any of the BehaviorBase traverse methods to actually effect a - * traversal of the focus. The default behavior of this method is to simply - * call impl_traverse on the given node, passing the given direction. A - * subclass may override this method. - * - * @param node The node to call impl_traverse on - * @param dir The direction to traverse - */ - protected void traverse(final Node node, final Direction dir) { - node.impl_traverse(dir); - } - - /** - * Calls the focus traversal engine and indicates that traversal should - * go the next focusTraversable Node above the current one. - */ - public final void traverseUp() { - traverse(control, com.sun.javafx.scene.traversal.Direction.UP); - } - - /** - * Calls the focus traversal engine and indicates that traversal should - * go the next focusTraversable Node below the current one. - */ - public final void traverseDown() { - traverse(control, com.sun.javafx.scene.traversal.Direction.DOWN); - } - - /** - * Calls the focus traversal engine and indicates that traversal should - * go the next focusTraversable Node left of the current one. - */ - public final void traverseLeft() { - traverse(control, com.sun.javafx.scene.traversal.Direction.LEFT); - } - - /** - * Calls the focus traversal engine and indicates that traversal should - * go the next focusTraversable Node right of the current one. - */ - public final void traverseRight() { - traverse(control, com.sun.javafx.scene.traversal.Direction.RIGHT); - } - - /** - * Calls the focus traversal engine and indicates that traversal should - * go the next focusTraversable Node in the focus traversal cycle. - */ - public final void traverseNext() { - traverse(control, com.sun.javafx.scene.traversal.Direction.NEXT); - } - - /** - * Calls the focus traversal engine and indicates that traversal should - * go the previous focusTraversable Node in the focus traversal cycle. - */ - public final void traversePrevious() { - traverse(control, com.sun.javafx.scene.traversal.Direction.PREVIOUS); - } - - /*************************************************************************** - * Event handler methods. * - * * - * I'm not sure why only mouse events are here. What about drag and * - * drop events for instance? What about touch events? What about the * - * other mouse events? It does seem like these need to be here, because * - * for example mouse interaction logic might differ from platform to * - * platform, and the Behavior is supposed to implement all the user * - * interaction logic (not just key handling). So it seems like * - * BehaviorBase should have methods for handling all forms of input events,* - * and not just these four mouse events. * - **************************************************************************/ - - /** - * Called whenever the focus on the control has changed. This method is - * intended to be overridden by subclasses that are interested in focus - * change events. - */ - protected void focusChanged() { } - - /** - * Invoked by a Skin when the body of the control has been pressed by - * the mouse. Subclasses should be sure to call super unless they intend - * to disable any built-in support. - * - * @param e the mouse event - */ - public void mousePressed(MouseEvent e) { } - - /** - * Invoked by a Skin when the body of the control has been dragged by - * the mouse. Subclasses should be sure to call super unless they intend - * to disable any built-in support (for example, for tooltips). - * - * @param e the mouse event - */ - public void mouseDragged(MouseEvent e) { } - - /** - * Invoked by a Skin when the body of the control has been released by - * the mouse. Subclasses should be sure to call super unless they intend - * to disable any built-in support (for example, for tooltips). - * - * @param e the mouse event - */ - public void mouseReleased(MouseEvent e) { } - - /** - * Invoked by a Skin when the body of the control has been entered by - * the mouse. Subclasses should be sure to call super unless they intend - * to disable any built-in support. - * - * @param e the mouse event - */ - public void mouseEntered(MouseEvent e) { } - - /** - * Invoked by a Skin when the body of the control has been exited by - * the mouse. Subclasses should be sure to call super unless they intend - * to disable any built-in support. - * - * @param e the mouse event - */ - public void mouseExited(MouseEvent e) { } - - /** - * Invoked by a Skin when the control has had its context menu requested, - * most commonly by right-clicking on the control. Subclasses should be sure - * to call super unless they intend to disable any built-in support. - * - * @param e the context menu event - */ - public void contextMenuRequested(ContextMenuEvent e) { } + boolean isRTL(Node n) { + switch(n.getEffectiveNodeOrientation()) { + case RIGHT_TO_LEFT: return true; + default: return false; + } + } }