From 1d68c6e161b27c982ebcc2e4a346eb8b383ac071 Mon Sep 17 00:00:00 2001 From: David Stevens Date: Thu, 27 Feb 2020 18:25:52 +0100 Subject: [PATCH 01/16] [feat] Implement Emacs key bindings Emacs style key bindings are re-added to JabRef through the preferences menu. The supported key bindings have feature parity with the previous implementation in JabRef v<4, and additionally support any class that extends TextInputControl. In practice, this means that the new implementation supports both TextFields and TextAreas by default. Some functionality may still be missing Co-authored-by: Felix Luthman <34520175+felixlut@users.noreply.github.com> Co-authored-by: Tommy Samuelsson Co-authored-by: muachilin <32566798+muachilin@users.noreply.github.com> Co-authored-by: Kristoffer Gunnarsson --- src/main/java/org/jabref/JabRefGUI.java | 8 + .../jabref/gui/keyboard/EmacsKeyBindings.java | 103 ++++++++++ .../org/jabref/gui/keyboard/KeyBinding.java | 15 ++ .../gui/preferences/EntryEditorTab.fxml | 22 ++- .../gui/preferences/EntryEditorTabView.java | 10 + .../preferences/EntryEditorTabViewModel.java | 26 +++ .../util/strings/EmacsStringManipulator.java | 178 ++++++++++++++++++ .../model/util/ResultingEmacsState.java | 12 ++ .../jabref/preferences/JabRefPreferences.java | 4 + src/main/resources/l10n/JabRef_en.properties | 26 +++ .../strings/EmacsStringManipulatorTest.java | 125 ++++++++++++ 11 files changed, 528 insertions(+), 1 deletion(-) create mode 100755 src/main/java/org/jabref/gui/keyboard/EmacsKeyBindings.java create mode 100644 src/main/java/org/jabref/logic/util/strings/EmacsStringManipulator.java create mode 100644 src/main/java/org/jabref/model/util/ResultingEmacsState.java create mode 100644 src/test/java/org/jabref/logic/util/strings/EmacsStringManipulatorTest.java diff --git a/src/main/java/org/jabref/JabRefGUI.java b/src/main/java/org/jabref/JabRefGUI.java index 3d9b20c3ba3..ef193ead33e 100644 --- a/src/main/java/org/jabref/JabRefGUI.java +++ b/src/main/java/org/jabref/JabRefGUI.java @@ -8,6 +8,7 @@ import javafx.application.Platform; import javafx.scene.Scene; +import javafx.scene.input.KeyEvent; import javafx.stage.Screen; import javafx.stage.Stage; @@ -19,6 +20,7 @@ import org.jabref.gui.icon.IconTheme; import org.jabref.gui.importer.ParserResultWarningDialog; import org.jabref.gui.importer.actions.OpenDatabaseAction; +import org.jabref.gui.keyboard.EmacsKeyBindings; import org.jabref.gui.shared.SharedDatabaseUIManager; import org.jabref.logic.autosaveandbackup.BackupManager; import org.jabref.logic.importer.OpenDatabase; @@ -87,6 +89,12 @@ private void openWindow(Stage mainStage) { root.getChildren().add(JabRefGUI.mainFrame); Scene scene = new Scene(root, 800, 800); + + //Handle Emacs key bindings + scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> { + EmacsKeyBindings.executeEmacs(scene, event); + }); + Globals.getThemeLoader().installCss(scene, Globals.prefs); mainStage.setTitle(JabRefFrame.FRAME_TITLE); mainStage.getIcons().addAll(IconTheme.getLogoSetFX()); diff --git a/src/main/java/org/jabref/gui/keyboard/EmacsKeyBindings.java b/src/main/java/org/jabref/gui/keyboard/EmacsKeyBindings.java new file mode 100755 index 00000000000..26f569cb7c9 --- /dev/null +++ b/src/main/java/org/jabref/gui/keyboard/EmacsKeyBindings.java @@ -0,0 +1,103 @@ +package org.jabref.gui.keyboard; + +import java.util.Optional; + +import javafx.scene.Scene; +import javafx.scene.control.TextInputControl; +import javafx.scene.input.KeyEvent; + +import org.jabref.Globals; +import org.jabref.logic.util.strings.EmacsStringManipulator; +import org.jabref.model.util.ResultingEmacsState; +import org.jabref.preferences.JabRefPreferences; + +public class EmacsKeyBindings { + + public static void executeEmacs(Scene scene, KeyEvent event) { + boolean EmacsFlag = Globals.prefs.getBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS); + if (EmacsFlag && scene.focusOwnerProperty().get() instanceof TextInputControl) { + boolean CAFlag = Globals.prefs.getBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS_REBIND_CA); + boolean CFFlag = Globals.prefs.getBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS_REBIND_CF); + boolean CNFlag = Globals.prefs.getBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS_REBIND_CN); + boolean AUFlag = Globals.prefs.getBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS_REBIND_AU); + + KeyBindingRepository keyBindingRepository = Globals.getKeyPrefs(); + TextInputControl focusedTextField = (TextInputControl) scene.focusOwnerProperty().get(); + Optional keyBinding = keyBindingRepository.mapToKeyBinding(event); + if (keyBinding.isPresent()) { + if (keyBinding.get().equals(KeyBinding.EMACS_DELETE)) { + focusedTextField.deletePreviousChar(); + event.consume(); + } else if (keyBinding.get().equals(KeyBinding.EMACS_BACKWARD)) { + focusedTextField.backward(); + event.consume(); + } else if (CFFlag && keyBinding.get().equals(KeyBinding.EMACS_FORWARD)) { + focusedTextField.forward(); + event.consume(); + } else if (CAFlag && keyBinding.get().equals(KeyBinding.EMACS_BEGINNING)) { + focusedTextField.home(); + event.consume(); + } else if (keyBinding.get().equals(KeyBinding.EMACS_END)) { + focusedTextField.end(); + event.consume(); + } else if (keyBinding.get().equals(KeyBinding.EMACS_BEGINNING_DOC)) { + focusedTextField.home(); + event.consume(); + } else if (keyBinding.get().equals(KeyBinding.EMACS_END_DOC)) { + focusedTextField.end(); + event.consume(); + } else if (keyBinding.get().equals(KeyBinding.EMACS_UP)) { + focusedTextField.home(); + event.consume(); + } else if (CNFlag && keyBinding.get().equals(KeyBinding.EMACS_DOWN)) { + focusedTextField.end(); + event.consume(); + } else if (keyBinding.get().equals(KeyBinding.EMACS_CAPITALIZE)) { + int pos = focusedTextField.getCaretPosition(); + String text = focusedTextField.getText(0, focusedTextField.getText().length()); + ResultingEmacsState res = EmacsStringManipulator.capitalize(pos, text); + focusedTextField.setText(res.text); + focusedTextField.positionCaret(res.caretPos); + event.consume(); + } + else if (keyBinding.get().equals(KeyBinding.EMACS_LOWERCASE)) { + int pos = focusedTextField.getCaretPosition(); + String text = focusedTextField.getText(0, focusedTextField.getText().length()); + ResultingEmacsState res = EmacsStringManipulator.lowercase(pos, text); + focusedTextField.setText(res.text); + focusedTextField.positionCaret(res.caretPos); + event.consume(); + } + else if (AUFlag && keyBinding.get().equals(KeyBinding.EMACS_UPPERCASE)) { + int pos = focusedTextField.getCaretPosition(); + String text = focusedTextField.getText(0, focusedTextField.getText().length()); + ResultingEmacsState res = EmacsStringManipulator.uppercase(pos, text); + focusedTextField.setText(res.text); + focusedTextField.positionCaret(res.caretPos); + event.consume(); + } + else if (keyBinding.get().equals(KeyBinding.EMACS_KILLLINE)) { + int pos = focusedTextField.getCaretPosition(); + focusedTextField.setText(focusedTextField.getText(0, pos)); + focusedTextField.positionCaret(pos); + event.consume(); + } else if (keyBinding.get().equals(KeyBinding.EMACS_KILLWORD)) { + int pos = focusedTextField.getCaretPosition(); + String text = focusedTextField.getText(0, focusedTextField.getText().length()); + ResultingEmacsState res = EmacsStringManipulator.killWord(pos, text); + focusedTextField.setText(res.text); + focusedTextField.positionCaret(res.caretPos); + event.consume(); + } + else if (keyBinding.get().equals(KeyBinding.EMACS_BACKWARDKILLWORD)) { + int pos = focusedTextField.getCaretPosition(); + String text = focusedTextField.getText(0, focusedTextField.getText().length()); + ResultingEmacsState res = EmacsStringManipulator.backwardKillWord(pos, text); + focusedTextField.setText(res.text); + focusedTextField.positionCaret(res.caretPos); + event.consume(); + } + } + } + } +} diff --git a/src/main/java/org/jabref/gui/keyboard/KeyBinding.java b/src/main/java/org/jabref/gui/keyboard/KeyBinding.java index 7c4f66a193d..7985d2296e5 100644 --- a/src/main/java/org/jabref/gui/keyboard/KeyBinding.java +++ b/src/main/java/org/jabref/gui/keyboard/KeyBinding.java @@ -3,6 +3,21 @@ import org.jabref.logic.l10n.Localization; public enum KeyBinding { + EMACS_DELETE("Emacs delete", Localization.lang("Delete text"), "ctrl+D", KeyBindingCategory.EDIT), + EMACS_BACKWARD("Emacs move caret left", Localization.lang("Move caret left"), "ctrl+B", KeyBindingCategory.EDIT), + EMACS_FORWARD("Emacs move caret right", Localization.lang("Move caret right"), "ctrl+F", KeyBindingCategory.EDIT), + EMACS_BEGINNING("Emacs move caret to beginning", Localization.lang("Move caret to beginning"), "ctrl+A", KeyBindingCategory.EDIT), + EMACS_END("Emacs move caret to end", Localization.lang("Move caret to end"), "ctrl+E", KeyBindingCategory.EDIT), + EMACS_BEGINNING_DOC("Emacs move caret to beginning of the document", Localization.lang("Move the caret to the beginning of the document"), "alt+LESS", KeyBindingCategory.EDIT), + EMACS_END_DOC("Emacs move caret to end of the document", Localization.lang("Move the caret to the end of the document"), "alt+shift+LESS", KeyBindingCategory.EDIT), + EMACS_UP("Emacs move caret up", Localization.lang("Move the caret up"), "ctrl+P", KeyBindingCategory.EDIT), + EMACS_DOWN("Emacs move caret down", Localization.lang("Move the caret down"), "ctrl+N", KeyBindingCategory.EDIT), + EMACS_CAPITALIZE("Emacs capitalize next word", Localization.lang("Capitalize the next word"), "alt+C", KeyBindingCategory.EDIT), + EMACS_LOWERCASE("Emacs lowercase next word", Localization.lang("Make all characters in the next word lowercase"), "alt+L", KeyBindingCategory.EDIT), + EMACS_UPPERCASE("Emacs uppercase next word", Localization.lang("Make all characters in the next word uppercase"), "alt+U", KeyBindingCategory.EDIT), + EMACS_KILLLINE("Emacs remove line", Localization.lang("Remove words after the cursor"), "ctrl+K", KeyBindingCategory.EDIT), + EMACS_KILLWORD("Emacs remove the next word", Localization.lang("Remove the next word in the line"), "alt+D", KeyBindingCategory.EDIT), + EMACS_BACKWARDKILLWORD("Emacs remove the previous word", Localization.lang("Remove the previous word in the line"), "alt+DELETE", KeyBindingCategory.EDIT), ABBREVIATE("Abbreviate", Localization.lang("Abbreviate journal names"), "ctrl+alt+A", KeyBindingCategory.TOOLS), AUTOGENERATE_BIBTEX_KEYS("Autogenerate BibTeX keys", Localization.lang("Autogenerate BibTeX keys"), "ctrl+G", KeyBindingCategory.QUALITY), diff --git a/src/main/java/org/jabref/gui/preferences/EntryEditorTab.fxml b/src/main/java/org/jabref/gui/preferences/EntryEditorTab.fxml index 4458fb6ee94..8741483ea08 100644 --- a/src/main/java/org/jabref/gui/preferences/EntryEditorTab.fxml +++ b/src/main/java/org/jabref/gui/preferences/EntryEditorTab.fxml @@ -27,7 +27,27 @@ - + + + + + + + + + + + + + + + + + + + + +