modules/controls/src/main/java/com/sun/javafx/scene/control/behavior/BehaviorBase.java
Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization
*** 1,7 ****
/*
! * 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
--- 1,7 ----
/*
! * 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
*** 20,408 ****
*
* 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.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 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.
! *
! * <p>Although BehaviorBase is typically used as a base class, it is not abstract and
! * several skins instantiate an instance of BehaviorBase directly.</p>
! *
! * <p>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:</p>
! * <ul>
! * <li>TraverseUp</li>
! * <li>TraverseDown</li>
! * <li>TraverseLeft</li>
! * <li>TraverseRight</li>
! * <li>TraverseNext</li>
! * <li>TraversePrevious</li>
! * </ul>
! *
! * <p>Note that by convention, action names are camel case with the first letter
! * uppercase, matching class naming conventions.</p>
! */
! public class BehaviorBase<C extends Control> {
! /**
! * 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:
! * <ul>
! * <li>TraverseUp</li>
! * <li>TraverseDown</li>
! * <li>TraverseLeft</li>
! * <li>TraverseRight</li>
! * <li>TraverseNext</li>
! * <li>TraversePrevious</li>
! * </ul>
! */
! protected static final List<KeyBinding> 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<KeyBinding> keyBindings;
- /**
- * Listens to any key events on the Control and responds to them
- */
- private final EventHandler<KeyEvent> keyEventListener = e -> {
- if (!e.isConsumed()) {
- callActionForEvent(e);
- }
- };
! /**
! * 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<KeyBinding> 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);
}
! /***************************************************************************
! * 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();
! }
}
! /**
! * 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;
! }
! }
! String action = null;
! if (match != null) {
! action = match.getAction();
! }
! return action;
! }
! /**
! * Called to invoke the action associated with the given name.
! *
! * <p>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.</p>
! */
! 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;
! }
}
! /***************************************************************************
! * 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) { }
}
--- 20,146 ----
*
* 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.control.behavior;
import javafx.scene.Node;
! import com.sun.javafx.scene.control.inputmap.InputMap;
! import com.sun.javafx.scene.control.inputmap.InputMap.Mapping;
!
import java.util.ArrayList;
import java.util.List;
! import java.util.function.Consumer;
! public abstract class BehaviorBase<N extends Node> {
! private final N node;
! private final List<Mapping<?>> installedDefaultMappings;
! private final List<Runnable> childInputMapDisposalHandlers;
! public BehaviorBase(N node) {
! this.node = node;
! this.installedDefaultMappings = new ArrayList<>();
! this.childInputMapDisposalHandlers = new ArrayList<>();
}
! public abstract InputMap<N> getInputMap();
! public final N getNode() {
! return node;
}
! 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<N> inputMap = getInputMap();
! // if (inputMap != null) {
! // inputMap.dispose();
! // }
}
! protected void addDefaultMapping(List<Mapping<?>> newMapping) {
! addDefaultMapping(getInputMap(), newMapping.toArray(new Mapping[newMapping.size()]));
}
! protected void addDefaultMapping(Mapping<?>... newMapping) {
! addDefaultMapping(getInputMap(), newMapping);
}
! protected void addDefaultMapping(InputMap<N> inputMap, Mapping<?>... newMapping) {
! // make a copy of the existing mappings, so we only check against those
! List<Mapping<?>> 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);
! }
}
! protected <T extends Node> void addDefaultChildMap(InputMap<T> parentInputMap, InputMap<T> newChildInputMap) {
! parentInputMap.getChildInputMaps().add(newChildInputMap);
! childInputMapDisposalHandlers.add(() -> parentInputMap.getChildInputMaps().remove(newChildInputMap));
! }
! protected InputMap<N> createInputMap() {
! // TODO re-enable when InputMap moves back to Node / Control
! // return node.getInputMap() != null ?
! // (InputMap<N>)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;
! }
! }
! <T> void rtl(Node node, T object, Consumer<T> rtlMethod, Consumer<T> nonRtlMethod) {
! switch(node.getEffectiveNodeOrientation()) {
! case RIGHT_TO_LEFT: rtlMethod.accept(object); break;
! default: nonRtlMethod.accept(object); break;
! }
! }
! boolean isRTL(Node n) {
! switch(n.getEffectiveNodeOrientation()) {
! case RIGHT_TO_LEFT: return true;
! default: return false;
! }
! }
}