actionHandler = (ActionEvent e) -> updateComponents();
- nameField.setOnAction(actionHandler);
- descriptionField.setOnAction(actionHandler);
- iconField.setOnAction(actionHandler);
- keywordGroupSearchField.setOnAction(actionHandler);
- keywordGroupSearchTerm.setOnAction(actionHandler);
- keywordGroupCaseSensitive.setOnAction(actionHandler);
- keywordGroupRegExp.setOnAction(actionHandler);
- searchGroupSearchExpression.setOnAction(actionHandler);
- searchGroupRegExp.setOnAction(actionHandler);
-
- // configure for current type
- if (editedGroup == null) {
- // creating new group -> defaults!
- colorField.setValue(IconTheme.getDefaultGroupColor());
- explicitRadioButton.setSelected(true);
- setContext(GroupHierarchyType.INDEPENDENT);
- } else {
- nameField.setText(editedGroup.getName());
- colorField.setValue(editedGroup.getColor().orElse(IconTheme.getDefaultGroupColor()));
- descriptionField.setText(editedGroup.getDescription().orElse(""));
- iconField.setText(editedGroup.getIconName().orElse(""));
- setContext(editedGroup.getHierarchicalContext());
-
- if (editedGroup.getClass() == WordKeywordGroup.class) {
- WordKeywordGroup group = (WordKeywordGroup) editedGroup;
- keywordGroupSearchField.setText(group.getSearchField().getName());
- keywordGroupSearchTerm.setText(group.getSearchExpression());
- keywordGroupCaseSensitive.setSelected(group.isCaseSensitive());
- keywordGroupRegExp.setSelected(false);
- keywordsRadioButton.setSelected(true);
- } else if (editedGroup.getClass() == RegexKeywordGroup.class) {
- RegexKeywordGroup group = (RegexKeywordGroup) editedGroup;
- keywordGroupSearchField.setText(group.getSearchField().getName());
- keywordGroupSearchTerm.setText(group.getSearchExpression());
- keywordGroupCaseSensitive.setSelected(group.isCaseSensitive());
- keywordGroupRegExp.setSelected(true);
- keywordsRadioButton.setSelected(true);
- } else if (editedGroup.getClass() == SearchGroup.class) {
- SearchGroup group = (SearchGroup) editedGroup;
- searchGroupSearchExpression.setText(group.getSearchExpression());
- searchGroupCaseSensitive.setSelected(group.isCaseSensitive());
- searchGroupRegExp.setSelected(group.isRegularExpression());
- searchRadioButton.setSelected(true);
- } else if (editedGroup.getClass() == ExplicitGroup.class) {
- explicitRadioButton.setSelected(true);
- } else if (editedGroup instanceof AutomaticGroup) {
- autoRadioButton.setSelected(true);
-
- if (editedGroup.getClass() == AutomaticKeywordGroup.class) {
- AutomaticKeywordGroup group = (AutomaticKeywordGroup) editedGroup;
- autoGroupKeywordsDeliminator.setText(group.getKeywordDelimiter().toString());
- autoGroupKeywordsHierarchicalDeliminator.setText(group.getKeywordHierarchicalDelimiter().toString());
- autoGroupKeywordsField.setText(group.getField().getName());
- } else if (editedGroup.getClass() == AutomaticPersonsGroup.class) {
- AutomaticPersonsGroup group = (AutomaticPersonsGroup) editedGroup;
- autoGroupPersonsField.setText(group.getField().getName());
- }
- } else if (editedGroup.getClass() == TexGroup.class) {
- texRadioButton.setSelected(true);
-
- TexGroup group = (TexGroup) editedGroup;
- texGroupFilePath.setText(group.getFilePath().toString());
- }
- }
- getDialogPane().getScene().getWindow().sizeToScene();
- }
-
- public GroupDialog(DialogService dialogService) {
- this(dialogService, JabRefGUI.getMainFrame().getCurrentBasePanel(), Globals.prefs, null);
- }
-
- public GroupDialog(DialogService dialogService, AbstractGroup editedGroup) {
- this(dialogService, JabRefGUI.getMainFrame().getCurrentBasePanel(), Globals.prefs, editedGroup);
- }
-
- private static String formatRegExException(String regExp, Exception e) {
- String[] sa = e.getMessage().split("\\n");
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < sa.length; ++i) {
- if (i > 0) {
- sb.append("
");
- }
- sb.append(StringUtil.quoteForHTML(sa[i]));
- }
- String s = Localization.lang(
- "The regular expression %0 is invalid:",
- StringUtil.quoteForHTML(regExp))
- + ""
- + sb
- + "";
- if (!(e instanceof PatternSyntaxException)) {
- return s;
- }
- int lastNewline = s.lastIndexOf("
");
- int hat = s.lastIndexOf('^');
- if ((lastNewline >= 0) && (hat >= 0) && (hat > lastNewline)) {
- return s.substring(0, lastNewline + 4) + s.substring(lastNewline + 4).replace(" ", " ");
- }
- return s;
- }
-
- private VBox createOptionsTexGroup() {
- VBox texPanel = new VBox();
- texPanel.setVisible(false);
- texPanel.getChildren().add(new Label(Localization.lang("Aux file")));
- texGroupBrowseButton.setOnAction((ActionEvent e) -> openBrowseDialog());
- texGroupHBox.getChildren().add(texGroupFilePath);
- texGroupHBox.getChildren().add(texGroupBrowseButton);
- HBox.setHgrow(texGroupFilePath, Priority.ALWAYS);
- texPanel.getChildren().add(texGroupHBox);
- return texPanel;
- }
-
- private VBox createOptionsAutoGroup() {
- VBox autoPanel = new VBox(10);
- autoPanel.setVisible(false);
- ToggleGroup tg = new ToggleGroup();
- autoGroupKeywordsOption.setToggleGroup(tg);
- autoGroupPersonsOption.setToggleGroup(tg);
- VBox fieldToGroupByKeywords = new VBox(
- new Label(Localization.lang("Field to group by") + ":"),
- autoGroupKeywordsField
- );
- fieldToGroupByKeywords.setPadding(new Insets(0, 0, 0, 20));
- VBox delimiterCharacters = new VBox(
- new Label(Localization.lang("Use the following delimiter character(s):")),
- new HBox(10,
- autoGroupKeywordsDeliminator,
- autoGroupKeywordsHierarchicalDeliminator
- )
- );
- delimiterCharacters.setPadding(new Insets(0, 0, 0, 20));
- VBox fieldToGroupByPersons = new VBox(
- new Label(Localization.lang("Field to group by") + ":"),
- autoGroupPersonsField
- );
- fieldToGroupByPersons.setPadding(new Insets(0, 0, 0, 20));
- autoPanel.getChildren().setAll(
- autoGroupKeywordsOption,
- fieldToGroupByKeywords,
- delimiterCharacters,
- autoGroupPersonsOption,
- fieldToGroupByPersons
- );
- autoGroupKeywordsOption.setSelected(true);
- autoGroupKeywordsField.setText(Globals.prefs.get(JabRefPreferences.GROUPS_DEFAULT_FIELD));
- autoGroupKeywordsDeliminator.setText(Globals.prefs.get(JabRefPreferences.KEYWORD_SEPARATOR));
- autoGroupKeywordsHierarchicalDeliminator.setText(Keyword.DEFAULT_HIERARCHICAL_DELIMITER.toString());
- autoGroupPersonsField.setText(StandardField.AUTHOR.getName());
- return autoPanel;
- }
-
- private VBox createOptionsSearchGroup() {
- VBox searchPanel = new VBox(10);
- searchPanel.setVisible(false);
- searchPanel.getChildren().setAll(
- new VBox(
- new Label(Localization.lang("Search expression")),
- searchGroupSearchExpression
- ),
- searchGroupCaseSensitive,
- searchGroupRegExp
- );
- return searchPanel;
- }
-
- private VBox createOptionsExplicitGroup() {
- return new VBox();
- }
-
- private VBox createOptionsKeywordGroup() {
- VBox keywordPanel = new VBox(10);
- keywordPanel.setVisible(false);
- keywordPanel.getChildren().setAll(
- new VBox(
- new Label(Localization.lang("Field")),
- keywordGroupSearchField
- ),
- new VBox(
- new Label(Localization.lang("Keyword")),
- keywordGroupSearchTerm
- ),
- keywordGroupCaseSensitive,
- keywordGroupRegExp
- );
- return keywordPanel;
- }
-
- private void updateComponents() {
- // all groups need a name
- boolean okEnabled = !nameField.getText().trim().isEmpty();
- if (!okEnabled) {
- setDescription(Localization.lang("Please enter a name for the group."));
- getDialogPane().lookupButton(ButtonType.OK).setDisable(true);
- return;
- }
- String s1;
- String s2;
- if (keywordsRadioButton.isSelected()) {
- s1 = keywordGroupSearchField.getText().trim();
- okEnabled = okEnabled && s1.matches("\\w+");
- s2 = keywordGroupSearchTerm.getText().trim();
- okEnabled = okEnabled && !s2.isEmpty();
- if (okEnabled) {
- if (keywordGroupRegExp.isSelected()) {
- try {
- Pattern.compile(s2);
- setDescription(GroupDescriptions.getDescriptionForPreview(s1, s2, keywordGroupCaseSensitive.isSelected(),
- keywordGroupRegExp.isSelected()));
- } catch (PatternSyntaxException e) {
- okEnabled = false;
- setDescription(formatRegExException(s2, e));
- }
- } else {
- setDescription(GroupDescriptions.getDescriptionForPreview(s1, s2, keywordGroupCaseSensitive.isSelected(),
- keywordGroupRegExp.isSelected()));
- }
- } else {
- setDescription(Localization.lang(
- "Please enter the field to search (e.g. keywords) and the keyword to search it for (e.g. electrical)."));
- }
- setNameFontItalic(true);
- } else if (searchRadioButton.isSelected()) {
- s1 = searchGroupSearchExpression.getText().trim();
- okEnabled = okEnabled & !s1.isEmpty();
- if (okEnabled) {
- setDescription(fromTextFlowToHTMLString(SearchDescribers.getSearchDescriberFor(
- new SearchQuery(s1, isCaseSensitive(), isRegex()))
- .getDescription()));
-
- if (isRegex()) {
- try {
- Pattern.compile(s1);
- } catch (PatternSyntaxException e) {
- okEnabled = false;
- setDescription(formatRegExException(s1, e));
- }
- }
- } else {
- setDescription(Localization
- .lang("Please enter a search term. For example, to search all fields for Smith, enter:
"
- + "smith
"
- + "To search the field Author for Smith and the field Title for electrical, enter:
"
- + "author=smith and title=electrical"));
- }
- setNameFontItalic(true);
- } else if (explicitRadioButton.isSelected()) {
- setDescription(GroupDescriptions.getDescriptionForPreview());
- setNameFontItalic(false);
- }
- getDialogPane().lookupButton(ButtonType.OK).setDisable(!okEnabled);
- }
-
- private void openBrowseDialog() {
- FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder()
- .addExtensionFilter(StandardFileType.AUX)
- .withDefaultExtension(StandardFileType.AUX)
- .withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)).build();
- dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(file -> texGroupFilePath.setText(relativize(file.toAbsolutePath()).toString()));
- }
-
- private Path relativize(Path path) {
- List fileDirectories = getFileDirectoriesAsPaths();
- return FileUtil.relativize(path, fileDirectories);
- }
-
- private List getFileDirectoriesAsPaths() {
- List fileDirs = new ArrayList<>();
- MetaData metaData = basePanel.getBibDatabaseContext().getMetaData();
- metaData.getLaTexFileDirectory(prefs.getFilePreferences().getUser())
- .ifPresent(laTexFileDirectory -> fileDirs.add(laTexFileDirectory));
-
- return fileDirs;
- }
-
- private String fromTextFlowToHTMLString(TextFlow textFlow) {
- StringBuilder htmlStringBuilder = new StringBuilder();
- for (Node node : textFlow.getChildren()) {
- if (node instanceof Text) {
- htmlStringBuilder.append(TooltipTextUtil.textToHTMLString((Text) node));
- }
- }
- return htmlStringBuilder.toString();
- }
-
- private boolean isRegex() {
- return searchGroupRegExp.isSelected();
- }
-
- private boolean isCaseSensitive() {
- return searchGroupCaseSensitive.isSelected();
- }
-
- private void setDescription(String description) {
- descriptionTextFlow.getChildren().setAll(createFormattedDescription(description));
- }
-
- private ArrayList createFormattedDescription(String descriptionHTML) {
- ArrayList nodes = new ArrayList<>();
-
- descriptionHTML = descriptionHTML.replaceAll("|
", "\n");
-
- String[] boldSplit = descriptionHTML.split("(?=)|(?<=)|(?=)|(?<=)|(?=)|(?<=)|(?=)|(?<=)");
-
- for (String bs : boldSplit) {
-
- if (bs.matches("[^<>]*")) {
-
- bs = bs.replaceAll("|", "");
- Text textElement = new Text(bs);
- textElement.setStyle("-fx-font-weight: bold");
- nodes.add(textElement);
- } else if (bs.matches("[^<>]*")) {
-
- bs = bs.replaceAll("|", "");
- Text textElement = new Text(bs);
- textElement.setStyle("-fx-font-style: italic");
- nodes.add(textElement);
- } else if (bs.matches("[^<>]*|[^<>]*")) {
-
- bs = bs.replaceAll("|||", "");
- Text textElement = new Text(bs);
- textElement.setStyle("-fx-font-family: 'Courier New', Courier, monospace");
- nodes.add(textElement);
- } else {
- nodes.add(new Text(bs));
- }
- }
-
- return nodes;
- }
-
- /**
- * Sets the font of the name entry field.
- */
- private void setNameFontItalic(boolean italic) {
- Font f = nameField.getFont();
- if (italic) {
- Font.font(f.getFamily(), FontPosture.ITALIC, f.getSize());
- } else {
- Font.font(f.getFamily(), FontPosture.REGULAR, f.getSize());
- }
- }
-
- /**
- * Returns the int representing the selected hierarchical group context.
- */
- private GroupHierarchyType getContext() {
- if (independentButton.isSelected()) {
- return GroupHierarchyType.INDEPENDENT;
- }
- if (intersectionButton.isSelected()) {
- return GroupHierarchyType.REFINING;
- }
- if (unionButton.isSelected()) {
- return GroupHierarchyType.INCLUDING;
- }
- return GroupHierarchyType.INDEPENDENT; // default
- }
-
- private void setContext(GroupHierarchyType context) {
- switch (context) {
- case INDEPENDENT:
- independentButton.setSelected(true);
- break;
- case REFINING:
- intersectionButton.setSelected(true);
- break;
- case INCLUDING:
- unionButton.setSelected(true);
- break;
- }
- }
-}
diff --git a/src/main/java/org/jabref/gui/groups/GroupDialogView.java b/src/main/java/org/jabref/gui/groups/GroupDialogView.java
new file mode 100644
index 00000000000..67b7122c553
--- /dev/null
+++ b/src/main/java/org/jabref/gui/groups/GroupDialogView.java
@@ -0,0 +1,159 @@
+package org.jabref.gui.groups;
+
+import java.util.EnumMap;
+
+import javafx.application.Platform;
+import javafx.fxml.FXML;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.ColorPicker;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.RadioButton;
+import javafx.scene.control.TextField;
+
+import org.jabref.gui.DialogService;
+import org.jabref.gui.util.BaseDialog;
+import org.jabref.gui.util.IconValidationDecorator;
+import org.jabref.gui.util.ViewModelListCellFactory;
+import org.jabref.logic.l10n.Localization;
+import org.jabref.model.database.BibDatabaseContext;
+import org.jabref.model.groups.AbstractGroup;
+import org.jabref.model.groups.GroupHierarchyType;
+import org.jabref.preferences.PreferencesService;
+
+import com.airhacks.afterburner.views.ViewLoader;
+import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer;
+
+public class GroupDialogView extends BaseDialog {
+
+ // Basic Settings
+ @FXML private TextField nameField;
+ @FXML private TextField descriptionField;
+ @FXML private TextField iconField;
+ @FXML private ColorPicker colorField;
+ @FXML private ComboBox hierarchicalContextCombo;
+
+ // Type
+ @FXML private RadioButton explicitRadioButton;
+ @FXML private RadioButton keywordsRadioButton;
+ @FXML private RadioButton searchRadioButton;
+ @FXML private RadioButton autoRadioButton;
+ @FXML private RadioButton texRadioButton;
+
+ // Option Groups
+ @FXML private TextField keywordGroupSearchTerm;
+ @FXML private TextField keywordGroupSearchField;
+ @FXML private CheckBox keywordGroupCaseSensitive;
+ @FXML private CheckBox keywordGroupRegex;
+
+ @FXML private TextField searchGroupSearchTerm;
+ @FXML private CheckBox searchGroupCaseSensitive;
+ @FXML private CheckBox searchGroupRegex;
+
+ @FXML private RadioButton autoGroupKeywordsOption;
+ @FXML private TextField autoGroupKeywordsField;
+ @FXML private TextField autoGroupKeywordsDeliminator;
+ @FXML private TextField autoGroupKeywordsHierarchicalDeliminator;
+ @FXML private RadioButton autoGroupPersonsOption;
+ @FXML private TextField autoGroupPersonsField;
+
+ @FXML private TextField texGroupFilePath;
+
+ private final EnumMap hierarchyText = new EnumMap<>(GroupHierarchyType.class);
+ private final EnumMap hierarchyToolTip = new EnumMap<>(GroupHierarchyType.class);
+
+ private final ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer();
+ private final GroupDialogViewModel viewModel;
+
+ public GroupDialogView(DialogService dialogService, BibDatabaseContext currentDatabase, PreferencesService preferencesService, AbstractGroup editedGroup) {
+ viewModel = new GroupDialogViewModel(dialogService, currentDatabase, preferencesService, editedGroup);
+
+ ViewLoader.view(this)
+ .load()
+ .setAsDialogPane(this);
+
+ if (editedGroup == null) {
+ this.setTitle(Localization.lang("Add subgroup"));
+ } else {
+ this.setTitle(Localization.lang("Edit group") + " " + editedGroup.getName());
+ }
+
+ setResultConverter(viewModel::resultConverter);
+ getDialogPane().getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
+ }
+
+ @FXML
+ public void initialize() {
+ hierarchyText.put(GroupHierarchyType.INCLUDING, Localization.lang("Union"));
+ hierarchyToolTip.put(GroupHierarchyType.INCLUDING, Localization.lang("Include subgroups: When selected, view entries contained in this group or its subgroups"));
+ hierarchyText.put(GroupHierarchyType.REFINING, Localization.lang("Intersection"));
+ hierarchyToolTip.put(GroupHierarchyType.REFINING, Localization.lang("Refine supergroup: When selected, view entries contained in both this group and its supergroup"));
+ hierarchyText.put(GroupHierarchyType.INDEPENDENT, Localization.lang("Independent"));
+ hierarchyToolTip.put(GroupHierarchyType.INDEPENDENT, Localization.lang("Independent group: When selected, view only this group's entries"));
+
+ nameField.textProperty().bindBidirectional(viewModel.nameProperty());
+ descriptionField.textProperty().bindBidirectional(viewModel.descriptionProperty());
+ iconField.textProperty().bindBidirectional(viewModel.iconProperty());
+ colorField.valueProperty().bindBidirectional(viewModel.colorFieldProperty());
+ hierarchicalContextCombo.itemsProperty().bind(viewModel.groupHierarchyListProperty());
+ new ViewModelListCellFactory()
+ .withText(hierarchyText::get)
+ .withStringTooltip(hierarchyToolTip::get)
+ .install(hierarchicalContextCombo);
+ hierarchicalContextCombo.valueProperty().bindBidirectional(viewModel.groupHierarchySelectedProperty());
+
+ explicitRadioButton.selectedProperty().bindBidirectional(viewModel.typeExplicitProperty());
+ keywordsRadioButton.selectedProperty().bindBidirectional(viewModel.typeKeywordsProperty());
+ searchRadioButton.selectedProperty().bindBidirectional(viewModel.typeSearchProperty());
+ autoRadioButton.selectedProperty().bindBidirectional(viewModel.typeAutoProperty());
+ texRadioButton.selectedProperty().bindBidirectional(viewModel.typeTexProperty());
+
+ keywordGroupSearchTerm.textProperty().bindBidirectional(viewModel.keywordGroupSearchTermProperty());
+ keywordGroupSearchField.textProperty().bindBidirectional(viewModel.keywordGroupSearchFieldProperty());
+ keywordGroupCaseSensitive.selectedProperty().bindBidirectional(viewModel.keywordGroupCaseSensitiveProperty());
+ keywordGroupRegex.selectedProperty().bindBidirectional(viewModel.keywordGroupRegexProperty());
+
+ searchGroupSearchTerm.textProperty().bindBidirectional(viewModel.searchGroupSearchTermProperty());
+ searchGroupCaseSensitive.selectedProperty().bindBidirectional(viewModel.searchGroupCaseSensitiveProperty());
+ searchGroupRegex.selectedProperty().bindBidirectional(viewModel.searchGroupRegexProperty());
+
+ autoGroupKeywordsOption.selectedProperty().bindBidirectional(viewModel.autoGroupKeywordsOptionProperty());
+ autoGroupKeywordsField.textProperty().bindBidirectional(viewModel.autoGroupKeywordsFieldProperty());
+ autoGroupKeywordsDeliminator.textProperty().bindBidirectional(viewModel.autoGroupKeywordsDeliminatorProperty());
+ autoGroupKeywordsHierarchicalDeliminator.textProperty().bindBidirectional(viewModel.autoGroupKeywordsHierarchicalDeliminatorProperty());
+ autoGroupPersonsOption.selectedProperty().bindBidirectional(viewModel.autoGroupPersonsOptionProperty());
+ autoGroupPersonsField.textProperty().bindBidirectional(viewModel.autoGroupPersonsFieldProperty());
+
+ texGroupFilePath.textProperty().bindBidirectional(viewModel.texGroupFilePathProperty());
+
+ validationVisualizer.setDecoration(new IconValidationDecorator());
+ Platform.runLater(() -> {
+ validationVisualizer.initVisualization(viewModel.nameValidationStatus(), nameField);
+ validationVisualizer.initVisualization(viewModel.nameContainsDelimiterValidationStatus(), nameField, false);
+ validationVisualizer.initVisualization(viewModel.sameNameValidationStatus(), nameField);
+ validationVisualizer.initVisualization(viewModel.searchRegexValidationStatus(), searchGroupSearchTerm);
+ validationVisualizer.initVisualization(viewModel.searchSearchTermEmptyValidationStatus(), searchGroupSearchTerm);
+ validationVisualizer.initVisualization(viewModel.keywordRegexValidationStatus(), keywordGroupSearchTerm);
+ validationVisualizer.initVisualization(viewModel.keywordSearchTermEmptyValidationStatus(), keywordGroupSearchTerm);
+ });
+
+ // Binding to the button throws a NPE, since it doesn't exist yet. Working around.
+ viewModel.validationStatus().validProperty().addListener((obs, oldValue, newValue) -> {
+ if (newValue) {
+ getDialogPane().lookupButton(ButtonType.OK).setDisable(false);
+ } else {
+ getDialogPane().lookupButton(ButtonType.OK).setDisable(true);
+ }
+ });
+ }
+
+ @FXML
+ private void texGroupBrowse() {
+ viewModel.texGroupBrowse();
+ }
+
+ @FXML
+ private void openHelp() {
+ viewModel.openHelpPage();
+ }
+}
diff --git a/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java b/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java
new file mode 100644
index 00000000000..47a2c88f52f
--- /dev/null
+++ b/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java
@@ -0,0 +1,465 @@
+package org.jabref.gui.groups;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ListProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+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.scene.control.ButtonType;
+import javafx.scene.paint.Color;
+
+import org.jabref.Globals;
+import org.jabref.gui.DialogService;
+import org.jabref.gui.help.HelpAction;
+import org.jabref.gui.icon.IconTheme;
+import org.jabref.gui.util.FileDialogConfiguration;
+import org.jabref.logic.auxparser.DefaultAuxParser;
+import org.jabref.logic.help.HelpFile;
+import org.jabref.logic.l10n.Localization;
+import org.jabref.logic.util.StandardFileType;
+import org.jabref.logic.util.io.FileUtil;
+import org.jabref.model.database.BibDatabase;
+import org.jabref.model.database.BibDatabaseContext;
+import org.jabref.model.entry.field.FieldFactory;
+import org.jabref.model.groups.AbstractGroup;
+import org.jabref.model.groups.AutomaticGroup;
+import org.jabref.model.groups.AutomaticKeywordGroup;
+import org.jabref.model.groups.AutomaticPersonsGroup;
+import org.jabref.model.groups.ExplicitGroup;
+import org.jabref.model.groups.GroupHierarchyType;
+import org.jabref.model.groups.GroupTreeNode;
+import org.jabref.model.groups.RegexKeywordGroup;
+import org.jabref.model.groups.SearchGroup;
+import org.jabref.model.groups.TexGroup;
+import org.jabref.model.groups.WordKeywordGroup;
+import org.jabref.model.metadata.MetaData;
+import org.jabref.model.strings.StringUtil;
+import org.jabref.preferences.PreferencesService;
+
+import de.saxsys.mvvmfx.utils.validation.CompositeValidator;
+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;
+
+public class GroupDialogViewModel {
+
+ // Basic Settings
+ private final StringProperty nameProperty = new SimpleStringProperty("");
+ private final StringProperty descriptionProperty = new SimpleStringProperty("");
+ private final StringProperty iconProperty = new SimpleStringProperty("");
+ private final ObjectProperty colorProperty = new SimpleObjectProperty<>();
+ private final ListProperty groupHierarchyListProperty = new SimpleListProperty<>();
+ private final ObjectProperty groupHierarchySelectedProperty = new SimpleObjectProperty<>();
+
+ // Type
+ private final BooleanProperty typeExplicitProperty = new SimpleBooleanProperty();
+ private final BooleanProperty typeKeywordsProperty = new SimpleBooleanProperty();
+ private final BooleanProperty typeSearchProperty = new SimpleBooleanProperty();
+ private final BooleanProperty typeAutoProperty = new SimpleBooleanProperty();
+ private final BooleanProperty typeTexProperty = new SimpleBooleanProperty();
+
+ // Option Groups
+ private final StringProperty keywordGroupSearchTermProperty = new SimpleStringProperty("");
+ private final StringProperty keywordGroupSearchFieldProperty = new SimpleStringProperty("");
+ private final BooleanProperty keywordGroupCaseSensitiveProperty = new SimpleBooleanProperty();
+ private final BooleanProperty keywordGroupRegexProperty = new SimpleBooleanProperty();
+
+ private final StringProperty searchGroupSearchTermProperty = new SimpleStringProperty("");
+ private final BooleanProperty searchGroupCaseSensitiveProperty = new SimpleBooleanProperty();
+ private final BooleanProperty searchGroupRegexProperty = new SimpleBooleanProperty();
+
+ private final BooleanProperty autoGroupKeywordsOptionProperty = new SimpleBooleanProperty();
+ private final StringProperty autoGroupKeywordsFieldProperty = new SimpleStringProperty("");
+ private final StringProperty autoGroupKeywordsDelimiterProperty = new SimpleStringProperty("");
+ private final StringProperty autoGroupKeywordsHierarchicalDelimiterProperty = new SimpleStringProperty("");
+ private final BooleanProperty autoGroupPersonsOptionProperty = new SimpleBooleanProperty();
+ private final StringProperty autoGroupPersonsFieldProperty = new SimpleStringProperty("");
+
+ private final StringProperty texGroupFilePathProperty = new SimpleStringProperty("");
+
+ private Validator nameValidator;
+ private Validator nameContainsDelimiterValidator;
+ private Validator sameNameValidator;
+ private Validator keywordRegexValidator;
+ private Validator keywordSearchTermEmptyValidator;
+ private Validator searchRegexValidator;
+ private Validator searchSearchTermEmptyValidator;
+ private CompositeValidator validator = new CompositeValidator();
+
+ private final DialogService dialogService;
+ private final PreferencesService preferencesService;
+ private final BibDatabaseContext currentDatabase;
+ private final AbstractGroup editedGroup;
+
+ public GroupDialogViewModel(DialogService dialogService, BibDatabaseContext currentDatabase, PreferencesService preferencesService, AbstractGroup editedGroup) {
+ this.dialogService = dialogService;
+ this.preferencesService = preferencesService;
+ this.currentDatabase = currentDatabase;
+ this.editedGroup = editedGroup;
+
+ setupValidation();
+ setValues();
+ }
+
+ private void setupValidation() {
+ nameValidator = new FunctionBasedValidator<>(
+ nameProperty,
+ StringUtil::isNotBlank,
+ ValidationMessage.error(Localization.lang("Please enter a name for the group.")));
+
+ nameContainsDelimiterValidator = new FunctionBasedValidator<>(
+ nameProperty,
+ name -> !name.contains(Character.toString(preferencesService.getKeywordDelimiter())),
+ ValidationMessage.warning(
+ Localization.lang(
+ "The group name contains the keyword separator \"%0\" and thus probably does not work as expected.",
+ Character.toString(preferencesService.getKeywordDelimiter())
+ )));
+
+ sameNameValidator = new FunctionBasedValidator<>(
+ nameProperty,
+ name -> {
+ Optional rootGroup = currentDatabase.getMetaData().getGroups();
+ if (rootGroup.isPresent()) {
+ int groupsWithSameName = rootGroup.get().findChildrenSatisfying(group -> group.getName().equals(name)).size();
+ if ((editedGroup == null) && (groupsWithSameName > 0)) {
+ // New group but there is already one group with the same name
+ return false;
+ }
+
+ if ((editedGroup != null) && !editedGroup.getName().equals(name) && (groupsWithSameName > 0)) {
+ // Edit group, changed name to something that is already present
+ return false;
+ }
+ }
+ return true;
+ },
+ ValidationMessage.error(Localization.lang("There exists already a group with the same name.")));
+
+ keywordRegexValidator = new FunctionBasedValidator<>(
+ keywordGroupSearchTermProperty,
+ input -> {
+ if (!keywordGroupRegexProperty.getValue()) {
+ return true;
+ }
+
+ if (StringUtil.isNullOrEmpty(input)) {
+ return false;
+ }
+
+ try {
+ Pattern.compile(input);
+ return true;
+ } catch (PatternSyntaxException ignored) {
+ return false;
+ }
+ },
+ ValidationMessage.error(String.format("%s > %n %s %n %n %s",
+ Localization.lang("Searching for keywords"),
+ Localization.lang("Keywords"),
+ Localization.lang("Invalid regular expression."))));
+
+ keywordSearchTermEmptyValidator = new FunctionBasedValidator<>(
+ keywordGroupSearchTermProperty,
+ input -> !StringUtil.isNullOrEmpty(input),
+ ValidationMessage.error(String.format("%s > %n %s %n %n %s",
+ Localization.lang("Searching for keywords"),
+ Localization.lang("Keywords"),
+ Localization.lang("Search term is empty.")
+ )));
+
+ searchRegexValidator = new FunctionBasedValidator<>(
+ searchGroupSearchTermProperty,
+ input -> {
+ if (!searchGroupRegexProperty.getValue()) {
+ return true;
+ }
+
+ if (StringUtil.isNullOrEmpty(input)) {
+ return false;
+ }
+
+ try {
+ Pattern.compile(input);
+ return true;
+ } catch (PatternSyntaxException ignored) {
+ return false;
+ }
+ },
+ ValidationMessage.error(String.format("%s > %n %s",
+ Localization.lang("Free search expression"),
+ Localization.lang("Invalid regular expression."))));
+
+ searchSearchTermEmptyValidator = new FunctionBasedValidator<>(
+ searchGroupSearchTermProperty,
+ input -> !StringUtil.isNullOrEmpty(input),
+ ValidationMessage.error(String.format("%s > %n %s",
+ Localization.lang("Free search expression"),
+ Localization.lang("Search term is empty.")
+ )));
+
+ validator.addValidators(nameValidator, sameNameValidator);
+
+ typeSearchProperty.addListener((obs,oldVal,newVal) -> {
+ if (newVal) {
+ validator.addValidators(searchRegexValidator, searchSearchTermEmptyValidator);
+ } else {
+ validator.removeValidators(searchRegexValidator, searchSearchTermEmptyValidator);
+ }
+ });
+
+ typeKeywordsProperty.addListener((obs,oldVal,newVal) -> {
+ if (newVal) {
+ validator.addValidators(keywordRegexValidator, keywordSearchTermEmptyValidator);
+ } else {
+ validator.removeValidators(keywordRegexValidator, keywordSearchTermEmptyValidator);
+ }
+ });
+ }
+
+ public AbstractGroup resultConverter(ButtonType button) {
+ if (button == ButtonType.OK) {
+ ValidationStatus validationStatus = validator.getValidationStatus();
+ if (validationStatus.getHighestMessage().isPresent()) {
+ dialogService.showErrorDialogAndWait(validationStatus.getHighestMessage().get().getMessage());
+ return null;
+ }
+
+ AbstractGroup resultingGroup = null;
+ try {
+ String groupName = nameProperty.getValue().trim();
+ if (typeExplicitProperty.getValue()) {
+ resultingGroup = new ExplicitGroup(
+ groupName,
+ groupHierarchySelectedProperty.getValue(),
+ preferencesService.getKeywordDelimiter());
+ } else if (typeKeywordsProperty.getValue()) {
+ if (keywordGroupRegexProperty.getValue()) {
+ resultingGroup = new RegexKeywordGroup(
+ groupName,
+ groupHierarchySelectedProperty.getValue(),
+ FieldFactory.parseField(keywordGroupSearchFieldProperty.getValue().trim()),
+ keywordGroupSearchTermProperty.getValue().trim(),
+ keywordGroupCaseSensitiveProperty.getValue());
+ } else {
+ resultingGroup = new WordKeywordGroup(
+ groupName,
+ groupHierarchySelectedProperty.getValue(),
+ FieldFactory.parseField(keywordGroupSearchFieldProperty.getValue().trim()),
+ keywordGroupSearchTermProperty.getValue().trim(),
+ keywordGroupCaseSensitiveProperty.getValue(),
+ preferencesService.getKeywordDelimiter(),
+ false);
+ }
+ } else if (typeSearchProperty.getValue()) {
+ resultingGroup = new SearchGroup(
+ groupName,
+ groupHierarchySelectedProperty.getValue(),
+ searchGroupSearchTermProperty.getValue().trim(),
+ searchGroupCaseSensitiveProperty.getValue(),
+ searchGroupRegexProperty.getValue());
+ } else if (typeAutoProperty.getValue()) {
+ if (autoGroupKeywordsOptionProperty.getValue()) {
+ resultingGroup = new AutomaticKeywordGroup(
+ groupName,
+ groupHierarchySelectedProperty.getValue(),
+ FieldFactory.parseField(autoGroupKeywordsFieldProperty.getValue().trim()),
+ autoGroupKeywordsDelimiterProperty.getValue().charAt(0),
+ autoGroupKeywordsHierarchicalDelimiterProperty.getValue().charAt(0));
+ } else {
+ resultingGroup = new AutomaticPersonsGroup(
+ groupName,
+ groupHierarchySelectedProperty.getValue(),
+ FieldFactory.parseField(autoGroupPersonsFieldProperty.getValue().trim()));
+ }
+ } else if (typeTexProperty.getValue()) {
+ resultingGroup = TexGroup.create(
+ groupName,
+ groupHierarchySelectedProperty.getValue(),
+ Paths.get(texGroupFilePathProperty.getValue().trim()),
+ new DefaultAuxParser(new BibDatabase()),
+ Globals.getFileUpdateMonitor(),
+ currentDatabase.getMetaData());
+ }
+
+ if (resultingGroup != null) {
+ resultingGroup.setColor(colorProperty.getValue());
+ resultingGroup.setDescription(descriptionProperty.getValue());
+ resultingGroup.setIconName(iconProperty.getValue());
+ return resultingGroup;
+ } else {
+ return null;
+ }
+
+ } catch (IllegalArgumentException | IOException exception) {
+ dialogService.showErrorDialogAndWait(exception.getLocalizedMessage(), exception);
+ return null;
+ }
+ }
+ return null;
+ }
+
+ public void setValues() {
+ groupHierarchyListProperty.setValue(FXCollections.observableArrayList(GroupHierarchyType.values()));
+
+ if (editedGroup == null) {
+ // creating new group -> defaults!
+ colorProperty.setValue(IconTheme.getDefaultGroupColor());
+ typeExplicitProperty.setValue(true);
+ groupHierarchySelectedProperty.setValue(GroupHierarchyType.INDEPENDENT);
+ } else {
+ nameProperty.setValue(editedGroup.getName());
+ colorProperty.setValue(editedGroup.getColor().orElse(IconTheme.getDefaultGroupColor()));
+ descriptionProperty.setValue(editedGroup.getDescription().orElse(""));
+ iconProperty.setValue(editedGroup.getIconName().orElse(""));
+ groupHierarchySelectedProperty.setValue(editedGroup.getHierarchicalContext());
+
+ if (editedGroup.getClass() == WordKeywordGroup.class) {
+ typeKeywordsProperty.setValue(true);
+
+ WordKeywordGroup group = (WordKeywordGroup) editedGroup;
+ keywordGroupSearchFieldProperty.setValue(group.getSearchField().getName());
+ keywordGroupSearchTermProperty.setValue(group.getSearchExpression());
+ keywordGroupCaseSensitiveProperty.setValue(group.isCaseSensitive());
+ keywordGroupRegexProperty.setValue(false);
+ } else if (editedGroup.getClass() == RegexKeywordGroup.class) {
+ typeKeywordsProperty.setValue(true);
+
+ RegexKeywordGroup group = (RegexKeywordGroup) editedGroup;
+ keywordGroupSearchFieldProperty.setValue(group.getSearchField().getName());
+ keywordGroupSearchTermProperty.setValue(group.getSearchExpression());
+ keywordGroupCaseSensitiveProperty.setValue(group.isCaseSensitive());
+ keywordGroupRegexProperty.setValue(true);
+ } else if (editedGroup.getClass() == SearchGroup.class) {
+ typeSearchProperty.setValue(true);
+
+ SearchGroup group = (SearchGroup) editedGroup;
+ searchGroupSearchTermProperty.setValue(group.getSearchExpression());
+ searchGroupCaseSensitiveProperty.setValue(group.isCaseSensitive());
+ searchGroupRegexProperty.setValue(group.isRegularExpression());
+ } else if (editedGroup.getClass() == ExplicitGroup.class) {
+ typeExplicitProperty.setValue(true);
+ } else if (editedGroup instanceof AutomaticGroup) {
+ typeAutoProperty.setValue(true);
+
+ if (editedGroup.getClass() == AutomaticKeywordGroup.class) {
+ AutomaticKeywordGroup group = (AutomaticKeywordGroup) editedGroup;
+ autoGroupKeywordsDelimiterProperty.setValue(group.getKeywordDelimiter().toString());
+ autoGroupKeywordsHierarchicalDelimiterProperty.setValue(group.getKeywordHierarchicalDelimiter().toString());
+ autoGroupKeywordsFieldProperty.setValue(group.getField().getName());
+ } else if (editedGroup.getClass() == AutomaticPersonsGroup.class) {
+ AutomaticPersonsGroup group = (AutomaticPersonsGroup) editedGroup;
+ autoGroupPersonsFieldProperty.setValue(group.getField().getName());
+ }
+ } else if (editedGroup.getClass() == TexGroup.class) {
+ typeTexProperty.setValue(true);
+
+ TexGroup group = (TexGroup) editedGroup;
+ texGroupFilePathProperty.setValue(group.getFilePath().toString());
+ }
+ }
+ }
+
+ public void texGroupBrowse() {
+ FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder()
+ .addExtensionFilter(StandardFileType.AUX)
+ .withDefaultExtension(StandardFileType.AUX)
+ .withInitialDirectory(preferencesService.getWorkingDir()).build();
+ dialogService.showFileOpenDialog(fileDialogConfiguration)
+ .ifPresent(file -> texGroupFilePathProperty.setValue(
+ FileUtil.relativize(file.toAbsolutePath(), getFileDirectoriesAsPaths()).toString()
+ ));
+ }
+
+ public void openHelpPage() {
+ HelpAction.openHelpPage(HelpFile.GROUPS);
+ }
+
+ private List getFileDirectoriesAsPaths() {
+ List fileDirs = new ArrayList<>();
+ MetaData metaData = currentDatabase.getMetaData();
+ metaData.getLaTexFileDirectory(preferencesService.getFilePreferences().getUser()).ifPresent(fileDirs::add);
+
+ return fileDirs;
+ }
+
+ public ValidationStatus validationStatus() { return validator.getValidationStatus(); }
+
+ public ValidationStatus nameValidationStatus() { return nameValidator.getValidationStatus(); }
+
+ public ValidationStatus nameContainsDelimiterValidationStatus() { return nameContainsDelimiterValidator.getValidationStatus(); }
+
+ public ValidationStatus sameNameValidationStatus() { return sameNameValidator.getValidationStatus(); }
+
+ public ValidationStatus searchRegexValidationStatus() { return searchRegexValidator.getValidationStatus(); }
+
+ public ValidationStatus searchSearchTermEmptyValidationStatus() { return searchSearchTermEmptyValidator.getValidationStatus(); }
+
+ public ValidationStatus keywordRegexValidationStatus() { return keywordRegexValidator.getValidationStatus(); }
+
+ public ValidationStatus keywordSearchTermEmptyValidationStatus() { return keywordSearchTermEmptyValidator.getValidationStatus(); }
+
+ public StringProperty nameProperty() { return nameProperty; }
+
+ public StringProperty descriptionProperty() { return descriptionProperty; }
+
+ public StringProperty iconProperty() { return iconProperty; }
+
+ public ObjectProperty colorFieldProperty() { return colorProperty; }
+
+ public ListProperty groupHierarchyListProperty() { return groupHierarchyListProperty; }
+
+ public ObjectProperty groupHierarchySelectedProperty() { return groupHierarchySelectedProperty; }
+
+ public BooleanProperty typeExplicitProperty() { return typeExplicitProperty; }
+
+ public BooleanProperty typeKeywordsProperty() { return typeKeywordsProperty; }
+
+ public BooleanProperty typeSearchProperty() { return typeSearchProperty; }
+
+ public BooleanProperty typeAutoProperty() { return typeAutoProperty; }
+
+ public BooleanProperty typeTexProperty() { return typeTexProperty; }
+
+ public StringProperty keywordGroupSearchTermProperty() { return keywordGroupSearchTermProperty; }
+
+ public StringProperty keywordGroupSearchFieldProperty() { return keywordGroupSearchFieldProperty; }
+
+ public BooleanProperty keywordGroupCaseSensitiveProperty() { return keywordGroupCaseSensitiveProperty; }
+
+ public BooleanProperty keywordGroupRegexProperty() { return keywordGroupRegexProperty; }
+
+ public StringProperty searchGroupSearchTermProperty() { return searchGroupSearchTermProperty; }
+
+ public BooleanProperty searchGroupCaseSensitiveProperty() { return searchGroupCaseSensitiveProperty; }
+
+ public BooleanProperty searchGroupRegexProperty() { return searchGroupRegexProperty; }
+
+ public BooleanProperty autoGroupKeywordsOptionProperty() { return autoGroupKeywordsOptionProperty; }
+
+ public StringProperty autoGroupKeywordsFieldProperty() { return autoGroupKeywordsFieldProperty; }
+
+ public StringProperty autoGroupKeywordsDeliminatorProperty() { return autoGroupKeywordsDelimiterProperty; }
+
+ public StringProperty autoGroupKeywordsHierarchicalDeliminatorProperty() { return autoGroupKeywordsHierarchicalDelimiterProperty; }
+
+ public BooleanProperty autoGroupPersonsOptionProperty() { return autoGroupPersonsOptionProperty; }
+
+ public StringProperty autoGroupPersonsFieldProperty() { return autoGroupPersonsFieldProperty; }
+
+ public StringProperty texGroupFilePathProperty() { return texGroupFilePathProperty; }
+}
diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeView.java b/src/main/java/org/jabref/gui/groups/GroupTreeView.java
index b410e6be80d..aad18e6af6d 100644
--- a/src/main/java/org/jabref/gui/groups/GroupTreeView.java
+++ b/src/main/java/org/jabref/gui/groups/GroupTreeView.java
@@ -45,6 +45,7 @@
import org.jabref.logic.l10n.Localization;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.groups.AllEntriesGroup;
+import org.jabref.preferences.PreferencesService;
import org.controlsfx.control.textfield.CustomTextField;
import org.controlsfx.control.textfield.TextFields;
@@ -67,6 +68,8 @@ public class GroupTreeView {
@Inject private StateManager stateManager;
@Inject private DialogService dialogService;
@Inject private TaskExecutor taskExecutor;
+ @Inject private PreferencesService preferencesService;
+
private GroupTreeViewModel viewModel;
private CustomLocalDragboard localDragboard;
@@ -75,7 +78,7 @@ public class GroupTreeView {
@FXML
public void initialize() {
this.localDragboard = GUIGlobals.localDragboard;
- viewModel = new GroupTreeViewModel(stateManager, dialogService, taskExecutor, localDragboard);
+ viewModel = new GroupTreeViewModel(stateManager, dialogService, preferencesService, taskExecutor, localDragboard);
// Set-up groups tree
groupTree.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java
index 47ae8b58c62..d0b77597aad 100644
--- a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java
+++ b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java
@@ -29,6 +29,7 @@
import org.jabref.model.groups.ExplicitGroup;
import org.jabref.model.groups.GroupTreeNode;
import org.jabref.model.metadata.MetaData;
+import org.jabref.preferences.PreferencesService;
import org.fxmisc.easybind.EasyBind;
@@ -38,6 +39,7 @@ public class GroupTreeViewModel extends AbstractViewModel {
private final ListProperty selectedGroups = new SimpleListProperty<>(FXCollections.observableArrayList());
private final StateManager stateManager;
private final DialogService dialogService;
+ private final PreferencesService preferences;
private final TaskExecutor taskExecutor;
private final CustomLocalDragboard localDragboard;
private final ObjectProperty> filterPredicate = new SimpleObjectProperty<>();
@@ -47,9 +49,10 @@ public class GroupTreeViewModel extends AbstractViewModel {
.compareToIgnoreCase(v2.getName());
private Optional currentDatabase;
- public GroupTreeViewModel(StateManager stateManager, DialogService dialogService, TaskExecutor taskExecutor, CustomLocalDragboard localDragboard) {
+ public GroupTreeViewModel(StateManager stateManager, DialogService dialogService, PreferencesService preferencesService, TaskExecutor taskExecutor, CustomLocalDragboard localDragboard) {
this.stateManager = Objects.requireNonNull(stateManager);
this.dialogService = Objects.requireNonNull(dialogService);
+ this.preferences = Objects.requireNonNull(preferencesService);
this.taskExecutor = Objects.requireNonNull(taskExecutor);
this.localDragboard = Objects.requireNonNull(localDragboard);
@@ -142,69 +145,83 @@ private void onActiveDatabaseChanged(Optional newDatabase) {
* Opens "New Group Dialog" and add the resulting group to the specified group
*/
public void addNewSubgroup(GroupNodeViewModel parent) {
- Optional newGroup = dialogService.showCustomDialogAndWait(new GroupDialog(dialogService));
- newGroup.ifPresent(group -> {
- parent.addSubgroup(group);
+ currentDatabase.ifPresent(database -> {
+ Optional newGroup = dialogService.showCustomDialogAndWait(new GroupDialogView(
+ dialogService,
+ database,
+ preferences,
+ null));
- // TODO: Add undo
- //UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup(parent, new GroupTreeNodeViewModel(newGroupNode), UndoableAddOrRemoveGroup.ADD_NODE);
- //panel.getUndoManager().addEdit(undo);
+ newGroup.ifPresent(group -> {
+ parent.addSubgroup(group);
- // TODO: Expand parent to make new group visible
- //parent.expand();
+ // TODO: Add undo
+ //UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup(parent, new GroupTreeNodeViewModel(newGroupNode), UndoableAddOrRemoveGroup.ADD_NODE);
+ //panel.getUndoManager().addEdit(undo);
- dialogService.notify(Localization.lang("Added group \"%0\".", group.getName()));
- writeGroupChangesToMetaData();
+ // TODO: Expand parent to make new group visible
+ //parent.expand();
+
+ dialogService.notify(Localization.lang("Added group \"%0\".", group.getName()));
+ writeGroupChangesToMetaData();
+ });
});
}
private void writeGroupChangesToMetaData() {
- currentDatabase.get().getMetaData().setGroups(rootGroup.get().getGroupNode());
+ currentDatabase.ifPresent(database -> database.getMetaData().setGroups(rootGroup.get().getGroupNode()));
}
/**
* Opens "Edit Group Dialog" and changes the given group to the edited one.
*/
public void editGroup(GroupNodeViewModel oldGroup) {
- Optional newGroup = dialogService
- .showCustomDialogAndWait(new GroupDialog(dialogService, oldGroup.getGroupNode().getGroup()));
- newGroup.ifPresent(group -> {
- // TODO: Keep assignments
- boolean keepPreviousAssignments = dialogService.showConfirmationDialogAndWait(
- Localization.lang("Change of Grouping Method"),
- Localization.lang("Assign the original group's entries to this group?"));
- // WarnAssignmentSideEffects.warnAssignmentSideEffects(newGroup, panel.frame());
- boolean removePreviousAssignments = (oldGroup.getGroupNode().getGroup() instanceof ExplicitGroup)
- && (group instanceof ExplicitGroup);
-
- oldGroup.getGroupNode().setGroup(
- group,
- keepPreviousAssignments,
- removePreviousAssignments,
- stateManager.getEntriesInCurrentDatabase());
-
- // TODO: Add undo
- // Store undo information.
- // AbstractUndoableEdit undoAddPreviousEntries = null;
- // UndoableModifyGroup undo = new UndoableModifyGroup(GroupSelector.this, groupsRoot, node, newGroup);
- // if (undoAddPreviousEntries == null) {
- // panel.getUndoManager().addEdit(undo);
- //} else {
- // NamedCompound nc = new NamedCompound("Modify Group");
- // nc.addEdit(undo);
- // nc.addEdit(undoAddPreviousEntries);
- // nc.end();/
- // panel.getUndoManager().addEdit(nc);
- //}
- //if (!addChange.isEmpty()) {
- // undoAddPreviousEntries = UndoableChangeEntriesOfGroup.getUndoableEdit(null, addChange);
- //}
-
- dialogService.notify(Localization.lang("Modified group \"%0\".", group.getName()));
- writeGroupChangesToMetaData();
-
- // This is ugly but we have no proper update mechanism in place to propagate the changes, so redraw everything
- refresh();
+ currentDatabase.ifPresent(database -> {
+ Optional newGroup = dialogService.showCustomDialogAndWait(new GroupDialogView(
+ dialogService,
+ database,
+ preferences,
+ oldGroup.getGroupNode().getGroup()));
+
+ newGroup.ifPresent(group -> {
+ // TODO: Keep assignments
+ boolean keepPreviousAssignments = dialogService.showConfirmationDialogAndWait(
+ Localization.lang("Change of Grouping Method"),
+ Localization.lang("Assign the original group's entries to this group?"));
+ // WarnAssignmentSideEffects.warnAssignmentSideEffects(newGroup, panel.frame());
+ boolean removePreviousAssignments = (oldGroup.getGroupNode().getGroup() instanceof ExplicitGroup)
+ && (group instanceof ExplicitGroup);
+
+ oldGroup.getGroupNode().setGroup(
+ group,
+ keepPreviousAssignments,
+ removePreviousAssignments,
+ database.getEntries());
+ // stateManager.getEntriesInCurrentDatabase());
+
+ // TODO: Add undo
+ // Store undo information.
+ // AbstractUndoableEdit undoAddPreviousEntries = null;
+ // UndoableModifyGroup undo = new UndoableModifyGroup(GroupSelector.this, groupsRoot, node, newGroup);
+ // if (undoAddPreviousEntries == null) {
+ // panel.getUndoManager().addEdit(undo);
+ //} else {
+ // NamedCompound nc = new NamedCompound("Modify Group");
+ // nc.addEdit(undo);
+ // nc.addEdit(undoAddPreviousEntries);
+ // nc.end();/
+ // panel.getUndoManager().addEdit(nc);
+ //}
+ //if (!addChange.isEmpty()) {
+ // undoAddPreviousEntries = UndoableChangeEntriesOfGroup.getUndoableEdit(null, addChange);
+ //}
+
+ dialogService.notify(Localization.lang("Modified group \"%0\".", group.getName()));
+ writeGroupChangesToMetaData();
+
+ // This is ugly but we have no proper update mechanism in place to propagate the changes, so redraw everything
+ refresh();
+ });
});
}
diff --git a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java
index d927845200a..bde67b260aa 100644
--- a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java
+++ b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java
@@ -131,7 +131,7 @@ private void setExactWidth(TableColumn, ?> column, double width) {
}
/**
- * Creates a text column to display any standard field.
+ * Creates a column with a continous number
*/
private TableColumn createIndexColumn(MainTableColumnModel columnModel) {
TableColumn column = new MainTableColumn<>(columnModel);
diff --git a/src/main/java/org/jabref/logic/help/HelpFile.java b/src/main/java/org/jabref/logic/help/HelpFile.java
index 6e5ce34fc12..b08b088c510 100644
--- a/src/main/java/org/jabref/logic/help/HelpFile.java
+++ b/src/main/java/org/jabref/logic/help/HelpFile.java
@@ -10,6 +10,7 @@ public enum HelpFile {
CONTENTS(""), // this is always the index
ENTRY_EDITOR("general/entryeditor"),
STRING_EDITOR("setup/stringeditor"),
+ GROUPS("finding-sorting-and-cleaning-entries/groups#groups-structure-creating-and-removing-groups"),
SPECIAL_FIELDS("fields/specialfields"),
BIBTEX_KEY_PATTERN("setup/bibtexkeypatterns"),
OWNER("fields/owner"),
diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties
index a74b6c18071..7b0b9e6ec57 100644
--- a/src/main/resources/l10n/JabRef_en.properties
+++ b/src/main/resources/l10n/JabRef_en.properties
@@ -49,8 +49,6 @@ Added\ group\ "%0".=Added group "%0".
Added\ string=Added string
-Additionally,\ entries\ whose\ %0\ field\ does\ not\ contain\ %1\ can\ be\ assigned\ manually\ to\ this\ group\ by\ selecting\ them\ then\ using\ either\ drag\ and\ drop\ or\ the\ context\ menu.\ This\ process\ adds\ the\ term\ %1\ to\ each\ entry's\ %0\ field.\ Entries\ can\ be\ removed\ manually\ from\ this\ group\ by\ selecting\ them\ then\ using\ the\ context\ menu.\ This\ process\ removes\ the\ term\ %1\ from\ each\ entry's\ %0\ field.=Additionally, entries whose %0 field does not contain %1 can be assigned manually to this group by selecting them then using either drag and drop or the context menu. This process adds the term %1 to each entry's %0 field. Entries can be removed manually from this group by selecting them then using the context menu. This process removes the term %1 from each entry's %0 field.
-
Advanced=Advanced
All\ entries=All entries
All\ entries\ of\ this\ type\ will\ be\ declared\ typeless.\ Continue?=All entries of this type will be declared typeless. Continue?
@@ -290,11 +288,8 @@ Enter\ URL\ to\ download=Enter URL to download
entries=entries
-Entries\ cannot\ be\ manually\ assigned\ to\ or\ removed\ from\ this\ group.=Entries cannot be manually assigned to or removed from this group.
-
Entries\ exported\ to\ clipboard=Entries exported to clipboard
-
entry=entry
Entry\ editor=Entry editor
@@ -495,6 +490,8 @@ keys\ in\ library=keys in library
Keyword=Keyword
+Keywords=Keywords
+
Label=Label
Language=Language
@@ -637,10 +634,6 @@ File\ has\ no\ attached\ annotations=File has no attached annotations
Please\ enter\ a\ name\ for\ the\ group.=Please enter a name for the group.
-Please\ enter\ a\ search\ term.\ For\ example,\ to\ search\ all\ fields\ for\ Smith,\ enter\:smith
To\ search\ the\ field\ Author\ for\ Smith\ and\ the\ field\ Title\ for\ electrical,\ enter\:
author\=smith\ and\ title\=electrical=Please enter a search term. For example, to search all fields for Smith, enter:
smith
To search the field Author for Smith and the field Title for electrical, enter:
author=smith and title=electrical
-
-Please\ enter\ the\ field\ to\ search\ (e.g.\ keywords)\ and\ the\ keyword\ to\ search\ it\ for\ (e.g.\ electrical).=Please enter the field to search (e.g. keywords) and the keyword to search it for (e.g. electrical).
-
Please\ enter\ the\ string's\ label=Please enter the string's label
Please\ restart\ JabRef\ for\ preferences\ to\ take\ effect.=Please restart JabRef for preferences to take effect.
@@ -872,20 +865,12 @@ The\ label\ of\ the\ string\ cannot\ contain\ the\ '\#'\ character.=The label of
The\ output\ option\ depends\ on\ a\ valid\ import\ option.=The output option depends on a valid import option.
-The\ regular\ expression\ %0\ is\ invalid\:=The regular expression %0 is invalid:
-
The\ search\ is\ case\ insensitive.=The search is case insensitive.
The\ search\ is\ case\ sensitive.=The search is case sensitive.
There\ are\ possible\ duplicates\ (marked\ with\ an\ icon)\ that\ haven't\ been\ resolved.\ Continue?=There are possible duplicates (marked with an icon) that haven't been resolved. Continue?
-This\ group\ contains\ entries\ based\ on\ manual\ assignment.\ Entries\ can\ be\ assigned\ to\ this\ group\ by\ selecting\ them\ then\ using\ either\ drag\ and\ drop\ or\ the\ context\ menu.\ Entries\ can\ be\ removed\ from\ this\ group\ by\ selecting\ them\ then\ using\ the\ context\ menu.=This group contains entries based on manual assignment. Entries can be assigned to this group by selecting them then using either drag and drop or the context menu. Entries can be removed from this group by selecting them then using the context menu.
-
-This\ group\ contains\ entries\ whose\ %0\ field\ contains\ the\ keyword\ %1=This group contains entries whose %0 field contains the keyword %1
-
-This\ group\ contains\ entries\ whose\ %0\ field\ contains\ the\ regular\ expression\ %1=This group contains entries whose %0 field contains the regular expression %1
-
This\ operation\ requires\ all\ selected\ entries\ to\ have\ BibTeX\ keys\ defined.=This operation requires all selected entries to have BibTeX keys defined.
This\ operation\ requires\ one\ or\ more\ entries\ to\ be\ selected.=This operation requires one or more entries to be selected.
@@ -1025,8 +1010,6 @@ Could\ not\ save,\ file\ locked\ by\ another\ JabRef\ instance.=Could not save,
Metadata\ change=Metadata change
The\ following\ metadata\ changed\:=The following metadata changed:
-Generate\ groups\ for\ author\ last\ names=Generate groups for author last names
-Generate\ groups\ from\ keywords\ in\ a\ BibTeX\ field=Generate groups from keywords in a BibTeX field
Enforce\ legal\ characters\ in\ BibTeX\ keys=Enforce legal characters in BibTeX keys
Unable\ to\ create\ backup=Unable to create backup
@@ -1220,7 +1203,7 @@ Not\ connected\ to\ any\ Writer\ document.\ Please\ make\ sure\ a\ document\ is\
Removed\ all\ subgroups\ of\ group\ "%0".=Removed all subgroups of group "%0".
To\ disable\ the\ memory\ stick\ mode\ rename\ or\ remove\ the\ jabref.xml\ file\ in\ the\ same\ folder\ as\ JabRef.=To disable the memory stick mode rename or remove the jabref.xml file in the same folder as JabRef.
Unable\ to\ connect.\ One\ possible\ reason\ is\ that\ JabRef\ and\ OpenOffice/LibreOffice\ are\ not\ both\ running\ in\ either\ 32\ bit\ mode\ or\ 64\ bit\ mode.=Unable to connect. One possible reason is that JabRef and OpenOffice/LibreOffice are not both running in either 32 bit mode or 64 bit mode.
-Use\ the\ following\ delimiter\ character(s)\:=Use the following delimiter character(s):
+Delimiter(s)=Delimiter(s)
When\ downloading\ files,\ or\ moving\ linked\ files\ to\ the\ file\ directory,\ prefer\ the\ BIB\ file\ location\ rather\ than\ the\ file\ directory\ set\ above=When downloading files, or moving linked files to the file directory, prefer the BIB file location rather than the file directory set above
Your\ style\ file\ specifies\ the\ character\ format\ '%0',\ which\ is\ undefined\ in\ your\ current\ OpenOffice/LibreOffice\ document.=Your style file specifies the character format '%0', which is undefined in your current OpenOffice/LibreOffice document.
Your\ style\ file\ specifies\ the\ paragraph\ format\ '%0',\ which\ is\ undefined\ in\ your\ current\ OpenOffice/LibreOffice\ document.=Your style file specifies the paragraph format '%0', which is undefined in your current OpenOffice/LibreOffice document.
@@ -1939,7 +1922,6 @@ Server\ Timezone\:=Server Timezone\:
Remember\ Password=Remember Password
Use\ SSL=Use SSL
Move\ preprint\ information\ from\ 'URL'\ and\ 'journal'\ field\ to\ the\ 'eprint'\ field=Move preprint information from 'URL' and 'journal' field to the 'eprint' field
-Type=Type
Customize\ Export\ Formats=Customize Export Formats
Export\ name=Export name
Main\ layout\ file\:=Main layout file\:
@@ -2101,6 +2083,18 @@ Mark\ all\ changes\ as\ accepted=Mark all changes as accepted
Unmark\ all\ changes=Unmark all changes
Normalize\ newline\ characters=Normalize newline characters
Normalizes\ all\ newline\ characters\ in\ the\ field\ content.=Normalizes all newline characters in the field content.
-
Index=Index
+Independent=Independent
+Intersection=Intersection
+Union=Union
+Collect\ by=Collect by
+Explicit\ selection=Explicit selection
+Searching\ for\ keywords=Searching for keywords
+Free\ search\ expression=Free search expression
+Specified\ keywords=Specified keywords
+Cited\ entries=Cited entries
+Search\ term\ is\ empty.=Search term is empty.
+Invalid\ regular\ expression.=Invalid regular expression.
+Keyword\ delimiter=Keyword delimiter
+Hierarchical\ keyword\ delimiter=Hierarchical keyword delimiter
Escape\ ampersands=Escape ampersands
diff --git a/src/test/java/org/jabref/gui/groups/GroupTreeViewModelTest.java b/src/test/java/org/jabref/gui/groups/GroupTreeViewModelTest.java
index 6e90070a69b..40d37af02b4 100644
--- a/src/test/java/org/jabref/gui/groups/GroupTreeViewModelTest.java
+++ b/src/test/java/org/jabref/gui/groups/GroupTreeViewModelTest.java
@@ -15,6 +15,7 @@
import org.jabref.model.groups.ExplicitGroup;
import org.jabref.model.groups.GroupHierarchyType;
import org.jabref.model.groups.WordKeywordGroup;
+import org.jabref.preferences.PreferencesService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -34,7 +35,7 @@ public void setUp() throws Exception {
stateManager = new StateManager();
stateManager.activeDatabaseProperty().setValue(Optional.of(databaseContext));
taskExecutor = new CurrentThreadTaskExecutor();
- groupTree = new GroupTreeViewModel(stateManager, mock(DialogService.class), taskExecutor, new CustomLocalDragboard());
+ groupTree = new GroupTreeViewModel(stateManager, mock(DialogService.class), mock(PreferencesService.class), taskExecutor, new CustomLocalDragboard());
}
@Test