Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package org.jabref.gui.preferences.websearch;

import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.RotateTransition;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TextField;
import javafx.util.Duration;

import org.jabref.gui.FXDialog;
import org.jabref.gui.icon.IconTheme;
import org.jabref.gui.preferences.websearch.WebSearchTabViewModel.FetcherViewModel;
import org.jabref.gui.util.DelayedExecution;
import org.jabref.logic.l10n.Localization;

import com.airhacks.afterburner.views.ViewLoader;

public class ApiKeyDialog extends FXDialog {
/// The length at which the status message is shown after testing the API key
public static final Duration STATUS_DURATION = Duration.seconds(5);

@FXML private TextField apiKeyField;
@FXML private CheckBox persistApiKeysCheckBox;
@FXML private Button testButton;

private final WebSearchTabViewModel viewModel;
private final FetcherViewModel fetcherViewModel;
private final BooleanProperty apiKeyValid = new SimpleBooleanProperty();
private RotateTransition rotation;

public ApiKeyDialog(WebSearchTabViewModel viewModel, FetcherViewModel fetcherViewModel) {
super(AlertType.NONE, Localization.lang("API Key for %0", fetcherViewModel.getName()));
this.viewModel = viewModel;
this.fetcherViewModel = fetcherViewModel;

ViewLoader.view(this)
.load()
.setAsDialogPane(this);

setResultConverter(this::convertResult);
}

@FXML
private void initialize() {
apiKeyField.setText(fetcherViewModel.getApiKey());

apiKeyValid.set(apiKeyField.getText().isEmpty() || fetcherViewModel.shouldUseCustomApiKey());

Button okButton = (Button) getDialogPane().lookupButton(ButtonType.OK);
if (okButton != null) {
okButton.disableProperty().bind(apiKeyValid.not());
}

apiKeyField.textProperty().addListener((obs, oldValue, newValue) -> {
apiKeyValid.set(newValue.isEmpty());
testButton.setGraphic(null);
testButton.setText(Localization.lang("Test connection"));
testButton.getStyleClass().removeAll("success", "error");
});

persistApiKeysCheckBox.selectedProperty().bindBidirectional(viewModel.getApikeyPersistProperty());
persistApiKeysCheckBox.disableProperty().bind(viewModel.apiKeyPersistAvailable().not());

rotation = new RotateTransition(Duration.seconds(1), IconTheme.JabRefIcons.REFRESH.getGraphicNode());
rotation.setByAngle(360);
rotation.setCycleCount(Animation.INDEFINITE);
rotation.setInterpolator(Interpolator.LINEAR);
}

@FXML
private void testApiKey() {
testButton.setDisable(true);
testButton.setText(Localization.lang("Testing..."));
testButton.setGraphic(rotation.getNode());
rotation.play();
viewModel.checkApiKey(fetcherViewModel, apiKeyField.getText(), this::handleTestResult);
}

private void handleTestResult(boolean success) {
Platform.runLater(() -> {
rotation.stop();
testButton.setDisable(false);

testButton.setGraphic(success ? IconTheme.JabRefIcons.SUCCESS.getGraphicNode() : IconTheme.JabRefIcons.ERROR.getGraphicNode());
testButton.getStyleClass().removeAll("success", "error");
testButton.getStyleClass().add(success ? "success" : "error");
testButton.setText(success ? Localization.lang("API Key Valid") : Localization.lang("API Key Invalid"));
apiKeyValid.set(success);

new DelayedExecution(STATUS_DURATION, () -> {
testButton.setGraphic(null);
testButton.setText(Localization.lang("Test connection"));
}).start();
});
}

private ButtonType convertResult(ButtonType button) {
if (button == ButtonType.OK) {
String apiKey = apiKeyField.getText().trim();
fetcherViewModel.apiKeyProperty().set(apiKey);
fetcherViewModel.useCustomApiKeyProperty().set(!apiKey.isEmpty());
}
return button;
}
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
package org.jabref.gui.preferences.websearch;

import java.util.Optional;

import javafx.beans.InvalidationListener;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;

import org.jabref.gui.preferences.AbstractPreferenceTabView;
import org.jabref.gui.preferences.PreferencesTab;
import org.jabref.gui.slr.StudyCatalogItem;
import org.jabref.gui.util.ViewModelListCellFactory;
import org.jabref.gui.util.ViewModelTableRowFactory;
import org.jabref.gui.util.component.HelpButton;
import org.jabref.logic.help.HelpFile;
import org.jabref.logic.importer.plaincitation.PlainCitationParserChoice;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.preferences.FetcherApiKey;
import org.jabref.model.strings.StringUtil;

import com.airhacks.afterburner.views.ViewLoader;
import com.tobiasdiez.easybind.EasyBind;

public class WebSearchTab extends AbstractPreferenceTabView<WebSearchTabViewModel> implements PreferencesTab {

@FXML private CheckBox enableWebSearch;
@FXML private CheckBox warnAboutDuplicatesOnImport;
@FXML private CheckBox downloadLinkedOnlineFiles;
Expand All @@ -45,17 +44,7 @@ public class WebSearchTab extends AbstractPreferenceTabView<WebSearchTabViewMode
@FXML private CheckBox grobidEnabled;
@FXML private TextField grobidURL;

@FXML private TableView<FetcherApiKey> apiKeySelectorTable;
@FXML private TableColumn<FetcherApiKey, String> apiKeyName;
@FXML private TableColumn<FetcherApiKey, String> customApiKey;
@FXML private TableColumn<FetcherApiKey, Boolean> useCustomApiKey;
@FXML private Button testCustomApiKey;

@FXML private CheckBox persistApiKeys;
@FXML private SplitPane persistentTooltipWrapper; // The disabled persistApiKeys control does not show tooltips
@FXML private TableView<StudyCatalogItem> catalogTable;
@FXML private TableColumn<StudyCatalogItem, Boolean> catalogEnabledColumn;
@FXML private TableColumn<StudyCatalogItem, String> catalogColumn;
@FXML private VBox fetchersContainer;

private final ReadOnlyBooleanProperty refAiEnabled;

Expand All @@ -73,7 +62,7 @@ public String getTabName() {
}

public void initialize() {
this.viewModel = new WebSearchTabViewModel(preferences, dialogService, refAiEnabled);
this.viewModel = new WebSearchTabViewModel(preferences, refAiEnabled, taskExecutor);

enableWebSearch.selectedProperty().bindBidirectional(viewModel.enableWebSearchProperty());
warnAboutDuplicatesOnImport.selectedProperty().bindBidirectional(viewModel.warnAboutDuplicatesOnImportProperty());
Expand Down Expand Up @@ -117,86 +106,47 @@ public void initialize() {
useCustomDOIName.textProperty().bindBidirectional(viewModel.useCustomDOINameProperty());
useCustomDOIName.disableProperty().bind(useCustomDOI.selectedProperty().not());

new ViewModelTableRowFactory<StudyCatalogItem>()
.withOnMouseClickedEvent((entry, event) -> {
if (event.getButton() == MouseButton.PRIMARY) {
entry.setEnabled(!entry.isEnabled());
}
})
.install(catalogTable);

catalogColumn.setReorderable(false);
catalogColumn.setCellFactory(TextFieldTableCell.forTableColumn());

catalogEnabledColumn.setResizable(false);
catalogEnabledColumn.setReorderable(false);
catalogEnabledColumn.setCellFactory(CheckBoxTableCell.forTableColumn(catalogEnabledColumn));
catalogEnabledColumn.setCellValueFactory(param -> param.getValue().enabledProperty());

catalogColumn.setEditable(false);
catalogColumn.setCellValueFactory(param -> param.getValue().nameProperty());
catalogTable.setItems(viewModel.getCatalogs());

testCustomApiKey.setDisable(true);

new ViewModelTableRowFactory<FetcherApiKey>()
.install(apiKeySelectorTable);

apiKeySelectorTable.getSelectionModel().selectedItemProperty().addListener((_, oldValue, newValue) -> {
if (oldValue != null) {
updateFetcherApiKey(oldValue);
}
if (newValue != null) {
viewModel.selectedApiKeyProperty().setValue(newValue);
testCustomApiKey.disableProperty().bind(newValue.useProperty().not());
}
});

apiKeyName.setCellValueFactory(param -> param.getValue().nameProperty());
apiKeyName.setCellFactory(TextFieldTableCell.forTableColumn());
apiKeyName.setReorderable(false);
apiKeyName.setEditable(false);

customApiKey.setCellValueFactory(param -> param.getValue().keyProperty());
customApiKey.setCellFactory(TextFieldTableCell.forTableColumn());
customApiKey.setReorderable(false);
customApiKey.setResizable(true);
customApiKey.setEditable(true);

useCustomApiKey.setCellValueFactory(param -> param.getValue().useProperty());
useCustomApiKey.setCellFactory(CheckBoxTableCell.forTableColumn(useCustomApiKey));
useCustomApiKey.setEditable(true);
useCustomApiKey.setResizable(true);
useCustomApiKey.setReorderable(false);

persistApiKeys.selectedProperty().bindBidirectional(viewModel.getApikeyPersistProperty());
persistApiKeys.disableProperty().bind(viewModel.apiKeyPersistAvailable().not());
EasyBind.subscribe(viewModel.apiKeyPersistAvailable(), available -> {
if (!available) {
persistentTooltipWrapper.setTooltip(new Tooltip(Localization.lang("Credential store not available.")));
} else {
persistentTooltipWrapper.setTooltip(null);
}
});

apiKeySelectorTable.setItems(viewModel.fetcherApiKeys());

// Content is set later
viewModel.fetcherApiKeys().addListener((InvalidationListener) _ -> {
if (!apiKeySelectorTable.getItems().isEmpty()) {
apiKeySelectorTable.getSelectionModel().selectFirst();
}
});
InvalidationListener listener = _ -> fetchersContainer
.getChildren()
.setAll(viewModel.getFetchers()
.stream()
.map(this::createFetcherNode).toList());
viewModel.getFetchers().addListener(listener);
}

private void updateFetcherApiKey(FetcherApiKey apiKey) {
if (apiKey != null) {
apiKey.setKey(customApiKey.getCellData(apiKey).trim());
private Node createFetcherNode(WebSearchTabViewModel.FetcherViewModel item) {
HBox container = new HBox();
container.getStyleClass().add("fetcher-list-cell");
container.setAlignment(Pos.CENTER_LEFT);

CheckBox enabledCheckBox = new CheckBox();
enabledCheckBox.selectedProperty().bindBidirectional(item.enabledProperty());

Label nameLabel = new Label(item.getName());
nameLabel.getStyleClass().add("fetcher-name");

Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);

HelpButton helpButton = new HelpButton();
Optional<HelpFile> helpFile = item.getFetcher().getHelpPage();
if (helpFile.isPresent() && !helpFile.get().getPageName().isEmpty()) {
helpButton.setHelpFile(helpFile.get(), dialogService, preferences.getExternalApplicationsPreferences());
helpButton.setVisible(true);
} else {
helpButton.setVisible(false);
}

Button configureButton = new Button(Localization.lang("Configure API key"));
configureButton.getStyleClass().add("configure-button");
configureButton.setOnAction(_ -> showApiKeyDialog(item));
configureButton.setVisible(item.isCustomizable());

container.getChildren().addAll(enabledCheckBox, nameLabel, spacer, helpButton, configureButton);
return container;
}

@FXML
void checkCustomApiKey() {
viewModel.checkCustomApiKey();
private void showApiKeyDialog(WebSearchTabViewModel.FetcherViewModel fetcherViewModel) {
dialogService.showCustomDialogAndWait(new ApiKeyDialog(viewModel, fetcherViewModel));
}
}
Loading
Loading