Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configurable keybinds #479

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import de.sciss.syntaxpane.actions.DocumentSearchData;
import de.sciss.syntaxpane.actions.gui.QuickFindDialog;

import cuchaz.enigma.gui.config.keybind.KeyBinds;

public class EnigmaQuickFindDialog extends QuickFindDialog {
public EnigmaQuickFindDialog(JTextComponent target) {
super(target, DocumentSearchData.getFromEditor(target));
Expand All @@ -29,11 +31,12 @@ public EnigmaQuickFindDialog(JTextComponent target) {
public void keyPressed(KeyEvent e) {
super.keyPressed(e);

if (e.getKeyCode() == KeyEvent.VK_ENTER) {
if (KeyBinds.QUICK_FIND_DIALOG_PREVIOUS.matches(e)) {
JToolBar toolBar = getToolBar();
getPrevButton(toolBar).doClick();
} else if (KeyBinds.QUICK_FIND_DIALOG_NEXT.matches(e)) {
JToolBar toolBar = getToolBar();
boolean next = !e.isShiftDown();
JButton button = next ? getNextButton(toolBar) : getPrevButton(toolBar);
button.doClick();
getNextButton(toolBar).doClick();
}
}
});
Expand Down
5 changes: 5 additions & 0 deletions enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
Original file line number Diff line number Diff line change
Expand Up @@ -666,4 +666,9 @@ public boolean validateImmediateAction(Consumer<ValidationContext> op) {
public boolean isEditable(EditableType t) {
return this.editableTypes.contains(t);
}

public void reloadKeyBinds() {
this.menuBar.setKeyBinds();
this.editorTabbedPane.reloadKeyBinds();
}
}
3 changes: 3 additions & 0 deletions enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import cuchaz.enigma.EnigmaProfile;
import cuchaz.enigma.gui.config.Themes;
import cuchaz.enigma.gui.config.UiConfig;
import cuchaz.enigma.gui.config.keybind.KeyBinds;
import cuchaz.enigma.gui.dialog.CrashDialog;
import cuchaz.enigma.translation.mapping.serde.MappingFormat;
import cuchaz.enigma.utils.I18n;
Expand Down Expand Up @@ -105,6 +106,8 @@ public static void main(String[] args) throws IOException {
System.setProperty("apple.laf.useScreenMenuBar", "true");
Themes.setupTheme();

KeyBinds.loadConfig();

Gui gui = new Gui(parsedProfile, editables);
GuiController controller = gui.getController();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cuchaz.enigma.gui.config;

import cuchaz.enigma.config.ConfigContainer;
import cuchaz.enigma.config.ConfigSection;
import cuchaz.enigma.gui.config.keybind.KeyBind;

public final class KeyBindsConfig {
private KeyBindsConfig() {
}

private static final ConfigContainer cfg = ConfigContainer.getOrCreate("enigma/enigmakeybinds");

public static void save() {
cfg.save();
}

private static ConfigSection getSection(KeyBind keyBind) {
return keyBind.category().isEmpty() ? cfg.data() : cfg.data().section(keyBind.category());
}

public static String[] getKeyBindCodes(KeyBind keyBind) {
return getSection(keyBind).getArray(keyBind.name()).orElse(keyBind.serializeCombinations());
}

public static void setKeyBind(KeyBind keyBind) {
getSection(keyBind).setArray(keyBind.name(), keyBind.serializeCombinations());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package cuchaz.enigma.gui.config.keybind;

import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import javax.swing.KeyStroke;

import cuchaz.enigma.utils.I18n;

public record KeyBind(String name, String category, List<Combination> combinations) {
public record Combination(int keyCode, int keyModifiers) {
public static final Combination EMPTY = new Combination(-1, 0);
public boolean matches(KeyEvent e) {
return e.getKeyCode() == keyCode && e.getModifiersEx() == keyModifiers;
}

public KeyStroke toKeyStroke(int modifiers) {
modifiers = keyModifiers | modifiers;
return KeyStroke.getKeyStroke(keyCode, modifiers);
}

public String serialize() {
return keyCode + ";" + Integer.toString(keyModifiers, 16);
}

public static Combination deserialize(String str) {
String[] parts = str.split(";", 2);
return new Combination(Integer.parseInt(parts[0]), Integer.parseInt(parts[1], 16));
}

@Override
public String toString() {
return "Combination[keyCode=" + keyCode + ", keyModifiers=0x" + Integer.toString(keyModifiers, 16).toUpperCase(Locale.ROOT) + "]";
}
}

public void setFrom(KeyBind other) {
this.combinations.clear();
this.combinations.addAll(other.combinations);
}

public boolean matches(KeyEvent e) {
return combinations.stream().anyMatch(c -> c.matches(e));
}

public KeyStroke toKeyStroke(int modifiers) {
return isEmpty() ? null : combinations.get(0).toKeyStroke(modifiers);
}

public KeyStroke toKeyStroke() {
return toKeyStroke(0);
}

public boolean isEmpty() {
return combinations.isEmpty();
}

public String[] serializeCombinations() {
return combinations.stream().map(Combination::serialize).toArray(String[]::new);
}

public void deserializeCombinations(String[] serialized) {
combinations.clear();

for (String serializedCombination : serialized) {
if (!serializedCombination.isEmpty()) {
combinations.add(Combination.deserialize(serializedCombination));
} else {
System.out.println("warning: empty combination deserialized for keybind " + (category.isEmpty() ? "" : category + ".") + name);
}
}
}

private String getTranslationKey() {
return "keybind." + (category.isEmpty() ? "" : category + ".") + this.name;
}

public String getTranslatedName() {
return I18n.translate(getTranslationKey());
}

public KeyBind copy() {
return new KeyBind(name, category, new ArrayList<>(combinations));
}

public KeyBind toImmutable() {
return new KeyBind(name, category, List.copyOf(combinations));
}

public boolean isSameKeyBind(KeyBind other) {
return name.equals(other.name) && category.equals(other.category);
}

public static Builder builder(String name) {
return new Builder(name);
}

public static Builder builder(String name, String category) {
return new Builder(name, category);
}

public static class Builder {
private final String name;
private final String category;
private final List<Combination> combinations = new ArrayList<>();
private int modifiers = 0;

private Builder(String name) {
this.name = name;
this.category = "";
}

private Builder(String name, String category) {
this.name = name;
this.category = category;
}

public KeyBind build() {
return new KeyBind(name, category, combinations);
}

public Builder key(int keyCode, int keyModifiers) {
combinations.add(new Combination(keyCode, keyModifiers | modifiers));
return this;
}

public Builder key(int keyCode) {
return key(keyCode, 0);
}

public Builder keys(int... keyCodes) {
for (int keyCode : keyCodes) {
key(keyCode);
}

return this;
}

public Builder mod(int modifiers) {
this.modifiers |= modifiers;
return this;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package cuchaz.enigma.gui.config.keybind;

import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import cuchaz.enigma.gui.config.KeyBindsConfig;

public final class KeyBinds {
private static final String QUICK_FIND_DIALOG_CATEGORY = "quick_find_dialog";
private static final String SEARCH_DIALOG_CATEGORY = "search_dialog";
private static final String EDITOR_CATEGORY = "editor";
private static final String MENU_CATEGORY = "menu";

public static final KeyBind EXIT = KeyBind.builder("close").key(KeyEvent.VK_ESCAPE).build();
public static final KeyBind DIALOG_SAVE = KeyBind.builder("dialog_save").key(KeyEvent.VK_ENTER).build();

public static final KeyBind QUICK_FIND_DIALOG_NEXT = KeyBind.builder("next", QUICK_FIND_DIALOG_CATEGORY).key(KeyEvent.VK_ENTER).build();
public static final KeyBind QUICK_FIND_DIALOG_PREVIOUS = KeyBind.builder("previous", QUICK_FIND_DIALOG_CATEGORY).mod(KeyEvent.SHIFT_DOWN_MASK).key(KeyEvent.VK_ENTER).build();
public static final KeyBind SEARCH_DIALOG_NEXT = KeyBind.builder("next", SEARCH_DIALOG_CATEGORY).key(KeyEvent.VK_DOWN).build();
public static final KeyBind SEARCH_DIALOG_PREVIOUS = KeyBind.builder("previous", SEARCH_DIALOG_CATEGORY).key(KeyEvent.VK_UP).build();

public static final KeyBind EDITOR_RENAME = KeyBind.builder("rename", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_R).build();
public static final KeyBind EDITOR_PASTE = KeyBind.builder("paste", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_V).build();
public static final KeyBind EDITOR_EDIT_JAVADOC = KeyBind.builder("edit_javadoc", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_D).build();
public static final KeyBind EDITOR_SHOW_INHERITANCE = KeyBind.builder("show_inheritance", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_I).build();
public static final KeyBind EDITOR_SHOW_IMPLEMENTATIONS = KeyBind.builder("show_implementations", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_M).build();
public static final KeyBind EDITOR_SHOW_CALLS = KeyBind.builder("show_calls", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_C).build();
public static final KeyBind EDITOR_SHOW_CALLS_SPECIFIC = KeyBind.builder("show_calls_specific", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK).key(KeyEvent.VK_C).build();
public static final KeyBind EDITOR_OPEN_ENTRY = KeyBind.builder("open_entry", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_N).build();
public static final KeyBind EDITOR_OPEN_PREVIOUS = KeyBind.builder("open_previous", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_P).build();
public static final KeyBind EDITOR_OPEN_NEXT = KeyBind.builder("open_next", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_E).build();
public static final KeyBind EDITOR_TOGGLE_MAPPING = KeyBind.builder("toggle_mapping", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_O).build();
public static final KeyBind EDITOR_ZOOM_IN = KeyBind.builder("zoom_in", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).keys(KeyEvent.VK_PLUS, KeyEvent.VK_ADD, KeyEvent.VK_EQUALS).build();
public static final KeyBind EDITOR_ZOOM_OUT = KeyBind.builder("zoom_out", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).keys(KeyEvent.VK_MINUS, KeyEvent.VK_SUBTRACT).build();
public static final KeyBind EDITOR_CLOSE_TAB = KeyBind.builder("close_tab", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_4).build();
public static final KeyBind EDITOR_RELOAD_CLASS = KeyBind.builder("reload_class", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_F5).build();
public static final KeyBind EDITOR_QUICK_FIND = KeyBind.builder("quick_find", EDITOR_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_F).build();

public static final KeyBind SAVE_MAPPINGS = KeyBind.builder("save", MENU_CATEGORY).mod(KeyEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_S).build();
public static final KeyBind DROP_MAPPINGS = KeyBind.builder("drop_mappings", MENU_CATEGORY).build();
public static final KeyBind RELOAD_MAPPINGS = KeyBind.builder("reload_mappings", MENU_CATEGORY).build();
public static final KeyBind RELOAD_ALL = KeyBind.builder("reload_all", MENU_CATEGORY).build();
public static final KeyBind MAPPING_STATS = KeyBind.builder("mapping_stats", MENU_CATEGORY).build();
public static final KeyBind SEARCH_CLASS = KeyBind.builder("search_class", MENU_CATEGORY).mod(KeyEvent.SHIFT_DOWN_MASK).key(KeyEvent.VK_SPACE).build();
public static final KeyBind SEARCH_METHOD = KeyBind.builder("search_method", MENU_CATEGORY).build();
public static final KeyBind SEARCH_FIELD = KeyBind.builder("search_field", MENU_CATEGORY).build();

private static final List<KeyBind> DEFAULT_KEY_BINDS = Stream.of(EXIT, DIALOG_SAVE, QUICK_FIND_DIALOG_NEXT,
QUICK_FIND_DIALOG_PREVIOUS, SEARCH_DIALOG_NEXT, SEARCH_DIALOG_PREVIOUS, EDITOR_RENAME, EDITOR_PASTE,
EDITOR_EDIT_JAVADOC, EDITOR_SHOW_INHERITANCE, EDITOR_SHOW_IMPLEMENTATIONS, EDITOR_SHOW_CALLS,
EDITOR_SHOW_CALLS_SPECIFIC, EDITOR_OPEN_ENTRY, EDITOR_OPEN_PREVIOUS, EDITOR_OPEN_NEXT,
EDITOR_TOGGLE_MAPPING, EDITOR_ZOOM_IN, EDITOR_ZOOM_OUT, EDITOR_CLOSE_TAB, EDITOR_RELOAD_CLASS,
EDITOR_QUICK_FIND, SAVE_MAPPINGS, DROP_MAPPINGS, RELOAD_MAPPINGS, RELOAD_ALL, MAPPING_STATS, SEARCH_CLASS,
SEARCH_METHOD, SEARCH_FIELD).map(KeyBind::toImmutable).toList();

private static final List<KeyBind> CONFIGURABLE_KEY_BINDS = List.of(EDITOR_RENAME, EDITOR_PASTE, EDITOR_EDIT_JAVADOC,
EDITOR_SHOW_INHERITANCE, EDITOR_SHOW_IMPLEMENTATIONS, EDITOR_SHOW_CALLS, EDITOR_SHOW_CALLS_SPECIFIC,
EDITOR_OPEN_ENTRY, EDITOR_OPEN_PREVIOUS, EDITOR_OPEN_NEXT, EDITOR_TOGGLE_MAPPING, EDITOR_ZOOM_IN,
EDITOR_ZOOM_OUT, EDITOR_CLOSE_TAB, EDITOR_RELOAD_CLASS, SAVE_MAPPINGS, DROP_MAPPINGS, RELOAD_MAPPINGS,
RELOAD_ALL, MAPPING_STATS, SEARCH_CLASS, SEARCH_METHOD, SEARCH_FIELD);
// Editing entries in CONFIGURABLE_KEY_BINDS directly wouldn't allow to revert the changes instead of saving
private static List<KeyBind> EDITABLE_KEY_BINDS;

private KeyBinds() {
}

public static boolean isConfigurable(KeyBind keyBind) {
return CONFIGURABLE_KEY_BINDS.stream().anyMatch(bind -> bind.isSameKeyBind(keyBind));
}

public static Map<String, List<KeyBind>> getEditableKeyBindsByCategory() {
return EDITABLE_KEY_BINDS.stream()
.collect(Collectors.groupingBy(KeyBind::category));
}

public static void loadConfig() {
for (KeyBind keyBind : CONFIGURABLE_KEY_BINDS) {
keyBind.deserializeCombinations(KeyBindsConfig.getKeyBindCodes(keyBind));
}

resetEditableKeyBinds();
}

public static void saveConfig() {
boolean modified = false;

for (int i = 0; i < CONFIGURABLE_KEY_BINDS.size(); i++) {
KeyBind keyBind = CONFIGURABLE_KEY_BINDS.get(i);
KeyBind editedKeyBind = EDITABLE_KEY_BINDS.get(i);

if (!editedKeyBind.equals(keyBind)) {
modified = true;
keyBind.setFrom(editedKeyBind);
KeyBindsConfig.setKeyBind(editedKeyBind);
}
}

if (modified) {
KeyBindsConfig.save();
}
}

// Reset the key binds to the saved values
public static void resetEditableKeyBinds() {
EDITABLE_KEY_BINDS = CONFIGURABLE_KEY_BINDS.stream().map(KeyBind::copy)
.collect(Collectors.toCollection(ArrayList::new));
}

public static void resetToDefault(KeyBind keyBind) {
// Ensure the key bind is editable
if (!EDITABLE_KEY_BINDS.contains(keyBind)) {
return;
}

KeyBind defaultKeyBind = DEFAULT_KEY_BINDS.stream().filter(bind -> bind.isSameKeyBind(keyBind)).findFirst().orElse(null);

if (defaultKeyBind == null) {
throw new IllegalStateException("Could not find default key bind for " + keyBind);
}

keyBind.setFrom(defaultKeyBind);
}

public static List<KeyBind> getEditableKeyBinds() {
return EDITABLE_KEY_BINDS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import javax.swing.JLabel;
import javax.swing.JPanel;

import cuchaz.enigma.gui.config.keybind.KeyBinds;
import cuchaz.enigma.utils.I18n;

public class ChangeDialog {
Expand All @@ -35,7 +36,7 @@ public static void show(Window parent) {
okButton.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
if (KeyBinds.EXIT.matches(e)) {
frame.dispose();
}
}
Expand Down
Loading