Skip to content

Commit

Permalink
Replaced abandoned JFoenix controls (#10046)
Browse files Browse the repository at this point in the history
Co-authored-by: Christoph <siedlerkiller@gmail.com>
  • Loading branch information
calixtus and Siedlerchr authored Jul 1, 2023
1 parent c98d078 commit 4bca1b0
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 56 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,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)
Expand Down
19 changes: 9 additions & 10 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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',
Expand All @@ -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'
]
}
}
Expand Down Expand Up @@ -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"
Expand Down
1 change: 0 additions & 1 deletion docs/code-howtos/javafx.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions eclipse.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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:';
Expand Down
8 changes: 0 additions & 8 deletions external-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
29 changes: 25 additions & 4 deletions src/main/java/org/jabref/gui/Base.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.HBox?>
<?import com.jfoenix.controls.JFXChipView?>
<?import com.dlsc.gemsfx.TagsField?>
<fx:root xmlns:fx="http://javafx.com/fxml/1" type="HBox" xmlns="http://javafx.com/javafx/8.0.112"
fx:controller="org.jabref.gui.fieldeditors.LinkedEntriesEditor">
<JFXChipView fx:id="chipView" HBox.hgrow="ALWAYS" maxWidth="Infinity"/>
<TagsField fx:id="entryLinkField" HBox.hgrow="ALWAYS"/>
</fx:root>
119 changes: 99 additions & 20 deletions src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java
Original file line number Diff line number Diff line change
@@ -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<ParsedEntryLink> entryLinkField;

@Inject private DialogService dialogService;
@Inject private ClipBoardManager clipBoardManager;
@Inject private KeyBindingRepository keyBindingRepository;

private final LinkedEntriesEditorViewModel viewModel;
@FXML
private JFXChipView<ParsedEntryLink> chipView;

public LinkedEntriesEditor(Field field, BibDatabaseContext databaseContext, SuggestionProvider<BibEntry> 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<ParsedEntryLink>()
.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<ParsedEntryLink> 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<ParsedEntryLink>().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() {
Expand All @@ -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());
}
}
}
}
4 changes: 1 addition & 3 deletions src/main/java/org/jabref/gui/icon/IconTheme.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 4bca1b0

Please sign in to comment.