Skip to content

Commit f83fd80

Browse files
Yubo-Caosubhramit
andauthored
feat: Update WebSearchTab (#13791)
* feat: Update WebSearchTab * docs: Update CHANGELOG.md * fix: Apply Trag suggestion * fix: Typo in the JabRef_en.properties * docs: Update CHANGELOG.md for another PR which I forgot to update the CHANGELOG * docs: Update CHANGELOG.md for another and another PR which I forgot to update the CHANGELOG * fix: Apply Subhramit's suggestions * docs: Update CHANGELOG.md * Fix checkstyle * fix brace --------- Co-authored-by: Subhramit Basu <[email protected]>
1 parent 090a4cf commit f83fd80

File tree

17 files changed

+461
-274
lines changed

17 files changed

+461
-274
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
8484
- The Welcome tab now has a responsive layout. [#12664](https://github.com/JabRef/jabref/issues/12664)
8585
- We introduced a donation prompt in the Welcome tab. [#12664](https://github.com/JabRef/jabref/issues/12664)
8686
- We changed to syntax for the websearch to the one of the main search bar. [#13607](https://github.com/JabRef/jabref/issues/13607)
87+
- We improved the for the web search tab in the preferences dialog [#13791](https://github.com/JabRef/jabref/pull/13791)
88+
- We improved the event viewer for debugging [#13783](https://github.com/JabRef/jabref/pull/13783).
8789

8890
### Fixed
8991

@@ -118,6 +120,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
118120
- We fixed an issue where "Copy to" was enabled even if no other library was opened. [#13280](https://github.com/JabRef/jabref/pull/13280)
119121
- We fixed an issue where the groups were still displayed after closing all libraries. [#13382](https://github.com/JabRef/jabref/issues/13382)
120122
- Enhanced field selection logic in the Merge Entries dialog when fetching from DOI to prefer valid years and entry types. [#12549](https://github.com/JabRef/jabref/issues/12549)
123+
- We fixed an issue where theme or font size are not respected for all dialogs [#13558](https://github.com/JabRef/jabref/issues/13558)
121124
- We removed unnecessary spacing and margin in the AutomaticFieldEditor. [#13792](https://github.com/JabRef/jabref/pull/13792)
122125

123126
### Removed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package org.jabref.gui.preferences.websearch;
2+
3+
import javafx.animation.Animation;
4+
import javafx.animation.Interpolator;
5+
import javafx.animation.RotateTransition;
6+
import javafx.application.Platform;
7+
import javafx.beans.property.BooleanProperty;
8+
import javafx.beans.property.SimpleBooleanProperty;
9+
import javafx.fxml.FXML;
10+
import javafx.scene.control.Button;
11+
import javafx.scene.control.ButtonType;
12+
import javafx.scene.control.CheckBox;
13+
import javafx.scene.control.TextField;
14+
import javafx.util.Duration;
15+
16+
import org.jabref.gui.FXDialog;
17+
import org.jabref.gui.icon.IconTheme;
18+
import org.jabref.gui.preferences.websearch.WebSearchTabViewModel.FetcherViewModel;
19+
import org.jabref.gui.util.DelayedExecution;
20+
import org.jabref.logic.l10n.Localization;
21+
22+
import com.airhacks.afterburner.views.ViewLoader;
23+
24+
public class ApiKeyDialog extends FXDialog {
25+
public static final Duration STATUS_DURATION = Duration.seconds(5);
26+
27+
@FXML private TextField apiKeyField;
28+
@FXML private CheckBox persistApiKeysCheckBox;
29+
@FXML private Button testButton;
30+
31+
private final WebSearchTabViewModel viewModel;
32+
private final FetcherViewModel fetcherViewModel;
33+
private final BooleanProperty apiKeyValid = new SimpleBooleanProperty();
34+
private RotateTransition rotation;
35+
36+
public ApiKeyDialog(WebSearchTabViewModel viewModel, FetcherViewModel fetcherViewModel) {
37+
super(AlertType.NONE, Localization.lang("API Key for %0", fetcherViewModel.getName()));
38+
this.viewModel = viewModel;
39+
this.fetcherViewModel = fetcherViewModel;
40+
41+
ViewLoader.view(this)
42+
.load()
43+
.setAsDialogPane(this);
44+
45+
setResultConverter(this::convertResult);
46+
}
47+
48+
@FXML
49+
private void initialize() {
50+
apiKeyField.setText(fetcherViewModel.getApiKey());
51+
52+
apiKeyValid.set(apiKeyField.getText().isEmpty() || fetcherViewModel.shouldUseCustomApiKey());
53+
54+
Button okButton = (Button) getDialogPane().lookupButton(ButtonType.OK);
55+
if (okButton != null) {
56+
okButton.disableProperty().bind(apiKeyValid.not());
57+
}
58+
59+
apiKeyField.textProperty().addListener((_, _, newValue) -> {
60+
apiKeyValid.set(newValue.isEmpty());
61+
testButton.setGraphic(null);
62+
testButton.setText(Localization.lang("Test connection"));
63+
testButton.getStyleClass().removeAll("success", "error");
64+
});
65+
66+
persistApiKeysCheckBox.selectedProperty().bindBidirectional(viewModel.getApikeyPersistProperty());
67+
persistApiKeysCheckBox.disableProperty().bind(viewModel.apiKeyPersistAvailable().not());
68+
69+
rotation = new RotateTransition(Duration.seconds(1), IconTheme.JabRefIcons.REFRESH.getGraphicNode());
70+
rotation.setByAngle(360);
71+
rotation.setCycleCount(Animation.INDEFINITE);
72+
rotation.setInterpolator(Interpolator.LINEAR);
73+
}
74+
75+
@FXML
76+
private void testApiKey() {
77+
testButton.setDisable(true);
78+
testButton.setText(Localization.lang("Testing..."));
79+
testButton.setGraphic(rotation.getNode());
80+
rotation.play();
81+
viewModel.checkApiKey(fetcherViewModel, apiKeyField.getText(), this::handleTestResult);
82+
}
83+
84+
private void handleTestResult(boolean success) {
85+
Platform.runLater(() -> {
86+
rotation.stop();
87+
testButton.setDisable(false);
88+
89+
testButton.setGraphic(success ? IconTheme.JabRefIcons.SUCCESS.getGraphicNode() : IconTheme.JabRefIcons.ERROR.getGraphicNode());
90+
testButton.getStyleClass().removeAll("success", "error");
91+
testButton.getStyleClass().add(success ? "success" : "error");
92+
testButton.setText(success ? Localization.lang("API Key Valid") : Localization.lang("API Key Invalid"));
93+
apiKeyValid.set(success);
94+
95+
new DelayedExecution(STATUS_DURATION, () -> {
96+
testButton.setGraphic(null);
97+
testButton.setText(Localization.lang("Test connection"));
98+
}).start();
99+
});
100+
}
101+
102+
private ButtonType convertResult(ButtonType button) {
103+
if (button == ButtonType.OK) {
104+
String apiKey = apiKeyField.getText().trim();
105+
fetcherViewModel.apiKeyProperty().set(apiKey);
106+
fetcherViewModel.useCustomApiKeyProperty().set(!apiKey.isEmpty());
107+
}
108+
return button;
109+
}
110+
}
Lines changed: 50 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,34 @@
11
package org.jabref.gui.preferences.websearch;
22

3+
import java.util.Optional;
4+
35
import javafx.beans.InvalidationListener;
46
import javafx.beans.property.ReadOnlyBooleanProperty;
57
import javafx.fxml.FXML;
8+
import javafx.geometry.Pos;
9+
import javafx.scene.Node;
610
import javafx.scene.control.Button;
711
import javafx.scene.control.CheckBox;
812
import javafx.scene.control.ComboBox;
9-
import javafx.scene.control.SplitPane;
10-
import javafx.scene.control.TableColumn;
11-
import javafx.scene.control.TableView;
13+
import javafx.scene.control.Label;
1214
import javafx.scene.control.TextField;
13-
import javafx.scene.control.Tooltip;
14-
import javafx.scene.control.cell.CheckBoxTableCell;
15-
import javafx.scene.control.cell.TextFieldTableCell;
16-
import javafx.scene.input.MouseButton;
15+
import javafx.scene.layout.HBox;
16+
import javafx.scene.layout.Priority;
17+
import javafx.scene.layout.Region;
18+
import javafx.scene.layout.VBox;
1719

1820
import org.jabref.gui.preferences.AbstractPreferenceTabView;
1921
import org.jabref.gui.preferences.PreferencesTab;
20-
import org.jabref.gui.slr.StudyCatalogItem;
2122
import org.jabref.gui.util.ViewModelListCellFactory;
22-
import org.jabref.gui.util.ViewModelTableRowFactory;
23+
import org.jabref.gui.util.component.HelpButton;
24+
import org.jabref.logic.help.HelpFile;
2325
import org.jabref.logic.importer.plaincitation.PlainCitationParserChoice;
2426
import org.jabref.logic.l10n.Localization;
25-
import org.jabref.logic.preferences.FetcherApiKey;
2627
import org.jabref.model.strings.StringUtil;
2728

2829
import com.airhacks.afterburner.views.ViewLoader;
29-
import com.tobiasdiez.easybind.EasyBind;
3030

3131
public class WebSearchTab extends AbstractPreferenceTabView<WebSearchTabViewModel> implements PreferencesTab {
32-
3332
@FXML private CheckBox enableWebSearch;
3433
@FXML private CheckBox warnAboutDuplicatesOnImport;
3534
@FXML private CheckBox downloadLinkedOnlineFiles;
@@ -45,17 +44,7 @@ public class WebSearchTab extends AbstractPreferenceTabView<WebSearchTabViewMode
4544
@FXML private CheckBox grobidEnabled;
4645
@FXML private TextField grobidURL;
4746

48-
@FXML private TableView<FetcherApiKey> apiKeySelectorTable;
49-
@FXML private TableColumn<FetcherApiKey, String> apiKeyName;
50-
@FXML private TableColumn<FetcherApiKey, String> customApiKey;
51-
@FXML private TableColumn<FetcherApiKey, Boolean> useCustomApiKey;
52-
@FXML private Button testCustomApiKey;
53-
54-
@FXML private CheckBox persistApiKeys;
55-
@FXML private SplitPane persistentTooltipWrapper; // The disabled persistApiKeys control does not show tooltips
56-
@FXML private TableView<StudyCatalogItem> catalogTable;
57-
@FXML private TableColumn<StudyCatalogItem, Boolean> catalogEnabledColumn;
58-
@FXML private TableColumn<StudyCatalogItem, String> catalogColumn;
47+
@FXML private VBox fetchersContainer;
5948

6049
private final ReadOnlyBooleanProperty refAiEnabled;
6150

@@ -73,7 +62,7 @@ public String getTabName() {
7362
}
7463

7564
public void initialize() {
76-
this.viewModel = new WebSearchTabViewModel(preferences, dialogService, refAiEnabled);
65+
this.viewModel = new WebSearchTabViewModel(preferences, refAiEnabled, taskExecutor);
7766

7867
enableWebSearch.selectedProperty().bindBidirectional(viewModel.enableWebSearchProperty());
7968
warnAboutDuplicatesOnImport.selectedProperty().bindBidirectional(viewModel.warnAboutDuplicatesOnImportProperty());
@@ -117,86 +106,47 @@ public void initialize() {
117106
useCustomDOIName.textProperty().bindBidirectional(viewModel.useCustomDOINameProperty());
118107
useCustomDOIName.disableProperty().bind(useCustomDOI.selectedProperty().not());
119108

120-
new ViewModelTableRowFactory<StudyCatalogItem>()
121-
.withOnMouseClickedEvent((entry, event) -> {
122-
if (event.getButton() == MouseButton.PRIMARY) {
123-
entry.setEnabled(!entry.isEnabled());
124-
}
125-
})
126-
.install(catalogTable);
127-
128-
catalogColumn.setReorderable(false);
129-
catalogColumn.setCellFactory(TextFieldTableCell.forTableColumn());
130-
131-
catalogEnabledColumn.setResizable(false);
132-
catalogEnabledColumn.setReorderable(false);
133-
catalogEnabledColumn.setCellFactory(CheckBoxTableCell.forTableColumn(catalogEnabledColumn));
134-
catalogEnabledColumn.setCellValueFactory(param -> param.getValue().enabledProperty());
135-
136-
catalogColumn.setEditable(false);
137-
catalogColumn.setCellValueFactory(param -> param.getValue().nameProperty());
138-
catalogTable.setItems(viewModel.getCatalogs());
139-
140-
testCustomApiKey.setDisable(true);
141-
142-
new ViewModelTableRowFactory<FetcherApiKey>()
143-
.install(apiKeySelectorTable);
144-
145-
apiKeySelectorTable.getSelectionModel().selectedItemProperty().addListener((_, oldValue, newValue) -> {
146-
if (oldValue != null) {
147-
updateFetcherApiKey(oldValue);
148-
}
149-
if (newValue != null) {
150-
viewModel.selectedApiKeyProperty().setValue(newValue);
151-
testCustomApiKey.disableProperty().bind(newValue.useProperty().not());
152-
}
153-
});
154-
155-
apiKeyName.setCellValueFactory(param -> param.getValue().nameProperty());
156-
apiKeyName.setCellFactory(TextFieldTableCell.forTableColumn());
157-
apiKeyName.setReorderable(false);
158-
apiKeyName.setEditable(false);
159-
160-
customApiKey.setCellValueFactory(param -> param.getValue().keyProperty());
161-
customApiKey.setCellFactory(TextFieldTableCell.forTableColumn());
162-
customApiKey.setReorderable(false);
163-
customApiKey.setResizable(true);
164-
customApiKey.setEditable(true);
165-
166-
useCustomApiKey.setCellValueFactory(param -> param.getValue().useProperty());
167-
useCustomApiKey.setCellFactory(CheckBoxTableCell.forTableColumn(useCustomApiKey));
168-
useCustomApiKey.setEditable(true);
169-
useCustomApiKey.setResizable(true);
170-
useCustomApiKey.setReorderable(false);
171-
172-
persistApiKeys.selectedProperty().bindBidirectional(viewModel.getApikeyPersistProperty());
173-
persistApiKeys.disableProperty().bind(viewModel.apiKeyPersistAvailable().not());
174-
EasyBind.subscribe(viewModel.apiKeyPersistAvailable(), available -> {
175-
if (!available) {
176-
persistentTooltipWrapper.setTooltip(new Tooltip(Localization.lang("Credential store not available.")));
177-
} else {
178-
persistentTooltipWrapper.setTooltip(null);
179-
}
180-
});
181-
182-
apiKeySelectorTable.setItems(viewModel.fetcherApiKeys());
183-
184-
// Content is set later
185-
viewModel.fetcherApiKeys().addListener((InvalidationListener) _ -> {
186-
if (!apiKeySelectorTable.getItems().isEmpty()) {
187-
apiKeySelectorTable.getSelectionModel().selectFirst();
188-
}
189-
});
109+
InvalidationListener listener = _ -> fetchersContainer
110+
.getChildren()
111+
.setAll(viewModel.getFetchers()
112+
.stream()
113+
.map(this::createFetcherNode).toList());
114+
viewModel.getFetchers().addListener(listener);
190115
}
191116

192-
private void updateFetcherApiKey(FetcherApiKey apiKey) {
193-
if (apiKey != null) {
194-
apiKey.setKey(customApiKey.getCellData(apiKey).trim());
117+
private Node createFetcherNode(WebSearchTabViewModel.FetcherViewModel item) {
118+
HBox container = new HBox();
119+
container.getStyleClass().add("fetcher-list-cell");
120+
container.setAlignment(Pos.CENTER_LEFT);
121+
122+
CheckBox enabledCheckBox = new CheckBox();
123+
enabledCheckBox.selectedProperty().bindBidirectional(item.enabledProperty());
124+
125+
Label nameLabel = new Label(item.getName());
126+
nameLabel.getStyleClass().add("fetcher-name");
127+
128+
Region spacer = new Region();
129+
HBox.setHgrow(spacer, Priority.ALWAYS);
130+
131+
HelpButton helpButton = new HelpButton();
132+
Optional<HelpFile> helpFile = item.getFetcher().getHelpPage();
133+
if (helpFile.isPresent() && !helpFile.get().getPageName().isEmpty()) {
134+
helpButton.setHelpFile(helpFile.get(), dialogService, preferences.getExternalApplicationsPreferences());
135+
helpButton.setVisible(true);
136+
} else {
137+
helpButton.setVisible(false);
195138
}
139+
140+
Button configureButton = new Button(Localization.lang("Configure API key"));
141+
configureButton.getStyleClass().add("configure-button");
142+
configureButton.setOnAction(_ -> showApiKeyDialog(item));
143+
configureButton.setVisible(item.isCustomizable());
144+
145+
container.getChildren().addAll(enabledCheckBox, nameLabel, spacer, helpButton, configureButton);
146+
return container;
196147
}
197148

198-
@FXML
199-
void checkCustomApiKey() {
200-
viewModel.checkCustomApiKey();
149+
private void showApiKeyDialog(WebSearchTabViewModel.FetcherViewModel fetcherViewModel) {
150+
dialogService.showCustomDialogAndWait(new ApiKeyDialog(viewModel, fetcherViewModel));
201151
}
202152
}

0 commit comments

Comments
 (0)