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

Implement Emacs key bindings #6037

Merged
merged 20 commits into from
Nov 6, 2020
Merged
Show file tree
Hide file tree
Changes from 7 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
23 changes: 15 additions & 8 deletions src/main/java/org/jabref/gui/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 @@ -16,6 +17,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 @@ -86,6 +88,12 @@ private void openWindow(Stage mainStage) {

Scene scene = new Scene(root, 800, 800);
Globals.prefs.getTheme().installCss(scene);

// Handle Emacs key bindings
scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
EmacsKeyBindings.executeEmacs(scene, event, Globals.prefs);
});

mainStage.setTitle(JabRefFrame.FRAME_TITLE);
mainStage.getIcons().addAll(IconTheme.getLogoSetFX());
mainStage.setScene(scene);
Expand Down Expand Up @@ -219,14 +227,13 @@ private void saveWindowState(Stage mainStage) {
*/
private void debugLogWindowState(Stage mainStage) {
if (LOGGER.isDebugEnabled()) {
StringBuilder debugLogString = new StringBuilder();
debugLogString.append("SCREEN DATA:");
debugLogString.append("mainStage.WINDOW_MAXIMISED: ").append(mainStage.isMaximized()).append("\n");
debugLogString.append("mainStage.POS_X: ").append(mainStage.getX()).append("\n");
debugLogString.append("mainStage.POS_Y: ").append(mainStage.getY()).append("\n");
debugLogString.append("mainStage.SIZE_X: ").append(mainStage.getWidth()).append("\n");
debugLogString.append("mainStages.SIZE_Y: ").append(mainStage.getHeight()).append("\n");
LOGGER.debug(debugLogString.toString());
String debugLogString = "SCREEN DATA:" +
"mainStage.WINDOW_MAXIMISED: " + mainStage.isMaximized() + "\n" +
"mainStage.POS_X: " + mainStage.getX() + "\n" +
"mainStage.POS_Y: " + mainStage.getY() + "\n" +
"mainStage.SIZE_X: " + mainStage.getWidth() + "\n" +
"mainStages.SIZE_Y: " + mainStage.getHeight() + "\n";
LOGGER.debug(debugLogString);
}
}

Expand Down
99 changes: 99 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,99 @@
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.gui.Globals;
import org.jabref.logic.util.strings.EmacsStringManipulator;
import org.jabref.model.util.ResultingEmacsState;
import org.jabref.preferences.PreferencesService;

public class EmacsKeyBindings {

public static void executeEmacs(Scene scene, KeyEvent event, PreferencesService preferencesService) {
if (preferencesService.getEmacsKeyPreferences().useEmacsKeyBindings()
&& scene.focusOwnerProperty().get() instanceof TextInputControl) {

KeyBindingRepository keyBindingRepository = Globals.getKeyPrefs();
TextInputControl focusedTextField = (TextInputControl) scene.focusOwnerProperty().get();
Optional<KeyBinding> keyBinding = keyBindingRepository.mapToKeyBinding(event);
keyBinding.ifPresent(binding -> {
if (binding.equals(KeyBinding.EMACS_DELETE)) {
focusedTextField.deletePreviousChar();
event.consume();
calixtus marked this conversation as resolved.
Show resolved Hide resolved
} else if (binding == KeyBinding.EMACS_BACKWARD) {
focusedTextField.backward();
event.consume();
} else if (preferencesService.getEmacsKeyPreferences().shouldRebindCF()
&& binding == KeyBinding.EMACS_FORWARD) {
focusedTextField.forward();
event.consume();
} else if (preferencesService.getEmacsKeyPreferences().shouldRebindCA()
&& binding == KeyBinding.EMACS_BEGINNING) {
focusedTextField.home();
event.consume();
} else if (binding == KeyBinding.EMACS_END) {
focusedTextField.end();
event.consume();
} else if (binding == KeyBinding.EMACS_BEGINNING_DOC) {
focusedTextField.home();
event.consume();
} else if (binding == KeyBinding.EMACS_END_DOC) {
focusedTextField.end();
event.consume();
} else if (binding == KeyBinding.EMACS_UP) {
focusedTextField.home();
event.consume();
} else if (preferencesService.getEmacsKeyPreferences().shouldRebindCN()
&& binding == KeyBinding.EMACS_DOWN) {
focusedTextField.end();
event.consume();
} else if (binding == 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 (binding == 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 (preferencesService.getEmacsKeyPreferences().shouldRebindAU()
&& binding == 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 (binding == KeyBinding.EMACS_KILL_LINE) {
int pos = focusedTextField.getCaretPosition();
focusedTextField.setText(focusedTextField.getText(0, pos));
focusedTextField.positionCaret(pos);
event.consume();
} else if (binding == KeyBinding.EMACS_KILL_WORD) {
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 (binding == KeyBinding.EMACS_KILL_WORD_BACKWARD) {
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();
}
});
}
}
}
41 changes: 41 additions & 0 deletions src/main/java/org/jabref/gui/keyboard/EmacsKeyPreferences.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.jabref.gui.keyboard;

public class EmacsKeyPreferences {
private final boolean useEmacsKeyBindings;
private final boolean rebindCA;
private final boolean rebindCF;
private final boolean rebindCN;
private final boolean rebindAU;

public EmacsKeyPreferences(boolean useEmacsKeyBindings,
boolean rebindCA,
boolean rebindCF,
boolean rebindCN,
boolean rebindAU) {
this.useEmacsKeyBindings = useEmacsKeyBindings;
this.rebindCA = rebindCA;
this.rebindCF = rebindCF;
this.rebindCN = rebindCN;
this.rebindAU = rebindAU;
}

public boolean useEmacsKeyBindings() {
return useEmacsKeyBindings;
}

public boolean shouldRebindCA() {
return rebindCA;
}

public boolean shouldRebindCF() {
return rebindCF;
}

public boolean shouldRebindCN() {
return rebindCN;
}

public boolean shouldRebindAU() {
return rebindAU;
}
}
17 changes: 17 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,23 @@
import org.jabref.logic.l10n.Localization;

public enum KeyBinding {
EMACS_DELETE("Emacs delete", Localization.lang("Delete text"), "ctrl+D", KeyBindingCategory.EMACS),
EMACS_BACKWARD("Emacs move caret left", Localization.lang("Move caret left"), "ctrl+B", KeyBindingCategory.EMACS),
EMACS_FORWARD("Emacs move caret right", Localization.lang("Move caret right"), "ctrl+F", KeyBindingCategory.EMACS),
EMACS_WORD_BACKWARD("Emacs move caret to previous word", Localization.lang("Move caret to previous word"), "alt+B", KeyBindingCategory.EMACS),
EMACS_WORD_FORWARD("Emacs move caret to next word", Localization.lang("Move caret to next word"), "alt+B", KeyBindingCategory.EMACS),
EMACS_BEGINNING("Emacs move caret to beginning of line", Localization.lang("Move caret to beginning of line"), "ctrl+A", KeyBindingCategory.EMACS),
calixtus marked this conversation as resolved.
Show resolved Hide resolved
EMACS_END("Emacs move caret to of line", Localization.lang("Move caret to end of line"), "ctrl+E", KeyBindingCategory.EMACS),
EMACS_BEGINNING_DOC("Emacs move caret to beginning of text", Localization.lang("Move the caret to the beginning of text"), "alt+LESS", KeyBindingCategory.EMACS),
EMACS_END_DOC("Emacs move caret to end of text", Localization.lang("Move the caret to the end of text"), "alt+shift+LESS", KeyBindingCategory.EMACS),
EMACS_UP("Emacs move caret up", Localization.lang("Move the caret up"), "ctrl+P", KeyBindingCategory.EMACS),
EMACS_DOWN("Emacs move caret down", Localization.lang("Move the caret down"), "ctrl+N", KeyBindingCategory.EMACS),
EMACS_CAPITALIZE("Emacs capitalize word", Localization.lang("Capitalize current word"), "alt+C", KeyBindingCategory.EMACS),
EMACS_LOWERCASE("Emacs lowercase word", Localization.lang("Make current word lowercase"), "alt+L", KeyBindingCategory.EMACS),
EMACS_UPPERCASE("Emacs uppercase word", Localization.lang("Make current word uppercase"), "alt+U", KeyBindingCategory.EMACS),
EMACS_KILL_LINE("Emacs remove all characters caret to end of line", Localization.lang("Remove line after caret"), "ctrl+K", KeyBindingCategory.EMACS),
EMACS_KILL_WORD("Emacs remove characters until next word", Localization.lang("Remove characters until next word"), "alt+D", KeyBindingCategory.EMACS),
EMACS_KILL_WORD_BACKWARD("Emacs characters until previous word", Localization.lang("Remove the current word backwards"), "alt+DELETE", KeyBindingCategory.EMACS),

ABBREVIATE("Abbreviate", Localization.lang("Abbreviate journal names"), "ctrl+alt+A", KeyBindingCategory.TOOLS),
AUTOGENERATE_CITATION_KEYS("Autogenerate citation keys", Localization.lang("Autogenerate citation keys"), "ctrl+G", KeyBindingCategory.QUALITY),
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/org/jabref/gui/keyboard/KeyBindingCategory.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ public enum KeyBindingCategory {
VIEW(Localization.lang("View")),
BIBTEX(BibDatabaseMode.BIBTEX.getFormattedName()),
QUALITY(Localization.lang("Quality")),
TOOLS(Localization.lang("Tools"));
TOOLS(Localization.lang("Tools")),
EMACS(Localization.lang("Emacs"));

private final String name;

private KeyBindingCategory(String name) {
KeyBindingCategory(String name) {
this.name = name;
}

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" disable="${!enableEmacsKeyBindings.selected}">
<padding>
<Insets left="20.0"/>
</padding>
</CheckBox>
<CheckBox fx:id="enableEmacsRebindCF" text="%Rebind C-f" disable="${!enableEmacsKeyBindings.selected}">
<padding>
<Insets left="20.0"/>
</padding>
</CheckBox>
<CheckBox fx:id="enableEmacsRebindCN" text="%Rebind C-n" disable="${!enableEmacsKeyBindings.selected}">
<padding>
<Insets left="20.0"/>
</padding>
</CheckBox>
<CheckBox fx:id="enableEmacsRebindAU" text="%Rebind A-u" 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 @@ -49,6 +54,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 @@ -12,6 +12,7 @@
import org.jabref.gui.autocompleter.AutoCompleteFirstNameMode;
import org.jabref.gui.autocompleter.AutoCompletePreferences;
import org.jabref.gui.entryeditor.EntryEditorPreferences;
import org.jabref.gui.keyboard.EmacsKeyPreferences;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.entry.field.FieldFactory;
import org.jabref.preferences.PreferencesService;
Expand All @@ -32,10 +33,16 @@ 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 final DialogService dialogService;
private final PreferencesService preferencesService;
private final EntryEditorPreferences initialEntryEditorPreferences;
private final EmacsKeyPreferences initialEmacsKeyPreferences;
private final AutoCompletePreferences initialAutoCompletePreferences;

private final List<String> restartWarnings = new ArrayList<>();
Expand All @@ -44,6 +51,7 @@ public EntryEditorTabViewModel(DialogService dialogService, PreferencesService p
this.dialogService = dialogService;
this.preferencesService = preferencesService;
this.initialEntryEditorPreferences = preferencesService.getEntryEditorPreferences();
this.initialEmacsKeyPreferences = preferencesService.getEmacsKeyPreferences();
this.initialAutoCompletePreferences = preferencesService.getAutoCompletePreferences();
}

Expand All @@ -59,6 +67,12 @@ public void setValues() {
enableLatexCitationsTabProperty.setValue(initialEntryEditorPreferences.shouldShowLatexCitationsTab());
enableValidationProperty.setValue(initialEntryEditorPreferences.isEnableValidation());

enableEmacsKeyBindingsProperty.setValue(initialEmacsKeyPreferences.useEmacsKeyBindings());
enableEmacsRebindCAProperty.setValue(initialEmacsKeyPreferences.shouldRebindCA());
enableEmacsRebindCFProperty.setValue(initialEmacsKeyPreferences.shouldRebindCF());
enableEmacsRebindCNProperty.setValue(initialEmacsKeyPreferences.shouldRebindCN());
enableEmacsRebindAUProperty.setValue(initialEmacsKeyPreferences.shouldRebindAU());

enableAutoCompleteProperty.setValue(initialAutoCompletePreferences.shouldAutoComplete());
autoCompleteFieldsProperty.setValue(initialAutoCompletePreferences.getCompleteNamesAsString());

Expand Down Expand Up @@ -89,6 +103,13 @@ public void storeSettings() {
enableValidationProperty.getValue(),
initialEntryEditorPreferences.getDividerPosition()));

preferencesService.storeEmacsKeyPreferences(new EmacsKeyPreferences(
enableEmacsKeyBindingsProperty.getValue(),
enableEmacsRebindCAProperty.getValue(),
enableEmacsRebindCFProperty.getValue(),
enableEmacsRebindCNProperty.getValue(),
enableEmacsRebindAUProperty.getValue()));

// default
AutoCompletePreferences.NameFormat nameFormat = AutoCompletePreferences.NameFormat.BOTH;
if (autoCompleteFirstLastProperty.getValue()) {
Expand Down Expand Up @@ -186,4 +207,19 @@ public BooleanProperty 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