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 @@ - - +