Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add search for citations in LaTeX files (back-end and GUI) #5123

Merged
merged 10 commits into from
Jul 14, 2019
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ 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.


### Fixed
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/jabref/gui/JabRefFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
import org.jabref.gui.search.GlobalSearchBar;
import org.jabref.gui.shared.ConnectToSharedDatabaseCommand;
import org.jabref.gui.specialfields.SpecialFieldMenuItemFactory;
import org.jabref.gui.texparser.ParseTexAction;
import org.jabref.gui.undo.CountingUndoManager;
import org.jabref.gui.util.BackgroundTask;
import org.jabref.gui.util.DefaultTaskExecutor;
Expand Down Expand Up @@ -768,6 +769,7 @@ private MenuBar createMenu() {
pushToApplicationsManager.setMenuItem(pushToApplicationMenuItem);

tools.getItems().addAll(
factory.createMenuItem(StandardActions.PARSE_TEX, new ParseTexAction(stateManager)),
factory.createMenuItem(StandardActions.NEW_SUB_LIBRARY_FROM_AUX, new NewSubLibraryAction(this, stateManager)),
factory.createMenuItem(StandardActions.FIND_UNLINKED_FILES, new FindUnlinkedFilesAction(this, stateManager)),
factory.createMenuItem(StandardActions.WRITE_XMP, new OldDatabaseCommandWrapper(Actions.WRITE_XMP, this, stateManager)),
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/jabref/gui/actions/StandardActions.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +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("LaTeX references search"), IconTheme.JabRefIcons.APPLICATION_TEXSTUDIO),
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),
Expand Down
58 changes: 58 additions & 0 deletions src/main/java/org/jabref/gui/texparser/FileNodeViewModel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.jabref.gui.texparser;

import java.nio.file.Path;
import java.util.StringJoiner;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import org.jabref.logic.l10n.Localization;

class FileNodeViewModel {

private final Path path;
private final ObservableList<FileNodeViewModel> children;
private int fileCount;

public FileNodeViewModel(Path path) {
this.path = path;
this.fileCount = 0;
this.children = FXCollections.observableArrayList();
}

public Path getPath() {
return path;
}

public int getFileCount() {
return fileCount;
}

public void setFileCount(int fileCount) {
this.fileCount = fileCount;
}

public ObservableList<FileNodeViewModel> getChildren() {
return children;
}

/**
* Return a string for displaying a node name (and its number of children if it is a directory).
*/
public String getDisplayText() {
if (path.toFile().isDirectory()) {
return String.format("%s (%s %s)", path.getFileName(), fileCount,
fileCount == 1 ? Localization.lang("file") : Localization.lang("files"));
}
return path.getFileName().toString();
}

@Override
public String toString() {
return new StringJoiner(", ", FileNodeViewModel.class.getSimpleName() + "[", "]")
.add("path=" + path)
.add("fileCount=" + fileCount)
.add("children=" + children)
.toString();
}
}
25 changes: 25 additions & 0 deletions src/main/java/org/jabref/gui/texparser/ParseTexAction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.jabref.gui.texparser;

import org.jabref.gui.StateManager;
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));
}

@Override
public void execute() {
BibDatabaseContext database = stateManager.getActiveDatabase().orElseThrow(NullPointerException::new);
ParseTexDialogView dialog = new ParseTexDialogView(database);

dialog.showAndWait();
}
}
42 changes: 42 additions & 0 deletions src/main/java/org/jabref/gui/texparser/ParseTexDialog.fxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonType?>
<?import javafx.scene.control.DialogPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressIndicator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import org.controlsfx.control.CheckTreeView?>

<DialogPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.jabref.gui.texparser.ParseTexDialogView"
prefWidth="500.0" prefHeight="650.0">
<content>
<VBox spacing="10.0">
<VBox spacing="5.0">
<Label text="%LaTeX files directory:"/>
<HBox spacing="10.0">
<TextField fx:id="texDirectoryField" HBox.hgrow="ALWAYS"/>
<Button fx:id="browseButton" onAction="#browseButtonClicked" text="%Browse"/>
<Button fx:id="searchButton" defaultButton="true" onAction="#searchButtonClicked" text="%Search"/>
</HBox>
</VBox>
<VBox spacing="5.0">
<Label text="%LaTeX files found:"/>
<CheckTreeView fx:id="fileTreeView" prefHeight="500.0" VBox.vgrow="ALWAYS"/>
</VBox>
<VBox spacing="5.0">
<HBox spacing="10.0">
<Button fx:id="selectAllButton" text="%Select all" styleClass="text-button" onAction="#selectAll"/>
<Button fx:id="unselectAllButton" text="%Unselect all" styleClass="text-button"
onAction="#unselectAll"/>
</HBox>
</VBox>
<ProgressIndicator fx:id="progressIndicator" prefHeight="200.0"/>
</VBox>
</content>
<ButtonType fx:constant="CLOSE"/>
<ButtonType fx:id="parseButtonType" text="%Parse"/>
</DialogPane>
113 changes: 113 additions & 0 deletions src/main/java/org/jabref/gui/texparser/ParseTexDialogView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package org.jabref.gui.texparser;

import javax.inject.Inject;

import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TextField;

import org.jabref.gui.DialogService;
import org.jabref.gui.util.BaseDialog;
import org.jabref.gui.util.ControlHelper;
import org.jabref.gui.util.IconValidationDecorator;
import org.jabref.gui.util.RecursiveTreeItem;
import org.jabref.gui.util.TaskExecutor;
import org.jabref.gui.util.ViewModelTreeCellFactory;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.preferences.PreferencesService;

import com.airhacks.afterburner.views.ViewLoader;
import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer;
import org.controlsfx.control.CheckTreeView;
import org.fxmisc.easybind.EasyBind;

public class ParseTexDialogView extends BaseDialog<Void> {

private final BibDatabaseContext databaseContext;
private final ControlsFxVisualizer validationVisualizer;
@FXML private TextField texDirectoryField;
@FXML private Button browseButton;
@FXML private Button searchButton;
@FXML private ProgressIndicator progressIndicator;
@FXML private CheckTreeView<FileNodeViewModel> fileTreeView;
@FXML private Button selectAllButton;
@FXML private Button unselectAllButton;
@FXML private ButtonType parseButtonType;
@Inject private DialogService dialogService;
@Inject private TaskExecutor taskExecutor;
@Inject private PreferencesService preferencesService;
private ParseTexDialogViewModel viewModel;

public ParseTexDialogView(BibDatabaseContext databaseContext) {
this.databaseContext = databaseContext;
this.validationVisualizer = new ControlsFxVisualizer();

this.setTitle(Localization.lang("LaTeX references search"));

ViewLoader.view(this)
.load()
.setAsDialogPane(this);

ControlHelper.setAction(parseButtonType, getDialogPane(), event -> viewModel.parseButtonClicked());
Button parseButton = (Button) getDialogPane().lookupButton(parseButtonType);
parseButton.disableProperty().bind(viewModel.noFilesFoundProperty().or(
Bindings.isEmpty(viewModel.getCheckedFileList())));
}

@FXML
private void initialize() {
viewModel = new ParseTexDialogViewModel(databaseContext, dialogService, taskExecutor, preferencesService);

fileTreeView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
fileTreeView.showRootProperty().bindBidirectional(viewModel.successfulSearchProperty());
fileTreeView.rootProperty().bind(EasyBind.map(viewModel.rootProperty(), fileNode ->
new RecursiveTreeItem<>(fileNode, FileNodeViewModel::getChildren)));

EasyBind.subscribe(fileTreeView.rootProperty(), root -> {
((CheckBoxTreeItem<FileNodeViewModel>) root).setSelected(true);
root.setExpanded(true);
EasyBind.listBind(viewModel.getCheckedFileList(), fileTreeView.getCheckModel().getCheckedItems());
});

new ViewModelTreeCellFactory<FileNodeViewModel>()
.withText(FileNodeViewModel::getDisplayText)
.install(fileTreeView);

texDirectoryField.textProperty().bindBidirectional(viewModel.texDirectoryProperty());
validationVisualizer.setDecoration(new IconValidationDecorator());
validationVisualizer.initVisualization(viewModel.texDirectoryValidation(), texDirectoryField);

browseButton.disableProperty().bindBidirectional(viewModel.searchInProgressProperty());
searchButton.disableProperty().bind(viewModel.texDirectoryValidation().validProperty().not());
selectAllButton.disableProperty().bindBidirectional(viewModel.noFilesFoundProperty());
unselectAllButton.disableProperty().bindBidirectional(viewModel.noFilesFoundProperty());

progressIndicator.visibleProperty().bindBidirectional(viewModel.searchInProgressProperty());
}

@FXML
private void browseButtonClicked() {
viewModel.browseButtonClicked();
}

@FXML
private void searchButtonClicked() {
viewModel.searchButtonClicked();
}

@FXML
private void selectAll() {
fileTreeView.getCheckModel().checkAll();
}

@FXML
private void unselectAll() {
fileTreeView.getCheckModel().clearChecks();
}
}
Loading