Skip to content

Commit 623a6fe

Browse files
authored
Merge branch 'main' into fix-for-issue-14401
2 parents 956f058 + a9577aa commit 623a6fe

File tree

73 files changed

+1573
-253
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+1573
-253
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,17 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
2525
- We added "Close library" to the File menu. [#14381](https://github.com/JabRef/jabref/issues/14381)
2626
- We added a "Regenerate" button for the AI chat allowing the user to make the language model reformulate its response to the previous prompt. [#12191](https://github.com/JabRef/jabref/issues/12191)
2727
- We added support for transliteration of fields to English and automatic transliteration of generated citation key. [#11377](https://github.com/JabRef/jabref/issues/11377)
28+
- We added the generation of follow-up questions in AI chat. [#12243](https://github.com/JabRef/jabref/issues/12243)
29+
- We added support for "Search Google Scholar" and "Search Semantic Scholar" to quickly search for a selected entry's title in Google Scholar or Semantic Scholar directly from the main table's context menu [#12268](https://github.com/JabRef/jabref/issues/12268)
30+
- We introduced a new "Search Engine URL Template" setting in Preferences to allow users to customize their search engine URL templates [#12268](https://github.com/JabRef/jabref/issues/12268)
2831

2932
### Changed
3033

3134
- We replaced the standard ComboBox with a SearchableComboBox and added a free text field in custom Entry Types [#14082](https://github.com/JabRef/jabref/issues/14082)
3235
- We separated the "Clean up entries" dialog into three tabs for clarity [#13819](https://github.com/JabRef/jabref/issues/13819)
3336
- `JabKit`: `--porcelain` does not output any logs to the console anymore. [#14244](https://github.com/JabRef/jabref/pull/14244)
3437
- <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>L</kbd> now opens the terminal in the active library directory. [#14130](https://github.com/JabRef/jabref/issues/14130)
38+
- The URL integrity check now checks the complete URL syntax. [#14370](https://github.com/JabRef/jabref/pull/14370)
3539
- We changed fixed-value ComboBoxes to SearchableComboBox for better usability. [#14083](https://github.com/JabRef/jabref/issues/14083)
3640

3741
### Fixed

jabgui/src/main/java/org/jabref/gui/CoreGuiPreferences.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,32 @@ public CoreGuiPreferences(double positionX,
3232
this.verticalDividerPosition = new SimpleDoubleProperty(verticalDividerPosition);
3333
}
3434

35+
/// Creates object with default values
36+
private CoreGuiPreferences() {
37+
this(
38+
0, // Main window position x
39+
0, // Main window position y
40+
1024, // Main window size x
41+
768, // Main window size y
42+
true, // Main window maximized
43+
0.15, // Horizontal divider position
44+
0.65); // Vertical divider position
45+
}
46+
47+
public static CoreGuiPreferences getDefault() {
48+
return new CoreGuiPreferences();
49+
}
50+
51+
public void setAll(CoreGuiPreferences preferences) {
52+
this.positionX.set(preferences.getPositionX());
53+
this.positionY.set(preferences.getPositionY());
54+
this.sizeX.set(preferences.getSizeX());
55+
this.sizeY.set(preferences.getSizeY());
56+
this.windowMaximised.set(preferences.isWindowMaximised());
57+
this.horizontalDividerPosition.set(preferences.getHorizontalDividerPosition());
58+
this.verticalDividerPosition.set(preferences.getVerticalDividerPosition());
59+
}
60+
3561
public double getPositionX() {
3662
return positionX.get();
3763
}

jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ public enum StandardActions implements Action {
4444
EXTRACT_FILE_REFERENCES_ONLINE(Localization.lang("Extract references from file (online)"), IconTheme.JabRefIcons.FILE_STAR),
4545
EXTRACT_FILE_REFERENCES_OFFLINE(Localization.lang("Extract references from file (offline)"), IconTheme.JabRefIcons.FILE_STAR),
4646
OPEN_URL(Localization.lang("Open URL or DOI"), IconTheme.JabRefIcons.WWW, KeyBinding.OPEN_URL_OR_DOI),
47+
SEARCH(Localization.lang("Search...")),
48+
SEARCH_GOOGLE_SCHOLAR(Localization.lang("Search Google Scholar")),
49+
SEARCH_SEMANTIC_SCHOLAR(Localization.lang("Search Semantic Scholar")),
4750
SEARCH_SHORTSCIENCE(Localization.lang("Search ShortScience")),
4851
MERGE_WITH_FETCHED_ENTRY(Localization.lang("Get bibliographic data from %0", "DOI/ISBN/..."), KeyBinding.MERGE_WITH_FETCHED_ENTRY),
4952
BATCH_MERGE_WITH_FETCHED_ENTRY(Localization.lang("Get bibliographic data from %0 (fully automated)", "DOI/ISBN/...")),

jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import javafx.scene.control.Button;
1616
import javafx.scene.control.Hyperlink;
1717
import javafx.scene.control.Label;
18+
import javafx.scene.control.Tooltip;
1819
import javafx.scene.layout.HBox;
1920
import javafx.scene.layout.VBox;
2021
import javafx.scene.paint.Color;
@@ -78,6 +79,7 @@ public class AiChatComponent extends VBox {
7879
@FXML private Hyperlink exQuestion2;
7980
@FXML private Hyperlink exQuestion3;
8081
@FXML private HBox exQuestionBox;
82+
@FXML private HBox followUpQuestionsBox;
8183

8284
private String noticeTemplate;
8385

@@ -114,6 +116,7 @@ public void initialize() {
114116
initializeNotifications();
115117
sendExampleQuestions();
116118
initializeExampleQuestions();
119+
initializeFollowUpQuestions();
117120
}
118121

119122
private void initializeNotifications() {
@@ -214,6 +217,42 @@ private void initializeChatPrompt() {
214217
updatePromptHistory();
215218
}
216219

220+
private void initializeFollowUpQuestions() {
221+
aiChatLogic.getFollowUpQuestions().addListener((javafx.collections.ListChangeListener<String>) change -> {
222+
updateFollowUpQuestions();
223+
});
224+
}
225+
226+
private void updateFollowUpQuestions() {
227+
List<String> questions = new ArrayList<>(aiChatLogic.getFollowUpQuestions());
228+
229+
UiTaskExecutor.runInJavaFXThread(() -> {
230+
followUpQuestionsBox.getChildren().removeIf(node -> node instanceof Hyperlink);
231+
232+
if (questions.isEmpty()) {
233+
followUpQuestionsBox.setVisible(false);
234+
followUpQuestionsBox.setManaged(false);
235+
exQuestionBox.setVisible(true);
236+
exQuestionBox.setManaged(true);
237+
} else {
238+
followUpQuestionsBox.setVisible(true);
239+
followUpQuestionsBox.setManaged(true);
240+
exQuestionBox.setVisible(false);
241+
exQuestionBox.setManaged(false);
242+
243+
for (String question : questions) {
244+
Hyperlink link = new Hyperlink(question);
245+
link.getStyleClass().add("exampleQuestionStyle");
246+
link.setTooltip(new Tooltip(question));
247+
link.setOnAction(event -> {
248+
onSendMessage(question);
249+
});
250+
followUpQuestionsBox.getChildren().add(link);
251+
}
252+
}
253+
});
254+
}
255+
217256
private void updateNotifications() {
218257
notifications.clear();
219258
notifications.addAll(entries.stream().map(this::updateNotificationsForEntry).flatMap(List::stream).toList());
@@ -278,6 +317,13 @@ private List<Notification> updateNotificationsForEntry(BibEntry entry) {
278317
}
279318

280319
private void onSendMessage(String userPrompt) {
320+
aiChatLogic.getFollowUpQuestions().clear();
321+
322+
UiTaskExecutor.runInJavaFXThread(() -> {
323+
exQuestionBox.setVisible(false);
324+
exQuestionBox.setManaged(false);
325+
});
326+
281327
UserMessage userMessage = new UserMessage(userPrompt);
282328
updatePromptHistory();
283329
setLoading(true);

jabgui/src/main/java/org/jabref/gui/cleanup/CleanupDialogViewModel.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,4 +198,3 @@ private void showFailures(List<JabRefException> failures) {
198198
);
199199
}
200200
}
201-

jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ public static ContextMenu create(BibEntryTableViewModel entry,
9999
extractFileReferencesOffline,
100100

101101
factory.createMenuItem(StandardActions.OPEN_URL, new OpenUrlAction(dialogService, stateManager, preferences)),
102-
factory.createMenuItem(StandardActions.SEARCH_SHORTSCIENCE, new SearchShortScienceAction(dialogService, stateManager, preferences)),
102+
103+
createSearchSubMenu(factory, dialogService, stateManager, preferences),
103104

104105
new SeparatorMenuItem(),
105106

@@ -237,4 +238,17 @@ private static Menu createSendSubMenu(ActionFactory factory,
237238

238239
return sendMenu;
239240
}
241+
242+
private static Menu createSearchSubMenu(ActionFactory factory,
243+
DialogService dialogService,
244+
StateManager stateManager,
245+
GuiPreferences preferences) {
246+
Menu searchMenu = factory.createMenu(StandardActions.SEARCH);
247+
searchMenu.getItems().addAll(
248+
factory.createMenuItem(StandardActions.SEARCH_GOOGLE_SCHOLAR, new SearchGoogleScholarAction(dialogService, stateManager, preferences)),
249+
factory.createMenuItem(StandardActions.SEARCH_SEMANTIC_SCHOLAR, new SearchSemanticScholarAction(dialogService, stateManager, preferences)),
250+
factory.createMenuItem(StandardActions.SEARCH_SHORTSCIENCE, new SearchShortScienceAction(dialogService, stateManager, preferences))
251+
);
252+
return searchMenu;
253+
}
240254
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package org.jabref.gui.maintable;
2+
3+
import java.io.IOException;
4+
import java.util.List;
5+
6+
import javafx.beans.binding.BooleanExpression;
7+
8+
import org.jabref.gui.DialogService;
9+
import org.jabref.gui.StateManager;
10+
import org.jabref.gui.actions.SimpleCommand;
11+
import org.jabref.gui.desktop.os.NativeDesktop;
12+
import org.jabref.gui.preferences.GuiPreferences;
13+
import org.jabref.logic.l10n.Localization;
14+
import org.jabref.logic.util.ExternalLinkCreator;
15+
import org.jabref.model.entry.BibEntry;
16+
import org.jabref.model.entry.field.StandardField;
17+
18+
import org.slf4j.Logger;
19+
import org.slf4j.LoggerFactory;
20+
21+
import static org.jabref.gui.actions.ActionHelper.isFieldSetForSelectedEntry;
22+
import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected;
23+
24+
public class SearchGoogleScholarAction extends SimpleCommand {
25+
private static final Logger LOGGER = LoggerFactory.getLogger(SearchGoogleScholarAction.class);
26+
27+
private final DialogService dialogService;
28+
private final StateManager stateManager;
29+
private final GuiPreferences preferences;
30+
private final ExternalLinkCreator externalLinkCreator;
31+
32+
public SearchGoogleScholarAction(DialogService dialogService, StateManager stateManager, GuiPreferences preferences) {
33+
this.dialogService = dialogService;
34+
this.stateManager = stateManager;
35+
this.preferences = preferences;
36+
37+
this.externalLinkCreator = new ExternalLinkCreator(preferences.getImporterPreferences());
38+
39+
BooleanExpression fieldIsSet = isFieldSetForSelectedEntry(StandardField.TITLE, stateManager);
40+
this.executable.bind(needsEntriesSelected(1, stateManager).and(fieldIsSet));
41+
}
42+
43+
@Override
44+
public void execute() {
45+
stateManager.getActiveDatabase().ifPresent(databaseContext -> {
46+
final List<BibEntry> bibEntries = stateManager.getSelectedEntries();
47+
externalLinkCreator.getGoogleScholarSearchURL(bibEntries.getFirst()).ifPresent(url -> {
48+
try {
49+
NativeDesktop.openExternalViewer(databaseContext, preferences, url, StandardField.URL, dialogService, bibEntries.getFirst());
50+
} catch (IOException ex) {
51+
LOGGER.warn("Could not open Google Scholar", ex);
52+
dialogService.notify(Localization.lang("Unable to open Google Scholar.") + " " + ex.getMessage());
53+
}
54+
});
55+
});
56+
}
57+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package org.jabref.gui.maintable;
2+
3+
import java.io.IOException;
4+
import java.util.List;
5+
6+
import javafx.beans.binding.BooleanExpression;
7+
8+
import org.jabref.gui.DialogService;
9+
import org.jabref.gui.StateManager;
10+
import org.jabref.gui.actions.ActionHelper;
11+
import org.jabref.gui.actions.SimpleCommand;
12+
import org.jabref.gui.desktop.os.NativeDesktop;
13+
import org.jabref.gui.preferences.GuiPreferences;
14+
import org.jabref.logic.l10n.Localization;
15+
import org.jabref.logic.util.ExternalLinkCreator;
16+
import org.jabref.model.entry.BibEntry;
17+
import org.jabref.model.entry.field.StandardField;
18+
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
22+
public class SearchSemanticScholarAction extends SimpleCommand {
23+
private static final Logger LOGGER = LoggerFactory.getLogger(SearchSemanticScholarAction.class);
24+
25+
private final DialogService dialogService;
26+
private final StateManager stateManager;
27+
private final GuiPreferences preferences;
28+
private final ExternalLinkCreator externalLinkCreator;
29+
30+
public SearchSemanticScholarAction(DialogService dialogService, StateManager stateManager, GuiPreferences preferences) {
31+
this.dialogService = dialogService;
32+
this.stateManager = stateManager;
33+
this.preferences = preferences;
34+
35+
this.externalLinkCreator = new ExternalLinkCreator(preferences.getImporterPreferences());
36+
37+
BooleanExpression fieldIsSet = ActionHelper.isFieldSetForSelectedEntry(StandardField.TITLE, stateManager);
38+
this.executable.bind(ActionHelper.needsEntriesSelected(1, stateManager).and(fieldIsSet));
39+
}
40+
41+
@Override
42+
public void execute() {
43+
stateManager.getActiveDatabase().ifPresent(databaseContext -> {
44+
final List<BibEntry> bibEntries = stateManager.getSelectedEntries();
45+
externalLinkCreator.getSemanticScholarSearchURL(bibEntries.getFirst()).ifPresent(url -> {
46+
try {
47+
NativeDesktop.openExternalViewer(databaseContext, preferences, url, StandardField.URL, dialogService, bibEntries.getFirst());
48+
} catch (IOException ex) {
49+
LOGGER.warn("Could not open Semantic Scholar", ex);
50+
dialogService.notify(Localization.lang("Unable to open Semantic Scholar.") + " " + ex.getMessage());
51+
}
52+
});
53+
});
54+
}
55+
}

jabgui/src/main/java/org/jabref/gui/maintable/SearchShortScienceAction.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,25 @@
1515
import org.jabref.model.entry.BibEntry;
1616
import org.jabref.model.entry.field.StandardField;
1717

18+
import org.slf4j.Logger;
19+
import org.slf4j.LoggerFactory;
20+
1821
import static org.jabref.gui.actions.ActionHelper.isFieldSetForSelectedEntry;
1922
import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected;
2023

2124
public class SearchShortScienceAction extends SimpleCommand {
25+
private static final Logger LOGGER = LoggerFactory.getLogger(SearchShortScienceAction.class);
26+
2227
private final DialogService dialogService;
2328
private final StateManager stateManager;
2429
private final GuiPreferences preferences;
30+
private final ExternalLinkCreator externalLinkCreator;
2531

2632
public SearchShortScienceAction(DialogService dialogService, StateManager stateManager, GuiPreferences preferences) {
2733
this.dialogService = dialogService;
2834
this.stateManager = stateManager;
2935
this.preferences = preferences;
36+
this.externalLinkCreator = new ExternalLinkCreator(preferences.getImporterPreferences());
3037

3138
BooleanExpression fieldIsSet = isFieldSetForSelectedEntry(StandardField.TITLE, stateManager);
3239
this.executable.bind(needsEntriesSelected(1, stateManager).and(fieldIsSet));
@@ -41,11 +48,12 @@ public void execute() {
4148
dialogService.notify(Localization.lang("This operation requires exactly one item to be selected."));
4249
return;
4350
}
44-
ExternalLinkCreator.getShortScienceSearchURL(bibEntries.getFirst()).ifPresent(url -> {
51+
externalLinkCreator.getShortScienceSearchURL(bibEntries.getFirst()).ifPresent(url -> {
4552
try {
4653
NativeDesktop.openExternalViewer(databaseContext, preferences, url, StandardField.URL, dialogService, bibEntries.getFirst());
4754
} catch (IOException ex) {
48-
dialogService.showErrorDialogAndWait(Localization.lang("Unable to open ShortScience."), ex);
55+
LOGGER.warn("Could not open ShortScience", ex);
56+
dialogService.notify(Localization.lang("Unable to open ShortScience.") + " " + ex.getMessage());
4957
}
5058
});
5159
});

jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/FieldRowViewModel.java

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,13 @@
1616

1717
import org.jabref.gui.mergeentries.threewaymerge.fieldsmerger.FieldMerger;
1818
import org.jabref.gui.mergeentries.threewaymerge.fieldsmerger.FieldMergerFactory;
19-
import org.jabref.logic.bibtex.comparator.ComparisonResult;
20-
import org.jabref.logic.bibtex.comparator.YearFieldValuePlausibilityComparator;
19+
import org.jabref.logic.bibtex.comparator.plausibility.PlausibilityComparatorFactory;
2120
import org.jabref.logic.util.strings.StringUtil;
2221
import org.jabref.model.entry.BibEntry;
2322
import org.jabref.model.entry.field.Field;
2423
import org.jabref.model.entry.field.FieldTextMapper;
2524
import org.jabref.model.entry.field.InternalField;
26-
import org.jabref.model.entry.field.StandardField;
2725
import org.jabref.model.entry.types.EntryTypeFactory;
28-
import org.jabref.model.entry.types.StandardEntryType;
2926

3027
import com.tobiasdiez.easybind.EasyBind;
3128
import org.slf4j.Logger;
@@ -131,19 +128,18 @@ public void autoSelectBetterValue() {
131128
String leftValue = getLeftFieldValue();
132129
String rightValue = getRightFieldValue();
133130

134-
if (StandardField.YEAR == field) {
135-
YearFieldValuePlausibilityComparator comparator = new YearFieldValuePlausibilityComparator();
136-
ComparisonResult comparison = comparator.compare(leftValue, rightValue);
137-
if (ComparisonResult.RIGHT_BETTER == comparison) {
138-
selectRightValue();
139-
} else if (ComparisonResult.LEFT_BETTER == comparison) {
140-
selectLeftValue();
141-
}
142-
} else if (InternalField.TYPE_HEADER == field) {
143-
if (leftValue.equalsIgnoreCase(StandardEntryType.Misc.getName())) {
144-
selectRightValue();
145-
}
146-
}
131+
PlausibilityComparatorFactory.INSTANCE
132+
.getPlausibilityComparator(field)
133+
.map(comparator -> comparator.compare(leftValue, rightValue))
134+
.ifPresent(result -> {
135+
switch (result) {
136+
case RIGHT_BETTER ->
137+
selectRightValue();
138+
case LEFT_BETTER ->
139+
selectLeftValue();
140+
default -> { /* nothing */ }
141+
}
142+
});
147143
}
148144

149145
public void selectNonEmptyValue() {

0 commit comments

Comments
 (0)