Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We added support for import of a Refer/BibIX file format. [#13069](https://github.com/JabRef/jabref/issues/13069)
- We added a new `jabkit` command `pseudonymize` to pseudonymize the library. [#13109](https://github.com/JabRef/jabref/issues/13109)
- We added functionality to focus running instance when trying to start a second instance. [#13129](https://github.com/JabRef/jabref/issues/13129)
- We added support for "Search Google Scholar" to quickly search for a selected entry's title in Google Scholar directly from the main table's context menu [#12268](https://github.com/JabRef/jabref/issues/12268)
- 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)

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public enum StandardActions implements Action {
EXTRACT_FILE_REFERENCES_OFFLINE(Localization.lang("Extract references from file (offline)"), IconTheme.JabRefIcons.FILE_STAR),
OPEN_URL(Localization.lang("Open URL or DOI"), IconTheme.JabRefIcons.WWW, KeyBinding.OPEN_URL_OR_DOI),
SEARCH_SHORTSCIENCE(Localization.lang("Search ShortScience")),
SEARCH_GOOGLE_SCHOLAR(Localization.lang("Search Google Scholar")),
SEARCH(Localization.lang("Search...")),
MERGE_WITH_FETCHED_ENTRY(Localization.lang("Get bibliographic data from %0", "DOI/ISBN/..."), KeyBinding.MERGE_WITH_FETCHED_ENTRY),
BATCH_MERGE_WITH_FETCHED_ENTRY(Localization.lang("Get bibliographic data from %0 (fully automated)", "DOI/ISBN/...")),
ATTACH_FILE(Localization.lang("Attach file"), IconTheme.JabRefIcons.ATTACH_FILE),
Expand Down
27 changes: 14 additions & 13 deletions jabgui/src/main/java/org/jabref/gui/maintable/MainTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ public MainTable(MainTableDataModel model,
taskExecutor,
Injector.instantiateModelOrService(JournalAbbreviationRepository.class),
entryTypesManager,
importHandler))
importHandler,
preferences.getImporterPreferences()))
.withPseudoClass(MATCHING_SEARCH_AND_GROUPS, entry -> entry.matchCategory().isEqualTo(MatchCategory.MATCHING_SEARCH_AND_GROUPS))
.withPseudoClass(MATCHING_SEARCH_NOT_GROUPS, entry -> entry.matchCategory().isEqualTo(MatchCategory.MATCHING_SEARCH_NOT_GROUPS))
.withPseudoClass(MATCHING_GROUPS_NOT_SEARCH, entry -> entry.matchCategory().isEqualTo(MatchCategory.MATCHING_GROUPS_NOT_SEARCH))
Expand All @@ -180,10 +181,10 @@ public MainTable(MainTableDataModel model,
// force match category column to be the first sort order, (match_category column is always the first column)
this.getSortOrder().addFirst(getColumns().getFirst());
this.getSortOrder().addListener((ListChangeListener<TableColumn<BibEntryTableViewModel, ?>>) change -> {
if (!this.getSortOrder().getFirst().equals(getColumns().getFirst())) {
this.getSortOrder().addFirst(getColumns().getFirst());
}
});
if (!this.getSortOrder().getFirst().equals(getColumns().getFirst())) {
this.getSortOrder().addFirst(getColumns().getFirst());
}
});

mainTablePreferences.getColumnPreferences().getColumnSortOrder().forEach(columnModel ->
this.getColumns().stream()
Expand Down Expand Up @@ -414,7 +415,7 @@ private void setupKeyBindings(KeyBindingRepository keyBindings) {
event.consume();
break;
case SCROLL_TO_PREVIOUS_MATCH_CATEGORY:
scrollToPreviousMatchCategory();
scrollToPreviousMatchCategory();
event.consume();
break;
case OPEN_URL_OR_DOI:
Expand Down Expand Up @@ -576,13 +577,13 @@ public void setCitationMergeMode(boolean citationMerge) {
}

private void updatePlaceholder(VBox placeholderBox) {
if (database.getDatabase().getEntries().isEmpty()) {
this.setPlaceholder(placeholderBox);
// [impl->req~maintable.focus~1]
requestFocus();
} else {
this.setPlaceholder(null);
}
if (database.getDatabase().getEntries().isEmpty()) {
this.setPlaceholder(placeholderBox);
// [impl->req~maintable.focus~1]
requestFocus();
} else {
this.setPlaceholder(null);
}
}

private BibEntry addExampleEntry() {
Expand Down
20 changes: 17 additions & 3 deletions jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.jabref.gui.specialfields.SpecialFieldMenuItemFactory;
import org.jabref.logic.citationstyle.CitationStyleOutputFormat;
import org.jabref.logic.citationstyle.CitationStylePreviewLayout;
import org.jabref.logic.importer.ImporterPreferences;
import org.jabref.logic.importer.WebFetchers;
import org.jabref.logic.journals.JournalAbbreviationRepository;
import org.jabref.logic.l10n.Localization;
Expand All @@ -62,7 +63,8 @@ public static ContextMenu create(BibEntryTableViewModel entry,
TaskExecutor taskExecutor,
JournalAbbreviationRepository abbreviationRepository,
BibEntryTypesManager entryTypesManager,
ImportHandler importHandler) {
ImportHandler importHandler,
ImporterPreferences importerPreferences) {
ActionFactory factory = new ActionFactory();
ContextMenu contextMenu = new ContextMenu();

Expand Down Expand Up @@ -101,8 +103,7 @@ public static ContextMenu create(BibEntryTableViewModel entry,
extractFileReferencesOffline,

factory.createMenuItem(StandardActions.OPEN_URL, new OpenUrlAction(dialogService, stateManager, preferences)),
factory.createMenuItem(StandardActions.SEARCH_SHORTSCIENCE, new SearchShortScienceAction(dialogService, stateManager, preferences)),

createSearchSubMenu(factory, dialogService, stateManager, preferences, importerPreferences),
new SeparatorMenuItem(),

new ChangeEntryTypeMenu(libraryTab.getSelectedEntries(), libraryTab.getBibDatabaseContext(), undoManager, entryTypesManager).asSubMenu(),
Expand Down Expand Up @@ -219,4 +220,17 @@ private static Menu createSendSubMenu(ActionFactory factory,

return sendMenu;
}

private static Menu createSearchSubMenu(ActionFactory factory,
DialogService dialogService,
StateManager stateManager,
GuiPreferences preferences,
ImporterPreferences importerPreferences) {
Menu searchMenu = factory.createMenu(StandardActions.SEARCH);
searchMenu.getItems().addAll(
factory.createMenuItem(StandardActions.SEARCH_SHORTSCIENCE, new SearchShortScienceAction(dialogService, stateManager, preferences, importerPreferences)),
factory.createMenuItem(StandardActions.SEARCH_GOOGLE_SCHOLAR, new SearchGoogleScholarAction(dialogService, stateManager, preferences, importerPreferences))
);
return searchMenu;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.jabref.gui.maintable;

import java.io.IOException;
import java.util.List;

import javafx.beans.binding.BooleanExpression;

import org.jabref.gui.DialogService;
import org.jabref.gui.StateManager;
import org.jabref.gui.actions.SimpleCommand;
import org.jabref.gui.desktop.os.NativeDesktop;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.logic.importer.ImporterPreferences;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.util.ExternalLinkCreator;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.field.StandardField;

import static org.jabref.gui.actions.ActionHelper.isFieldSetForSelectedEntry;
import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected;

public class SearchGoogleScholarAction extends SimpleCommand {
private final DialogService dialogService;
private final StateManager stateManager;
private final GuiPreferences preferences;
private final ExternalLinkCreator externalLinkCreator;

public SearchGoogleScholarAction(DialogService dialogService, StateManager stateManager, GuiPreferences preferences, ImporterPreferences importerPreferences) {
this.dialogService = dialogService;
this.stateManager = stateManager;
this.preferences = preferences;
this.externalLinkCreator = new ExternalLinkCreator(importerPreferences);

BooleanExpression fieldIsSet = isFieldSetForSelectedEntry(StandardField.TITLE, stateManager);
this.executable.bind(needsEntriesSelected(1, stateManager).and(fieldIsSet));
}

@Override
public void execute() {
stateManager.getActiveDatabase().ifPresent(databaseContext -> {
final List<BibEntry> bibEntries = stateManager.getSelectedEntries();

if (bibEntries.size() != 1) {
dialogService.notify(Localization.lang("This operation requires exactly one item to be selected."));
return;
}
externalLinkCreator.getGoogleScholarSearchURL(bibEntries.getFirst()).ifPresent(url -> {
try {
NativeDesktop.openExternalViewer(databaseContext, preferences, url, StandardField.URL, dialogService, bibEntries.getFirst());
} catch (IOException ex) {
dialogService.showErrorDialogAndWait(Localization.lang("Unable to open Google Scholar."), ex);
}
});
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.jabref.gui.actions.SimpleCommand;
import org.jabref.gui.desktop.os.NativeDesktop;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.logic.importer.ImporterPreferences;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.util.ExternalLinkCreator;
import org.jabref.model.entry.BibEntry;
Expand All @@ -22,11 +23,13 @@ public class SearchShortScienceAction extends SimpleCommand {
private final DialogService dialogService;
private final StateManager stateManager;
private final GuiPreferences preferences;
private final ExternalLinkCreator externalLinkCreator;

public SearchShortScienceAction(DialogService dialogService, StateManager stateManager, GuiPreferences preferences) {
public SearchShortScienceAction(DialogService dialogService, StateManager stateManager, GuiPreferences preferences, ImporterPreferences importerPreferences) {
this.dialogService = dialogService;
this.stateManager = stateManager;
this.preferences = preferences;
this.externalLinkCreator = new ExternalLinkCreator(importerPreferences);

BooleanExpression fieldIsSet = isFieldSetForSelectedEntry(StandardField.TITLE, stateManager);
this.executable.bind(needsEntriesSelected(1, stateManager).and(fieldIsSet));
Expand All @@ -41,7 +44,7 @@ public void execute() {
dialogService.notify(Localization.lang("This operation requires exactly one item to be selected."));
return;
}
ExternalLinkCreator.getShortScienceSearchURL(bibEntries.getFirst()).ifPresent(url -> {
externalLinkCreator.getShortScienceSearchURL(bibEntries.getFirst()).ifPresent(url -> {
try {
NativeDesktop.openExternalViewer(databaseContext, preferences, url, StandardField.URL, dialogService, bibEntries.getFirst());
} catch (IOException ex) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.jabref.gui.preferences.websearch;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class SearchEngineItem {
private final StringProperty name;
private final StringProperty urlTemplate;

public SearchEngineItem(String name, String urlTemplate) {
this.name = new SimpleStringProperty(name);
this.urlTemplate = new SimpleStringProperty(urlTemplate);
}

public StringProperty nameProperty() {
return name;
}

public StringProperty urlTemplateProperty() {
return urlTemplate;
}

public String getName() {
return name.get();
}

public String getUrlTemplate() {
return urlTemplate.get();
}

public void setUrlTemplate(String urlTemplate) {
this.urlTemplate.set(urlTemplate);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ public class WebSearchTab extends AbstractPreferenceTabView<WebSearchTabViewMode
@FXML private CheckBox grobidEnabled;
@FXML private TextField grobidURL;

@FXML private TableView<SearchEngineItem> searchEngineTable;
@FXML private TableColumn<SearchEngineItem, String> searchEngineName;
@FXML private TableColumn<SearchEngineItem, String> searchEngineUrlTemplate;

@FXML private TableView<FetcherApiKey> apiKeySelectorTable;
@FXML private TableColumn<FetcherApiKey, String> apiKeyName;
@FXML private TableColumn<FetcherApiKey, String> customApiKey;
Expand Down Expand Up @@ -75,6 +79,16 @@ public String getTabName() {
public void initialize() {
this.viewModel = new WebSearchTabViewModel(preferences, dialogService, refAiEnabled);

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

searchEngineUrlTemplate.setCellValueFactory(param -> param.getValue().urlTemplateProperty());
searchEngineUrlTemplate.setCellFactory(TextFieldTableCell.forTableColumn());
searchEngineUrlTemplate.setEditable(true);

searchEngineTable.setItems(viewModel.getSearchEngines());

enableWebSearch.selectedProperty().bindBidirectional(viewModel.enableWebSearchProperty());
warnAboutDuplicatesOnImport.selectedProperty().bindBidirectional(viewModel.warnAboutDuplicatesOnImportProperty());
downloadLinkedOnlineFiles.selectedProperty().bindBidirectional(viewModel.shouldDownloadLinkedOnlineFiles());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -68,6 +69,8 @@ public class WebSearchTabViewModel implements PreferenceTabViewModel {
private final BooleanProperty apikeyPersistProperty = new SimpleBooleanProperty();
private final BooleanProperty apikeyPersistAvailableProperty = new SimpleBooleanProperty();

private final ObservableList<SearchEngineItem> searchEngines = FXCollections.observableArrayList();

private final DialogService dialogService;
private final CliPreferences preferences;
private final DOIPreferences doiPreferences;
Expand All @@ -92,6 +95,7 @@ public WebSearchTabViewModel(CliPreferences preferences, DialogService dialogSer
this.refAiEnabled = refAiEnabled;

setupPlainCitationParsers(preferences);
setupSearchEngines();
}

private void setupPlainCitationParsers(CliPreferences preferences) {
Expand Down Expand Up @@ -132,6 +136,14 @@ private void setupPlainCitationParsers(CliPreferences preferences) {
});
}

private void setupSearchEngines() {
// Add default search engines
searchEngines.addAll(
new SearchEngineItem("Google Scholar", "https://scholar.google.com/scholar?q={title}"),
new SearchEngineItem("Short Science", "https://www.shortscience.org/internalsearch?q={title}")
);
}

@Override
public void setValues() {
enableWebSearchProperty.setValue(importerPreferences.areImporterEnabled());
Expand Down Expand Up @@ -164,6 +176,13 @@ public void setValues() {
return new StudyCatalogItem(name, enabled);
})
.toList());

// Load custom URL templates from preferences if they exist
Map<String, String> savedTemplates = preferences.getImporterPreferences().getSearchEngineUrlTemplates();
if (!savedTemplates.isEmpty()) {
searchEngines.clear();
savedTemplates.forEach((name, url) -> searchEngines.add(new SearchEngineItem(name, url)));
}
}

@Override
Expand Down Expand Up @@ -196,6 +215,14 @@ public void storeSettings() {
if (apikeyPersistAvailableProperty.get()) {
preferences.getImporterPreferences().getApiKeys().addAll(apiKeys);
}

// Save custom URL templates to preferences
Map<String, String> templates = searchEngines.stream()
.collect(Collectors.toMap(
SearchEngineItem::getName,
SearchEngineItem::getUrlTemplate
));
preferences.getImporterPreferences().setSearchEngineUrlTemplates(templates);
}

public BooleanProperty enableWebSearchProperty() {
Expand Down Expand Up @@ -270,6 +297,10 @@ public IntegerProperty citationsRelationsStoreTTLProperty() {
return citationsRelationStoreTTL;
}

public ObservableList<SearchEngineItem> getSearchEngines() {
return searchEngines;
}

public void checkCustomApiKey() {
final String apiKeyName = selectedApiKeyProperty.get().getName();

Expand Down
Loading