diff --git a/CHANGELOG.md b/CHANGELOG.md index 887eba378df..5ff9e2ec848 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,6 +98,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We fixed an issue with the creation of a group of cited entries. Now the file path to an aux file gets validated. [#6585](https://github.com/JabRef/jabref/issues/6585) - We fixed an issue on Linux systems where the application would crash upon inotify failure. Now, the user is prompted with a warning, and given the choice to continue the session. [#6073](https://github.com/JabRef/jabref/issues/6073) - We moved the search modifier buttons into the search bar, as they were not accessible, if autocompletion was disabled. [#6625](https://github.com/JabRef/jabref/issues/6625) +- We fixed an issue about duplicated group color indicators [#6175](https://github.com/JabRef/jabref/issues/6175) - We fixed an issue where entries with the entry type Misc from an imported aux file would not be saved correctly to the bib file on disk [#6405](https://github.com/JabRef/jabref/issues/6405) - We fixed an issue where percent sign ('%') was not formatted properly by the HTML formatter [#6753](https://github.com/JabRef/jabref/issues/6753) diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/BasePanel.java index 1d2778f736b..eacb550283a 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/BasePanel.java @@ -284,7 +284,14 @@ public void editEntryAndFocusField(BibEntry entry, Field field) { private void createMainTable() { bibDatabaseContext.getDatabase().registerListener(SpecialFieldDatabaseChangeListener.INSTANCE); - mainTable = new MainTable(tableModel, frame, this, bibDatabaseContext, preferences.getTablePreferences(), externalFileTypes, preferences.getKeyBindings()); + mainTable = new MainTable(tableModel, + this, + bibDatabaseContext, + Globals.prefs, + dialogService, + Globals.stateManager, + externalFileTypes, + preferences.getKeyBindings()); // Add the listener that binds selection to state manager (TODO: should be replaced by proper JavaFX binding as soon as table is implemented in JavaFX) mainTable.addSelectionListener(listEvent -> Globals.stateManager.setSelectedEntries(mainTable.getSelectedEntries())); diff --git a/src/main/java/org/jabref/gui/maintable/MainTable.java b/src/main/java/org/jabref/gui/maintable/MainTable.java index 6b0183299a4..67f2427912d 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTable.java +++ b/src/main/java/org/jabref/gui/maintable/MainTable.java @@ -26,12 +26,14 @@ import org.jabref.Globals; import org.jabref.gui.BasePanel; +import org.jabref.gui.DialogService; import org.jabref.gui.DragAndDropDataFormats; -import org.jabref.gui.JabRefFrame; +import org.jabref.gui.StateManager; import org.jabref.gui.externalfiles.ImportHandler; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.keyboard.KeyBindingRepository; +import org.jabref.gui.maintable.columns.MainTableColumn; import org.jabref.gui.util.ControlHelper; import org.jabref.gui.util.CustomLocalDragboard; import org.jabref.gui.util.DefaultTaskExecutor; @@ -41,6 +43,7 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.event.EntriesAddedEvent; import org.jabref.model.entry.BibEntry; +import org.jabref.preferences.PreferencesService; import com.google.common.eventbus.Subscribe; import org.slf4j.Logger; @@ -51,43 +54,43 @@ public class MainTable extends TableView { private static final Logger LOGGER = LoggerFactory.getLogger(MainTable.class); private final BasePanel panel; - private final BibDatabaseContext database; - private final UndoManager undoManager; - private final MainTableDataModel model; + private final ImportHandler importHandler; private final CustomLocalDragboard localDragboard; private long lastKeyPressTime; private String columnSearchTerm; - public MainTable(MainTableDataModel model, JabRefFrame frame, - BasePanel panel, BibDatabaseContext database, - MainTablePreferences preferences, ExternalFileTypes externalFileTypes, KeyBindingRepository keyBindingRepository) { + public MainTable(MainTableDataModel model, + BasePanel panel, + BibDatabaseContext database, + PreferencesService preferencesService, + DialogService dialogService, + StateManager stateManager, + ExternalFileTypes externalFileTypes, + KeyBindingRepository keyBindingRepository) { super(); - this.setOnKeyTyped(key -> { - if (this.getSortOrder().isEmpty()) { - return; - } - this.jumpToSearchKey(getSortOrder().get(0), key); - }); - - this.model = model; + this.panel = panel; this.database = Objects.requireNonNull(database); - - this.undoManager = panel.getUndoManager(); + this.model = model; + UndoManager undoManager = panel.getUndoManager(); + MainTablePreferences mainTablePreferences = preferencesService.getMainTablePreferences(); importHandler = new ImportHandler( - frame.getDialogService(), database, externalFileTypes, - Globals.prefs, + dialogService, database, externalFileTypes, + preferencesService, Globals.getFileUpdateMonitor(), undoManager, - Globals.stateManager); - localDragboard = Globals.stateManager.getLocalDragboard(); + stateManager); + + localDragboard = stateManager.getLocalDragboard(); - this.getColumns().addAll(new MainTableColumnFactory(database, preferences.getColumnPreferences(), externalFileTypes, panel.getUndoManager(), frame.getDialogService()).createColumns()); + this.getColumns().addAll( + new MainTableColumnFactory(database, preferencesService, externalFileTypes, panel.getUndoManager(), dialogService) + .createColumns()); new ViewModelTableRowFactory() .withOnMouseClickedEvent((entry, event) -> { @@ -95,7 +98,12 @@ public MainTable(MainTableDataModel model, JabRefFrame frame, panel.showAndEdit(entry.getEntry()); } }) - .withContextMenu(entry -> RightClickMenu.create(entry, keyBindingRepository, panel, frame.getDialogService(), Globals.stateManager, Globals.prefs)) + .withContextMenu(entry -> RightClickMenu.create(entry, + keyBindingRepository, + panel, + dialogService, + stateManager, + preferencesService)) .setOnDragDetected(this::handleOnDragDetected) .setOnDragDropped(this::handleOnDragDropped) .setOnDragOver(this::handleOnDragOver) @@ -104,34 +112,41 @@ public MainTable(MainTableDataModel model, JabRefFrame frame, .install(this); this.getSortOrder().clear(); - preferences.getColumnPreferences().getColumnSortOrder().forEach(columnModel -> + mainTablePreferences.getColumnPreferences().getColumnSortOrder().forEach(columnModel -> this.getColumns().stream() .map(column -> (MainTableColumn) column) .filter(column -> column.getModel().equals(columnModel)) .findFirst() .ifPresent(column -> this.getSortOrder().add(column))); - if (preferences.getResizeColumnsToFit()) { + if (mainTablePreferences.getResizeColumnsToFit()) { this.setColumnResizePolicy(new SmartConstrainedResizePolicy()); } + this.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); this.setItems(model.getEntriesFilteredAndSorted()); + // Enable sorting model.getEntriesFilteredAndSorted().comparatorProperty().bind(this.comparatorProperty()); - this.panel = panel; - this.getStylesheets().add(MainTable.class.getResource("MainTable.css").toExternalForm()); // Store visual state - new PersistenceVisualStateTable(this, Globals.prefs); + new PersistenceVisualStateTable(this, preferencesService); // TODO: Float marked entries // model.updateMarkingState(Globals.prefs.getBoolean(JabRefPreferences.FLOAT_MARKED_ENTRIES)); setupKeyBindings(keyBindingRepository); + this.setOnKeyTyped(key -> { + if (this.getSortOrder().isEmpty()) { + return; + } + this.jumpToSearchKey(getSortOrder().get(0), key); + }); + database.getDatabase().registerListener(this); } diff --git a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java index c66a530f5fe..caf1f038e4b 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java @@ -1,6 +1,5 @@ package org.jabref.gui.maintable; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -14,67 +13,59 @@ import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; -import javafx.scene.control.ContextMenu; -import javafx.scene.control.MenuItem; import javafx.scene.control.TableColumn; import javafx.scene.control.Tooltip; -import javafx.scene.input.MouseButton; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.text.Text; -import org.jabref.Globals; import org.jabref.gui.DialogService; -import org.jabref.gui.desktop.JabRefDesktop; -import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; -import org.jabref.gui.fieldeditors.LinkedFileViewModel; import org.jabref.gui.icon.IconTheme; -import org.jabref.gui.icon.JabRefIcon; +import org.jabref.gui.maintable.columns.FieldColumn; +import org.jabref.gui.maintable.columns.FileColumn; +import org.jabref.gui.maintable.columns.LinkedIdentifierColumn; +import org.jabref.gui.maintable.columns.MainTableColumn; +import org.jabref.gui.maintable.columns.SpecialFieldColumn; import org.jabref.gui.specialfields.SpecialFieldValueViewModel; -import org.jabref.gui.specialfields.SpecialFieldViewModel; -import org.jabref.gui.specialfields.SpecialFieldsPreferences; -import org.jabref.gui.util.ControlHelper; -import org.jabref.gui.util.OptionalValueTableCellFactory; import org.jabref.gui.util.ValueTableCellFactory; -import org.jabref.gui.util.comparator.NumericFieldComparator; -import org.jabref.gui.util.comparator.PriorityFieldComparator; -import org.jabref.gui.util.comparator.RankingFieldComparator; -import org.jabref.gui.util.comparator.ReadStatusFieldComparator; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.SpecialField; -import org.jabref.model.entry.field.SpecialFieldValue; -import org.jabref.model.entry.field.StandardField; import org.jabref.model.groups.AbstractGroup; import org.jabref.model.util.OptionalUtil; +import org.jabref.preferences.PreferencesService; -import com.tobiasdiez.easybind.EasyBind; -import org.controlsfx.control.Rating; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -class MainTableColumnFactory { +public class MainTableColumnFactory { + + public static final String STYLE_ICON_COLUMN = "column-icon"; - private static final String STYLE_ICON_COLUMN = "column-icon"; private static final Logger LOGGER = LoggerFactory.getLogger(MainTableColumnFactory.class); - private final ColumnPreferences preferences; + private final PreferencesService preferencesService; + private final ColumnPreferences columnPreferences; private final ExternalFileTypes externalFileTypes; private final BibDatabaseContext database; private final CellFactory cellFactory; private final UndoManager undoManager; private final DialogService dialogService; - public MainTableColumnFactory(BibDatabaseContext database, ColumnPreferences preferences, ExternalFileTypes externalFileTypes, UndoManager undoManager, DialogService dialogService) { + public MainTableColumnFactory(BibDatabaseContext database, + PreferencesService preferencesService, + ExternalFileTypes externalFileTypes, + UndoManager undoManager, + DialogService dialogService) { this.database = Objects.requireNonNull(database); - this.preferences = Objects.requireNonNull(preferences); + this.preferencesService = Objects.requireNonNull(preferencesService); + this.columnPreferences = preferencesService.getColumnPreferences(); this.externalFileTypes = Objects.requireNonNull(externalFileTypes); this.dialogService = dialogService; this.cellFactory = new CellFactory(externalFileTypes, undoManager); @@ -84,7 +75,7 @@ public MainTableColumnFactory(BibDatabaseContext database, ColumnPreferences pre public List> createColumns() { List> columns = new ArrayList<>(); - preferences.getColumns().forEach(column -> { + columnPreferences.getColumns().forEach(column -> { switch (column.getType()) { case INDEX: @@ -127,7 +118,7 @@ public MainTableColumnFactory(BibDatabaseContext database, ColumnPreferences pre return columns; } - private void setExactWidth(TableColumn column, double width) { + public static void setExactWidth(TableColumn column, double width) { column.setMinWidth(width); column.setPrefWidth(width); column.setMaxWidth(width); @@ -183,18 +174,18 @@ private Node createGroupColorRegion(BibEntryTableViewModel entry, List { Rectangle groupRectangle = new Rectangle(); groupRectangle.getStyleClass().add("groupColumnBackground"); groupRectangle.setWidth(3); groupRectangle.setHeight(18); groupRectangle.setFill(groupColor); groupRectangle.setStrokeWidth(1); - container.getChildren().add(groupRectangle); - } + }); String matchedGroupsString = matchedGroups.stream() + .distinct() .map(AbstractGroup::getName) .collect(Collectors.joining(", ")); Tooltip tooltip = new Tooltip(Localization.lang("Entry is contained in the following groups:") + "\n" + matchedGroupsString); @@ -208,245 +199,44 @@ private Node createGroupColorRegion(BibEntryTableViewModel entry, List createFieldColumn(MainTableColumnModel columnModel) { - FieldColumn column = new FieldColumn(columnModel, - FieldFactory.parseOrFields(columnModel.getQualifier()) - ); - new ValueTableCellFactory() - .withText(text -> text) - .install(column); - column.setComparator(new NumericFieldComparator()); - column.setSortable(true); - return column; + return new FieldColumn(columnModel); } /** * 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() + ": " + - ControlHelper.truncateString(values.get(field), -1, "...", ControlHelper.EllipsisPosition.CENTER), - 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; + return new LinkedIdentifierColumn(columnModel, cellFactory, database, dialogService); } /** - * A column that displays a SpecialField + * Creates a column that displays a {@link 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(STYLE_ICON_COLUMN); - if (specialField == SpecialField.RANKING) { - setExactWidth(column, SpecialFieldsPreferences.COLUMN_RANKING_WIDTH); - column.setResizable(false); - new OptionalValueTableCellFactory() - .withGraphicIfPresent(this::createSpecialRating) - .install(column); - } else { - setExactWidth(column, ColumnPreferences.ICON_COLUMN_WIDTH); - column.setResizable(false); - - if (specialField.isSingleValueField()) { - new OptionalValueTableCellFactory() - .withGraphic((entry, value) -> createSpecialFieldIcon(value, specialFieldViewModel)) - .withOnMouseClickedEvent((entry, value) -> event -> { - if (event.getButton() == MouseButton.PRIMARY) { - specialFieldViewModel.toggle(entry.getEntry()); - } - }) - .install(column); - } else { - new OptionalValueTableCellFactory() - .withGraphic((entry, value) -> createSpecialFieldIcon(value, specialFieldViewModel)) - .withMenu((entry, value) -> createSpecialFieldMenu(entry.getEntry(), specialFieldViewModel)) - .install(column); - } - } - column.setCellValueFactory(cellData -> cellData.getValue().getSpecialField(specialField)); - - if (specialField == SpecialField.RANKING) { - column.setComparator(new RankingFieldComparator()); - } - - // Added comparator for Read Status - if (specialField == SpecialField.READ_STATUS) { - column.setComparator(new ReadStatusFieldComparator()); - } - - if (specialField == SpecialField.PRIORITY) { - column.setComparator(new PriorityFieldComparator()); - } - - column.setSortable(true); - - return column; - } - - private Rating createSpecialRating(BibEntryTableViewModel entry, SpecialFieldValueViewModel value) { - Rating ranking = new Rating(); - ranking.setRating(value.getValue().toRating()); - EasyBind.subscribe(ranking.ratingProperty(), rating -> - new SpecialFieldViewModel(SpecialField.RANKING, undoManager).setSpecialFieldValue(entry.getEntry(), SpecialFieldValue.getRating(rating.intValue())) - ); - return ranking; - } - - private ContextMenu createSpecialFieldMenu(BibEntry entry, SpecialFieldViewModel specialField) { - ContextMenu contextMenu = new ContextMenu(); - - for (SpecialFieldValueViewModel value : specialField.getValues()) { - MenuItem menuItem = new MenuItem(value.getMenuString(), value.getIcon().map(JabRefIcon::getGraphicNode).orElse(null)); - menuItem.setOnAction(event -> specialField.setSpecialFieldValue(entry, value.getValue())); - contextMenu.getItems().add(menuItem); - } - - return contextMenu; - } - - private Node createSpecialFieldIcon(Optional fieldValue, SpecialFieldViewModel specialField) { - return fieldValue - .flatMap(SpecialFieldValueViewModel::getIcon) - .map(JabRefIcon::getGraphicNode) - .orElseGet(() -> { - Node node = specialField.getEmptyIcon().getGraphicNode(); - node.getStyleClass().add("empty-special-field"); - return node; - }); + return new SpecialFieldColumn(columnModel, undoManager); } /** - * 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. + * Creates a column for all the linked files. Instead of creating a column for a single file type, like {@link + * #createExtraFileColumn(MainTableColumnModel)} 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(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); - return column; - } - - private String createFileTooltip(List linkedFiles) { - if (linkedFiles.size() > 0) { - return Localization.lang("Open file %0", linkedFiles.get(0).getLink()); - } - return null; - } - - private ContextMenu createFileMenu(BibEntryTableViewModel entry, List linkedFiles) { - if (linkedFiles.size() <= 1) { - return null; - } - - ContextMenu contextMenu = new ContextMenu(); - - for (LinkedFile linkedFile : linkedFiles) { - LinkedFileViewModel linkedFileViewModel = new LinkedFileViewModel(linkedFile, entry.getEntry(), database, Globals.TASK_EXECUTOR, dialogService, Globals.prefs.getXMPPreferences(), Globals.prefs.getFilePreferences(), externalFileTypes); - - MenuItem menuItem = new MenuItem(linkedFileViewModel.getTruncatedDescriptionAndLink(), linkedFileViewModel.getTypeIcon().getGraphicNode()); - menuItem.setOnAction(event -> linkedFileViewModel.open()); - contextMenu.getItems().add(menuItem); - } - - return contextMenu; - } - - 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(); - } else { - return null; - } + return new FileColumn(columnModel, + database, + externalFileTypes, + dialogService, + preferencesService); } /** * 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; + return new FileColumn(columnModel, + database, + externalFileTypes, + dialogService, + preferencesService, + columnModel.getQualifier()); } } diff --git a/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java b/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java index 1a9216722b0..6b695be291d 100644 --- a/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java +++ b/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java @@ -4,6 +4,7 @@ import javafx.beans.InvalidationListener; +import org.jabref.gui.maintable.columns.MainTableColumn; import org.jabref.preferences.PreferencesService; /** diff --git a/src/main/java/org/jabref/gui/maintable/FieldColumn.java b/src/main/java/org/jabref/gui/maintable/columns/FieldColumn.java similarity index 55% rename from src/main/java/org/jabref/gui/maintable/FieldColumn.java rename to src/main/java/org/jabref/gui/maintable/columns/FieldColumn.java index fac40ce2d87..2822f5bce30 100644 --- a/src/main/java/org/jabref/gui/maintable/FieldColumn.java +++ b/src/main/java/org/jabref/gui/maintable/columns/FieldColumn.java @@ -1,7 +1,12 @@ -package org.jabref.gui.maintable; +package org.jabref.gui.maintable.columns; import javafx.beans.value.ObservableValue; +import org.jabref.gui.maintable.BibEntryTableViewModel; +import org.jabref.gui.maintable.MainTableColumnModel; +import org.jabref.gui.util.ValueTableCellFactory; +import org.jabref.gui.util.comparator.NumericFieldComparator; +import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.OrFields; /** @@ -11,12 +16,18 @@ public class FieldColumn extends MainTableColumn { private final OrFields fields; - public FieldColumn(MainTableColumnModel model, OrFields fields) { + public FieldColumn(MainTableColumnModel model) { super(model); - this.fields = fields; + this.fields = FieldFactory.parseOrFields(model.getQualifier()); setText(getDisplayName()); setCellValueFactory(param -> getFieldValue(param.getValue())); + + new ValueTableCellFactory() + .withText(text -> text) + .install(this); + this.setComparator(new NumericFieldComparator()); + this.setSortable(true); } /** diff --git a/src/main/java/org/jabref/gui/maintable/columns/FileColumn.java b/src/main/java/org/jabref/gui/maintable/columns/FileColumn.java new file mode 100644 index 00000000000..2c46132a52c --- /dev/null +++ b/src/main/java/org/jabref/gui/maintable/columns/FileColumn.java @@ -0,0 +1,160 @@ +package org.jabref.gui.maintable.columns; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import javafx.scene.Node; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.Tooltip; +import javafx.scene.input.MouseButton; + +import org.jabref.Globals; +import org.jabref.gui.DialogService; +import org.jabref.gui.externalfiletype.ExternalFileType; +import org.jabref.gui.externalfiletype.ExternalFileTypes; +import org.jabref.gui.fieldeditors.LinkedFileViewModel; +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.maintable.BibEntryTableViewModel; +import org.jabref.gui.maintable.ColumnPreferences; +import org.jabref.gui.maintable.MainTableColumnFactory; +import org.jabref.gui.maintable.MainTableColumnModel; +import org.jabref.gui.util.ValueTableCellFactory; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.LinkedFile; +import org.jabref.preferences.PreferencesService; + +/** + * A column that draws a clickable symbol for either all the files of a defined file type + * or a joined column with all the files of any type + */ +public class FileColumn extends MainTableColumn> { + + private final ExternalFileTypes externalFileTypes; + private final DialogService dialogService; + private final BibDatabaseContext database; + private final PreferencesService preferencesService; + + /** + * Creates a joined column for all the linked files. + */ + public FileColumn(MainTableColumnModel model, + BibDatabaseContext database, + ExternalFileTypes externalFileTypes, + DialogService dialogService, + PreferencesService preferencesService) { + super(model); + this.externalFileTypes = Objects.requireNonNull(externalFileTypes); + this.database = Objects.requireNonNull(database); + this.dialogService = dialogService; + this.preferencesService = preferencesService; + + setCommonSettings(); + + Node headerGraphic = IconTheme.JabRefIcons.FILE.getGraphicNode(); + Tooltip.install(headerGraphic, new Tooltip(Localization.lang("Linked files"))); + this.setGraphic(headerGraphic); + + 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, + preferencesService.getXMPPreferences(), + preferencesService.getFilePreferences(), + externalFileTypes); + linkedFileViewModel.open(); + } + }) + .install(this); + } + + /** + * Creates a column for all the linked files of a single file type. + */ + public FileColumn(MainTableColumnModel model, + BibDatabaseContext database, + ExternalFileTypes externalFileTypes, + DialogService dialogService, + PreferencesService preferencesService, + String fileType) { + super(model); + this.externalFileTypes = Objects.requireNonNull(externalFileTypes); + this.database = Objects.requireNonNull(database); + this.dialogService = dialogService; + this.preferencesService = preferencesService; + + setCommonSettings(); + + this.setGraphic(externalFileTypes + .getExternalFileTypeByName(fileType) + .map(ExternalFileType::getIcon).orElse(IconTheme.JabRefIcons.FILE) + .getGraphicNode()); + + new ValueTableCellFactory>() + .withGraphic(linkedFiles -> createFileIcon(linkedFiles.stream().filter(linkedFile -> + linkedFile.getFileType().equalsIgnoreCase(fileType)).collect(Collectors.toList()))) + .install(this); + } + + private void setCommonSettings() { + this.setResizable(false); + MainTableColumnFactory.setExactWidth(this, ColumnPreferences.ICON_COLUMN_WIDTH); + this.getStyleClass().add(MainTableColumnFactory.STYLE_ICON_COLUMN); + this.setCellValueFactory(cellData -> cellData.getValue().getLinkedFiles()); + } + + private String createFileTooltip(List linkedFiles) { + if (linkedFiles.size() > 0) { + return Localization.lang("Open file %0", linkedFiles.get(0).getLink()); + } + return null; + } + + private ContextMenu createFileMenu(BibEntryTableViewModel entry, List linkedFiles) { + if (linkedFiles.size() <= 1) { + return null; + } + + ContextMenu contextMenu = new ContextMenu(); + + for (LinkedFile linkedFile : linkedFiles) { + LinkedFileViewModel linkedFileViewModel = new LinkedFileViewModel(linkedFile, + entry.getEntry(), + database, + Globals.TASK_EXECUTOR, + dialogService, + preferencesService.getXMPPreferences(), + preferencesService.getFilePreferences(), + externalFileTypes); + + MenuItem menuItem = new MenuItem(linkedFileViewModel.getTruncatedDescriptionAndLink(), + linkedFileViewModel.getTypeIcon().getGraphicNode()); + menuItem.setOnAction(event -> linkedFileViewModel.open()); + contextMenu.getItems().add(menuItem); + } + + return contextMenu; + } + + 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(); + } else { + return null; + } + } +} diff --git a/src/main/java/org/jabref/gui/maintable/columns/LinkedIdentifierColumn.java b/src/main/java/org/jabref/gui/maintable/columns/LinkedIdentifierColumn.java new file mode 100644 index 00000000000..ea0ad5376b2 --- /dev/null +++ b/src/main/java/org/jabref/gui/maintable/columns/LinkedIdentifierColumn.java @@ -0,0 +1,92 @@ +package org.jabref.gui.maintable.columns; + +import java.io.IOException; +import java.util.Map; + +import javafx.scene.Node; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.Tooltip; + +import org.jabref.gui.DialogService; +import org.jabref.gui.desktop.JabRefDesktop; +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.maintable.BibEntryTableViewModel; +import org.jabref.gui.maintable.CellFactory; +import org.jabref.gui.maintable.ColumnPreferences; +import org.jabref.gui.maintable.MainTableColumnFactory; +import org.jabref.gui.maintable.MainTableColumnModel; +import org.jabref.gui.util.ControlHelper; +import org.jabref.gui.util.ValueTableCellFactory; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.StandardField; + +/** + * A clickable icons column for DOIs, URLs, URIs and EPrints. + */ +public class LinkedIdentifierColumn extends MainTableColumn> { + + private final BibDatabaseContext database; + private final CellFactory cellFactory; + private final DialogService dialogService; + + public LinkedIdentifierColumn(MainTableColumnModel model, + CellFactory cellFactory, + BibDatabaseContext database, + DialogService dialogService) { + super(model); + this.database = database; + this.cellFactory = cellFactory; + this.dialogService = dialogService; + + Node headerGraphic = IconTheme.JabRefIcons.WWW.getGraphicNode(); + Tooltip.install(headerGraphic, new Tooltip(Localization.lang("Linked identifiers"))); + this.setGraphic(headerGraphic); + this.getStyleClass().add(MainTableColumnFactory.STYLE_ICON_COLUMN); + MainTableColumnFactory.setExactWidth(this, ColumnPreferences.ICON_COLUMN_WIDTH); + this.setResizable(false); + this.setCellValueFactory(cellData -> cellData.getValue().getLinkedIdentifiers()); + new ValueTableCellFactory>() + .withGraphic(this::createIdentifierGraphic) + .withTooltip(this::createIdentifierTooltip) + .withMenu(this::createIdentifierMenu) + .install(this); + } + + 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() + ": " + + ControlHelper.truncateString(values.get(field), -1, "...", ControlHelper.EllipsisPosition.CENTER), + 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; + } +} diff --git a/src/main/java/org/jabref/gui/maintable/MainTableColumn.java b/src/main/java/org/jabref/gui/maintable/columns/MainTableColumn.java similarity index 87% rename from src/main/java/org/jabref/gui/maintable/MainTableColumn.java rename to src/main/java/org/jabref/gui/maintable/columns/MainTableColumn.java index 7333e1a8b17..034ac62d7b0 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableColumn.java +++ b/src/main/java/org/jabref/gui/maintable/columns/MainTableColumn.java @@ -1,8 +1,10 @@ -package org.jabref.gui.maintable; +package org.jabref.gui.maintable.columns; import javafx.beans.value.ObservableValue; import javafx.scene.control.TableColumn; +import org.jabref.gui.maintable.BibEntryTableViewModel; +import org.jabref.gui.maintable.MainTableColumnModel; import org.jabref.gui.util.BindingsHelper; public class MainTableColumn extends TableColumn { diff --git a/src/main/java/org/jabref/gui/maintable/columns/SpecialFieldColumn.java b/src/main/java/org/jabref/gui/maintable/columns/SpecialFieldColumn.java new file mode 100644 index 00000000000..cb28ee91a3f --- /dev/null +++ b/src/main/java/org/jabref/gui/maintable/columns/SpecialFieldColumn.java @@ -0,0 +1,128 @@ +package org.jabref.gui.maintable.columns; + +import java.util.Optional; + +import javax.swing.undo.UndoManager; + +import javafx.scene.Node; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.Tooltip; +import javafx.scene.input.MouseButton; + +import org.jabref.gui.icon.JabRefIcon; +import org.jabref.gui.maintable.BibEntryTableViewModel; +import org.jabref.gui.maintable.ColumnPreferences; +import org.jabref.gui.maintable.MainTableColumnFactory; +import org.jabref.gui.maintable.MainTableColumnModel; +import org.jabref.gui.specialfields.SpecialFieldValueViewModel; +import org.jabref.gui.specialfields.SpecialFieldViewModel; +import org.jabref.gui.specialfields.SpecialFieldsPreferences; +import org.jabref.gui.util.OptionalValueTableCellFactory; +import org.jabref.gui.util.comparator.PriorityFieldComparator; +import org.jabref.gui.util.comparator.RankingFieldComparator; +import org.jabref.gui.util.comparator.ReadStatusFieldComparator; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.FieldFactory; +import org.jabref.model.entry.field.SpecialField; +import org.jabref.model.entry.field.SpecialFieldValue; + +import com.tobiasdiez.easybind.EasyBind; +import org.controlsfx.control.Rating; + +/** + * A column that displays a SpecialField + */ +public class SpecialFieldColumn extends MainTableColumn> { + + private final UndoManager undoManager; + + public SpecialFieldColumn(MainTableColumnModel model, UndoManager undoManager) { + super(model); + this.undoManager = undoManager; + + SpecialField specialField = (SpecialField) FieldFactory.parseField(model.getQualifier()); + SpecialFieldViewModel specialFieldViewModel = new SpecialFieldViewModel(specialField, undoManager); + + Node headerGraphic = specialFieldViewModel.getIcon().getGraphicNode(); + Tooltip.install(headerGraphic, new Tooltip(specialFieldViewModel.getLocalization())); + this.setGraphic(headerGraphic); + this.getStyleClass().add(MainTableColumnFactory.STYLE_ICON_COLUMN); + + if (specialField == SpecialField.RANKING) { + MainTableColumnFactory.setExactWidth(this, SpecialFieldsPreferences.COLUMN_RANKING_WIDTH); + this.setResizable(false); + new OptionalValueTableCellFactory() + .withGraphicIfPresent(this::createSpecialRating) + .install(this); + } else { + MainTableColumnFactory.setExactWidth(this, ColumnPreferences.ICON_COLUMN_WIDTH); + this.setResizable(false); + + if (specialField.isSingleValueField()) { + new OptionalValueTableCellFactory() + .withGraphic((entry, value) -> createSpecialFieldIcon(value, specialFieldViewModel)) + .withOnMouseClickedEvent((entry, value) -> event -> { + if (event.getButton() == MouseButton.PRIMARY) { + specialFieldViewModel.toggle(entry.getEntry()); + } + }) + .install(this); + } else { + new OptionalValueTableCellFactory() + .withGraphic((entry, value) -> createSpecialFieldIcon(value, specialFieldViewModel)) + .withMenu((entry, value) -> createSpecialFieldMenu(entry.getEntry(), specialFieldViewModel)) + .install(this); + } + } + + this.setCellValueFactory(cellData -> cellData.getValue().getSpecialField(specialField)); + + if (specialField == SpecialField.RANKING) { + this.setComparator(new RankingFieldComparator()); + } + + // Added comparator for Read Status + if (specialField == SpecialField.READ_STATUS) { + this.setComparator(new ReadStatusFieldComparator()); + } + + if (specialField == SpecialField.PRIORITY) { + this.setComparator(new PriorityFieldComparator()); + } + + this.setSortable(true); + } + + private Rating createSpecialRating(BibEntryTableViewModel entry, SpecialFieldValueViewModel value) { + Rating ranking = new Rating(); + ranking.setRating(value.getValue().toRating()); + EasyBind.subscribe(ranking.ratingProperty(), rating -> + new SpecialFieldViewModel(SpecialField.RANKING, undoManager) + .setSpecialFieldValue(entry.getEntry(), SpecialFieldValue.getRating(rating.intValue()))); + + return ranking; + } + + private ContextMenu createSpecialFieldMenu(BibEntry entry, SpecialFieldViewModel specialField) { + ContextMenu contextMenu = new ContextMenu(); + + for (SpecialFieldValueViewModel value : specialField.getValues()) { + MenuItem menuItem = new MenuItem(value.getMenuString(), value.getIcon().map(JabRefIcon::getGraphicNode).orElse(null)); + menuItem.setOnAction(event -> specialField.setSpecialFieldValue(entry, value.getValue())); + contextMenu.getItems().add(menuItem); + } + + return contextMenu; + } + + private Node createSpecialFieldIcon(Optional fieldValue, SpecialFieldViewModel specialField) { + return fieldValue.flatMap(SpecialFieldValueViewModel::getIcon) + .map(JabRefIcon::getGraphicNode) + .orElseGet(() -> { + Node node = specialField.getEmptyIcon().getGraphicNode(); + node.getStyleClass().add("empty-special-field"); + return node; + }); + } +}