From d1484f89956f1eb694a3b9eb6d52bd9b66ebafa1 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Sun, 10 Nov 2019 20:57:55 +0100 Subject: [PATCH] Reorderable columns in maintable for groups, URI, file and eprint (#5544) * Initial * Added combined identifier column * Removed superfluous properties * Added display names and refactored * Fixed persistent state * changelog * Added columns to available set * checkstyle * Fixed bug in persistent view * Refactored parse, rewording, cleanups * Removed TableColumnsItemModel, cleanups * Added migration and some minor tweaks * Removed superfluous method getHeaderLabel * Cleanups, seperating specialFieldsPreferences, added width-binding * Refactored according to remarks * Added Tests --- CHANGELOG.md | 1 + src/main/java/org/jabref/gui/GUIGlobals.java | 4 - .../gui/maintable/BibEntryTableViewModel.java | 13 + .../gui/maintable/ColumnPreferences.java | 64 +--- ...ormalTableColumn.java => FieldColumn.java} | 44 +-- .../jabref/gui/maintable/MainTableColumn.java | 21 +- .../gui/maintable/MainTableColumnFactory.java | 344 +++++++++--------- .../gui/maintable/MainTableColumnModel.java | 174 +++++++++ .../PersistenceVisualStateTable.java | 31 +- .../preferences/TableColumnsItemModel.java | 90 ----- .../gui/preferences/TableColumnsTab.fxml | 10 - .../gui/preferences/TableColumnsTabView.java | 45 ++- .../preferences/TableColumnsTabViewModel.java | 159 ++++---- .../SpecialFieldsPreferences.java | 22 ++ .../java/org/jabref/gui/util/FieldsUtil.java | 31 -- .../migrations/PreferencesMigrations.java | 28 ++ .../jabref/preferences/JabRefPreferences.java | 119 +++--- src/main/resources/l10n/JabRef_en.properties | 14 +- .../maintable/MainTableColumnModelTest.java | 39 ++ .../migrations/PreferencesMigrationsTest.java | 30 ++ 20 files changed, 691 insertions(+), 592 deletions(-) rename src/main/java/org/jabref/gui/maintable/{NormalTableColumn.java => FieldColumn.java} (76%) create mode 100644 src/main/java/org/jabref/gui/maintable/MainTableColumnModel.java delete mode 100644 src/main/java/org/jabref/gui/preferences/TableColumnsItemModel.java create mode 100644 src/main/java/org/jabref/gui/specialfields/SpecialFieldsPreferences.java create mode 100644 src/test/java/org/jabref/gui/maintable/MainTableColumnModelTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 4df363334e1..17873d43bf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - The entry editor is now open by default when JabRef starts up. [#5460](https://github.com/JabRef/jabref/issues/5460) - We added a new ADS fetcher to use the new ADS API [#4949](https://github.com/JabRef/jabref/issues/4949) - We added support of the [X11 primary selection](https://unix.stackexchange.com/a/139193/18033) [#2389](https://github.com/JabRef/jabref/issues/2389) +- We made the columns for groups, files and uri in the main table reorderable and merged the clickable icon columns for uri, url, doi and eprint. [#5544](https://github.com/JabRef/jabref/pull/5544) ### Fixed diff --git a/src/main/java/org/jabref/gui/GUIGlobals.java b/src/main/java/org/jabref/gui/GUIGlobals.java index 909465a1e43..21c19c98081 100644 --- a/src/main/java/org/jabref/gui/GUIGlobals.java +++ b/src/main/java/org/jabref/gui/GUIGlobals.java @@ -22,10 +22,6 @@ public class GUIGlobals { public static CustomLocalDragboard localDragboard = new CustomLocalDragboard(); - public static final int WIDTH_ICON_COL = 16 + 12; // add some additional space to improve appearance - - public static final int WIDTH_ICON_COL_RANKING = 5 * 16; // Width of Ranking Icon Column - public static final String UNTITLED_TITLE = Localization.lang("untitled"); private GUIGlobals() { diff --git a/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java b/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java index af7358b450b..d7391790d7a 100644 --- a/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java +++ b/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java @@ -1,7 +1,9 @@ package org.jabref.gui.maintable; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -50,6 +52,17 @@ public ObservableValue> getLinkedFiles() { return EasyBind.map(getField(StandardField.FILE), FileFieldParser::parse); } + public ObservableValue> getLinkedIdentifiers() { + SimpleObjectProperty> linkedIdentifiers = new SimpleObjectProperty<>(new HashMap<>()); + + entry.getField(StandardField.URL).ifPresent(value -> linkedIdentifiers.getValue().put(StandardField.URL, value)); + entry.getField(StandardField.DOI).ifPresent(value -> linkedIdentifiers.getValue().put(StandardField.DOI, value)); + entry.getField(StandardField.URI).ifPresent(value -> linkedIdentifiers.getValue().put(StandardField.URI, value)); + entry.getField(StandardField.EPRINT).ifPresent(value -> linkedIdentifiers.getValue().put(StandardField.EPRINT, value)); + + return linkedIdentifiers; + } + public ObservableValue> getMatchedGroups(BibDatabaseContext database) { SimpleObjectProperty> matchedGroups = new SimpleObjectProperty<>(Collections.emptyList()); diff --git a/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java b/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java index f2a8364f7c7..e80ed4b4bfa 100644 --- a/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java +++ b/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java @@ -7,71 +7,23 @@ public class ColumnPreferences { - public static final double DEFAULT_FIELD_LENGTH = 100; - private final boolean showFileColumn; - private final boolean showUrlColumn; - private final boolean preferDoiOverUrl; - private final boolean showEPrintColumn; - private final List columnNames; - private final boolean specialFieldsEnabled; - private final boolean autoSyncSpecialFieldsToKeyWords; - private final boolean serializeSpecialFields; + public static final double DEFAULT_WIDTH = 100; + public static final double ICON_COLUMN_WIDTH = 16 + 12; // add some additional space to improve appearance + + private final List columns; private final boolean extraFileColumnsEnabled; - private final Map columnWidths; private final Map columnSortType; - public ColumnPreferences(boolean showFileColumn, boolean showUrlColumn, boolean preferDoiOverUrl, boolean showEPrintColumn, List columnNames, boolean specialFieldsEnabled, boolean autoSyncSpecialFieldsToKeyWords, boolean serializeSpecialFields, boolean extraFileColumnsEnabled, Map columnWidths, Map columnSortType) { - this.showFileColumn = showFileColumn; - this.showUrlColumn = showUrlColumn; - this.preferDoiOverUrl = preferDoiOverUrl; - this.showEPrintColumn = showEPrintColumn; - this.columnNames = columnNames; - this.specialFieldsEnabled = specialFieldsEnabled; - this.autoSyncSpecialFieldsToKeyWords = autoSyncSpecialFieldsToKeyWords; - this.serializeSpecialFields = serializeSpecialFields; + public ColumnPreferences(List columns, boolean extraFileColumnsEnabled, Map columnSortType) { + this.columns = columns; this.extraFileColumnsEnabled = extraFileColumnsEnabled; - this.columnWidths = columnWidths; this.columnSortType = columnSortType; } - public boolean showFileColumn() { - return showFileColumn; - } - - public boolean showUrlColumn() { - return showUrlColumn; - } - - public boolean preferDoiOverUrl() { - return preferDoiOverUrl; - } - - public boolean showEprintColumn() { - return showEPrintColumn; - } - - public boolean getSpecialFieldsEnabled() { return specialFieldsEnabled; } - - public boolean getAutoSyncSpecialFieldsToKeyWords() { - return autoSyncSpecialFieldsToKeyWords; - } - - public boolean getSerializeSpecialFields() { - return serializeSpecialFields; - } - public boolean getExtraFileColumnsEnabled() { return extraFileColumnsEnabled; } - public List getColumnNames() { - return columnNames; - } - - public Map getColumnWidths() { - return columnWidths; - } - - public double getColumnWidth(String columnName) { - return columnWidths.getOrDefault(columnName, DEFAULT_FIELD_LENGTH); + public List getColumns() { + return columns; } public Map getSortTypesForColumns() { diff --git a/src/main/java/org/jabref/gui/maintable/NormalTableColumn.java b/src/main/java/org/jabref/gui/maintable/FieldColumn.java similarity index 76% rename from src/main/java/org/jabref/gui/maintable/NormalTableColumn.java rename to src/main/java/org/jabref/gui/maintable/FieldColumn.java index 11a8bc7861a..b288e5f2511 100644 --- a/src/main/java/org/jabref/gui/maintable/NormalTableColumn.java +++ b/src/main/java/org/jabref/gui/maintable/FieldColumn.java @@ -5,10 +5,7 @@ import javafx.beans.binding.Bindings; import javafx.beans.binding.ObjectBinding; import javafx.beans.value.ObservableValue; -import javafx.scene.Node; -import javafx.scene.control.Label; -import org.jabref.gui.icon.JabRefIcon; import org.jabref.logic.layout.LayoutFormatter; import org.jabref.logic.layout.format.LatexToUnicodeFormatter; import org.jabref.model.database.BibDatabase; @@ -18,26 +15,24 @@ import org.jabref.model.entry.field.InternalField; import org.jabref.model.entry.field.OrFields; -public class NormalTableColumn extends MainTableColumn { +/** + * A column that displays the text-value of the field + */ +public class FieldColumn extends MainTableColumn { private final OrFields bibtexFields; - private final boolean isIconColumn; - - private final Optional iconLabel; - private final Optional database; private final LayoutFormatter toUnicode = new LatexToUnicodeFormatter(); - private final String columnName; - public NormalTableColumn(String columnName, OrFields bibtexFields, BibDatabase database) { - super(columnName); - this.columnName = columnName; + public FieldColumn(MainTableColumnModel model, OrFields bibtexFields, BibDatabase database) { + super(model); this.bibtexFields = bibtexFields; - this.isIconColumn = false; - this.iconLabel = Optional.empty(); this.database = Optional.of(database); + + setText(getDisplayName()); + setCellValueFactory(param -> getColumnValue(param.getValue())); } /** @@ -45,17 +40,15 @@ public NormalTableColumn(String columnName, OrFields bibtexFields, BibDatabase d * * @return name to be displayed. null if field is empty. */ - public String getDisplayName() { - return bibtexFields.getDisplayName(); - } - @Override + public String getDisplayName() { return bibtexFields.getDisplayName(); } + public ObservableValue getColumnValue(BibEntryTableViewModel entry) { if (bibtexFields.isEmpty()) { return null; } - ObjectBinding[] dependencies = bibtexFields.stream().map(entry::getField).toArray(ObjectBinding[]::new); + ObjectBinding[] dependencies = bibtexFields.stream().map(entry::getField).toArray(ObjectBinding[]::new); return Bindings.createStringBinding(() -> computeText(entry), dependencies); } @@ -83,20 +76,12 @@ private String computeText(BibEntryTableViewModel entry) { return result; } - public Node getHeaderLabel() { - if (isIconColumn) { - return iconLabel.map(JabRefIcon::getGraphicNode).get(); - } else { - return new Label(getDisplayName()); - } - } - /** * Check if the value returned by getColumnValue() is the same as a simple check of the entry's field(s) would give * The reasons for being different are (combinations may also happen): - The entry has a crossref where the field * content is obtained from - The field has a string in it (which getColumnValue() resolves) - There are some alias * fields. For example, if the entry has a date field but no year field, {@link - * BibEntry#getResolvedFieldOrAlias(String, BibDatabase)} will return the year value from the date field when + * BibEntry#getResolvedFieldOrAlias(Field, BibDatabase)} will return the year value from the date field when * queried for year * * @param entry the BibEntry @@ -126,7 +111,4 @@ public boolean isResolved(BibEntry entry) { return (!resolvedFieldContent.equals(plainFieldContent)); } - public String getColumnName() { - return columnName; - } } diff --git a/src/main/java/org/jabref/gui/maintable/MainTableColumn.java b/src/main/java/org/jabref/gui/maintable/MainTableColumn.java index 815f623f3a0..efdfd28133a 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableColumn.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableColumn.java @@ -1,15 +1,24 @@ package org.jabref.gui.maintable; -import javafx.beans.value.ObservableValue; import javafx.scene.control.TableColumn; -abstract class MainTableColumn extends TableColumn { +import org.jabref.gui.util.BindingsHelper; - MainTableColumn(String text) { - super(text); +public class MainTableColumn extends TableColumn { - setCellValueFactory(param -> getColumnValue(param.getValue())); + private MainTableColumnModel model; + + public MainTableColumn(MainTableColumnModel model) { + this.model = model; + + BindingsHelper.bindBidirectional( + this.widthProperty(), + model.widthProperty(), + value -> this.setPrefWidth(model.widthProperty().getValue()), + value -> model.widthProperty().setValue(this.getWidth())); } - abstract ObservableValue getColumnValue(BibEntryTableViewModel entry); + public MainTableColumnModel getModel() { return model; } + + public String getDisplayName() { return model.getDisplayName(); } } diff --git a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java index 0ed9b632c2a..1448a7d68e7 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -17,7 +18,6 @@ import javafx.scene.control.TableColumn; import javafx.scene.control.Tooltip; import javafx.scene.input.MouseButton; -import javafx.scene.input.MouseEvent; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; @@ -25,7 +25,6 @@ import org.jabref.Globals; import org.jabref.gui.DialogService; -import org.jabref.gui.GUIGlobals; import org.jabref.gui.desktop.JabRefDesktop; import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; @@ -34,7 +33,7 @@ import org.jabref.gui.icon.JabRefIcon; import org.jabref.gui.specialfields.SpecialFieldValueViewModel; import org.jabref.gui.specialfields.SpecialFieldViewModel; -import org.jabref.gui.util.FieldsUtil; +import org.jabref.gui.specialfields.SpecialFieldsPreferences; import org.jabref.gui.util.OptionalValueTableCellFactory; import org.jabref.gui.util.ValueTableCellFactory; import org.jabref.gui.util.comparator.RankingFieldComparator; @@ -58,9 +57,7 @@ class MainTableColumnFactory { - private static final String ICON_COLUMN = "column-icon"; - private static final String GROUP_COLUMN = "column-groups"; - + private static final String STYLE_ICON_COLUMN = "column-icon"; private static final Logger LOGGER = LoggerFactory.getLogger(MainTableColumnFactory.class); private final ColumnPreferences preferences; @@ -70,7 +67,6 @@ class MainTableColumnFactory { private final UndoManager undoManager; private final DialogService dialogService; - public MainTableColumnFactory(BibDatabaseContext database, ColumnPreferences preferences, ExternalFileTypes externalFileTypes, UndoManager undoManager, DialogService dialogService) { this.database = Objects.requireNonNull(database); this.preferences = Objects.requireNonNull(preferences); @@ -82,44 +78,64 @@ public MainTableColumnFactory(BibDatabaseContext database, ColumnPreferences pre public List> createColumns() { List> columns = new ArrayList<>(); - columns.add(createGroupColumn()); - - if (preferences.showFileColumn()) { - columns.add(createFileColumn()); - } - - if (preferences.showUrlColumn()) { - if (preferences.preferDoiOverUrl()) { - columns.add(createUrlOrDoiColumn(IconTheme.JabRefIcons.DOI, StandardField.DOI, StandardField.URL)); - } else { - columns.add(createUrlOrDoiColumn(IconTheme.JabRefIcons.WWW, StandardField.URL, StandardField.DOI)); - } - } - - if (preferences.showEprintColumn()) { - columns.add(createEprintColumn(IconTheme.JabRefIcons.WWW, StandardField.EPRINT)); - } - preferences.getColumnNames().stream().map(FieldFactory::parseField).forEach(field -> { - if (field instanceof FieldsUtil.ExtraFilePseudoField) { - columns.add(createExtraFileColumn(field.getName())); - } else if (field instanceof SpecialField) { - columns.add(createSpecialFieldColumn((SpecialField) field)); - } else { - columns.add(createNormalColumn(field)); + preferences.getColumns().forEach(column -> { + + switch (column.getType()) { + case GROUPS: + columns.add(createGroupColumn(column)); + break; + case FILES: + columns.add(createFilesColumn(column)); + break; + case LINKED_IDENTIFIER: + columns.add(createIdentifierColumn(column)); + break; + case EXTRAFILE: + if (!column.getQualifier().isBlank()) { + columns.add(createExtraFileColumn(column)); } - }); + break; + case SPECIALFIELD: + if (!column.getQualifier().isBlank()) { + Field field = FieldFactory.parseField(column.getQualifier()); + if (field instanceof SpecialField) { + columns.add(createSpecialFieldColumn(column)); + } else { + LOGGER.warn(Localization.lang("Special field type %0 is unknown. Using normal column type.", column.getQualifier())); + columns.add(createFieldColumn(column)); + } + } + break; + default: + case NORMALFIELD: + if (!column.getQualifier().isBlank()) { + columns.add(createFieldColumn(column)); + } + break; + } + }); return columns; } - private TableColumn createGroupColumn() { - TableColumn> column = new TableColumn<>(); + private void setExactWidth(TableColumn column, double width) { + column.setMinWidth(width); + column.setPrefWidth(width); + column.setMaxWidth(width); + } + + /** + * Creates a column for group color bars. + */ + private TableColumn createGroupColumn(MainTableColumnModel columnModel) { + TableColumn> column = new MainTableColumn<>(columnModel); Node headerGraphic = IconTheme.JabRefIcons.DEFAULT_GROUP_ICON.getGraphicNode(); Tooltip.install(headerGraphic, new Tooltip(Localization.lang("Group color"))); column.setGraphic(headerGraphic); - column.getStyleClass().add(ICON_COLUMN); - setExactWidth(column, GUIGlobals.WIDTH_ICON_COL); + column.getStyleClass().add(STYLE_ICON_COLUMN); + setExactWidth(column, ColumnPreferences.ICON_COLUMN_WIDTH); + column.setResizable(false); column.setCellValueFactory(cellData -> cellData.getValue().getMatchedGroups(database)); new ValueTableCellFactory>() .withGraphic(this::createGroupColorRegion) @@ -153,8 +169,8 @@ private Node createGroupColorRegion(BibEntryTableViewModel entry, List createNormalColumn(Field field) { - String columnName = field.getName(); - NormalTableColumn column = new NormalTableColumn(columnName, FieldFactory.parseOrFields(columnName), database.getDatabase()); - new ValueTableCellFactory() - .withText(text -> text) - .install(column); - column.setSortable(true); - column.setPrefWidth(preferences.getColumnWidth(columnName)); + /** + * Creates a text column to display any standard field. + */ + private TableColumn createFieldColumn(MainTableColumnModel columnModel) { + FieldColumn column = new FieldColumn(columnModel, + FieldFactory.parseOrFields(columnModel.getQualifier()), + database.getDatabase()); + new ValueTableCellFactory() + .withText(text -> text) + .install(column); + column.setSortable(true); return column; } - private TableColumn> createSpecialFieldColumn(SpecialField specialField) { - TableColumn> column = new TableColumn<>(); + /** + * Creates a clickable icons column for DOIs, URLs, URIs and EPrints. + */ + private TableColumn> createIdentifierColumn(MainTableColumnModel columnModel) { + TableColumn> column = new MainTableColumn<>(columnModel); + Node headerGraphic = IconTheme.JabRefIcons.WWW.getGraphicNode(); + Tooltip.install(headerGraphic, new Tooltip(Localization.lang("Linked identifiers"))); + column.setGraphic(headerGraphic); + column.getStyleClass().add(STYLE_ICON_COLUMN); + setExactWidth(column, ColumnPreferences.ICON_COLUMN_WIDTH); + column.setResizable(false); + column.setCellValueFactory(cellData -> cellData.getValue().getLinkedIdentifiers()); + new ValueTableCellFactory>() + .withGraphic(this::createIdentifierGraphic) + .withTooltip(this::createIdentifierTooltip) + .withMenu(this::createIdentifierMenu) + .install(column); + return column; + } + + private Node createIdentifierGraphic(Map values) { + if (values.isEmpty()) { + return null; + } else { + return cellFactory.getTableIcon(StandardField.URL); + } + } + + private String createIdentifierTooltip(Map values) { + StringBuilder identifiers = new StringBuilder(); + values.keySet().forEach(field -> identifiers.append(field.getDisplayName()).append(": ").append(values.get(field)).append("\n")); + return identifiers.toString(); + } + + private ContextMenu createIdentifierMenu(BibEntryTableViewModel entry, Map values) { + ContextMenu contextMenu = new ContextMenu(); + + values.keySet().forEach(field -> { + MenuItem menuItem = new MenuItem(field.getDisplayName() + ": " + values.get(field), cellFactory.getTableIcon(field)); + menuItem.setOnAction(event -> { + try { + JabRefDesktop.openExternalViewer(database, values.get(field), field); + } catch (IOException e) { + dialogService.showErrorDialogAndWait(Localization.lang("Unable to open link."), e); + } + event.consume(); + }); + contextMenu.getItems().add(menuItem); + }); + + return contextMenu; + } + + /** + * A column that displays a SpecialField + */ + private TableColumn> createSpecialFieldColumn(MainTableColumnModel columnModel) { + SpecialField specialField = (SpecialField) FieldFactory.parseField(columnModel.getQualifier()); + TableColumn> column = new MainTableColumn<>(columnModel); SpecialFieldViewModel specialFieldViewModel = new SpecialFieldViewModel(specialField, undoManager); Node headerGraphic = specialFieldViewModel.getIcon().getGraphicNode(); Tooltip.install(headerGraphic, new Tooltip(specialFieldViewModel.getLocalization())); column.setGraphic(headerGraphic); - column.getStyleClass().add(ICON_COLUMN); + column.getStyleClass().add(STYLE_ICON_COLUMN); if (specialField == SpecialField.RANKING) { - setExactWidth(column, GUIGlobals.WIDTH_ICON_COL_RANKING); + setExactWidth(column, SpecialFieldsPreferences.COLUMN_RANKING_WIDTH); + column.setResizable(false); new OptionalValueTableCellFactory() - .withGraphicIfPresent(this::createRating) + .withGraphicIfPresent(this::createSpecialRating) .install(column); } else { - setExactWidth(column, GUIGlobals.WIDTH_ICON_COL); + setExactWidth(column, ColumnPreferences.ICON_COLUMN_WIDTH); + column.setResizable(false); if (specialField.isSingleValueField()) { new OptionalValueTableCellFactory() @@ -216,10 +294,11 @@ private TableColumn } column.setSortable(true); + return column; } - private Rating createRating(BibEntryTableViewModel entry, SpecialFieldValueViewModel value) { + private Rating createSpecialRating(BibEntryTableViewModel entry, SpecialFieldValueViewModel value) { Rating ranking = new Rating(); ranking.setRating(value.getValue().toRating()); EasyBind.subscribe(ranking.ratingProperty(), rating -> @@ -251,32 +330,31 @@ private Node createSpecialFieldIcon(Optional fieldVa }); } - private void setExactWidth(TableColumn column, int width) { - column.setMinWidth(width); - column.setPrefWidth(width); - column.setMaxWidth(width); - } - - private TableColumn> createFileColumn() { - TableColumn> column = new TableColumn<>(); + /** + * Creates a column for all the linked files. Instead of creating a column for a single file type, like {@code + * createExtraFileColumn} does, this creates one single column collecting all file links. + */ + private TableColumn> createFilesColumn(MainTableColumnModel columnModel) { + TableColumn> column = new MainTableColumn<>(columnModel); Node headerGraphic = IconTheme.JabRefIcons.FILE.getGraphicNode(); Tooltip.install(headerGraphic, new Tooltip(Localization.lang("Linked files"))); column.setGraphic(headerGraphic); - column.getStyleClass().add(ICON_COLUMN); - setExactWidth(column, GUIGlobals.WIDTH_ICON_COL); + column.getStyleClass().add(STYLE_ICON_COLUMN); + setExactWidth(column, ColumnPreferences.ICON_COLUMN_WIDTH); + column.setResizable(false); column.setCellValueFactory(cellData -> cellData.getValue().getLinkedFiles()); - new ValueTableCellFactory>() - .withGraphic(this::createFileIcon) - .withTooltip(this::createFileTooltip) - .withMenu(this::createFileMenu) - .withOnMouseClickedEvent((entry, linkedFiles) -> event -> { - if ((event.getButton() == MouseButton.PRIMARY) && (linkedFiles.size() == 1)) { - // Only one linked file -> open directly - LinkedFileViewModel linkedFileViewModel = new LinkedFileViewModel(linkedFiles.get(0), entry.getEntry(), database, Globals.TASK_EXECUTOR, dialogService, Globals.prefs.getXMPPreferences(), Globals.prefs.getFilePreferences(), externalFileTypes); - linkedFileViewModel.open(); - } - }) - .install(column); + new ValueTableCellFactory>() + .withGraphic(this::createFileIcon) + .withTooltip(this::createFileTooltip) + .withMenu(this::createFileMenu) + .withOnMouseClickedEvent((entry, linkedFiles) -> event -> { + if ((event.getButton() == MouseButton.PRIMARY) && (linkedFiles.size() == 1)) { + // Only one linked file -> open directly + LinkedFileViewModel linkedFileViewModel = new LinkedFileViewModel(linkedFiles.get(0), entry.getEntry(), database, Globals.TASK_EXECUTOR, dialogService, Globals.prefs.getXMPPreferences(), Globals.prefs.getFilePreferences(), externalFileTypes); + linkedFileViewModel.open(); + } + }) + .install(column); return column; } @@ -305,105 +383,37 @@ private ContextMenu createFileMenu(BibEntryTableViewModel entry, List createUrlOrDoiColumn(JabRefIcon icon, Field firstField, Field secondField) { - TableColumn column = new TableColumn<>(); - Node headerGraphic = icon.getGraphicNode(); - Tooltip.install(headerGraphic, new Tooltip(firstField.getDisplayName() + " / " + secondField.getDisplayName())); - column.setGraphic(headerGraphic); - column.getStyleClass().add(ICON_COLUMN); - setExactWidth(column, GUIGlobals.WIDTH_ICON_COL); - // icon is chosen based on field name in cell, so map fields to its names - column.setCellValueFactory(cellData -> EasyBind.monadic(cellData.getValue().getField(firstField)).map(x -> firstField).orElse(EasyBind.monadic(cellData.getValue().getField(secondField)).map(x -> secondField))); - new ValueTableCellFactory() - .withGraphic(cellFactory::getTableIcon) - .withTooltip(this::createIdentifierTooltip) - .withOnMouseClickedEvent((BibEntryTableViewModel entry, Field content) -> (MouseEvent event) -> openUrlOrDoi(event, entry, content)) - .install(column); - return column; - } - - private void openUrlOrDoi(MouseEvent event, BibEntryTableViewModel entry, Field field) { - if (event.getButton() != MouseButton.PRIMARY) { - return; - } - - if (!entry.getEntry().hasField(field)) { - LOGGER.error("Requested opening viewer for {} of entry '{}', but field is not present.", field, entry.getEntry().getId()); - return; - } - - entry.getEntry().getField(field).ifPresent(identifier -> { - try { - JabRefDesktop.openExternalViewer(database, identifier, field); - } catch (IOException e) { - dialogService.showErrorDialogAndWait(Localization.lang("Unable to open link."), e); - } - }); - event.consume(); - } - - private String createIdentifierTooltip(BibEntryTableViewModel entry, Field field) { - Optional value = entry.getEntry().getField(field); - if (value.isPresent()) { - if (StandardField.DOI.equals(field)) { - return Localization.lang("Open %0 URL (%1)", "DOI", value.get()); - } else if (StandardField.URL.equals(field)) { - return Localization.lang("Open URL (%0)", value.get()); - } else if (StandardField.EPRINT.equals(field)) { - return Localization.lang("Open %0 URL (%1)", "ArXiv", value.get()); - } - } - return null; - } - - private TableColumn createEprintColumn(JabRefIcon icon, Field field) { - TableColumn column = new TableColumn<>(); - column.setGraphic(icon.getGraphicNode()); - column.getStyleClass().add(ICON_COLUMN); - setExactWidth(column, GUIGlobals.WIDTH_ICON_COL); - column.setCellValueFactory(cellData -> EasyBind.monadic(cellData.getValue().getField(field)).map(x -> field)); - new ValueTableCellFactory() - .withGraphic(cellFactory::getTableIcon) - .withTooltip(this::createIdentifierTooltip) - .withOnMouseClickedEvent((BibEntryTableViewModel entry, Field content) -> (MouseEvent event) -> openUrlOrDoi(event, entry, field)) - .install(column); - return column; - } - - /** - * Creates a column for specific file types. Shows the icon for the given type (or the FILE_MULTIPLE icon) - * - * @param externalFileTypeName the name of the externalFileType - */ - private TableColumn> createExtraFileColumn(String externalFileTypeName) { - TableColumn> column = new TableColumn<>(); - column.setGraphic( - externalFileTypes.getExternalFileTypeByName(externalFileTypeName) - .map(ExternalFileType::getIcon).orElse(IconTheme.JabRefIcons.FILE) - .getGraphicNode()); - column.getStyleClass().add(ICON_COLUMN); - setExactWidth(column, GUIGlobals.WIDTH_ICON_COL); - column.setCellValueFactory(cellData -> cellData.getValue().getLinkedFiles()); - new ValueTableCellFactory>() - .withGraphic(linkedFiles -> createFileIcon(linkedFiles.stream().filter(linkedFile -> linkedFile.getFileType().equalsIgnoreCase(externalFileTypeName)).collect(Collectors.toList()))) - .install(column); - return column; - } - private Node createFileIcon(List linkedFiles) { if (linkedFiles.size() > 1) { return IconTheme.JabRefIcons.FILE_MULTIPLE.getGraphicNode(); } else if (linkedFiles.size() == 1) { return externalFileTypes.fromLinkedFile(linkedFiles.get(0), false) - .map(ExternalFileType::getIcon) - .orElse(IconTheme.JabRefIcons.FILE) - .getGraphicNode(); + .map(ExternalFileType::getIcon) + .orElse(IconTheme.JabRefIcons.FILE) + .getGraphicNode(); } else { return null; } } + + /** + * Creates a column for all the linked files of a single file type. + */ + private TableColumn> createExtraFileColumn(MainTableColumnModel columnModel) { + TableColumn> column = new MainTableColumn<>(columnModel); + column.setGraphic(externalFileTypes + .getExternalFileTypeByName(columnModel.getQualifier()) + .map(ExternalFileType::getIcon).orElse(IconTheme.JabRefIcons.FILE) + .getGraphicNode()); + column.getStyleClass().add(STYLE_ICON_COLUMN); + setExactWidth(column, ColumnPreferences.ICON_COLUMN_WIDTH); + column.setResizable(false); + column.setCellValueFactory(cellData -> cellData.getValue().getLinkedFiles()); + new ValueTableCellFactory>() + .withGraphic(linkedFiles -> createFileIcon(linkedFiles.stream().filter(linkedFile -> + linkedFile.getFileType().equalsIgnoreCase(columnModel.getQualifier())).collect(Collectors.toList()))) + .install(column); + + return column; + } } diff --git a/src/main/java/org/jabref/gui/maintable/MainTableColumnModel.java b/src/main/java/org/jabref/gui/maintable/MainTableColumnModel.java new file mode 100644 index 00000000000..99ce8bd9ce8 --- /dev/null +++ b/src/main/java/org/jabref/gui/maintable/MainTableColumnModel.java @@ -0,0 +1,174 @@ +package org.jabref.gui.maintable; + +import java.util.Objects; + +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyStringWrapper; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +import org.jabref.gui.util.FieldsUtil; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.entry.field.FieldFactory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Represents the full internal name of a column in the main table. Consists of two parts: + * The type of the column and a qualifier, like the field name to be displayed in the column. + */ +public class MainTableColumnModel { + + public static final Character COLUMNS_QUALIFIER_DELIMITER = ':'; + + private static final Logger LOGGER = LoggerFactory.getLogger(MainTableColumnModel.class); + + public enum Type { + EXTRAFILE("extrafile", Localization.lang("File type")), + FILES("files", Localization.lang("Linked files")), + GROUPS("groups", Localization.lang("Groups")), + LINKED_IDENTIFIER("linked_id", Localization.lang("Linked identifiers")), + NORMALFIELD("field"), + SPECIALFIELD("special", Localization.lang("Special")); + + private String name; + private String displayName; + + Type(String name) { + this.name = name; + this.displayName = name; + } + + Type(String name, String displayName) { + this.name = name; + this.displayName = displayName; + } + + public String getName() { + return name; + } + + public String getDisplayName() { + return displayName; + } + + public static Type fromString(String text) { + for (Type type : Type.values()) { + if (type.getName().equals(text)) { + return type; + } + } + LOGGER.warn(Localization.lang("Column type %0 is unknown.", text)); + return NORMALFIELD; + } + } + + private final ObjectProperty typeProperty = new SimpleObjectProperty<>(); + private final StringProperty qualifierProperty = new SimpleStringProperty(""); + private final DoubleProperty widthProperty = new SimpleDoubleProperty(ColumnPreferences.DEFAULT_WIDTH); + + /** + * This is used by the preferences dialog, to initialize available columns the user can add to the table. + * + * @param type the {@code MainTableColumnModel.Type} of the column, e.g. "NORMALFIELD" or "GROUPS" + * @param qualifier the stored qualifier of the column, e.g. "author/editor" + */ + public MainTableColumnModel(Type type, String qualifier) { + Objects.requireNonNull(type); + this.typeProperty.setValue(type); + this.qualifierProperty.setValue(qualifier); + } + + public MainTableColumnModel(Type type) { + this(type, ""); + } + + public Type getType() { return typeProperty.getValue(); } + + public String getQualifier() { return qualifierProperty.getValue(); } + + public String getName() { + if (qualifierProperty.getValue().isBlank()) { + return typeProperty.getValue().getName(); + } else { + return typeProperty.getValue().getName() + COLUMNS_QUALIFIER_DELIMITER + qualifierProperty.getValue(); + } + } + + public String getDisplayName() { + if ((typeProperty.getValue() == Type.GROUPS + || typeProperty.getValue() == Type.FILES + || typeProperty.getValue() == Type.LINKED_IDENTIFIER) + && qualifierProperty.getValue().isBlank()) { + return typeProperty.getValue().getDisplayName(); + } else { + return FieldsUtil.getNameWithType(FieldFactory.parseField(qualifierProperty.getValue())); + } + } + + public StringProperty nameProperty() { return new ReadOnlyStringWrapper(getDisplayName()); } + + public DoubleProperty widthProperty() { return widthProperty; } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + MainTableColumnModel that = (MainTableColumnModel) o; + + if (typeProperty != that.typeProperty) { + return false; + } + return Objects.equals(qualifierProperty, that.qualifierProperty); + } + + public int hashCode() { + return Objects.hash(typeProperty.getValue(), qualifierProperty.getValue()); + } + + /** + * This is used by JabRefPreferences, to create a new ColumnModel out ouf the stored preferences. + * + * @param rawColumnName the stored name of the column, e.g. "field:author" + * @param width the stored width of the column + */ + public static MainTableColumnModel parse(String rawColumnName, Double width) { + MainTableColumnModel columnModel = parse(rawColumnName); + + Objects.requireNonNull(width); + columnModel.widthProperty().setValue(width); + return columnModel; + } + + /** + * This is used by the preferences dialog, to allow the user to type in a field he wants to add to the table. + * + * @param rawColumnName the stored name of the column, e.g. "field:author", or "author" + */ + public static MainTableColumnModel parse(String rawColumnName) { + Objects.requireNonNull(rawColumnName); + String[] splittedName = rawColumnName.split(COLUMNS_QUALIFIER_DELIMITER.toString()); + + Type type = Type.fromString(splittedName[0]); + String qualifier = ""; + + if (type == Type.NORMALFIELD || type == Type.SPECIALFIELD || type == Type.EXTRAFILE) { + if (splittedName.length == 1) { + qualifier = splittedName[0]; // By default the rawColumnName is parsed as NORMALFIELD + } else { + qualifier = splittedName[1]; + } + } + + return new MainTableColumnModel(type, qualifier); + } +} diff --git a/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java b/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java index 0b7d7b1dbb4..e9ef10fcb74 100644 --- a/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java +++ b/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java @@ -1,9 +1,8 @@ package org.jabref.gui.maintable; -import java.util.ArrayList; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javafx.collections.ListChangeListener; import javafx.scene.control.TableColumn; @@ -26,8 +25,10 @@ public PersistenceVisualStateTable(final MainTable mainTable, JabRefPreferences this.preferences = preferences; mainTable.getColumns().addListener(this::onColumnsChanged); - mainTable.getColumns().forEach(col -> col.sortTypeProperty().addListener(obs -> - updateColumnSortType(col.getText(), col.getSortType()))); + mainTable.getColumns().forEach(col -> { + MainTableColumn column = (MainTableColumn) col; + col.sortTypeProperty().addListener(obs -> updateColumnSortType(column.getModel().getName(), column.getSortType())); + }); mainTable.getColumns().forEach(col -> col.widthProperty().addListener(obs -> updateColumnPreferences())); } @@ -53,22 +54,10 @@ private void updateColumnSortType(String text, SortType sortType) { * Store shown columns and their width in preferences. */ private void updateColumnPreferences() { - List columnNames = new ArrayList<>(); - List columnsWidths = new ArrayList<>(); - - for (TableColumn column : mainTable.getColumns()) { - if (column instanceof NormalTableColumn) { - NormalTableColumn normalColumn = (NormalTableColumn) column; - - columnNames.add(normalColumn.getColumnName()); - columnsWidths.add(String.valueOf(Double.valueOf(normalColumn.getWidth()).intValue())); - } - } - - if ((columnNames.size() == columnsWidths.size()) && - (columnNames.size() == preferences.getStringList(JabRefPreferences.COLUMN_NAMES).size())) { - preferences.putStringList(JabRefPreferences.COLUMN_NAMES, columnNames); - preferences.putStringList(JabRefPreferences.COLUMN_WIDTHS, columnsWidths); - } + ColumnPreferences oldColumnPreferences = preferences.getColumnPreferences(); + preferences.storeColumnPreferences(new ColumnPreferences( + mainTable.getColumns().stream().map(column -> ((MainTableColumn) column).getModel()).collect(Collectors.toList()), + oldColumnPreferences.getExtraFileColumnsEnabled(), + columnsSortOrder)); } } diff --git a/src/main/java/org/jabref/gui/preferences/TableColumnsItemModel.java b/src/main/java/org/jabref/gui/preferences/TableColumnsItemModel.java deleted file mode 100644 index 9ac5faa8fb7..00000000000 --- a/src/main/java/org/jabref/gui/preferences/TableColumnsItemModel.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.jabref.gui.preferences; - -import java.util.Objects; - -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.ReadOnlyBooleanProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleDoubleProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; -import javafx.beans.value.ObservableValue; - -import org.jabref.gui.maintable.ColumnPreferences; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.entry.field.Field; -import org.jabref.model.entry.field.UnknownField; - -public class TableColumnsItemModel { - - private final ObjectProperty field; - private final StringProperty name = new SimpleStringProperty(""); - private final DoubleProperty length = new SimpleDoubleProperty(ColumnPreferences.DEFAULT_FIELD_LENGTH); - private final BooleanProperty editableProperty = new SimpleBooleanProperty(true); - - public TableColumnsItemModel() { - this.field = new SimpleObjectProperty<>(new UnknownField(Localization.lang("New column"))); - } - - public TableColumnsItemModel(Field field) { - this.field = new SimpleObjectProperty<>(field); - this.editableProperty.setValue(this.field.get() instanceof UnknownField); - } - - public TableColumnsItemModel(Field field, double length) { - this.field = new SimpleObjectProperty<>(field); - this.length.setValue(length); - this.editableProperty.setValue(this.field.get() instanceof UnknownField); - } - - public void setField(Field field) { - this.field.set(field); - } - - public Field getField() { - return field.get(); - } - - public ObservableValue fieldProperty() { return this.field; } - - public void setName(String name) { - if (editableProperty.get()) { - field.setValue(new UnknownField(name)); - } - } - - public String getName() { - return field.get().getName(); - } - - public StringProperty nameProperty() { return this.name; } - - public void setLength(double length) { - this.length.set(length); - } - - public double getLength() { - return length.get(); - } - - public DoubleProperty lengthProperty() { return this.length; } - - public ReadOnlyBooleanProperty editableProperty() { return editableProperty; } - - @Override - public int hashCode() { - return Objects.hash(field); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof TableColumnsItemModel) { - return Objects.equals(this.field, ((TableColumnsItemModel) obj).field); - } else { - return false; - } - } -} diff --git a/src/main/java/org/jabref/gui/preferences/TableColumnsTab.fxml b/src/main/java/org/jabref/gui/preferences/TableColumnsTab.fxml index 1bd8fb9c59a..c2c1dd444e6 100644 --- a/src/main/java/org/jabref/gui/preferences/TableColumnsTab.fxml +++ b/src/main/java/org/jabref/gui/preferences/TableColumnsTab.fxml @@ -79,16 +79,6 @@ - - - - - - - - - - diff --git a/src/main/java/org/jabref/gui/preferences/TableColumnsTabView.java b/src/main/java/org/jabref/gui/preferences/TableColumnsTabView.java index fcf310daeef..2bf146a50b5 100644 --- a/src/main/java/org/jabref/gui/preferences/TableColumnsTabView.java +++ b/src/main/java/org/jabref/gui/preferences/TableColumnsTabView.java @@ -14,13 +14,12 @@ import org.jabref.gui.actions.StandardActions; import org.jabref.gui.help.HelpAction; import org.jabref.gui.icon.IconTheme; -import org.jabref.gui.util.FieldsUtil; +import org.jabref.gui.maintable.MainTableColumnModel; import org.jabref.gui.util.IconValidationDecorator; import org.jabref.gui.util.ValueTableCellFactory; import org.jabref.gui.util.ViewModelListCellFactory; import org.jabref.logic.help.HelpFile; import org.jabref.logic.l10n.Localization; -import org.jabref.model.entry.field.Field; import org.jabref.preferences.JabRefPreferences; import com.airhacks.afterburner.views.ViewLoader; @@ -28,15 +27,10 @@ public class TableColumnsTabView extends AbstractPreferenceTabView implements PreferencesTab { - @FXML private TableView columnsList; - @FXML private TableColumn nameColumn; - @FXML private TableColumn actionsColumn; - @FXML private ComboBox addColumnName; - @FXML private CheckBox showFileColumn; - @FXML private CheckBox showUrlColumn; - @FXML private RadioButton urlFirst; - @FXML private RadioButton doiFirst; - @FXML private CheckBox showEPrintColumn; + @FXML private TableView columnsList; + @FXML private TableColumn nameColumn; + @FXML private TableColumn actionsColumn; + @FXML private ComboBox addColumnName; @FXML private CheckBox specialFieldsEnable; @FXML private Button specialFieldsHelp; @FXML private RadioButton specialFieldsSyncKeywords; @@ -73,15 +67,17 @@ public void initialize() { private void setupTable() { nameColumn.setSortable(false); nameColumn.setReorderable(false); - nameColumn.setCellValueFactory(cellData -> cellData.getValue().fieldProperty()); - new ValueTableCellFactory().withText(FieldsUtil::getNameWithType).install(nameColumn); + nameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); + new ValueTableCellFactory() + .withText(name -> name) + .install(nameColumn); actionsColumn.setSortable(false); actionsColumn.setReorderable(false); - actionsColumn.setCellValueFactory(cellData -> cellData.getValue().fieldProperty()); - new ValueTableCellFactory() + actionsColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); + new ValueTableCellFactory() .withGraphic(item -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) - .withTooltip(name -> Localization.lang("Remove column") + " " + name.getDisplayName()) + .withTooltip(name -> Localization.lang("Remove column") + " " + name) .withOnMouseClickedEvent(item -> evt -> viewModel.removeColumn(columnsList.getFocusModel().getFocusedItem())) .install(actionsColumn); @@ -95,23 +91,24 @@ private void setupTable() { columnsList.itemsProperty().bind(viewModel.columnsListProperty()); - new ViewModelListCellFactory() - .withText(FieldsUtil::getNameWithType) + new ViewModelListCellFactory() + .withText(MainTableColumnModel::getDisplayName) .install(addColumnName); addColumnName.itemsProperty().bind(viewModel.availableColumnsProperty()); addColumnName.valueProperty().bindBidirectional(viewModel.addColumnProperty()); - addColumnName.setConverter(FieldsUtil.fieldStringConverter); + addColumnName.setConverter(TableColumnsTabViewModel.columnNameStringConverter); + addColumnName.setOnKeyPressed(event -> { + if (event.getCode() == KeyCode.ENTER) { + viewModel.insertColumnInList(); + event.consume(); + } + }); validationVisualizer.setDecoration(new IconValidationDecorator()); Platform.runLater(() -> validationVisualizer.initVisualization(viewModel.columnsListValidationStatus(), columnsList)); } private void setupBindings() { - showFileColumn.selectedProperty().bindBidirectional(viewModel.showFileColumnProperty()); - showUrlColumn.selectedProperty().bindBidirectional(viewModel.showUrlColumnProperty()); - urlFirst.selectedProperty().bindBidirectional(viewModel.preferUrlProperty()); - doiFirst.selectedProperty().bindBidirectional(viewModel.preferDoiProperty()); - showEPrintColumn.selectedProperty().bindBidirectional(viewModel.showEPrintColumnProperty()); specialFieldsEnable.selectedProperty().bindBidirectional(viewModel.specialFieldsEnabledProperty()); specialFieldsSyncKeywords.selectedProperty().bindBidirectional(viewModel.specialFieldsSyncKeywordsProperty()); specialFieldsSerialize.selectedProperty().bindBidirectional(viewModel.specialFieldsSerializeProperty()); diff --git a/src/main/java/org/jabref/gui/preferences/TableColumnsTabViewModel.java b/src/main/java/org/jabref/gui/preferences/TableColumnsTabViewModel.java index cce80e1cd14..256a0f9e639 100644 --- a/src/main/java/org/jabref/gui/preferences/TableColumnsTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/TableColumnsTabViewModel.java @@ -2,10 +2,7 @@ import java.util.ArrayList; import java.util.EnumSet; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; @@ -15,16 +12,17 @@ import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.scene.control.SelectionModel; +import javafx.util.StringConverter; import org.jabref.gui.DialogService; import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.maintable.ColumnPreferences; -import org.jabref.gui.util.FieldsUtil; +import org.jabref.gui.maintable.MainTableColumnModel; +import org.jabref.gui.specialfields.SpecialFieldsPreferences; import org.jabref.gui.util.NoSelectionModel; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.field.Field; -import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.InternalField; import org.jabref.model.entry.field.SpecialField; import org.jabref.model.entry.field.StandardField; @@ -36,18 +34,29 @@ public class TableColumnsTabViewModel implements PreferenceTabViewModel { - private final ListProperty columnsListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ObjectProperty> selectedColumnModelProperty = new SimpleObjectProperty<>(new NoSelectionModel<>()); - private final ListProperty availableColumnsProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ObjectProperty addColumnProperty = new SimpleObjectProperty<>(); + static StringConverter columnNameStringConverter = new StringConverter<>() { + @Override + public String toString(MainTableColumnModel object) { + if (object != null) { + return object.getName(); + } else { + return ""; + } + } + + @Override + public MainTableColumnModel fromString(String string) { + return MainTableColumnModel.parse(string); + } + }; + + private final ListProperty columnsListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ObjectProperty> selectedColumnModelProperty = new SimpleObjectProperty<>(new NoSelectionModel<>()); + private final ListProperty availableColumnsProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ObjectProperty addColumnProperty = new SimpleObjectProperty<>(); private final BooleanProperty specialFieldsEnabledProperty = new SimpleBooleanProperty(); private final BooleanProperty specialFieldsSyncKeywordsProperty = new SimpleBooleanProperty(); private final BooleanProperty specialFieldsSerializeProperty = new SimpleBooleanProperty(); - private final BooleanProperty showFileColumnProperty = new SimpleBooleanProperty(); - private final BooleanProperty showUrlColumnProperty = new SimpleBooleanProperty(); - private final BooleanProperty preferUrlProperty = new SimpleBooleanProperty(); - private final BooleanProperty preferDoiProperty = new SimpleBooleanProperty(); - private final BooleanProperty showEPrintColumnProperty = new SimpleBooleanProperty(); private final BooleanProperty extraFileColumnsEnabledProperty = new SimpleBooleanProperty(); private FunctionBasedValidator columnsNotEmptyValidator; @@ -57,11 +66,13 @@ public class TableColumnsTabViewModel implements PreferenceTabViewModel { private final DialogService dialogService; private final JabRefPreferences preferences; private final ColumnPreferences columnPreferences; + private final SpecialFieldsPreferences specialFieldsPreferences; public TableColumnsTabViewModel(DialogService dialogService, JabRefPreferences preferences) { this.dialogService = dialogService; this.preferences = preferences; this.columnPreferences = preferences.getColumnPreferences(); + this.specialFieldsPreferences = preferences.getSpecialFieldsPreferences(); specialFieldsEnabledProperty.addListener((observable, oldValue, newValue) -> { if (newValue) { @@ -90,27 +101,32 @@ public TableColumnsTabViewModel(DialogService dialogService, JabRefPreferences p @Override public void setValues() { - showFileColumnProperty.setValue(columnPreferences.showFileColumn()); - showUrlColumnProperty.setValue(columnPreferences.showUrlColumn()); - preferUrlProperty.setValue(!columnPreferences.preferDoiOverUrl()); - preferDoiProperty.setValue(columnPreferences.preferDoiOverUrl()); - showEPrintColumnProperty.setValue(columnPreferences.showEprintColumn()); - specialFieldsEnabledProperty.setValue(columnPreferences.getSpecialFieldsEnabled()); - specialFieldsSyncKeywordsProperty.setValue(columnPreferences.getAutoSyncSpecialFieldsToKeyWords()); - specialFieldsSerializeProperty.setValue(columnPreferences.getSerializeSpecialFields()); + ColumnPreferences columnPreferences = preferences.getColumnPreferences(); + SpecialFieldsPreferences specialFieldsPreferences = preferences.getSpecialFieldsPreferences(); + + specialFieldsEnabledProperty.setValue(specialFieldsPreferences.getSpecialFieldsEnabled()); + specialFieldsSyncKeywordsProperty.setValue(specialFieldsPreferences.getAutoSyncSpecialFieldsToKeyWords()); + specialFieldsSerializeProperty.setValue(specialFieldsPreferences.getSerializeSpecialFields()); extraFileColumnsEnabledProperty.setValue(columnPreferences.getExtraFileColumnsEnabled()); fillColumnList(); availableColumnsProperty.clear(); - availableColumnsProperty.add(InternalField.TIMESTAMP); - availableColumnsProperty.add(InternalField.OWNER); - availableColumnsProperty.add(InternalField.GROUPS); - availableColumnsProperty.add(InternalField.KEY_FIELD); - availableColumnsProperty.add(InternalField.TYPE_HEADER); - - EnumSet.allOf(StandardField.class).forEach(item -> availableColumnsProperty.getValue().add(item)); + availableColumnsProperty.add(new MainTableColumnModel(MainTableColumnModel.Type.LINKED_IDENTIFIER)); + availableColumnsProperty.add(new MainTableColumnModel(MainTableColumnModel.Type.GROUPS)); + availableColumnsProperty.add(new MainTableColumnModel(MainTableColumnModel.Type.FILES)); + availableColumnsProperty.add(new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, InternalField.TIMESTAMP.getName())); + availableColumnsProperty.add(new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, InternalField.OWNER.getName())); + availableColumnsProperty.add(new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, InternalField.GROUPS.getName())); + availableColumnsProperty.add(new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, InternalField.KEY_FIELD.getName())); + availableColumnsProperty.add(new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, InternalField.TYPE_HEADER.getName())); + availableColumnsProperty.add(new MainTableColumnModel(MainTableColumnModel.Type.LINKED_IDENTIFIER)); + + EnumSet.allOf(StandardField.class).stream() + .map(Field::getName) + .map(name -> new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, name)) + .forEach(item -> availableColumnsProperty.getValue().add(item)); if (specialFieldsEnabledProperty.getValue()) { insertSpecialFieldColumns(); @@ -123,32 +139,31 @@ public void setValues() { public void fillColumnList() { columnsListProperty.getValue().clear(); - - columnPreferences.getColumnNames().stream() - .map(FieldFactory::parseField) - .map(field -> new TableColumnsItemModel(field, columnPreferences.getColumnWidth(field.getName()))) - .forEach(columnsListProperty.getValue()::add); + columnPreferences.getColumns().forEach(columnsListProperty.getValue()::add); } private void insertSpecialFieldColumns() { - EnumSet.allOf(SpecialField.class).forEach(item -> availableColumnsProperty.getValue().add(0, item)); + EnumSet.allOf(SpecialField.class).stream() + .map(Field::getName) + .map(name -> new MainTableColumnModel(MainTableColumnModel.Type.SPECIALFIELD, name)) + .forEach(item -> availableColumnsProperty.getValue().add(0, item)); } private void removeSpecialFieldColumns() { - columnsListProperty.getValue().removeIf(column -> column.getField() instanceof SpecialField); - availableColumnsProperty.getValue().removeIf(field -> field instanceof SpecialField); + columnsListProperty.getValue().removeIf(column -> column.getType().equals(MainTableColumnModel.Type.SPECIALFIELD)); + availableColumnsProperty.getValue().removeIf(column -> column.getType().equals(MainTableColumnModel.Type.SPECIALFIELD)); } private void insertExtraFileColumns() { ExternalFileTypes.getInstance().getExternalFileTypeSelection().stream() - .map(ExternalFileType::getName) - .map(FieldsUtil.ExtraFilePseudoField::new) - .forEach(availableColumnsProperty::add); + .map(ExternalFileType::getName) + .map(name -> new MainTableColumnModel(MainTableColumnModel.Type.EXTRAFILE, name)) + .forEach(item -> availableColumnsProperty.getValue().add(item)); } private void removeExtraFileColumns() { - columnsListProperty.getValue().removeIf(column -> column.getField() instanceof FieldsUtil.ExtraFilePseudoField); - availableColumnsProperty.getValue().removeIf(field -> field instanceof FieldsUtil.ExtraFilePseudoField); + columnsListProperty.getValue().removeIf(column -> column.getType().equals(MainTableColumnModel.Type.EXTRAFILE)); + availableColumnsProperty.getValue().removeIf(column -> column.getType().equals(MainTableColumnModel.Type.EXTRAFILE)); } public void insertColumnInList() { @@ -156,18 +171,18 @@ public void insertColumnInList() { return; } - if (columnsListProperty.getValue().stream().filter(item -> item.getField().equals(addColumnProperty.getValue())).findAny().isEmpty()) { - columnsListProperty.add(new TableColumnsItemModel(addColumnProperty.getValue())); + if (columnsListProperty.getValue().stream().filter(item -> item.equals(addColumnProperty.getValue())).findAny().isEmpty()) { + columnsListProperty.add(addColumnProperty.getValue()); addColumnProperty.setValue(null); } } - public void removeColumn(TableColumnsItemModel column) { + public void removeColumn(MainTableColumnModel column) { columnsListProperty.remove(column); } public void moveColumnUp() { - TableColumnsItemModel selectedColumn = selectedColumnModelProperty.getValue().getSelectedItem(); + MainTableColumnModel selectedColumn = selectedColumnModelProperty.getValue().getSelectedItem(); int row = columnsListProperty.getValue().indexOf(selectedColumn); if (selectedColumn == null || row < 1) { return; @@ -179,7 +194,7 @@ public void moveColumnUp() { } public void moveColumnDown() { - TableColumnsItemModel selectedColumn = selectedColumnModelProperty.getValue().getSelectedItem(); + MainTableColumnModel selectedColumn = selectedColumnModelProperty.getValue().getSelectedItem(); int row = columnsListProperty.getValue().indexOf(selectedColumn); if (selectedColumn == null || row > columnsListProperty.getValue().size() - 2) { return; @@ -192,36 +207,26 @@ public void moveColumnDown() { @Override public void storeSettings() { - List columnNames = columnsListProperty.stream() - .map(item -> item.getField().getName()).collect(Collectors.toList()); - - // for each column get either actual width or - if it does not exist - default value - Map columnWidths = new HashMap<>(); - columnNames.forEach(field -> columnWidths.put(field,columnPreferences.getColumnWidth(field))); - - ColumnPreferences newColumnPreferences = new ColumnPreferences( - showFileColumnProperty.getValue(), - showUrlColumnProperty.getValue(), - preferDoiProperty.getValue(), - showEPrintColumnProperty.getValue(), - columnNames, - specialFieldsEnabledProperty.getValue(), - specialFieldsSyncKeywordsProperty.getValue(), - specialFieldsSerializeProperty.getValue(), + preferences.storeColumnPreferences(new ColumnPreferences( + columnsListProperty.getValue(), extraFileColumnsEnabledProperty.getValue(), - columnWidths, columnPreferences.getSortTypesForColumns() - ); + )); - if (columnPreferences.getAutoSyncSpecialFieldsToKeyWords() != newColumnPreferences.getAutoSyncSpecialFieldsToKeyWords()) { + SpecialFieldsPreferences newSpecialFieldsPreferences = new SpecialFieldsPreferences( + specialFieldsEnabledProperty.getValue(), + specialFieldsSyncKeywordsProperty.getValue(), + specialFieldsSerializeProperty.getValue()); + + if (specialFieldsPreferences.getAutoSyncSpecialFieldsToKeyWords() != newSpecialFieldsPreferences.getAutoSyncSpecialFieldsToKeyWords()) { restartWarnings.add(Localization.lang("Synchronize special fields to keywords")); } - if (columnPreferences.getSerializeSpecialFields() != newColumnPreferences.getSerializeSpecialFields()) { + if (specialFieldsPreferences.getSerializeSpecialFields() != newSpecialFieldsPreferences.getSerializeSpecialFields()) { restartWarnings.add(Localization.lang("Serialize special fields")); } - preferences.storeColumnPreferences(newColumnPreferences); + preferences.storeSpecialFieldsPreferences(newSpecialFieldsPreferences); } ValidationStatus columnsListValidationStatus() { @@ -243,21 +248,13 @@ public List getRestartWarnings() { return restartWarnings; } - public ListProperty columnsListProperty() { return this.columnsListProperty; } - - public ObjectProperty> selectedColumnModelProperty() { return selectedColumnModelProperty; } + public ListProperty columnsListProperty() { return this.columnsListProperty; } - public ListProperty availableColumnsProperty() { return this.availableColumnsProperty; } + public ObjectProperty> selectedColumnModelProperty() { return selectedColumnModelProperty; } - public ObjectProperty addColumnProperty() { return this.addColumnProperty; } + public ListProperty availableColumnsProperty() { return this.availableColumnsProperty; } - public BooleanProperty showFileColumnProperty() { return this.showFileColumnProperty; } - - public BooleanProperty showUrlColumnProperty() { return this.showUrlColumnProperty; } - - public BooleanProperty preferUrlProperty() { return this.preferUrlProperty; } - - public BooleanProperty preferDoiProperty() { return this.preferDoiProperty; } + public ObjectProperty addColumnProperty() { return this.addColumnProperty; } public BooleanProperty specialFieldsEnabledProperty() { return this.specialFieldsEnabledProperty; } @@ -265,8 +262,6 @@ public List getRestartWarnings() { public BooleanProperty specialFieldsSerializeProperty() { return this.specialFieldsSerializeProperty; } - public BooleanProperty showEPrintColumnProperty() { return this.showEPrintColumnProperty; } - public BooleanProperty extraFileColumnsEnabledProperty() { return this.extraFileColumnsEnabledProperty; } } diff --git a/src/main/java/org/jabref/gui/specialfields/SpecialFieldsPreferences.java b/src/main/java/org/jabref/gui/specialfields/SpecialFieldsPreferences.java new file mode 100644 index 00000000000..b584e28024e --- /dev/null +++ b/src/main/java/org/jabref/gui/specialfields/SpecialFieldsPreferences.java @@ -0,0 +1,22 @@ +package org.jabref.gui.specialfields; + +public class SpecialFieldsPreferences { + + public static final int COLUMN_RANKING_WIDTH = 5 * 16; // Width of Ranking Icon Column + + private final boolean specialFieldsEnabled; + private final boolean autoSyncSpecialFieldsToKeyWords; + private final boolean serializeSpecialFields; + + public SpecialFieldsPreferences(boolean specialFieldsEnabled, boolean autoSyncSpecialFieldsToKeyWords, boolean serializeSpecialFields) { + this.specialFieldsEnabled = specialFieldsEnabled; + this.autoSyncSpecialFieldsToKeyWords = autoSyncSpecialFieldsToKeyWords; + this.serializeSpecialFields = serializeSpecialFields; + } + + public boolean getSpecialFieldsEnabled() { return specialFieldsEnabled; } + + public boolean getAutoSyncSpecialFieldsToKeyWords() { return autoSyncSpecialFieldsToKeyWords; } + + public boolean getSerializeSpecialFields() { return serializeSpecialFields; } +} diff --git a/src/main/java/org/jabref/gui/util/FieldsUtil.java b/src/main/java/org/jabref/gui/util/FieldsUtil.java index 69c2158c156..7a72b944c91 100644 --- a/src/main/java/org/jabref/gui/util/FieldsUtil.java +++ b/src/main/java/org/jabref/gui/util/FieldsUtil.java @@ -1,14 +1,10 @@ package org.jabref.gui.util; -import java.util.Collections; -import java.util.Set; - import javafx.util.StringConverter; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; -import org.jabref.model.entry.field.FieldProperty; import org.jabref.model.entry.field.IEEEField; import org.jabref.model.entry.field.InternalField; import org.jabref.model.entry.field.SpecialField; @@ -41,35 +37,8 @@ public static String getNameWithType(Field field) { return field.getDisplayName() + " (" + Localization.lang("Internal") + ")"; } else if (field instanceof UnknownField) { return field.getDisplayName() + " (" + Localization.lang("Custom") + ")"; - } else if (field instanceof ExtraFilePseudoField) { - return field.getDisplayName() + " (" + Localization.lang("File type") + ")"; } else { return field.getDisplayName(); } } - - public static class ExtraFilePseudoField implements Field { - - String name; - - public ExtraFilePseudoField(String name) { - this.name = name; - } - - @Override - public Set getProperties() { - return Collections.emptySet(); - } - - @Override - public String getName() { - return name; - } - - @Override - public boolean isStandardField() { - return false; - } - - } } diff --git a/src/main/java/org/jabref/migrations/PreferencesMigrations.java b/src/main/java/org/jabref/migrations/PreferencesMigrations.java index a5386c8b1d4..8401e0b6165 100644 --- a/src/main/java/org/jabref/migrations/PreferencesMigrations.java +++ b/src/main/java/org/jabref/migrations/PreferencesMigrations.java @@ -12,6 +12,7 @@ import org.jabref.Globals; import org.jabref.JabRefMain; +import org.jabref.gui.maintable.MainTableColumnModel; import org.jabref.model.bibtexkeypattern.GlobalBibtexKeyPattern; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.EntryTypeFactory; @@ -43,6 +44,7 @@ public static void runMigrations() { upgradeKeyBindingsToJavaFX(Globals.prefs); addCrossRefRelatedFieldsForAutoComplete(Globals.prefs); upgradePreviewStyleFromReviewToComment(Globals.prefs); + upgradeColumnPreferences(Globals.prefs); } /** @@ -294,4 +296,30 @@ static void upgradePreviewStyleFromReviewToComment(JabRefPreferences prefs) { String migratedStyle = currentPreviewStyle.replace("\\begin{review}

Review: \\format[HTMLChars]{\\review} \\end{review}", "\\begin{comment}

Comment: \\format[HTMLChars]{\\comment} \\end{comment}"); prefs.setPreviewStyle(migratedStyle); } + + /** + * The former preferences default of columns was a simple list of strings ("author;title;year;..."). Since 5.0 + * the preferences store the type of the column too, so that the formerly hardwired columns like the graphic groups + * column or the other icon columns can be reordered in the main table and behave like any other field column + * ("groups;linked_id;field:author;special:readstatus;extrafile:pdf;..."). + * + * Simple strings are by default parsed as a FieldColumn, so there is nothing to do there, but the formerly hard + * wired columns need to be added. + */ + static void upgradeColumnPreferences(JabRefPreferences preferences) { + String columnNames = preferences.get(JabRefPreferences.COLUMN_NAMES); + + if (!columnNames.isEmpty() && + !columnNames.contains(MainTableColumnModel.Type.NORMALFIELD.getName() // "field:" + + MainTableColumnModel.COLUMNS_QUALIFIER_DELIMITER)) { + + String columnWidths = preferences.get(JabRefPreferences.COLUMN_WIDTHS); + + columnNames = "groups;linked_id;" + columnNames; + columnWidths = "28;28;" + columnWidths; + + preferences.put(JabRefPreferences.COLUMN_NAMES, columnNames); + preferences.put(JabRefPreferences.COLUMN_WIDTHS,columnWidths); + } + } } diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 3d59d2ecbd9..76e0c962e8b 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -25,7 +25,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.TreeMap; import java.util.UUID; import java.util.prefs.BackingStoreException; import java.util.prefs.InvalidPreferencesFormatException; @@ -49,11 +48,13 @@ import org.jabref.gui.groups.GroupViewMode; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.maintable.ColumnPreferences; +import org.jabref.gui.maintable.MainTableColumnModel; import org.jabref.gui.maintable.MainTablePreferences; import org.jabref.gui.mergeentries.MergeEntries; import org.jabref.gui.preferences.ImportTabViewModel; import org.jabref.gui.push.PushToApplication; import org.jabref.gui.push.PushToApplicationsManager; +import org.jabref.gui.specialfields.SpecialFieldsPreferences; import org.jabref.gui.util.ThemeLoader; import org.jabref.logic.bibtex.FieldContentParserPreferences; import org.jabref.logic.bibtex.LatexFieldFormatterPreferences; @@ -206,10 +207,6 @@ public class JabRefPreferences implements PreferencesService { public static final String KEYWORD_SEPARATOR = "groupKeywordSeparator"; public static final String AUTO_ASSIGN_GROUP = "autoAssignGroup"; public static final String EXTRA_FILE_COLUMNS = "extraFileColumns"; - public static final String ARXIV_COLUMN = "arxivColumn"; - public static final String FILE_COLUMN = "fileColumn"; - public static final String PREFER_URL_DOI = "preferUrlDoi"; - public static final String URL_COLUMN = "urlColumn"; // Colors public static final String FIELD_EDITOR_TEXT_COLOR = "fieldEditorTextColor"; public static final String ACTIVE_FIELD_EDITOR_BACKGROUND_COLOR = "activeFieldEditorBackgroundColor"; @@ -357,9 +354,12 @@ public class JabRefPreferences implements PreferencesService { // Id Entry Generator Preferences public static final String ID_ENTRY_GENERATOR = "idEntryGenerator"; - //File linking Options for entry editor + // File linking Options for entry editor public static final String ENTRY_EDITOR_DRAG_DROP_PREFERENCE_TYPE = "DragDropPreferenceType"; + // String delimiter + public static final Character STRINGLIST_DELIMITER = ';'; + // Preview private static final String PREVIEW_STYLE = "previewStyle"; private static final String CYCLE_PREVIEW_POS = "cyclePreviewPos"; @@ -525,8 +525,8 @@ private JabRefPreferences() { defaults.put(SIDE_PANE_COMPONENT_NAMES, ""); defaults.put(SIDE_PANE_COMPONENT_PREFERRED_POSITIONS, ""); - defaults.put(COLUMN_NAMES, "entrytype;author/editor;title;year;journal/booktitle;bibtexkey"); - defaults.put(COLUMN_WIDTHS, "75;300;470;60;130;100"); + defaults.put(COLUMN_NAMES, "groups;linked_id;field:entrytype;field:author/editor;field:title;field:year;field:journal/booktitle;field:bibtexkey"); + defaults.put(COLUMN_WIDTHS, "28;28;75;300;470;60;130;100"); defaults.put(XMP_PRIVACY_FILTERS, "pdf;timestamp;keywords;owner;note;review"); defaults.put(USE_XMP_PRIVACY_FILTER, Boolean.FALSE); @@ -599,11 +599,6 @@ private JabRefPreferences() { // default icon colors defaults.put(ICON_DISABLED_COLOR, "200:200:200"); - defaults.put(URL_COLUMN, Boolean.TRUE); - defaults.put(PREFER_URL_DOI, Boolean.FALSE); - defaults.put(FILE_COLUMN, Boolean.TRUE); - defaults.put(ARXIV_COLUMN, Boolean.FALSE); - defaults.put(EXTRA_FILE_COLUMNS, Boolean.FALSE); defaults.put(PROTECTED_TERMS_ENABLED_INTERNAL, convertListToString(ProtectedTermsLoader.getInternalLists())); @@ -782,7 +777,7 @@ public static JabRefPreferences getInstance() { } private static String convertListToString(List value) { - return value.stream().map(val -> StringUtil.quote(val, ";", '\\')).collect(Collectors.joining(";")); + return value.stream().map(val -> StringUtil.quote(val, STRINGLIST_DELIMITER.toString(), '\\')).collect(Collectors.joining(STRINGLIST_DELIMITER.toString())); } /** @@ -827,7 +822,7 @@ private static Optional getNextUnit(Reader data) throws IOException { // last character was escape symbol boolean escape = false; - // true if a ";" is found + // true if a STRINGLIST_DELIMITER is found boolean done = false; StringBuilder res = new StringBuilder(); @@ -840,9 +835,9 @@ private static Optional getNextUnit(Reader data) throws IOException { escape = true; } } else { - if (c == ';') { + if (c == STRINGLIST_DELIMITER) { if (escape) { - res.append(';'); + res.append(STRINGLIST_DELIMITER); } else { done = true; } @@ -968,7 +963,7 @@ private List getCustomTabFieldNames() { break; } - customFields.addAll(Arrays.stream(fields.split(";")).map(FieldFactory::parseField).collect(Collectors.toList())); + customFields.addAll(Arrays.stream(fields.split(STRINGLIST_DELIMITER.toString())).map(FieldFactory::parseField).collect(Collectors.toList())); defNumber++; } return customFields; @@ -977,7 +972,7 @@ private List getCustomTabFieldNames() { public void setLanguageDependentDefaultValues() { // Entry editor tab 0: defaults.put(CUSTOM_TAB_NAME + "_def0", Localization.lang("General")); - String fieldNames = FieldFactory.getDefaultGeneralFields().stream().map(Field::getName).collect(Collectors.joining(";")); + String fieldNames = FieldFactory.getDefaultGeneralFields().stream().map(Field::getName).collect(Collectors.joining(STRINGLIST_DELIMITER.toString())); defaults.put(CUSTOM_TAB_FIELDS + "_def0", fieldNames); // Entry editor tab 1: @@ -1066,8 +1061,8 @@ public void remove(String key) { } /** - * Puts a list of strings into the Preferences, by linking its elements with ';' into a single string. Escape - * characters make the process transparent even if strings contain ';'. + * Puts a list of strings into the Preferences, by linking its elements with a STRINGLIST_DELIMITER into a single + * string. Escape characters make the process transparent even if strings contains a STRINGLIST_DELIMITER. */ public void putStringList(String key, List value) { if (value == null) { @@ -1856,67 +1851,71 @@ public void storeSidePanePreferredPositions(Map preferred putStringList(SIDE_PANE_COMPONENT_PREFERRED_POSITIONS, positions); } - private Map createColumnWidths() { - List columns = getStringList(COLUMN_NAMES); - List widths = getStringList(COLUMN_WIDTHS) - .stream() - .map(string -> { - try { - return Double.parseDouble(string); - } catch (NumberFormatException e) { - LOGGER.error("Exception while parsing column widths. Choosing default.", e); - return ColumnPreferences.DEFAULT_FIELD_LENGTH; - } - }) - .collect(Collectors.toList()); - - Map map = new TreeMap<>(); - for (int i = 0; i < columns.size(); i++) { - map.put(columns.get(i), widths.get(i)); + private List createMainTableColumns() { + List columnNames = getStringList(COLUMN_NAMES); + List columnWidths = getStringList(COLUMN_WIDTHS) + .stream() + .map(string -> { + try { + return Double.parseDouble(string); + } catch (NumberFormatException e) { + LOGGER.error("Exception while parsing column widths. Choosing default.", e); + return ColumnPreferences.DEFAULT_WIDTH; + } + }) + .collect(Collectors.toList()); + + List columns = new ArrayList<>(); + for (int i = 0; i < columnNames.size(); i++) { + if (i < columnWidths.size()) { + columns.add(MainTableColumnModel.parse(columnNames.get(i), columnWidths.get(i))); + } else { + columns.add(MainTableColumnModel.parse(columnNames.get(i))); + } } - return map; + return columns; } public ColumnPreferences getColumnPreferences() { return new ColumnPreferences( - getBoolean(FILE_COLUMN), - getBoolean(URL_COLUMN), - getBoolean(PREFER_URL_DOI), - getBoolean(ARXIV_COLUMN), - getStringList(COLUMN_NAMES), - getBoolean(SPECIALFIELDSENABLED), - getBoolean(AUTOSYNCSPECIALFIELDSTOKEYWORDS), - getBoolean(SERIALIZESPECIALFIELDS), + createMainTableColumns(), getBoolean(EXTRA_FILE_COLUMNS), - createColumnWidths(), getMainTableColumnSortTypes()); } public void storeColumnPreferences(ColumnPreferences columnPreferences) { - putBoolean(FILE_COLUMN, columnPreferences.showFileColumn()); - putBoolean(URL_COLUMN, columnPreferences.showUrlColumn()); - putBoolean(PREFER_URL_DOI, columnPreferences.preferDoiOverUrl()); - putBoolean(ARXIV_COLUMN, columnPreferences.showEprintColumn()); - putStringList(COLUMN_NAMES, columnPreferences.getColumnNames()); - - putBoolean(SPECIALFIELDSENABLED, columnPreferences.getSpecialFieldsEnabled()); - putBoolean(AUTOSYNCSPECIALFIELDSTOKEYWORDS, columnPreferences.getAutoSyncSpecialFieldsToKeyWords()); - putBoolean(SERIALIZESPECIALFIELDS, columnPreferences.getSerializeSpecialFields()); + + putStringList(COLUMN_NAMES, columnPreferences.getColumns().stream() + .map(MainTableColumnModel::getName) + .collect(Collectors.toList())); + putBoolean(EXTRA_FILE_COLUMNS, columnPreferences.getExtraFileColumnsEnabled()); List columnWidthsInOrder = new ArrayList<>(); - columnPreferences.getColumnNames().forEach(name -> columnWidthsInOrder.add(columnPreferences.getColumnWidths().get(name).toString())); + columnPreferences.getColumns().forEach(column -> columnWidthsInOrder.add(column.widthProperty().getValue().toString())); putStringList(COLUMN_WIDTHS, columnWidthsInOrder); setMainTableColumnSortType(columnPreferences.getSortTypesForColumns()); } public MainTablePreferences getMainTablePreferences() { - return new MainTablePreferences( - getColumnPreferences(), + return new MainTablePreferences(getColumnPreferences(), getBoolean(AUTO_RESIZE_MODE)); } + public SpecialFieldsPreferences getSpecialFieldsPreferences() { + return new SpecialFieldsPreferences( + getBoolean(SPECIALFIELDSENABLED), + getBoolean(AUTOSYNCSPECIALFIELDSTOKEYWORDS), + getBoolean(SERIALIZESPECIALFIELDS)); + } + + public void storeSpecialFieldsPreferences(SpecialFieldsPreferences specialFieldsPreferences) { + putBoolean(SPECIALFIELDSENABLED, specialFieldsPreferences.getSpecialFieldsEnabled()); + putBoolean(AUTOSYNCSPECIALFIELDSTOKEYWORDS, specialFieldsPreferences.getAutoSyncSpecialFieldsToKeyWords()); + putBoolean(SERIALIZESPECIALFIELDS, specialFieldsPreferences.getSerializeSpecialFields()); + } + @Override public Path getWorkingDir() { return Paths.get(get(WORKING_DIRECTORY)); diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 5b3dbc721ff..0a489731034 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -831,8 +831,6 @@ Show\ BibTeX\ source\ by\ default=Show BibTeX source by default Show\ confirmation\ dialog\ when\ deleting\ entries=Show confirmation dialog when deleting entries -Show\ file\ column=Show file column - Show\ last\ names\ only=Show last names only Show\ names\ unchanged=Show names unchanged @@ -841,8 +839,6 @@ Show\ optional\ fields=Show optional fields Show\ required\ fields=Show required fields -Show\ URL/DOI\ column=Show URL/DOI column - Show\ validation\ messages=Show validation messages Simple\ HTML=Simple HTML @@ -1068,7 +1064,6 @@ General\ file\ directory=General file directory User-specific\ file\ directory=User-specific file directory LaTex\ file\ directory=LaTex file directory Search\ failed\:\ illegal\ search\ expression=Search failed: illegal search expression -Show\ ArXiv\ column=Show ArXiv column You\ must\ enter\ an\ integer\ value\ in\ the\ interval\ 1025-65535=You must enter an integer value in the interval 1025-65535 Automatically\ open\ browse\ dialog\ when\ creating\ new\ file\ link=Automatically open browse dialog when creating new file link @@ -1256,8 +1251,6 @@ Merged\ entries=Merged entries None=None Parse=Parse Result=Result -Show\ DOI\ first=Show DOI first -Show\ URL\ first=Show URL first You\ have\ to\ choose\ exactly\ two\ entries\ to\ merge.=You have to choose exactly two entries to merge. Update\ timestamp\ on\ modification=Update timestamp on modification @@ -1911,8 +1904,6 @@ Move\ panel\ down=Move panel down Linked\ files=Linked files Group\ view\ mode\ set\ to\ intersection=Group view mode set to intersection Group\ view\ mode\ set\ to\ union=Group view mode set to union -Open\ %0\ URL\ (%1)=Open %0 URL (%1) -Open\ URL\ (%0)=Open URL (%0) Open\ file\ %0=Open file %0 Toggle\ intersection=Toggle intersection Toggle\ union=Toggle union @@ -2085,7 +2076,6 @@ List\ must\ not\ be\ empty.=List must not be empty. Add\ field\ to\ filter\ list=Add field to filter list Add\ formatter\ to\ list=Add formatter to list Filter\ List=Filter List -New\ column=New column Open\ files...=Open files... Affected\ fields\:=Affected fields: @@ -2109,3 +2099,7 @@ Start\ on\ second\ duplicate\ key\ with\ letter\ B\ (b,\ c,\ ...)=Start on secon Always\ add\ letter\ (a,\ b,\ ...)\ to\ generated\ keys=Always add letter (a, b, ...) to generated keys Default\ pattern=Default pattern Reset\ %s\ to\ default\ value=Reset %s to default value + +Column\ type\ %0\ is\ unknown.=Column type %0 is unknown. +Linked\ identifiers=Linked identifiers +Special\ field\ type\ %0\ is\ unknown.\ Using\ normal\ column\ type.=Special field type %0 is unknown. Using normal column type. diff --git a/src/test/java/org/jabref/gui/maintable/MainTableColumnModelTest.java b/src/test/java/org/jabref/gui/maintable/MainTableColumnModelTest.java new file mode 100644 index 00000000000..253039bd340 --- /dev/null +++ b/src/test/java/org/jabref/gui/maintable/MainTableColumnModelTest.java @@ -0,0 +1,39 @@ +package org.jabref.gui.maintable; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MainTableColumnModelTest { + + private static String testName = "field:author"; + private static MainTableColumnModel.Type testType = MainTableColumnModel.Type.NORMALFIELD; + private static String testQualifier = "author"; + + private static String testTypeOnlyName = "linked_id"; + private static MainTableColumnModel.Type testTypeOnlyType = MainTableColumnModel.Type.LINKED_IDENTIFIER; + + @Test + public void testMainTableColumnModelParser() { + MainTableColumnModel testColumnModel = MainTableColumnModel.parse(testQualifier); + + assertEquals(testColumnModel.getType(),testType); + assertEquals(testColumnModel.getQualifier(), testQualifier); + } + + @Test + public void testMainTableColumnModelParserFull() { + MainTableColumnModel testColumnModel = MainTableColumnModel.parse(testName); + + assertEquals(testColumnModel.getType(),testType); + assertEquals(testColumnModel.getQualifier(), testQualifier); + } + + @Test + public void testMainTableColumnModelParserTypeOnly() { + MainTableColumnModel testColumnModel = MainTableColumnModel.parse(testTypeOnlyName); + + assertEquals(testColumnModel.getType(), testTypeOnlyType); + assertEquals(testColumnModel.getQualifier(), ""); + } +} diff --git a/src/test/java/org/jabref/migrations/PreferencesMigrationsTest.java b/src/test/java/org/jabref/migrations/PreferencesMigrationsTest.java index 6a2ee9bc5e8..537b91be4a2 100644 --- a/src/test/java/org/jabref/migrations/PreferencesMigrationsTest.java +++ b/src/test/java/org/jabref/migrations/PreferencesMigrationsTest.java @@ -115,4 +115,34 @@ void testPreviewStyle() { verify(prefs).setPreviewStyle(newPreviewStyle); } + + @Test + void testUpgradeColumnPreferencesAlreadyMigrated() { + String columnNames = "groups;linked_id;field:entrytype;field:author/editor;field:title;field:year;field:journal/booktitle;field:bibtexkey"; + String columnWidths = "28;28;75;300;470;60;130;100"; + + when(prefs.get(JabRefPreferences.COLUMN_NAMES)).thenReturn(columnNames); + when(prefs.get(JabRefPreferences.COLUMN_WIDTHS)).thenReturn(columnWidths); + + PreferencesMigrations.upgradeColumnPreferences(prefs); + + verify(prefs, never()).put(JabRefPreferences.COLUMN_NAMES, "anyString"); + verify(prefs, never()).put(JabRefPreferences.COLUMN_WIDTHS, "anyString"); + } + + @Test + void testUpgradeColumnPreferencesFromWithoutTypes() { + String columnNames = "entrytype;author/editor;title;year;journal/booktitle;bibtexkey"; + String columnWidths = "75;300;470;60;130;100"; + String updatedNames = "groups;linked_id;entrytype;author/editor;title;year;journal/booktitle;bibtexkey"; + String updatedWidths = "28;28;75;300;470;60;130;100"; + + when(prefs.get(JabRefPreferences.COLUMN_NAMES)).thenReturn(columnNames); + when(prefs.get(JabRefPreferences.COLUMN_WIDTHS)).thenReturn(columnWidths); + + PreferencesMigrations.upgradeColumnPreferences(prefs); + + verify(prefs).put(JabRefPreferences.COLUMN_NAMES, updatedNames); + verify(prefs).put(JabRefPreferences.COLUMN_WIDTHS, updatedWidths); + } }