-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reimplement custom entry types dialog (#5799)
- Loading branch information
1 parent
c5209ba
commit 58f1db5
Showing
16 changed files
with
634 additions
and
392 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,4 +76,5 @@ | |
requires org.antlr.antlr4.runtime; | ||
requires flowless; | ||
requires org.apache.tika.core; | ||
requires javafx.base; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
216 changes: 216 additions & 0 deletions
216
src/main/java/org/jabref/gui/customentrytypes/CustomEntryTypeDialogViewModel.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
package org.jabref.gui.customentrytypes; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.function.Predicate; | ||
import java.util.stream.Collectors; | ||
|
||
import javafx.beans.Observable; | ||
import javafx.beans.property.ListProperty; | ||
import javafx.beans.property.ObjectProperty; | ||
import javafx.beans.property.SimpleListProperty; | ||
import javafx.beans.property.SimpleObjectProperty; | ||
import javafx.beans.property.SimpleStringProperty; | ||
import javafx.beans.property.StringProperty; | ||
import javafx.collections.FXCollections; | ||
import javafx.collections.ObservableList; | ||
import javafx.util.StringConverter; | ||
|
||
import org.jabref.logic.l10n.Localization; | ||
import org.jabref.model.database.BibDatabaseMode; | ||
import org.jabref.model.entry.BibEntryType; | ||
import org.jabref.model.entry.BibEntryTypesManager; | ||
import org.jabref.model.entry.field.BibField; | ||
import org.jabref.model.entry.field.Field; | ||
import org.jabref.model.entry.field.FieldFactory; | ||
import org.jabref.model.entry.field.FieldPriority; | ||
import org.jabref.model.entry.field.OrFields; | ||
import org.jabref.model.entry.field.UnknownField; | ||
import org.jabref.model.entry.types.EntryType; | ||
import org.jabref.model.entry.types.UnknownEntryType; | ||
import org.jabref.preferences.PreferencesService; | ||
|
||
import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; | ||
import de.saxsys.mvvmfx.utils.validation.ValidationMessage; | ||
import de.saxsys.mvvmfx.utils.validation.ValidationStatus; | ||
import de.saxsys.mvvmfx.utils.validation.Validator; | ||
import org.fxmisc.easybind.EasyBind; | ||
|
||
public class CustomEntryTypeDialogViewModel { | ||
|
||
public static final StringConverter<Field> FIELD_STRING_CONVERTER = new StringConverter<>() { | ||
|
||
@Override | ||
public String toString(Field object) { | ||
return object != null ? object.getDisplayName() : ""; | ||
} | ||
|
||
@Override | ||
public Field fromString(String string) { | ||
return new UnknownField(string); | ||
} | ||
}; | ||
|
||
private final ListProperty<BibEntryType> entryTypes; | ||
private final ListProperty<Field> fields; | ||
private final ObjectProperty<BibEntryType> selectedEntryTypes = new SimpleObjectProperty<>(); | ||
private final ListProperty<FieldViewModel> fieldsForType; | ||
private final ObjectProperty<Field> selectedFieldToAdd = new SimpleObjectProperty<>(); | ||
private final StringProperty entryTypeToAdd = new SimpleStringProperty(""); | ||
private final ObservableList<BibEntryType> allEntryTypes; | ||
private final ObservableList<FieldViewModel> allFieldsForType = FXCollections.observableArrayList(extractor -> new Observable[] {extractor.fieldName(), extractor.fieldType()}); | ||
private final ObjectProperty<Field> newFieldToAdd = new SimpleObjectProperty<>(); | ||
private final BibDatabaseMode mode; | ||
private final Map<BibEntryType, List<FieldViewModel>> typesWithFields = new HashMap<>(); | ||
private final List<BibEntryType> typesToRemove = new ArrayList<>(); | ||
|
||
private final PreferencesService preferencesService; | ||
private final BibEntryTypesManager entryTypesManager; | ||
|
||
private final Validator entryTypeValidator; | ||
private final Validator fieldValidator; | ||
|
||
public CustomEntryTypeDialogViewModel(BibDatabaseMode mode, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager) { | ||
this.mode = mode; | ||
this.preferencesService = preferencesService; | ||
this.entryTypesManager = entryTypesManager; | ||
|
||
Collection<BibEntryType> allTypes = entryTypesManager.getAllTypes(mode); | ||
allTypes.addAll(entryTypesManager.getAllCustomTypes(mode)); | ||
|
||
allEntryTypes = FXCollections.observableArrayList(allTypes); | ||
entryTypes = new SimpleListProperty<>(allEntryTypes); | ||
|
||
fields = new SimpleListProperty<>(FXCollections.observableArrayList(FieldFactory.getCommonFields())); | ||
|
||
for (BibEntryType entryType : allTypes) { | ||
List<FieldViewModel> fields = entryType.getAllFields().stream().map(bibField -> new FieldViewModel(bibField.getField(), entryType.isRequired(bibField.getField()), bibField.getPriority(), entryType)).collect(Collectors.toList()); | ||
typesWithFields.put(entryType, fields); | ||
} | ||
|
||
this.fieldsForType = new SimpleListProperty<>(allFieldsForType); | ||
|
||
EasyBind.subscribe(selectedEntryTypes, type -> { | ||
if (type != null) { | ||
allFieldsForType.setAll(typesWithFields.get(type)); | ||
} | ||
}); | ||
|
||
Predicate<String> notEmpty = input -> (input != null) && !input.trim().isEmpty(); | ||
entryTypeValidator = new FunctionBasedValidator<>(entryTypeToAdd, notEmpty, ValidationMessage.error(Localization.lang("Entry type cannot be empty. Please enter a name."))); | ||
fieldValidator = new FunctionBasedValidator<>(newFieldToAdd, | ||
input -> input != null && !input.getDisplayName().isEmpty(), | ||
ValidationMessage.error(Localization.lang("Field cannot be empty. Please enter a name."))); | ||
} | ||
|
||
public ListProperty<BibEntryType> entryTypes() { | ||
return this.entryTypes; | ||
} | ||
|
||
public ListProperty<Field> fields() { | ||
return this.fields; | ||
} | ||
|
||
public enum FieldType { | ||
|
||
REQUIRED(Localization.lang("Required")), | ||
OPTIONAL(Localization.lang("Optional")); | ||
|
||
private String name; | ||
|
||
FieldType(String name) { | ||
this.name = name; | ||
} | ||
|
||
public String getDisplayName() { | ||
return this.name; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return this.name; | ||
} | ||
} | ||
|
||
public void addNewField() { | ||
Field field = newFieldToAdd.getValue(); | ||
FieldViewModel model = new FieldViewModel(field, true, FieldPriority.IMPORTANT, selectedEntryTypes.getValue()); | ||
typesWithFields.computeIfAbsent(selectedEntryTypes.getValue(), key -> new ArrayList<>()).add(model); | ||
allFieldsForType.add(model); | ||
newFieldToAddProperty().setValue(null); | ||
} | ||
|
||
public void addNewCustomEntryType() { | ||
EntryType newentryType = new UnknownEntryType(entryTypeToAdd.getValue()); | ||
BibEntryType type = new BibEntryType(newentryType, new ArrayList<>(), Collections.emptyList()); | ||
this.allEntryTypes.add(type); | ||
this.entryTypeToAdd.setValue(""); | ||
this.typesWithFields.put(type, new ArrayList<>()); | ||
} | ||
|
||
public ObjectProperty<BibEntryType> selectedEntryTypeProperty() { | ||
return this.selectedEntryTypes; | ||
} | ||
|
||
public ListProperty<FieldViewModel> fieldsforTypesProperty() { | ||
return this.fieldsForType; | ||
} | ||
|
||
public ObjectProperty<Field> selectedFieldToAddProperty() { | ||
return this.selectedFieldToAdd; | ||
} | ||
|
||
public StringProperty entryTypeToAddProperty() { | ||
return this.entryTypeToAdd; | ||
} | ||
|
||
public ObjectProperty<Field> newFieldToAddProperty() { | ||
return this.newFieldToAdd; | ||
} | ||
|
||
public ValidationStatus entryTypeValidationStatus() { | ||
return entryTypeValidator.getValidationStatus(); | ||
} | ||
|
||
public ValidationStatus fieldValidationStatus() { | ||
return fieldValidator.getValidationStatus(); | ||
} | ||
|
||
public void removeEntryType(BibEntryType focusedItem) { | ||
typesToRemove.add(focusedItem); | ||
typesWithFields.remove(focusedItem); | ||
allEntryTypes.remove(focusedItem); | ||
} | ||
|
||
public void removeField(FieldViewModel focusedItem) { | ||
typesWithFields.computeIfAbsent(selectedEntryTypes.getValue(), key -> new ArrayList<>()).remove(focusedItem); | ||
allFieldsForType.remove(focusedItem); | ||
} | ||
|
||
public void apply() { | ||
|
||
for (var typeWithField : typesWithFields.entrySet()) { | ||
BibEntryType type = typeWithField.getKey(); | ||
List<FieldViewModel> allFields = typeWithField.getValue(); | ||
|
||
List<OrFields> requiredFields = allFields.stream().filter(field -> field.getFieldType() == FieldType.REQUIRED).map(FieldViewModel::getField).map(OrFields::new).collect(Collectors.toList()); | ||
List<BibField> otherFields = allFields.stream().filter(field -> field.getFieldType() == FieldType.OPTIONAL).map(bibField -> new BibField(bibField.getField(), bibField.getFieldPriority())).collect(Collectors.toList()); | ||
|
||
BibEntryType newType = new BibEntryType(type.getType(), otherFields, requiredFields); | ||
entryTypesManager.addCustomOrModifiedType(newType, mode); | ||
} | ||
|
||
for (var type : typesToRemove) { | ||
entryTypesManager.removeCustomOrModifiedEntryType(type, mode); | ||
} | ||
preferencesService.saveCustomEntryTypes(); | ||
//Reload types from preferences to make sure any modifications are present when reopening the dialog | ||
entryTypesManager.addCustomOrModifiedTypes(preferencesService.loadBibEntryTypes(BibDatabaseMode.BIBTEX), | ||
preferencesService.loadBibEntryTypes(BibDatabaseMode.BIBLATEX)); | ||
} | ||
|
||
} |
18 changes: 13 additions & 5 deletions
18
src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryAction.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,27 @@ | ||
package org.jabref.gui.customentrytypes; | ||
|
||
import org.jabref.gui.JabRefFrame; | ||
import org.jabref.gui.StateManager; | ||
import org.jabref.gui.actions.SimpleCommand; | ||
import org.jabref.model.database.BibDatabaseContext; | ||
import org.jabref.model.entry.BibEntryTypesManager; | ||
|
||
import static org.jabref.gui.actions.ActionHelper.needsDatabase; | ||
|
||
public class CustomizeEntryAction extends SimpleCommand { | ||
|
||
private final JabRefFrame frame; | ||
private final StateManager stateManager; | ||
private final BibEntryTypesManager entryTypesManager; | ||
|
||
public CustomizeEntryAction(JabRefFrame frame) { | ||
this.frame = frame; | ||
public CustomizeEntryAction(StateManager stateManager, BibEntryTypesManager entryTypesManager) { | ||
this.stateManager = stateManager; | ||
this.executable.bind(needsDatabase(this.stateManager)); | ||
this.entryTypesManager = entryTypesManager; | ||
} | ||
|
||
@Override | ||
public void execute() { | ||
EntryTypeCustomizationDialog dialog = new EntryTypeCustomizationDialog(); | ||
BibDatabaseContext database = stateManager.getActiveDatabase().orElseThrow(() -> new NullPointerException("Database null")); | ||
CustomizeEntryTypeDialogView dialog = new CustomizeEntryTypeDialogView(database, entryTypesManager); | ||
dialog.showAndWait(); | ||
} | ||
} |
101 changes: 101 additions & 0 deletions
101
src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryTypeDialog.fxml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
|
||
<?import javafx.geometry.Insets?> | ||
<?import javafx.scene.control.Button?> | ||
<?import javafx.scene.control.ButtonType?> | ||
<?import javafx.scene.control.ComboBox?> | ||
<?import javafx.scene.control.DialogPane?> | ||
<?import javafx.scene.control.Label?> | ||
<?import javafx.scene.control.TableColumn?> | ||
<?import javafx.scene.control.TableView?> | ||
<?import javafx.scene.control.TextField?> | ||
<?import javafx.scene.layout.HBox?> | ||
<?import javafx.scene.layout.VBox?> | ||
|
||
<?import org.jabref.gui.icon.JabRefIconView?> | ||
<?import javafx.scene.control.Tooltip?> | ||
|
||
<DialogPane prefHeight="596.0" prefWidth="680" | ||
xmlns="http://javafx.com/javafx/8.0.171" | ||
xmlns:fx="http://javafx.com/fxml/1" | ||
fx:controller="org.jabref.gui.customentrytypes.CustomizeEntryTypeDialogView"> | ||
<content> | ||
<HBox minHeight="-Infinity" minWidth="-Infinity" | ||
prefHeight="400.0" prefWidth="400.0" spacing="10.0"> | ||
<children> | ||
<VBox prefHeight="400.0" prefWidth="100.0" spacing="10.0"> | ||
<children> | ||
<Label text="Entry types" /> | ||
<TableView fx:id="entryTypes" minWidth="-Infinity" | ||
VBox.vgrow="ALWAYS"> | ||
<columns> | ||
<TableColumn fx:id="entryTypColumn" minWidth="100.0" | ||
prefWidth="100.0" text="Entry Type" /> | ||
<TableColumn fx:id="entryTypeActionsColumn" | ||
maxWidth="40.0" minWidth="40.0" resizable="false" /> | ||
</columns> | ||
<columnResizePolicy> | ||
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" /> | ||
</columnResizePolicy> | ||
</TableView> | ||
<HBox spacing="10.0"> | ||
<children> | ||
<TextField fx:id="addNewEntryType" /> | ||
<Button fx:id="addNewEntryTypeButton" prefHeight="20.0" prefWidth="20.0" | ||
styleClass="icon-button,narrow" onAction="#addEntryType"> | ||
<graphic> | ||
<JabRefIconView glyph="ADD_NOBOX" /> | ||
</graphic> | ||
<tooltip> | ||
<Tooltip text="%Add new entry type" /> | ||
</tooltip> | ||
</Button> | ||
</children> | ||
<VBox.margin> | ||
<Insets /> | ||
</VBox.margin> | ||
</HBox> | ||
</children> | ||
</VBox> | ||
<VBox prefHeight="400.0" prefWidth="100.0" spacing="10.0" | ||
HBox.hgrow="ALWAYS"> | ||
<children> | ||
<Label text="%Required and optional fields" /> | ||
<TableView fx:id="fields" minWidth="-Infinity" | ||
VBox.vgrow="ALWAYS"> | ||
<columns> | ||
<TableColumn fx:id="fieldNameColumn" | ||
minWidth="150.0" prefWidth="-1.0" text="%Field" /> | ||
<TableColumn fx:id="fieldTypeColumn" | ||
minWidth="100.0" prefWidth="100.0" text="%Field type" /> | ||
<TableColumn fx:id="fieldTypeActionColumn" | ||
maxWidth="40.0" minWidth="40.0" resizable="false" /> | ||
</columns> | ||
<columnResizePolicy> | ||
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" /> | ||
</columnResizePolicy> | ||
</TableView> | ||
<HBox spacing="10.0"> | ||
<children> | ||
<ComboBox fx:id="addNewField" editable="true" | ||
prefWidth="150.0" /> | ||
<Button fx:id="addNewFieldButton" prefHeight="20.0" prefWidth="20.0" | ||
styleClass="icon-button,narrow" onAction="#addNewField"> | ||
<graphic> | ||
<JabRefIconView glyph="ADD_NOBOX" /> | ||
</graphic> | ||
<tooltip> | ||
<Tooltip text="%Add new Field" /> | ||
</tooltip> | ||
</Button> | ||
</children> | ||
</HBox> | ||
</children> | ||
</VBox> | ||
</children> | ||
</HBox> | ||
</content> | ||
<ButtonType fx:id="applyButton" buttonData="OK_DONE" | ||
text="%Apply" /> | ||
<ButtonType fx:constant="CANCEL" /> | ||
</DialogPane> |
Oops, something went wrong.