Skip to content

Commit

Permalink
Install an InputMap (stackable) and later reinstall previous one
Browse files Browse the repository at this point in the history
  • Loading branch information
JordanMartinez committed Apr 11, 2018
1 parent 8107986 commit 1716e85
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 1 deletion.
54 changes: 53 additions & 1 deletion src/main/java/org/fxmisc/wellbehaved/event/Nodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;

import javafx.collections.MapChangeListener;
import javafx.collections.ObservableMap;
Expand All @@ -30,12 +31,17 @@
* <li>
* To remove an {@code InputMap}, use {@link #removeInputMap(Node, InputMap)}.
* </li>
* <li>
* See also {@link #pushInputMap(Node, InputMap)} and {@link #popInputMap(Node)} for temporary behavior
* modification.
* </li>
* </ul>
*/
public class Nodes {

private static final String P_INPUTMAP = "org.fxmisc.wellbehaved.event.inputmap";
private static final String P_HANDLERS = "org.fxmisc.wellbehaved.event.handlers";
private static final String P_STACK = "org.fxmisc.wellbehaved.event.stack";

/**
* Adds the given input map to the start of the node's list of input maps, so that an event will be pattern-matched
Expand Down Expand Up @@ -65,11 +71,46 @@ public static void removeInputMap(Node node, InputMap<?> im) {
setInputMapUnsafe(node, getInputMap(node).without(im));
}

static InputMap<?> getInputMap(Node node) {
/**
* Gets the {@link InputMap} for the given node or {@link InputMap#empty()} if there is none.
*/
public static InputMap<?> getInputMap(Node node) {
init(node);
return getInputMapUnsafe(node);
}

/**
* Removes the currently installed {@link InputMap} (InputMap1) on the given node and installs the {@code im}
* (InputMap2) in its place. When finished, InputMap2 can be uninstalled and InputMap1 reinstalled via
* {@link #popInputMap(Node)}. Multiple InputMaps can be installed so that InputMap(n) will be installed over
* InputMap(n-1)
*/
public static void pushInputMap(Node node, InputMap<?> im) {
// store currently installed im; getInputMap calls init
InputMap<?> previousInputMap = getInputMap(node);
getStack(node).push(previousInputMap);

// completely override the previous one with the given one
setInputMapUnsafe(node, im);
}

/**
* If the internal stack has an {@link InputMap}, removes the current {@link InputMap} that was installed
* on the give node via {@link #pushInputMap(Node, InputMap)}, reinstalls the previous {@code InputMap},
* and then returns true. If the stack is empty, returns false.
*/
public static boolean popInputMap(Node node) {
Stack<InputMap<?>> stackedInputMaps = getStack(node);
if (!stackedInputMaps.isEmpty()) {
// If stack is not empty, node has already been initialized, so can use unsafe methods.
// Now, completely override current input map with previous one on stack
setInputMapUnsafe(node, stackedInputMaps.pop());
return true;
} else {
return false;
}
}

/**
*
* @param node
Expand Down Expand Up @@ -120,6 +161,17 @@ private static List<Map.Entry<EventType<?>, EventHandler<?>>> getHandlers(Node n
return (List<Entry<EventType<?>, EventHandler<?>>>) getProperties(node).get(P_HANDLERS);
}

private static Stack<InputMap<?>> getStack(Node node) {
ObservableMap<Object, Object> nodeProperties = getProperties(node);
if (nodeProperties.get(P_STACK) == null) {
Stack<InputMap<?>> stackedInputMaps = new Stack<>();
nodeProperties.put(P_STACK, stackedInputMaps);
return stackedInputMaps;
}

return (Stack<InputMap<?>>) nodeProperties.get(P_STACK);
}

private static ObservableMap<Object, Object> getProperties(Node node) {
return node.getProperties();
}
Expand Down
47 changes: 47 additions & 0 deletions src/test/java/org/fxmisc/wellbehaved/event/InputMapTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.function.Supplier;

public class InputMapTest {

@BeforeClass
Expand Down Expand Up @@ -411,4 +413,49 @@ public void ifProceededTest() {
assertEquals(4, counter.get());
assertFalse(right.isConsumed());
}

@Test
public void pushAndPopInputMap() {
StringProperty res = new SimpleStringProperty();

Region node = new Region();
Nodes.addInputMap(node, InputMap.consume(keyPressed(UP), e -> res.set("Up")));
Supplier<KeyEvent> createUpKeyEvent = () ->
new KeyEvent(KEY_PRESSED, "", "", UP, false, false, false, false);

// regular input map works
KeyEvent up = createUpKeyEvent.get();

dispatch(up, node);
assertEquals("Up", res.get());
assertTrue(up.isConsumed());

// temporary input map works
Nodes.pushInputMap(node, InputMap.consume(keyPressed(UP), e -> res.set("Down")));
up = createUpKeyEvent.get();

dispatch(up, node);
assertEquals("Down", res.get());
assertTrue(up.isConsumed());

// popping reinstalls previous input map
Nodes.popInputMap(node);
up = createUpKeyEvent.get();

dispatch(up, node);
assertEquals("Up", res.get());
assertTrue(up.isConsumed());

// popping when no temporary input maps exist does nothing
Nodes.popInputMap(node);
up = createUpKeyEvent.get();

// set value to something else to insure test works as expected
res.set("Other value");

dispatch(up, node);
assertEquals("Up", res.get());
assertTrue(up.isConsumed());
}

}

0 comments on commit 1716e85

Please sign in to comment.