diff --git a/CHANGELOG.md b/CHANGELOG.md index acdfd94176e..3fe0b01fb9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve ### Added +- We added new "Customization" tab to the preferences which includes option to choose a custom address for DOI access. [#7337](https://github.com/JabRef/jabref/issues/7337) - We added zbmath to the public databases from which the bibliographic information of an existing entry can be updated. [#7437](https://github.com/JabRef/jabref/issues/7437) - We added the possibility to add a new entry via its zbMath ID (zbMATH can be chosen as ID type in the "Select entry type" window). [#7202](https://github.com/JabRef/jabref/issues/7202) - We added the extension support and the external application support (For Texshow, Texmaker and LyX) to the flatpak [#7248](https://github.com/JabRef/jabref/pull/7248) diff --git a/src/main/java/org/jabref/gui/fieldeditors/IdentifierEditor.java b/src/main/java/org/jabref/gui/fieldeditors/IdentifierEditor.java index 5de21fb6f04..567d643bdfd 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/IdentifierEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/IdentifierEditor.java @@ -36,7 +36,7 @@ public IdentifierEditor(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, PreferencesService preferences) { - this.viewModel = new IdentifierEditorViewModel(field, suggestionProvider, taskExecutor, dialogService, fieldCheckers); + this.viewModel = new IdentifierEditorViewModel(field, suggestionProvider, taskExecutor, dialogService, fieldCheckers, preferences); ViewLoader.view(this) .root(this) @@ -50,6 +50,7 @@ public IdentifierEditor(Field field, new Tooltip(Localization.lang("Look up %0", field.getDisplayName()))); if (field.equals(StandardField.DOI)) { + textArea.initContextMenu(EditorMenus.getDOIMenu(textArea)); } else { textArea.initContextMenu(new DefaultMenu(textArea)); diff --git a/src/main/java/org/jabref/gui/fieldeditors/IdentifierEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/IdentifierEditorViewModel.java index 65659729cd0..775558e2c89 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/IdentifierEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/IdentifierEditorViewModel.java @@ -21,7 +21,10 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.identifier.DOI; import org.jabref.model.entry.identifier.Identifier; +import org.jabref.preferences.PreferencesService; import com.tobiasdiez.easybind.EasyBind; @@ -32,12 +35,16 @@ public class IdentifierEditorViewModel extends AbstractEditorViewModel { private final ObjectProperty> identifier = new SimpleObjectProperty<>(); private final TaskExecutor taskExecutor; private final DialogService dialogService; + private final Field field; + private PreferencesService preferences; - public IdentifierEditorViewModel(Field field, SuggestionProvider suggestionProvider, TaskExecutor taskExecutor, DialogService dialogService, FieldCheckers fieldCheckers) { + public IdentifierEditorViewModel(Field field, SuggestionProvider suggestionProvider, TaskExecutor taskExecutor, DialogService dialogService, FieldCheckers fieldCheckers, PreferencesService preferences) { super(field, suggestionProvider, fieldCheckers); this.taskExecutor = taskExecutor; this.dialogService = dialogService; + this.preferences = preferences; + this.field = field; identifier.bind( EasyBind.map(text, input -> IdentifierParser.parse(field, input)) @@ -67,6 +74,15 @@ public BooleanProperty validIdentifierIsNotPresentProperty() { } public void openExternalLink() { + if (field.equals(StandardField.DOI) && preferences.getDOIPreferences().isUseCustom()) { + String baseURI = preferences.getDOIPreferences().getDefaultBaseURI(); + openDOIWithCustomBase(baseURI); + } else { + openExternalLinkDefault(); + } + } + + public void openExternalLinkDefault() { identifier.get().flatMap(Identifier::getExternalURI).ifPresent( url -> { try { @@ -78,6 +94,18 @@ public void openExternalLink() { ); } + public void openDOIWithCustomBase(String baseURI) { + identifier.get().map(identifier -> (DOI) identifier).flatMap(doi -> doi.getExternalURIWithCustomBase(baseURI)).ifPresent( + uri -> { + try { + JabRefDesktop.openBrowser(uri); + } catch (IOException ex) { + dialogService.showErrorDialogAndWait(Localization.lang("Unable to open link."), ex); + } + } + ); + } + public boolean getIdentifierLookupInProgress() { return identifierLookupInProgress.get(); } diff --git a/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java b/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java index 19cb6e80139..ca1f1883f32 100644 --- a/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java @@ -14,6 +14,7 @@ import org.jabref.gui.JabRefFrame; import org.jabref.gui.preferences.appearance.AppearanceTab; import org.jabref.gui.preferences.citationkeypattern.CitationKeyPatternTab; +import org.jabref.gui.preferences.customization.CustomizationTab; import org.jabref.gui.preferences.entryeditor.EntryEditorTab; import org.jabref.gui.preferences.entryeditortabs.CustomEditorFieldsTab; import org.jabref.gui.preferences.exporter.ExportCustomizationTab; @@ -74,6 +75,7 @@ public PreferencesDialogViewModel(DialogService dialogService, PreferencesServic new JournalAbbreviationsTab(), new GroupsTab(), new EntryEditorTab(), + new CustomizationTab(), new CustomEditorFieldsTab(), new CitationKeyPatternTab(), new LinkedFilesTab(), diff --git a/src/main/java/org/jabref/gui/preferences/customization/CustomizationTab.fxml b/src/main/java/org/jabref/gui/preferences/customization/CustomizationTab.fxml new file mode 100644 index 00000000000..d69938526ff --- /dev/null +++ b/src/main/java/org/jabref/gui/preferences/customization/CustomizationTab.fxml @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/src/main/java/org/jabref/gui/preferences/customization/CustomizationTab.java b/src/main/java/org/jabref/gui/preferences/customization/CustomizationTab.java new file mode 100644 index 00000000000..68933ada7ac --- /dev/null +++ b/src/main/java/org/jabref/gui/preferences/customization/CustomizationTab.java @@ -0,0 +1,36 @@ +package org.jabref.gui.preferences.customization; + +import javafx.fxml.FXML; +import javafx.scene.control.CheckBox; +import javafx.scene.control.TextField; + +import org.jabref.gui.preferences.AbstractPreferenceTabView; +import org.jabref.gui.preferences.PreferencesTab; +import org.jabref.logic.l10n.Localization; + +import com.airhacks.afterburner.views.ViewLoader; + +public class CustomizationTab extends AbstractPreferenceTabView implements PreferencesTab { + + @FXML private CheckBox useCustomDOI; + @FXML private TextField useCustomDOIName; + + public CustomizationTab() { + ViewLoader.view(this) + .root(this) + .load(); + } + + @Override + public String getTabName() { + return Localization.lang("Customization"); + } + + public void initialize() { + this.viewModel = new CustomizationTabViewModel(dialogService, preferencesService); + + useCustomDOI.selectedProperty().bindBidirectional(viewModel.useCustomDOIProperty()); + useCustomDOIName.textProperty().bindBidirectional(viewModel.useCustomDOINameProperty()); + useCustomDOIName.disableProperty().bind(useCustomDOI.selectedProperty().not()); + } +} diff --git a/src/main/java/org/jabref/gui/preferences/customization/CustomizationTabViewModel.java b/src/main/java/org/jabref/gui/preferences/customization/CustomizationTabViewModel.java new file mode 100644 index 00000000000..4a8e2072aa5 --- /dev/null +++ b/src/main/java/org/jabref/gui/preferences/customization/CustomizationTabViewModel.java @@ -0,0 +1,48 @@ +package org.jabref.gui.preferences.customization; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +import org.jabref.gui.DialogService; +import org.jabref.gui.preferences.PreferenceTabViewModel; +import org.jabref.logic.preferences.DOIPreferences; +import org.jabref.preferences.PreferencesService; + +public class CustomizationTabViewModel implements PreferenceTabViewModel { + + private final BooleanProperty useCustomDOIProperty = new SimpleBooleanProperty(); + private final StringProperty useCustomDOINameProperty = new SimpleStringProperty(""); + + private final DialogService dialogService; + private final PreferencesService preferencesService; + private final DOIPreferences initialDOIPreferences; + + public CustomizationTabViewModel(DialogService dialogService, PreferencesService preferencesService) { + this.dialogService = dialogService; + this.preferencesService = preferencesService; + this.initialDOIPreferences = preferencesService.getDOIPreferences(); + } + + @Override + public void setValues() { + useCustomDOIProperty.setValue(initialDOIPreferences.isUseCustom()); + useCustomDOINameProperty.setValue(initialDOIPreferences.getDefaultBaseURI()); + } + + @Override + public void storeSettings() { + preferencesService.storeDOIPreferences(new DOIPreferences( + useCustomDOIProperty.getValue(), + useCustomDOINameProperty.getValue().trim())); + } + + public BooleanProperty useCustomDOIProperty() { + return this.useCustomDOIProperty; + } + + public StringProperty useCustomDOINameProperty() { + return this.useCustomDOINameProperty; + } +} diff --git a/src/main/java/org/jabref/gui/preferences/general/GeneralTab.java b/src/main/java/org/jabref/gui/preferences/general/GeneralTab.java index e6f0c886242..23e2fbc7fed 100644 --- a/src/main/java/org/jabref/gui/preferences/general/GeneralTab.java +++ b/src/main/java/org/jabref/gui/preferences/general/GeneralTab.java @@ -26,6 +26,7 @@ public class GeneralTab extends AbstractPreferenceTabView implements PreferencesTab { + private final ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer(); @FXML private ComboBox language; @FXML private ComboBox defaultEncoding; @FXML private ComboBox biblatexMode; @@ -42,8 +43,6 @@ public class GeneralTab extends AbstractPreferenceTabView i @FXML private CheckBox addCreationDate; @FXML private CheckBox addModificationDate; - private final ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer(); - public GeneralTab() { ViewLoader.view(this) .root(this) diff --git a/src/main/java/org/jabref/logic/preferences/DOIPreferences.java b/src/main/java/org/jabref/logic/preferences/DOIPreferences.java new file mode 100644 index 00000000000..93c0f11fa4b --- /dev/null +++ b/src/main/java/org/jabref/logic/preferences/DOIPreferences.java @@ -0,0 +1,19 @@ +package org.jabref.logic.preferences; + +public class DOIPreferences { + private final boolean useCustom; + private final String defaultBaseURI; + + public DOIPreferences(boolean useCustom, String defaultBaseURI) { + this.useCustom = useCustom; + this.defaultBaseURI = defaultBaseURI; + } + + public boolean isUseCustom() { + return useCustom; + } + + public String getDefaultBaseURI() { + return defaultBaseURI; + } +} diff --git a/src/main/java/org/jabref/model/entry/identifier/DOI.java b/src/main/java/org/jabref/model/entry/identifier/DOI.java index ad18670b4f6..a9500406903 100644 --- a/src/main/java/org/jabref/model/entry/identifier/DOI.java +++ b/src/main/java/org/jabref/model/entry/identifier/DOI.java @@ -15,8 +15,7 @@ import org.slf4j.LoggerFactory; /** - * Class for working with Digital object identifiers - * (DOIs) and Short DOIs + * Class for working with Digital object identifiers (DOIs) and Short DOIs */ public class DOI implements Identifier { @@ -24,8 +23,7 @@ public class DOI implements Identifier { private static final Logger LOGGER = LoggerFactory.getLogger(DOI.class); - // DOI/Short DOI resolver - private static final URI RESOLVER = URI.create("https://doi.org"); + private static final URI RESOLVER = URI.create("https://doi.org/"); // Regex // (see http://www.doi.org/doi_handbook/2_Numbering.html) @@ -147,8 +145,7 @@ public DOI(String doi) { /** * Creates an Optional<DOI> from various schemes including URL, URN, and plain DOIs. *

- * Useful for suppressing the IllegalArgumentException of the Constructor and checking for - * Optional.isPresent() instead. + * Useful for suppressing the IllegalArgumentException of the Constructor and checking for Optional.isPresent() instead. * * @param doi the DOI/Short DOI string * @return an Optional containing the DOI or an empty Optional @@ -233,8 +230,16 @@ public boolean isShortDoi() { */ @Override public Optional getExternalURI() { + return getExternalURIFromBase(RESOLVER); + } + + public Optional getExternalURIWithCustomBase(String customBase) { + return getExternalURIFromBase(URI.create(customBase)); + } + + private Optional getExternalURIFromBase(URI base) { try { - URI uri = new URI(RESOLVER.getScheme(), RESOLVER.getHost(), "/" + doi, null); + URI uri = new URI(base.getScheme(), base.getHost(), "/" + doi, null); return Optional.of(uri); } catch (URISyntaxException e) { // should never happen diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index ef5513797e9..b2d676ad1f9 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -87,6 +87,7 @@ import org.jabref.logic.net.ProxyPreferences; import org.jabref.logic.openoffice.OpenOfficePreferences; import org.jabref.logic.openoffice.StyleLoader; +import org.jabref.logic.preferences.DOIPreferences; import org.jabref.logic.preferences.OwnerPreferences; import org.jabref.logic.preferences.TimestampPreferences; import org.jabref.logic.preview.PreviewLayout; @@ -201,6 +202,9 @@ public class JabRefPreferences implements PreferencesService { public static final String SHOW_ADVANCED_HINTS = "showAdvancedHints"; public static final String DEFAULT_ENCODING = "defaultEncoding"; + public static final String BASE_DOI_URI = "baseDOIURI"; + public static final String USE_CUSTOM_DOI_URI = "useCustomDOIURI"; + public static final String USE_OWNER = "useOwner"; public static final String DEFAULT_OWNER = "defaultOwner"; public static final String OVERWRITE_OWNER = "overwriteOwner"; @@ -445,6 +449,9 @@ private JabRefPreferences() { // Set DOI to be the default ID entry generator defaults.put(ID_ENTRY_GENERATOR, DoiFetcher.NAME); + defaults.put(USE_CUSTOM_DOI_URI, Boolean.FALSE); + defaults.put(BASE_DOI_URI, "https://doi.org"); + if (OS.OS_X) { defaults.put(FONT_FAMILY, "SansSerif"); defaults.put(PUSH_EMACS_PATH, "emacsclient"); @@ -1361,6 +1368,19 @@ public void storeTelemetryPreferences(TelemetryPreferences preferences) { putBoolean(ALREADY_ASKED_TO_COLLECT_TELEMETRY, !preferences.shouldAskToCollectTelemetry()); // mind the ! } + @Override + public DOIPreferences getDOIPreferences() { + return new DOIPreferences( + getBoolean(USE_CUSTOM_DOI_URI), + get(BASE_DOI_URI)); + } + + @Override + public void storeDOIPreferences(DOIPreferences preferences) { + putBoolean(USE_CUSTOM_DOI_URI, preferences.isUseCustom()); + put(BASE_DOI_URI, preferences.getDefaultBaseURI()); + } + @Override public OwnerPreferences getOwnerPreferences() { return new OwnerPreferences( diff --git a/src/main/java/org/jabref/preferences/PreferencesService.java b/src/main/java/org/jabref/preferences/PreferencesService.java index 11b97e26f0c..eddd506dc45 100644 --- a/src/main/java/org/jabref/preferences/PreferencesService.java +++ b/src/main/java/org/jabref/preferences/PreferencesService.java @@ -36,6 +36,7 @@ import org.jabref.logic.layout.format.NameFormatterPreferences; import org.jabref.logic.net.ProxyPreferences; import org.jabref.logic.openoffice.OpenOfficePreferences; +import org.jabref.logic.preferences.DOIPreferences; import org.jabref.logic.preferences.OwnerPreferences; import org.jabref.logic.preferences.TimestampPreferences; import org.jabref.logic.protectedterms.ProtectedTermsPreferences; @@ -144,6 +145,10 @@ public interface PreferencesService { void storeTelemetryPreferences(TelemetryPreferences preferences); + DOIPreferences getDOIPreferences(); + + void storeDOIPreferences(DOIPreferences preferences); + OwnerPreferences getOwnerPreferences(); void storeOwnerPreferences(OwnerPreferences preferences); diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 2df2bb3322b..a42bcf6ec43 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2288,3 +2288,7 @@ Separate\ citations=Separate citations Unprotect\ terms=Unprotect terms Error\ connecting\ to\ Writer\ document=Error connecting to Writer document You\ need\ to\ open\ Writer\ with\ a\ document\ before\ connecting=You need to open Writer with a document before connecting + +Custom\ DOI\ URI=Custom DOI URI +Customization=Customization +Use\ custom\ DOI\ base\ URI\ for\ article\ access=Use custom DOI base URI for article access diff --git a/src/test/java/org/jabref/gui/fieldeditors/IdentifierEditorViewModelTest.java b/src/test/java/org/jabref/gui/fieldeditors/IdentifierEditorViewModelTest.java index d9bc547941c..ca71d8a268f 100644 --- a/src/test/java/org/jabref/gui/fieldeditors/IdentifierEditorViewModelTest.java +++ b/src/test/java/org/jabref/gui/fieldeditors/IdentifierEditorViewModelTest.java @@ -5,6 +5,7 @@ import org.jabref.gui.util.CurrentThreadTaskExecutor; import org.jabref.logic.integrity.FieldCheckers; import org.jabref.model.entry.field.StandardField; +import org.jabref.preferences.PreferencesService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -18,7 +19,7 @@ class IdentifierEditorViewModelTest { @BeforeEach void setUp() throws Exception { - viewModel = new IdentifierEditorViewModel(StandardField.DOI, new EmptySuggestionProvider(), new CurrentThreadTaskExecutor(), mock(DialogService.class), mock(FieldCheckers.class)); + viewModel = new IdentifierEditorViewModel(StandardField.DOI, new EmptySuggestionProvider(), new CurrentThreadTaskExecutor(), mock(DialogService.class), mock(FieldCheckers.class), mock(PreferencesService.class)); } @Test