diff --git a/CHANGELOG.md b/CHANGELOG.md index 76e1d0e6cb8..a33cc1ffc7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,7 +70,8 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - We moved the dropdown menu for selecting the push-application from the toolbar into the external application preferences. [#674](https://github.com/JabRef/jabref/issues/674) - We removed the alphabetical ordering of the custom tabs and updated the error message when trying to create a general field with a name containing an illegal character. [#5019](https://github.com/JabRef/jabref/issues/5019) - We added a context menu to the bib(la)tex-source-editor to copy'n'paste. [#5007](https://github.com/JabRef/jabref/pull/5007) -- We added a bibliographic references search, for finding references in several LaTeX files. This tool scans directories and shows which entries are used, how many times and where. +- We added a tool that allows searching for citations in LaTeX files. It scans directories and shows which entries are used, how many times and where. +- We added a 'LaTeX citations' tab to the entry editor, to search for citations to the active entry in the LaTeX file directory. It can be disabled in the preferences dialog. - We added an option in preferences to allow for integers in field "edition" when running database in bibtex mode. [#4680](https://github.com/JabRef/jabref/issues/4680) diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/BasePanel.java index d64f2226ff0..34bd8089063 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/BasePanel.java @@ -182,7 +182,7 @@ public BasePanel(JabRefFrame frame, BasePanelPreferences preferences, BibDatabas this.getDatabase().registerListener(new UpdateTimestampListener(Globals.prefs)); - this.entryEditor = new EntryEditor(this, preferences.getEntryEditorPreferences(), Globals.getFileUpdateMonitor(), dialogService, externalFileTypes, Globals.TASK_EXECUTOR, Globals.stateManager); + this.entryEditor = new EntryEditor(this, externalFileTypes); this.preview = new PreviewPanel(getBibDatabaseContext(), this, dialogService, externalFileTypes, Globals.getKeyPrefs(), preferences.getPreviewPreferences()); } diff --git a/src/main/java/org/jabref/gui/actions/StandardActions.java b/src/main/java/org/jabref/gui/actions/StandardActions.java index 159efec0e1f..09f06684fdf 100644 --- a/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -85,7 +85,7 @@ public enum StandardActions implements Action { TOOGLE_OO(Localization.lang("OpenOffice/LibreOffice"), IconTheme.JabRefIcons.FILE_OPENOFFICE, KeyBinding.OPEN_OPEN_OFFICE_LIBRE_OFFICE_CONNECTION), TOGGLE_WEB_SEARCH(Localization.lang("Web search"), Localization.lang("Toggle web search interface"), IconTheme.JabRefIcons.WWW, KeyBinding.WEB_SEARCH), - PARSE_TEX(Localization.lang("Search for Citations in LaTeX Files"), IconTheme.JabRefIcons.APPLICATION_TEXSTUDIO), + PARSE_TEX(Localization.lang("Search for Citations in LaTeX Files"), IconTheme.JabRefIcons.LATEX_CITATIONS), NEW_SUB_LIBRARY_FROM_AUX(Localization.lang("New sublibrary based on AUX file") + "...", Localization.lang("New BibTeX sublibrary") + Localization.lang("This feature generates a new library based on which entries are needed in an existing LaTeX document."), IconTheme.JabRefIcons.NEW), WRITE_XMP(Localization.lang("Write XMP-metadata to PDFs"), Localization.lang("Will write XMP-metadata to the PDFs linked from selected entries."), KeyBinding.WRITE_XMP), OPEN_FOLDER(Localization.lang("Open folder"), Localization.lang("Open folder"), KeyBinding.OPEN_FOLDER), diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index b7a875655e8..37a77ac9aee 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -9,6 +9,8 @@ import java.util.Optional; import java.util.stream.Collectors; +import javax.inject.Inject; + import javafx.fxml.FXML; import javafx.geometry.Side; import javafx.scene.control.Button; @@ -22,7 +24,6 @@ import javafx.scene.input.TransferMode; import javafx.scene.layout.BorderPane; -import org.jabref.Globals; import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.GUIGlobals; @@ -46,6 +47,7 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.util.FileUpdateMonitor; +import org.jabref.preferences.PreferencesService; import com.airhacks.afterburner.views.ViewLoader; import org.fxmisc.easybind.EasyBind; @@ -54,63 +56,54 @@ import org.slf4j.LoggerFactory; /** - * GUI component that allows editing of the fields of a BibEntry (i.e. the - * one that shows up, when you double click on an entry in the table) + * GUI component that allows editing of the fields of a BibEntry (i.e. the one that shows up, when you double click on + * an entry in the table) *

* It hosts the tabs (required, general, optional) and the buttons to the left. *

- * EntryEditor also registers itself to the event bus, receiving - * events whenever a field of the entry changes, enabling the text fields to - * update themselves if the change is made from somewhere else. + * EntryEditor also registers itself to the event bus, receiving events whenever a field of the entry changes, enabling + * the text fields to update themselves if the change is made from somewhere else. */ public class EntryEditor extends BorderPane { private static final Logger LOGGER = LoggerFactory.getLogger(EntryEditor.class); - private final BibDatabaseContext databaseContext; - private final CountingUndoManager undoManager; private final BasePanel panel; - private Subscription typeSubscription; + private final BibDatabaseContext databaseContext; + private final EntryEditorPreferences entryEditorPreferences; + private final ExternalFilesEntryLinker fileLinker; private final List tabs; - private final FileUpdateMonitor fileMonitor; - /** - * A reference to the entry this editor works on. - */ - private BibEntry entry; + private Subscription typeSubscription; + private BibEntry entry; // A reference to the entry this editor works on. private SourceTab sourceTab; - @FXML private TabPane tabbed; @FXML private Button typeChangeButton; @FXML private Button fetcherButton; @FXML private Label typeLabel; - - private final EntryEditorPreferences preferences; - private final DialogService dialogService; - private final ExternalFilesEntryLinker fileLinker; - private final TaskExecutor taskExecutor; - private final StateManager stateManager; - - public EntryEditor(BasePanel panel, EntryEditorPreferences preferences, FileUpdateMonitor fileMonitor, DialogService dialogService, ExternalFileTypes externalFileTypes, TaskExecutor taskExecutor, StateManager stateManager) { + @Inject private DialogService dialogService; + @Inject private TaskExecutor taskExecutor; + @Inject private PreferencesService preferencesService; + @Inject private StateManager stateManager; + @Inject private FileUpdateMonitor fileMonitor; + @Inject private CountingUndoManager undoManager; + + public EntryEditor(BasePanel panel, ExternalFileTypes externalFileTypes) { this.panel = panel; this.databaseContext = panel.getBibDatabaseContext(); - this.undoManager = panel.getUndoManager(); - this.preferences = Objects.requireNonNull(preferences); - this.fileMonitor = fileMonitor; - this.dialogService = dialogService; - this.taskExecutor = taskExecutor; - this.stateManager = stateManager; - - fileLinker = new ExternalFilesEntryLinker(externalFileTypes, Globals.prefs.getFilePreferences(), databaseContext); ViewLoader.view(this) .root(this) .load(); + this.entryEditorPreferences = preferencesService.getEntryEditorPreferences(); + this.fileLinker = new ExternalFilesEntryLinker(externalFileTypes, preferencesService.getFilePreferences(), + databaseContext); + if (GUIGlobals.currentFont != null) { - setStyle( - "text-area-background: " + ColorUtil.toHex(GUIGlobals.validFieldBackgroundColor) + ";" - + "text-area-foreground: " + ColorUtil.toHex(GUIGlobals.editorTextColor) + ";" - + "text-area-highlight: " + ColorUtil.toHex(GUIGlobals.activeBackgroundColor) + ";"); + setStyle(String.format("text-area-background: %s;text-area-foreground: %s;text-area-highlight: %s;", + ColorUtil.toHex(GUIGlobals.validFieldBackgroundColor), + ColorUtil.toHex(GUIGlobals.editorTextColor), + ColorUtil.toHex(GUIGlobals.activeBackgroundColor))); } EasyBind.subscribe(tabbed.getSelectionModel().selectedItemProperty(), tab -> { @@ -122,7 +115,7 @@ public EntryEditor(BasePanel panel, EntryEditorPreferences preferences, FileUpda setupKeyBindings(); - tabs = createTabs(); + this.tabs = createTabs(); this.setOnDragOver(event -> { if (event.getDragboard().hasFiles()) { @@ -134,60 +127,65 @@ public EntryEditor(BasePanel panel, EntryEditorPreferences preferences, FileUpda this.setOnDragDropped(event -> { BibEntry entry = this.getEntry(); boolean success = false; + if (event.getDragboard().hasContent(DataFormat.FILES)) { List files = event.getDragboard().getFiles().stream().map(File::toPath).collect(Collectors.toList()); - - FileDragDropPreferenceType dragDropPreferencesType = Globals.prefs.getEntryEditorFileLinkPreference(); + FileDragDropPreferenceType dragDropPreferencesType = preferencesService.getEntryEditorFileLinkPreference(); if (dragDropPreferencesType == FileDragDropPreferenceType.MOVE) { - if (event.getTransferMode() == TransferMode.LINK) //alt on win - { + if (event.getTransferMode() == TransferMode.LINK) { + // Alt on Windows LOGGER.debug("Mode LINK"); fileLinker.addFilesToEntry(entry, files); - } else if (event.getTransferMode() == TransferMode.COPY) //ctrl on win, no modifier on Xubuntu - { + } else if (event.getTransferMode() == TransferMode.COPY) { + // Ctrl on Windows, no modifier on Xubuntu LOGGER.debug("Mode COPY"); fileLinker.copyFilesToFileDirAndAddToEntry(entry, files); } else { - LOGGER.debug("Mode MOVE"); //shift on win or no modifier + // Shift on Windows or no modifier + LOGGER.debug("Mode MOVE"); fileLinker.moveFilesToFileDirAndAddToEntry(entry, files); } + success = true; } if (dragDropPreferencesType == FileDragDropPreferenceType.COPY) { - if (event.getTransferMode() == TransferMode.COPY) //ctrl on win, no modifier on Xubuntu - { + if (event.getTransferMode() == TransferMode.COPY) { + // Ctrl on Windows, no modifier on Xubuntu LOGGER.debug("Mode MOVE"); fileLinker.moveFilesToFileDirAndAddToEntry(entry, files); - } else if (event.getTransferMode() == TransferMode.LINK) //alt on win - { + } else if (event.getTransferMode() == TransferMode.LINK) { + // Alt on Windows LOGGER.debug("Mode LINK"); fileLinker.addFilesToEntry(entry, files); } else { - LOGGER.debug("Mode COPY"); //shift on win or no modifier + // Shift on Windows or no modifier + LOGGER.debug("Mode COPY"); fileLinker.copyFilesToFileDirAndAddToEntry(entry, files); } + success = true; } if (dragDropPreferencesType == FileDragDropPreferenceType.LINK) { - if (event.getTransferMode() == TransferMode.COPY) //ctrl on win, no modifier on Xubuntu - { + if (event.getTransferMode() == TransferMode.COPY) { + // Ctrl on Windows, no modifier on Xubuntu LOGGER.debug("Mode COPY"); fileLinker.copyFilesToFileDirAndAddToEntry(entry, files); - } else if (event.getTransferMode() == TransferMode.LINK) //alt on win - { + } else if (event.getTransferMode() == TransferMode.LINK) { + // Alt on Windows LOGGER.debug("Mode MOVE"); fileLinker.moveFilesToFileDirAndAddToEntry(entry, files); } else { - LOGGER.debug("Mode LINK"); //shift on win or no modifier + // Shift on Windows or no modifier + LOGGER.debug("Mode LINK"); fileLinker.addFilesToEntry(entry, files); } + success = true; } } event.setDropCompleted(success); event.consume(); - }); } @@ -196,7 +194,7 @@ public EntryEditor(BasePanel panel, EntryEditorPreferences preferences, FileUpda */ private void setupKeyBindings() { this.addEventHandler(KeyEvent.KEY_PRESSED, event -> { - Optional keyBinding = preferences.getKeyBindings().mapToKeyBinding(event); + Optional keyBinding = entryEditorPreferences.getKeyBindings().mapToKeyBinding(event); if (keyBinding.isPresent()) { switch (keyBinding.get()) { case ENTRY_EDITOR_NEXT_PANEL: @@ -222,9 +220,6 @@ private void setupKeyBindings() { event.consume(); break; case CLOSE: - close(); - event.consume(); - break; case CLOSE_ENTRY: close(); event.consume(); @@ -248,7 +243,8 @@ private void deleteEntry() { @FXML void generateCiteKeyButton() { - GenerateBibtexKeySingleAction action = new GenerateBibtexKeySingleAction(getEntry(), databaseContext, dialogService, preferences, undoManager); + GenerateBibtexKeySingleAction action = new GenerateBibtexKeySingleAction(getEntry(), databaseContext, + dialogService, entryEditorPreferences, undoManager); action.execute(); } @@ -266,29 +262,37 @@ private List createTabs() { List tabs = new LinkedList<>(); // Required fields - tabs.add(new RequiredFieldsTab(panel.getBibDatabaseContext(), panel.getSuggestionProviders(), undoManager, dialogService)); + tabs.add(new RequiredFieldsTab(databaseContext, panel.getSuggestionProviders(), undoManager, dialogService)); // Optional fields - tabs.add(new OptionalFieldsTab(panel.getBibDatabaseContext(), panel.getSuggestionProviders(), undoManager, dialogService)); - tabs.add(new OptionalFields2Tab(panel.getBibDatabaseContext(), panel.getSuggestionProviders(), undoManager, dialogService)); - tabs.add(new DeprecatedFieldsTab(panel.getBibDatabaseContext(), panel.getSuggestionProviders(), undoManager, dialogService)); + tabs.add(new OptionalFieldsTab(databaseContext, panel.getSuggestionProviders(), undoManager, dialogService)); + tabs.add(new OptionalFields2Tab(databaseContext, panel.getSuggestionProviders(), undoManager, dialogService)); + tabs.add(new DeprecatedFieldsTab(databaseContext, panel.getSuggestionProviders(), undoManager, dialogService)); // Other fields - tabs.add(new OtherFieldsTab(panel.getBibDatabaseContext(), panel.getSuggestionProviders(), undoManager, preferences.getCustomTabFieldNames(), dialogService)); + tabs.add(new OtherFieldsTab(databaseContext, panel.getSuggestionProviders(), undoManager, + entryEditorPreferences.getCustomTabFieldNames(), dialogService)); // General fields from preferences - for (Map.Entry> tab : preferences.getEntryEditorTabList().entrySet()) { - tabs.add(new UserDefinedFieldsTab(tab.getKey(), tab.getValue(), panel.getBibDatabaseContext(), panel.getSuggestionProviders(), undoManager, dialogService)); + for (Map.Entry> tab : entryEditorPreferences.getEntryEditorTabList().entrySet()) { + tabs.add(new UserDefinedFieldsTab(tab.getKey(), tab.getValue(), databaseContext, + panel.getSuggestionProviders(), undoManager, dialogService)); } // Special tabs tabs.add(new MathSciNetTab()); tabs.add(new FileAnnotationTab(panel.getAnnotationCache())); - tabs.add(new RelatedArticlesTab(this, preferences, dialogService)); + tabs.add(new RelatedArticlesTab(this, entryEditorPreferences, dialogService)); // Source tab - sourceTab = new SourceTab(databaseContext, undoManager, preferences.getLatexFieldFormatterPreferences(), preferences.getImportFormatPreferences(), fileMonitor, dialogService, stateManager); + sourceTab = new SourceTab(databaseContext, undoManager, + entryEditorPreferences.getLatexFieldFormatterPreferences(), + entryEditorPreferences.getImportFormatPreferences(), fileMonitor, dialogService, stateManager); tabs.add(sourceTab); + + // LaTeX citations tab + tabs.add(new LatexCitationsTab(databaseContext, preferencesService, taskExecutor)); + return tabs; } @@ -309,6 +313,7 @@ private void recalculateVisibleTabs() { for (int i = 0; i < visibleTabs.size(); i++) { Tab toBeAdded = visibleTabs.get(i); Tab shown = null; + if (i < tabbed.getTabs().size()) { shown = tabbed.getTabs().get(i); } @@ -340,7 +345,7 @@ public void setEntry(BibEntry entry) { this.entry = entry; recalculateVisibleTabs(); - if (preferences.showSourceTabByDefault()) { + if (entryEditorPreferences.showSourceTabByDefault()) { tabbed.getSelectionModel().select(sourceTab); } @@ -370,9 +375,10 @@ private void setupToolBar() { ContextMenu typeMenu = new ChangeEntryTypeMenu().getChangeEntryTypePopupMenu(entry, databaseContext, undoManager); typeLabel.setOnMouseClicked(event -> typeMenu.show(typeLabel, Side.RIGHT, 0, 0)); typeChangeButton.setOnMouseClicked(event -> typeMenu.show(typeChangeButton, Side.RIGHT, 0, 0)); + // Add menu for fetching bibliographic information ContextMenu fetcherMenu = new ContextMenu(); - for (EntryBasedFetcher fetcher : WebFetchers.getEntryBasedFetchers(preferences.getImportFormatPreferences())) { + for (EntryBasedFetcher fetcher : WebFetchers.getEntryBasedFetchers(entryEditorPreferences.getImportFormatPreferences())) { MenuItem fetcherMenuItem = new MenuItem(fetcher.getName()); fetcherMenuItem.setOnAction(event -> fetchAndMerge(fetcher)); fetcherMenu.getItems().add(fetcherMenuItem); diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java index ed059ab9f7f..40f7a26cd9c 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java @@ -20,8 +20,9 @@ public class EntryEditorPreferences { private boolean showSourceTabByDefault; private final KeyBindingRepository keyBindings; private boolean avoidOverwritingCiteKey; + private final boolean shouldShowLatexCitationsTab; - public EntryEditorPreferences(Map> entryEditorTabList, LatexFieldFormatterPreferences latexFieldFormatterPreferences, ImportFormatPreferences importFormatPreferences, List customTabFieldNames, boolean shouldShowRecommendationsTab, boolean isMrdlibAccepted, boolean showSourceTabByDefault, BibtexKeyPatternPreferences bibtexKeyPatternPreferences, KeyBindingRepository keyBindings, boolean avoidOverwritingCiteKey) { + public EntryEditorPreferences(Map> entryEditorTabList, LatexFieldFormatterPreferences latexFieldFormatterPreferences, ImportFormatPreferences importFormatPreferences, List customTabFieldNames, boolean shouldShowRecommendationsTab, boolean isMrdlibAccepted, boolean shouldShowLatexCitationsTab, boolean showSourceTabByDefault, BibtexKeyPatternPreferences bibtexKeyPatternPreferences, KeyBindingRepository keyBindings, boolean avoidOverwritingCiteKey) { this.entryEditorTabList = entryEditorTabList; this.latexFieldFormatterPreferences = latexFieldFormatterPreferences; this.importFormatPreferences = importFormatPreferences; @@ -32,6 +33,7 @@ public EntryEditorPreferences(Map> entryEditorTabList, Late this.bibtexKeyPatternPreferences = bibtexKeyPatternPreferences; this.keyBindings = keyBindings; this.avoidOverwritingCiteKey = avoidOverwritingCiteKey; + this.shouldShowLatexCitationsTab = shouldShowLatexCitationsTab; } public Map> getEntryEditorTabList() { @@ -78,6 +80,10 @@ public void setShowSourceTabByDefault(boolean showSourceTabByDefault) { this.showSourceTabByDefault = showSourceTabByDefault; } + public boolean shouldShowLatexCitationsTab() { + return shouldShowLatexCitationsTab; + } + public boolean avoidOverwritingCiteKey() { return avoidOverwritingCiteKey; } diff --git a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java new file mode 100644 index 00000000000..1e36be7b54a --- /dev/null +++ b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java @@ -0,0 +1,122 @@ +package org.jabref.gui.entryeditor; + +import java.nio.file.Path; +import java.util.Optional; +import java.util.stream.Collectors; + +import javafx.scene.control.ProgressIndicator; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; + +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.preferences.PreferencesService; + +import org.fxmisc.easybind.EasyBind; + +public class LatexCitationsTab extends EntryEditorTab { + + private final LatexCitationsTabViewModel viewModel; + private final StackPane searchPane; + private final ProgressIndicator progressIndicator; + + public LatexCitationsTab(BibDatabaseContext databaseContext, PreferencesService preferencesService, + TaskExecutor taskExecutor) { + this.viewModel = new LatexCitationsTabViewModel(databaseContext, preferencesService, taskExecutor); + this.searchPane = new StackPane(); + this.progressIndicator = new ProgressIndicator(); + + setText(Localization.lang("LaTeX Citations")); + setTooltip(new Tooltip(Localization.lang("Search citations for this entry in LaTeX files"))); + setGraphic(IconTheme.JabRefIcons.LATEX_CITATIONS.getGraphicNode()); + setSearchPane(); + } + + private void setSearchPane() { + progressIndicator.setMaxSize(100, 100); + searchPane.getStyleClass().add("related-articles-tab"); + + setContent(searchPane); + + EasyBind.subscribe(viewModel.statusProperty(), status -> { + switch (status) { + case IN_PROGRESS: + searchPane.getChildren().setAll(progressIndicator); + break; + case CITATIONS_FOUND: + searchPane.getChildren().setAll(getCitationsPane()); + break; + case NO_RESULTS: + searchPane.getChildren().setAll(getNotFoundPane()); + break; + case ERROR: + searchPane.getChildren().setAll(getErrorPane()); + break; + } + }); + } + + private ScrollPane getCitationsPane() { + Text titleText = new Text(Localization.lang("Citations found")); + titleText.getStyleClass().add("recommendation-heading"); + + VBox citationsBox = new VBox(20, titleText); + Path basePath = viewModel.directoryProperty().get(); + citationsBox.getChildren().addAll(viewModel.getCitationList().stream().map( + citation -> citation.getDisplayGraphic(basePath, Optional.empty())).collect(Collectors.toList())); + + ScrollPane citationsPane = new ScrollPane(); + citationsPane.setContent(citationsBox); + + return citationsPane; + } + + private ScrollPane getNotFoundPane() { + Text notFoundTitleText = new Text(Localization.lang("No citations found")); + notFoundTitleText.getStyleClass().add("recommendation-heading"); + + Text notFoundText = new Text(Localization.lang("No LaTeX files containing this entry were found.")); + notFoundText.setStyle("-fx-font-size: 110%"); + + Text notFoundAdviceText = new Text(Localization.lang( + "You can set the LaTeX file directory in the 'Library properties' dialog.")); + notFoundAdviceText.setStyle("-fx-font-weight: bold;"); + + VBox notFoundBox = new VBox(20, notFoundTitleText, notFoundText, notFoundAdviceText); + ScrollPane notFoundPane = new ScrollPane(); + notFoundPane.setContent(notFoundBox); + + return notFoundPane; + } + + private ScrollPane getErrorPane() { + Text errorTitleText = new Text(Localization.lang("Error")); + errorTitleText.setStyle("-fx-fill: -fx-accent;"); + errorTitleText.getStyleClass().add("recommendation-heading"); + + Text errorMessageText = new Text(viewModel.searchErrorProperty().get()); + errorMessageText.setStyle("-fx-font-family: monospace;-fx-font-size: 120%;"); + + VBox errorBox = new VBox(20, errorTitleText, errorMessageText); + ScrollPane errorPane = new ScrollPane(); + errorPane.setContent(errorBox); + + return errorPane; + } + + @Override + protected void bindToEntry(BibEntry entry) { + viewModel.init(entry); + } + + @Override + public boolean shouldShow(BibEntry entry) { + return viewModel.shouldShow(); + } +} diff --git a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java new file mode 100644 index 00000000000..239027f68cf --- /dev/null +++ b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java @@ -0,0 +1,134 @@ +package org.jabref.gui.entryeditor; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Future; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyListWrapper; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import org.jabref.gui.AbstractViewModel; +import org.jabref.gui.texparser.CitationViewModel; +import org.jabref.gui.util.BackgroundTask; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.texparser.DefaultTexParser; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.texparser.TexParserResult; +import org.jabref.preferences.PreferencesService; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LatexCitationsTabViewModel extends AbstractViewModel { + + enum Status { + IN_PROGRESS, + CITATIONS_FOUND, + NO_RESULTS, + ERROR + } + + private static final Logger LOGGER = LoggerFactory.getLogger(LatexCitationsTabViewModel.class); + private static final String TEX_EXT = ".tex"; + private final BibDatabaseContext databaseContext; + private final PreferencesService preferencesService; + private final TaskExecutor taskExecutor; + private final ObjectProperty directory; + private final ObservableList citationList; + private final ObjectProperty status; + private final StringProperty searchError; + private Future searchTask; + + public LatexCitationsTabViewModel(BibDatabaseContext databaseContext, PreferencesService preferencesService, + TaskExecutor taskExecutor) { + this.databaseContext = databaseContext; + this.preferencesService = preferencesService; + this.taskExecutor = taskExecutor; + this.directory = new SimpleObjectProperty<>(null); + this.citationList = FXCollections.observableArrayList(); + this.status = new SimpleObjectProperty<>(Status.IN_PROGRESS); + this.searchError = new SimpleStringProperty(""); + } + + public void init(BibEntry entry) { + cancelSearch(); + + Optional citeKey = entry.getCiteKeyOptional(); + if (citeKey.isPresent()) { + startSearch(citeKey.get()); + } else { + searchError.set(Localization.lang("Selected entry does not have an associated BibTeX key.")); + status.set(Status.ERROR); + } + } + + public ObjectProperty directoryProperty() { + return directory; + } + + public ObservableList getCitationList() { + return new ReadOnlyListWrapper<>(citationList); + } + + public ObjectProperty statusProperty() { + return status; + } + + public StringProperty searchErrorProperty() { + return searchError; + } + + private void startSearch(String citeKey) { + searchTask = BackgroundTask.wrap(() -> searchAndParse(citeKey)) + .onRunning(() -> status.set(Status.IN_PROGRESS)) + .onSuccess(status::set) + .onFailure(error -> { + searchError.set(String.format("%s%n%n%s", error.getMessage(), error.getCause())); + status.set(Status.ERROR); + }) + .executeWith(taskExecutor); + } + + private void cancelSearch() { + if (searchTask == null || searchTask.isCancelled() || searchTask.isDone()) { + return; + } + + status.set(Status.IN_PROGRESS); + searchTask.cancel(true); + } + + private Status searchAndParse(String citeKey) throws IOException { + directory.set(databaseContext.getMetaData().getLaTexFileDirectory(preferencesService.getUser()) + .orElseGet(preferencesService::getWorkingDir)); + List texFiles; + try (Stream filesStream = Files.walk(directory.get())) { + texFiles = filesStream.filter(path -> path.toFile().isFile() && path.toString().endsWith(TEX_EXT)) + .collect(Collectors.toList()); + } catch (IOException e) { + LOGGER.error("Error searching files", e); + throw new IOException("Error searching files", e); + } + + TexParserResult texParserResult = new DefaultTexParser().parse(citeKey, texFiles); + citationList.setAll(texParserResult.getCitations().values().stream().map(CitationViewModel::new).collect(Collectors.toList())); + + return citationList.isEmpty() ? Status.NO_RESULTS : Status.CITATIONS_FOUND; + } + + public boolean shouldShow() { + return preferencesService.getEntryEditorPreferences().shouldShowLatexCitationsTab(); + } +} diff --git a/src/main/java/org/jabref/gui/icon/IconTheme.java b/src/main/java/org/jabref/gui/icon/IconTheme.java index e24a8e5d80a..e7d26a338b0 100644 --- a/src/main/java/org/jabref/gui/icon/IconTheme.java +++ b/src/main/java/org/jabref/gui/icon/IconTheme.java @@ -316,7 +316,8 @@ public enum JabRefIcons implements JabRefIcon { OWNER(MaterialDesignIcon.ACCOUNT), CLOSE_JABREF(MaterialDesignIcon.GLASSDOOR), ARTICLE(MaterialDesignIcon.FILE_DOCUMENT), - BOOK(MaterialDesignIcon.BOOK_OPEN_PAGE_VARIANT); + BOOK(MaterialDesignIcon.BOOK_OPEN_PAGE_VARIANT), + LATEX_CITATIONS(JabRefMaterialDesignIcon.TEX_STUDIO); private final JabRefIcon icon; diff --git a/src/main/java/org/jabref/gui/preferences/EntryEditorPrefsTab.java b/src/main/java/org/jabref/gui/preferences/EntryEditorPrefsTab.java index 4b7b840306c..143f9871bd1 100644 --- a/src/main/java/org/jabref/gui/preferences/EntryEditorPrefsTab.java +++ b/src/main/java/org/jabref/gui/preferences/EntryEditorPrefsTab.java @@ -30,6 +30,7 @@ class EntryEditorPrefsTab extends Pane implements PrefsTab { private final CheckBox autoComplete; private final CheckBox recommendations; private final CheckBox acceptRecommendations; + private final CheckBox latexCitations; private final CheckBox validation; private final RadioButton autoCompBoth; private final RadioButton autoCompFF; @@ -60,6 +61,7 @@ public EntryEditorPrefsTab(JabRefPreferences prefs) { autoComplete = new CheckBox(Localization.lang("Enable word/name autocompletion")); recommendations = new CheckBox(Localization.lang("Show 'Related Articles' tab")); acceptRecommendations = new CheckBox(Localization.lang("Accept recommendations from Mr. DLib")); + latexCitations = new CheckBox(Localization.lang("Show 'LaTeX Citations' tab")); validation = new CheckBox(Localization.lang("Show validation messages")); // allowed name formats @@ -96,8 +98,9 @@ public EntryEditorPrefsTab(JabRefPreferences prefs) { builder.add(emacsRebindCtrlF, 1, 6); builder.add(recommendations, 1, 7); builder.add(acceptRecommendations, 1, 8); - builder.add(validation, 1, 9); - builder.add(new Label(""), 1, 10); + builder.add(latexCitations, 1, 9); + builder.add(validation, 1, 10); + builder.add(new Label(""), 1, 11); builder.add(new Separator(), 1, 13); @@ -189,6 +192,7 @@ public void setValues() { emacsRebindCtrlF.setSelected(prefs.getBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS_REBIND_CF)); recommendations.setSelected(prefs.getBoolean(JabRefPreferences.SHOW_RECOMMENDATIONS)); acceptRecommendations.setSelected(prefs.getBoolean(JabRefPreferences.ACCEPT_RECOMMENDATIONS)); + latexCitations.setSelected(prefs.getBoolean(JabRefPreferences.SHOW_LATEX_CITATIONS)); autoComplete.setSelected(autoCompletePreferences.shouldAutoComplete()); autoCompFields.setText(autoCompletePreferences.getCompleteNamesAsString()); @@ -235,6 +239,7 @@ public void storeSettings() { prefs.putBoolean(JabRefPreferences.DEFAULT_SHOW_SOURCE, defSource.isSelected()); prefs.putBoolean(JabRefPreferences.SHOW_RECOMMENDATIONS, recommendations.isSelected()); prefs.putBoolean(JabRefPreferences.ACCEPT_RECOMMENDATIONS, acceptRecommendations.isSelected()); + prefs.putBoolean(JabRefPreferences.SHOW_LATEX_CITATIONS, latexCitations.isSelected()); prefs.putBoolean(JabRefPreferences.VALIDATE_IN_ENTRY_EDITOR, validation.isSelected()); boolean emacsModeChanged = prefs.getBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS) != emacsMode.isSelected(); boolean emacsRebindCtrlAChanged = prefs.getBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS_REBIND_CA) != emacsRebindCtrlA.isSelected(); diff --git a/src/main/java/org/jabref/gui/texparser/CitationViewModel.java b/src/main/java/org/jabref/gui/texparser/CitationViewModel.java new file mode 100644 index 00000000000..fd2aa15b99c --- /dev/null +++ b/src/main/java/org/jabref/gui/texparser/CitationViewModel.java @@ -0,0 +1,59 @@ +package org.jabref.gui.texparser; + +import java.nio.file.Path; +import java.util.Optional; + +import javafx.scene.Node; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; + +import org.jabref.model.strings.LatexToUnicodeAdapter; +import org.jabref.model.texparser.Citation; + +public class CitationViewModel { + + private final Citation citation; + + public CitationViewModel(Citation citation) { + this.citation = citation; + } + + public Path getPath() { + return citation.getPath(); + } + + public int getLine() { + return citation.getLine(); + } + + public int getColStart() { + return citation.getColStart(); + } + + public int getColEnd() { + return citation.getColEnd(); + } + + public String getContext() { + return citation.getContext(); + } + + public Node getDisplayGraphic(Path basePath, Optional wrappingWidth) { + Text contextText = new Text(LatexToUnicodeAdapter.format(getContext())); + wrappingWidth.ifPresent(contextText::setWrappingWidth); + + HBox contextBox = new HBox(contextText); + contextBox.setStyle("-fx-border-color: grey;-fx-border-insets: 10 0;-fx-border-style: dashed;-fx-border-width: 2;-fx-font-size: 110%;-fx-padding: 10;-fx-pref-width: 500"); + + Text positionText = new Text(String.format("%s (%s:%s-%s)", basePath.relativize(getPath()), getLine(), getColStart(), getColEnd())); + positionText.setStyle("-fx-font-family: monospace;"); + + return new VBox(contextBox, positionText); + } + + @Override + public String toString() { + return String.format("CitationViewModel{citation=%s}", this.citation); + } +} diff --git a/src/main/java/org/jabref/gui/texparser/FileNodeViewModel.java b/src/main/java/org/jabref/gui/texparser/FileNodeViewModel.java index 63c743df3fc..2ce06b0948b 100644 --- a/src/main/java/org/jabref/gui/texparser/FileNodeViewModel.java +++ b/src/main/java/org/jabref/gui/texparser/FileNodeViewModel.java @@ -1,14 +1,14 @@ package org.jabref.gui.texparser; import java.nio.file.Path; -import java.util.StringJoiner; +import javafx.beans.property.ReadOnlyListWrapper; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import org.jabref.logic.l10n.Localization; -class FileNodeViewModel { +public class FileNodeViewModel { private final Path path; private final ObservableList children; @@ -16,14 +16,18 @@ class FileNodeViewModel { public FileNodeViewModel(Path path) { this.path = path; - this.fileCount = 0; this.children = FXCollections.observableArrayList(); + this.fileCount = 0; } public Path getPath() { return path; } + public ObservableList getChildren() { + return new ReadOnlyListWrapper<>(children); + } + public int getFileCount() { return fileCount; } @@ -32,10 +36,6 @@ public void setFileCount(int fileCount) { this.fileCount = fileCount; } - public ObservableList getChildren() { - return children; - } - /** * Return a string for displaying a node name (and its number of children if it is a directory). */ @@ -49,10 +49,9 @@ public String getDisplayText() { @Override public String toString() { - return new StringJoiner(", ", FileNodeViewModel.class.getSimpleName() + "[", "]") - .add("path=" + path) - .add("fileCount=" + fileCount) - .add("children=" + children) - .toString(); + return String.format("FileNodeViewModel{path=%s, children=%s, fileCount=%s}", + this.path, + this.children, + this.fileCount); } } diff --git a/src/main/java/org/jabref/gui/texparser/ParseTexAction.java b/src/main/java/org/jabref/gui/texparser/ParseTexAction.java index b057f040ab2..0ae73eab70f 100644 --- a/src/main/java/org/jabref/gui/texparser/ParseTexAction.java +++ b/src/main/java/org/jabref/gui/texparser/ParseTexAction.java @@ -1,18 +1,18 @@ package org.jabref.gui.texparser; import org.jabref.gui.StateManager; +import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; import org.jabref.model.database.BibDatabaseContext; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; - public class ParseTexAction extends SimpleCommand { private final StateManager stateManager; public ParseTexAction(StateManager stateManager) { this.stateManager = stateManager; - this.executable.bind(needsDatabase(stateManager)); + + executable.bind(ActionHelper.needsDatabase(stateManager)); } @Override diff --git a/src/main/java/org/jabref/gui/texparser/ParseTexDialogView.java b/src/main/java/org/jabref/gui/texparser/ParseTexDialogView.java index 0e0fc98100a..cae6ddabac0 100644 --- a/src/main/java/org/jabref/gui/texparser/ParseTexDialogView.java +++ b/src/main/java/org/jabref/gui/texparser/ParseTexDialogView.java @@ -48,11 +48,9 @@ public ParseTexDialogView(BibDatabaseContext databaseContext) { this.databaseContext = databaseContext; this.validationVisualizer = new ControlsFxVisualizer(); - this.setTitle(Localization.lang("Search for Citations in LaTeX Files")); + setTitle(Localization.lang("Search for Citations in LaTeX Files")); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); ControlHelper.setAction(parseButtonType, getDialogPane(), event -> viewModel.parseButtonClicked()); Button parseButton = (Button) getDialogPane().lookupButton(parseButtonType); diff --git a/src/main/java/org/jabref/gui/texparser/ParseTexDialogViewModel.java b/src/main/java/org/jabref/gui/texparser/ParseTexDialogViewModel.java index eab42881f6f..ef6b25f06f8 100644 --- a/src/main/java/org/jabref/gui/texparser/ParseTexDialogViewModel.java +++ b/src/main/java/org/jabref/gui/texparser/ParseTexDialogViewModel.java @@ -35,14 +35,14 @@ import de.saxsys.mvvmfx.utils.validation.ValidationMessage; import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -class ParseTexDialogViewModel extends AbstractViewModel { +public class ParseTexDialogViewModel extends AbstractViewModel { private static final String TEX_EXT = ".tex"; private final DialogService dialogService; private final TaskExecutor taskExecutor; private final PreferencesService preferencesService; private final StringProperty texDirectory; - private final FunctionBasedValidator texDirectoryValidator; + private final FunctionBasedValidator texDirectoryValidator; private final ObjectProperty root; private final ObservableList> checkedFileList; private final BooleanProperty noFilesFound; @@ -56,7 +56,7 @@ public ParseTexDialogViewModel(BibDatabaseContext databaseContext, DialogService this.preferencesService = preferencesService; this.texDirectory = new SimpleStringProperty( databaseContext.getMetaData().getLaTexFileDirectory(preferencesService.getUser()) - .orElse(preferencesService.getWorkingDir()) + .orElseGet(preferencesService::getWorkingDir) .toAbsolutePath().toString()); this.root = new SimpleObjectProperty<>(); this.checkedFileList = FXCollections.observableArrayList(); @@ -131,7 +131,7 @@ public void searchButtonClicked() { private FileNodeViewModel searchDirectory(Path directory) throws IOException { if (directory == null || !directory.toFile().exists() || !directory.toFile().isDirectory()) { - throw new IOException("An error occurred while searching an invalid directory."); + throw new IOException("Error searching an invalid directory"); } FileNodeViewModel parent = new FileNodeViewModel(directory); @@ -140,7 +140,7 @@ private FileNodeViewModel searchDirectory(Path directory) throws IOException { try (Stream filesStream = Files.list(directory)) { fileListPartition = filesStream.collect(Collectors.partitioningBy(path -> path.toFile().isDirectory())); } catch (IOException e) { - throw new IOException("An error occurred while searching files: ", e); + throw new IOException("Error searching files", e); } List subDirectories = fileListPartition.get(true); @@ -181,7 +181,7 @@ public void parseButtonClicked() { BackgroundTask.wrap(() -> new DefaultTexParser().parse(fileList)) .onRunning(() -> searchInProgress.set(true)) .onFinished(() -> searchInProgress.set(false)) - .onSuccess(result -> new ParseTexResultView(result).showAndWait()) + .onSuccess(result -> new ParseTexResultView(result, Paths.get(texDirectory.get())).showAndWait()) .onFailure(dialogService::showErrorDialogAndWait) .executeWith(taskExecutor); } diff --git a/src/main/java/org/jabref/gui/texparser/ParseTexResult.css b/src/main/java/org/jabref/gui/texparser/ParseTexResult.css deleted file mode 100644 index 2b17b3ca7c9..00000000000 --- a/src/main/java/org/jabref/gui/texparser/ParseTexResult.css +++ /dev/null @@ -1,16 +0,0 @@ -.list-view .list-cell { - -fx-padding: 10px; -} - -.list-view .list-cell .contextBox { - -fx-border-color: grey; - -fx-border-style: dashed; - -fx-border-width: 2px; - -fx-font-size: 110%; - -fx-font-style: italic; - -fx-padding: 15px; -} - -.list-view .list-cell .fileData { - -fx-font-family: 'Courier New', Courier, monospace; -} diff --git a/src/main/java/org/jabref/gui/texparser/ParseTexResultView.java b/src/main/java/org/jabref/gui/texparser/ParseTexResultView.java index 2ff402c9c7d..1e3d9d71831 100644 --- a/src/main/java/org/jabref/gui/texparser/ParseTexResultView.java +++ b/src/main/java/org/jabref/gui/texparser/ParseTexResultView.java @@ -1,16 +1,14 @@ package org.jabref.gui.texparser; +import java.nio.file.Path; +import java.util.Optional; + import javafx.fxml.FXML; import javafx.scene.control.ListView; -import javafx.scene.layout.HBox; -import javafx.scene.layout.VBox; -import javafx.scene.text.Text; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.ViewModelListCellFactory; import org.jabref.logic.l10n.Localization; -import org.jabref.model.strings.LatexToUnicodeAdapter; -import org.jabref.model.texparser.Citation; import org.jabref.model.texparser.TexParserResult; import com.airhacks.afterburner.views.ViewLoader; @@ -19,49 +17,35 @@ public class ParseTexResultView extends BaseDialog { private final TexParserResult texParserResult; + private final Path basePath; @FXML private ListView referenceListView; - @FXML private ListView citationListView; - private ParseTexResultViewModel viewModel; + @FXML private ListView citationListView; - public ParseTexResultView(TexParserResult texParserResult) { + public ParseTexResultView(TexParserResult texParserResult, Path basePath) { this.texParserResult = texParserResult; + this.basePath = basePath; - this.setTitle(Localization.lang("LaTeX Citations Search Results")); + setTitle(Localization.lang("LaTeX Citations Search Results")); ViewLoader.view(this).load().setAsDialogPane(this); } @FXML private void initialize() { - viewModel = new ParseTexResultViewModel(texParserResult); + ParseTexResultViewModel viewModel = new ParseTexResultViewModel(texParserResult); referenceListView.setItems(viewModel.getReferenceList()); - EasyBind.subscribe(referenceListView.getSelectionModel().selectedItemProperty(), - viewModel::activeReferenceChanged); referenceListView.getSelectionModel().selectFirst(); new ViewModelListCellFactory() .withText(ReferenceViewModel::getDisplayText) .install(referenceListView); citationListView.setItems(viewModel.getCitationListByReference()); - new ViewModelListCellFactory() - .withGraphic(this::citationToGraphic) + new ViewModelListCellFactory() + .withGraphic(citation -> citation.getDisplayGraphic(basePath, Optional.of(citationListView.getWidth() - 50))) .install(citationListView); - } - - private VBox citationToGraphic(Citation item) { - String contextString = LatexToUnicodeAdapter.format(item.getLineText()); - String fileDataString = String.format("%n%s (%s:%s-%s)%n", item.getPath().getFileName(), item.getLine(), - item.getColStart(), item.getColEnd()); - Text context = new Text(contextString); - context.setWrappingWidth(citationListView.getWidth() - 50.0); - HBox contextBox = new HBox(context); - contextBox.getStyleClass().add("contextBox"); - - Text fileData = new Text(fileDataString); - fileData.getStyleClass().add("fileData"); - - return new VBox(contextBox, fileData); + EasyBind.subscribe(referenceListView.getSelectionModel().selectedItemProperty(), + viewModel::activeReferenceChanged); } } diff --git a/src/main/java/org/jabref/gui/texparser/ParseTexResultViewModel.java b/src/main/java/org/jabref/gui/texparser/ParseTexResultViewModel.java index b3b6dbb77cf..73684259883 100644 --- a/src/main/java/org/jabref/gui/texparser/ParseTexResultViewModel.java +++ b/src/main/java/org/jabref/gui/texparser/ParseTexResultViewModel.java @@ -5,13 +5,12 @@ import javafx.collections.ObservableList; import org.jabref.gui.AbstractViewModel; -import org.jabref.model.texparser.Citation; import org.jabref.model.texparser.TexParserResult; -class ParseTexResultViewModel extends AbstractViewModel { +public class ParseTexResultViewModel extends AbstractViewModel { private final ObservableList referenceList; - private final ObservableList citationList; + private final ObservableList citationList; public ParseTexResultViewModel(TexParserResult texParserResult) { this.referenceList = FXCollections.observableArrayList(); @@ -26,7 +25,7 @@ public ObservableList getReferenceList() { return new ReadOnlyListWrapper<>(referenceList); } - public ObservableList getCitationListByReference() { + public ObservableList getCitationListByReference() { return new ReadOnlyListWrapper<>(citationList); } diff --git a/src/main/java/org/jabref/gui/texparser/ReferenceViewModel.java b/src/main/java/org/jabref/gui/texparser/ReferenceViewModel.java index 7a2d3fcdba3..34a272a6b8f 100644 --- a/src/main/java/org/jabref/gui/texparser/ReferenceViewModel.java +++ b/src/main/java/org/jabref/gui/texparser/ReferenceViewModel.java @@ -1,7 +1,7 @@ package org.jabref.gui.texparser; import java.util.Collection; -import java.util.StringJoiner; +import java.util.stream.Collectors; import javafx.beans.property.ReadOnlyListWrapper; import javafx.collections.FXCollections; @@ -9,19 +9,19 @@ import org.jabref.model.texparser.Citation; -class ReferenceViewModel { +public class ReferenceViewModel { private final String entry; - private final ObservableList citationList; + private final ObservableList citationList; public ReferenceViewModel(String entry, Collection citationColl) { this.entry = entry; this.citationList = FXCollections.observableArrayList(); - citationList.setAll(citationColl); + citationList.setAll(citationColl.stream().map(CitationViewModel::new).collect(Collectors.toList())); } - public ObservableList getCitationList() { + public ObservableList getCitationList() { return new ReadOnlyListWrapper<>(citationList); } @@ -34,9 +34,8 @@ public String getDisplayText() { @Override public String toString() { - return new StringJoiner(", ", ReferenceViewModel.class.getSimpleName() + "[", "]") - .add("entry='" + entry + "'") - .add("citationList=" + citationList) - .toString(); + return String.format("ReferenceViewModel{entry='%s', citationList=%s}", + this.entry, + this.citationList); } } diff --git a/src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java b/src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java index b1cde500093..42b5806fe94 100644 --- a/src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java +++ b/src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java @@ -104,7 +104,7 @@ public void shutdown() { EXECUTOR.shutdownNow(); } - private Task getJavaFXTask(BackgroundTask task) { + public Task getJavaFXTask(BackgroundTask task) { Task javaTask = new Task() { { diff --git a/src/main/java/org/jabref/logic/texparser/DefaultTexParser.java b/src/main/java/org/jabref/logic/texparser/DefaultTexParser.java index 6b103d97243..c7dcc0d5327 100644 --- a/src/main/java/org/jabref/logic/texparser/DefaultTexParser.java +++ b/src/main/java/org/jabref/logic/texparser/DefaultTexParser.java @@ -2,10 +2,12 @@ import java.io.IOException; import java.io.LineNumberReader; +import java.nio.channels.ClosedChannelException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; @@ -44,69 +46,83 @@ public class DefaultTexParser implements TexParser { private static final String TEX_EXT = ".tex"; - private final TexParserResult result; + private final TexParserResult texParserResult; public DefaultTexParser() { - this.result = new TexParserResult(); + this.texParserResult = new TexParserResult(); } - public TexParserResult getResult() { - return result; + public TexParserResult getTexParserResult() { + return texParserResult; } @Override public TexParserResult parse(String citeString) { - matchCitation(Paths.get("foo/bar"), 1, citeString); - return result; + matchCitation(null, Paths.get(""), 1, citeString); + return texParserResult; } @Override public TexParserResult parse(Path texFile) { - return parse(Collections.singletonList(texFile)); + return parse(null, Collections.singletonList(texFile)); } @Override public TexParserResult parse(List texFiles) { - List referencedFiles = new ArrayList<>(); + return parse(null, texFiles); + } - result.addFiles(texFiles); + /** + * Parse a list of TEX files for searching a given entry. + * + * @param entryKey String that contains the entry key we are searching (null for all entries) + * @param texFiles List of Path objects linked to a TEX file + * @return a TexParserResult, which contains all data related to the bibliographic entries + */ + public TexParserResult parse(String entryKey, List texFiles) { + texParserResult.addFiles(texFiles); + + List referencedFiles = new ArrayList<>(); for (Path file : texFiles) { - try (LineNumberReader lnr = new LineNumberReader(Files.newBufferedReader(file))) { - for (String line = lnr.readLine(); line != null; line = lnr.readLine()) { + try (LineNumberReader lineNumberReader = new LineNumberReader(Files.newBufferedReader(file))) { + for (String line = lineNumberReader.readLine(); line != null; line = lineNumberReader.readLine()) { + // Skip comments and blank lines. if (line.isEmpty() || line.charAt(0) == '%') { - // Skip comments and blank lines. continue; } - - matchCitation(file, lnr.getLineNumber(), line); + // Skip the citation matching if the line does not contain the given entry to speed up the parsing. + if (entryKey == null || line.contains(entryKey)) { + matchCitation(entryKey, file, lineNumberReader.getLineNumber(), line); + } matchNestedFile(file, texFiles, referencedFiles, line); } + } catch (ClosedChannelException e) { + LOGGER.info("Parsing has been interrupted"); + return texParserResult; } catch (IOException e) { - LOGGER.warn("Error opening the TEX file", e); + LOGGER.error("Error opening a TEX file", e); } } // Parse all files referenced by TEX files, recursively. if (!referencedFiles.isEmpty()) { - parse(referencedFiles); + parse(entryKey, referencedFiles); } - return result; + return texParserResult; } /** * Find cites along a specific line and store them. */ - private void matchCitation(Path file, int lineNumber, String line) { + private void matchCitation(String entryKey, Path file, int lineNumber, String line) { Matcher citeMatch = CITE_PATTERN.matcher(line); while (citeMatch.find()) { - String[] keys = citeMatch.group(CITE_GROUP).split(","); - - for (String key : keys) { - result.addKey(key, file, lineNumber, citeMatch.start(), citeMatch.end(), line); - } + Arrays.stream(citeMatch.group(CITE_GROUP).split(",")) + .filter(key -> entryKey == null || key.equals(entryKey)) + .forEach(key -> texParserResult.addKey(key, file, lineNumber, citeMatch.start(), citeMatch.end(), line)); } } @@ -115,10 +131,9 @@ private void matchCitation(Path file, int lineNumber, String line) { */ private void matchNestedFile(Path file, List texFiles, List referencedFiles, String line) { Matcher includeMatch = INCLUDE_PATTERN.matcher(line); - StringBuilder include; while (includeMatch.find()) { - include = new StringBuilder(includeMatch.group(INCLUDE_GROUP)); + StringBuilder include = new StringBuilder(includeMatch.group(INCLUDE_GROUP)); if (!include.toString().endsWith(TEX_EXT)) { include.append(TEX_EXT); diff --git a/src/main/java/org/jabref/logic/texparser/TexBibEntriesResolver.java b/src/main/java/org/jabref/logic/texparser/TexBibEntriesResolver.java index d32bc9c7289..6fb8820eb99 100644 --- a/src/main/java/org/jabref/logic/texparser/TexBibEntriesResolver.java +++ b/src/main/java/org/jabref/logic/texparser/TexBibEntriesResolver.java @@ -26,7 +26,7 @@ public TexBibEntriesResolverResult resolveKeys(TexParserResult texParserResult) Set keySet = result.getCitationsKeySet(); for (String key : keySet) { - if (!result.checkEntryNewDatabase(key)) { + if (result.isNotKeyIntoNewEntries(key)) { Optional entry = masterDatabase.getEntryByKey(key); if (entry.isPresent()) { @@ -46,7 +46,7 @@ public TexBibEntriesResolverResult resolveKeys(TexParserResult texParserResult) */ private void resolveCrossReferences(TexBibEntriesResolverResult result, BibEntry entry) { entry.getField(FieldName.CROSSREF).ifPresent(crossRef -> { - if (!result.checkEntryNewDatabase(crossRef)) { + if (result.isNotKeyIntoNewEntries(crossRef)) { Optional refEntry = masterDatabase.getEntryByKey(crossRef); if (refEntry.isPresent()) { diff --git a/src/main/java/org/jabref/model/texparser/Citation.java b/src/main/java/org/jabref/model/texparser/Citation.java index 24f9a503873..f574b08156b 100644 --- a/src/main/java/org/jabref/model/texparser/Citation.java +++ b/src/main/java/org/jabref/model/texparser/Citation.java @@ -2,7 +2,6 @@ import java.nio.file.Path; import java.util.Objects; -import java.util.StringJoiner; public class Citation { @@ -18,10 +17,6 @@ public class Citation { private final String lineText; public Citation(Path path, int line, int colStart, int colEnd, String lineText) { - if (path == null) { - throw new IllegalArgumentException("Path cannot be null."); - } - if (line <= 0) { throw new IllegalArgumentException("Line has to be greater than 0."); } @@ -30,7 +25,7 @@ public Citation(Path path, int line, int colStart, int colEnd, String lineText) throw new IllegalArgumentException("Citation has to be between 0 and line length."); } - this.path = path; + this.path = Objects.requireNonNull(path); this.line = line; this.colStart = colStart; this.colEnd = colEnd; @@ -57,10 +52,6 @@ public String getLineText() { return lineText; } - public int getContextWidth() { - return CONTEXT_WIDTH; - } - /** * Get a fixed-width string that contains a cite and the text that surrounds it along the same line. */ @@ -78,26 +69,25 @@ public String getContext() { @Override public String toString() { - return new StringJoiner(", ", getClass().getSimpleName() + "[", "]") - .add("path = " + path) - .add("line = " + line) - .add("colStart = " + colStart) - .add("colEnd = " + colEnd) - .add("lineText = " + lineText) - .toString(); + return String.format("Citation{path=%s, line=%s, colStart=%s, colEnd=%s, lineText='%s'}", + this.path, + this.line, + this.colStart, + this.colEnd, + this.lineText); } @Override - public boolean equals(Object o) { - if (this == o) { + public boolean equals(Object obj) { + if (this == obj) { return true; } - if (o == null || getClass() != o.getClass()) { + if (obj == null || getClass() != obj.getClass()) { return false; } - Citation that = (Citation) o; + Citation that = (Citation) obj; return Objects.equals(path, that.path) && Objects.equals(line, that.line) diff --git a/src/main/java/org/jabref/model/texparser/TexBibEntriesResolverResult.java b/src/main/java/org/jabref/model/texparser/TexBibEntriesResolverResult.java index e8602d119cb..3e0ccf17ce6 100644 --- a/src/main/java/org/jabref/model/texparser/TexBibEntriesResolverResult.java +++ b/src/main/java/org/jabref/model/texparser/TexBibEntriesResolverResult.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.StringJoiner; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; @@ -63,10 +62,10 @@ public void addUnresolvedKey(String key) { } /** - * Check if an entry with the given key is present in the list of new entries. + * Check if an entry with the given key is not present in the list of new entries. */ - public boolean checkEntryNewDatabase(String key) { - return newEntries.stream().anyMatch(entry -> key.equals(entry.getCiteKeyOptional().orElse(null))); + public boolean isNotKeyIntoNewEntries(String key) { + return newEntries.stream().noneMatch(entry -> key.equals(entry.getCiteKeyOptional().orElse(null))); } /** @@ -92,25 +91,24 @@ public void insertEntry(BibEntry entry) { @Override public String toString() { - return new StringJoiner(", ", this.getClass().getSimpleName() + "[", "]") - .add("texParserResult = " + texParserResult) - .add("unresolvedKeys = " + unresolvedKeys) - .add("newEntries = " + newEntries) - .add("crossRefsCount = " + crossRefsCount) - .toString(); + return String.format("TexBibEntriesResolverResult{texParserResult=%s, unresolvedKeys=%s, newEntries=%s, crossRefsCount=%s}", + this.texParserResult, + this.unresolvedKeys, + this.newEntries, + this.crossRefsCount); } @Override - public boolean equals(Object o) { - if (this == o) { + public boolean equals(Object obj) { + if (this == obj) { return true; } - if (o == null || getClass() != o.getClass()) { + if (obj == null || getClass() != obj.getClass()) { return false; } - TexBibEntriesResolverResult that = (TexBibEntriesResolverResult) o; + TexBibEntriesResolverResult that = (TexBibEntriesResolverResult) obj; return Objects.equals(texParserResult, that.texParserResult) && Objects.equals(unresolvedKeys, that.unresolvedKeys) diff --git a/src/main/java/org/jabref/model/texparser/TexParser.java b/src/main/java/org/jabref/model/texparser/TexParser.java index 45ed93b8c1e..41e4ee2c673 100644 --- a/src/main/java/org/jabref/model/texparser/TexParser.java +++ b/src/main/java/org/jabref/model/texparser/TexParser.java @@ -9,7 +9,7 @@ public interface TexParser { * For testing purposes. * * @param citeString String that contains a citation - * @return a TexParserResult, where Path is foo/bar and lineNumber is 1 + * @return a TexParserResult, where Path is "" and lineNumber is 1 */ TexParserResult parse(String citeString); diff --git a/src/main/java/org/jabref/model/texparser/TexParserResult.java b/src/main/java/org/jabref/model/texparser/TexParserResult.java index 043ca473c68..cfbeea25b51 100644 --- a/src/main/java/org/jabref/model/texparser/TexParserResult.java +++ b/src/main/java/org/jabref/model/texparser/TexParserResult.java @@ -3,10 +3,12 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.StringJoiner; + +import org.jabref.model.entry.BibEntry; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; @@ -49,6 +51,13 @@ public Collection getCitationsByKey(String key) { return citations.get(key); } + /** + * Return a collection of citations using a BibEntry as reference. + */ + public Collection getCitationsByKey(BibEntry entry) { + return entry.getCiteKeyOptional().map(this::getCitationsByKey).orElse(Collections.emptyList()); + } + /** * Add a list of files to fileList or nestedFiles, depending on whether this is the first list. */ @@ -70,24 +79,23 @@ public void addKey(String key, Path file, int lineNumber, int start, int end, St @Override public String toString() { - return new StringJoiner(", ", getClass().getSimpleName() + "[", "]") - .add("fileList = " + fileList) - .add("nestedFiles = " + nestedFiles) - .add("citations = " + citations) - .toString(); + return String.format("TexParserResult{fileList=%s, nestedFiles=%s, citations=%s}", + this.fileList, + this.nestedFiles, + this.citations); } @Override - public boolean equals(Object o) { - if (this == o) { + public boolean equals(Object obj) { + if (this == obj) { return true; } - if (o == null || getClass() != o.getClass()) { + if (obj == null || getClass() != obj.getClass()) { return false; } - TexParserResult that = (TexParserResult) o; + TexParserResult that = (TexParserResult) obj; return Objects.equals(fileList, that.fileList) && Objects.equals(nestedFiles, that.nestedFiles) diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 4a209790519..e360a17f42c 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -299,6 +299,7 @@ public class JabRefPreferences implements PreferencesService { public static final String PUSH_TO_APPLICATION = "pushToApplication"; public static final String SHOW_RECOMMENDATIONS = "showRecommendations"; public static final String ACCEPT_RECOMMENDATIONS = "acceptRecommendations"; + public static final String SHOW_LATEX_CITATIONS = "showLatexCitations"; public static final String SEND_LANGUAGE_DATA = "sendLanguageData"; public static final String SEND_OS_DATA = "sendOSData"; public static final String SEND_TIMEZONE_DATA = "sendTimezoneData"; @@ -574,6 +575,7 @@ private JabRefPreferences() { defaults.put(SHOW_RECOMMENDATIONS, Boolean.TRUE); defaults.put(ACCEPT_RECOMMENDATIONS, Boolean.FALSE); + defaults.put(SHOW_LATEX_CITATIONS, Boolean.TRUE); defaults.put(SEND_LANGUAGE_DATA, Boolean.FALSE); defaults.put(SEND_OS_DATA, Boolean.FALSE); defaults.put(SEND_TIMEZONE_DATA, Boolean.FALSE); @@ -901,6 +903,7 @@ public EntryEditorPreferences getEntryEditorPreferences() { getCustomTabFieldNames(), getBoolean(SHOW_RECOMMENDATIONS), getBoolean(ACCEPT_RECOMMENDATIONS), + getBoolean(SHOW_LATEX_CITATIONS), getBoolean(DEFAULT_SHOW_SOURCE), getBibtexKeyPatternPreferences(), Globals.getKeyPrefs(), @@ -1350,12 +1353,12 @@ public Map> getEntryEditorTabList() { } @Override - public Boolean getEnforceLegalKeys() { + public boolean getEnforceLegalKeys() { return getBoolean(ENFORCE_LEGAL_BIBTEX_KEY); } @Override - public Boolean getAllowIntegerEdition() { + public boolean getAllowIntegerEdition() { return getBoolean(ALLOW_INTEGER_EDITION_BIBTEX); } diff --git a/src/main/java/org/jabref/preferences/PreferencesService.java b/src/main/java/org/jabref/preferences/PreferencesService.java index d904a0b7ad8..2fd1689d4a2 100644 --- a/src/main/java/org/jabref/preferences/PreferencesService.java +++ b/src/main/java/org/jabref/preferences/PreferencesService.java @@ -5,6 +5,8 @@ import java.util.List; import java.util.Map; +import org.jabref.gui.entryeditor.EntryEditorPreferences; +import org.jabref.gui.entryeditor.FileDragDropPreferenceType; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.logic.exporter.SavePreferences; import org.jabref.logic.exporter.TemplateExporter; @@ -52,7 +54,7 @@ public interface PreferencesService { Map> getEntryEditorTabList(); - Boolean getEnforceLegalKeys(); + boolean getEnforceLegalKeys(); Map getCustomTabsNamesAndFields(); @@ -78,14 +80,14 @@ public interface PreferencesService { String getExportWorkingDirectory(); + void setExportWorkingDirectory(String layoutFileDirString); + Charset getDefaultEncoding(); void setDefaultEncoding(Charset encoding); String getUser(); - void setExportWorkingDirectory(String layoutFileDirString); - SaveOrderConfig loadExportSaveOrder(); void storeExportSaveOrder(SaveOrderConfig config); @@ -96,5 +98,9 @@ public interface PreferencesService { void saveCustomEntryTypes(); - public Boolean getAllowIntegerEdition(); + boolean getAllowIntegerEdition(); + + FileDragDropPreferenceType getEntryEditorFileLinkPreference(); + + EntryEditorPreferences getEntryEditorPreferences(); } diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 5b75642afa4..d7c11a32df4 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2114,3 +2114,11 @@ LaTeX\ Citations\ Search\ Results=LaTeX Citations Search Results LaTeX\ files\ directory\:=LaTeX files directory: LaTeX\ files\ found\:=LaTeX files found: files=files +Show\ 'LaTeX\ Citations'\ tab=Show 'LaTeX Citations' tab +LaTeX\ Citations=LaTeX Citations +Search\ citations\ for\ this\ entry\ in\ LaTeX\ files=Search citations for this entry in LaTeX files +Citations\ found=Citations found +No\ citations\ found=No citations found +No\ LaTeX\ files\ containing\ this\ entry\ were\ found.=No LaTeX files containing this entry were found. +You\ can\ set\ the\ LaTeX\ file\ directory\ in\ the\ 'Library\ properties'\ dialog.=You can set the LaTeX file directory in the 'Library properties' dialog. +Selected\ entry\ does\ not\ have\ an\ associated\ BibTeX\ key.=Selected entry does not have an associated BibTeX key. diff --git a/src/test/java/org/jabref/logic/texparser/DefaultTexParserTest.java b/src/test/java/org/jabref/logic/texparser/DefaultTexParserTest.java index 961fd9339b0..d474b4ed8c3 100644 --- a/src/test/java/org/jabref/logic/texparser/DefaultTexParserTest.java +++ b/src/test/java/org/jabref/logic/texparser/DefaultTexParserTest.java @@ -26,7 +26,7 @@ private void testMatchCite(String key, String citeString) { TexParserResult texParserResult = new DefaultTexParser().parse(citeString); TexParserResult expectedParserResult = new TexParserResult(); - expectedParserResult.addKey(key, Paths.get("foo/bar"), 1, 0, citeString.length(), citeString); + expectedParserResult.addKey(key, Paths.get(""), 1, 0, citeString.length(), citeString); assertEquals(expectedParserResult, texParserResult); } @@ -59,8 +59,8 @@ public void testTwoCitationsSameLine() { TexParserResult texParserResult = new DefaultTexParser().parse(citeString); TexParserResult expectedParserResult = new TexParserResult(); - expectedParserResult.addKey(EINSTEIN_C, Paths.get("foo/bar"), 1, 0, 21, citeString); - expectedParserResult.addKey(EINSTEIN_A, Paths.get("foo/bar"), 1, 26, 47, citeString); + expectedParserResult.addKey(EINSTEIN_C, Paths.get(""), 1, 0, 21, citeString); + expectedParserResult.addKey(EINSTEIN_A, Paths.get(""), 1, 26, 47, citeString); assertEquals(expectedParserResult, texParserResult); }