Skip to content

Commit

Permalink
Allow ALT and ALT + CONTROL (or ALTGR on Windows) accelerators. (#922)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaeqs authored Apr 12, 2020
1 parent fe947d0 commit 7552c74
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.fxmisc.richtext;

import javafx.event.EventTarget;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

//This class requires to be in this package, as it requires access to GenericStyledAreaBehavior.
public class AcceleratorsTests extends InlineCssTextAreaAppTest {

@Test
public void typing_alt_control_combinations_dont_consume_events_if_they_dont_have_any_character_assigned() {
AcceleratorsTestsHelper[] events;

if (System.getProperty("os.name").startsWith("Windows")) {
//WINDOWS TESTS
events = new AcceleratorsTestsHelper[]{
//CHARACTER WITHOUT MODIFIERS
new AcceleratorsTestsHelper(area, "f", KeyCode.F, false, false, true),
//CHARACTER WITH CONTROL
new AcceleratorsTestsHelper(area, "\0", KeyCode.F, true, false, false),
//CHARACTER WITH ALT
new AcceleratorsTestsHelper(area, "\0", KeyCode.F, false, true, false),
//CHARACTER WITH ALT + CONTROL / ALTGR on Windows
new AcceleratorsTestsHelper(area, "\0", KeyCode.K, true, true, false),
//ALT + CONTROL / ALTGR on Windows with an assigned special character (E -> Euro on spanish keyboard)
new AcceleratorsTestsHelper(area, "\u20AC", KeyCode.E, true, true, true)
};
} else {
//LINUX TESTS
events = new AcceleratorsTestsHelper[]{
//CHARACTER WITHOUT MODIFIERS
new AcceleratorsTestsHelper(area, "f", KeyCode.F, false, false, true),
//CHARACTER WITH CONTROL
new AcceleratorsTestsHelper(area, "\0", KeyCode.F, true, false, false),
//CHARACTER WITH ALT
new AcceleratorsTestsHelper(area, "\0", KeyCode.F, false, true, false),
//CHARACTER WITH ALT + CONTROL
new AcceleratorsTestsHelper(area, "\0", KeyCode.F, true, true, false),
};
}
AcceleratorsTestsHelper helper;
for (int i = 0; i < events.length; i++) {
helper = events[i];
assertEquals("Event " + i + " unexpected result.", helper.expectedConsumeResult,
GenericStyledAreaBehavior.isControlKeyEvent(helper.keyEvent));
}
}

/**
* Small helper class. Allows to make tests faster.
*/
private static class AcceleratorsTestsHelper {

KeyEvent keyEvent;
boolean expectedConsumeResult;

public AcceleratorsTestsHelper(EventTarget source, String character, KeyCode key, boolean controlDown, boolean altDown, boolean expected) {
keyEvent = new KeyEvent(source, source, KeyEvent.KEY_TYPED, character, key.getName(), key,
false, controlDown, altDown, false);
expectedConsumeResult = expected;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import static org.fxmisc.wellbehaved.event.template.InputMapTemplate.*;
import static org.reactfx.EventStreams.*;

import java.util.Arrays;
import java.util.function.Predicate;

import javafx.event.Event;
Expand Down Expand Up @@ -44,6 +45,7 @@ class GenericStyledAreaBehavior {
}

private static final InputMapTemplate<GenericStyledAreaBehavior, ? super Event> EVENT_TEMPLATE;
private static final Predicate<KeyEvent> controlKeysFilter;

static {
SelectionPolicy selPolicy = isMac
Expand All @@ -65,7 +67,7 @@ class GenericStyledAreaBehavior {
KeyCharacterCombination SHORTCUT_Y = new KeyCharacterCombination( "y", SHORTCUT_DOWN );
KeyCharacterCombination SHORTCUT_Z = new KeyCharacterCombination( "z", SHORTCUT_DOWN );
KeyCharacterCombination SHORTCUT_SHIFT_Z = new KeyCharacterCombination( "z", SHORTCUT_DOWN, SHIFT_DOWN );

InputMapTemplate<GenericStyledAreaBehavior, KeyEvent> editsBase = sequence(
// deletion
consume(keyPressed(DELETE), GenericStyledAreaBehavior::deleteForward),
Expand Down Expand Up @@ -167,18 +169,26 @@ class GenericStyledAreaBehavior {
), (b, e) -> b.view.copy()
);

Predicate<KeyEvent> noControlKeys = e ->
// filter out control keys
(!e.isControlDown() && !e.isMetaDown())
// except on Windows allow the Ctrl+Alt combination (produced by AltGr)
|| (isWindows && !e.isMetaDown() && (!e.isControlDown() || e.isAltDown()));
controlKeysFilter = e -> {
if (isWindows) {
//Windows input. ALT + CONTROL accelerators are the same as ALT GR accelerators.
//If ALT + CONTROL are pressed and the given character is valid then print the character.
//Else, don't consume the event. This change allows Windows users to use accelerators and
//printing special characters at the same time.
// (For example: ALT + CONTROL + E prints the euro symbol in the spanish keyboard while ALT + CONTROL + L has assigned an accelerator.)
//Note that this is how several IDEs such JetBrains IDEs or Eclipse behave.
if (e.isControlDown() && e.isAltDown() && !e.isMetaDown() && e.getCharacter().length() == 1
&& e.getCharacter().getBytes()[0] != 0) return true;
}
return !e.isControlDown() && !e.isAltDown() && !e.isMetaDown();
};

Predicate<KeyEvent> isChar = e ->
e.getCode().isLetterKey() ||
e.getCode().isDigitKey() ||
e.getCode().isWhitespaceKey();

InputMapTemplate<GenericStyledAreaBehavior, KeyEvent> charPressConsumer = consume(keyPressed().onlyIf(isChar.and(noControlKeys)));
InputMapTemplate<GenericStyledAreaBehavior, KeyEvent> charPressConsumer = consume(keyPressed().onlyIf(isChar.and(controlKeysFilter)));

InputMapTemplate<GenericStyledAreaBehavior, ? super KeyEvent> keyPressedTemplate = edits
.orElse(otherNavigation).ifConsumed((b, e) -> b.view.clearTargetCaretOffset())
Expand All @@ -191,7 +201,7 @@ class GenericStyledAreaBehavior {

InputMapTemplate<GenericStyledAreaBehavior, KeyEvent> keyTypedBase = consume(
// character input
EventPattern.keyTyped().onlyIf(noControlKeys.and(e -> isLegal(e.getCharacter()))),
EventPattern.keyTyped().onlyIf(controlKeysFilter.and(e -> isLegal(e.getCharacter()))),
GenericStyledAreaBehavior::keyTyped
).ifConsumed((b, e) -> b.view.requestFollowCaret());
InputMapTemplate<GenericStyledAreaBehavior, ? super KeyEvent> keyTypedTemplate = when(b -> b.view.isEditable(), keyTypedBase);
Expand Down Expand Up @@ -344,16 +354,6 @@ private void keyTyped(KeyEvent event) {
view.replaceSelection(text);
}

private static boolean isLegal(String text) {
int n = text.length();
for(int i = 0; i < n; ++i) {
if(Character.isISOControl(text.charAt(i))) {
return false;
}
}
return true;
}

private void deleteBackward(KeyEvent ignore) {
IndexRange selection = view.getSelection();
if(selection.getLength() == 0) {
Expand Down Expand Up @@ -585,4 +585,18 @@ private static Point2D project(Point2D p, Bounds bounds) {
private static double clamp(double x, double min, double max) {
return Math.min(Math.max(x, min), max);
}

static boolean isControlKeyEvent(KeyEvent event) {
return controlKeysFilter.test(event);
}

private static boolean isLegal(String text) {
int n = text.length();
for(int i = 0; i < n; ++i) {
if(Character.isISOControl(text.charAt(i))) {
return false;
}
}
return true;
}
}

0 comments on commit 7552c74

Please sign in to comment.