Skip to content
Merged
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