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

Implement Tabular UI and Backend Logic for Linked Files Pattern List Configuration #11368 #12090

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ private void init(CleanupPreferences cleanupPreferences, FilePreferences filePre

String currentPattern = Localization.lang("Filename format pattern")
.concat(": ")
.concat(filePreferences.getFileNamePattern());
.concat(filePreferences.getFileNamePattern().toString());
cleanupRenamePDFLabel.setText(currentPattern);

cleanUpBibtex.selectedProperty().addListener(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.jabref.gui.commonfxcontrols;

import java.util.Objects;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

import org.jabref.model.entry.types.EntryType;

public class FilenamePatternItemModel {
private final ObjectProperty<EntryType> entryType = new SimpleObjectProperty<>();
private final StringProperty pattern = new SimpleStringProperty("");

public FilenamePatternItemModel(EntryType entryType, String pattern) {
Objects.requireNonNull(entryType);
Objects.requireNonNull(pattern);
this.entryType.setValue(entryType);
this.pattern.setValue(pattern);
}

public EntryType getEntryType() {
return entryType.getValue();
}

public ObjectProperty<EntryType> entryType() {
return entryType;
}

public void setPattern(String pattern) {
this.pattern.setValue(pattern);
}

public String getPattern() {
return pattern.getValue();
}

public StringProperty pattern() {
return pattern;
}

@Override
public String toString() {
return "[" + entryType.getValue().getName() + "," + pattern.getValue() + "]";
}
Comment on lines +43 to +46
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would put that in a separate method - toString() is more used for debugging.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<fx:root editable="true" minWidth="280.0" type="TableView"
xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.jabref.gui.commonfxcontrols.FilenamePatternPanel">
<columns>
<TableColumn fx:id="entryTypeColumn" editable="false" minWidth="100.0" text="%Name"/>
<TableColumn fx:id="patternColumn" minWidth="100.0" text="%Key pattern"/>
<TableColumn fx:id="actionsColumn" editable="false" maxWidth="30.0" minWidth="30.0" prefWidth="30.0"
reorderable="false" resizable="false" sortable="false"/>
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
</fx:root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package org.jabref.gui.commonfxcontrols;

import java.util.Collection;

import javafx.beans.property.ListProperty;
import javafx.beans.property.ObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.input.KeyEvent;

import org.jabref.gui.icon.IconTheme;
import org.jabref.gui.util.ValueTableCellFactory;
import org.jabref.logic.filenameformatpatterns.AbstractFilenameFormatPatterns;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.preferences.CliPreferences;
import org.jabref.model.entry.BibEntryType;
import org.jabref.model.entry.types.EntryType;

import com.airhacks.afterburner.views.ViewLoader;
import jakarta.inject.Inject;

public class FilenamePatternPanel extends TableView<FilenamePatternItemModel> {

@FXML public TableColumn<FilenamePatternItemModel, EntryType> entryTypeColumn;
@FXML public TableColumn<FilenamePatternItemModel, String> patternColumn;
@FXML public TableColumn<FilenamePatternItemModel, EntryType> actionsColumn;

@Inject private CliPreferences preferences;

private FilenamePatternPanelViewModel viewModel;

private long lastKeyPressTime;
private String tableSearchTerm;

public FilenamePatternPanel() {
super();

ViewLoader.view(this)
.root(this)
.load();
}

@FXML
private void initialize() {
viewModel = new FilenamePatternPanelViewModel(preferences.getFilePreferences());

this.setEditable(true);

entryTypeColumn.setSortable(true);
entryTypeColumn.setReorderable(false);
entryTypeColumn.setCellValueFactory(cellData -> cellData.getValue().entryType());
new ValueTableCellFactory<FilenamePatternItemModel, EntryType>()
.withText(EntryType::getDisplayName)
.install(entryTypeColumn);
this.setOnSort(event ->
viewModel.patternListProperty().sort(FilenamePatternPanelViewModel.defaultOnTopComparator));

patternColumn.setSortable(true);
patternColumn.setReorderable(false);
patternColumn.setCellValueFactory(cellData -> cellData.getValue().pattern());
patternColumn.setCellFactory(TextFieldTableCell.forTableColumn());
patternColumn.setEditable(true);
patternColumn.setOnEditCommit(
(TableColumn.CellEditEvent<FilenamePatternItemModel, String> event) ->
event.getRowValue().setPattern(event.getNewValue()));

actionsColumn.setSortable(false);
actionsColumn.setReorderable(false);
actionsColumn.setCellValueFactory(cellData -> cellData.getValue().entryType());
new ValueTableCellFactory<FilenamePatternItemModel, EntryType>()
.withGraphic(entryType -> IconTheme.JabRefIcons.REFRESH.getGraphicNode())
.withTooltip(entryType ->
Localization.lang("Reset %s to default value").formatted(entryType.getDisplayName()))
.withOnMouseClickedEvent(item -> evt ->
viewModel.setItemToDefaultPattern(this.getFocusModel().getFocusedItem()))
.install(actionsColumn);

this.setRowFactory(item -> new HighlightTableRow());
this.setOnKeyTyped(this::jumpToSearchKey);
this.itemsProperty().bindBidirectional(viewModel.patternListProperty());
}

public void setValues(Collection<BibEntryType> entryTypeList, AbstractFilenameFormatPatterns keyPattern) {
viewModel.setValues(entryTypeList, keyPattern);
}

public void resetAll() {
viewModel.resetAll();
}

public ListProperty<FilenamePatternItemModel> patternListProperty() {
return viewModel.patternListProperty();
}

public ObjectProperty<FilenamePatternItemModel> defaultKeyPatternProperty() {
return viewModel.defaultKeyPatternProperty();
}

private void jumpToSearchKey(KeyEvent keypressed) {
if (keypressed.getCharacter() == null) {
return;
}

if (System.currentTimeMillis() - lastKeyPressTime < 1000) {
tableSearchTerm += keypressed.getCharacter().toLowerCase();
} else {
tableSearchTerm = keypressed.getCharacter().toLowerCase();
}

lastKeyPressTime = System.currentTimeMillis();

this.getItems().stream().filter(item -> item.getEntryType().getName().toLowerCase().startsWith(tableSearchTerm))
.findFirst().ifPresent(this::scrollTo);
}

private static class HighlightTableRow extends TableRow<FilenamePatternItemModel> {
@Override
public void updateItem(FilenamePatternItemModel item, boolean empty) {
super.updateItem(item, empty);
if (item == null || item.getEntryType() == null) {
setStyle("");
} else if (isSelected()) {
setStyle("-fx-background-color: -fx-selection-bar");
} else if (item.getEntryType().getName().equals(FilenamePatternPanelViewModel.ENTRY_TYPE_DEFAULT_NAME)) {
setStyle("-fx-background-color: -fx-default-button");
} else {
setStyle("");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package org.jabref.gui.commonfxcontrols;

import java.util.Collection;
import java.util.Comparator;

import javafx.beans.property.ListProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;

import org.jabref.logic.FilePreferences;
import org.jabref.logic.filenameformatpatterns.AbstractFilenameFormatPatterns;
import org.jabref.logic.filenameformatpatterns.FilenameFormatPattern;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.entry.BibEntryType;
import org.jabref.model.entry.types.EntryType;

public class FilenamePatternPanelViewModel {

public static final String ENTRY_TYPE_DEFAULT_NAME = "default";

public static Comparator<FilenamePatternItemModel> defaultOnTopComparator = (o1, o2) -> {
String itemOneName = o1.getEntryType().getName();
String itemTwoName = o2.getEntryType().getName();

if (itemOneName.equals(itemTwoName)) {
return 0;
} else if (itemOneName.equals(ENTRY_TYPE_DEFAULT_NAME)) {
return -1;
} else if (itemTwoName.equals(ENTRY_TYPE_DEFAULT_NAME)) {
return 1;
}

return 0;
};

private final ListProperty<FilenamePatternItemModel> patternListProperty = new SimpleListProperty<>();
private final ObjectProperty<FilenamePatternItemModel> defaultItemProperty = new SimpleObjectProperty<>();

private final FilePreferences filePreferences;

public FilenamePatternPanelViewModel(FilePreferences filePreferences) {
this.filePreferences = filePreferences;
}

public void setValues(Collection<BibEntryType> entryTypeList, AbstractFilenameFormatPatterns initialKeyPattern) {
String defaultPattern;
if ((initialKeyPattern.getDefaultValue() == null) || initialKeyPattern.getDefaultValue().equals(FilenameFormatPattern.NULL_FileName_PATTERN)) {
defaultPattern = "";
} else {
defaultPattern = initialKeyPattern.getDefaultValue().stringRepresentation();
}

defaultItemProperty.setValue(new FilenamePatternItemModel(new DefaultEntryType(), defaultPattern));
patternListProperty.setValue(FXCollections.observableArrayList());
patternListProperty.add(defaultItemProperty.getValue());

entryTypeList.stream()
.map(BibEntryType::getType)
.forEach(entryType -> {
String pattern;
if (initialKeyPattern.isDefaultValue(entryType)) {
pattern = "";
} else {
pattern = initialKeyPattern.getPatterns().get(entryType).stringRepresentation();
}
patternListProperty.add(new FilenamePatternItemModel(entryType, pattern));
});
}

public void setItemToDefaultPattern(FilenamePatternItemModel item) {
item.setPattern(filePreferences.getDefaultPattern());
}

public void resetAll() {
patternListProperty.forEach(item -> item.setPattern(""));
defaultItemProperty.getValue().setPattern(filePreferences.getDefaultPattern());
}

public ListProperty<FilenamePatternItemModel> patternListProperty() {
return patternListProperty;
}

public ObjectProperty<FilenamePatternItemModel> defaultKeyPatternProperty() {
return defaultItemProperty;
}

public static class DefaultEntryType implements EntryType {
@Override
public String getName() {
return ENTRY_TYPE_DEFAULT_NAME;
}

@Override
public String getDisplayName() {
return Localization.lang("Default pattern");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import org.jabref.gui.commonfxcontrols.FilenamePatternPanel?>
<?import org.jabref.gui.icon.JabRefIconView?>
<fx:root spacing="10.0" type="VBox"
xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
Expand Down Expand Up @@ -59,8 +58,21 @@
<CheckBox fx:id="fulltextIndex" text="%Automatically index all linked files for fulltext search"/>

<Label styleClass="sectionHeader" text="%Linked file name conventions"/>
<Label text="%( Note: Press return to commit changes in the table! )"/>
<AnchorPane>
<FilenamePatternPanel fx:id="bibtexKeyPatternTable"
AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0"
AnchorPane.topAnchor="0.0"
AnchorPane.bottomAnchor="0.0"
prefHeight="180.0"/>
<Button text="%Reset All" onAction="#resetAllFileNamePatterns"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"/>
</AnchorPane>

<GridPane hgap="4.0" vgap="4.0">
<columnConstraints>

<!-- <columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" percentWidth="30.0"/>
<ColumnConstraints hgrow="SOMETIMES"/>
</columnConstraints>
Expand All @@ -70,7 +82,13 @@
</rowConstraints>
<Label text="%Filename format pattern"/>
<ComboBox fx:id="fileNamePattern" promptText="%Choose pattern" GridPane.columnIndex="1" editable="true"
prefWidth="300" minWidth="300" maxWidth="300"/>
prefWidth="300" minWidth="300" maxWidth="300"/> -->


<!-- <AnchorPane>
<FilenamePatternPanel fx:id="bibtexKeyPatternTable" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" prefHeight="180.0"/>
<Button text="%Reset All" onAction="#resetAllFileNamePatterns" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"/>
</AnchorPane> -->

<Label text="%File directory pattern" GridPane.rowIndex="1"/>
<TextField fx:id="fileDirectoryPattern" GridPane.columnIndex="1" GridPane.rowIndex="1"
Expand Down
Loading
Loading