Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We added a field for the latest ICORE conference ranking lookup on the General Tab. [#13476](https://github.com/JabRef/jabref/issues/13476)
- We added BibLaTeX datamodel validation support in order to improve error message quality in entries' fields validation. [#13318](https://github.com/JabRef/jabref/issues/13318)
- We added more supported formats of CAYW endpoint of HTTP server. [#13578](https://github.com/JabRef/jabref/issues/13578)
- We added an integrity checker for the booktitle field. [#12271](https://github.com/JabRef/jabref/issues/12271)
- We added chronological navigation for entries in each library. [#6352](https://github.com/JabRef/jabref/issues/6352)
- We added support for using Medline/Pubmed fetcher with an API key. [#11296](https://github.com/JabRef/jabref/issues/11296#issuecomment-3289005011)
- We added support for using OpenAlex fetcher. [#13940](https://github.com/JabRef/jabref/issues/13940)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;

import org.jabref.gui.commonfxcontrols.BooktitleCleanupPanel;
import org.jabref.gui.commonfxcontrols.FieldFormatterCleanupsPanel;
import org.jabref.logic.FilePreferences;
import org.jabref.logic.cleanup.CleanupPreferences;
Expand Down Expand Up @@ -39,6 +40,7 @@ public class CleanupPresetPanel extends VBox {
@FXML private CheckBox cleanUpBibtex;
@FXML private CheckBox cleanUpTimestampToCreationDate;
@FXML private CheckBox cleanUpTimestampToModificationDate;
@FXML private BooktitleCleanupPanel booktitleCleanupPanel;
@FXML private FieldFormatterCleanupsPanel formatterCleanupsPanel;

public CleanupPresetPanel(@NonNull BibDatabaseContext databaseContext,
Expand Down Expand Up @@ -118,6 +120,8 @@ private void updateDisplay(CleanupPreferences preset) {
cleanUpTimestampToCreationDate.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CONVERT_TIMESTAMP_TO_CREATIONDATE));
cleanUpTimestampToModificationDate.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CONVERT_TIMESTAMP_TO_MODIFICATIONDATE));
cleanUpTimestampToModificationDate.setSelected(preset.isActive(CleanupPreferences.CleanupStep.DO_NOT_CONVERT_TIMESTAMP));
booktitleCleanupPanel.cleanupsDisableProperty().setValue(!preset.getBooktitleCleanups().isEnabled());
booktitleCleanupPanel.cleanupsProperty().setValue(FXCollections.observableArrayList(preset.getBooktitleCleanups().getConfiguredActions()));
formatterCleanupsPanel.cleanupsDisableProperty().setValue(!preset.getFieldFormatterCleanups().isEnabled());
formatterCleanupsPanel.cleanupsProperty().setValue(FXCollections.observableArrayList(preset.getFieldFormatterCleanups().getConfiguredActions()));
}
Expand Down Expand Up @@ -168,8 +172,13 @@ public CleanupPreferences getCleanupPreset() {

activeJobs.add(CleanupPreferences.CleanupStep.FIX_FILE_LINKS);

return new CleanupPreferences(activeJobs, new FieldFormatterCleanups(
!formatterCleanupsPanel.cleanupsDisableProperty().getValue(),
formatterCleanupsPanel.cleanupsProperty()));
return new CleanupPreferences(
activeJobs,
new FieldFormatterCleanups(
!formatterCleanupsPanel.cleanupsDisableProperty().getValue(),
formatterCleanupsPanel.cleanupsProperty()
),
booktitleCleanupPanel.createCleanupAction()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package org.jabref.gui.commonfxcontrols;

import java.util.EnumMap;
import java.util.Map;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.MapChangeListener;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.RowConstraints;
import javafx.scene.layout.VBox;

import org.jabref.gui.util.BindingsHelper;
import org.jabref.logic.cleanup.BooktitleCleanups;
import org.jabref.logic.util.LocationDetector;
import org.jabref.model.cleanup.BooktitleCleanupAction;
import org.jabref.model.cleanup.BooktitleCleanupField;

import com.airhacks.afterburner.views.ViewLoader;

/**
* A JavaFX panel component for configuring book title cleanup operations.
*
* <p>This panel provides a user interface for managing cleanup actions that extract and move
* embedded fields from booktitles, including months, years, page ranges, and locations. Users can
* configure how each field's cleanup should be handled through radio button groups.</p>
*
* <p>The radio buttons are dynamically generated for each cleanup field based on the
* {@link BooktitleCleanupField} enum and {@link BooktitleCleanupAction} enum.
*
* <p>Available actions for each metadata type:</p>
* <ul>
* <li><b>Remove Only:</b> Extract and remove the metadata from the book title</li>
* <li><b>Replace:</b> Extract, remove from title, and populate the corresponding field</li>
* <li><b>Replace If Empty:</b> Extract and populate only if the target field is empty</li>
* <li><b>Skip:</b> Do not perform any cleanup for this metadata type</li>
* </ul>
*
* For details on how the cleanups are executed, see {@link BooktitleCleanups}
*/
public class BooktitleCleanupPanel extends VBox {

public static final double ROW_MIN_HEIGHT = 15.0;
public static final double ROW_PREF_HEIGHT = 20.0;

private final Map<BooktitleCleanupField, ToggleGroup> toggleGroups = new EnumMap<>(BooktitleCleanupField.class);

@FXML private CheckBox cleanupsEnabled;
@FXML private GridPane cleanupGrid;

private BooktitleCleanupPanelViewModel viewModel;

public BooktitleCleanupPanel() {
ViewLoader.view(this)
.root(this)
.load();
}

@FXML
private void initialize() {
this.viewModel = new BooktitleCleanupPanelViewModel(LocationDetector.getInstance());

generateCleanupRows();
setupBindings();
}

/**
* Dynamically generates radio button rows for each cleanup field type.
*
* <p>For each {@link BooktitleCleanupField}, this method:</p>
* <ul>
* <li>Creates a row constraint in the grid</li>
* <li>Adds a label for the field name</li>
* <li>Gets the toggle group from the view model</li>
* <li>Creates radio buttons for each {@link BooktitleCleanupAction}</li>
* <li>Selects the default action</li>
* </ul>
*/
private void generateCleanupRows() {
int rowIndex = 1;
int labelIndex = 0;

for (BooktitleCleanupField field : BooktitleCleanupField.values()) {
RowConstraints rowConstraint = new RowConstraints();
rowConstraint.setMinHeight(ROW_MIN_HEIGHT);
rowConstraint.setPrefHeight(ROW_PREF_HEIGHT);
cleanupGrid.getRowConstraints().add(rowConstraint);

Label fieldLabel = new Label(field.getDisplayName());
fieldLabel.getStyleClass().add("field-label");
cleanupGrid.add(fieldLabel, labelIndex, rowIndex);

ToggleGroup toggleGroup = new ToggleGroup();
toggleGroups.put(field, toggleGroup);

int columnIndex = 1;
for (BooktitleCleanupAction cleanupAction : BooktitleCleanupAction.values()) {
RadioButton radioButton = createRadioButton(cleanupAction, toggleGroup);

if (cleanupAction == field.getDefaultAction()) {
radioButton.setSelected(true);
}

cleanupGrid.add(radioButton, columnIndex, rowIndex);
columnIndex++;
}

rowIndex++;
}
}

private RadioButton createRadioButton(BooktitleCleanupAction cleanupAction, ToggleGroup toggleGroup) {
RadioButton radioButton = new RadioButton();
radioButton.setToggleGroup(toggleGroup);
radioButton.setUserData(cleanupAction);
return radioButton;
}

private void setupBindings() {
BindingsHelper.bindBidirectional(
(ObservableValue<Boolean>) cleanupsEnabled.selectedProperty(),
viewModel.cleanupsDisableProperty(),
disabled -> cleanupsEnabled.selectedProperty().setValue(!disabled),
selected -> viewModel.cleanupsDisableProperty().setValue(!selected));

// setup bidirectional binding between each toggleGroup and its corresponding field-to-cleanup-action map in the view model
toggleGroups.forEach((field, toggleGroup) -> {
toggleGroup.selectedToggleProperty().addListener((_, _, selectedToggle) -> {
if (selectedToggle != null) {
BooktitleCleanupAction action = (BooktitleCleanupAction) selectedToggle.getUserData();
viewModel.setSelectedAction(field, action);
}
});
viewModel.selectedActionsProperty().addListener((MapChangeListener<BooktitleCleanupField, BooktitleCleanupAction>) change -> {
if (change.wasAdded() && change.getKey() == field) {
BooktitleCleanupAction newAction = change.getValueAdded();
for (Toggle toggle : toggleGroup.getToggles()) {
if (toggle.getUserData() == newAction) {
toggleGroup.selectToggle(toggle);
break;
}
}
}
});
});
}

public BooktitleCleanups createCleanupAction() {
return viewModel.createCleanup();
}

public BooleanProperty cleanupsDisableProperty() {
return viewModel.cleanupsDisableProperty();
}

public ListProperty<BooktitleCleanupAction> cleanupsProperty() {
return viewModel.cleanupsListProperty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.jabref.gui.commonfxcontrols;

import java.util.EnumMap;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.MapProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleMapProperty;
import javafx.collections.FXCollections;

import org.jabref.logic.cleanup.BooktitleCleanups;
import org.jabref.logic.util.LocationDetector;
import org.jabref.model.cleanup.BooktitleCleanupAction;
import org.jabref.model.cleanup.BooktitleCleanupField;

public class BooktitleCleanupPanelViewModel {

private final BooleanProperty cleanupsDisableProperty = new SimpleBooleanProperty();
private final ListProperty<BooktitleCleanupAction> cleanupsListProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
/*
* Map Property mapping each cleanup field to its corresponding cleanup action.
* Each field corresponds to a ToggleGroup in the Cleanup Panel with each Radio Button representing
* an individual cleanup action.
*/
private final MapProperty<BooktitleCleanupField, BooktitleCleanupAction> selectedActions =
new SimpleMapProperty<>(FXCollections.observableMap(new EnumMap<>(BooktitleCleanupField.class)));

private final LocationDetector locationDetector;

public BooktitleCleanupPanelViewModel(LocationDetector locationDetector) {
this.locationDetector = locationDetector;
}

public BooktitleCleanups createCleanup() {
return new BooktitleCleanups(
getSelectedAction(BooktitleCleanupField.YEAR),
getSelectedAction(BooktitleCleanupField.MONTH),
getSelectedAction(BooktitleCleanupField.PAGE_RANGE),
getSelectedAction(BooktitleCleanupField.LOCATION),
locationDetector
);
}

public MapProperty<BooktitleCleanupField, BooktitleCleanupAction> selectedActionsProperty() {
return selectedActions;
}

public void setSelectedAction(BooktitleCleanupField field, BooktitleCleanupAction action) {
selectedActions.put(field, action);
}

public BooktitleCleanupAction getSelectedAction(BooktitleCleanupField field) {
return selectedActions.get(field);
}

public BooleanProperty cleanupsDisableProperty() {
return cleanupsDisableProperty;
}

public ListProperty<BooktitleCleanupAction> cleanupsListProperty() {
return cleanupsListProperty;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.VBox?>
<?import org.jabref.gui.commonfxcontrols.FieldFormatterCleanupsPanel?>
<?import org.jabref.gui.commonfxcontrols.BooktitleCleanupPanel?>
<fx:root xmlns:fx="http://javafx.com/fxml/1" spacing="10.0" type="VBox" xmlns="http://javafx.com/javafx/8.0.121"
fx:controller="org.jabref.gui.cleanup.CleanupPresetPanel">
<fx:define>
Expand All @@ -24,8 +25,10 @@
<CheckBox fx:id="cleanUpBibtex" text="%Convert to BibTeX format (e.g., store publication date in year and month fields)" />
<CheckBox fx:id="cleanUpTimestampToCreationDate" text="%Convert timestamp field to field 'creationdate'" />
<CheckBox fx:id="cleanUpTimestampToModificationDate" text="%Convert timestamp field to field 'modificationdate'" />
<BooktitleCleanupPanel fx:id="booktitleCleanupPanel" />
</VBox>


<Label text="%File-related" />
<VBox>
<VBox.margin>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<fx:root spacing="10.0" type="VBox"
xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.jabref.gui.commonfxcontrols.BooktitleCleanupPanel">
<CheckBox fx:id="cleanupsEnabled" text="%Clean up 'booktitle' and move extracted fields" />
<!-- Main Grid Table -->
<GridPane fx:id="cleanupGrid" hgap="4.0" vgap="4.0" styleClass="grid-table" disable="${!cleanupsEnabled.selected}">
<columnConstraints>
<ColumnConstraints minWidth="80.0" prefWidth="100.0" />
<ColumnConstraints minWidth="80.0" prefWidth="100.0" halignment="CENTER" />
<ColumnConstraints minWidth="80.0" prefWidth="100.0" halignment="CENTER" />
<ColumnConstraints minWidth="80.0" prefWidth="100.0" halignment="CENTER" />
<ColumnConstraints minWidth="80.0" prefWidth="100.0" halignment="CENTER" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="15.0" prefHeight="20.0" />
</rowConstraints>

<!--Only table headers; Radio Buttons are populated dynamically-->
<Label text="Field" styleClass="table-header" GridPane.columnIndex="0" GridPane.rowIndex="0" />
<Label text="Remove Only" styleClass="table-header" GridPane.columnIndex="1" GridPane.rowIndex="0" />
<Label text="Replace" styleClass="table-header" GridPane.columnIndex="2" GridPane.rowIndex="0" />
<Label text="Move if Empty" styleClass="table-header" GridPane.columnIndex="3" GridPane.rowIndex="0" />
<Label text="Skip Cleanup" styleClass="table-header" GridPane.columnIndex="4" GridPane.rowIndex="0" />

<padding>
<Insets left="20.0" />
</padding>
</GridPane>

</fx:root>
1 change: 1 addition & 0 deletions jablib/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
exports org.jabref.logic.git.preferences;
exports org.jabref.logic.icore;
exports org.jabref.model.icore;
exports org.jabref.model.cleanup;
exports org.jabref.logic.git.merge.planning;
exports org.jabref.logic.git.merge.execution;
exports org.jabref.model.sciteTallies;
Expand Down
Loading
Loading