Skip to content

Commit

Permalink
Implement Emacs key bindings (#6037)
Browse files Browse the repository at this point in the history
Co-authored-by: Felix Luthman <34520175+felixlut@users.noreply.github.com>
Co-authored-by: Tommy Samuelsson <Zodbigt@users.noreply.github.com>
Co-authored-by: muachilin <32566798+muachilin@users.noreply.github.com>
Co-authored-by: Kristoffer Gunnarsson <kristoffergunnarsson47@gmail.com>
Co-authored-by: David Stevens <dstevens@kth.se>
Co-authored-by: Carl Christian Snethlage <cc.snethlage@gmail.com>
  • Loading branch information
6 people committed Nov 6, 2020
1 parent cc1bb83 commit 82e555d
Show file tree
Hide file tree
Showing 18 changed files with 735 additions and 37 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ to the page field for cases where the page numbers are missing. [#7019](https://
- We added a new fetcher to enable users to search jstor.org [#6627](https://github.com/JabRef/jabref/issues/6627)
- We added an error message in the New Entry dialog that is shown in case the fetcher did not find anything . [#7000](https://github.com/JabRef/jabref/issues/7000)
- We added a new formatter to output shorthand month format. [#6579](https://github.com/JabRef/jabref/issues/6579)
- We reintroduced emacs/bash-like keybindings. [#6017](https://github.com/JabRef/jabref/issues/6017)

### Changed

Expand Down
21 changes: 13 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.TextInputKeyBindings;
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,10 @@ private void openWindow(Stage mainStage) {

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

// Handle TextEditor key bindings
scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> TextInputKeyBindings.call(scene, event));

mainStage.setTitle(JabRefFrame.FRAME_TITLE);
mainStage.getIcons().addAll(IconTheme.getLogoSetFX());
mainStage.setScene(scene);
Expand Down Expand Up @@ -219,14 +225,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
19 changes: 7 additions & 12 deletions src/main/java/org/jabref/gui/entryeditor/SourceTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Tooltip;
import javafx.scene.input.InputMethodRequests;
import javafx.scene.input.KeyEvent;

import org.jabref.gui.DialogService;
import org.jabref.gui.Globals;
Expand All @@ -26,6 +27,7 @@
import org.jabref.gui.actions.SimpleCommand;
import org.jabref.gui.actions.StandardActions;
import org.jabref.gui.icon.IconTheme;
import org.jabref.gui.keyboard.CodeAreaKeyBindings;
import org.jabref.gui.keyboard.KeyBindingRepository;
import org.jabref.gui.undo.CountingUndoManager;
import org.jabref.gui.undo.NamedCompound;
Expand Down Expand Up @@ -85,18 +87,10 @@ public EditAction(StandardActions command) {
@Override
public void execute() {
switch (command) {
case COPY:
codeArea.copy();
break;
case CUT:
codeArea.cut();
break;
case PASTE:
codeArea.paste();
break;
case SELECT_ALL:
codeArea.selectAll();
break;
case COPY -> codeArea.copy();
case CUT -> codeArea.cut();
case PASTE -> codeArea.paste();
case SELECT_ALL -> codeArea.selectAll();
}
codeArea.requestFocus();
}
Expand Down Expand Up @@ -178,6 +172,7 @@ private void setupSourceEditor() {
}
});
codeArea.setId("bibtexSourceCodeArea");
codeArea.addEventFilter(KeyEvent.KEY_PRESSED, event -> CodeAreaKeyBindings.call(codeArea, event));

ActionFactory factory = new ActionFactory(keyBindingRepository);
ContextMenu contextMenu = new ContextMenu();
Expand Down
113 changes: 113 additions & 0 deletions src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package org.jabref.gui.keyboard;

import javafx.scene.input.KeyEvent;

import org.jabref.gui.Globals;
import org.jabref.logic.util.strings.StringManipulator;
import org.jabref.model.util.ResultingStringState;

import org.fxmisc.richtext.CodeArea;
import org.fxmisc.richtext.NavigationActions;

public class CodeAreaKeyBindings {

public static void call(CodeArea codeArea, KeyEvent event) {
KeyBindingRepository keyBindingRepository = Globals.getKeyPrefs();
keyBindingRepository.mapToKeyBinding(event).ifPresent(binding -> {
switch (binding) {
case EDITOR_DELETE -> {
codeArea.deleteNextChar();
event.consume();
}
case EDITOR_BACKWARD -> {
codeArea.previousChar(NavigationActions.SelectionPolicy.CLEAR);
event.consume();
}
case EDITOR_FORWARD -> {
codeArea.nextChar(NavigationActions.SelectionPolicy.CLEAR);
event.consume();
}
case EDITOR_WORD_BACKWARD -> {
codeArea.wordBreaksBackwards(2, NavigationActions.SelectionPolicy.CLEAR);
event.consume();
}
case EDITOR_WORD_FORWARD -> {
codeArea.wordBreaksForwards(2, NavigationActions.SelectionPolicy.CLEAR);
event.consume();
}
case EDITOR_BEGINNING_DOC -> {
codeArea.start(NavigationActions.SelectionPolicy.CLEAR);
event.consume();
}
case EDITOR_UP -> {
codeArea.paragraphStart(NavigationActions.SelectionPolicy.CLEAR);
event.consume();
}
case EDITOR_BEGINNING -> {
codeArea.lineStart(NavigationActions.SelectionPolicy.CLEAR);
event.consume();
}
case EDITOR_END_DOC -> {
codeArea.end(NavigationActions.SelectionPolicy.CLEAR);
event.consume();
}
case EDITOR_DOWN -> {
codeArea.paragraphEnd(NavigationActions.SelectionPolicy.CLEAR);
event.consume();
}
case EDITOR_END -> {
codeArea.lineEnd(NavigationActions.SelectionPolicy.CLEAR);
event.consume();
}
case EDITOR_CAPITALIZE -> {
int pos = codeArea.getCaretPosition();
String text = codeArea.getText(0, codeArea.getText().length());
ResultingStringState res = StringManipulator.capitalize(pos, text);
codeArea.replaceText(res.text);
codeArea.displaceCaret(res.caretPosition);
event.consume();
}
case EDITOR_LOWERCASE -> {
int pos = codeArea.getCaretPosition();
String text = codeArea.getText(0, codeArea.getText().length());
ResultingStringState res = StringManipulator.lowercase(pos, text);
codeArea.replaceText(res.text);
codeArea.displaceCaret(res.caretPosition);
event.consume();
}
case EDITOR_UPPERCASE -> {
int pos = codeArea.getCaretPosition();
String text = codeArea.getText(0, codeArea.getText().length());
ResultingStringState res = StringManipulator.uppercase(pos, text);
codeArea.clear();
codeArea.replaceText(res.text);
codeArea.displaceCaret(res.caretPosition);
event.consume();
}
case EDITOR_KILL_LINE -> {
int pos = codeArea.getCaretPosition();
codeArea.replaceText(codeArea.getText(0, pos));
codeArea.displaceCaret(pos);
event.consume();
}
case EDITOR_KILL_WORD -> {
int pos = codeArea.getCaretPosition();
String text = codeArea.getText(0, codeArea.getText().length());
ResultingStringState res = StringManipulator.killWord(pos, text);
codeArea.replaceText(res.text);
codeArea.displaceCaret(res.caretPosition);
event.consume();
}
case EDITOR_KILL_WORD_BACKWARD -> {
int pos = codeArea.getCaretPosition();
String text = codeArea.getText(0, codeArea.getText().length());
ResultingStringState res = StringManipulator.backwardKillWord(pos, text);
codeArea.replaceText(res.text);
codeArea.displaceCaret(res.caretPosition);
event.consume();
}
}
});
}
}

18 changes: 18 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,24 @@
import org.jabref.logic.l10n.Localization;

public enum KeyBinding {
EDITOR_DELETE("Delete", Localization.lang("Delete text"), "", KeyBindingCategory.EDITOR),
// DELETE BACKWARDS = Rubout
EDITOR_BACKWARD("Move caret left", Localization.lang("Move caret left"), "", KeyBindingCategory.EDITOR),
EDITOR_FORWARD("Move caret right", Localization.lang("Move caret right"), "", KeyBindingCategory.EDITOR),
EDITOR_WORD_BACKWARD("Move caret to previous word", Localization.lang("Move caret to previous word"), "", KeyBindingCategory.EDITOR),
EDITOR_WORD_FORWARD("Move caret to next word", Localization.lang("Move caret to next word"), "", KeyBindingCategory.EDITOR),
EDITOR_BEGINNING("Move caret to beginning of line", Localization.lang("Move caret to beginning of line"), "", KeyBindingCategory.EDITOR),
EDITOR_END("Move caret to of line", Localization.lang("Move caret to end of line"), "", KeyBindingCategory.EDITOR),
EDITOR_BEGINNING_DOC("Move caret to beginning of text", Localization.lang("Move the caret to the beginning of text"), "", KeyBindingCategory.EDITOR),
EDITOR_END_DOC("Move caret to end of text", Localization.lang("Move the caret to the end of text"), "", KeyBindingCategory.EDITOR),
EDITOR_UP("Move caret up", Localization.lang("Move the caret up"), "", KeyBindingCategory.EDITOR),
EDITOR_DOWN("Move caret down", Localization.lang("Move the caret down"), "", KeyBindingCategory.EDITOR),
EDITOR_CAPITALIZE("Capitalize word", Localization.lang("Capitalize current word"), "", KeyBindingCategory.EDITOR),
EDITOR_LOWERCASE("Lowercase word", Localization.lang("Make current word lowercase"), "", KeyBindingCategory.EDITOR),
EDITOR_UPPERCASE("Uppercase word", Localization.lang("Make current word uppercase"), "", KeyBindingCategory.EDITOR),
EDITOR_KILL_LINE("Remove all characters caret to end of line", Localization.lang("Remove line after caret"), "", KeyBindingCategory.EDITOR),
EDITOR_KILL_WORD("Remove characters until next word", Localization.lang("Remove characters until next word"), "", KeyBindingCategory.EDITOR),
EDITOR_KILL_WORD_BACKWARD("Characters until previous word", Localization.lang("Remove the current word backwards"), "", KeyBindingCategory.EDITOR),

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")),
EDITOR(Localization.lang("Text editor"));

private final String name;

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

Expand Down
20 changes: 16 additions & 4 deletions src/main/java/org/jabref/gui/keyboard/KeyBindingViewModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ public String getBinding() {
private void setBinding(String bind) {
this.realBinding = bind;
String[] parts = bind.split(" ");
String displayBind = "";
StringBuilder displayBind = new StringBuilder();
for (String part : parts) {
displayBind += CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, part) + " ";
displayBind.append(CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, part)).append(" ");
}
this.shownBinding.set(displayBind.trim().replace(" ", " + "));
this.shownBinding.set(displayBind.toString().trim().replace(" ", " + "));
}

private void setDisplayName() {
Expand Down Expand Up @@ -135,7 +135,19 @@ public void resetToDefault() {
}
}

public Optional<JabRefIcon> getIcon() {
public void clear() {
if (!isCategory()) {
String key = getKeyBinding().getConstant();
keyBindingRepository.put(key, "");
setBinding(keyBindingRepository.get(key));
}
}

public Optional<JabRefIcon> getResetIcon() {
return isCategory() ? Optional.empty() : Optional.of(IconTheme.JabRefIcons.REFRESH);
}

public Optional<JabRefIcon> getClearIcon() {
return isCategory() ? Optional.empty() : Optional.of(IconTheme.JabRefIcons.CLEANUP_ENTRIES);
}
}
14 changes: 10 additions & 4 deletions src/main/java/org/jabref/gui/keyboard/KeyBindingsDialog.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,29 @@

<?import javafx.scene.control.ButtonType?>
<?import javafx.scene.control.DialogPane?>
<?import javafx.scene.control.MenuButton?>
<?import javafx.scene.control.TreeTableColumn?>
<?import javafx.scene.control.TreeTableView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<DialogPane xmlns:fx="http://javafx.com/fxml/1" minHeight="450.0" minWidth="375.0" prefHeight="450.0" prefWidth="407.0"
xmlns="http://javafx.com/javafx/8.0.65" fx:controller="org.jabref.gui.keyboard.KeyBindingsDialogView">
<content>
<VBox>
<VBox spacing="4">
<TreeTableView fx:id="keyBindingsTable" showRoot="false" styleClass="keybinding-table">
<columns>
<TreeTableColumn fx:id="actionColumn" prefWidth="75.0" text="%Action"/>
<TreeTableColumn fx:id="shortcutColumn" prefWidth="75.0" text="%Shortcut"/>
<TreeTableColumn fx:id="resetColumn" maxWidth="600.0" prefWidth="75.0"/>
<TreeTableColumn fx:id="actionColumn" prefWidth="200.0" text="%Action"/>
<TreeTableColumn fx:id="shortcutColumn" prefWidth="100.0" text="%Shortcut"/>
<TreeTableColumn fx:id="resetColumn" maxWidth="25.0" prefWidth="25.0" minWidth="25.0"/>
<TreeTableColumn fx:id="clearColumn" maxWidth="25.0" prefWidth="25.0" minWidth="25.0"/>
</columns>
<columnResizePolicy>
<TreeTableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
</TreeTableView>
<HBox spacing="4" alignment="CENTER_RIGHT">
<MenuButton fx:id="presetsButton" text="%Presets"/>
</HBox>
</VBox>
</content>
<ButtonType fx:id="resetButton" text="%Reset Bindings" buttonData="LEFT"/>
Expand Down
19 changes: 18 additions & 1 deletion src/main/java/org/jabref/gui/keyboard/KeyBindingsDialogView.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import javafx.fxml.FXML;
import javafx.scene.control.ButtonType;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.SelectionModel;
import javafx.scene.control.TreeItem;
Expand All @@ -12,6 +14,7 @@

import org.jabref.gui.DialogService;
import org.jabref.gui.icon.JabRefIcon;
import org.jabref.gui.keyboard.presets.KeyBindingPreset;
import org.jabref.gui.util.BaseDialog;
import org.jabref.gui.util.ControlHelper;
import org.jabref.gui.util.RecursiveTreeItem;
Expand All @@ -30,6 +33,8 @@ public class KeyBindingsDialogView extends BaseDialog<Void> {
@FXML private TreeTableColumn<KeyBindingViewModel, String> actionColumn;
@FXML private TreeTableColumn<KeyBindingViewModel, String> shortcutColumn;
@FXML private TreeTableColumn<KeyBindingViewModel, KeyBindingViewModel> resetColumn;
@FXML private TreeTableColumn<KeyBindingViewModel, KeyBindingViewModel> clearColumn;
@FXML private MenuButton presetsButton;

@Inject private KeyBindingRepository keyBindingRepository;
@Inject private DialogService dialogService;
Expand Down Expand Up @@ -66,9 +71,21 @@ private void initialize() {
actionColumn.setCellValueFactory(cellData -> cellData.getValue().getValue().nameProperty());
shortcutColumn.setCellValueFactory(cellData -> cellData.getValue().getValue().shownBindingProperty());
new ViewModelTreeTableCellFactory<KeyBindingViewModel>()
.withGraphic(keyBinding -> keyBinding.getIcon().map(JabRefIcon::getGraphicNode).orElse(null))
.withGraphic(keyBinding -> keyBinding.getResetIcon().map(JabRefIcon::getGraphicNode).orElse(null))
.withOnMouseClickedEvent(keyBinding -> evt -> keyBinding.resetToDefault())
.install(resetColumn);
new ViewModelTreeTableCellFactory<KeyBindingViewModel>()
.withGraphic(keyBinding -> keyBinding.getClearIcon().map(JabRefIcon::getGraphicNode).orElse(null))
.withOnMouseClickedEvent(keyBinding -> evt -> keyBinding.clear())
.install(clearColumn);

viewModel.keyBindingPresets().forEach(preset -> presetsButton.getItems().add(createMenuItem(preset)));
}

private MenuItem createMenuItem(KeyBindingPreset preset) {
MenuItem item = new MenuItem(preset.getName());
item.setOnAction((event) -> viewModel.loadPreset(preset));
return item;
}

@FXML
Expand Down
Loading

0 comments on commit 82e555d

Please sign in to comment.