diff --git a/CHANGELOG.md b/CHANGELOG.md
index 284a9311440..ce48a9fd293 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -58,6 +58,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve
- We moved the custom entry types dialog into the preferences dialog. [#9760](https://github.com/JabRef/jabref/pull/9760)
- We moved the manage content selectors dialog to the library properties. [#9768](https://github.com/JabRef/jabref/pull/9768)
- We moved the preferences menu command from the options menu to the file menu. [#9768](https://github.com/JabRef/jabref/pull/9768)
+- We reworked the cross ref labels in the entry editor and added a right click menu. [#10046](https://github.com/JabRef/jabref/pull/10046)
- We reorganized the order of tabs and settings in the library properties. [#9836](https://github.com/JabRef/jabref/pull/9836)
- We changed the handling of an "overflow" of authors at `[authIniN]`: JabRef uses `+` to indicate an overflow. Example: `[authIni2]` produces `A+` (instead of `AB`) for `Aachen and Berlin and Chemnitz`. [#9703](https://github.com/JabRef/jabref/pull/9703)
- We moved the preferences option to open the last edited files on startup to the 'General' tab. [#9808](https://github.com/JabRef/jabref/pull/9808)
diff --git a/build.gradle b/build.gradle
index 7a75a7847fd..73787f662a8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -88,6 +88,7 @@ repositories {
maven { url 'https://oss.sonatype.org/content/groups/public' }
maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' }
maven { url 'https://jitpack.io' }
+ maven { url 'https://sandec.jfrog.io/artifactory/repo' }
}
configurations {
@@ -176,7 +177,11 @@ dependencies {
implementation 'com.tobiasdiez:easybind:2.2.1-SNAPSHOT'
implementation 'org.fxmisc.flowless:flowless:0.7.0'
implementation 'org.fxmisc.richtext:richtextfx:0.11.0'
- implementation 'com.jfoenix:jfoenix:9.0.10'
+ implementation (group: 'com.dlsc.gemsfx', name: 'gemsfx', version: '1.74.0') {
+ exclude module: 'javax.inject' // Split package, use only jakarta.inject
+ exclude group: 'org.apache.logging.log4j'
+ }
+
implementation 'org.controlsfx:controlsfx:11.1.2'
implementation 'org.jsoup:jsoup:1.16.1'
@@ -343,8 +348,6 @@ run {
'javafx.controls/com.sun.javafx.scene.control' : 'org.jabref',
'org.controlsfx.controls/impl.org.controlsfx.skin' : 'org.jabref',
- 'javafx.controls/com.sun.javafx.scene.control.behavior' : 'com.jfoenix',
-
// Not sure why we need to restate the controlfx exports
// Taken from here: https://github.com/controlsfx/controlsfx/blob/9.0.0/build.gradle#L1
'javafx.graphics/com.sun.javafx.scene' : 'org.controlsfx.controls',
@@ -365,12 +368,7 @@ run {
'javafx.controls/com.sun.javafx.scene.control' : 'org.jabref',
'javafx.controls/javafx.scene.control.skin' : 'org.controlsfx.controls',
- 'javafx.graphics/javafx.scene' : 'org.controlsfx.controls',
-
- 'javafx.controls/com.sun.javafx.scene.control.behavior' : 'com.jfoenix',
- 'javafx.base/com.sun.javafx.binding' : 'com.jfoenix',
- 'javafx.graphics/com.sun.javafx.stage' : 'com.jfoenix',
- 'javafx.base/com.sun.javafx.event' : 'com.jfoenix'
+ 'javafx.graphics/javafx.scene' : 'org.controlsfx.controls'
]
}
}
@@ -588,7 +586,8 @@ jlink {
'org.mariadb.jdbc.credential.env.EnvCredentialPlugin',
'org.mariadb.jdbc.credential.system.PropertiesCredentialPlugin'
provides 'java.security.Provider' with 'org.bouncycastle.jce.provider.BouncyCastleProvider',
- 'org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider' }
+ 'org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider'
+ }
jpackage {
outputDir = "distribution"
diff --git a/docs/code-howtos/javafx.md b/docs/code-howtos/javafx.md
index c08fe96b489..c5faee31d74 100644
--- a/docs/code-howtos/javafx.md
+++ b/docs/code-howtos/javafx.md
@@ -201,7 +201,6 @@ JabRef makes heavy use of Properties and Bindings. These are wrappers around Obs
* [Validation framework](https://github.com/sialcasa/mvvmFX/wiki/Validation)
* [mvvm framework](https://github.com/sialcasa/mvvmFX/wiki)
* [CSS Reference](http://docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html)
-* [JFoenix](https://github.com/jfoenixadmin/JFoenix) Material Designs look & feel
* [JavaFX Documentation project](https://fxdocs.github.io/docs/html5/index.html): Collected information on JavaFX in a central place
* [FXExperience](http://fxexperience.com) JavaFX Links of the week
* [Foojay](https://foojay.io) Java and JavaFX tutorials
diff --git a/eclipse.gradle b/eclipse.gradle
index dab4df54bb6..9d61901d09e 100644
--- a/eclipse.gradle
+++ b/eclipse.gradle
@@ -25,12 +25,11 @@ eclipse {
}
def javafxcontrols = entries.find { isJavafxControls(it) };
- javafxcontrols.entryAttributes['add-exports'] = 'javafx.controls/com.sun.javafx.scene.control=org.jabref:javafx.controls/com.sun.javafx.scene.control.behavior=org.jabref:javafx.controls/javafx.scene.control=org.jabref:javafx.controls/com.sun.javafx.scene.control.behavior=com.jfoenix';
+ javafxcontrols.entryAttributes['add-exports'] = 'javafx.controls/com.sun.javafx.scene.control=org.jabref:javafx.controls/com.sun.javafx.scene.control.behavior=org.jabref:javafx.controls/javafx.scene.control=org.jabref';
javafxcontrols.entryAttributes['add-opens'] = 'javafx.controls/com.sun.javafx.scene.control=org.jabref:javafx.controls/com.sun.javafx.scene.control.behavior=org.jabref:javafx.controls/javafx.scene.control=org.jabref:javafx.controls/javafx.scene.control.skin=org.controlsfx.controls';
def javafxgraphics = entries.find { isJavafxGraphics(it) };
javafxgraphics.entryAttributes['add-opens'] = 'javafx.graphics/javafx.scene=org.controlsfx.controls';
- javafxgraphics.entryAttributes['add-exports'] = 'javafx.graphics/com.sun.javafx.stage=com.jfoenix';
def javafxbase = entries.find { isJavafxBase(it) };
javafxbase.entryAttributes['add-exports'] = 'javafx.base/com.sun.javafx.event=org.controlsfx.controls:';
diff --git a/external-libraries.md b/external-libraries.md
index 0744d3f3c3e..4a75d8eccf3 100644
--- a/external-libraries.md
+++ b/external-libraries.md
@@ -99,13 +99,6 @@ URL: https://github.com/lemire/javaewah
License: Apache-2.0
```
-```yaml
-Id: com.jfoenix:jfoenix
-Project: JavaFX MAterial Design Library
-URL: https://github.com/jfoenixadmin/JFoenix
-License: Apache-2.0
-```
-
```yaml
Id: com.konghq.unirest
Project: Unirest for Java
@@ -539,7 +532,6 @@ com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.j2objc:j2objc-annotations:1.3
com.googlecode.javaewah:JavaEWAH:1.1.13
com.h2database:h2-mvstore:2.1.214
-com.jfoenix:jfoenix:9.0.10
com.konghq:unirest-java:3.14.1
com.microsoft.azure:applicationinsights-core:2.4.1
com.microsoft.azure:applicationinsights-logging-log4j2:2.4.1
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 91229649901..81ec7d99844 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -9,12 +9,12 @@
// JavaFX
requires javafx.base;
requires javafx.graphics;
- requires javafx.swing;
requires javafx.controls;
requires javafx.web;
requires javafx.fxml;
requires afterburner.fx;
- requires com.jfoenix;
+ requires com.dlsc.gemsfx;
+ uses com.dlsc.gemsfx.TagsField;
requires de.saxsys.mvvmfx;
requires org.kordamp.ikonli.core;
diff --git a/src/main/java/org/jabref/gui/Base.css b/src/main/java/org/jabref/gui/Base.css
index 85b0b83ad34..68f96eebf28 100644
--- a/src/main/java/org/jabref/gui/Base.css
+++ b/src/main/java/org/jabref/gui/Base.css
@@ -1244,10 +1244,10 @@ We want to have a look that matches our icons in the tool-bar */
-fx-background-color: -jr-accent;
}
-.jfx-color-picker:armed,
-.jfx-color-picker:hover,
-.jfx-color-picker:focused,
-.jfx-color-picker {
+.color-picker:armed,
+.color-picker:hover,
+.color-picker:focused,
+.color-picker {
-fx-background-color: transparent, transparent, transparent, transparent;
-fx-background-radius: 0px;
-fx-background-insets: 0px;
@@ -1387,6 +1387,27 @@ We want to have a look that matches our icons in the tool-bar */
-fx-pref-height: 30px;
-fx-padding: 0px 0px 0px -8px;
-fx-margin: 0em;
+ -fx-border-style: none;
+}
+
+.tags-field {
+ -fx-pref-height: 30px;
+ -fx-margin: 0em;
+ -fx-border-style: none;
+}
+
+.tags-field > .flow-pane > .tag-view {
+ -fx-background-color: -fx-default-button;
+ -fx-text-fill: -fx-focused-text-base-color;
+}
+
+.tags-field > .flow-pane > .tag-view:selected {
+ -fx-background-color: derive(-fx-default-button, -20%);
+ -fx-text-fill: -fx-focused-text-base-color;
+}
+
+.tags-field-editor {
+ -fx-border-width: 0;
}
.searchBar:invalid {
diff --git a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java
index 9010ec3379c..607709742b1 100644
--- a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java
+++ b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java
@@ -5,8 +5,9 @@
import java.io.IOException;
import java.util.Objects;
-import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image;
+import javafx.scene.image.PixelWriter;
+import javafx.scene.image.WritableImage;
import org.jabref.architecture.AllowedToUseAwt;
@@ -51,7 +52,7 @@ public Image render(int width, int height) {
try {
int resolution = 96;
BufferedImage image = renderer.renderImageWithDPI(pageNumber, 2 * resolution, ImageType.RGB);
- return SwingFXUtils.toFXImage(resize(image, width, height), null);
+ return convertToFxImage(resize(image, width, height));
} catch (IOException e) {
// TODO: LOG
return null;
@@ -68,4 +69,19 @@ public double getAspectRatio() {
PDRectangle mediaBox = page.getMediaBox();
return mediaBox.getWidth() / mediaBox.getHeight();
}
+
+ // See https://stackoverflow.com/a/57552025/3450689
+ private static Image convertToFxImage(BufferedImage image) {
+ WritableImage wr = null;
+ if (image != null) {
+ wr = new WritableImage(image.getWidth(), image.getHeight());
+ PixelWriter pw = wr.getPixelWriter();
+ for (int x = 0; x < image.getWidth(); x++) {
+ for (int y = 0; y < image.getHeight(); y++) {
+ pw.setArgb(x, y, image.getRGB(x, y));
+ }
+ }
+ }
+ return wr;
+ }
}
diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.fxml b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.fxml
index 4b75bbe6311..10d87fdc46e 100644
--- a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.fxml
+++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.fxml
@@ -1,8 +1,8 @@
-
+
-
+
diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java
index 3067ee36a11..8c54685b210 100644
--- a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java
+++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java
@@ -1,53 +1,101 @@
package org.jabref.gui.fieldeditors;
+import java.util.Comparator;
import java.util.stream.Collectors;
import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
+import javafx.scene.Node;
import javafx.scene.Parent;
+import javafx.scene.control.ContentDisplay;
+import javafx.scene.control.ContextMenu;
+import javafx.scene.control.Label;
+import javafx.scene.input.MouseButton;
import javafx.scene.layout.HBox;
+import org.jabref.gui.ClipBoardManager;
+import org.jabref.gui.DialogService;
+import org.jabref.gui.JabRefDialogService;
+import org.jabref.gui.actions.ActionFactory;
+import org.jabref.gui.actions.SimpleCommand;
+import org.jabref.gui.actions.StandardActions;
import org.jabref.gui.autocompleter.SuggestionProvider;
+import org.jabref.gui.icon.IconTheme;
+import org.jabref.gui.keyboard.KeyBindingRepository;
import org.jabref.gui.util.ViewModelListCellFactory;
import org.jabref.logic.integrity.FieldCheckers;
+import org.jabref.logic.l10n.Localization;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.ParsedEntryLink;
import org.jabref.model.entry.field.Field;
import com.airhacks.afterburner.views.ViewLoader;
-import com.jfoenix.controls.JFXChip;
-import com.jfoenix.controls.JFXChipView;
-import com.jfoenix.controls.JFXDefaultChip;
+import com.dlsc.gemsfx.TagsField;
+import jakarta.inject.Inject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class LinkedEntriesEditor extends HBox implements FieldEditorFX {
- @FXML
+ private static final Logger LOGGER = LoggerFactory.getLogger(LinkedEntriesEditor.class);
+
+ @FXML public TagsField entryLinkField;
+
+ @Inject private DialogService dialogService;
+ @Inject private ClipBoardManager clipBoardManager;
+ @Inject private KeyBindingRepository keyBindingRepository;
+
private final LinkedEntriesEditorViewModel viewModel;
- @FXML
- private JFXChipView chipView;
public LinkedEntriesEditor(Field field, BibDatabaseContext databaseContext, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers) {
- this.viewModel = new LinkedEntriesEditorViewModel(field, suggestionProvider, databaseContext, fieldCheckers);
-
ViewLoader.view(this)
.root(this)
.load();
- chipView.setConverter(viewModel.getStringConverter());
- var autoCompletionItemFactory = new ViewModelListCellFactory()
- .withText(ParsedEntryLink::getKey);
- chipView.getAutoCompletePopup().setSuggestionsCellFactory(autoCompletionItemFactory);
- chipView.getAutoCompletePopup().setCellLimit(5);
- chipView.getSuggestions().addAll(suggestionProvider.getPossibleSuggestions().stream().map(ParsedEntryLink::new).collect(Collectors.toList()));
-
- chipView.setChipFactory((view, item) -> {
- JFXChip chip = new JFXDefaultChip<>(view, item);
- chip.setOnMouseClicked(event -> viewModel.jumpToEntry(item));
- return chip;
+ this.viewModel = new LinkedEntriesEditorViewModel(field, suggestionProvider, databaseContext, fieldCheckers);
+
+ entryLinkField.setCellFactory(new ViewModelListCellFactory().withText(ParsedEntryLink::getKey));
+ // Mind the .collect(Collectors.toList()) as the list needs to be mutable
+ entryLinkField.setSuggestionProvider(request ->
+ suggestionProvider.getPossibleSuggestions().stream()
+ .filter(suggestion -> suggestion.getCitationKey().orElse("").toLowerCase()
+ .contains(request.getUserText().toLowerCase()))
+ .map(ParsedEntryLink::new)
+ .collect(Collectors.toList()));
+ entryLinkField.setTagViewFactory(this::createTag);
+ entryLinkField.setConverter(viewModel.getStringConverter());
+ entryLinkField.setNewItemProducer(searchText -> viewModel.getStringConverter().fromString(searchText));
+ entryLinkField.setMatcher((entryLink, searchText) -> entryLink.getKey().toLowerCase().startsWith(searchText.toLowerCase()));
+ entryLinkField.setComparator(Comparator.comparing(ParsedEntryLink::getKey));
+ entryLinkField.setShowSearchIcon(false);
+ entryLinkField.getEditor().getStyleClass().clear();
+ entryLinkField.getEditor().getStyleClass().add("tags-field-editor");
+
+ Bindings.bindContentBidirectional(entryLinkField.getTags(), viewModel.linkedEntriesProperty());
+ }
+
+ private Node createTag(ParsedEntryLink entryLink) {
+ Label tagLabel = new Label();
+ tagLabel.setText(entryLinkField.getConverter().toString(entryLink));
+ tagLabel.setGraphic(IconTheme.JabRefIcons.REMOVE_TAGS.getGraphicNode());
+ tagLabel.getGraphic().setOnMouseClicked(event -> entryLinkField.removeTags(entryLink));
+ tagLabel.setContentDisplay(ContentDisplay.RIGHT);
+ tagLabel.setOnMouseClicked(event -> {
+ if (event.getClickCount() == 2 && event.getButton().equals(MouseButton.PRIMARY)) {
+ viewModel.jumpToEntry(entryLink);
+ }
});
- Bindings.bindContentBidirectional(chipView.getChips(), viewModel.linkedEntriesProperty());
+ ContextMenu contextMenu = new ContextMenu();
+ ActionFactory factory = new ActionFactory(keyBindingRepository);
+ contextMenu.getItems().addAll(
+ factory.createMenuItem(StandardActions.COPY, new TagContextAction(StandardActions.COPY, entryLink)),
+ factory.createMenuItem(StandardActions.CUT, new TagContextAction(StandardActions.CUT, entryLink)),
+ factory.createMenuItem(StandardActions.DELETE, new TagContextAction(StandardActions.DELETE, entryLink))
+ );
+ tagLabel.setContextMenu(contextMenu);
+ return tagLabel;
}
public LinkedEntriesEditorViewModel getViewModel() {
@@ -63,4 +111,35 @@ public void bindToEntry(BibEntry entry) {
public Parent getNode() {
return this;
}
+
+ private class TagContextAction extends SimpleCommand {
+ private final StandardActions command;
+ private final ParsedEntryLink entryLink;
+
+ public TagContextAction(StandardActions command, ParsedEntryLink entryLink) {
+ this.command = command;
+ this.entryLink = entryLink;
+ }
+
+ @Override
+ public void execute() {
+ switch (command) {
+ case COPY -> {
+ clipBoardManager.setContent(entryLink.getKey());
+ dialogService.notify(Localization.lang("Copied '%0' to clipboard.",
+ JabRefDialogService.shortenDialogMessage(entryLink.getKey())));
+ }
+ case CUT -> {
+ clipBoardManager.setContent(entryLink.getKey());
+ dialogService.notify(Localization.lang("Copied '%0' to clipboard.",
+ JabRefDialogService.shortenDialogMessage(entryLink.getKey())));
+ entryLinkField.removeTags(entryLink);
+ }
+ case DELETE ->
+ entryLinkField.removeTags(entryLink);
+ default ->
+ LOGGER.info("Action {} not defined", command.getText());
+ }
+ }
+ }
}
diff --git a/src/main/java/org/jabref/gui/icon/IconTheme.java b/src/main/java/org/jabref/gui/icon/IconTheme.java
index 65489ac1e60..c92fea5ac8e 100644
--- a/src/main/java/org/jabref/gui/icon/IconTheme.java
+++ b/src/main/java/org/jabref/gui/icon/IconTheme.java
@@ -345,11 +345,9 @@ public enum JabRefIcons implements JabRefIcon {
KEEP_ON_TOP(MaterialDesignP.PIN),
KEEP_ON_TOP_OFF(MaterialDesignP.PIN_OFF_OUTLINE),
OPEN_GLOBAL_SEARCH(MaterialDesignO.OPEN_IN_NEW),
-
+ REMOVE_TAGS(MaterialDesignC.CLOSE),
ACCEPT_LEFT(MaterialDesignS.SUBDIRECTORY_ARROW_LEFT),
-
ACCEPT_RIGHT(MaterialDesignS.SUBDIRECTORY_ARROW_RIGHT),
-
MERGE_GROUPS(MaterialDesignS.SOURCE_MERGE);
private final JabRefIcon icon;
diff --git a/src/test/java/org/jabref/testutils/interactive/styletester/StyleTester.fxml b/src/test/java/org/jabref/testutils/interactive/styletester/StyleTester.fxml
index 2240e83d8bb..69b59a87a7e 100644
--- a/src/test/java/org/jabref/testutils/interactive/styletester/StyleTester.fxml
+++ b/src/test/java/org/jabref/testutils/interactive/styletester/StyleTester.fxml
@@ -6,6 +6,7 @@
+
@@ -35,7 +36,6 @@
-
-
+