diff --git a/CHANGELOG.md b/CHANGELOG.md index 247520fab32..e7cf6c325e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve ### Added - We added the extension support and the external application support (For Texshow, Texmaker and LyX) to the flatpak [#7248](https://github.com/JabRef/jabref/pull/7248) +- We added some symbols and keybindings to the context menu in the entry editor. [#7268]((https://github.com/JabRef/jabref/pull/7268)) ### Changed diff --git a/src/main/java/org/jabref/gui/actions/Action.java b/src/main/java/org/jabref/gui/actions/Action.java index 5d940ffba55..e6ccfe487b2 100644 --- a/src/main/java/org/jabref/gui/actions/Action.java +++ b/src/main/java/org/jabref/gui/actions/Action.java @@ -6,11 +6,17 @@ import org.jabref.gui.keyboard.KeyBinding; public interface Action { - Optional getIcon(); + default Optional getIcon() { + return Optional.empty(); + } - Optional getKeyBinding(); + default Optional getKeyBinding() { + return Optional.empty(); + } String getText(); - String getDescription(); + default String getDescription() { + return ""; + } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditor.java b/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditor.java index 1f4b7532022..9dc9a5db436 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditor.java @@ -1,5 +1,7 @@ package org.jabref.gui.fieldeditors; +import java.util.Collections; + import javax.swing.undo.UndoManager; import javafx.fxml.FXML; @@ -50,6 +52,8 @@ public CitationKeyEditor(Field field, textField.textProperty().bindBidirectional(viewModel.textProperty()); + textField.initContextMenu(Collections::emptyList); + new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textField); } diff --git a/src/main/java/org/jabref/gui/fieldeditors/ContextMenuAddable.java b/src/main/java/org/jabref/gui/fieldeditors/ContextMenuAddable.java index 9f9bff69e41..708ca1c785b 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/ContextMenuAddable.java +++ b/src/main/java/org/jabref/gui/fieldeditors/ContextMenuAddable.java @@ -11,5 +11,5 @@ public interface ContextMenuAddable { * to be instantiated at this point. They are populated when the user needs them which prevents many unnecessary * allocations when the main table is just scrolled with the entry editor open. */ - void addToContextMenu(final Supplier> items); + void initContextMenu(final Supplier> items); } diff --git a/src/main/java/org/jabref/gui/fieldeditors/EditorTextArea.java b/src/main/java/org/jabref/gui/fieldeditors/EditorTextArea.java index 13a7f7f6ac3..92eb4e88817 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/EditorTextArea.java +++ b/src/main/java/org/jabref/gui/fieldeditors/EditorTextArea.java @@ -11,6 +11,8 @@ import javafx.scene.control.MenuItem; import org.jabref.gui.ClipBoardManager; +import org.jabref.gui.Globals; +import org.jabref.gui.fieldeditors.contextmenu.EditorContextAction; public class EditorTextArea extends javafx.scene.control.TextArea implements Initializable, ContextMenuAddable { @@ -36,9 +38,9 @@ public EditorTextArea(final String text) { } @Override - public void addToContextMenu(final Supplier> items) { + public void initContextMenu(final Supplier> items) { setOnContextMenuRequested(event -> { - contextMenu.getItems().setAll(TextInputControlBehavior.getDefaultContextMenuItems(this)); + contextMenu.getItems().setAll(EditorContextAction.getDefaultContextMenuItems(this, Globals.getKeyPrefs())); contextMenu.getItems().addAll(0, items.get()); TextInputControlBehavior.showContextMenu(this, contextMenu, event); diff --git a/src/main/java/org/jabref/gui/fieldeditors/EditorTextField.java b/src/main/java/org/jabref/gui/fieldeditors/EditorTextField.java index 215083980a8..c5e8642e23e 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/EditorTextField.java +++ b/src/main/java/org/jabref/gui/fieldeditors/EditorTextField.java @@ -12,6 +12,8 @@ import javafx.scene.layout.Priority; import org.jabref.gui.ClipBoardManager; +import org.jabref.gui.Globals; +import org.jabref.gui.fieldeditors.contextmenu.EditorContextAction; public class EditorTextField extends javafx.scene.control.TextField implements Initializable, ContextMenuAddable { @@ -32,9 +34,9 @@ public EditorTextField(final String text) { } @Override - public void addToContextMenu(final Supplier> items) { + public void initContextMenu(final Supplier> items) { setOnContextMenuRequested(event -> { - contextMenu.getItems().setAll(TextInputControlBehavior.getDefaultContextMenuItems(this)); + contextMenu.getItems().setAll(EditorContextAction.getDefaultContextMenuItems(this, Globals.getKeyPrefs())); contextMenu.getItems().addAll(0, items.get()); TextInputControlBehavior.showContextMenu(this, contextMenu, event); diff --git a/src/main/java/org/jabref/gui/fieldeditors/GenderEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/GenderEditorViewModel.java index 0a71d908215..c324d3fbbcb 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/GenderEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/GenderEditorViewModel.java @@ -10,7 +10,7 @@ public class GenderEditorViewModel extends MapBasedEditorViewModel { - private BiMap itemMap = HashBiMap.create(7); + private final BiMap itemMap = HashBiMap.create(7); public GenderEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers) { super(field, suggestionProvider, fieldCheckers); diff --git a/src/main/java/org/jabref/gui/fieldeditors/IdentifierEditor.java b/src/main/java/org/jabref/gui/fieldeditors/IdentifierEditor.java index f659ffb5747..5de21fb6f04 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/IdentifierEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/IdentifierEditor.java @@ -10,6 +10,7 @@ import org.jabref.gui.DialogService; import org.jabref.gui.autocompleter.SuggestionProvider; +import org.jabref.gui.fieldeditors.contextmenu.DefaultMenu; import org.jabref.gui.fieldeditors.contextmenu.EditorMenus; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.integrity.FieldCheckers; @@ -49,9 +50,9 @@ public IdentifierEditor(Field field, new Tooltip(Localization.lang("Look up %0", field.getDisplayName()))); if (field.equals(StandardField.DOI)) { - textArea.addToContextMenu(EditorMenus.getDOIMenu(textArea)); + textArea.initContextMenu(EditorMenus.getDOIMenu(textArea)); } else { - textArea.addToContextMenu(EditorMenus.getDefaultMenu(textArea)); + textArea.initContextMenu(new DefaultMenu(textArea)); } new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textArea); diff --git a/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java b/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java index 0351d74f111..491818b4663 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java @@ -6,7 +6,7 @@ import org.jabref.gui.autocompleter.AutoCompletionTextInputBinding; import org.jabref.gui.autocompleter.SuggestionProvider; -import org.jabref.gui.fieldeditors.contextmenu.EditorMenus; +import org.jabref.gui.fieldeditors.contextmenu.DefaultMenu; import org.jabref.logic.integrity.FieldCheckers; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.model.entry.BibEntry; @@ -32,7 +32,7 @@ public JournalEditor(Field field, .load(); textField.textProperty().bindBidirectional(viewModel.textProperty()); - textField.addToContextMenu(EditorMenus.getDefaultMenu(textField)); + textField.initContextMenu(new DefaultMenu(textField)); AutoCompletionTextInputBinding.autoComplete(textField, viewModel::complete); diff --git a/src/main/java/org/jabref/gui/fieldeditors/OptionEditor.java b/src/main/java/org/jabref/gui/fieldeditors/OptionEditor.java index cbd397a1ef3..9de9856149b 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/OptionEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/OptionEditor.java @@ -3,8 +3,11 @@ import javafx.fxml.FXML; import javafx.scene.Parent; import javafx.scene.control.ComboBox; +import javafx.scene.control.ContextMenu; import javafx.scene.layout.HBox; +import org.jabref.gui.Globals; +import org.jabref.gui.fieldeditors.contextmenu.EditorContextAction; import org.jabref.gui.util.ViewModelListCellFactory; import org.jabref.model.entry.BibEntry; @@ -15,7 +18,7 @@ */ public class OptionEditor extends HBox implements FieldEditorFX { - @FXML private OptionEditorViewModel viewModel; + @FXML private final OptionEditorViewModel viewModel; @FXML private ComboBox comboBox; public OptionEditor(OptionEditorViewModel viewModel) { @@ -29,6 +32,12 @@ public OptionEditor(OptionEditorViewModel viewModel) { comboBox.setCellFactory(new ViewModelListCellFactory().withText(viewModel::convertToDisplayText)); comboBox.getItems().setAll(viewModel.getItems()); comboBox.getEditor().textProperty().bindBidirectional(viewModel.textProperty()); + + comboBox.getEditor().setOnContextMenuRequested(event -> { + ContextMenu contextMenu = new ContextMenu(); + contextMenu.getItems().setAll(EditorContextAction.getDefaultContextMenuItems(comboBox.getEditor(), Globals.getKeyPrefs())); + TextInputControlBehavior.showContextMenu(comboBox.getEditor(), contextMenu, event); + }); } public OptionEditorViewModel getViewModel() { diff --git a/src/main/java/org/jabref/gui/fieldeditors/OwnerEditor.java b/src/main/java/org/jabref/gui/fieldeditors/OwnerEditor.java index 51c2489f935..2c9ba1d1dfb 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/OwnerEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/OwnerEditor.java @@ -5,6 +5,7 @@ import javafx.scene.layout.HBox; import org.jabref.gui.autocompleter.SuggestionProvider; +import org.jabref.gui.fieldeditors.contextmenu.EditorMenus; import org.jabref.logic.integrity.FieldCheckers; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; @@ -29,6 +30,8 @@ public OwnerEditor(Field field, textArea.textProperty().bindBidirectional(viewModel.textProperty()); + textArea.initContextMenu(EditorMenus.getNameMenu(textArea)); + new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textArea); } diff --git a/src/main/java/org/jabref/gui/fieldeditors/PersonsEditor.java b/src/main/java/org/jabref/gui/fieldeditors/PersonsEditor.java index c73ea2d63c2..aa49582bb6b 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/PersonsEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/PersonsEditor.java @@ -30,7 +30,7 @@ public PersonsEditor(final Field field, decoratedStringProperty = new UiThreadStringProperty(viewModel.textProperty()); textInput.textProperty().bindBidirectional(decoratedStringProperty); - ((ContextMenuAddable) textInput).addToContextMenu(EditorMenus.getNameMenu(textInput)); + ((ContextMenuAddable) textInput).initContextMenu(EditorMenus.getNameMenu(textInput)); this.getChildren().add(textInput); AutoCompletionTextInputBinding.autoComplete(textInput, viewModel::complete, viewModel.getAutoCompletionConverter(), viewModel.getAutoCompletionStrategy()); diff --git a/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java b/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java index 8b30f9fdf7a..bf720d0e72b 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java @@ -8,7 +8,7 @@ import org.jabref.gui.autocompleter.AutoCompletionTextInputBinding; import org.jabref.gui.autocompleter.ContentSelectorSuggestionProvider; import org.jabref.gui.autocompleter.SuggestionProvider; -import org.jabref.gui.fieldeditors.contextmenu.EditorMenus; +import org.jabref.gui.fieldeditors.contextmenu.DefaultMenu; import org.jabref.logic.integrity.FieldCheckers; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; @@ -30,7 +30,7 @@ public SimpleEditor(final Field field, HBox.setHgrow(textInput, Priority.ALWAYS); textInput.textProperty().bindBidirectional(viewModel.textProperty()); - ((ContextMenuAddable) textInput).addToContextMenu(EditorMenus.getDefaultMenu(textInput)); + ((ContextMenuAddable) textInput).initContextMenu(new DefaultMenu(textInput)); this.getChildren().add(textInput); if (!isMultiLine) { diff --git a/src/main/java/org/jabref/gui/fieldeditors/TextInputControlBehavior.java b/src/main/java/org/jabref/gui/fieldeditors/TextInputControlBehavior.java index bbe745e8de9..2051a10b068 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/TextInputControlBehavior.java +++ b/src/main/java/org/jabref/gui/fieldeditors/TextInputControlBehavior.java @@ -1,30 +1,17 @@ package org.jabref.gui.fieldeditors; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - import javafx.geometry.Point2D; import javafx.geometry.Rectangle2D; import javafx.scene.Scene; import javafx.scene.control.ContextMenu; -import javafx.scene.control.IndexRange; -import javafx.scene.control.MenuItem; -import javafx.scene.control.PasswordField; -import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; -import javafx.scene.control.TextInputControl; import javafx.scene.control.skin.TextAreaSkin; import javafx.scene.control.skin.TextFieldSkin; -import javafx.scene.input.Clipboard; import javafx.scene.input.ContextMenuEvent; import javafx.stage.Screen; import javafx.stage.Window; -import org.jabref.logic.l10n.Localization; -import org.jabref.logic.util.OS; - import com.sun.javafx.scene.control.Properties; /** @@ -35,63 +22,6 @@ */ public class TextInputControlBehavior { - private static final boolean SHOW_HANDLES = Properties.IS_TOUCH_SUPPORTED && !OS.OS_X; - - /** - * Returns the default context menu items (except undo/redo) - */ - public static List getDefaultContextMenuItems(TextInputControl textInputControl) { - boolean editable = textInputControl.isEditable(); - boolean hasText = (textInputControl.getLength() > 0); - boolean hasSelection = (textInputControl.getSelection().getLength() > 0); - boolean allSelected = (textInputControl.getSelection().getLength() == textInputControl.getLength()); - boolean maskText = (textInputControl instanceof PasswordField); // (maskText("A") != "A"); - ArrayList items = new ArrayList<>(); - - MenuItem cutMI = new MenuItem(Localization.lang("Cut")); - cutMI.setOnAction(e -> textInputControl.cut()); - MenuItem copyMI = new MenuItem(Localization.lang("Copy")); - copyMI.setOnAction(e -> textInputControl.copy()); - MenuItem pasteMI = new MenuItem(Localization.lang("Paste")); - pasteMI.setOnAction(e -> textInputControl.paste()); - MenuItem deleteMI = new MenuItem(Localization.lang("Delete")); - deleteMI.setOnAction(e -> { - IndexRange selection = textInputControl.getSelection(); - textInputControl.deleteText(selection); - }); - MenuItem selectAllMI = new MenuItem(Localization.lang("Select all")); - selectAllMI.setOnAction(e -> textInputControl.selectAll()); - MenuItem separatorMI = new SeparatorMenuItem(); - - if (SHOW_HANDLES) { - if (!maskText && hasSelection) { - if (editable) { - items.add(cutMI); - } - items.add(copyMI); - } - if (editable && Clipboard.getSystemClipboard().hasString()) { - items.add(pasteMI); - } - if (hasText && !allSelected) { - items.add(selectAllMI); - } - selectAllMI.getProperties().put("refreshMenu", Boolean.TRUE); - } else { - if (editable) { - items.addAll(Arrays.asList(cutMI, copyMI, pasteMI, deleteMI, separatorMI, selectAllMI)); - } else { - items.addAll(Arrays.asList(copyMI, separatorMI, selectAllMI)); - } - cutMI.setDisable(maskText || !hasSelection); - copyMI.setDisable(maskText || !hasSelection); - pasteMI.setDisable(!Clipboard.getSystemClipboard().hasString()); - deleteMI.setDisable(!hasSelection); - } - - return items; - } - /** * @implNote taken from {@link com.sun.javafx.scene.control.behavior.TextFieldBehavior#contextMenuRequested(javafx.scene.input.ContextMenuEvent)} */ @@ -146,6 +76,8 @@ public static void showContextMenu(TextField textField, ContextMenu contextMenu, textField.getProperties().put("CONTEXT_MENU_SCENE_X", 0); contextMenu.show(textField, menuX, screenY); } + + e.consume(); } /** @@ -202,5 +134,7 @@ public static void showContextMenu(TextArea textArea, ContextMenu contextMenu, C textArea.getProperties().put("CONTEXT_MENU_SCENE_X", 0); contextMenu.show(textArea, menuX, screenY); } + + e.consume(); } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/UrlEditor.java b/src/main/java/org/jabref/gui/fieldeditors/UrlEditor.java index 4988ea66bbb..8510b5fe95f 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/UrlEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/UrlEditor.java @@ -39,7 +39,7 @@ public UrlEditor(Field field, textArea.textProperty().bindBidirectional(viewModel.textProperty()); Supplier> contextMenuSupplier = EditorMenus.getCleanupUrlMenu(textArea); - textArea.addToContextMenu(contextMenuSupplier); + textArea.initContextMenu(contextMenuSupplier); // init paste handler for UrlEditor to format pasted url link in textArea textArea.setPasteActionHandler(() -> textArea.setText(new CleanupUrlFormatter().format(new TrimWhitespaceFormatter().format(textArea.getText())))); diff --git a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/CaseChangeMenu.java b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/CaseChangeMenu.java deleted file mode 100644 index 66465ef95aa..00000000000 --- a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/CaseChangeMenu.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.jabref.gui.fieldeditors.contextmenu; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import javafx.beans.property.StringProperty; -import javafx.scene.control.CustomMenuItem; -import javafx.scene.control.Label; -import javafx.scene.control.Menu; -import javafx.scene.control.Tooltip; - -import org.jabref.gui.Globals; -import org.jabref.logic.cleanup.Formatter; -import org.jabref.logic.formatter.Formatters; -import org.jabref.logic.formatter.casechanger.ProtectTermsFormatter; -import org.jabref.logic.l10n.Localization; - -class CaseChangeMenu extends Menu { - - public CaseChangeMenu(final StringProperty text) { - super(Localization.lang("Change case")); - Objects.requireNonNull(text); - - // create menu items, one for each case changer - List caseChangers = new ArrayList<>(); - caseChangers.addAll(Formatters.getCaseChangers()); - caseChangers.add(new ProtectTermsFormatter(Globals.protectedTermsLoader)); - for (final Formatter caseChanger : caseChangers) { - CustomMenuItem menuItem = new CustomMenuItem(new Label(caseChanger.getName())); - Tooltip toolTip = new Tooltip(caseChanger.getDescription()); - Tooltip.install(menuItem.getContent(), toolTip); - menuItem.setOnAction(event -> text.set(caseChanger.format(text.get()))); - - this.getItems().add(menuItem); - } - } -} diff --git a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ClearField.java b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ClearField.java deleted file mode 100644 index 22b846f44e2..00000000000 --- a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ClearField.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.jabref.gui.fieldeditors.contextmenu; - -import javafx.scene.control.MenuItem; -import javafx.scene.control.TextInputControl; - -import org.jabref.logic.l10n.Localization; - -class ClearField extends MenuItem { - - public ClearField(TextInputControl opener) { - super(Localization.lang("Clear")); - setOnAction(event -> opener.setText("")); - } -} diff --git a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ConversionMenu.java b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ConversionMenu.java deleted file mode 100644 index 8e6743933b4..00000000000 --- a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ConversionMenu.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.jabref.gui.fieldeditors.contextmenu; - -import javafx.beans.property.StringProperty; -import javafx.scene.control.CustomMenuItem; -import javafx.scene.control.Label; -import javafx.scene.control.Menu; -import javafx.scene.control.Tooltip; - -import org.jabref.logic.cleanup.Formatter; -import org.jabref.logic.formatter.Formatters; -import org.jabref.logic.l10n.Localization; - -/** - * Menu to show up on right-click in a text field for converting text formats - */ -class ConversionMenu extends Menu { - - public ConversionMenu(StringProperty text) { - super(Localization.lang("Convert")); - - // create menu items, one for each converter - for (Formatter converter : Formatters.getConverters()) { - CustomMenuItem menuItem = new CustomMenuItem(new Label(converter.getName())); - Tooltip toolTip = new Tooltip(converter.getDescription()); - Tooltip.install(menuItem.getContent(), toolTip); - menuItem.setOnAction(event -> text.set(converter.format(text.get()))); - this.getItems().add(menuItem); - } - } -} diff --git a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/DefaultMenu.java b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/DefaultMenu.java new file mode 100644 index 00000000000..c73686663f5 --- /dev/null +++ b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/DefaultMenu.java @@ -0,0 +1,80 @@ +package org.jabref.gui.fieldeditors.contextmenu; + +import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; + +import javafx.scene.control.CustomMenuItem; +import javafx.scene.control.Label; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.SeparatorMenuItem; +import javafx.scene.control.TextInputControl; +import javafx.scene.control.Tooltip; + +import org.jabref.logic.cleanup.Formatter; +import org.jabref.logic.formatter.Formatters; +import org.jabref.logic.l10n.Localization; + +public class DefaultMenu implements Supplier> { + + TextInputControl textInputControl; + + /** + * The default menu that contains functions for changing the case of text and doing several conversions. + * + * @param textInputControl that this menu will be connected to + */ + public DefaultMenu(TextInputControl textInputControl) { + this.textInputControl = textInputControl; + } + + public List get() { + return List.of( + getCaseChangeMenu(textInputControl), + getConversionMenu(textInputControl), + new SeparatorMenuItem(), + new ProtectedTermsMenu(textInputControl), + new SeparatorMenuItem(), + getClearFieldMenuItem(textInputControl)); + } + + private static Menu getCaseChangeMenu(TextInputControl textInputControl) { + Objects.requireNonNull(textInputControl.textProperty()); + Menu submenu = new Menu(Localization.lang("Change case")); + + for (final Formatter caseChanger : Formatters.getCaseChangers()) { + CustomMenuItem menuItem = new CustomMenuItem(new Label(caseChanger.getName())); + Tooltip toolTip = new Tooltip(caseChanger.getDescription()); + Tooltip.install(menuItem.getContent(), toolTip); + menuItem.setOnAction(event -> + textInputControl.textProperty().set(caseChanger.format(textInputControl.textProperty().get()))); + submenu.getItems().add(menuItem); + } + + return submenu; + } + + private static Menu getConversionMenu(TextInputControl textInputControl) { + Menu submenu = new Menu(Localization.lang("Convert")); + + for (Formatter converter : Formatters.getConverters()) { + CustomMenuItem menuItem = new CustomMenuItem(new Label(converter.getName())); + Tooltip toolTip = new Tooltip(converter.getDescription()); + Tooltip.install(menuItem.getContent(), toolTip); + menuItem.setOnAction(event -> + textInputControl.textProperty().set(converter.format(textInputControl.textProperty().get()))); + submenu.getItems().add(menuItem); + } + + return submenu; + } + + // Icon: DELETE_SWEEP + private static MenuItem getClearFieldMenuItem(TextInputControl textInputControl) { + MenuItem menuItem = new MenuItem(Localization.lang("Clear")); + menuItem.setOnAction(event -> textInputControl.setText("")); + + return menuItem; + } +} diff --git a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorContextAction.java b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorContextAction.java new file mode 100644 index 00000000000..d0a978f0958 --- /dev/null +++ b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorContextAction.java @@ -0,0 +1,89 @@ +package org.jabref.gui.fieldeditors.contextmenu; + +import java.util.List; + +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.BooleanProperty; +import javafx.scene.control.MenuItem; +import javafx.scene.control.PasswordField; +import javafx.scene.control.TextInputControl; +import javafx.scene.input.Clipboard; + +import org.jabref.gui.actions.ActionFactory; +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.gui.actions.StandardActions; +import org.jabref.gui.keyboard.KeyBindingRepository; +import org.jabref.gui.util.BindingsHelper; +import org.jabref.logic.util.OS; + +import com.sun.javafx.scene.control.Properties; + +public class EditorContextAction extends SimpleCommand { + + private static final boolean SHOW_HANDLES = Properties.IS_TOUCH_SUPPORTED && !OS.OS_X; + + private final StandardActions command; + private final TextInputControl textInputControl; + + public EditorContextAction(StandardActions command, TextInputControl textInputControl) { + this.command = command; + this.textInputControl = textInputControl; + + BooleanProperty editableBinding = textInputControl.editableProperty(); + BooleanBinding hasTextBinding = Bindings.createBooleanBinding(() -> textInputControl.getLength() > 0, textInputControl.textProperty()); + BooleanBinding hasStringInClipboardBinding = (BooleanBinding) BindingsHelper.constantOf(Clipboard.getSystemClipboard().hasString()); + BooleanBinding hasSelectionBinding = Bindings.createBooleanBinding(() -> textInputControl.getSelection().getLength() > 0, textInputControl.selectionProperty()); + BooleanBinding allSelectedBinding = Bindings.createBooleanBinding(() -> textInputControl.getSelection().getLength() == textInputControl.getLength()); + BooleanBinding maskTextBinding = (BooleanBinding) BindingsHelper.constantOf(textInputControl instanceof PasswordField); // (maskText("A") != "A"); + + this.executable.bind( + switch (command) { + case COPY -> editableBinding.and(maskTextBinding.not()).and(hasSelectionBinding); + case CUT -> maskTextBinding.not().and(hasSelectionBinding); + case PASTE -> editableBinding.and(hasStringInClipboardBinding); + case DELETE -> editableBinding.and(hasSelectionBinding); + case SELECT_ALL -> { + if (SHOW_HANDLES) { + yield hasTextBinding.and(allSelectedBinding.not()); + } else { + yield BindingsHelper.constantOf(true); + } + } + default -> BindingsHelper.constantOf(true); + }); + } + + @Override + public void execute() { + switch (command) { + case COPY -> textInputControl.copy(); + case CUT -> textInputControl.cut(); + case PASTE -> textInputControl.paste(); + case DELETE -> textInputControl.deleteText(textInputControl.getSelection()); + case SELECT_ALL -> textInputControl.selectAll(); + } + textInputControl.requestFocus(); + } + + /** + * Returns the default context menu items (except undo/redo) + */ + public static List getDefaultContextMenuItems(TextInputControl textInputControl, + KeyBindingRepository keyBindingRepository) { + ActionFactory factory = new ActionFactory(keyBindingRepository); + + MenuItem selectAllMenuItem = factory.createMenuItem(StandardActions.SELECT_ALL, + new EditorContextAction(StandardActions.SELECT_ALL, textInputControl)); + if (SHOW_HANDLES) { + selectAllMenuItem.getProperties().put("refreshMenu", Boolean.TRUE); + } + + return List.of( + factory.createMenuItem(StandardActions.CUT, new EditorContextAction(StandardActions.CUT, textInputControl)), + factory.createMenuItem(StandardActions.COPY, new EditorContextAction(StandardActions.COPY, textInputControl)), + factory.createMenuItem(StandardActions.PASTE, new EditorContextAction(StandardActions.PASTE, textInputControl)), + factory.createMenuItem(StandardActions.DELETE, new EditorContextAction(StandardActions.DELETE, textInputControl)), + selectAllMenuItem); + } +} diff --git a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorMenus.java b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorMenus.java index 53376639fbb..ce2fabc50cd 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorMenus.java +++ b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorMenus.java @@ -24,29 +24,10 @@ * Provides context menus for the text fields of the entry editor. Note that we use {@link Supplier} to prevent an early * instantiation of the menus. Therefore, they are attached to each text field but instantiation happens on the first * right-click of the user in that field. The late instantiation is done by {@link - * org.jabref.gui.fieldeditors.EditorTextArea#addToContextMenu(java.util.function.Supplier)}. + * org.jabref.gui.fieldeditors.EditorTextArea#initContextMenu(java.util.function.Supplier)}. */ public class EditorMenus { - /** - * The default menu that contains functions for changing the case of text and doing several conversions. - * - * @param textInput text-input-control that this menu will be connected to - * @return default context menu available for most text fields - */ - public static Supplier> getDefaultMenu(final TextInputControl textInput) { - return () -> { - List menuItems = new ArrayList<>(6); - menuItems.add(new CaseChangeMenu(textInput.textProperty())); - menuItems.add(new ConversionMenu(textInput.textProperty())); - menuItems.add(new SeparatorMenuItem()); - menuItems.add(new ProtectedTermsMenu(textInput)); - menuItems.add(new SeparatorMenuItem()); - menuItems.add(new ClearField(textInput)); - return menuItems; - }; - } - /** * The default context menu with a specific menu for normalizing person names regarding to BibTex rules. * @@ -61,7 +42,7 @@ public static Supplier> getNameMenu(final TextInputControl textIn Tooltip.install(normalizeNames.getContent(), toolTip); List menuItems = new ArrayList<>(6); menuItems.add(normalizeNames); - menuItems.addAll(getDefaultMenu(textInput).get()); + menuItems.addAll(new DefaultMenu(textInput).get()); return menuItems; }; } @@ -76,11 +57,10 @@ public static Supplier> getDOIMenu(TextArea textArea) { return () -> { ActionFactory factory = new ActionFactory(Globals.getKeyPrefs()); MenuItem copyDoiUrlMenuItem = factory.createMenuItem(StandardActions.COPY_DOI, new CopyDoiUrlAction(textArea)); - List menuItems = new ArrayList<>(); menuItems.add(copyDoiUrlMenuItem); menuItems.add(new SeparatorMenuItem()); - menuItems.addAll(getDefaultMenu(textArea).get()); + menuItems.addAll(new DefaultMenu(textArea).get()); return menuItems; }; } @@ -96,7 +76,6 @@ public static Supplier> getCleanupUrlMenu(TextArea textArea) { CustomMenuItem cleanupURL = new CustomMenuItem(new Label(Localization.lang("Cleanup URL link"))); cleanupURL.setDisable(textArea.textProperty().isEmpty().get()); cleanupURL.setOnAction(event -> textArea.setText(new CleanupUrlFormatter().format(textArea.getText()))); - List menuItems = new ArrayList<>(); menuItems.add(cleanupURL); return menuItems; diff --git a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ProtectedTermsMenu.java b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ProtectedTermsMenu.java index 0ebd8f1126e..e085eb981bd 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ProtectedTermsMenu.java +++ b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ProtectedTermsMenu.java @@ -1,7 +1,7 @@ package org.jabref.gui.fieldeditors.contextmenu; -import java.util.List; -import java.util.stream.Collectors; +import java.util.Objects; +import java.util.Optional; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; @@ -9,58 +9,102 @@ import javafx.scene.control.TextInputControl; import org.jabref.gui.Globals; +import org.jabref.gui.actions.Action; +import org.jabref.gui.actions.ActionFactory; +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.icon.JabRefIcon; import org.jabref.logic.cleanup.Formatter; import org.jabref.logic.formatter.casechanger.ProtectTermsFormatter; import org.jabref.logic.l10n.Localization; import org.jabref.logic.protectedterms.ProtectedTermsList; -import org.jabref.logic.protectedterms.ProtectedTermsLoader; class ProtectedTermsMenu extends Menu { private static final Formatter FORMATTER = new ProtectTermsFormatter(Globals.protectedTermsLoader); - private final Menu externalFiles; - private final TextInputControl opener; + private final TextInputControl textInputControl; + private final ActionFactory factory = new ActionFactory(Globals.getKeyPrefs()); - public ProtectedTermsMenu(final TextInputControl opener) { + private final Action protectSelectionActionInformation = new Action() { + @Override + public String getText() { + return Localization.lang("Protect selection"); + } + + @Override + public Optional getIcon() { + return Optional.of(IconTheme.JabRefIcons.PROTECT_STRING); + } + + @Override + public String getDescription() { + return Localization.lang("Add {} around selected text"); + } + }; + + private class ProtectSelectionAction extends SimpleCommand { + ProtectSelectionAction() { + this.executable.bind(textInputControl.selectedTextProperty().isNotEmpty()); + } + + @Override + public void execute() { + String selectedText = textInputControl.getSelectedText(); + textInputControl.replaceSelection("{" + selectedText + "}"); + } + } + + private class FormatFieldAction extends SimpleCommand { + FormatFieldAction() { + this.executable.bind(textInputControl.textProperty().isNotEmpty()); + } + + @Override + public void execute() { + textInputControl.setText(FORMATTER.format(textInputControl.getText())); + } + } + + private class AddToProtectedTermsAction extends SimpleCommand { + ProtectedTermsList list; + + public AddToProtectedTermsAction(ProtectedTermsList list) { + Objects.requireNonNull(list); + + this.list = list; + this.executable.bind(textInputControl.selectedTextProperty().isNotEmpty()); + } + + @Override + public void execute() { + list.addProtectedTerm(textInputControl.getSelectedText()); + } + } + + public ProtectedTermsMenu(final TextInputControl textInputControl) { super(Localization.lang("Protect terms")); - this.opener = opener; - MenuItem protectItem = new MenuItem(Localization.lang("Add {} around selected text")); - protectItem.setOnAction(event -> { - String selectedText = opener.getSelectedText(); - if ((selectedText != null) && !selectedText.isEmpty()) { - opener.replaceSelection("{" + selectedText + "}"); - } - }); - - MenuItem formatItem = new MenuItem(Localization.lang("Format field")); - formatItem.setOnAction(event -> opener.setText(FORMATTER.format(opener.getText()))); - - externalFiles = new Menu(Localization.lang("Add selected text to list")); - updateFiles(); - - this.getItems().add(protectItem); - this.getItems().add(externalFiles); - this.getItems().add(new SeparatorMenuItem()); - this.getItems().add(formatItem); + this.textInputControl = textInputControl; + + getItems().addAll(factory.createMenuItem(protectSelectionActionInformation, new ProtectSelectionAction()), + getExternalFilesMenu(), + new SeparatorMenuItem(), + factory.createMenuItem(() -> Localization.lang("Format field"), new FormatFieldAction())); } - private void updateFiles() { - externalFiles.getItems().clear(); - ProtectedTermsLoader loader = Globals.protectedTermsLoader; - List nonInternal = loader - .getProtectedTermsLists().stream() - .filter(list -> !list.isInternalList()) - .collect(Collectors.toList()); - for (ProtectedTermsList list : nonInternal) { - MenuItem fileItem = new MenuItem(list.getDescription()); - fileItem.setOnAction(event -> { - String selectedText = opener.getSelectedText(); - if ((selectedText != null) && !selectedText.isEmpty()) { - list.addProtectedTerm(selectedText); - } - }); - externalFiles.getItems().add(fileItem); + private Menu getExternalFilesMenu() { + Menu protectedTermsMenu = factory.createSubMenu(() -> Localization.lang("Add selected text to list")); + + Globals.protectedTermsLoader.getProtectedTermsLists().stream() + .filter(list -> !list.isInternalList()) + .forEach(list -> protectedTermsMenu.getItems().add( + factory.createMenuItem(list::getDescription, new AddToProtectedTermsAction(list)))); + + if (protectedTermsMenu.getItems().isEmpty()) { + MenuItem emptyItem = new MenuItem(Localization.lang("No list enabled")); + emptyItem.setDisable(true); + protectedTermsMenu.getItems().add(emptyItem); } - externalFiles.getItems().add(new SeparatorMenuItem()); + + return protectedTermsMenu; } } diff --git a/src/main/java/org/jabref/gui/icon/IconTheme.java b/src/main/java/org/jabref/gui/icon/IconTheme.java index c796da67268..0b9876e486b 100644 --- a/src/main/java/org/jabref/gui/icon/IconTheme.java +++ b/src/main/java/org/jabref/gui/icon/IconTheme.java @@ -309,7 +309,8 @@ public enum JabRefIcons implements JabRefIcon { REMOTE_DATABASE(MaterialDesignIcon.DATABASE), HOME(MaterialDesignIcon.HOME), LINK(MaterialDesignIcon.LINK), - LINK_VARIANT(MaterialDesignIcon.LINK_VARIANT); + LINK_VARIANT(MaterialDesignIcon.LINK_VARIANT), + PROTECT_STRING(MaterialDesignIcon.CODE_BRACES); private final JabRefIcon icon; diff --git a/src/main/java/org/jabref/gui/util/component/TemporalAccessorPicker.java b/src/main/java/org/jabref/gui/util/component/TemporalAccessorPicker.java index 1ea42c7cdbc..f134024315b 100644 --- a/src/main/java/org/jabref/gui/util/component/TemporalAccessorPicker.java +++ b/src/main/java/org/jabref/gui/util/component/TemporalAccessorPicker.java @@ -9,12 +9,17 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalQueries; +import java.util.Objects; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.control.ContextMenu; import javafx.scene.control.DatePicker; import javafx.util.StringConverter; +import org.jabref.gui.Globals; +import org.jabref.gui.fieldeditors.TextInputControlBehavior; +import org.jabref.gui.fieldeditors.contextmenu.EditorContextAction; import org.jabref.gui.util.BindingsHelper; /** @@ -34,10 +39,10 @@ * Inspiration taken from https://github.com/edvin/tornadofx-controls/blob/master/src/main/java/tornadofx/control/DateTimePicker.java */ public class TemporalAccessorPicker extends DatePicker { - private ObjectProperty temporalAccessorValue = new SimpleObjectProperty<>(null); + private final ObjectProperty temporalAccessorValue = new SimpleObjectProperty<>(null); - private DateTimeFormatter defaultFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); - private ObjectProperty> converter = new SimpleObjectProperty>(null); + private final DateTimeFormatter defaultFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + private final ObjectProperty> converter = new SimpleObjectProperty<>(null); public TemporalAccessorPicker() { setConverter(new InternalConverter()); @@ -46,6 +51,12 @@ public TemporalAccessorPicker() { BindingsHelper.bindBidirectional(valueProperty(), temporalAccessorValue, TemporalAccessorPicker::addCurrentTime, TemporalAccessorPicker::getDate); + + getEditor().setOnContextMenuRequested(event -> { + ContextMenu contextMenu = new ContextMenu(); + contextMenu.getItems().setAll(EditorContextAction.getDefaultContextMenuItems(getEditor(), Globals.getKeyPrefs())); + TextInputControlBehavior.showContextMenu(getEditor(), contextMenu, event); + }); } private static TemporalAccessor addCurrentTime(LocalDate date) { @@ -82,22 +93,17 @@ public final ObjectProperty> stringConverterPr } public final StringConverter getStringConverter() { - StringConverter converter = stringConverterProperty().get(); - if (converter != null) { - return converter; - } else { - return new StringConverter() { - @Override - public String toString(TemporalAccessor value) { - return defaultFormatter.format(value); - } - - @Override - public TemporalAccessor fromString(String value) { - return LocalDateTime.parse(value, defaultFormatter); - } - }; - } + return Objects.requireNonNullElseGet(stringConverterProperty().get(), () -> new StringConverter<>() { + @Override + public String toString(TemporalAccessor value) { + return defaultFormatter.format(value); + } + + @Override + public TemporalAccessor fromString(String value) { + return LocalDateTime.parse(value, defaultFormatter); + } + }); } public final void setStringConverter(StringConverter value) { diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 7fa46947f7c..b7cf4e58008 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2253,6 +2253,9 @@ Reveal\ in\ file\ explorer=Reveal in file explorer Autolink\ files=Autolink files +No\ list\ enabled=No list enabled +Protect\ selection=Protect selection + Customized\ preview\ style=Customized preview style Next\ preview\ style=Next preview style Previous\ preview\ style=Previous preview style