Skip to content

Commit

Permalink
[feat] Implement Emacs key bindings
Browse files Browse the repository at this point in the history
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 <[email protected]>
Co-authored-by: Tommy Samuelsson <[email protected]>
Co-authored-by: muachilin <[email protected]>
Co-authored-by: Kristoffer Gunnarsson <[email protected]>
  • Loading branch information
5 people committed Feb 27, 2020
1 parent cc94cca commit 1d68c6e
Show file tree
Hide file tree
Showing 11 changed files with 528 additions and 1 deletion.
8 changes: 8 additions & 0 deletions src/main/java/org/jabref/JabRefGUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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());
Expand Down
103 changes: 103 additions & 0 deletions src/main/java/org/jabref/gui/keyboard/EmacsKeyBindings.java
Original file line number Diff line number Diff line change
@@ -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> 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();
}
}
}
}
}
15 changes: 15 additions & 0 deletions src/main/java/org/jabref/gui/keyboard/KeyBinding.java
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
22 changes: 21 additions & 1 deletion src/main/java/org/jabref/gui/preferences/EntryEditorTab.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,27 @@
</CheckBox>
<CheckBox fx:id="enableLatexCitationsTab" text="%Show 'LaTeX Citations' tab"/>
<CheckBox fx:id="enableValidation" text="%Show validation messages"/>

<CheckBox fx:id="enableEmacsKeyBindings" text="%Use Emacs key bindings"/>
<CheckBox fx:id="enableEmacsRebindCA" text="%Rebind C-a, too" disable="${!enableEmacsKeyBindings.selected}">
<padding>
<Insets left="20.0"/>
</padding>
</CheckBox>
<CheckBox fx:id="enableEmacsRebindCF" text="%Rebind C-f, too" disable="${!enableEmacsKeyBindings.selected}">
<padding>
<Insets left="20.0"/>
</padding>
</CheckBox>
<CheckBox fx:id="enableEmacsRebindCN" text="%Rebind C-n, too" disable="${!enableEmacsKeyBindings.selected}">
<padding>
<Insets left="20.0"/>
</padding>
</CheckBox>
<CheckBox fx:id="enableEmacsRebindAU" text="%Rebind A-u, too" disable="${!enableEmacsKeyBindings.selected}">
<padding>
<Insets left="20.0"/>
</padding>
</CheckBox>
<Label styleClass="sectionHeader" text="%Autocompletion"/>
<CheckBox fx:id="enableAutoComplete" text="%Use autocompletion"/>
<VBox spacing="10.0">
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/jabref/gui/preferences/EntryEditorTabView.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ public class EntryEditorTabView extends AbstractPreferenceTabView<EntryEditorTab
@FXML private CheckBox enableLatexCitationsTab;
@FXML private CheckBox enableValidation;
@FXML private CheckBox enableAutoComplete;
@FXML private CheckBox enableEmacsKeyBindings;
@FXML private CheckBox enableEmacsRebindCA;
@FXML private CheckBox enableEmacsRebindCF;
@FXML private CheckBox enableEmacsRebindCN;
@FXML private CheckBox enableEmacsRebindAU;
@FXML private TextField autoCompleteFields;
@FXML private RadioButton autoCompleteFirstLast;
@FXML private RadioButton autoCompleteLastFirst;
Expand Down Expand Up @@ -48,6 +53,11 @@ public void initialize () {
enableLatexCitationsTab.selectedProperty().bindBidirectional(viewModel.enableLatexCitationsTabProperty());
enableValidation.selectedProperty().bindBidirectional(viewModel.enableValidationProperty());
enableAutoComplete.selectedProperty().bindBidirectional(viewModel.enableAutoCompleteProperty());
enableEmacsKeyBindings.selectedProperty().bindBidirectional(viewModel.enableEmacsKeyBindingsProperty());
enableEmacsRebindCA.selectedProperty().bindBidirectional(viewModel.enableEmacsRebindCAProperty());
enableEmacsRebindCF.selectedProperty().bindBidirectional(viewModel.enableEmacsRebindCFProperty());
enableEmacsRebindCN.selectedProperty().bindBidirectional(viewModel.enableEmacsRebindCNProperty());
enableEmacsRebindAU.selectedProperty().bindBidirectional(viewModel.enableEmacsRebindAUProperty());
autoCompleteFields.textProperty().bindBidirectional(viewModel.autoCompleteFieldsProperty());
autoCompleteFirstLast.selectedProperty().bindBidirectional(viewModel.autoCompleteFirstLastProperty());
autoCompleteLastFirst.selectedProperty().bindBidirectional(viewModel.autoCompleteLastFirstProperty());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ public class EntryEditorTabViewModel implements PreferenceTabViewModel {
private final BooleanProperty firstNameModeAbbreviatedProperty = new SimpleBooleanProperty();
private final BooleanProperty firstNameModeFullProperty = new SimpleBooleanProperty();
private final BooleanProperty firstNameModeBothProperty = new SimpleBooleanProperty();
private final BooleanProperty enableEmacsKeyBindingsProperty = new SimpleBooleanProperty();
private final BooleanProperty enableEmacsRebindCAProperty = new SimpleBooleanProperty();
private final BooleanProperty enableEmacsRebindCFProperty = new SimpleBooleanProperty();
private final BooleanProperty enableEmacsRebindCNProperty = new SimpleBooleanProperty();
private final BooleanProperty enableEmacsRebindAUProperty = new SimpleBooleanProperty();

private AutoCompletePreferences autoCompletePreferences;

Expand All @@ -52,6 +57,11 @@ public void setValues() {
acceptRecommendationsProperty.setValue(preferences.getBoolean(JabRefPreferences.ACCEPT_RECOMMENDATIONS));
enableLatexCitationsTabProperty.setValue(preferences.getBoolean(JabRefPreferences.SHOW_LATEX_CITATIONS));
enableValidationProperty.setValue(preferences.getBoolean(JabRefPreferences.VALIDATE_IN_ENTRY_EDITOR));
enableEmacsKeyBindingsProperty.setValue(preferences.getBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS));
enableEmacsRebindCAProperty.setValue(preferences.getBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS_REBIND_CA));
enableEmacsRebindCFProperty.setValue(preferences.getBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS_REBIND_CF));
enableEmacsRebindCNProperty.setValue(preferences.getBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS_REBIND_CN));
enableEmacsRebindAUProperty.setValue(preferences.getBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS_REBIND_AU));
enableAutoCompleteProperty.setValue(autoCompletePreferences.shouldAutoComplete());
autoCompleteFieldsProperty.setValue(autoCompletePreferences.getCompleteNamesAsString());

Expand Down Expand Up @@ -84,6 +94,11 @@ public void storeSettings() {
preferences.putBoolean(JabRefPreferences.ACCEPT_RECOMMENDATIONS, acceptRecommendationsProperty.getValue());
preferences.putBoolean(JabRefPreferences.SHOW_LATEX_CITATIONS, enableLatexCitationsTabProperty.getValue());
preferences.putBoolean(JabRefPreferences.VALIDATE_IN_ENTRY_EDITOR, enableValidationProperty.getValue());
preferences.putBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS, enableEmacsKeyBindingsProperty.getValue());
preferences.putBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS_REBIND_CA, enableEmacsRebindCAProperty.getValue());
preferences.putBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS_REBIND_CF, enableEmacsRebindCFProperty.getValue());
preferences.putBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS_REBIND_CN, enableEmacsRebindCNProperty.getValue());
preferences.putBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS_REBIND_AU, enableEmacsRebindAUProperty.getValue());

autoCompletePreferences.setShouldAutoComplete(enableAutoCompleteProperty.getValue());
autoCompletePreferences.setCompleteNames(autoCompleteFieldsProperty.getValue());
Expand Down Expand Up @@ -148,4 +163,15 @@ public List<String> getRestartWarnings() {
public BooleanProperty firstNameModeFullProperty() { return firstNameModeFullProperty; }

public BooleanProperty firstNameModeBothProperty() { return firstNameModeBothProperty; }

public BooleanProperty enableEmacsKeyBindingsProperty() { return enableEmacsKeyBindingsProperty; }

public BooleanProperty enableEmacsRebindCAProperty() { return enableEmacsRebindCAProperty; }

public BooleanProperty enableEmacsRebindCFProperty() { return enableEmacsRebindCFProperty; }

public BooleanProperty enableEmacsRebindCNProperty() { return enableEmacsRebindCNProperty; }

public BooleanProperty enableEmacsRebindAUProperty() { return enableEmacsRebindAUProperty; }
}

Loading

0 comments on commit 1d68c6e

Please sign in to comment.