From 053b35588f641b61afdd2062c450e038dbe0268d Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Thu, 11 Jan 2018 22:20:33 +0100 Subject: [PATCH 1/4] Show special field icons properly in main table --- src/main/java/org/jabref/gui/IconTheme.java | 5 +- .../gui/InternalMaterialDesignIcon.java | 40 +++++----- .../org/jabref/gui/maintable/MainTable.css | 28 +++++++ .../gui/maintable/MainTableColumnFactory.java | 74 ++++++++++++++++++- .../SpecialFieldValueViewModel.java | 46 ++++++------ .../specialfields/SpecialFieldViewModel.java | 11 +++ .../java/org/jabref/gui/util/ColorUtil.java | 28 +++++++ .../gui/util/ValueTableCellFactory.java | 20 +++++ .../jabref/preferences/JabRefPreferences.java | 8 +- 9 files changed, 208 insertions(+), 52 deletions(-) create mode 100644 src/main/java/org/jabref/gui/util/ColorUtil.java diff --git a/src/main/java/org/jabref/gui/IconTheme.java b/src/main/java/org/jabref/gui/IconTheme.java index da228780bd5..49de563c05f 100644 --- a/src/main/java/org/jabref/gui/IconTheme.java +++ b/src/main/java/org/jabref/gui/IconTheme.java @@ -27,6 +27,7 @@ import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import org.jabref.gui.util.ColorUtil; import org.jabref.logic.groups.DefaultGroupsFactory; import org.jabref.preferences.JabRefPreferences; @@ -71,7 +72,7 @@ private static InputStream getMaterialDesignIconsStream() { } public static javafx.scene.paint.Color getDefaultColor() { - return javafx.scene.paint.Color.rgb(DEFAULT_COLOR.getRed(), DEFAULT_COLOR.getGreen(), DEFAULT_COLOR.getBlue(), DEFAULT_COLOR.getAlpha() / 255.0); + return ColorUtil.toFX(DEFAULT_COLOR); } /** @@ -277,7 +278,7 @@ public enum JabRefIcons implements JabRefIcon { private final JabRefIcon icon; JabRefIcons(MaterialDesignIcon... icons) { - this(IconTheme.DEFAULT_COLOR, icons); + icon = new InternalMaterialDesignIcon(icons); } JabRefIcons(Color color, MaterialDesignIcon... icons) { diff --git a/src/main/java/org/jabref/gui/InternalMaterialDesignIcon.java b/src/main/java/org/jabref/gui/InternalMaterialDesignIcon.java index d8d9f44e338..e568773f91f 100644 --- a/src/main/java/org/jabref/gui/InternalMaterialDesignIcon.java +++ b/src/main/java/org/jabref/gui/InternalMaterialDesignIcon.java @@ -2,13 +2,16 @@ import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import javax.swing.Icon; import javafx.scene.Node; import javafx.scene.paint.Color; +import javafx.scene.text.Text; +import org.jabref.gui.util.ColorUtil; import org.jabref.preferences.JabRefPreferences; import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; @@ -16,11 +19,11 @@ public class InternalMaterialDesignIcon implements JabRefIcon { private final List icons; - private final Color color; + private Optional color; private final String unicode; public InternalMaterialDesignIcon(java.awt.Color color, MaterialDesignIcon... icons) { - this(toFX(color), Arrays.asList(icons)); + this(ColorUtil.toFX(color), Arrays.asList(icons)); } public InternalMaterialDesignIcon(Color color, MaterialDesignIcon... icons) { @@ -28,45 +31,40 @@ public InternalMaterialDesignIcon(Color color, MaterialDesignIcon... icons) { } InternalMaterialDesignIcon(Color color, List icons) { - this.icons = icons; - this.color = color; - this.unicode = icons.stream().map(MaterialDesignIcon::unicode).collect(Collectors.joining()); + this(icons); + this.color = Optional.of(color); } - public static java.awt.Color toAWT(Color color) { - return new java.awt.Color((float) color.getRed(), - (float) color.getGreen(), - (float) color.getBlue(), - (float) color.getOpacity()); + public InternalMaterialDesignIcon(MaterialDesignIcon... icons) { + this(Arrays.asList(icons)); } - public static Color toFX(java.awt.Color awtColor) { - int r = awtColor.getRed(); - int g = awtColor.getGreen(); - int b = awtColor.getBlue(); - int a = awtColor.getAlpha(); - double opacity = a / 255.0; - return javafx.scene.paint.Color.rgb(r, g, b, opacity); + public InternalMaterialDesignIcon(List icons) { + this.icons = icons; + this.unicode = icons.stream().map(MaterialDesignIcon::unicode).collect(Collectors.joining()); + this.color = Optional.empty(); } @Override public Icon getIcon() { - return new IconTheme.FontBasedIcon(this.unicode, toAWT(this.color)); + return new IconTheme.FontBasedIcon(this.unicode, ColorUtil.toAWT(this.color.orElse(IconTheme.getDefaultColor()))); } @Override public Icon getSmallIcon() { - return new IconTheme.FontBasedIcon(this.unicode, toAWT(this.color), JabRefPreferences.getInstance().getInt(JabRefPreferences.ICON_SIZE_SMALL)); + return new IconTheme.FontBasedIcon(this.unicode, ColorUtil.toAWT(this.color.orElse(IconTheme.getDefaultColor())), JabRefPreferences.getInstance().getInt(JabRefPreferences.ICON_SIZE_SMALL)); } @Override public Node getGraphicNode() { - return MaterialDesignIconFactory.get().createIcon(this.icons.get(0)); + Text icon = MaterialDesignIconFactory.get().createIcon(icons.get(0)); + color.ifPresent(color -> icon.setStyle(icon.getStyle() + String.format("-fx-fill: %s;", ColorUtil.toRGBCode(color)))); + return icon; } @Override public JabRefIcon disabled() { - return new InternalMaterialDesignIcon(toFX(IconTheme.DEFAULT_DISABLED_COLOR), icons); + return new InternalMaterialDesignIcon(ColorUtil.toFX(IconTheme.DEFAULT_DISABLED_COLOR), icons); } public String getCode() { diff --git a/src/main/java/org/jabref/gui/maintable/MainTable.css b/src/main/java/org/jabref/gui/maintable/MainTable.css index 1bb042ca76d..00b26481afe 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTable.css +++ b/src/main/java/org/jabref/gui/maintable/MainTable.css @@ -48,3 +48,31 @@ -fx-padding: 0; -fx-alignment: baseline-center; } + +.empty-special-field { + visibility: hidden; +} + +.table-row-cell:hover .empty-special-field { + visibility: visible; + -fx-fill: -fx-unimportant; +} + +.rating > .container { + -fx-spacing: 2; +} + +.rating > .container > .button { + -fx-pref-width: 10; + -fx-pref-height: 10; + -fx-background-size: cover; + -fx-padding: 0; +} + +.rating > .container > .button.strong { + +} + +.rating > .container > .button:hover { + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.6), 8, 0.0, 0, 0); +} diff --git a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java index aa5c0a10b66..246c1930e3a 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java @@ -8,6 +8,8 @@ import java.util.stream.Collectors; import javafx.scene.Node; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; import javafx.scene.control.TableColumn; import org.jabref.gui.GUIGlobals; @@ -22,7 +24,9 @@ import org.jabref.model.entry.FieldName; import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.specialfields.SpecialField; +import org.jabref.model.entry.specialfields.SpecialFieldValue; +import org.controlsfx.control.Rating; import org.fxmisc.easybind.EasyBind; class MainTableColumnFactory { @@ -94,21 +98,83 @@ public MainTableColumnFactory(BibDatabase database, ColumnPreferences preference private TableColumn> createSpecialFieldColumn(SpecialField specialField) { TableColumn> column = new TableColumn<>(); - column.setGraphic(new SpecialFieldViewModel(specialField).getIcon().getGraphicNode()); + SpecialFieldViewModel specialFieldViewModel = new SpecialFieldViewModel(specialField); + column.setGraphic(specialFieldViewModel.getIcon().getGraphicNode()); column.getStyleClass().add(STYLE_ICON); if (specialField == SpecialField.RANKING) { setExactWidth(column, GUIGlobals.WIDTH_ICON_COL_RANKING); + column.setCellFactory( + new ValueTableCellFactory>() + .withGraphic(value -> value.map(this::createRating).orElse(null)) + ); } else { setExactWidth(column, GUIGlobals.WIDTH_ICON_COL); + + if (specialField.isSingleValueField()) { + column.setCellFactory( + new ValueTableCellFactory>() + .withGraphic(value -> createSpecialFieldIcon(value, specialFieldViewModel)) + ); + } else { + column.setCellFactory( + new ValueTableCellFactory>() + .withGraphic(value -> createSpecialFieldIcon(value, specialFieldViewModel)) + .withContextMenu(value -> createSpecialFieldContextMenu(specialFieldViewModel)) + ); + } } column.setCellValueFactory(cellData -> cellData.getValue().getSpecialField(specialField)); - column.setCellFactory( - new ValueTableCellFactory>() - .withGraphic(param -> param.map(specialFieldValue -> specialFieldValue.getIcon().getGraphicNode()).orElse(null))); + return column; } + private Rating createRating(SpecialFieldValueViewModel value) { + Rating ranking = new Rating(); + ranking.setRating(toRating(value.getValue())); + EasyBind.subscribe(ranking.ratingProperty(), rating -> { + }); + return ranking; + } + + private int toRating(SpecialFieldValue ranking) { + switch (ranking) { + case RANK_1: + return 1; + case RANK_2: + return 2; + case RANK_3: + return 3; + case RANK_4: + return 4; + case RANK_5: + return 5; + default: + throw new UnsupportedOperationException(ranking + "is not a valid ranking"); + } + } + + private ContextMenu createSpecialFieldContextMenu(SpecialFieldViewModel specialField) { + ContextMenu contextMenu = new ContextMenu(); + + for (SpecialFieldValueViewModel value : specialField.getValues()) { + contextMenu.getItems().add(new MenuItem(value.getMenuString(), value.getIcon().map(JabRefIcon::getGraphicNode).orElse(null))); + } + + 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; + }); + } + private void setExactWidth(TableColumn column, int widthIconCol) { column.setMinWidth(widthIconCol); column.setPrefWidth(widthIconCol); diff --git a/src/main/java/org/jabref/gui/specialfields/SpecialFieldValueViewModel.java b/src/main/java/org/jabref/gui/specialfields/SpecialFieldValueViewModel.java index 54b6166d949..639d43d5a94 100644 --- a/src/main/java/org/jabref/gui/specialfields/SpecialFieldValueViewModel.java +++ b/src/main/java/org/jabref/gui/specialfields/SpecialFieldValueViewModel.java @@ -1,6 +1,7 @@ package org.jabref.gui.specialfields; import java.util.Objects; +import java.util.Optional; import javax.swing.Icon; import javax.swing.JLabel; @@ -12,6 +13,10 @@ public class SpecialFieldValueViewModel { + public SpecialFieldValue getValue() { + return value; + } + private final SpecialFieldValue value; public SpecialFieldValueViewModel(SpecialFieldValue value) { @@ -21,48 +26,43 @@ public SpecialFieldValueViewModel(SpecialFieldValue value) { } public Icon getSpecialFieldValueIcon() { - JabRefIcon icon = getIcon(); - if (icon == null) { - return null; - } else { - return icon.getSmallIcon(); - } + return getIcon().map(JabRefIcon::getSmallIcon).orElse(null); } - public JabRefIcon getIcon() { + public Optional getIcon() { switch (value) { case PRINTED: - return IconTheme.JabRefIcons.PRINTED; + return Optional.of(IconTheme.JabRefIcons.PRINTED); case CLEAR_PRIORITY: - return null; + return Optional.empty(); case PRIORITY_HIGH: - return IconTheme.JabRefIcons.PRIORITY_HIGH; + return Optional.of(IconTheme.JabRefIcons.PRIORITY_HIGH); case PRIORITY_MEDIUM: - return IconTheme.JabRefIcons.PRIORITY_MEDIUM; + return Optional.of(IconTheme.JabRefIcons.PRIORITY_MEDIUM); case PRIORITY_LOW: - return IconTheme.JabRefIcons.PRIORITY_LOW; + return Optional.of(IconTheme.JabRefIcons.PRIORITY_LOW); case QUALITY_ASSURED: - return IconTheme.JabRefIcons.QUALITY_ASSURED; + return Optional.of(IconTheme.JabRefIcons.QUALITY_ASSURED); case CLEAR_RANK: - return null; + return Optional.empty(); case RANK_1: - return IconTheme.JabRefIcons.RANK1; + return Optional.of(IconTheme.JabRefIcons.RANK1); case RANK_2: - return IconTheme.JabRefIcons.RANK2; + return Optional.of(IconTheme.JabRefIcons.RANK2); case RANK_3: - return IconTheme.JabRefIcons.RANK3; + return Optional.of(IconTheme.JabRefIcons.RANK3); case RANK_4: - return IconTheme.JabRefIcons.RANK4; + return Optional.of(IconTheme.JabRefIcons.RANK4); case RANK_5: - return IconTheme.JabRefIcons.RANK5; + return Optional.of(IconTheme.JabRefIcons.RANK5); case CLEAR_READ_STATUS: - return null; + return Optional.empty(); case READ: - return IconTheme.JabRefIcons.READ_STATUS_READ; + return Optional.of(IconTheme.JabRefIcons.READ_STATUS_READ); case SKIMMED: - return IconTheme.JabRefIcons.READ_STATUS_SKIMMED; + return Optional.of(IconTheme.JabRefIcons.READ_STATUS_SKIMMED); case RELEVANT: - return IconTheme.JabRefIcons.RELEVANCE; + return Optional.of(IconTheme.JabRefIcons.RELEVANCE); default: throw new IllegalArgumentException("There is no icon mapping for special field value " + value); } diff --git a/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java b/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java index a372ba71417..5b235bc4f6d 100644 --- a/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java +++ b/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java @@ -1,6 +1,8 @@ package org.jabref.gui.specialfields; +import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import javax.swing.Icon; @@ -85,4 +87,13 @@ public String getLocalization() { } } + public JabRefIcon getEmptyIcon() { + return getIcon(); + } + + public List getValues() { + return field.getValues().stream() + .map(SpecialFieldValueViewModel::new) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/org/jabref/gui/util/ColorUtil.java b/src/main/java/org/jabref/gui/util/ColorUtil.java new file mode 100644 index 00000000000..3b713ded83c --- /dev/null +++ b/src/main/java/org/jabref/gui/util/ColorUtil.java @@ -0,0 +1,28 @@ +package org.jabref.gui.util; + +import javafx.scene.paint.Color; + +public class ColorUtil { + public static java.awt.Color toAWT(Color color) { + return new java.awt.Color((float) color.getRed(), + (float) color.getGreen(), + (float) color.getBlue(), + (float) color.getOpacity()); + } + + public static Color toFX(java.awt.Color awtColor) { + int r = awtColor.getRed(); + int g = awtColor.getGreen(); + int b = awtColor.getBlue(); + int a = awtColor.getAlpha(); + double opacity = a / 255.0; + return Color.rgb(r, g, b, opacity); + } + + public static String toRGBCode(Color color) { + return String.format("#%02X%02X%02X", + (int) (color.getRed() * 255), + (int) (color.getGreen() * 255), + (int) (color.getBlue() * 255)); + } +} diff --git a/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java b/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java index 2d4892086de..e8398de843b 100644 --- a/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java +++ b/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java @@ -1,7 +1,10 @@ package org.jabref.gui.util; +import java.util.function.Function; + import javafx.event.EventHandler; import javafx.scene.Node; +import javafx.scene.control.ContextMenu; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.Tooltip; @@ -22,6 +25,7 @@ public class ValueTableCellFactory implements Callback, private Callback toGraphic; private Callback> toOnMouseClickedEvent; private Callback toTooltip; + private Function contextMenuFactory; public ValueTableCellFactory withText(Callback toText) { this.toText = toText; @@ -44,6 +48,11 @@ public ValueTableCellFactory withOnMouseClickedEvent( return this; } + public ValueTableCellFactory withContextMenu(Function contextMenuFactory) { + this.contextMenuFactory = contextMenuFactory; + return this; + } + @Override public TableCell call(TableColumn param) { @@ -74,6 +83,17 @@ protected void updateItem(T item, boolean empty) { if (toOnMouseClickedEvent != null) { setOnMouseClicked(toOnMouseClickedEvent.call(item)); } + + if (contextMenuFactory != null) { + // We only create the context menu when really necessary + setOnContextMenuRequested(event -> { + if (!isEmpty()) { + setContextMenu(contextMenuFactory.apply(item)); + getContextMenu().show(this, event.getScreenX(), event.getScreenY()); + } + event.consume(); + }); + } } } }; diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index fa1fdcc2425..80422e2a647 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -632,7 +632,7 @@ private JabRefPreferences() { defaults.put(FIELD_EDITOR_TEXT_COLOR, "0:0:0"); // default icon colors - defaults.put(ICON_ENABLED_COLOR, "79:95:143"); + defaults.put(ICON_ENABLED_COLOR, "0:0:0"); defaults.put(ICON_DISABLED_COLOR, "200:200:200"); defaults.put(INCOMPLETE_ENTRY_BACKGROUND, "250:175:175"); @@ -1292,7 +1292,11 @@ private Object getObject(String key) { try { return this.getBoolean(key); } catch (ClassCastException e2) { - return this.getInt(key); + try { + return this.getInt(key); + } catch (ClassCastException e3) { + return this.getDouble(key); + } } } } From 18ccd0805f8b76915c6a26e592ab521cf62d9e6f Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Thu, 18 Jan 2018 18:53:48 +0100 Subject: [PATCH 2/4] Make icons functional --- src/main/java/org/jabref/gui/BasePanel.java | 12 +-- .../org/jabref/gui/maintable/CellFactory.java | 16 +-- .../org/jabref/gui/maintable/MainTable.java | 2 +- .../gui/maintable/MainTableColumnFactory.java | 90 ++++++++-------- .../jabref/gui/maintable/RightClickMenu.java | 2 +- .../gui/specialfields/SpecialFieldAction.java | 2 +- .../specialfields/SpecialFieldDropDown.java | 2 +- .../specialfields/SpecialFieldViewModel.java | 29 ++++- .../util/OptionalValueTableCellFactory.java | 41 +++++++ .../gui/util/ValueTableCellFactory.java | 62 ++++++++--- .../specialfields/SpecialFieldsUtils.java | 6 +- .../specialfields/SpecialFieldValue.java | 33 ++++++ src/test/resources/testbib/complex.bib | 102 +++++++++--------- 13 files changed, 262 insertions(+), 137 deletions(-) create mode 100644 src/main/java/org/jabref/gui/util/OptionalValueTableCellFactory.java diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/BasePanel.java index 668a0046864..f8917ace927 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/BasePanel.java @@ -638,26 +638,26 @@ public void update() { // Note that we can't put the number of entries that have been reverted into the undoText as the concrete number cannot be injected actions.put(new SpecialFieldValueViewModel(SpecialField.RELEVANCE.getValues().get(0)).getActionName(), - new SpecialFieldViewModel(SpecialField.RELEVANCE).getSpecialFieldAction( + new SpecialFieldViewModel(SpecialField.RELEVANCE, undoManager).getSpecialFieldAction( SpecialField.RELEVANCE.getValues().get(0), frame)); actions.put(new SpecialFieldValueViewModel(SpecialField.QUALITY.getValues().get(0)).getActionName(), - new SpecialFieldViewModel(SpecialField.QUALITY) + new SpecialFieldViewModel(SpecialField.QUALITY, undoManager) .getSpecialFieldAction(SpecialField.QUALITY.getValues().get(0), frame)); actions.put(new SpecialFieldValueViewModel(SpecialField.PRINTED.getValues().get(0)).getActionName(), - new SpecialFieldViewModel(SpecialField.PRINTED).getSpecialFieldAction( + new SpecialFieldViewModel(SpecialField.PRINTED, undoManager).getSpecialFieldAction( SpecialField.PRINTED.getValues().get(0), frame)); for (SpecialFieldValue prio : SpecialField.PRIORITY.getValues()) { actions.put(new SpecialFieldValueViewModel(prio).getActionName(), - new SpecialFieldViewModel(SpecialField.PRIORITY).getSpecialFieldAction(prio, this.frame)); + new SpecialFieldViewModel(SpecialField.PRIORITY, undoManager).getSpecialFieldAction(prio, this.frame)); } for (SpecialFieldValue rank : SpecialField.RANKING.getValues()) { actions.put(new SpecialFieldValueViewModel(rank).getActionName(), - new SpecialFieldViewModel(SpecialField.RANKING).getSpecialFieldAction(rank, this.frame)); + new SpecialFieldViewModel(SpecialField.RANKING, undoManager).getSpecialFieldAction(rank, this.frame)); } for (SpecialFieldValue status : SpecialField.READ_STATUS.getValues()) { actions.put(new SpecialFieldValueViewModel(status).getActionName(), - new SpecialFieldViewModel(SpecialField.READ_STATUS).getSpecialFieldAction(status, this.frame)); + new SpecialFieldViewModel(SpecialField.READ_STATUS, undoManager).getSpecialFieldAction(status, this.frame)); } actions.put(Actions.TOGGLE_PREVIEW, (BaseAction) () -> { diff --git a/src/main/java/org/jabref/gui/maintable/CellFactory.java b/src/main/java/org/jabref/gui/maintable/CellFactory.java index 72b118cfcb2..f3ab65dbf5d 100644 --- a/src/main/java/org/jabref/gui/maintable/CellFactory.java +++ b/src/main/java/org/jabref/gui/maintable/CellFactory.java @@ -3,6 +3,8 @@ import java.util.HashMap; import java.util.Map; +import javax.swing.undo.UndoManager; + import javafx.scene.Node; import org.jabref.gui.IconTheme; @@ -16,7 +18,7 @@ public class CellFactory { private final Map TABLE_ICONS = new HashMap<>(); - public CellFactory(ExternalFileTypes externalFileTypes) { + public CellFactory(ExternalFileTypes externalFileTypes, UndoManager undoManager) { Node label; label = IconTheme.JabRefIcons.PDF_FILE.getGraphicNode(); //label.setToo(Localization.lang("Open") + " PDF"); @@ -56,36 +58,36 @@ public CellFactory(ExternalFileTypes externalFileTypes) { TABLE_ICONS.put(fileType.getName(), label); } - SpecialFieldViewModel relevanceViewModel = new SpecialFieldViewModel(SpecialField.RELEVANCE); + SpecialFieldViewModel relevanceViewModel = new SpecialFieldViewModel(SpecialField.RELEVANCE, undoManager); label = relevanceViewModel.getIcon().getGraphicNode(); //label.setToolTipText(relevanceViewModel.getLocalization()); TABLE_ICONS.put(SpecialField.RELEVANCE.getFieldName(), label); - SpecialFieldViewModel qualityViewModel = new SpecialFieldViewModel(SpecialField.QUALITY); + SpecialFieldViewModel qualityViewModel = new SpecialFieldViewModel(SpecialField.QUALITY, undoManager); label = qualityViewModel.getIcon().getGraphicNode(); //label.setToolTipText(qualityViewModel.getLocalization()); TABLE_ICONS.put(SpecialField.QUALITY.getFieldName(), label); // Ranking item in the menu uses one star - SpecialFieldViewModel rankViewModel = new SpecialFieldViewModel(SpecialField.RANKING); + SpecialFieldViewModel rankViewModel = new SpecialFieldViewModel(SpecialField.RANKING, undoManager); label = rankViewModel.getIcon().getGraphicNode(); //label.setToolTipText(rankViewModel.getLocalization()); TABLE_ICONS.put(SpecialField.RANKING.getFieldName(), label); // Priority icon used for the menu - SpecialFieldViewModel priorityViewModel = new SpecialFieldViewModel(SpecialField.PRIORITY); + SpecialFieldViewModel priorityViewModel = new SpecialFieldViewModel(SpecialField.PRIORITY, undoManager); label = priorityViewModel.getIcon().getGraphicNode(); //label.setToolTipText(priorityViewModel.getLocalization()); TABLE_ICONS.put(SpecialField.PRIORITY.getFieldName(), label); // Read icon used for menu - SpecialFieldViewModel readViewModel = new SpecialFieldViewModel(SpecialField.READ_STATUS); + SpecialFieldViewModel readViewModel = new SpecialFieldViewModel(SpecialField.READ_STATUS, undoManager); label = readViewModel.getIcon().getGraphicNode(); //label.setToolTipText(readViewModel.getLocalization()); TABLE_ICONS.put(SpecialField.READ_STATUS.getFieldName(), label); // Print icon used for menu - SpecialFieldViewModel printedViewModel = new SpecialFieldViewModel(SpecialField.PRINTED); + SpecialFieldViewModel printedViewModel = new SpecialFieldViewModel(SpecialField.PRINTED, undoManager); label = printedViewModel.getIcon().getGraphicNode(); //label.setToolTipText(printedViewModel.getLocalization()); TABLE_ICONS.put(SpecialField.PRINTED.getFieldName(), label); diff --git a/src/main/java/org/jabref/gui/maintable/MainTable.java b/src/main/java/org/jabref/gui/maintable/MainTable.java index 07e4044b26e..f02924df464 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTable.java +++ b/src/main/java/org/jabref/gui/maintable/MainTable.java @@ -76,7 +76,7 @@ public MainTable(MainTableDataModel model, JabRefFrame frame, super(); this.model = model; - this.getColumns().addAll(new MainTableColumnFactory(database, preferences.getColumnPreferences(), externalFileTypes).createColumns()); + this.getColumns().addAll(new MainTableColumnFactory(database, preferences.getColumnPreferences(), externalFileTypes, panel.getUndoManager()).createColumns()); this.setRowFactory(new ViewModelTableRowFactory() .withOnMouseClickedEvent((entry, event) -> { if (event.getClickCount() == 2) { diff --git a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java index 246c1930e3a..dbc3bf96656 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java @@ -7,10 +7,13 @@ import java.util.Optional; import java.util.stream.Collectors; +import javax.swing.undo.UndoManager; + import javafx.scene.Node; import javafx.scene.control.ContextMenu; import javafx.scene.control.MenuItem; import javafx.scene.control.TableColumn; +import javafx.scene.input.MouseButton; import org.jabref.gui.GUIGlobals; import org.jabref.gui.IconTheme; @@ -19,8 +22,10 @@ import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.specialfields.SpecialFieldValueViewModel; import org.jabref.gui.specialfields.SpecialFieldViewModel; +import org.jabref.gui.util.OptionalValueTableCellFactory; import org.jabref.gui.util.ValueTableCellFactory; import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.specialfields.SpecialField; @@ -37,12 +42,14 @@ class MainTableColumnFactory { private final ExternalFileTypes externalFileTypes; private final BibDatabase database; private final CellFactory cellFactory; + private final UndoManager undoManager; - public MainTableColumnFactory(BibDatabase database, ColumnPreferences preferences, ExternalFileTypes externalFileTypes) { + public MainTableColumnFactory(BibDatabase database, ColumnPreferences preferences, ExternalFileTypes externalFileTypes, UndoManager undoManager) { this.database = Objects.requireNonNull(database); this.preferences = Objects.requireNonNull(preferences); this.externalFileTypes = Objects.requireNonNull(externalFileTypes); - this.cellFactory = new CellFactory(externalFileTypes); + this.cellFactory = new CellFactory(externalFileTypes, undoManager); + this.undoManager = undoManager; } public List> createColumns() { @@ -98,67 +105,54 @@ public MainTableColumnFactory(BibDatabase database, ColumnPreferences preference private TableColumn> createSpecialFieldColumn(SpecialField specialField) { TableColumn> column = new TableColumn<>(); - SpecialFieldViewModel specialFieldViewModel = new SpecialFieldViewModel(specialField); + SpecialFieldViewModel specialFieldViewModel = new SpecialFieldViewModel(specialField, undoManager); column.setGraphic(specialFieldViewModel.getIcon().getGraphicNode()); column.getStyleClass().add(STYLE_ICON); if (specialField == SpecialField.RANKING) { setExactWidth(column, GUIGlobals.WIDTH_ICON_COL_RANKING); - column.setCellFactory( - new ValueTableCellFactory>() - .withGraphic(value -> value.map(this::createRating).orElse(null)) - ); + new OptionalValueTableCellFactory() + .withGraphicIfPresent(this::createRating) + .install(column); } else { setExactWidth(column, GUIGlobals.WIDTH_ICON_COL); if (specialField.isSingleValueField()) { - column.setCellFactory( - new ValueTableCellFactory>() - .withGraphic(value -> createSpecialFieldIcon(value, specialFieldViewModel)) - ); + new OptionalValueTableCellFactory() + .withGraphic((entry, value) -> createSpecialFieldIcon(value, specialFieldViewModel)) + .withOnMouseClickedEvent((entry, value) -> event -> { + if (event.getButton() == MouseButton.PRIMARY) { + specialFieldViewModel.toggle(entry.getEntry()); + } + }) + .install(column); } else { - column.setCellFactory( - new ValueTableCellFactory>() - .withGraphic(value -> createSpecialFieldIcon(value, specialFieldViewModel)) - .withContextMenu(value -> createSpecialFieldContextMenu(specialFieldViewModel)) - ); + new OptionalValueTableCellFactory() + .withGraphic((entry, value) -> createSpecialFieldIcon(value, specialFieldViewModel)) + .withMenu((entry, value) -> createSpecialFieldMenu(entry.getEntry(), specialFieldViewModel)) + .install(column); } } column.setCellValueFactory(cellData -> cellData.getValue().getSpecialField(specialField)); - return column; } - private Rating createRating(SpecialFieldValueViewModel value) { + private Rating createRating(BibEntryTableViewModel entry, SpecialFieldValueViewModel value) { Rating ranking = new Rating(); - ranking.setRating(toRating(value.getValue())); - EasyBind.subscribe(ranking.ratingProperty(), 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 int toRating(SpecialFieldValue ranking) { - switch (ranking) { - case RANK_1: - return 1; - case RANK_2: - return 2; - case RANK_3: - return 3; - case RANK_4: - return 4; - case RANK_5: - return 5; - default: - throw new UnsupportedOperationException(ranking + "is not a valid ranking"); - } - } - - private ContextMenu createSpecialFieldContextMenu(SpecialFieldViewModel specialField) { + private ContextMenu createSpecialFieldMenu(BibEntry entry, SpecialFieldViewModel specialField) { ContextMenu contextMenu = new ContextMenu(); for (SpecialFieldValueViewModel value : specialField.getValues()) { - contextMenu.getItems().add(new MenuItem(value.getMenuString(), value.getIcon().map(JabRefIcon::getGraphicNode).orElse(null))); + 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; @@ -187,9 +181,9 @@ private TableColumn> createFileColumn() column.getStyleClass().add(STYLE_ICON); setExactWidth(column, GUIGlobals.WIDTH_ICON_COL); column.setCellValueFactory(cellData -> cellData.getValue().getLinkedFiles()); - column.setCellFactory( new ValueTableCellFactory>() - .withGraphic(this::createFileIcon)); + .withGraphic(this::createFileIcon) + .install(column); return column; } @@ -202,9 +196,9 @@ private TableColumn createIconColumn(JabRefIcon column.getStyleClass().add(STYLE_ICON); setExactWidth(column, GUIGlobals.WIDTH_ICON_COL); column.setCellValueFactory(cellData -> EasyBind.monadic(cellData.getValue().getField(firstField)).orElse(cellData.getValue().getField(secondField))); - column.setCellFactory( new ValueTableCellFactory() - .withGraphic(cellFactory::getTableIcon)); + .withGraphic(cellFactory::getTableIcon) + .install(column); return column; } @@ -214,9 +208,9 @@ private TableColumn createIconColumn(JabRefIcon column.getStyleClass().add(STYLE_ICON); setExactWidth(column, GUIGlobals.WIDTH_ICON_COL); column.setCellValueFactory(cellData -> cellData.getValue().getField(field)); - column.setCellFactory( new ValueTableCellFactory() - .withGraphic(cellFactory::getTableIcon)); + .withGraphic(cellFactory::getTableIcon) + .install(column); return column; } @@ -234,9 +228,9 @@ private TableColumn> createExtraFileCol column.getStyleClass().add(STYLE_ICON); setExactWidth(column, GUIGlobals.WIDTH_ICON_COL); column.setCellValueFactory(cellData -> cellData.getValue().getLinkedFiles()); - column.setCellFactory( new ValueTableCellFactory>() - .withGraphic(linkedFiles -> createFileIcon(linkedFiles.stream().filter(linkedFile -> linkedFile.getFileType().equalsIgnoreCase(externalFileTypeName)).collect(Collectors.toList())))); + .withGraphic(linkedFiles -> createFileIcon(linkedFiles.stream().filter(linkedFile -> linkedFile.getFileType().equalsIgnoreCase(externalFileTypeName)).collect(Collectors.toList()))) + .install(column); return column; } diff --git a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index 88b80af064f..d98a8e7b292 100644 --- a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -223,7 +223,7 @@ public RightClickMenu(JabRefFrame frame, BasePanel panel) { * Then cycle through all available values, and add them. */ public static void populateSpecialFieldMenu(JMenu menu, SpecialField field, JabRefFrame frame) { - SpecialFieldViewModel viewModel = new SpecialFieldViewModel(field); + SpecialFieldViewModel viewModel = new SpecialFieldViewModel(field, frame.getCurrentBasePanel().getUndoManager()); menu.setText(viewModel.getLocalization()); menu.setIcon(viewModel.getRepresentingIcon()); for (SpecialFieldValue val : field.getValues()) { diff --git a/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java b/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java index bed9b589bc7..ea0f6b6c8d0 100644 --- a/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java +++ b/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java @@ -83,7 +83,7 @@ public void action() { private String getTextDone(SpecialField field, String... params) { Objects.requireNonNull(params); - SpecialFieldViewModel viewModel = new SpecialFieldViewModel(field); + SpecialFieldViewModel viewModel = new SpecialFieldViewModel(field, frame.getCurrentBasePanel().getUndoManager()); if (field.isSingleValueField() && (params.length == 1) && (params[0] != null)) { // Single value fields can be toggled only diff --git a/src/main/java/org/jabref/gui/specialfields/SpecialFieldDropDown.java b/src/main/java/org/jabref/gui/specialfields/SpecialFieldDropDown.java index 7e3245571fc..7ef6db49b0f 100644 --- a/src/main/java/org/jabref/gui/specialfields/SpecialFieldDropDown.java +++ b/src/main/java/org/jabref/gui/specialfields/SpecialFieldDropDown.java @@ -22,7 +22,7 @@ private SpecialFieldDropDown() { public static JButton generateSpecialFieldButtonWithDropDown(SpecialField field, JabRefFrame frame) { Dimension buttonDim = new Dimension(23, 23); - SpecialFieldViewModel viewModel = new SpecialFieldViewModel(field); + SpecialFieldViewModel viewModel = new SpecialFieldViewModel(field, frame.getCurrentBasePanel().getUndoManager()); JButton button = new JButton(viewModel.getRepresentingIcon()); button.setToolTipText(viewModel.getLocalization()); button.setPreferredSize(buttonDim); diff --git a/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java b/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java index 5b235bc4f6d..1806888515f 100644 --- a/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java +++ b/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java @@ -5,21 +5,33 @@ import java.util.stream.Collectors; import javax.swing.Icon; +import javax.swing.undo.UndoManager; +import org.jabref.Globals; import org.jabref.gui.IconTheme; import org.jabref.gui.JabRefFrame; import org.jabref.gui.JabRefIcon; +import org.jabref.gui.undo.UndoableFieldChange; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.specialfields.SpecialFieldsUtils; +import org.jabref.model.FieldChange; +import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.specialfields.SpecialField; import org.jabref.model.entry.specialfields.SpecialFieldValue; public class SpecialFieldViewModel { + private UndoManager undoManager; + + public SpecialFieldViewModel(SpecialField field, UndoManager undoManager) { + this.field = Objects.requireNonNull(field); + this.undoManager = Objects.requireNonNull(undoManager); + } + private final SpecialField field; - public SpecialFieldViewModel(SpecialField field) { - Objects.requireNonNull(field); - this.field = field; + public SpecialField getField() { + return field; } public SpecialFieldAction getSpecialFieldAction(SpecialFieldValue value, JabRefFrame frame) { @@ -96,4 +108,15 @@ public List getValues() { .map(SpecialFieldValueViewModel::new) .collect(Collectors.toList()); } + + public void setSpecialFieldValue(BibEntry be, SpecialFieldValue value) { + List changes = SpecialFieldsUtils.updateField(getField(), value.getFieldValue().orElse(null), be, getField().isSingleValueField(), Globals.prefs.isKeywordSyncEnabled(), Globals.prefs.getKeywordDelimiter()); + for (FieldChange change : changes) { + undoManager.addEdit(new UndoableFieldChange(change)); + } + } + + public void toggle(BibEntry entry) { + setSpecialFieldValue(entry, getField().getValues().get(0)); + } } diff --git a/src/main/java/org/jabref/gui/util/OptionalValueTableCellFactory.java b/src/main/java/org/jabref/gui/util/OptionalValueTableCellFactory.java new file mode 100644 index 00000000000..2232561583b --- /dev/null +++ b/src/main/java/org/jabref/gui/util/OptionalValueTableCellFactory.java @@ -0,0 +1,41 @@ +package org.jabref.gui.util; + +import java.util.Optional; +import java.util.function.BiFunction; + +import javafx.scene.Node; +import javafx.scene.control.TableCell; + +/** + * Constructs a {@link TableCell} based on an optional value of the cell and a bunch of specified converter methods. + * + * @param view model of table row + * @param cell value + */ +public class OptionalValueTableCellFactory extends ValueTableCellFactory> { + + private BiFunction toGraphicIfPresent; + private Node defaultGraphic; + + public OptionalValueTableCellFactory withGraphicIfPresent(BiFunction toGraphicIfPresent) { + this.toGraphicIfPresent = toGraphicIfPresent; + setToGraphic(); + return this; + } + + public OptionalValueTableCellFactory withDefaultGraphic(Node defaultGraphic) { + this.defaultGraphic = defaultGraphic; + setToGraphic(); + return this; + } + + private void setToGraphic() { + withGraphic((rowItem, item) -> { + if (item.isPresent() && toGraphicIfPresent != null) { + return toGraphicIfPresent.apply(rowItem, item.get()); + } else { + return defaultGraphic; + } + }); + } +} diff --git a/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java b/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java index e8398de843b..b2418d342cd 100644 --- a/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java +++ b/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java @@ -1,5 +1,6 @@ package org.jabref.gui.util; +import java.util.function.BiFunction; import java.util.function.Function; import javafx.event.EventHandler; @@ -7,7 +8,9 @@ import javafx.scene.control.ContextMenu; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; +import javafx.scene.control.TableRow; import javafx.scene.control.Tooltip; +import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; import javafx.util.Callback; @@ -21,38 +24,53 @@ */ public class ValueTableCellFactory implements Callback, TableCell> { - private Callback toText; - private Callback toGraphic; - private Callback> toOnMouseClickedEvent; - private Callback toTooltip; + private Function toText; + private BiFunction toGraphic; + private BiFunction> toOnMouseClickedEvent; + private Function toTooltip; private Function contextMenuFactory; + private BiFunction menuFactory; - public ValueTableCellFactory withText(Callback toText) { + public ValueTableCellFactory withText(Function toText) { this.toText = toText; return this; } - public ValueTableCellFactory withGraphic(Callback toGraphic) { + public ValueTableCellFactory withGraphic(Function toGraphic) { + this.toGraphic = (rowItem, value) -> toGraphic.apply(value); + return this; + } + + public ValueTableCellFactory withGraphic(BiFunction toGraphic) { this.toGraphic = toGraphic; return this; } - public ValueTableCellFactory withTooltip(Callback toTooltip) { + public ValueTableCellFactory withTooltip(Function toTooltip) { this.toTooltip = toTooltip; return this; } - public ValueTableCellFactory withOnMouseClickedEvent( - Callback> toOnMouseClickedEvent) { + public ValueTableCellFactory withOnMouseClickedEvent(BiFunction> toOnMouseClickedEvent) { this.toOnMouseClickedEvent = toOnMouseClickedEvent; return this; } + public ValueTableCellFactory withOnMouseClickedEvent(Function> toOnMouseClickedEvent) { + this.toOnMouseClickedEvent = (rowItem, value) -> toOnMouseClickedEvent.apply(value); + return this; + } + public ValueTableCellFactory withContextMenu(Function contextMenuFactory) { this.contextMenuFactory = contextMenuFactory; return this; } + public ValueTableCellFactory withMenu(BiFunction menuFactory) { + this.menuFactory = menuFactory; + return this; + } + @Override public TableCell call(TableColumn param) { @@ -62,26 +80,28 @@ public TableCell call(TableColumn param) { protected void updateItem(T item, boolean empty) { super.updateItem(item, empty); - if (empty || (item == null)) { + if (empty || (item == null) || (getTableRow() == null) || (getTableRow().getItem() == null)) { setText(null); setGraphic(null); setOnMouseClicked(null); setTooltip(null); } else { + S rowItem = ((TableRow) getTableRow()).getItem(); + if (toText != null) { - setText(toText.call(item)); + setText(toText.apply(item)); } if (toGraphic != null) { - setGraphic(toGraphic.call(item)); + setGraphic(toGraphic.apply(rowItem, item)); } if (toTooltip != null) { - String tooltipText = toTooltip.call(item); + String tooltipText = toTooltip.apply(item); if (StringUtil.isNotBlank(tooltipText)) { setTooltip(new Tooltip(tooltipText)); } } if (toOnMouseClickedEvent != null) { - setOnMouseClicked(toOnMouseClickedEvent.call(item)); + setOnMouseClicked(toOnMouseClickedEvent.apply(rowItem, item)); } if (contextMenuFactory != null) { @@ -94,8 +114,22 @@ protected void updateItem(T item, boolean empty) { event.consume(); }); } + + if (menuFactory != null) { + setOnMouseClicked(event -> { + if (event.getButton() == MouseButton.PRIMARY) { + ContextMenu menu = menuFactory.apply(rowItem, item); + menu.show(this, event.getScreenX(), event.getScreenY()); + event.consume(); + } + }); + } } } }; } + + public void install(TableColumn column) { + column.setCellFactory(this); + } } diff --git a/src/main/java/org/jabref/logic/specialfields/SpecialFieldsUtils.java b/src/main/java/org/jabref/logic/specialfields/SpecialFieldsUtils.java index 084239efe0d..0152f2fed86 100644 --- a/src/main/java/org/jabref/logic/specialfields/SpecialFieldsUtils.java +++ b/src/main/java/org/jabref/logic/specialfields/SpecialFieldsUtils.java @@ -32,7 +32,7 @@ public static List updateField(SpecialField field, String value, Bi List fieldChanges = new ArrayList<>(); UpdateField.updateField(entry, field.getFieldName(), value, nullFieldIfValueIsTheSame) - .ifPresent(fieldChange -> fieldChanges.add(fieldChange)); + .ifPresent(fieldChanges::add); // we cannot use "value" here as updateField has side effects: "nullFieldIfValueIsTheSame" nulls the field if value is the same if (isKeywordSyncEnabled) { fieldChanges.addAll(SpecialFieldsUtils.exportFieldToKeywords(field, entry, keywordDelimiter)); @@ -79,9 +79,7 @@ private static List importKeywordsForField(KeywordList keywordList, } UpdateField.updateNonDisplayableField(entry, field.getFieldName(), newValue.map(Keyword::toString).orElse(null)) - .ifPresent(fieldChange -> { - fieldChanges.add(fieldChange); - }); + .ifPresent(fieldChanges::add); return fieldChanges; } diff --git a/src/main/java/org/jabref/model/entry/specialfields/SpecialFieldValue.java b/src/main/java/org/jabref/model/entry/specialfields/SpecialFieldValue.java index 742b707bd36..7176b39a6ce 100644 --- a/src/main/java/org/jabref/model/entry/specialfields/SpecialFieldValue.java +++ b/src/main/java/org/jabref/model/entry/specialfields/SpecialFieldValue.java @@ -34,6 +34,23 @@ public enum SpecialFieldValue { this.keyword = Optional.ofNullable(keyword).map(Keyword::new); } + public static SpecialFieldValue getRating(int ranking) { + switch (ranking) { + case 1: + return RANK_1; + case 2: + return RANK_2; + case 3: + return RANK_3; + case 4: + return RANK_4; + case 5: + return RANK_5; + default: + throw new UnsupportedOperationException(ranking + "is not a valid ranking"); + } + } + public Optional getKeyword() { return keyword; } @@ -42,4 +59,20 @@ public Optional getFieldValue() { return keyword.map(Keyword::toString); } + public int toRating() { + switch (this) { + case RANK_1: + return 1; + case RANK_2: + return 2; + case RANK_3: + return 3; + case RANK_4: + return 4; + case RANK_5: + return 5; + default: + throw new UnsupportedOperationException(this + "is not a valid ranking"); + } + } } diff --git a/src/test/resources/testbib/complex.bib b/src/test/resources/testbib/complex.bib index dd64afe4e54..84a8899d40f 100644 --- a/src/test/resources/testbib/complex.bib +++ b/src/test/resources/testbib/complex.bib @@ -18,20 +18,20 @@ @PhdThesis{deBraga2001 school = {Department of Zoology, University of Toronto}, } -@ARTICLE{1102917, - author = {E. Bardram}, - title = {The trouble with login: on usability and computer security in ubiquitous - computing}, - journal = {Personal Ubiquitous Comput.}, - year = {2005}, - volume = {9}, - pages = {357--367}, - number = {6}, - address = {London, UK}, +@Article{1102917, + author = {E. Bardram}, + title = {The trouble with login: on usability and computer security in ubiquitous computing}, + journal = {Personal Ubiquitous Comput.}, + year = {2005}, + volume = {9}, + number = {6}, + pages = {357--367}, + issn = {1617-4909}, + doi = {http://dx.doi.org/10.1007/s00779-005-0347-6}, + address = {London, UK}, bdsk-url-1 = {http://dx.doi.org/10.1007/s00779-005-0347-6}, - doi = {http://dx.doi.org/10.1007/s00779-005-0347-6}, - issn = {1617-4909}, - publisher = {Springer-Verlag} + priority = {prio1}, + publisher = {Springer-Verlag}, } @InProceedings{1137631, @@ -65,20 +65,19 @@ @INPROCEEDINGS{1132768 location = {Montreal, Canada} } -@INPROCEEDINGS{1137628, - author = {Danilo Bruschi and Bart De Win and Mattia Monga}, - title = {Introduction to software engineering for secure systems: SESS06 -- - secure by design}, - booktitle = {SESS '06: Proceedings of the 2006 international workshop on Software - engineering for secure systems}, - year = {2006}, - pages = {1--2}, - address = {New York, NY, USA}, - publisher = {ACM}, +@InProceedings{1137628, + author = {Danilo Bruschi and Bart De Win and Mattia Monga}, + title = {Introduction to software engineering for secure systems: SESS06 -- secure by design}, + booktitle = {SESS '06: Proceedings of the 2006 international workshop on Software engineering for secure systems}, + year = {2006}, + publisher = {ACM}, + location = {Shanghai, China}, + isbn = {1-59593-411-1}, + pages = {1--2}, + doi = {http://doi.acm.org/10.1145/1137627.1137628}, + address = {New York, NY, USA}, bdsk-url-1 = {http://doi.acm.org/10.1145/1137627.1137628}, - doi = {http://doi.acm.org/10.1145/1137627.1137628}, - isbn = {1-59593-411-1}, - location = {Shanghai, China} + readstatus = {read}, } @ARTICLE{1373163, @@ -96,20 +95,20 @@ @ARTICLE{1373163 publisher = {IEEE Educational Activities Department} } -@ARTICLE{820136, - author = {Tony Clear}, - title = {Design and usability in security systems: daily life as a context - of use?}, - journal = {SIGCSE Bull.}, - year = {2002}, - volume = {34}, - pages = {13--14}, - number = {4}, - address = {New York, NY, USA}, +@Article{820136, + author = {Tony Clear}, + title = {Design and usability in security systems: daily life as a context of use?}, + journal = {SIGCSE Bull.}, + year = {2002}, + volume = {34}, + number = {4}, + pages = {13--14}, + issn = {0097-8418}, + doi = {http://doi.acm.org/10.1145/820127.820136}, + address = {New York, NY, USA}, bdsk-url-1 = {http://doi.acm.org/10.1145/820127.820136}, - doi = {http://doi.acm.org/10.1145/820127.820136}, - issn = {0097-8418}, - publisher = {ACM} + publisher = {ACM}, + readstatus = {skimmed}, } @BOOK{1098730, @@ -150,19 +149,20 @@ @InProceedings{1233448 groups = {StaticGroup}, } -@ARTICLE{10250999, - author = {von Hippel, Eric and Reagle, Jr., Joseph M. and Sherry, John F., Jr. and van den Huevel, Jr., Johan A}, - title = {Article with complex Authornames}, - journal = {IEEE Security and Privacy}, - year = {2004}, - volume = {2}, - pages = {25--31}, - number = {5}, - address = {Piscataway, NJ, USA}, +@Article{10250999, + author = {von Hippel, Eric and Reagle, Jr., Joseph M. and Sherry, John F., Jr. and van den Huevel, Jr., Johan A}, + title = {Article with complex Authornames}, + journal = {IEEE Security and Privacy}, + year = {2004}, + volume = {2}, + number = {5}, + pages = {25--31}, + issn = {1540-7993}, + doi = {http://dx.doi.org/10.1109/MSP.2004.81}, + address = {Piscataway, NJ, USA}, bdsk-url-1 = {http://dx.doi.org/10.1109/MSP.2004.81}, - doi = {http://dx.doi.org/10.1109/MSP.2004.81}, - issn = {1540-7993}, - publisher = {IEEE Educational Activities Department} + priority = {prio2}, + publisher = {IEEE Educational Activities Department}, } @InProceedings{1314293, From 1af083dd52570bee487246a6e726f3fba14863f7 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Thu, 18 Jan 2018 19:59:50 +0100 Subject: [PATCH 3/4] Fix NPE --- src/main/java/org/jabref/gui/BasePanel.java | 3 ++- src/main/java/org/jabref/gui/JabRefFrame.java | 6 ++++++ .../jabref/gui/fieldeditors/AbstractEditorViewModel.java | 2 +- src/main/java/org/jabref/gui/maintable/RightClickMenu.java | 2 +- .../org/jabref/gui/specialfields/SpecialFieldDropDown.java | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/BasePanel.java index f8917ace927..8a45e29ffb7 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/BasePanel.java @@ -157,7 +157,7 @@ public class BasePanel extends StackPane implements ClipboardOwner { // The undo manager. private final UndoAction undoAction = new UndoAction(); private final RedoAction redoAction = new RedoAction(); - private final CountingUndoManager undoManager = new CountingUndoManager(); + private final CountingUndoManager undoManager; private final List previousEntries = new ArrayList<>(); private final List nextEntries = new ArrayList<>(); // Keeps track of the string dialog if it is open. @@ -202,6 +202,7 @@ public BasePanel(JabRefFrame frame, BasePanelPreferences preferences, BibDatabas this.frame = Objects.requireNonNull(frame); this.bibDatabaseContext = Objects.requireNonNull(bibDatabaseContext); this.externalFileTypes = Objects.requireNonNull(externalFileTypes); + this.undoManager = frame.getUndoManager(); bibDatabaseContext.getDatabase().registerListener(this); bibDatabaseContext.getMetaData().registerListener(this); diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index f226cd73409..7c1892e7194 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -121,6 +121,7 @@ import org.jabref.gui.search.GlobalSearchBar; import org.jabref.gui.specialfields.SpecialFieldDropDown; import org.jabref.gui.specialfields.SpecialFieldValueViewModel; +import org.jabref.gui.undo.CountingUndoManager; import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.gui.util.WindowLocation; import org.jabref.gui.worker.MarkEntriesAction; @@ -468,6 +469,7 @@ public void actionPerformed(ActionEvent e) { private OpenOfficePanel openOfficePanel; private GroupSidePane groupSidePane; private JMenu newSpec; + private final CountingUndoManager undoManager = new CountingUndoManager(); public JabRefFrame() { init(); @@ -1973,6 +1975,10 @@ public GlobalSearchBar getGlobalSearchBar() { return globalSearchBar; } + public CountingUndoManager getUndoManager() { + return undoManager; + } + private static class MyGlassPane extends JPanel { public MyGlassPane() { addKeyListener(new KeyAdapter() { diff --git a/src/main/java/org/jabref/gui/fieldeditors/AbstractEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/AbstractEditorViewModel.java index f249aa1c37a..e88226ac0ba 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/AbstractEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/AbstractEditorViewModel.java @@ -64,7 +64,7 @@ public void bindToEntry(BibEntry entry) { if (newValue != null) { String oldValue = entry.getField(fieldName).orElse(null); entry.setField(fieldName, newValue); - UndoManager undoManager = JabRefGUI.getMainFrame().getCurrentBasePanel().getUndoManager(); + UndoManager undoManager = JabRefGUI.getMainFrame().getUndoManager(); undoManager.addEdit(new UndoableFieldChange(entry, fieldName, oldValue, newValue)); } }); diff --git a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index d98a8e7b292..4b1a9e10891 100644 --- a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -223,7 +223,7 @@ public RightClickMenu(JabRefFrame frame, BasePanel panel) { * Then cycle through all available values, and add them. */ public static void populateSpecialFieldMenu(JMenu menu, SpecialField field, JabRefFrame frame) { - SpecialFieldViewModel viewModel = new SpecialFieldViewModel(field, frame.getCurrentBasePanel().getUndoManager()); + SpecialFieldViewModel viewModel = new SpecialFieldViewModel(field, frame.getUndoManager()); menu.setText(viewModel.getLocalization()); menu.setIcon(viewModel.getRepresentingIcon()); for (SpecialFieldValue val : field.getValues()) { diff --git a/src/main/java/org/jabref/gui/specialfields/SpecialFieldDropDown.java b/src/main/java/org/jabref/gui/specialfields/SpecialFieldDropDown.java index 7ef6db49b0f..942350b4a2b 100644 --- a/src/main/java/org/jabref/gui/specialfields/SpecialFieldDropDown.java +++ b/src/main/java/org/jabref/gui/specialfields/SpecialFieldDropDown.java @@ -22,7 +22,7 @@ private SpecialFieldDropDown() { public static JButton generateSpecialFieldButtonWithDropDown(SpecialField field, JabRefFrame frame) { Dimension buttonDim = new Dimension(23, 23); - SpecialFieldViewModel viewModel = new SpecialFieldViewModel(field, frame.getCurrentBasePanel().getUndoManager()); + SpecialFieldViewModel viewModel = new SpecialFieldViewModel(field, frame.getUndoManager()); JButton button = new JButton(viewModel.getRepresentingIcon()); button.setToolTipText(viewModel.getLocalization()); button.setPreferredSize(buttonDim); From 83198cd112c63789b7c77473e3972e279ebf3e3f Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Fri, 19 Jan 2018 17:31:43 +0100 Subject: [PATCH 4/4] Handle file icon click --- src/main/java/org/jabref/gui/BasePanel.java | 2 +- .../org/jabref/gui/desktop/JabRefDesktop.java | 20 +++-------- .../gui/fieldeditors/LinkedFileViewModel.java | 26 ++++++++++---- .../gui/fieldeditors/LinkedFilesEditor.java | 2 +- .../org/jabref/gui/maintable/MainTable.java | 4 +-- .../gui/maintable/MainTableColumnFactory.java | 36 ++++++++++++++++--- .../gui/util/ValueTableCellFactory.java | 21 ++++++----- 7 files changed, 73 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/BasePanel.java index 8a45e29ffb7..fd5a5e6bbf5 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/BasePanel.java @@ -1212,7 +1212,7 @@ public void updateTableFont() { private void createMainTable() { bibDatabaseContext.getDatabase().registerListener(SpecialFieldDatabaseChangeListener.getInstance()); - mainTable = new MainTable(tableModel, frame, this, bibDatabaseContext.getDatabase(), preferences.getTablePreferences(), externalFileTypes); + mainTable = new MainTable(tableModel, frame, this, bibDatabaseContext, preferences.getTablePreferences(), externalFileTypes); mainTable.updateFont(); diff --git a/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java b/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java index a362446b26f..d5ad147d0a6 100644 --- a/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java +++ b/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java @@ -132,26 +132,16 @@ private static void openDoi(String doi) throws IOException { */ public static boolean openExternalFileAnyFormat(final BibDatabaseContext databaseContext, String link, final Optional type) throws IOException { - boolean httpLink = false; if (REMOTE_LINK_PATTERN.matcher(link.toLowerCase(Locale.ROOT)).matches()) { - httpLink = true; - } - - // For other platforms we'll try to find the file type: - Path file = null; - if (!httpLink) { - Optional tmp = FileHelper.expandFilename(databaseContext, link, - Globals.prefs.getFileDirectoryPreferences()); - if (tmp.isPresent()) { - file = tmp.get(); - } + openExternalFilePlatformIndependent(type, link); + return true; } - // Check if we have arrived at a file type, and either an http link or an existing file: - if (httpLink || ((file != null) && Files.exists(file) && (type.isPresent()))) { + Optional file = FileHelper.expandFilename(databaseContext, link, Globals.prefs.getFileDirectoryPreferences()); + if (file.isPresent() && Files.exists(file.get()) && (type.isPresent())) { // Open the file: - String filePath = httpLink ? link : file.toString(); + String filePath = file.get().toString(); openExternalFilePlatformIndependent(type, filePath); return true; } else { diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java index 6612a752c20..ac995be2994 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java @@ -24,6 +24,8 @@ import org.jabref.gui.AbstractViewModel; import org.jabref.gui.DialogService; import org.jabref.gui.FXDialogService; +import org.jabref.gui.IconTheme; +import org.jabref.gui.JabRefIcon; import org.jabref.gui.desktop.JabRefDesktop; import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; @@ -38,9 +40,8 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; +import org.jabref.model.strings.StringUtil; -import de.jensd.fx.glyphs.GlyphIcons; -import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -100,6 +101,14 @@ public String getDescription() { return linkedFile.getDescription(); } + public String getDescriptionAndLink() { + if (StringUtil.isBlank(linkedFile.getDescription())) { + return linkedFile.getLink(); + } else { + return linkedFile.getDescription() + " (" + linkedFile.getLink() + ")"; + } + } + public Optional findIn(List directories) { return linkedFile.findIn(directories); } @@ -108,8 +117,8 @@ public Optional findIn(List directories) { * TODO: Be a bit smarter and try to infer correct icon, for example using {@link * org.jabref.gui.externalfiletype.ExternalFileTypes#getExternalFileTypeByName(String)} */ - public GlyphIcons getTypeIcon() { - return MaterialDesignIcon.FILE_PDF; + public JabRefIcon getTypeIcon() { + return IconTheme.JabRefIcons.PDF_FILE; } public void markAsAutomaticallyFound() { @@ -131,9 +140,14 @@ public Observable[] getObservables() { public void open() { try { Optional type = ExternalFileTypes.getInstance().fromLinkedFile(linkedFile, true); - JabRefDesktop.openExternalFileAnyFormat(databaseContext, linkedFile.getLink(), type); + boolean successful = JabRefDesktop.openExternalFileAnyFormat(databaseContext, linkedFile.getLink(), type); + if (!successful) { + dialogService.showErrorDialogAndWait( + Localization.lang("File not found"), + Localization.lang("Could not find file '%0'.", linkedFile.getLink())); + } } catch (IOException e) { - LOGGER.warn("Cannot open selected file.", e); + dialogService.showErrorDialogAndWait(Localization.lang("Error opening file '%0'.", getLink()), e); } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java index 306bb3b148c..b01af694d40 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java @@ -148,7 +148,7 @@ private void handleOnDragDropped(LinkedFileViewModel originalItem, DragEvent eve } private static Node createFileDisplay(LinkedFileViewModel linkedFile) { - Text icon = MaterialDesignIconFactory.get().createIcon(linkedFile.getTypeIcon()); + Node icon = linkedFile.getTypeIcon().getGraphicNode(); icon.setOnMouseClicked(event -> linkedFile.open()); Text link = new Text(linkedFile.getLink()); Text desc = new Text(linkedFile.getDescription()); diff --git a/src/main/java/org/jabref/gui/maintable/MainTable.java b/src/main/java/org/jabref/gui/maintable/MainTable.java index f02924df464..3f6a6126f12 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTable.java +++ b/src/main/java/org/jabref/gui/maintable/MainTable.java @@ -23,7 +23,7 @@ import org.jabref.gui.renderer.IncompleteRenderer; import org.jabref.gui.util.ViewModelTableRowFactory; import org.jabref.logic.TypedBibEntry; -import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.preferences.JabRefPreferences; @@ -72,7 +72,7 @@ private enum CellRendererMode { } public MainTable(MainTableDataModel model, JabRefFrame frame, - BasePanel panel, BibDatabase database, MainTablePreferences preferences, ExternalFileTypes externalFileTypes) { + BasePanel panel, BibDatabaseContext database, MainTablePreferences preferences, ExternalFileTypes externalFileTypes) { super(); this.model = model; diff --git a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java index dbc3bf96656..435e9e7fc89 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java @@ -15,16 +15,18 @@ import javafx.scene.control.TableColumn; import javafx.scene.input.MouseButton; +import org.jabref.Globals; import org.jabref.gui.GUIGlobals; import org.jabref.gui.IconTheme; import org.jabref.gui.JabRefIcon; import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; +import org.jabref.gui.fieldeditors.LinkedFileViewModel; import org.jabref.gui.specialfields.SpecialFieldValueViewModel; import org.jabref.gui.specialfields.SpecialFieldViewModel; import org.jabref.gui.util.OptionalValueTableCellFactory; import org.jabref.gui.util.ValueTableCellFactory; -import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; import org.jabref.model.entry.LinkedFile; @@ -40,11 +42,11 @@ class MainTableColumnFactory { private final ColumnPreferences preferences; private final ExternalFileTypes externalFileTypes; - private final BibDatabase database; + private final BibDatabaseContext database; private final CellFactory cellFactory; private final UndoManager undoManager; - public MainTableColumnFactory(BibDatabase database, ColumnPreferences preferences, ExternalFileTypes externalFileTypes, UndoManager undoManager) { + public MainTableColumnFactory(BibDatabaseContext database, ColumnPreferences preferences, ExternalFileTypes externalFileTypes, UndoManager undoManager) { this.database = Objects.requireNonNull(database); this.preferences = Objects.requireNonNull(preferences); this.externalFileTypes = Objects.requireNonNull(externalFileTypes); @@ -96,7 +98,7 @@ public MainTableColumnFactory(BibDatabase database, ColumnPreferences preference // Stored column name will be used as header // There might be more than one field to display, e.g., "author/editor" or "date/year" - so split String[] fields = columnName.split(FieldName.FIELD_SEPARATOR); - StringTableColumn column = new StringTableColumn(columnName, Arrays.asList(fields), database); + StringTableColumn column = new StringTableColumn(columnName, Arrays.asList(fields), database.getDatabase()); column.setPrefWidth(preferences.getPrefColumnWidth(columnName)); columns.add(column); } @@ -183,10 +185,36 @@ private TableColumn> createFileColumn() column.setCellValueFactory(cellData -> cellData.getValue().getLinkedFiles()); new ValueTableCellFactory>() .withGraphic(this::createFileIcon) + .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); + linkedFileViewModel.open(); + } + }) .install(column); return column; } + 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); + + MenuItem menuItem = new MenuItem(linkedFileViewModel.getDescriptionAndLink(), linkedFileViewModel.getTypeIcon().getGraphicNode()); + menuItem.setOnAction(event -> linkedFileViewModel.open()); + contextMenu.getItems().add(menuItem); + } + + return contextMenu; + } + /** * Creates a column which shows an icon instead of the textual content */ diff --git a/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java b/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java index b2418d342cd..82468f56578 100644 --- a/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java +++ b/src/main/java/org/jabref/gui/util/ValueTableCellFactory.java @@ -100,9 +100,6 @@ protected void updateItem(T item, boolean empty) { setTooltip(new Tooltip(tooltipText)); } } - if (toOnMouseClickedEvent != null) { - setOnMouseClicked(toOnMouseClickedEvent.apply(rowItem, item)); - } if (contextMenuFactory != null) { // We only create the context menu when really necessary @@ -115,15 +112,21 @@ protected void updateItem(T item, boolean empty) { }); } - if (menuFactory != null) { - setOnMouseClicked(event -> { + setOnMouseClicked(event -> { + if (toOnMouseClickedEvent != null) { + toOnMouseClickedEvent.apply(rowItem, item).handle(event); + } + + if (menuFactory != null && !event.isConsumed()) { if (event.getButton() == MouseButton.PRIMARY) { ContextMenu menu = menuFactory.apply(rowItem, item); - menu.show(this, event.getScreenX(), event.getScreenY()); - event.consume(); + if (menu != null) { + menu.show(this, event.getScreenX(), event.getScreenY()); + event.consume(); + } } - }); - } + } + }); } } };