From a4e5ad4fbe6769ef783af832604974e241b26b07 Mon Sep 17 00:00:00 2001 From: Johannes Hupe Date: Fri, 8 Nov 2019 12:19:42 +0100 Subject: [PATCH 001/201] add paging support (#5518) * added paging interface * use collection instead of list * added default page size and documentation * removes interfaces from expected class list * Added paging support for ADS fetcher * removes search by page * make page immutable * added test case for invalid authors --- .../importer/PagedSearchBasedFetcher.java | 26 +++++++++++++ .../PagedSearchBasedParserFetcher.java | 16 ++++++++ .../logic/importer/SearchBasedFetcher.java | 4 ++ .../fetcher/AstrophysicsDataSystem.java | 32 +++++++++++++++- .../java/org/jabref/model/paging/Page.java | 37 +++++++++++++++++++ .../logic/importer/WebFetchersTest.java | 2 + .../fetcher/AstrophysicsDataSystemTest.java | 24 ++++++++++++ 7 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/jabref/logic/importer/PagedSearchBasedFetcher.java create mode 100644 src/main/java/org/jabref/logic/importer/PagedSearchBasedParserFetcher.java create mode 100644 src/main/java/org/jabref/model/paging/Page.java diff --git a/src/main/java/org/jabref/logic/importer/PagedSearchBasedFetcher.java b/src/main/java/org/jabref/logic/importer/PagedSearchBasedFetcher.java new file mode 100644 index 00000000000..9e173d4f829 --- /dev/null +++ b/src/main/java/org/jabref/logic/importer/PagedSearchBasedFetcher.java @@ -0,0 +1,26 @@ +package org.jabref.logic.importer; + +import org.jabref.model.entry.BibEntry; +import org.jabref.model.paging.Page; + +public interface PagedSearchBasedFetcher extends SearchBasedFetcher { + + /** + * @param query search query send to endpoint + * @param pageNumber requested site number + * @return Page with search results + */ + Page performSearchPaged(String query, int pageNumber) throws FetcherException; + + /** + * @return default pageSize + */ + default int getPageSize() { + return 20; + } + + @Override + default boolean supportsPaging() { + return true; + } +} diff --git a/src/main/java/org/jabref/logic/importer/PagedSearchBasedParserFetcher.java b/src/main/java/org/jabref/logic/importer/PagedSearchBasedParserFetcher.java new file mode 100644 index 00000000000..d825d82c013 --- /dev/null +++ b/src/main/java/org/jabref/logic/importer/PagedSearchBasedParserFetcher.java @@ -0,0 +1,16 @@ +package org.jabref.logic.importer; + +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; + +public interface PagedSearchBasedParserFetcher extends SearchBasedParserFetcher, PagedSearchBasedFetcher { + + /** + * Constructs a URL based on the query, size and page number. + * @param query the search query + * @param size the size of the page + * @param pageNumber the number of the page + * */ + URL getURLForQuery(String query, int size, int pageNumber) throws URISyntaxException, MalformedURLException, FetcherException; +} diff --git a/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java b/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java index 7c0e3f78bbf..a930f079d50 100644 --- a/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java +++ b/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java @@ -17,4 +17,8 @@ public interface SearchBasedFetcher extends WebFetcher { * @return a list of {@link BibEntry}, which are matched by the query (may be empty) */ List performSearch(String query) throws FetcherException; + + default boolean supportsPaging() { + return false; + } } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java b/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java index fcf2750dd79..f0e5c948dc1 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java @@ -22,15 +22,16 @@ import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.IdBasedParserFetcher; import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.PagedSearchBasedParserFetcher; import org.jabref.logic.importer.ParseException; import org.jabref.logic.importer.Parser; -import org.jabref.logic.importer.SearchBasedParserFetcher; import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.logic.net.URLDownload; import org.jabref.model.cleanup.FieldFormatterCleanup; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.field.UnknownField; +import org.jabref.model.paging.Page; import org.jabref.model.strings.StringUtil; import org.jabref.model.util.DummyFileUpdateMonitor; @@ -42,7 +43,7 @@ /** * Fetches data from the SAO/NASA Astrophysics Data System (https://ui.adsabs.harvard.edu/) */ -public class AstrophysicsDataSystem implements IdBasedParserFetcher, SearchBasedParserFetcher, EntryBasedParserFetcher { +public class AstrophysicsDataSystem implements IdBasedParserFetcher, PagedSearchBasedParserFetcher, EntryBasedParserFetcher { private static final String API_SEARCH_URL = "https://api.adsabs.harvard.edu/v1/search/query"; private static final String API_EXPORT_URL = "https://api.adsabs.harvard.edu/v1/export/bibtexabs"; @@ -87,6 +88,16 @@ public URL getURLForQuery(String query) throws URISyntaxException, MalformedURLE return builder.build().toURL(); } + @Override + public URL getURLForQuery(String query, int size, int pageNumber) throws URISyntaxException, MalformedURLException, FetcherException { + URIBuilder builder = new URIBuilder(API_SEARCH_URL); + builder.addParameter("q", query); + builder.addParameter("fl", "bibcode"); + builder.addParameter("rows", String.valueOf(size)); + builder.addParameter("start", String.valueOf(size * pageNumber)); + return builder.build().toURL(); + } + /** * @param entry BibEntry for which a search URL is created * @return URL which points to a search request for given entry @@ -276,4 +287,21 @@ private List performSearchByIds(Collection identifiers) throws throw new FetcherException("An internal parser error occurred", e); } } + + @Override + public Page performSearchPaged(String query, int pageNumber) throws FetcherException { + + if (StringUtil.isBlank(query)) { + return new Page<>(query, pageNumber); + } + try { + List bibcodes = fetchBibcodes(getURLForQuery(query, getPageSize(), pageNumber)); + Collection results = performSearchByIds(bibcodes); + return new Page<>(query, pageNumber, results); + } catch (URISyntaxException e) { + throw new FetcherException("Search URI is malformed", e); + } catch (IOException e) { + throw new FetcherException("A network error occurred", e); + } + } } diff --git a/src/main/java/org/jabref/model/paging/Page.java b/src/main/java/org/jabref/model/paging/Page.java new file mode 100644 index 00000000000..c65bd6f3f74 --- /dev/null +++ b/src/main/java/org/jabref/model/paging/Page.java @@ -0,0 +1,37 @@ +package org.jabref.model.paging; + +import java.util.Collection; +import java.util.Collections; + +public class Page { + + private int pageNumber; + private String query; + private Collection content; + + public Page(String query, int pageNumber, Collection content) { + this.query = query; + this.pageNumber = pageNumber; + this.content = Collections.unmodifiableCollection(content); + } + + public Page(String query, int pageNumber) { + this(query, pageNumber, Collections.emptyList()); + } + + public Collection getContent() { + return content; + } + + public int getPageNumber() { + return pageNumber; + } + + public String getQuery() { + return query; + } + + public int getSize() { + return content.size(); + } +} diff --git a/src/test/java/org/jabref/logic/importer/WebFetchersTest.java b/src/test/java/org/jabref/logic/importer/WebFetchersTest.java index b5d93a2be54..03cdc538f35 100644 --- a/src/test/java/org/jabref/logic/importer/WebFetchersTest.java +++ b/src/test/java/org/jabref/logic/importer/WebFetchersTest.java @@ -70,6 +70,8 @@ void getSearchBasedFetchersReturnsAllFetcherDerivingFromSearchBasedFetcher() thr Set> expected = controlClasses.loadClasses().stream().collect(Collectors.toSet()); expected.remove(SearchBasedParserFetcher.class); + expected.remove(PagedSearchBasedParserFetcher.class); + expected.remove(PagedSearchBasedFetcher.class); assertEquals(expected, getClasses(searchBasedFetchers)); } } diff --git a/src/test/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystemTest.java b/src/test/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystemTest.java index f3141019524..2cff0a2dad7 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystemTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystemTest.java @@ -8,6 +8,7 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.model.paging.Page; import org.jabref.testutils.category.FetcherTest; import org.junit.jupiter.api.BeforeEach; @@ -15,6 +16,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -198,4 +200,26 @@ public void testPerformSearchByLuceyPaulEntry() throws Exception { Optional fetchedEntry = fetcher.performSearchById("2000JGR...10520297L"); assertEquals(Optional.of(luceyPaulEntry), fetchedEntry); } + + @Test + public void performSearchByQueryPaged_searchLimitsSize() throws Exception { + Page page = fetcher.performSearchPaged("author:\"A\"", 0); + assertEquals(fetcher.getPageSize(), page.getSize(), "fetcher return wrong page size"); + } + + @Test + public void performSearchByQueryPaged_invalidAuthorsReturnEmptyPages() throws Exception { + Page page = fetcher.performSearchPaged("author:\"ThisAuthorWillNotBeFound\"", 0); + Page page5 = fetcher.performSearchPaged("author:\"ThisAuthorWillNotBeFound\"", 5); + assertEquals(0, page.getSize(), "fetcher doesnt return empty pages for invalid author"); + assertEquals(0, page5.getSize(), "fetcher doesnt return empty pages for invalid author"); + } + + @Test + public void performSearchByQueryPaged_twoPagesNotEqual() throws Exception { + Page page = fetcher.performSearchPaged("author:\"A\"", 0); + Page page2 = fetcher.performSearchPaged("author:\"A\"", 1); + // This tests if the fetcher actually performs paging + assertNotEquals(page.getContent(), page2.getContent(), "Two consecutive pages shouldn't be equal"); + } } From 2483e2f9af6f5b441056f506fec1f3ea9adb3874 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 18 Oct 2020 18:18:11 +0200 Subject: [PATCH 002/201] Add missing author --- .mailmap | 1 + AUTHORS | 1 + 2 files changed, 2 insertions(+) diff --git a/.mailmap b/.mailmap index 227cd3908e9..56af5eb0831 100644 --- a/.mailmap +++ b/.mailmap @@ -213,3 +213,4 @@ Muhammad Arsalan Badar ZhouSky <11711923@mail.sustech.edu.cn> Vincent Gagnon Tom Warnke +Eric Lau <919023+skeric@users.noreply.github.com> diff --git a/AUTHORS b/AUTHORS index 48da52cdc5b..f0aa26c6095 100644 --- a/AUTHORS +++ b/AUTHORS @@ -163,6 +163,7 @@ Igor Chernyavsky Igor Steinmacher Illes Solt Ingvar Jackal +Isaac Roles Jackson Ryan Jan Frederik Maas Jan Kubovy From d26a485ced69fa88a7373858e76cd0d6d2e7ce11 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 18 Oct 2020 18:33:12 +0200 Subject: [PATCH 003/201] Refine information on AUTHORS --- .github/workflows/tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7b376dcdff9..8d4a7ee6737 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -256,5 +256,6 @@ jobs: echo echo "In case you want to use a different one, please comment here and adjust your name in your git configuration for future commits" echo - echo "Background: Information about the AUTHORS file can be found at https://github.com/JabRef/jabref/blob/master/CONTRIBUTING.md#author-credits" + echo "Just adding yourself into the AUHTORS file does not help as it is overwritten by our script ./scripts/generate-authors." + echo "Read more on the AUTHORS file at found at https://github.com/JabRef/jabref/blob/master/CONTRIBUTING.md#author-credits" exit 1 From 5625da7eafbe40714279c36c59b0461c575b485a Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 18 Oct 2020 18:46:50 +0200 Subject: [PATCH 004/201] Journal lists need a manual update only --- .github/workflows/refresh-journal-lists.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/refresh-journal-lists.yml b/.github/workflows/refresh-journal-lists.yml index bb39672115b..414d195771e 100644 --- a/.github/workflows/refresh-journal-lists.yml +++ b/.github/workflows/refresh-journal-lists.yml @@ -1,9 +1,6 @@ name: Refresh Journal Lists on: - schedule: - # run on 1st and 15th of each month - - cron: '2 20 1,15 * *' workflow_dispatch: # Allow to run manually From 4213a8d1e0a8832acb4c346fa0d16f5750b21986 Mon Sep 17 00:00:00 2001 From: "Martin W. Kirst" Date: Sun, 18 Oct 2020 18:47:52 +0200 Subject: [PATCH 005/201] Improve error handling on GROBID server connection issues (#7026) * add explicit configuration for connectTimeout in URLDownload add information on GROBID server connection issue for user relates to #6517 and #6891 * add CHANGELOG entry * fix style issues detected by checkstyle * use .toMilliseconds() is more convenient use assertThrow is more convenient * incorporate feedback from review reduce detail level from user message rework to use FetcherException instead of UncheckedIOException * switch to debug level in order to reduce verboseness in the log --- CHANGELOG.md | 1 + .../BibtexExtractorViewModel.java | 15 ++++++ .../fetcher/GrobidCitationFetcher.java | 46 +++++++++++++------ .../logic/importer/util/GrobidService.java | 2 + .../org/jabref/logic/net/URLDownload.java | 16 ++++++- src/main/resources/l10n/JabRef_de.properties | 1 + src/main/resources/l10n/JabRef_en.properties | 1 + .../fetcher/GrobidCitationFetcherTest.java | 25 ++++++++-- .../org/jabref/logic/net/URLDownloadTest.java | 8 ++++ 9 files changed, 97 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c26f8942fc..be408c6c365 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://git - We improved the duplicate detection when identifiers like DOI or arxiv are semantiaclly the same, but just syntactically differ (e.g. with or without http(s):// prefix). [#6707](https://github.com/JabRef/jabref/issues/6707) - We changed in the group interface "Generate groups from keywords in a BibTeX field" by "Generate groups from keywords in the following field". [#6983](https://github.com/JabRef/jabref/issues/6983) - We changed the name of a group type from "Searching for keywords" to "Searching for a keyword". [6995](https://github.com/JabRef/jabref/pull/6995) +- We changed connect timeouts for server requests to 30 seconds in general and 5 seconds for GROBID server (special) and improved user notifications on connection issues. [7026](https://github.com/JabRef/jabref/pull/7026) ### Fixed diff --git a/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java b/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java index a7181ba3ae1..f1d99d74331 100644 --- a/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java +++ b/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java @@ -15,6 +15,7 @@ import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.fetcher.GrobidCitationFetcher; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; @@ -22,8 +23,13 @@ import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.JabRefPreferences; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class BibtexExtractorViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(BibtexExtractorViewModel.class); + private final StringProperty inputTextProperty = new SimpleStringProperty(""); private DialogService dialogService; private GrobidCitationFetcher currentCitationfetcher; @@ -58,6 +64,15 @@ public StringProperty inputTextProperty() { public void startParsing() { BackgroundTask.wrap(() -> currentCitationfetcher.performSearch(inputTextProperty.getValue())) .onRunning(() -> dialogService.notify(Localization.lang("Your text is being parsed..."))) + .onFailure((e) -> { + if (e instanceof FetcherException) { + String msg = Localization.lang("There are connection issues with a JabRef server. Detailed information: %0.", + e.getMessage()); + dialogService.notify(msg); + } else { + LOGGER.warn("Missing exception handling.", e); + } + }) .onSuccess(parsedEntries -> { dialogService.notify(Localization.lang("%0 entries were parsed from your query.", String.valueOf(parsedEntries.size()))); importHandler.importEntries(parsedEntries); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/GrobidCitationFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/GrobidCitationFetcher.java index 39d118c6df5..7a57535efe9 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/GrobidCitationFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/GrobidCitationFetcher.java @@ -1,11 +1,13 @@ package org.jabref.logic.importer.fetcher; import java.io.IOException; +import java.net.SocketTimeoutException; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; +import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ParseException; import org.jabref.logic.importer.SearchBasedFetcher; @@ -20,13 +22,18 @@ public class GrobidCitationFetcher implements SearchBasedFetcher { private static final Logger LOGGER = LoggerFactory.getLogger(GrobidCitationFetcher.class); + private static final String GROBID_URL = "http://grobid.jabref.org:8070"; private ImportFormatPreferences importFormatPreferences; private GrobidService grobidService; public GrobidCitationFetcher(ImportFormatPreferences importFormatPreferences) { + this(importFormatPreferences, new GrobidService(GROBID_URL)); + } + + GrobidCitationFetcher(ImportFormatPreferences importFormatPreferences, GrobidService grobidService) { this.importFormatPreferences = importFormatPreferences; - this.grobidService = new GrobidService(GROBID_URL); + this.grobidService = grobidService; } /** @@ -38,9 +45,14 @@ public GrobidCitationFetcher(ImportFormatPreferences importFormatPreferences) { private Optional parseUsingGrobid(String plainText) { try { return Optional.of(grobidService.processCitation(plainText, GrobidService.ConsolidateCitations.WITH_METADATA)); + } catch (SocketTimeoutException e) { + String msg = "Connection timed out."; + LOGGER.debug(msg, e); + throw new RuntimeException(msg, e); } catch (IOException e) { - LOGGER.debug("Could not process citation", e); - return Optional.empty(); + String msg = "Could not process citation. " + e.getMessage(); + LOGGER.debug(msg, e); + throw new RuntimeException(msg, e); } } @@ -54,20 +66,28 @@ private Optional parseBibToBibEntry(String bibtexString) { } @Override - public List performSearch(String query) { - return Arrays - .stream(query.split("\\r\\r+|\\n\\n+|\\r\\n(\\r\\n)+")) - .map(String::trim) - .filter(str -> !str.isBlank()) - .map(reference -> parseUsingGrobid(reference)) - .flatMap(Optional::stream) - .map(reference -> parseBibToBibEntry(reference)) - .flatMap(Optional::stream) - .collect(Collectors.toList()); + public List performSearch(String query) throws FetcherException { + List bibEntries = null; + try { + bibEntries = Arrays + .stream(query.split("\\r\\r+|\\n\\n+|\\r\\n(\\r\\n)+")) + .map(String::trim) + .filter(str -> !str.isBlank()) + .map(this::parseUsingGrobid) + .flatMap(Optional::stream) + .map(this::parseBibToBibEntry) + .flatMap(Optional::stream) + .collect(Collectors.toList()); + } catch (RuntimeException e) { + // un-wrap the wrapped exceptions + throw new FetcherException(e.getMessage(), e.getCause()); + } + return bibEntries; } @Override public String getName() { return "GROBID"; } + } diff --git a/src/main/java/org/jabref/logic/importer/util/GrobidService.java b/src/main/java/org/jabref/logic/importer/util/GrobidService.java index 4e3bc027c81..ec9b7567fc0 100644 --- a/src/main/java/org/jabref/logic/importer/util/GrobidService.java +++ b/src/main/java/org/jabref/logic/importer/util/GrobidService.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.time.Duration; import org.jabref.logic.net.URLDownload; @@ -47,6 +48,7 @@ public String processCitation(String rawCitation, ConsolidateCitations consolida rawCitation = URLEncoder.encode(rawCitation, StandardCharsets.UTF_8); URLDownload urlDownload = new URLDownload(grobidServerURL + "/api/processCitation"); + urlDownload.setConnectTimeout(Duration.ofSeconds(5)); urlDownload.addHeader("Accept", MediaTypes.APPLICATION_BIBTEX); urlDownload.setPostData("citations=" + rawCitation + "&consolidateCitations=" + consolidateCitations); String httpResponse = urlDownload.asString(); diff --git a/src/main/java/org/jabref/logic/net/URLDownload.java b/src/main/java/org/jabref/logic/net/URLDownload.java index eec18e1756a..99887c7ac93 100644 --- a/src/main/java/org/jabref/logic/net/URLDownload.java +++ b/src/main/java/org/jabref/logic/net/URLDownload.java @@ -26,6 +26,7 @@ import java.nio.file.StandardCopyOption; import java.security.SecureRandom; import java.security.cert.X509Certificate; +import java.time.Duration; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -62,11 +63,13 @@ public class URLDownload { public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0"; - private static final Logger LOGGER = LoggerFactory.getLogger(URLDownload.class); + private static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofSeconds(30); + private final URL source; private final Map parameters = new HashMap<>(); private String postData = ""; + private Duration connectTimeout = DEFAULT_CONNECT_TIMEOUT; /** * @param source the URL to download from @@ -316,6 +319,7 @@ private void copy(InputStream in, Writer out, Charset encoding) throws IOExcepti private URLConnection openConnection() throws IOException { URLConnection connection = this.source.openConnection(); + connection.setConnectTimeout((int) connectTimeout.toMillis()); for (Entry entry : this.parameters.entrySet()) { connection.setRequestProperty(entry.getKey(), entry.getValue()); } @@ -346,4 +350,14 @@ private URLConnection openConnection() throws IOException { return connection; } + + public void setConnectTimeout(Duration connectTimeout) { + if (connectTimeout != null) { + this.connectTimeout = connectTimeout; + } + } + + public Duration getConnectTimeout() { + return connectTimeout; + } } diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index 1cb9fda8946..9f697461486 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -1637,6 +1637,7 @@ User=Benutzer Connect=Verbinden Connection\ error=Verbindungsfehler Connection\ to\ %0\ server\ established.=Verbindung zum %0 Server hergestellt. +There\ are\ connection\ issues\ with\ a\ JabRef\ server.\ Detailed\ information\:\ %0.=Es gibt Verbindungsprobleme mit einem JabRef Server. Detailinformation: %0. Required\ field\ "%0"\ is\ empty.=Erforederliches Feld "%0" ist leer. %0\ driver\ not\ available.=%0-Treiber nicht verfügbar. The\ connection\ to\ the\ server\ has\ been\ terminated.=Verbindung zum Server wurde abgebrochen. diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 2d2bacc4113..47a7b78e9d8 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -1631,6 +1631,7 @@ User=User Connect=Connect Connection\ error=Connection error Connection\ to\ %0\ server\ established.=Connection to %0 server established. +There\ are\ connection\ issues\ with\ a\ JabRef\ server.\ Detailed\ information\:\ %0.=There are connection issues with a JabRef server. Detailed information: %0. Required\ field\ "%0"\ is\ empty.=Required field "%0" is empty. %0\ driver\ not\ available.=%0 driver not available. The\ connection\ to\ the\ server\ has\ been\ terminated.=The connection to the server has been terminated. diff --git a/src/test/java/org/jabref/logic/importer/fetcher/GrobidCitationFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/GrobidCitationFetcherTest.java index d223dca22a5..329748dcd56 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/GrobidCitationFetcherTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/GrobidCitationFetcherTest.java @@ -1,10 +1,13 @@ package org.jabref.logic.importer.fetcher; +import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.stream.Stream; +import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.util.GrobidService; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.StandardEntryType; @@ -17,7 +20,11 @@ import org.mockito.Answers; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @FetcherTest public class GrobidCitationFetcherTest { @@ -76,28 +83,38 @@ public static Stream provideInvalidInput() { @ParameterizedTest(name = "{0}") @MethodSource("provideExamplesForCorrectResultTest") - public void grobidPerformSearchCorrectResultTest(String testName, BibEntry expectedBibEntry, String searchQuery) { + public void grobidPerformSearchCorrectResultTest(String testName, BibEntry expectedBibEntry, String searchQuery) throws FetcherException { List entries = grobidCitationFetcher.performSearch(searchQuery); assertEquals(List.of(expectedBibEntry), entries); } @Test - public void grobidPerformSearchCorrectlySplitsStringTest() { + public void grobidPerformSearchCorrectlySplitsStringTest() throws FetcherException { List entries = grobidCitationFetcher.performSearch(example1 + "\n\n" + example2 + "\r\n\r\n" + example3 + "\r\r" + example4); assertEquals(List.of(example1AsBibEntry, example2AsBibEntry, example3AsBibEntry, example4AsBibEntry), entries); } @Test - public void grobidPerformSearchWithEmptyStringsTest() { + public void grobidPerformSearchWithEmptyStringsTest() throws FetcherException { List entries = grobidCitationFetcher.performSearch(" \n "); assertEquals(Collections.emptyList(), entries); } @ParameterizedTest @MethodSource("provideInvalidInput") - public void grobidPerformSearchWithInvalidDataTest(String invalidInput) { + public void grobidPerformSearchWithInvalidDataTest(String invalidInput) throws FetcherException { List entries = grobidCitationFetcher.performSearch(invalidInput); assertEquals(Collections.emptyList(), entries); } + @Test + public void performSearchThrowsExceptionInCaseOfConnectionIssues() throws IOException { + GrobidService grobidServiceMock = mock(GrobidService.class); + when(grobidServiceMock.processCitation(anyString(), any())).thenThrow(new IOException("Any IO Exception")); + grobidCitationFetcher = new GrobidCitationFetcher(importFormatPreferences, grobidServiceMock); + + assertThrows(FetcherException.class, () -> { + grobidCitationFetcher.performSearch("any text"); + }, "performSearch should throw an FetcherException, when there are underlying IOException."); + } } diff --git a/src/test/java/org/jabref/logic/net/URLDownloadTest.java b/src/test/java/org/jabref/logic/net/URLDownloadTest.java index f77f0fc2431..ea0f133c10f 100644 --- a/src/test/java/org/jabref/logic/net/URLDownloadTest.java +++ b/src/test/java/org/jabref/logic/net/URLDownloadTest.java @@ -109,4 +109,12 @@ public void testCheckConnectionFail() throws MalformedURLException { assertThrows(UnirestException.class, nonsense::canBeReached); } + @Test + public void connectTimeoutIsNeverNull() throws MalformedURLException { + URLDownload urlDownload = new URLDownload(new URL("http://www.example.com")); + assertNotNull(urlDownload.getConnectTimeout(), "there's a non-null default by the constructor"); + + urlDownload.setConnectTimeout(null); + assertNotNull(urlDownload.getConnectTimeout(), "no null value can be set"); + } } From 1ff88cb07a6b31b12268d5901c3ad1e689ab597b Mon Sep 17 00:00:00 2001 From: Benjamin Schroth <68321970+schrothbn@users.noreply.github.com> Date: Sun, 18 Oct 2020 20:20:53 +0200 Subject: [PATCH 006/201] Removed menu entry for 'Manage external file types' (#6990) (#7009) --- CHANGELOG.md | 2 ++ src/main/java/org/jabref/gui/JabRefFrame.java | 2 -- src/main/java/org/jabref/gui/actions/StandardActions.java | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be408c6c365..ec99d8c14d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,8 @@ inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://git ### Removed +- We removed the menu entry "Manage external file types" because it's already in 'Preferences' dialog [#6991](https://github.com/JabRef/jabref/issues/6991) + ## [5.1] – 2020-08-30 ### Added diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index e335117a6e2..c5f54e82dda 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -81,7 +81,6 @@ import org.jabref.gui.externalfiles.AutoLinkFilesAction; import org.jabref.gui.externalfiles.DownloadFullTextAction; import org.jabref.gui.externalfiles.FindUnlinkedFilesAction; -import org.jabref.gui.externalfiletype.EditExternalFileTypesAction; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.help.AboutAction; import org.jabref.gui.help.ErrorConsoleAction; @@ -909,7 +908,6 @@ private MenuBar createMenu() { factory.createMenuItem(StandardActions.SETUP_GENERAL_FIELDS, new SetupGeneralFieldsAction()), factory.createMenuItem(StandardActions.MANAGE_CUSTOM_IMPORTS, new ManageCustomImportsAction()), factory.createMenuItem(StandardActions.MANAGE_CUSTOM_EXPORTS, new ManageCustomExportsAction()), - factory.createMenuItem(StandardActions.MANAGE_EXTERNAL_FILETYPES, new EditExternalFileTypesAction()), factory.createMenuItem(StandardActions.MANAGE_JOURNALS, new ManageJournalsAction()), factory.createMenuItem(StandardActions.CUSTOMIZE_KEYBINDING, new CustomizeKeyBindingAction()), factory.createMenuItem(StandardActions.MANAGE_PROTECTED_TERMS, new ManageProtectedTermsAction()), diff --git a/src/main/java/org/jabref/gui/actions/StandardActions.java b/src/main/java/org/jabref/gui/actions/StandardActions.java index e3880399355..2b316dca80a 100644 --- a/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -104,7 +104,6 @@ public enum StandardActions implements Action { MANAGE_CUSTOM_IMPORTS(Localization.lang("Manage custom imports")), CUSTOMIZE_ENTRY_TYPES(Localization.lang("Customize entry types")), SETUP_GENERAL_FIELDS(Localization.lang("Set up general fields")), - MANAGE_EXTERNAL_FILETYPES(Localization.lang("Manage external file types")), MANAGE_PROTECTED_TERMS(Localization.lang("Manage protected terms")), CITATION_KEY_PATTERN(Localization.lang("Citation key patterns")), SHOW_PREFS(Localization.lang("Preferences")), From 07bc54ec81e6474e4666c206e44104b80e185912 Mon Sep 17 00:00:00 2001 From: Niffler Date: Sun, 18 Oct 2020 20:27:38 +0200 Subject: [PATCH 007/201] Add selection of 'All Entries' group on start-up (#7014) --- CHANGELOG.md | 1 + src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java | 4 ++++ .../java/org/jabref/gui/groups/GroupTreeViewModelTest.java | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec99d8c14d6..2584672543a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://git - We fixed an issue where spaces and newlines in an isbn would generate an exception. [#6456](https://github.com/JabRef/jabref/issues/6456) - We fixed an issue where identity column header had incorrect foreground color in the Dark theme. [#6796](https://github.com/JabRef/jabref/issues/6796) - We fixed an issue where clicking on Collapse All button in the Search for Unlinked Local Files expanded the directory structure erroneously [#6848](https://github.com/JabRef/jabref/issues/6848) +- We fixed an issue where the JabRef GUI does not highlight the "All entries" group on start-up [#6691](https://github.com/JabRef/jabref/issues/6691) ### Removed diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java index a5d51cc7328..23a9619f968 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java @@ -1,5 +1,6 @@ package org.jabref.gui.groups; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; @@ -129,6 +130,9 @@ private void onActiveDatabaseChanged(Optional newDatabase) { .orElse(GroupNodeViewModel.getAllEntriesGroup(newDatabase.get(), stateManager, taskExecutor, localDragboard, preferences)); rootGroup.setValue(newRoot); + if (stateManager.getSelectedGroup(newDatabase.get()).isEmpty()) { + stateManager.setSelectedGroups(newDatabase.get(), Collections.singletonList(newRoot.getGroupNode())); + } selectedGroups.setAll( stateManager.getSelectedGroup(newDatabase.get()).stream() .map(selectedGroup -> new GroupNodeViewModel(newDatabase.get(), stateManager, taskExecutor, selectedGroup, localDragboard, preferences)) diff --git a/src/test/java/org/jabref/gui/groups/GroupTreeViewModelTest.java b/src/test/java/org/jabref/gui/groups/GroupTreeViewModelTest.java index 0cbf15a1bde..f90c5fdf360 100644 --- a/src/test/java/org/jabref/gui/groups/GroupTreeViewModelTest.java +++ b/src/test/java/org/jabref/gui/groups/GroupTreeViewModelTest.java @@ -43,6 +43,11 @@ void rootGroupIsAllEntriesByDefault() throws Exception { assertEquals(new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, allEntriesGroup, new CustomLocalDragboard(), mock(PreferencesService.class)), groupTree.rootGroupProperty().getValue()); } + @Test + void rootGroupIsSelectedByDefault() { + assertEquals(groupTree.rootGroupProperty().get().getGroupNode(), stateManager.getSelectedGroup(databaseContext).get(0)); + } + @Test void explicitGroupsAreRemovedFromEntriesOnDelete() { ExplicitGroup group = new ExplicitGroup("group", GroupHierarchyType.INDEPENDENT, ','); From 8d3b3ae3f94f60866578e3b31a3e566821113a4d Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 18 Oct 2020 23:24:55 +0200 Subject: [PATCH 008/201] Add missing authors --- AUTHORS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AUTHORS b/AUTHORS index f0aa26c6095..4a06dd84643 100644 --- a/AUTHORS +++ b/AUTHORS @@ -47,6 +47,7 @@ Baruch Oltman Behrouz Javanmardi Benedikt Tutzer Benjamin Köhler +Benjamin Schroth Berk Gureken Bernd Kalbfuss Bernhard Tempel @@ -243,6 +244,7 @@ Marius Kleiner Mark Schenk Martin Kähmer Martin Stolle +Martin W. Kirst Martina Catizone Mathias Walter Matthias Geiger @@ -282,6 +284,7 @@ Nick Mancuso Nick S. Weatherley Nico Schlömer Nicolas Pavillon +Niffler Nikita Borovikov Niklas Schmitt nikmilpv From d5922d963aa46f5f1ec0bb747ff6221b8d56aabf Mon Sep 17 00:00:00 2001 From: Gennadiy Date: Mon, 19 Oct 2020 00:50:19 +0300 Subject: [PATCH 009/201] Corrected shortcut (#6960) --- CHANGELOG.md | 1 + src/main/java/org/jabref/gui/JabRefFrame.java | 3 +++ .../java/org/jabref/gui/actions/JabRefAction.java | 2 +- .../java/org/jabref/gui/keyboard/KeyBinding.java | 13 +++++++------ .../jabref/gui/keyboard/KeyBindingRepository.java | 15 ++++++++++----- .../java/org/jabref/gui/preview/PreviewPanel.java | 6 +++--- src/main/resources/l10n/JabRef_en.properties | 1 + 7 files changed, 26 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2584672543a..103e85bbb80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://git - We fixed an issue where spaces and newlines in an isbn would generate an exception. [#6456](https://github.com/JabRef/jabref/issues/6456) - We fixed an issue where identity column header had incorrect foreground color in the Dark theme. [#6796](https://github.com/JabRef/jabref/issues/6796) - We fixed an issue where clicking on Collapse All button in the Search for Unlinked Local Files expanded the directory structure erroneously [#6848](https://github.com/JabRef/jabref/issues/6848) +- We fixed an issue, when pulling changes from shared database via shortcut caused creation a new new tech report [6867](https://github.com/JabRef/jabref/issues/6867) - We fixed an issue where the JabRef GUI does not highlight the "All entries" group on start-up [#6691](https://github.com/JabRef/jabref/issues/6691) ### Removed diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index c5f54e82dda..612aa7d6e51 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -274,6 +274,9 @@ private void initKeyBindings() { case NEW_UNPUBLISHED: new NewEntryAction(this, StandardEntryType.Unpublished, dialogService, prefs, stateManager).execute(); break; + case NEW_INPROCEEDINGS: + new NewEntryAction(this, StandardEntryType.InProceedings, dialogService, prefs, stateManager).execute(); + break; case PASTE: if (OS.OS_X) { // Workaround for a jdk issue that executes paste twice when using cmd+v in a TextField // Extra workaround for CodeArea, which does not inherit from TextInputControl diff --git a/src/main/java/org/jabref/gui/actions/JabRefAction.java b/src/main/java/org/jabref/gui/actions/JabRefAction.java index 5d1fbd10e28..b19c9ca110e 100644 --- a/src/main/java/org/jabref/gui/actions/JabRefAction.java +++ b/src/main/java/org/jabref/gui/actions/JabRefAction.java @@ -20,7 +20,7 @@ public JabRefAction(Action action, KeyBindingRepository keyBindingRepository) { action.getIcon() .ifPresent(icon -> setGraphic(icon.getGraphicNode())); action.getKeyBinding() - .ifPresent(keyBinding -> setAccelerator(keyBindingRepository.getKeyCombination(keyBinding))); + .ifPresent(keyBinding -> keyBindingRepository.getKeyCombination(keyBinding).ifPresent(combination -> setAccelerator(combination))); setLongText(action.getDescription()); } diff --git a/src/main/java/org/jabref/gui/keyboard/KeyBinding.java b/src/main/java/org/jabref/gui/keyboard/KeyBinding.java index e6afb160540..06bac3e5c89 100644 --- a/src/main/java/org/jabref/gui/keyboard/KeyBinding.java +++ b/src/main/java/org/jabref/gui/keyboard/KeyBinding.java @@ -46,12 +46,13 @@ public enum KeyBinding { NEW_BOOK("New book", Localization.lang("New book"), "ctrl+shift+B", KeyBindingCategory.BIBTEX), NEW_ENTRY("New entry", Localization.lang("New entry"), "ctrl+N", KeyBindingCategory.BIBTEX), NEW_ENTRY_FROM_PLAIN_TEXT("New entry from plain text", Localization.lang("New entry from plain text"), "ctrl+shift+N", KeyBindingCategory.BIBTEX), - NEW_INBOOK("New inbook", Localization.lang("New inbook"), "ctrl+shift+I", KeyBindingCategory.BIBTEX), - NEW_MASTERSTHESIS("New mastersthesis", Localization.lang("New mastersthesis"), "ctrl+shift+M", KeyBindingCategory.BIBTEX), - NEW_PHDTHESIS("New phdthesis", Localization.lang("New phdthesis"), "ctrl+shift+T", KeyBindingCategory.BIBTEX), - NEW_PROCEEDINGS("New proceedings", Localization.lang("New proceedings"), "ctrl+shift+P", KeyBindingCategory.BIBTEX), - NEW_UNPUBLISHED("New unpublished", Localization.lang("New unpublished"), "ctrl+shift+U", KeyBindingCategory.BIBTEX), - NEW_TECHREPORT("New technical report", Localization.lang("New technical report"), "ctrl+shift+R", KeyBindingCategory.BIBTEX), + NEW_INBOOK("New inbook", Localization.lang("New inbook"), "", KeyBindingCategory.BIBTEX), + NEW_MASTERSTHESIS("New mastersthesis", Localization.lang("New mastersthesis"), "", KeyBindingCategory.BIBTEX), + NEW_PHDTHESIS("New phdthesis", Localization.lang("New phdthesis"), "", KeyBindingCategory.BIBTEX), + NEW_PROCEEDINGS("New proceedings", Localization.lang("New proceedings"), "", KeyBindingCategory.BIBTEX), + NEW_UNPUBLISHED("New unpublished", Localization.lang("New unpublished"), "", KeyBindingCategory.BIBTEX), + NEW_TECHREPORT("New technical report", Localization.lang("New technical report"), "", KeyBindingCategory.BIBTEX), + NEW_INPROCEEDINGS("New inproceesings", Localization.lang("New inproceedings"), "", KeyBindingCategory.BIBTEX), NEXT_PREVIEW_LAYOUT("Next preview layout", Localization.lang("Next preview layout"), "F9", KeyBindingCategory.VIEW), NEXT_LIBRARY("Next library", Localization.lang("Next library"), "ctrl+PAGE_DOWN", KeyBindingCategory.VIEW), OPEN_CONSOLE("Open terminal here", Localization.lang("Open terminal here"), "ctrl+shift+L", KeyBindingCategory.TOOLS), diff --git a/src/main/java/org/jabref/gui/keyboard/KeyBindingRepository.java b/src/main/java/org/jabref/gui/keyboard/KeyBindingRepository.java index de219527ed5..1e2a4c23a8f 100644 --- a/src/main/java/org/jabref/gui/keyboard/KeyBindingRepository.java +++ b/src/main/java/org/jabref/gui/keyboard/KeyBindingRepository.java @@ -120,13 +120,15 @@ public Optional mapToKeyBinding(KeyEvent keyEvent) { return Optional.empty(); } - public KeyCombination getKeyCombination(KeyBinding bindName) { + public Optional getKeyCombination(KeyBinding bindName) { String binding = get(bindName.getConstant()); + if (binding.isEmpty()) { + return Optional.empty(); + } if (OS.OS_X) { binding = binding.replace("ctrl", "meta"); } - - return KeyCombination.valueOf(binding); + return Optional.of(KeyCombination.valueOf(binding)); } /** @@ -137,8 +139,11 @@ public KeyCombination getKeyCombination(KeyBinding bindName) { * @return true if matching, else false */ public boolean checkKeyCombinationEquality(KeyBinding binding, KeyEvent keyEvent) { - KeyCombination keyCombination = getKeyCombination(binding); - return checkKeyCombinationEquality(keyCombination, keyEvent); + Optional keyCombination = getKeyCombination(binding); + if (!keyCombination.isPresent()) { + return false; + } + return checkKeyCombinationEquality(keyCombination.get(), keyEvent); } public List getBindNames() { diff --git a/src/main/java/org/jabref/gui/preview/PreviewPanel.java b/src/main/java/org/jabref/gui/preview/PreviewPanel.java index 83a80ae422c..113e65fe0c0 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewPanel.java +++ b/src/main/java/org/jabref/gui/preview/PreviewPanel.java @@ -136,15 +136,15 @@ private void createKeyBindings() { private ContextMenu createPopupMenu() { MenuItem copyPreview = new MenuItem(Localization.lang("Copy preview"), IconTheme.JabRefIcons.COPY.getGraphicNode()); - copyPreview.setAccelerator(keyBindingRepository.getKeyCombination(KeyBinding.COPY_PREVIEW)); + keyBindingRepository.getKeyCombination(KeyBinding.COPY_PREVIEW).ifPresent(keyCombination -> copyPreview.setAccelerator(keyCombination)); copyPreview.setOnAction(event -> previewView.copyPreviewToClipBoard()); MenuItem printEntryPreview = new MenuItem(Localization.lang("Print entry preview"), IconTheme.JabRefIcons.PRINTED.getGraphicNode()); printEntryPreview.setOnAction(event -> previewView.print()); MenuItem previousPreviewLayout = new MenuItem(Localization.lang("Previous preview layout")); - previousPreviewLayout.setAccelerator(keyBindingRepository.getKeyCombination(KeyBinding.PREVIOUS_PREVIEW_LAYOUT)); + keyBindingRepository.getKeyCombination(KeyBinding.PREVIOUS_PREVIEW_LAYOUT).ifPresent(keyCombination -> previousPreviewLayout.setAccelerator(keyCombination)); previousPreviewLayout.setOnAction(event -> this.previousPreviewStyle()); MenuItem nextPreviewLayout = new MenuItem(Localization.lang("Next preview layout")); - nextPreviewLayout.setAccelerator(keyBindingRepository.getKeyCombination(KeyBinding.NEXT_PREVIEW_LAYOUT)); + keyBindingRepository.getKeyCombination(KeyBinding.NEXT_PREVIEW_LAYOUT).ifPresent(keyCombination -> nextPreviewLayout.setAccelerator(keyCombination)); nextPreviewLayout.setOnAction(event -> this.nextPreviewStyle()); ContextMenu menu = new ContextMenu(); diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 47a7b78e9d8..bbb9e007210 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2258,6 +2258,7 @@ Previous\ preview\ style=Previous preview style (\ Note\:\ Press\ return\ to\ commit\ changes\ in\ the\ table\!\ )=( Note\: Press return to commit changes in the table\! ) Reset=Reset +New\ inproceedings=New inproceedings Reset\ entry\ types\ and\ fields\ to\ defaults=Reset entry types and fields to defaults This\ will\ reset\ all\ entry\ types\ to\ their\ default\ values\ and\ remove\ all\ custom\ entry\ types=This will reset all entry types to their default values and remove all custom entry types Replace\ tabs\ with\ space=Replace tabs with space From 0c5af041962d62630cd8ef30131adb3ce1d44ddb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2020 08:51:08 +0200 Subject: [PATCH 010/201] Bump guava from 29.0-jre to 30.0-jre (#7030) Bumps [guava](https://github.com/google/guava) from 29.0-jre to 30.0-jre. - [Release notes](https://github.com/google/guava/releases) - [Commits](https://github.com/google/guava/commits) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d07d7af7392..0227b44210c 100644 --- a/build.gradle +++ b/build.gradle @@ -150,7 +150,7 @@ dependencies { exclude module: 'oraclepki' } - implementation ('com.google.guava:guava:29.0-jre') { + implementation ('com.google.guava:guava:30.0-jre') { // TODO: Remove this as soon as https://github.com/google/guava/issues/2960 is fixed exclude module: "jsr305" } From d7ae1da35912208542d4aed8276730be62b1a475 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2020 08:51:19 +0200 Subject: [PATCH 011/201] Bump unirest-java from 3.11.01 to 3.11.02 (#7029) Bumps [unirest-java](https://github.com/Kong/unirest-java) from 3.11.01 to 3.11.02. - [Release notes](https://github.com/Kong/unirest-java/releases) - [Changelog](https://github.com/Kong/unirest-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/Kong/unirest-java/compare/v3.11.01...v3.11.02) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0227b44210c..584a40d10ce 100644 --- a/build.gradle +++ b/build.gradle @@ -170,7 +170,7 @@ dependencies { implementation 'org.controlsfx:controlsfx:11.0.2' implementation 'org.jsoup:jsoup:1.13.1' - implementation 'com.konghq:unirest-java:3.11.01' + implementation 'com.konghq:unirest-java:3.11.02' implementation 'org.slf4j:slf4j-api:2.0.0-alpha1' implementation group: 'org.apache.logging.log4j', name: 'log4j-jcl', version: '3.0.0-SNAPSHOT' From 29b3b4f1b2c43632ccb41962d4effb42c798fee2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2020 08:51:34 +0200 Subject: [PATCH 012/201] Bump postgresql from 42.2.17 to 42.2.18 (#7027) Bumps [postgresql](https://github.com/pgjdbc/pgjdbc) from 42.2.17 to 42.2.18. - [Release notes](https://github.com/pgjdbc/pgjdbc/releases) - [Changelog](https://github.com/pgjdbc/pgjdbc/blob/REL42.2.18/CHANGELOG.md) - [Commits](https://github.com/pgjdbc/pgjdbc/compare/REL42.2.17...REL42.2.18) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 584a40d10ce..8af95be9412 100644 --- a/build.gradle +++ b/build.gradle @@ -143,7 +143,7 @@ dependencies { implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.7.0' - implementation 'org.postgresql:postgresql:42.2.17' + implementation 'org.postgresql:postgresql:42.2.18' implementation ('com.oracle.ojdbc:ojdbc10:19.3.0.0') { // causing module issues From af203b6559e1b4a39ac58d938f39d73d0f06fe6a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2020 08:51:46 +0200 Subject: [PATCH 013/201] Bump com.adarshr.test-logger from 2.1.0 to 2.1.1 (#7028) Bumps com.adarshr.test-logger from 2.1.0 to 2.1.1. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8af95be9412..1c2f5f070b8 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ plugins { // nicer test outputs during running and completion // Homepage: https://github.com/radarsh/gradle-test-logger-plugin - id 'com.adarshr.test-logger' version '2.1.0' + id 'com.adarshr.test-logger' version '2.1.1' } gradle.startParameter.showStacktrace = org.gradle.api.logging.configuration.ShowStacktrace.ALWAYS From 79f0221249c7f2fac9a7cf20313115c2965211eb Mon Sep 17 00:00:00 2001 From: Galileo Sartor Date: Mon, 19 Oct 2020 20:12:14 +0200 Subject: [PATCH 014/201] Snapcraft: set version from new xml file (#7031) * Try fo fix snapcraft build with debug switch * Get version from .jpackage.xml file * Parse .jpackage.xml file * try to put app version to image options Co-authored-by: Siedlerchr --- .github/workflows/snap.yml | 2 ++ build.gradle | 1 + snap/snapcraft.yaml | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/snap.yml b/.github/workflows/snap.yml index dad9960111a..bba78f1338d 100644 --- a/.github/workflows/snap.yml +++ b/.github/workflows/snap.yml @@ -31,6 +31,8 @@ jobs: - name: Build snap (1) Run build uses: snapcore/action-build@v1 id: snapcraft + with: + snapcraft-args: "--debug" - name: Build snap (2) Upload snap if: ${{ steps.checksecrets.outputs.secretspresent }} uses: snapcore/action-publish@v1 diff --git a/build.gradle b/build.gradle index 1c2f5f070b8..f1e260f51bf 100644 --- a/build.gradle +++ b/build.gradle @@ -674,6 +674,7 @@ jlink { if (OperatingSystem.current().isLinux()) { imageOptions = [ '--icon', "${projectDir}/src/main/resources/icons/JabRef-icon-64.png", + '--app-version', "${project.version}", ] installerOptions = [ '--verbose', diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index f53d9d44f63..714a569d6ae 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -57,7 +57,7 @@ parts: - x11-utils override-build: | snapcraftctl build - snapcraftctl set-version "$(cat $SNAPCRAFT_PART_INSTALL/lib/app/JabRef.cfg | grep "app.version=" | cut -d'=' -f2)" + snapcraftctl set-version "$(cat $SNAPCRAFT_PART_INSTALL/lib/app/.jpackage.xml | grep "app-version" | cut -d">" -f2 | cut -d"<" -f1)" sed -i 's|/opt/jabref/lib/jabrefHost.py|/snap/bin/jabref.browser-proxy|g' $SNAPCRAFT_PART_INSTALL/lib/native-messaging-host/*/org.jabref.jabref.json rm $SNAPCRAFT_PART_INSTALL/bin/JabRef jabref-launcher: From a7b05d0755334caa9d8480a5e4aefdf6759b1895 Mon Sep 17 00:00:00 2001 From: Timucin Merdin Date: Tue, 20 Oct 2020 12:55:02 +0200 Subject: [PATCH 015/201] =?UTF-8?q?add=20Cleanup=20for=20copying=20over=20?= =?UTF-8?q?physcial=20review=20article=20id=20as=20the=20page=20n=E2=80=A6?= =?UTF-8?q?=20(#7025)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add Cleanup for copying over physcial review article id as the page number * remove PageFieldCleanup, add private methods to DoiFetcher * remove comment from DoiFetcherTest * replace regex with substring and string comparison * fix checkstyle issues * fix checkstyle issues * add regex to check aps doi format * move suffix pattern to private static field * add changelog entry * add issue link to changelog entry --- CHANGELOG.md | 2 ++ .../logic/importer/fetcher/DoiFetcher.java | 30 +++++++++++++++++++ .../importer/fetcher/DoiFetcherTest.java | 21 +++++++++++++ 3 files changed, 53 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 103e85bbb80..4456c8c69af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We added a query parser and mapping layer to enable conversion of queries formulated in simplified lucene syntax by the user into api queries. [#6799](https://github.com/JabRef/jabref/pull/6799) - We added some basic functionality to customise the look of JabRef by importing a css theme file. [#5790](https://github.com/JabRef/jabref/issues/5790) - We added connection check function in network preference setting [#6560](https://github.com/JabRef/jabref/issues/6560) +- We added a DOI format and organization check to detect [American Physical Society](https://journals.aps.org/) journals to copy the article ID +to the page field for cases where the page numbers are missing. [#7019](https://github.com/JabRef/jabref/issues/7019) - We added a new fetcher to enable users to search jstor.org [#6627](https://github.com/JabRef/jabref/issues/6627) ### Changed diff --git a/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java index ae1e3ab5961..617c0985814 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java @@ -5,6 +5,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.regex.Pattern; import org.jabref.logic.cleanup.FieldFormatterCleanup; import org.jabref.logic.formatter.bibtexfields.ClearFormatter; @@ -22,6 +23,7 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.identifier.DOI; +import org.jabref.model.entry.types.StandardEntryType; import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.model.util.OptionalUtil; @@ -32,8 +34,13 @@ import org.slf4j.LoggerFactory; public class DoiFetcher implements IdBasedFetcher, EntryBasedFetcher { + public static final String NAME = "DOI"; + private static final String APS_JOURNAL_ORG_DOI_ID = "1103"; + private static final String APS_SUFFIX = "([\\w]+\\.)([\\w]+\\.)([\\w]+)"; + private static final Pattern APS_SUFFIX_PATTERN = Pattern.compile(APS_SUFFIX); + private static final Logger LOGGER = LoggerFactory.getLogger(DoiFetcher.class); private final ImportFormatPreferences preferences; @@ -75,6 +82,14 @@ public Optional performSearchById(String identifier) throws FetcherExc fetchedEntry = BibtexParser.singleFromString(bibtexString, preferences, new DummyFileUpdateMonitor()); fetchedEntry.ifPresent(this::doPostCleanup); + // Check if the entry is an APS journal and add the article id as the page count if page field is missing + if (fetchedEntry.isPresent() && fetchedEntry.get().hasField(StandardField.DOI)) { + BibEntry entry = fetchedEntry.get(); + if (isAPSJournal(entry, entry.getField(StandardField.DOI).get()) && !entry.hasField(StandardField.PAGES)) { + setPageCountToArticleId(entry, entry.getField(StandardField.DOI).get()); + } + } + return fetchedEntry; } else { throw new FetcherException(Localization.lang("Invalid DOI: '%0'.", identifier)); @@ -123,4 +138,19 @@ public Optional getAgency(DOI doi) throws IOException { return agency; } + + private void setPageCountToArticleId(BibEntry entry, String doiAsString) { + String articleId = doiAsString.substring(doiAsString.lastIndexOf('.') + 1); + entry.setField(StandardField.PAGES, articleId); + } + + // checks if the entry is an APS journal by comparing the organization id and the suffix format + private boolean isAPSJournal(BibEntry entry, String doiAsString) { + if (!entry.getType().equals(StandardEntryType.Article)) { + return false; + } + String suffix = doiAsString.substring(doiAsString.lastIndexOf('/') + 1); + String organizationId = doiAsString.substring(doiAsString.indexOf('.') + 1, doiAsString.indexOf('/')); + return organizationId.equals(APS_JOURNAL_ORG_DOI_ID) && APS_SUFFIX_PATTERN.matcher(suffix).matches(); + } } diff --git a/src/test/java/org/jabref/logic/importer/fetcher/DoiFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/DoiFetcherTest.java index 70ff718193f..d824e77cc4c 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/DoiFetcherTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/DoiFetcherTest.java @@ -24,6 +24,7 @@ public class DoiFetcherTest { private BibEntry bibEntryBurd2011; private BibEntry bibEntryDecker2007; private BibEntry bibEntryIannarelli2019; + private BibEntry bibEntryStenzel2020; @BeforeEach public void setUp() { @@ -68,6 +69,20 @@ public void setUp() { .withField(StandardField.JOURNAL, "Chemical Engineering Transactions") .withField(StandardField.PAGES, "871-876") .withField(StandardField.VOLUME, "77"); + bibEntryStenzel2020 = new BibEntry(); + bibEntryStenzel2020.setType(StandardEntryType.Article); + bibEntryStenzel2020.setCitationKey("Stenzel_2020"); + bibEntryStenzel2020.setField(StandardField.AUTHOR, "L. Stenzel and A. L. C. Hayward and U. Schollwöck and F. Heidrich-Meisner"); + bibEntryStenzel2020.setField(StandardField.JOURNAL, "Physical Review A"); + bibEntryStenzel2020.setField(StandardField.TITLE, "Topological phases in the Fermi-Hofstadter-Hubbard model on hybrid-space ladders"); + bibEntryStenzel2020.setField(StandardField.YEAR, "2020"); + bibEntryStenzel2020.setField(StandardField.MONTH, "aug"); + bibEntryStenzel2020.setField(StandardField.VOLUME, "102"); + bibEntryStenzel2020.setField(StandardField.DOI, "10.1103/physreva.102.023315"); + bibEntryStenzel2020.setField(StandardField.PUBLISHER, "American Physical Society ({APS})"); + bibEntryStenzel2020.setField(StandardField.PAGES, "023315"); + bibEntryStenzel2020.setField(StandardField.NUMBER, "2"); + } @Test @@ -108,4 +123,10 @@ public void testPerformSearchNonTrimmedDOI() throws FetcherException { Optional fetchedEntry = fetcher.performSearchById("http s://doi.org/ 10.1109 /ICWS .2007.59 "); assertEquals(Optional.of(bibEntryDecker2007), fetchedEntry); } + + @Test + public void testAPSJournalCopiesArticleIdToPageField() throws FetcherException { + Optional fetchedEntry = fetcher.performSearchById("10.1103/physreva.102.023315"); + assertEquals(Optional.of(bibEntryStenzel2020), fetchedEntry); + } } From 4cf2fb06d3d876d2043f65ca0e51c3c4185fd05c Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Tue, 20 Oct 2020 23:27:30 +0200 Subject: [PATCH 016/201] Fix setting of title (and simplify BasePanel to LibraryTab) (#6129) Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> --- CHANGELOG.md | 1 + .../org/jabref/gui/BasePanelPreferences.java | 79 ---- .../java/org/jabref/gui/EntryTypeView.java | 13 +- .../org/jabref/gui/EntryTypeViewModel.java | 26 +- src/main/java/org/jabref/gui/JabRefFrame.java | 382 ++++++------------ src/main/java/org/jabref/gui/JabRefGUI.java | 16 +- .../gui/{BasePanel.java => LibraryTab.java} | 299 +++++++++----- .../java/org/jabref/gui/StateManager.java | 9 +- .../jabref/gui/UpdateTimestampListener.java | 14 +- .../jabref/gui/WaitForSaveFinishedDialog.java | 8 +- .../jabref/gui/auximport/FromAuxDialog.java | 8 +- .../CitationKeyPatternAction.java | 2 +- .../CitationKeyPatternDialog.java | 14 +- .../CitationKeyPatternPanel.java | 16 +- .../GenerateCitationKeyAction.java | 2 +- .../org/jabref/gui/cleanup/CleanupAction.java | 4 +- .../ContentSelectorDialogView.java | 10 +- .../ContentSelectorDialogViewModel.java | 14 +- .../ManageContentSelectorAction.java | 6 +- .../jabref/gui/dialogs/AutosaveUiManager.java | 14 +- .../duplicationFinder/DuplicateSearch.java | 20 +- .../java/org/jabref/gui/edit/EditAction.java | 46 +-- .../jabref/gui/edit/ReplaceStringAction.java | 2 +- .../jabref/gui/edit/ReplaceStringView.java | 6 +- .../gui/edit/ReplaceStringViewModel.java | 10 +- .../jabref/gui/entryeditor/EntryEditor.java | 50 +-- .../entryeditor/EntryEditorPreferences.java | 14 +- .../entryeditor/OpenEntryEditorAction.java | 2 +- .../gui/entryeditor/PreviewSwitchAction.java | 4 +- .../jabref/gui/exporter/ExportCommand.java | 10 +- .../gui/exporter/ExportToClipboardAction.java | 8 +- .../org/jabref/gui/exporter/SaveAction.java | 16 +- .../jabref/gui/exporter/SaveAllAction.java | 6 +- .../gui/exporter/SaveDatabaseAction.java | 56 ++- .../IdentifierEditorViewModel.java | 4 +- .../org/jabref/gui/importer/ImportAction.java | 9 +- .../gui/importer/ImportEntriesViewModel.java | 2 +- .../jabref/gui/importer/NewEntryAction.java | 6 +- .../importer/ParserResultWarningDialog.java | 6 +- .../actions/CheckForNewEntryTypesAction.java | 4 +- .../importer/actions/GUIPostOpenAction.java | 4 +- .../actions/MergeReviewIntoCommentAction.java | 6 +- .../importer/actions/OpenDatabaseAction.java | 41 +- .../fetcher/WebSearchPaneViewModel.java | 4 +- .../gui/integrity/IntegrityCheckAction.java | 2 +- .../gui/integrity/IntegrityCheckDialog.java | 10 +- .../jabref/gui/journals/AbbreviateAction.java | 20 +- .../LibraryPropertiesAction.java | 2 +- .../LibraryPropertiesDialogView.java | 10 +- .../gui/linkedfile/AttachFileAction.java | 15 +- .../org/jabref/gui/maintable/MainTable.java | 61 +-- .../jabref/gui/maintable/RightClickMenu.java | 38 +- .../gui/mergeentries/FetchAndMergeEntry.java | 18 +- .../gui/mergeentries/MergeEntriesAction.java | 2 +- .../MergeWithFetchedEntryAction.java | 10 +- .../gui/openoffice/OpenOfficePanel.java | 30 +- .../preferences/EntryEditorTabViewModel.java | 15 +- .../gui/preferences/GeneralTabViewModel.java | 4 +- .../PreferencesDialogViewModel.java | 2 +- .../gui/preferences/PreviewTabViewModel.java | 6 +- .../jabref/gui/search/GlobalSearchBar.java | 2 +- .../SharedDatabaseLoginDialogViewModel.java | 23 +- .../gui/shared/SharedDatabaseUIManager.java | 14 +- .../gui/specialfields/SpecialFieldAction.java | 6 +- .../org/jabref/gui/undo/UndoRedoAction.java | 16 +- .../bibtexfields/NormalizePagesFormatter.java | 2 +- .../preferences/GeneralPreferences.java | 13 +- .../jabref/preferences/JabRefPreferences.java | 10 +- .../gui/exporter/SaveDatabaseActionTest.java | 22 +- 69 files changed, 730 insertions(+), 896 deletions(-) delete mode 100644 src/main/java/org/jabref/gui/BasePanelPreferences.java rename src/main/java/org/jabref/gui/{BasePanel.java => LibraryTab.java} (71%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4456c8c69af..cffb38db9c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://git - We improved the duplicate detection when identifiers like DOI or arxiv are semantiaclly the same, but just syntactically differ (e.g. with or without http(s):// prefix). [#6707](https://github.com/JabRef/jabref/issues/6707) - We changed in the group interface "Generate groups from keywords in a BibTeX field" by "Generate groups from keywords in the following field". [#6983](https://github.com/JabRef/jabref/issues/6983) - We changed the name of a group type from "Searching for keywords" to "Searching for a keyword". [6995](https://github.com/JabRef/jabref/pull/6995) +- We changed the way JabRef displays the title of a tab and of the window. [4161](https://github.com/JabRef/jabref/issues/4161) - We changed connect timeouts for server requests to 30 seconds in general and 5 seconds for GROBID server (special) and improved user notifications on connection issues. [7026](https://github.com/JabRef/jabref/pull/7026) ### Fixed diff --git a/src/main/java/org/jabref/gui/BasePanelPreferences.java b/src/main/java/org/jabref/gui/BasePanelPreferences.java deleted file mode 100644 index bc95224afa6..00000000000 --- a/src/main/java/org/jabref/gui/BasePanelPreferences.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.jabref.gui; - -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.SimpleDoubleProperty; - -import org.jabref.gui.autocompleter.AutoCompletePreferences; -import org.jabref.gui.entryeditor.EntryEditorPreferences; -import org.jabref.gui.keyboard.KeyBindingRepository; -import org.jabref.gui.maintable.MainTablePreferences; -import org.jabref.preferences.JabRefPreferences; -import org.jabref.preferences.PreviewPreferences; - -import com.tobiasdiez.easybind.EasyBind; - -public class BasePanelPreferences { - private final MainTablePreferences tablePreferences; - private AutoCompletePreferences autoCompletePreferences; - private final EntryEditorPreferences entryEditorPreferences; - private final KeyBindingRepository keyBindings; - private final PreviewPreferences previewPreferences; - private final DoubleProperty entryEditorDividerPosition = new SimpleDoubleProperty(); - - public BasePanelPreferences(MainTablePreferences tablePreferences, AutoCompletePreferences autoCompletePreferences, EntryEditorPreferences entryEditorPreferences, KeyBindingRepository keyBindings, PreviewPreferences previewPreferences, Double entryEditorDividerPosition) { - this.tablePreferences = tablePreferences; - this.autoCompletePreferences = autoCompletePreferences; - this.entryEditorPreferences = entryEditorPreferences; - this.keyBindings = keyBindings; - this.previewPreferences = previewPreferences; - this.entryEditorDividerPosition.setValue(entryEditorDividerPosition); - } - - public static BasePanelPreferences from(JabRefPreferences preferences) { - BasePanelPreferences basePanelPreferences = new BasePanelPreferences( - preferences.getMainTablePreferences(), - preferences.getAutoCompletePreferences(), - preferences.getEntryEditorPreferences(), - Globals.getKeyPrefs(), - preferences.getPreviewPreferences(), - preferences.getDouble(JabRefPreferences.ENTRY_EDITOR_HEIGHT)); - EasyBind.subscribe(basePanelPreferences.entryEditorDividerPosition, value -> preferences.putDouble(JabRefPreferences.ENTRY_EDITOR_HEIGHT, value.doubleValue())); - return basePanelPreferences; - } - - public double getEntryEditorDividerPosition() { - return entryEditorDividerPosition.get(); - } - - public void setEntryEditorDividerPosition(double entryEditorDividerPosition) { - this.entryEditorDividerPosition.set(entryEditorDividerPosition); - } - - public DoubleProperty entryEditorDividerPositionProperty() { - return entryEditorDividerPosition; - } - - public MainTablePreferences getTablePreferences() { - return tablePreferences; - } - - public AutoCompletePreferences getAutoCompletePreferences() { - return autoCompletePreferences; - } - - public void setAutoCompletePreferences(AutoCompletePreferences autoCompletePreferences) { - this.autoCompletePreferences = autoCompletePreferences; - } - - public EntryEditorPreferences getEntryEditorPreferences() { - return entryEditorPreferences; - } - - public KeyBindingRepository getKeyBindings() { - return keyBindings; - } - - public PreviewPreferences getPreviewPreferences() { - return previewPreferences; - } -} diff --git a/src/main/java/org/jabref/gui/EntryTypeView.java b/src/main/java/org/jabref/gui/EntryTypeView.java index f304238b688..83d7baa191c 100644 --- a/src/main/java/org/jabref/gui/EntryTypeView.java +++ b/src/main/java/org/jabref/gui/EntryTypeView.java @@ -23,6 +23,7 @@ import org.jabref.gui.util.IconValidationDecorator; import org.jabref.gui.util.ViewModelListCellFactory; import org.jabref.logic.importer.IdBasedFetcher; +import org.jabref.logic.importer.WebFetcher; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntryType; @@ -60,7 +61,7 @@ public class EntryTypeView extends BaseDialog { @FXML private TitledPane customTitlePane; @FXML private TitledPane biblatexSoftwareTitlePane; - private final BasePanel basePanel; + private final LibraryTab libraryTab; private final DialogService dialogService; private final JabRefPreferences prefs; @@ -68,8 +69,8 @@ public class EntryTypeView extends BaseDialog { private EntryTypeViewModel viewModel; private final ControlsFxVisualizer visualizer = new ControlsFxVisualizer(); - public EntryTypeView(BasePanel basePanel, DialogService dialogService, JabRefPreferences preferences) { - this.basePanel = basePanel; + public EntryTypeView(LibraryTab libraryTab, DialogService dialogService, JabRefPreferences preferences) { + this.libraryTab = libraryTab; this.dialogService = dialogService; this.prefs = preferences; @@ -120,7 +121,7 @@ private void addEntriesToPane(FlowPane pane, Collection @FXML public void initialize() { visualizer.setDecoration(new IconValidationDecorator()); - viewModel = new EntryTypeViewModel(prefs, basePanel, dialogService, stateManager); + viewModel = new EntryTypeViewModel(prefs, libraryTab, dialogService, stateManager); idBasedFetchers.itemsProperty().bind(viewModel.fetcherItemsProperty()); idTextField.textProperty().bindBidirectional(viewModel.idTextProperty()); @@ -133,7 +134,7 @@ public void initialize() { } }); - new ViewModelListCellFactory().withText(item -> item.getName()).install(idBasedFetchers); + new ViewModelListCellFactory().withText(WebFetcher::getName).install(idBasedFetchers); // we set the managed property so that they will only be rendered when they are visble so that the Nodes only take the space when visible // avoids removing and adding from the scence graph @@ -143,7 +144,7 @@ public void initialize() { customTitlePane.managedProperty().bind(customTitlePane.visibleProperty()); biblatexSoftwareTitlePane.managedProperty().bind(biblatexSoftwareTitlePane.visibleProperty()); - if (basePanel.getBibDatabaseContext().isBiblatexMode()) { + if (libraryTab.getBibDatabaseContext().isBiblatexMode()) { addEntriesToPane(biblatexPane, BiblatexEntryTypeDefinitions.ALL); addEntriesToPane(biblatexSoftwarePane, BiblatexSoftwareEntryTypeDefinitions.ALL); diff --git a/src/main/java/org/jabref/gui/EntryTypeViewModel.java b/src/main/java/org/jabref/gui/EntryTypeViewModel.java index 926ad996e08..c2d055c08bb 100644 --- a/src/main/java/org/jabref/gui/EntryTypeViewModel.java +++ b/src/main/java/org/jabref/gui/EntryTypeViewModel.java @@ -46,13 +46,13 @@ public class EntryTypeViewModel { private final StringProperty idText = new SimpleStringProperty(); private final BooleanProperty focusAndSelectAllProperty = new SimpleBooleanProperty(); private Task> fetcherWorker = new FetcherWorker(); - private final BasePanel basePanel; + private final LibraryTab libraryTab; private final DialogService dialogService; private final Validator idFieldValidator; private final StateManager stateManager; - public EntryTypeViewModel(JabRefPreferences preferences, BasePanel basePanel, DialogService dialogService, StateManager stateManager) { - this.basePanel = basePanel; + public EntryTypeViewModel(JabRefPreferences preferences, LibraryTab libraryTab, DialogService dialogService, StateManager stateManager) { + this.libraryTab = libraryTab; this.prefs = preferences; this.dialogService = dialogService; this.stateManager = stateManager; @@ -147,22 +147,22 @@ public void runFetcherWorker() { Optional result = fetcherWorker.getValue(); if (result.isPresent()) { final BibEntry entry = result.get(); - ImportCleanup cleanup = new ImportCleanup(basePanel.getBibDatabaseContext().getMode()); + ImportCleanup cleanup = new ImportCleanup(libraryTab.getBibDatabaseContext().getMode()); cleanup.doPostCleanup(entry); - Optional duplicate = new DuplicateCheck(Globals.entryTypesManager).containsDuplicate(basePanel.getDatabase(), entry, basePanel.getBibDatabaseContext().getMode()); + Optional duplicate = new DuplicateCheck(Globals.entryTypesManager).containsDuplicate(libraryTab.getDatabase(), entry, libraryTab.getBibDatabaseContext().getMode()); if ((duplicate.isPresent())) { - DuplicateResolverDialog dialog = new DuplicateResolverDialog(entry, duplicate.get(), DuplicateResolverDialog.DuplicateResolverType.IMPORT_CHECK, basePanel.getBibDatabaseContext(), stateManager); + DuplicateResolverDialog dialog = new DuplicateResolverDialog(entry, duplicate.get(), DuplicateResolverDialog.DuplicateResolverType.IMPORT_CHECK, libraryTab.getBibDatabaseContext(), stateManager); switch (dialog.showAndWait().orElse(DuplicateResolverDialog.DuplicateResolverResult.BREAK)) { case KEEP_LEFT: - basePanel.getDatabase().removeEntry(duplicate.get()); - basePanel.getDatabase().insertEntry(entry); + libraryTab.getDatabase().removeEntry(duplicate.get()); + libraryTab.getDatabase().insertEntry(entry); break; case KEEP_BOTH: - basePanel.getDatabase().insertEntry(entry); + libraryTab.getDatabase().insertEntry(entry); break; case KEEP_MERGE: - basePanel.getDatabase().removeEntry(duplicate.get()); - basePanel.getDatabase().insertEntry(dialog.getMergedEntry()); + libraryTab.getDatabase().removeEntry(duplicate.get()); + libraryTab.getDatabase().insertEntry(dialog.getMergedEntry()); break; default: // Do nothing @@ -170,8 +170,8 @@ public void runFetcherWorker() { } } else { // Regenerate CiteKey of imported BibEntry - new CitationKeyGenerator(basePanel.getBibDatabaseContext(), prefs.getCitationKeyPatternPreferences()).generateAndSetKey(entry); - basePanel.insertEntry(entry); + new CitationKeyGenerator(libraryTab.getBibDatabaseContext(), prefs.getCitationKeyPatternPreferences()).generateAndSetKey(entry); + libraryTab.insertEntry(entry); } searchSuccesfulProperty.set(true); } else if (StringUtil.isBlank(idText.getValue())) { diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 612aa7d6e51..4855a16e3ca 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -1,6 +1,5 @@ package org.jabref.gui; -import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; @@ -11,8 +10,11 @@ import java.util.Objects; import java.util.Optional; import java.util.TimerTask; +import java.util.stream.Collectors; import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.StringBinding; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.concurrent.Task; @@ -131,17 +133,15 @@ import org.jabref.logic.undo.UndoChangeEvent; import org.jabref.logic.undo.UndoRedoEvent; import org.jabref.logic.util.OS; -import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.SpecialField; -import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.StandardEntryType; import org.jabref.preferences.JabRefPreferences; import org.jabref.preferences.LastFocusedTabPreferences; import com.google.common.eventbus.Subscribe; import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.EasyObservableList; import org.controlsfx.control.PopOver; import org.controlsfx.control.TaskProgressView; import org.fxmisc.richtext.CodeArea; @@ -153,7 +153,6 @@ */ public class JabRefFrame extends BorderPane { - // Frame titles. public static final String FRAME_TITLE = "JabRef"; private static final Logger LOGGER = LoggerFactory.getLogger(JabRefFrame.class); @@ -164,12 +163,13 @@ public class JabRefFrame extends BorderPane { private final FileHistoryMenu fileHistory; + @SuppressWarnings({"FieldCanBeLocal"}) private EasyObservableList openDatabaseList; + private final Stage mainStage; private final StateManager stateManager; private final CountingUndoManager undoManager; private final PushToApplicationsManager pushToApplicationsManager; private final DialogService dialogService; - private final JabRefExecutorService executorService; private SidePaneManager sidePaneManager; private TabPane tabbedPane; private SidePane sidePane; @@ -182,7 +182,6 @@ public JabRefFrame(Stage mainStage) { this.pushToApplicationsManager = new PushToApplicationsManager(dialogService, stateManager, prefs); this.undoManager = Globals.undoManager; this.fileHistory = new FileHistoryMenu(prefs, dialogService, getOpenDatabaseAction()); - this.executorService = JabRefExecutorService.INSTANCE; this.setOnKeyTyped(key -> { if (this.fileHistory.isShowing()) { if (this.fileHistory.openFileByKey(key)) { @@ -192,10 +191,6 @@ public JabRefFrame(Stage mainStage) { }); } - private static BasePanel getBasePanel(Tab tab) { - return (BasePanel) tab.getContent(); - } - private void initDragAndDrop() { Tab dndIndicator = new Tab(Localization.lang("Open files..."), null); dndIndicator.getStyleClass().add("drop"); @@ -236,7 +231,7 @@ private void initKeyBindings() { if (keyBinding.isPresent()) { switch (keyBinding.get()) { case FOCUS_ENTRY_TABLE: - getCurrentBasePanel().getMainTable().requestFocus(); + getCurrentLibraryTab().getMainTable().requestFocus(); event.consume(); break; case NEXT_LIBRARY: @@ -318,43 +313,6 @@ private Void showTrackingNotification() { return null; } - public void refreshTitleAndTabs() { - DefaultTaskExecutor.runInJavaFXThread(() -> { - - setWindowTitle(); - updateAllTabTitles(); - }); - } - - /** - * Sets the title of the main window. - */ - public void setWindowTitle() { - BasePanel panel = getCurrentBasePanel(); - - // no database open - if (panel == null) { - // setTitle(FRAME_TITLE); - return; - } - - String mode = panel.getBibDatabaseContext().getMode().getFormattedName(); - String modeInfo = String.format(" (%s)", Localization.lang("%0 mode", mode)); - boolean isAutosaveEnabled = Globals.prefs.getBoolean(JabRefPreferences.LOCAL_AUTO_SAVE); - - if (panel.getBibDatabaseContext().getLocation() == DatabaseLocation.LOCAL) { - String changeFlag = panel.isModified() && !isAutosaveEnabled ? "*" : ""; - String databaseFile = panel.getBibDatabaseContext() - .getDatabasePath() - .map(Path::toString) - .orElse(Localization.lang("untitled")); - // setTitle(FRAME_TITLE + " - " + databaseFile + changeFlag + modeInfo); - } else if (panel.getBibDatabaseContext().getLocation() == DatabaseLocation.SHARED) { - // setTitle(FRAME_TITLE + " - " + panel.getBibDatabaseContext().getDBMSSynchronizer().getDBName() + " [" - // + Localization.lang("shared") + "]" + modeInfo); - } - } - /** * The MacAdapter calls this method when a "BIB" file has been double-clicked from the Finder. */ @@ -394,7 +352,7 @@ private void tearDownJabRef(List filenames) { prefs.remove(JabRefPreferences.LAST_EDITED); } else { prefs.putStringList(JabRefPreferences.LAST_EDITED, filenames); - Path focusedDatabase = getCurrentBasePanel().getBibDatabaseContext().getDatabasePath().orElse(null); + Path focusedDatabase = getCurrentLibraryTab().getBibDatabaseContext().getDatabasePath().orElse(null); new LastFocusedTabPreferences(prefs).setLastFocusedTab(focusedDatabase); } } @@ -434,12 +392,12 @@ public boolean quit() { // Then ask if the user really wants to close, if the library has not been saved since last save. List filenames = new ArrayList<>(); for (int i = 0; i < tabbedPane.getTabs().size(); i++) { - BasePanel panel = getBasePanelAt(i); - final BibDatabaseContext context = panel.getBibDatabaseContext(); + LibraryTab libraryTab = getLibraryTabAt(i); + final BibDatabaseContext context = libraryTab.getBibDatabaseContext(); - if (panel.isModified() && (context.getLocation() == DatabaseLocation.LOCAL)) { + if (libraryTab.isModified() && (context.getLocation() == DatabaseLocation.LOCAL)) { tabbedPane.getSelectionModel().select(i); - if (!confirmClose(panel)) { + if (!confirmClose(libraryTab)) { return false; } } else if (context.getLocation() == DatabaseLocation.SHARED) { @@ -453,7 +411,7 @@ public boolean quit() { } WaitForSaveFinishedDialog waitForSaveFinishedDialog = new WaitForSaveFinishedDialog(dialogService); - waitForSaveFinishedDialog.showAndWait(getBasePanelList()); + waitForSaveFinishedDialog.showAndWait(getLibraryTabs()); // Good bye! tearDownJabRef(filenames); @@ -582,31 +540,29 @@ private Node createToolbar() { } /** - * Returns the indexed BasePanel. + * Returns the indexed LibraryTab. * * @param i Index of base */ - public BasePanel getBasePanelAt(int i) { - return (BasePanel) tabbedPane.getTabs().get(i).getContent(); + public LibraryTab getLibraryTabAt(int i) { + return (LibraryTab) tabbedPane.getTabs().get(i); } /** - * Returns a list of BasePanel. + * Returns a list of all LibraryTabs in this frame. */ - public List getBasePanelList() { - List returnList = new ArrayList<>(); - for (int i = 0; i < getBasePanelCount(); i++) { - returnList.add(getBasePanelAt(i)); - } - return returnList; + public List getLibraryTabs() { + return tabbedPane.getTabs().stream() + .map(tab -> (LibraryTab) tab) + .collect(Collectors.toList()); } - public void showBasePanelAt(int i) { + public void showLibraryTabAt(int i) { tabbedPane.getSelectionModel().select(i); } - public void showBasePanel(BasePanel bp) { - tabbedPane.getSelectionModel().select(getTab(bp)); + public void showLibraryTab(LibraryTab libraryTab) { + tabbedPane.getSelectionModel().select(libraryTab); } public void init() { @@ -617,26 +573,26 @@ public void init() { tabbedPane.setTabDragPolicy(TabPane.TabDragPolicy.REORDER); initLayout(); - initKeyBindings(); - initDragAndDrop(); - // setBounds(GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds()); - // WindowLocation pw = new WindowLocation(this, JabRefPreferences.POS_X, JabRefPreferences.POS_Y, JabRefPreferences.SIZE_X, - // JabRefPreferences.SIZE_Y); - // pw.displayWindowAtStoredLocation(); - // Bind global state + + // This variable cannot be inlined, since otherwise the list created by EasyBind is being garbage collected + openDatabaseList = EasyBind.map(tabbedPane.getTabs(), tab -> ((LibraryTab) tab).getBibDatabaseContext()); + EasyBind.bindContent(stateManager.getOpenDatabases(), openDatabaseList); + stateManager.activeDatabaseProperty().bind( EasyBind.map(tabbedPane.getSelectionModel().selectedItemProperty(), - tab -> Optional.ofNullable(tab).map(JabRefFrame::getBasePanel).map(BasePanel::getBibDatabaseContext))); + selectedTab -> Optional.ofNullable(selectedTab) + .map(tab -> (LibraryTab) tab) + .map(LibraryTab::getBibDatabaseContext))); // Subscribe to the search EasyBind.subscribe(stateManager.activeSearchQueryProperty(), query -> { - if (getCurrentBasePanel() != null) { - getCurrentBasePanel().setCurrentSearchQuery(query); + if (getCurrentLibraryTab() != null) { + getCurrentLibraryTab().setCurrentSearchQuery(query); } }); @@ -651,29 +607,30 @@ public void init() { EasyBind.subscribe(tabbedPane.getSelectionModel().selectedItemProperty(), tab -> { if (tab == null) { stateManager.setSelectedEntries(Collections.emptyList()); + mainStage.titleProperty().unbind(); + mainStage.setTitle(FRAME_TITLE); return; } - BasePanel newBasePanel = getBasePanel(tab); - if (newBasePanel != null) { - // Poor-mans binding to global state - stateManager.setSelectedEntries(newBasePanel.getSelectedEntries()); + LibraryTab libraryTab = (LibraryTab) tab; - // Update active search query when switching between databases - stateManager.activeSearchQueryProperty().set(newBasePanel.getCurrentSearchQuery()); + // Poor-mans binding to global state + stateManager.setSelectedEntries(libraryTab.getSelectedEntries()); - // groupSidePane.getToggleCommand().setSelected(sidePaneManager.isComponentVisible(GroupSidePane.class)); - // previewToggle.setSelected(Globals.prefs.getPreviewPreferences().isPreviewPanelEnabled()); - // generalFetcher.getToggleCommand().setSelected(sidePaneManager.isComponentVisible(WebSearchPane.class)); - // openOfficePanel.getToggleCommand().setSelected(sidePaneManager.isComponentVisible(OpenOfficeSidePanel.class)); + // Update active search query when switching between databases + stateManager.activeSearchQueryProperty().set(libraryTab.getCurrentSearchQuery()); - setWindowTitle(); - // Update search autocompleter with information for the correct database: - newBasePanel.updateSearchManager(); + // Update search autocompleter with information for the correct database: + libraryTab.updateSearchManager(); - newBasePanel.getUndoManager().postUndoRedoEvent(); - newBasePanel.getMainTable().requestFocus(); - } + libraryTab.getUndoManager().postUndoRedoEvent(); + libraryTab.getMainTable().requestFocus(); + + // Set window title - copy tab title + StringBinding windowTitle = Bindings.createStringBinding( + () -> libraryTab.textProperty().getValue() + " \u2013 " + FRAME_TITLE, + libraryTab.textProperty()); + mainStage.titleProperty().bind(windowTitle); }); initShowTrackingNotification(); } @@ -681,11 +638,11 @@ public void init() { /** * Returns the currently viewed BasePanel. */ - public BasePanel getCurrentBasePanel() { + public LibraryTab getCurrentLibraryTab() { if ((tabbedPane == null) || (tabbedPane.getSelectionModel().getSelectedItem() == null)) { return null; } - return getBasePanel(tabbedPane.getSelectionModel().getSelectedItem()); + return (LibraryTab) tabbedPane.getSelectionModel().getSelectedItem(); } /** @@ -695,15 +652,6 @@ public int getBasePanelCount() { return tabbedPane.getTabs().size(); } - private Tab getTab(BasePanel comp) { - for (Tab tab : tabbedPane.getTabs()) { - if (tab.getContent() == comp) { - return tab; - } - } - return null; - } - /** * @deprecated do not operate on tabs but on BibDatabaseContexts */ @@ -712,14 +660,6 @@ public TabPane getTabbedPane() { return tabbedPane; } - public void setTabTitle(BasePanel comp, String title, String toolTip) { - DefaultTaskExecutor.runInJavaFXThread(() -> { - Tab tab = getTab(comp); - tab.setText(title); - tab.setTooltip(new Tooltip(toolTip)); - }); - } - private MenuBar createMenu() { ActionFactory factory = new ActionFactory(Globals.getKeyPrefs()); Menu file = new Menu(Localization.lang("File")); @@ -1025,21 +965,23 @@ hide it and clip it to a square of (width x width) each time width is updated. public void addParserResult(ParserResult parserResult, boolean focusPanel) { if (parserResult.toOpenTab()) { // Add the entries to the open tab. - BasePanel panel = getCurrentBasePanel(); - if (panel == null) { + LibraryTab libraryTab = getCurrentLibraryTab(); + if (libraryTab == null) { // There is no open tab to add to, so we create a new tab: addTab(parserResult.getDatabaseContext(), focusPanel); } else { - addImportedEntries(panel, parserResult); + addImportedEntries(libraryTab, parserResult); } } else { // only add tab if DB is not already open - Optional panel = getBasePanelList().stream() - .filter(p -> p.getBibDatabaseContext().getDatabasePath().equals(parserResult.getPath())) - .findFirst(); - - if (panel.isPresent()) { - tabbedPane.getSelectionModel().select(getTab(panel.get())); + Optional libraryTab = getLibraryTabs().stream() + .filter(p -> p.getBibDatabaseContext() + .getDatabasePath() + .equals(parserResult.getPath())) + .findFirst(); + + if (libraryTab.isPresent()) { + tabbedPane.getSelectionModel().select(libraryTab.get()); } else { addTab(parserResult.getDatabaseContext(), focusPanel); } @@ -1047,66 +989,17 @@ public void addParserResult(ParserResult parserResult, boolean focusPanel) { } /** - * This method causes all open BasePanels to set up their tables anew. When called from PrefsDialog3, this updates - * to the new settings. - */ + * This method causes all open LibraryTabs to set up their tables anew. When called from PreferencesDialogViewModel, + * this updates to the new settings. + * We need to notify all tabs about the changes to avoid problems when changing the column set. + * */ public void setupAllTables() { - // This action can be invoked without an open database, so - // we have to check if we have one before trying to invoke - // methods to execute changes in the preferences. - - // We want to notify all tabs about the changes to - // avoid problems when changing the column set. - for (int i = 0; i < tabbedPane.getTabs().size(); i++) { - BasePanel bf = getBasePanelAt(i); - - // Update tables: - if (bf.getDatabase() != null) { - DefaultTaskExecutor.runInJavaFXThread(bf::setupMainPanel); - } - } - } - - private List collectDatabaseFilePaths() { - List dbPaths = new ArrayList<>(getBasePanelCount()); - - for (BasePanel basePanel : getBasePanelList()) { - // db file exists - if (basePanel.getBibDatabaseContext().getDatabasePath().isPresent()) { - dbPaths.add(basePanel.getBibDatabaseContext().getDatabasePath().get().toAbsolutePath().toString()); - } else { - dbPaths.add(""); + tabbedPane.getTabs().forEach(tab -> { + LibraryTab libraryTab = (LibraryTab) tab; + if (libraryTab.getDatabase() != null) { + DefaultTaskExecutor.runInJavaFXThread(libraryTab::setupMainPanel); } - } - return dbPaths; - } - - private List getUniquePathParts() { - List dbPaths = collectDatabaseFilePaths(); - - return FileUtil.uniquePathSubstrings(dbPaths); - } - - public void updateAllTabTitles() { - List paths = getUniquePathParts(); - for (int i = 0; i < getBasePanelCount(); i++) { - String uniqPath = paths.get(i); - Optional file = getBasePanelAt(i).getBibDatabaseContext().getDatabasePath(); - - if (file.isPresent()) { - if (!uniqPath.equals(file.get().getFileName().toString()) && uniqPath.contains(File.separator)) { - // remove filename - uniqPath = uniqPath.substring(0, uniqPath.lastIndexOf(File.separator)); - tabbedPane.getTabs().get(i).setText(getBasePanelAt(i).getTabTitle() + " \u2014 " + uniqPath); - } else { - // set original filename (again) - tabbedPane.getTabs().get(i).setText(getBasePanelAt(i).getTabTitle()); - } - } else { - tabbedPane.getTabs().get(i).setText(getBasePanelAt(i).getTabTitle()); - } - tabbedPane.getTabs().get(i).setTooltip(new Tooltip(file.map(Path::toAbsolutePath).map(Path::toString).orElse(null))); - } + }); } private ContextMenu createTabContextMenu(KeyBindingRepository keyBindingRepository) { @@ -1125,55 +1018,48 @@ private ContextMenu createTabContextMenu(KeyBindingRepository keyBindingReposito return contextMenu; } - public void addTab(BasePanel basePanel, boolean raisePanel) { - // add tab - Tab newTab = new Tab(basePanel.getTabTitle(), basePanel); - tabbedPane.getTabs().add(newTab); - newTab.setOnCloseRequest(event -> { - closeTab((BasePanel) newTab.getContent()); + public void addTab(LibraryTab libraryTab, boolean raisePanel) { + tabbedPane.getTabs().add(libraryTab); + + libraryTab.setOnCloseRequest(event -> { + closeTab(libraryTab); event.consume(); }); - // add tab context menu - newTab.setContextMenu(createTabContextMenu(Globals.getKeyPrefs())); - - // update all tab titles - updateAllTabTitles(); + libraryTab.setContextMenu(createTabContextMenu(Globals.getKeyPrefs())); if (raisePanel) { - tabbedPane.getSelectionModel().select(newTab); + tabbedPane.getSelectionModel().select(libraryTab); } - // Register undo/redo listener - basePanel.getUndoManager().registerListener(new UndoRedoEventManager()); + libraryTab.getUndoManager().registerListener(new UndoRedoEventManager()); - BibDatabaseContext context = basePanel.getBibDatabaseContext(); + BibDatabaseContext context = libraryTab.getBibDatabaseContext(); if (readyForAutosave(context)) { AutosaveManager autosaver = AutosaveManager.start(context); - autosaver.registerListener(new AutosaveUiManager(basePanel)); + autosaver.registerListener(new AutosaveUiManager(libraryTab)); } BackupManager.start(context, Globals.entryTypesManager, prefs); - // Track opening - trackOpenNewDatabase(basePanel); + trackOpenNewDatabase(libraryTab); } - private void trackOpenNewDatabase(BasePanel basePanel) { + private void trackOpenNewDatabase(LibraryTab libraryTab) { Map properties = new HashMap<>(); Map measurements = new HashMap<>(); - measurements.put("NumberOfEntries", (double) basePanel.getBibDatabaseContext().getDatabase().getEntryCount()); + measurements.put("NumberOfEntries", (double) libraryTab.getBibDatabaseContext().getDatabase().getEntryCount()); Globals.getTelemetryClient().ifPresent(client -> client.trackEvent("OpenNewDatabase", properties, measurements)); } - public BasePanel addTab(BibDatabaseContext databaseContext, boolean raisePanel) { + public LibraryTab addTab(BibDatabaseContext databaseContext, boolean raisePanel) { Objects.requireNonNull(databaseContext); - BasePanel bp = new BasePanel(this, BasePanelPreferences.from(Globals.prefs), databaseContext, ExternalFileTypes.getInstance()); - addTab(bp, raisePanel); - return bp; + LibraryTab libraryTab = new LibraryTab(this, prefs, databaseContext, ExternalFileTypes.getInstance()); + addTab(libraryTab, raisePanel); + return libraryTab; } private boolean readyForAutosave(BibDatabaseContext context) { @@ -1189,7 +1075,7 @@ private boolean readyForAutosave(BibDatabaseContext context) { * @param panel The BasePanel to add to. * @param parserResult The entries to add. */ - private void addImportedEntries(final BasePanel panel, final ParserResult parserResult) { + private void addImportedEntries(final LibraryTab panel, final ParserResult parserResult) { BackgroundTask task = BackgroundTask.wrap(() -> parserResult); ImportCleanup cleanup = new ImportCleanup(panel.getBibDatabaseContext().getMode()); cleanup.doPostCleanup(parserResult.getDatabase().getEntries()); @@ -1202,47 +1088,17 @@ public FileHistoryMenu getFileHistory() { return fileHistory; } - /** - * Return a boolean, if the selected entry have file - * - * @param selectEntryList A selected entries list of the current base pane - * @return true, if the selected entry contains file. false, if multiple entries are selected or the selected entry - * doesn't contains file - */ - private boolean isExistFile(List selectEntryList) { - if (selectEntryList.size() == 1) { - BibEntry selectedEntry = selectEntryList.get(0); - return selectedEntry.getField(StandardField.FILE).isPresent(); - } - return false; - } - - /** - * Return a boolean, if the selected entry have url or doi - * - * @param selectEntryList A selected entries list of the current base pane - * @return true, if the selected entry contains url or doi. false, if multiple entries are selected or the selected - * entry doesn't contains url or doi - */ - private boolean isExistURLorDOI(List selectEntryList) { - if (selectEntryList.size() == 1) { - BibEntry selectedEntry = selectEntryList.get(0); - return (selectedEntry.getField(StandardField.URL).isPresent() || selectedEntry.getField(StandardField.DOI).isPresent()); - } - return false; - } - /** * Ask if the user really wants to close the given database * * @return true if the user choose to close the database */ - private boolean confirmClose(BasePanel panel) { - String filename = panel.getBibDatabaseContext() - .getDatabasePath() - .map(Path::toAbsolutePath) - .map(Path::toString) - .orElse(Localization.lang("untitled")); + private boolean confirmClose(LibraryTab libraryTab) { + String filename = libraryTab.getBibDatabaseContext() + .getDatabasePath() + .map(Path::toAbsolutePath) + .map(Path::toString) + .orElse(Localization.lang("untitled")); ButtonType saveChanges = new ButtonType(Localization.lang("Save changes"), ButtonBar.ButtonData.YES); ButtonType discardChanges = new ButtonType(Localization.lang("Discard changes"), ButtonBar.ButtonData.NO); @@ -1256,7 +1112,7 @@ private boolean confirmClose(BasePanel panel) { if (response.isPresent() && response.get().equals(saveChanges)) { // The user wants to save. try { - SaveDatabaseAction saveAction = new SaveDatabaseAction(panel, Globals.prefs, Globals.entryTypesManager); + SaveDatabaseAction saveAction = new SaveDatabaseAction(libraryTab, Globals.prefs, Globals.entryTypesManager); if (saveAction.save()) { return true; } @@ -1272,17 +1128,17 @@ private boolean confirmClose(BasePanel panel) { return response.isEmpty() || !response.get().equals(cancel); } - private void closeTab(BasePanel panel) { + private void closeTab(LibraryTab libraryTab) { // empty tab without database - if (panel == null) { + if (libraryTab == null) { return; } - final BibDatabaseContext context = panel.getBibDatabaseContext(); + final BibDatabaseContext context = libraryTab.getBibDatabaseContext(); - if (panel.isModified() && (context.getLocation() == DatabaseLocation.LOCAL)) { - if (confirmClose(panel)) { - removeTab(panel); + if (libraryTab.isModified() && (context.getLocation() == DatabaseLocation.LOCAL)) { + if (confirmClose(libraryTab)) { + removeTab(libraryTab); } else { return; } @@ -1290,26 +1146,23 @@ private void closeTab(BasePanel panel) { context.convertToLocalDatabase(); context.getDBMSSynchronizer().closeSharedDatabase(); context.clearDBMSSynchronizer(); - removeTab(panel); + removeTab(libraryTab); } else { - removeTab(panel); + removeTab(libraryTab); } AutosaveManager.shutdown(context); BackupManager.shutdown(context); } - private void removeTab(BasePanel panel) { + private void removeTab(LibraryTab libraryTab) { DefaultTaskExecutor.runInJavaFXThread(() -> { - panel.cleanUp(); - tabbedPane.getTabs().remove(getTab(panel)); - setWindowTitle(); - // update tab titles - updateAllTabTitles(); + libraryTab.cleanUp(); + tabbedPane.getTabs().remove(libraryTab); }); } public void closeCurrentTab() { - removeTab(getCurrentBasePanel()); + removeTab(getCurrentLibraryTab()); } public OpenDatabaseAction getOpenDatabaseAction() { @@ -1351,7 +1204,7 @@ private class CloseDatabaseAction extends SimpleCommand { @Override public void execute() { - closeTab(getCurrentBasePanel()); + closeTab(getCurrentLibraryTab()); } } @@ -1363,11 +1216,11 @@ public CloseOthersDatabaseAction() { @Override public void execute() { - BasePanel currentBasePanel = getCurrentBasePanel(); + LibraryTab currentLibraryTab = getCurrentLibraryTab(); for (Tab tab : tabbedPane.getTabs()) { - BasePanel basePanel = getBasePanel(tab); - if (basePanel != currentBasePanel) { - closeTab(basePanel); + LibraryTab libraryTab = (LibraryTab) tab; + if (libraryTab != currentLibraryTab) { + closeTab(libraryTab); } } } @@ -1378,8 +1231,7 @@ private class CloseAllDatabaseAction extends SimpleCommand { @Override public void execute() { for (Tab tab : tabbedPane.getTabs()) { - BasePanel basePanel = getBasePanel(tab); - closeTab(basePanel); + closeTab((LibraryTab) tab); } } } @@ -1403,7 +1255,7 @@ private class UndoRedoEventManager { @Subscribe public void listen(UndoRedoEvent event) { updateTexts(event); - JabRefFrame.this.getCurrentBasePanel().updateEntryEditorIfShowing(); + JabRefFrame.this.getCurrentLibraryTab().updateEntryEditorIfShowing(); } @Subscribe diff --git a/src/main/java/org/jabref/gui/JabRefGUI.java b/src/main/java/org/jabref/gui/JabRefGUI.java index c107fd46004..f9c9d646282 100644 --- a/src/main/java/org/jabref/gui/JabRefGUI.java +++ b/src/main/java/org/jabref/gui/JabRefGUI.java @@ -195,9 +195,9 @@ private void openDatabases() { for (int i = 0; (i < bibDatabases.size()) && (i < mainFrame.getBasePanelCount()); i++) { ParserResult pr = bibDatabases.get(i); - BasePanel panel = mainFrame.getBasePanelAt(i); + LibraryTab libraryTab = mainFrame.getLibraryTabAt(i); - OpenDatabaseAction.performPostOpenActions(panel, pr); + OpenDatabaseAction.performPostOpenActions(libraryTab, pr); } LOGGER.debug("Finished adding panels"); @@ -215,17 +215,17 @@ private void saveWindowState(Stage mainStage) { /** * outprints the Data from the Screen (only in debug mode) * - * @param mainStage + * @param mainStage JabRefs stage */ private void debugLogWindowState(Stage mainStage) { if (LOGGER.isDebugEnabled()) { StringBuilder debugLogString = new StringBuilder(); debugLogString.append("SCREEN DATA:"); - debugLogString.append("mainStage.WINDOW_MAXIMISED: " + mainStage.isMaximized() + "\n"); - debugLogString.append("mainStage.POS_X: " + mainStage.getX() + "\n"); - debugLogString.append("mainStage.POS_Y: " + mainStage.getY() + "\n"); - debugLogString.append("mainStage.SIZE_X: " + mainStage.getWidth() + "\n"); - debugLogString.append("mainStages.SIZE_Y: " + mainStage.getHeight() + "\n"); + debugLogString.append("mainStage.WINDOW_MAXIMISED: ").append(mainStage.isMaximized()).append("\n"); + debugLogString.append("mainStage.POS_X: ").append(mainStage.getX()).append("\n"); + debugLogString.append("mainStage.POS_Y: ").append(mainStage.getY()).append("\n"); + debugLogString.append("mainStage.SIZE_X: ").append(mainStage.getWidth()).append("\n"); + debugLogString.append("mainStages.SIZE_Y: ").append(mainStage.getHeight()).append("\n"); LOGGER.debug(debugLogString.toString()); } } diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/LibraryTab.java similarity index 71% rename from src/main/java/org/jabref/gui/BasePanel.java rename to src/main/java/org/jabref/gui/LibraryTab.java index e98e641a73b..2cb7174d95c 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -1,16 +1,22 @@ package org.jabref.gui; +import java.io.File; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.collections.ListChangeListener; import javafx.geometry.Orientation; import javafx.scene.Node; import javafx.scene.control.SplitPane; -import javafx.scene.layout.StackPane; +import javafx.scene.control.Tab; +import javafx.scene.control.Tooltip; import org.jabref.gui.autocompleter.AutoCompletePreferences; import org.jabref.gui.autocompleter.PersonNameSuggestionProvider; @@ -34,6 +40,7 @@ import org.jabref.logic.search.SearchQuery; import org.jabref.logic.shared.DatabaseLocation; import org.jabref.logic.util.UpdateField; +import org.jabref.logic.util.io.FileUtil; import org.jabref.model.FieldChange; import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; @@ -45,7 +52,7 @@ import org.jabref.model.entry.event.EntryChangedEvent; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; -import org.jabref.preferences.JabRefPreferences; +import org.jabref.preferences.PreferencesService; import com.google.common.eventbus.Subscribe; import com.tobiasdiez.easybind.EasyBind; @@ -53,9 +60,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class BasePanel extends StackPane { +public class LibraryTab extends Tab { - private static final Logger LOGGER = LoggerFactory.getLogger(BasePanel.class); + private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); private final BibDatabaseContext bibDatabaseContext; private final MainTableDataModel tableModel; @@ -71,39 +78,45 @@ public class BasePanel extends StackPane { private final EntryEditor entryEditor; private final DialogService dialogService; + private final PreferencesService preferencesService; + private MainTable mainTable; - private BasePanelPreferences preferences; private BasePanelMode mode = BasePanelMode.SHOWING_NOTHING; private SplitPane splitPane; private DatabaseChangePane changePane; private boolean saving; private PersonNameSuggestionProvider searchAutoCompleter; - private boolean baseChanged; - private boolean nonUndoableChange; + + private final BooleanProperty changedProperty = new SimpleBooleanProperty(false); + private final BooleanProperty nonUndoableChangeProperty = new SimpleBooleanProperty(false); // Used to track whether the base has changed since last save. + private BibEntry showing; private SuggestionProviders suggestionProviders; - @SuppressWarnings({"FieldCanBeLocal", "unused"}) private Subscription dividerPositionSubscription; + @SuppressWarnings({"FieldCanBeLocal"}) private Subscription dividerPositionSubscription; // the query the user searches when this BasePanel is active private Optional currentSearchQuery = Optional.empty(); private Optional changeMonitor = Optional.empty(); - public BasePanel(JabRefFrame frame, BasePanelPreferences preferences, BibDatabaseContext bibDatabaseContext, ExternalFileTypes externalFileTypes) { - this.preferences = Objects.requireNonNull(preferences); + public LibraryTab(JabRefFrame frame, + PreferencesService preferencesService, + BibDatabaseContext bibDatabaseContext, + ExternalFileTypes externalFileTypes) { this.frame = Objects.requireNonNull(frame); this.bibDatabaseContext = Objects.requireNonNull(bibDatabaseContext); this.externalFileTypes = Objects.requireNonNull(externalFileTypes); this.undoManager = frame.getUndoManager(); this.dialogService = frame.getDialogService(); + this.preferencesService = Objects.requireNonNull(preferencesService); bibDatabaseContext.getDatabase().registerListener(this); bibDatabaseContext.getMetaData().registerListener(this); this.sidePaneManager = frame.getSidePaneManager(); - this.tableModel = new MainTableDataModel(getBibDatabaseContext(), Globals.prefs, Globals.stateManager); + this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferencesService, Globals.stateManager); citationStyleCache = new CitationStyleCache(bibDatabaseContext); - annotationCache = new FileAnnotationCache(bibDatabaseContext, Globals.prefs.getFilePreferences()); + annotationCache = new FileAnnotationCache(bibDatabaseContext, preferencesService.getFilePreferences()); setupMainPanel(); setupAutoCompletion(); @@ -116,54 +129,136 @@ public BasePanel(JabRefFrame frame, BasePanelPreferences preferences, BibDatabas // ensure that all entry changes mark the panel as changed this.bibDatabaseContext.getDatabase().registerListener(this); - this.getDatabase().registerListener(new UpdateTimestampListener(Globals.prefs)); + this.getDatabase().registerListener(new UpdateTimestampListener(preferencesService)); this.entryEditor = new EntryEditor(this, externalFileTypes); - } - @Subscribe - public void listen(BibDatabaseContextChangedEvent event) { - this.markBaseChanged(); + Platform.runLater(() -> { + EasyBind.subscribe(changedProperty, this::updateTabTitle); + Globals.stateManager.getOpenDatabases().addListener((ListChangeListener) c -> + updateTabTitle(changedProperty.getValue())); + }); } /** - * Returns a collection of suggestion providers, which are populated from the current library. + * Sets the title of the tab + * modification-asterisk filename – path-fragment + * + * The modification-asterisk (*) is shown if the file was modified since last save + * (path-fragment is only shown if filename is not (globally) unique) + * + * Example: + * *jabref-authors.bib – testbib */ - public SuggestionProviders getSuggestionProviders() { - return suggestionProviders; - } + public void updateTabTitle(boolean isChanged) { + boolean isAutosaveEnabled = preferencesService.getShouldAutosave(); + + DatabaseLocation databaseLocation = bibDatabaseContext.getLocation(); + Optional file = bibDatabaseContext.getDatabasePath(); - public String getTabTitle() { - StringBuilder title = new StringBuilder(); - DatabaseLocation databaseLocation = this.bibDatabaseContext.getLocation(); - boolean isAutosaveEnabled = Globals.prefs.getBoolean(JabRefPreferences.LOCAL_AUTO_SAVE); + StringBuilder tabTitle = new StringBuilder(); + StringBuilder toolTipText = new StringBuilder(); - if (databaseLocation == DatabaseLocation.LOCAL) { - if (this.bibDatabaseContext.getDatabasePath().isPresent()) { - title.append(this.bibDatabaseContext.getDatabasePath().get().getFileName()); - if (isModified() && !isAutosaveEnabled) { - title.append("*"); - } - } else { - title.append(Localization.lang("untitled")); + if (file.isPresent()) { + // Modification asterisk + if (isChanged && !isAutosaveEnabled) { + tabTitle.append('*'); + } + + // Filename + Path databasePath = file.get(); + String fileName = databasePath.getFileName().toString(); + tabTitle.append(fileName); + toolTipText.append(databasePath.toAbsolutePath().toString()); + + if (databaseLocation == DatabaseLocation.SHARED) { + tabTitle.append(" \u2013 "); + addSharedDbInformation(tabTitle, bibDatabaseContext); + toolTipText.append(' '); + addSharedDbInformation(toolTipText, bibDatabaseContext); + } + + // Database mode + addModeInfo(toolTipText, bibDatabaseContext); - if (getDatabase().hasEntries()) { + // Changed information (tooltip) + if (isChanged && !isAutosaveEnabled) { + addChangedInformation(toolTipText, fileName); + } + + // Unique path fragment + List uniquePathParts = FileUtil.uniquePathSubstrings(collectAllDatabasePaths()); + Optional uniquePathPart = uniquePathParts.stream() + .filter(part -> databasePath.toString().contains(part) + && !part.equals(fileName) && part.contains(File.separator)) + .findFirst(); + if (uniquePathPart.isPresent()) { + String uniquePath = uniquePathPart.get(); + // remove filename + uniquePath = uniquePath.substring(0, uniquePath.lastIndexOf(File.separator)); + tabTitle.append(" \u2013 ").append(uniquePath); + } + } else { + if (databaseLocation == DatabaseLocation.LOCAL) { + tabTitle.append(Localization.lang("untitled")); + if (bibDatabaseContext.getDatabase().hasEntries()) { // if the database is not empty and no file is assigned, // the database came from an import and has to be treated somehow // -> mark as changed - // This also happens internally at basepanel to ensure consistency line 224 - title.append('*'); + tabTitle.append('*'); } + } else { + addSharedDbInformation(tabTitle, bibDatabaseContext); + addSharedDbInformation(toolTipText, bibDatabaseContext); + } + addModeInfo(toolTipText, bibDatabaseContext); + if (databaseLocation == DatabaseLocation.LOCAL && bibDatabaseContext.getDatabase().hasEntries()) { + addChangedInformation(toolTipText, Localization.lang("untitled")); } - } else if (databaseLocation == DatabaseLocation.SHARED) { - title.append(this.bibDatabaseContext.getDBMSSynchronizer().getDBName() + " [" + Localization.lang("shared") + "]"); } - return title.toString(); + textProperty().setValue(tabTitle.toString()); + setTooltip(new Tooltip(toolTipText.toString())); } - public boolean isModified() { - return baseChanged; + private static void addChangedInformation(StringBuilder text, String fileName) { + text.append("\n"); + text.append(Localization.lang("Library '%0' has changed.", fileName)); + } + + private static void addModeInfo(StringBuilder text, BibDatabaseContext bibDatabaseContext) { + String mode = bibDatabaseContext.getMode().getFormattedName(); + String modeInfo = String.format("\n%s", Localization.lang("%0 mode", mode)); + text.append(modeInfo); + } + + private static void addSharedDbInformation(StringBuilder text, BibDatabaseContext bibDatabaseContext) { + text.append(bibDatabaseContext.getDBMSSynchronizer().getDBName()); + text.append(" ["); + text.append(Localization.lang("shared")); + text.append("]"); + } + + private List collectAllDatabasePaths() { + List list = new ArrayList<>(); + Globals.stateManager.getOpenDatabases().stream() + .map(BibDatabaseContext::getDatabasePath) + .forEachOrdered(pathOptional -> pathOptional.ifPresentOrElse( + path -> list.add(path.toAbsolutePath().toString()), + () -> list.add(""))); + return list; + } + + @Subscribe + public void listen(BibDatabaseContextChangedEvent event) { + this.changedProperty.setValue(true); + } + + /** + * Returns a collection of suggestion providers, which are populated from the current library. + */ + public SuggestionProviders getSuggestionProviders() { + return suggestionProviders; } public BasePanelMode getMode() { @@ -178,10 +273,6 @@ public JabRefFrame frame() { return frame; } - public void output(String s) { - dialogService.notify(s); - } - /** * Removes the selected entries from the database * @@ -210,8 +301,8 @@ private void delete(boolean cut, List entries) { bibDatabaseContext.getDatabase().removeEntries(entries); ensureNotShowingBottomPanel(entries); - markBaseChanged(); - this.output(formatOutputMessage(cut ? Localization.lang("Cut") : Localization.lang("Deleted"), entries.size())); + this.changedProperty.setValue(true); + dialogService.notify(formatOutputMessage(cut ? Localization.lang("Cut") : Localization.lang("Deleted"), entries.size())); // prevent the main table from loosing focus mainTable.requestFocus(); @@ -255,14 +346,14 @@ public void insertEntries(final List entries) { UpdateField.setAutomaticFields(entry, true, true, - Globals.prefs.getOwnerPreferences(), - Globals.prefs.getTimestampPreferences()); + preferencesService.getOwnerPreferences(), + preferencesService.getTimestampPreferences()); } // Create an UndoableInsertEntries object. getUndoManager().addEdit(new UndoableInsertEntries(bibDatabaseContext.getDatabase(), entries)); - markBaseChanged(); // The database just changed. - if (Globals.prefs.getBoolean(JabRefPreferences.AUTO_OPEN_FORM)) { + this.changedProperty.setValue(true); // The database just changed. + if (preferencesService.getEntryEditorPreferences().shouldOpenOnNewEntry()) { showAndEdit(entries.get(0)); } clearAndSelect(entries.get(0)); @@ -284,11 +375,11 @@ private void createMainTable() { mainTable = new MainTable(tableModel, this, bibDatabaseContext, - Globals.prefs, + preferencesService, dialogService, Globals.stateManager, externalFileTypes, - preferences.getKeyBindings()); + Globals.getKeyPrefs()); // Add the listener that binds selection to state manager (TODO: should be replaced by proper JavaFX binding as soon as table is implemented in JavaFX) mainTable.addSelectionListener(listEvent -> Globals.stateManager.setSelectedEntries(mainTable.getSelectedEntries())); @@ -301,11 +392,8 @@ private void createMainTable() { } public void setupMainPanel() { - preferences = BasePanelPreferences.from(Globals.prefs); - splitPane = new SplitPane(); splitPane.setOrientation(Orientation.VERTICAL); - adjustSplitter(); // restore last splitting state (before mainTable is created as creation affects the stored size of the entryEditors) createMainTable(); @@ -327,10 +415,10 @@ public void setupMainPanel() { // if the database is not empty and no file is assigned, // the database came from an import and has to be treated somehow // -> mark as changed - this.baseChanged = true; + this.changedProperty.setValue(true); } changePane = null; - getChildren().add(splitPane); + this.setContent(splitPane); } } @@ -338,7 +426,7 @@ public void setupMainPanel() { * Set up auto completion for this database */ private void setupAutoCompletion() { - AutoCompletePreferences autoCompletePreferences = preferences.getAutoCompletePreferences(); + AutoCompletePreferences autoCompletePreferences = preferencesService.getAutoCompletePreferences(); if (autoCompletePreferences.shouldAutoComplete()) { suggestionProviders = new SuggestionProviders(getDatabase(), Globals.journalAbbreviationRepository); } else { @@ -352,12 +440,6 @@ public void updateSearchManager() { frame.getGlobalSearchBar().setAutoCompleter(searchAutoCompleter); } - private void adjustSplitter() { - if (mode == BasePanelMode.SHOWING_EDITOR) { - splitPane.setDividerPositions(preferences.getEntryEditorDividerPosition()); - } - } - public EntryEditor getEntryEditor() { return entryEditor; } @@ -390,7 +472,8 @@ private void showBottomPane(BasePanelMode newMode) { splitPane.getItems().add(1, pane); } mode = newMode; - adjustSplitter(); + + splitPane.setDividerPositions(preferencesService.getEntryEditorPreferences().getDividerPosition()); } /** @@ -409,16 +492,6 @@ public void clearAndSelect(final BibEntry bibEntry) { mainTable.clearAndSelect(bibEntry); } - /** - * Select and open entry editor for first entry in main table. - */ - private void clearAndSelectFirst() { - mainTable.clearAndSelectFirst(); - if (!mainTable.getSelectedEntries().isEmpty()) { - showAndEdit(mainTable.getSelectedEntries().get(0)); - } - } - public void selectPreviousEntry() { mainTable.getSelectionModel().clearAndSelect(mainTable.getSelectionModel().getSelectedIndex() - 1); } @@ -455,32 +528,16 @@ public void updateEntryEditorIfShowing() { } } - public void markBaseChanged() { - baseChanged = true; - // Put an asterisk behind the filename to indicate the database has changed. - frame.setWindowTitle(); - DefaultTaskExecutor.runInJavaFXThread(frame::updateAllTabTitles); - } - - public void markNonUndoableBaseChanged() { - nonUndoableChange = true; - markBaseChanged(); - } + /** + * Put an asterisk behind the filename to indicate the database has changed. + */ public synchronized void markChangedOrUnChanged() { if (getUndoManager().hasChanged()) { - if (!baseChanged) { - markBaseChanged(); - } - } else if (baseChanged && !nonUndoableChange) { - baseChanged = false; - if (getBibDatabaseContext().getDatabasePath().isPresent()) { - frame.setTabTitle(this, getTabTitle(), getBibDatabaseContext().getDatabasePath().get().toAbsolutePath().toString()); - } else { - frame.setTabTitle(this, Localization.lang("untitled"), null); - } + this.changedProperty.setValue(true); + } else if (changedProperty.getValue() && !nonUndoableChangeProperty.getValue()) { + this.changedProperty.setValue(false); } - frame.setWindowTitle(); } public BibDatabase getDatabase() { @@ -488,7 +545,7 @@ public BibDatabase getDatabase() { } private boolean showDeleteConfirmationDialog(int numberOfEntries) { - if (Globals.prefs.getBoolean(JabRefPreferences.CONFIRM_DELETE)) { + if (preferencesService.getGeneralPreferences().shouldConfirmDelete()) { String title = Localization.lang("Delete entry"); String message = Localization.lang("Really delete the selected entry?"); String okButton = Localization.lang("Delete entry"); @@ -505,7 +562,8 @@ private boolean showDeleteConfirmationDialog(int numberOfEntries) { okButton, cancelButton, Localization.lang("Disable this confirmation dialog"), - optOut -> Globals.prefs.putBoolean(JabRefPreferences.CONFIRM_DELETE, !optOut)); + optOut -> preferencesService.storeGeneralPreferences( + preferencesService.getGeneralPreferences().withConfirmDelete(!optOut))); } else { return true; } @@ -517,7 +575,8 @@ private boolean showDeleteConfirmationDialog(int numberOfEntries) { */ private void saveDividerLocation(Number position) { if (mode == BasePanelMode.SHOWING_EDITOR) { - preferences.setEntryEditorDividerPosition(position.doubleValue()); + preferencesService.storeEntryEditorPreferences( + preferencesService.getEntryEditorPreferences().withDividerPosition(position.doubleValue())); } } @@ -546,14 +605,6 @@ public SidePaneManager getSidePaneManager() { return sidePaneManager; } - public void setNonUndoableChange(boolean nonUndoableChange) { - this.nonUndoableChange = nonUndoableChange; - } - - public void setBaseChanged(boolean baseChanged) { - this.baseChanged = baseChanged; - } - public boolean isSaving() { return saving; } @@ -603,7 +654,7 @@ public void resetChangeMonitorAndChangePane() { changePane = new DatabaseChangePane(splitPane, bibDatabaseContext, changeMonitor.get()); - this.getChildren().setAll(changePane); + this.setContent(changePane); } public void copy() { @@ -618,6 +669,32 @@ public void cut() { mainTable.cut(); } + public BooleanProperty changedProperty() { + return changedProperty; + } + + public boolean isModified() { + return changedProperty.getValue(); + } + + public void markBaseChanged() { + this.changedProperty.setValue(true); + } + + public BooleanProperty nonUndoableChangeProperty() { + return nonUndoableChangeProperty; + } + + public void markNonUndoableBaseChanged() { + this.nonUndoableChangeProperty.setValue(true); + this.changedProperty.setValue(true); + } + + public void resetChangedProperties() { + this.nonUndoableChangeProperty.setValue(false); + this.changedProperty.setValue(false); + } + private class GroupTreeListener { @Subscribe @@ -628,7 +705,7 @@ public void listen(EntriesAddedEvent addedEntriesEvent) { } // Automatically add new entries to the selected group (or set of groups) - if (Globals.prefs.getBoolean(JabRefPreferences.AUTO_ASSIGN_GROUP)) { + if (preferencesService.getGroupsPreferences().shouldAutoAssignGroup()) { Globals.stateManager.getSelectedGroup(bibDatabaseContext).forEach( selectedGroup -> selectedGroup.addEntriesToGroup(addedEntriesEvent.getBibEntries())); } diff --git a/src/main/java/org/jabref/gui/StateManager.java b/src/main/java/org/jabref/gui/StateManager.java index 7554467b7ed..f1ffee17247 100644 --- a/src/main/java/org/jabref/gui/StateManager.java +++ b/src/main/java/org/jabref/gui/StateManager.java @@ -44,6 +44,7 @@ public class StateManager { private final CustomLocalDragboard localDragboard = new CustomLocalDragboard(); + private final ObservableList openDatabases = FXCollections.observableArrayList(); private final OptionalObjectProperty activeDatabase = OptionalObjectProperty.empty(); private final ReadOnlyListWrapper activeGroups = new ReadOnlyListWrapper<>(FXCollections.observableArrayList()); private final ObservableList selectedEntries = FXCollections.observableArrayList(); @@ -51,9 +52,7 @@ public class StateManager { private final OptionalObjectProperty activeSearchQuery = OptionalObjectProperty.empty(); private final ObservableMap searchResultMap = FXCollections.observableHashMap(); private final OptionalObjectProperty focusOwner = OptionalObjectProperty.empty(); - private final ObservableList> backgroundTasks = FXCollections.observableArrayList(task -> { - return new Observable[]{task.progressProperty(), task.runningProperty()}; - }); + private final ObservableList> backgroundTasks = FXCollections.observableArrayList(task -> new Observable[]{task.progressProperty(), task.runningProperty()}); private final EasyBinding anyTaskRunning = EasyBind.reduce(backgroundTasks, tasks -> tasks.anyMatch(Task::isRunning)); private final EasyBinding tasksProgress = EasyBind.reduce(backgroundTasks, tasks -> tasks.filter(Task::isRunning).mapToDouble(Task::getProgress).average().orElse(1)); private final ObservableMap dialogWindowStates = FXCollections.observableHashMap(); @@ -66,6 +65,10 @@ public CustomLocalDragboard getLocalDragboard() { return localDragboard; } + public ObservableList getOpenDatabases() { + return openDatabases; + } + public OptionalObjectProperty activeDatabaseProperty() { return activeDatabase; } diff --git a/src/main/java/org/jabref/gui/UpdateTimestampListener.java b/src/main/java/org/jabref/gui/UpdateTimestampListener.java index 4af4d661c29..aad998db035 100644 --- a/src/main/java/org/jabref/gui/UpdateTimestampListener.java +++ b/src/main/java/org/jabref/gui/UpdateTimestampListener.java @@ -1,7 +1,7 @@ package org.jabref.gui; import org.jabref.model.entry.event.EntryChangedEvent; -import org.jabref.preferences.JabRefPreferences; +import org.jabref.preferences.PreferencesService; import com.google.common.eventbus.Subscribe; @@ -9,17 +9,17 @@ * Updates the timestamp of changed entries if the feature is enabled */ class UpdateTimestampListener { - private final JabRefPreferences jabRefPreferences; + private final PreferencesService preferencesService; - UpdateTimestampListener(JabRefPreferences jabRefPreferences) { - this.jabRefPreferences = jabRefPreferences; + UpdateTimestampListener(PreferencesService preferencesService) { + this.preferencesService = preferencesService; } @Subscribe public void listen(EntryChangedEvent event) { - if (jabRefPreferences.getTimestampPreferences().includeTimestamps()) { - event.getBibEntry().setField(jabRefPreferences.getTimestampPreferences().getTimestampField(), - jabRefPreferences.getTimestampPreferences().now()); + if (preferencesService.getTimestampPreferences().includeTimestamps()) { + event.getBibEntry().setField(preferencesService.getTimestampPreferences().getTimestampField(), + preferencesService.getTimestampPreferences().now()); } } } diff --git a/src/main/java/org/jabref/gui/WaitForSaveFinishedDialog.java b/src/main/java/org/jabref/gui/WaitForSaveFinishedDialog.java index 954d3fe47b0..43f784669de 100644 --- a/src/main/java/org/jabref/gui/WaitForSaveFinishedDialog.java +++ b/src/main/java/org/jabref/gui/WaitForSaveFinishedDialog.java @@ -17,12 +17,12 @@ public WaitForSaveFinishedDialog(DialogService dialogService) { this.dialogService = dialogService; } - public void showAndWait(List basePanels) { - if (basePanels.stream().anyMatch(BasePanel::isSaving)) { - Task waitForSaveFinished = new Task() { + public void showAndWait(List LibraryTabs) { + if (LibraryTabs.stream().anyMatch(LibraryTab::isSaving)) { + Task waitForSaveFinished = new Task<>() { @Override protected Void call() throws Exception { - while (basePanels.stream().anyMatch(BasePanel::isSaving)) { + while (LibraryTabs.stream().anyMatch(LibraryTab::isSaving)) { if (isCancelled()) { return null; } else { diff --git a/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java b/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java index 518b09efc2b..5626bc8b60e 100644 --- a/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java +++ b/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java @@ -11,9 +11,9 @@ import javafx.scene.control.TextArea; import javafx.scene.control.TextField; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.FileDialogConfiguration; import org.jabref.logic.auxparser.AuxParser; @@ -32,7 +32,7 @@ */ public class FromAuxDialog extends BaseDialog { - private final BasePanel basePanel; + private final LibraryTab libraryTab; @FXML private ButtonType generateButtonType; private final Button generateButton; @FXML private TextField auxFileField; @@ -44,7 +44,7 @@ public class FromAuxDialog extends BaseDialog { @Inject private DialogService dialogService; public FromAuxDialog(JabRefFrame frame) { - basePanel = frame.getCurrentBasePanel(); + libraryTab = frame.getCurrentLibraryTab(); this.setTitle(Localization.lang("AUX file import")); ViewLoader.view(this) @@ -67,7 +67,7 @@ public FromAuxDialog(JabRefFrame frame) { private void parseActionPerformed() { notFoundList.getItems().clear(); statusInfos.setText(""); - BibDatabase refBase = basePanel.getDatabase(); + BibDatabase refBase = libraryTab.getDatabase(); String auxName = auxFileField.getText(); if ((auxName != null) && (refBase != null) && !auxName.isEmpty()) { diff --git a/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternAction.java b/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternAction.java index 74f20a3b7b4..983e9103e00 100644 --- a/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternAction.java +++ b/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternAction.java @@ -18,6 +18,6 @@ public CitationKeyPatternAction(JabRefFrame frame, StateManager stateManager) { @Override public void execute() { - new CitationKeyPatternDialog(frame.getCurrentBasePanel()).showAndWait(); + new CitationKeyPatternDialog(frame.getCurrentLibraryTab()).showAndWait(); } } diff --git a/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternDialog.java b/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternDialog.java index 4b33b9de97b..d92f35c7b35 100644 --- a/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternDialog.java +++ b/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternDialog.java @@ -2,8 +2,8 @@ import javafx.scene.control.ButtonType; -import org.jabref.gui.BasePanel; import org.jabref.gui.Globals; +import org.jabref.gui.LibraryTab; import org.jabref.gui.util.BaseDialog; import org.jabref.logic.citationkeypattern.AbstractCitationKeyPattern; import org.jabref.logic.l10n.Localization; @@ -12,13 +12,13 @@ public class CitationKeyPatternDialog extends BaseDialog { private final MetaData metaData; - private final BasePanel panel; + private final LibraryTab libraryTab; private final CitationKeyPatternPanel citationKeyPatternPanel; - public CitationKeyPatternDialog(BasePanel panel) { - this.citationKeyPatternPanel = new CitationKeyPatternPanel(panel); - this.panel = panel; - this.metaData = panel.getBibDatabaseContext().getMetaData(); + public CitationKeyPatternDialog(LibraryTab libraryTab) { + this.citationKeyPatternPanel = new CitationKeyPatternPanel(libraryTab.getBibDatabaseContext()); + this.libraryTab = libraryTab; + this.metaData = libraryTab.getBibDatabaseContext().getMetaData(); AbstractCitationKeyPattern keyPattern = metaData.getCiteKeyPattern(Globals.prefs.getGlobalCitationKeyPattern()); citationKeyPatternPanel.setValues(keyPattern); init(); @@ -34,7 +34,7 @@ private void init() { this.setResultConverter(button -> { if (button == ButtonType.APPLY) { metaData.setCiteKeyPattern(citationKeyPatternPanel.getKeyPatternAsDatabaseKeyPattern()); - panel.markNonUndoableBaseChanged(); + libraryTab.markNonUndoableBaseChanged(); } return null; diff --git a/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternPanel.java b/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternPanel.java index 56f98b88e26..9f1115d8c52 100644 --- a/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternPanel.java +++ b/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternPanel.java @@ -10,7 +10,6 @@ import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; -import org.jabref.gui.BasePanel; import org.jabref.gui.Globals; import org.jabref.gui.actions.ActionFactory; import org.jabref.gui.actions.StandardActions; @@ -19,6 +18,7 @@ import org.jabref.logic.citationkeypattern.DatabaseCitationKeyPattern; import org.jabref.logic.help.HelpFile; import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.types.EntryType; @@ -31,11 +31,11 @@ public class CitationKeyPatternPanel extends Pane { // one field for each type private final Map textFields = new HashMap<>(); - private final BasePanel panel; + private final BibDatabaseContext databaseContext; private final GridPane gridPane = new GridPane(); - public CitationKeyPatternPanel(BasePanel panel) { - this.panel = panel; + public CitationKeyPatternPanel(BibDatabaseContext databaseContext) { + this.databaseContext = databaseContext; gridPane.setHgap(10); gridPane.setVgap(5); buildGUI(); @@ -50,7 +50,12 @@ private static void setValue(TextField tf, EntryType fieldName, AbstractCitation } private void buildGUI() { - BibDatabaseMode mode; + BibDatabaseMode mode = databaseContext.getMode(); + + // The following got irrelevant - global settings for CitationKeyPattern are handled by + // commonfxcontrols/CitationKeyPatternPanel.java + // ToDo: this one should be abandoned + /* // check mode of currently used DB if (panel != null) { mode = panel.getBibDatabaseContext().getMode(); @@ -58,6 +63,7 @@ private void buildGUI() { // use preferences value if no DB is open mode = Globals.prefs.getDefaultBibDatabaseMode(); } + */ int rowIndex = 1; int columnIndex = 0; diff --git a/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java b/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java index 4173db5cc65..0cf54e3ada9 100644 --- a/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java +++ b/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java @@ -101,7 +101,7 @@ private void generateKeys() { frame.getUndoManager().addEdit(compound); } - frame.getCurrentBasePanel().markBaseChanged(); + frame.getCurrentLibraryTab().markBaseChanged(); dialogService.notify(formatOutputMessage(Localization.lang("Generated citation key for"), entries.size())); }); } diff --git a/src/main/java/org/jabref/gui/cleanup/CleanupAction.java b/src/main/java/org/jabref/gui/cleanup/CleanupAction.java index 1fa40af1f84..50e7e703c0f 100644 --- a/src/main/java/org/jabref/gui/cleanup/CleanupAction.java +++ b/src/main/java/org/jabref/gui/cleanup/CleanupAction.java @@ -108,8 +108,8 @@ private void showResults() { } if (modifiedEntriesCount > 0) { - frame.getCurrentBasePanel().updateEntryEditorIfShowing(); - frame.getCurrentBasePanel().markBaseChanged(); + frame.getCurrentLibraryTab().updateEntryEditorIfShowing(); + frame.getCurrentLibraryTab().markBaseChanged(); } if (modifiedEntriesCount == 0) { diff --git a/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialogView.java b/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialogView.java index 766258c0b60..62832cb51ad 100644 --- a/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialogView.java +++ b/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialogView.java @@ -12,8 +12,8 @@ import javafx.scene.control.ListView; import javafx.scene.control.SelectionModel; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; +import org.jabref.gui.LibraryTab; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.ControlHelper; import org.jabref.logic.l10n.Localization; @@ -41,14 +41,14 @@ public class ContentSelectorDialogView extends BaseDialog { @Inject private DialogService dialogService; - private final BasePanel basePanel; + private final LibraryTab libraryTab; private ContentSelectorDialogViewModel viewModel; - public ContentSelectorDialogView(BasePanel basePanel) { + public ContentSelectorDialogView(LibraryTab libraryTab) { this.setTitle(Localization.lang("Manage content selectors")); this.getDialogPane().setPrefSize(375, 475); - this.basePanel = basePanel; + this.libraryTab = libraryTab; ViewLoader.view(this) .load() @@ -59,7 +59,7 @@ public ContentSelectorDialogView(BasePanel basePanel) { @FXML public void initialize() { - viewModel = new ContentSelectorDialogViewModel(basePanel, dialogService); + viewModel = new ContentSelectorDialogViewModel(libraryTab, dialogService); initFieldNameComponents(); initKeywordsComponents(); diff --git a/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialogViewModel.java b/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialogViewModel.java index 7d10d5841e2..d83a4de4469 100644 --- a/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialogViewModel.java +++ b/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialogViewModel.java @@ -20,8 +20,8 @@ import javafx.collections.FXCollections; import org.jabref.gui.AbstractViewModel; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; +import org.jabref.gui.LibraryTab; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; @@ -33,7 +33,7 @@ class ContentSelectorDialogViewModel extends AbstractViewModel { private static final List DEFAULT_FIELD_NAMES = Arrays.asList(StandardField.AUTHOR, StandardField.JOURNAL, StandardField.KEYWORDS, StandardField.PUBLISHER); - private final BasePanel basePanel; + private final LibraryTab libraryTab; private final MetaData metaData; private final DialogService dialogService; private final Map> fieldKeywordsMap = new HashMap<>(); @@ -43,9 +43,9 @@ class ContentSelectorDialogViewModel extends AbstractViewModel { private ObjectProperty selectedField = new SimpleObjectProperty<>(); private StringProperty selectedKeyword = new SimpleStringProperty(); - ContentSelectorDialogViewModel(BasePanel basePanel, DialogService dialogService) { - this.basePanel = basePanel; - this.metaData = basePanel.getBibDatabaseContext().getMetaData(); + ContentSelectorDialogViewModel(LibraryTab libraryTab, DialogService dialogService) { + this.libraryTab = libraryTab; + this.metaData = libraryTab.getBibDatabaseContext().getMetaData(); this.dialogService = dialogService; populateFieldNameKeywordsMapWithExistingValues(); populateFieldNamesListWithValues(); @@ -177,8 +177,8 @@ void saveChanges() { List fieldNamesToRemove = filterFieldsToRemove(); fieldNamesToRemove.forEach(metaData::clearContentSelectors); - basePanel.setupMainPanel(); - basePanel.markNonUndoableBaseChanged(); + libraryTab.setupMainPanel(); + libraryTab.markNonUndoableBaseChanged(); } private List filterFieldsToRemove() { diff --git a/src/main/java/org/jabref/gui/contentselector/ManageContentSelectorAction.java b/src/main/java/org/jabref/gui/contentselector/ManageContentSelectorAction.java index 555afe1dc20..e91c22b6e19 100644 --- a/src/main/java/org/jabref/gui/contentselector/ManageContentSelectorAction.java +++ b/src/main/java/org/jabref/gui/contentselector/ManageContentSelectorAction.java @@ -1,7 +1,7 @@ package org.jabref.gui.contentselector; -import org.jabref.gui.BasePanel; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; @@ -19,7 +19,7 @@ public ManageContentSelectorAction(JabRefFrame jabRefFrame, StateManager stateMa @Override public void execute() { - BasePanel basePanel = jabRefFrame.getCurrentBasePanel(); - new ContentSelectorDialogView(basePanel).showAndWait(); + LibraryTab libraryTab = jabRefFrame.getCurrentLibraryTab(); + new ContentSelectorDialogView(libraryTab).showAndWait(); } } diff --git a/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java b/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java index 660995a0795..7aee99be695 100644 --- a/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java +++ b/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java @@ -1,7 +1,7 @@ package org.jabref.gui.dialogs; -import org.jabref.gui.BasePanel; import org.jabref.gui.Globals; +import org.jabref.gui.LibraryTab; import org.jabref.gui.exporter.SaveDatabaseAction; import org.jabref.model.database.event.AutosaveEvent; @@ -11,21 +11,21 @@ /** * This class has an abstract UI role as it listens for an {@link AutosaveEvent} and saves the bib file associated with - * the given {@link BasePanel}. + * the given {@link LibraryTab}. */ public class AutosaveUiManager { private static final Logger LOGGER = LoggerFactory.getLogger(AutosaveUiManager.class); - private final BasePanel panel; + private final LibraryTab libraryTab; - public AutosaveUiManager(BasePanel panel) { - this.panel = panel; + public AutosaveUiManager(LibraryTab libraryTab) { + this.libraryTab = libraryTab; } @Subscribe - public void listen(@SuppressWarnings("unused") AutosaveEvent event) { + public void listen(AutosaveEvent event) { try { - new SaveDatabaseAction(panel, Globals.prefs, Globals.entryTypesManager).save(SaveDatabaseAction.SaveDatabaseMode.SILENT); + new SaveDatabaseAction(libraryTab, Globals.prefs, Globals.entryTypesManager).save(SaveDatabaseAction.SaveDatabaseMode.SILENT); } catch (Throwable e) { LOGGER.error("Problem occurred while saving.", e); } diff --git a/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java b/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java index b0bdb122ea1..230c3165e4d 100644 --- a/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java +++ b/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java @@ -11,11 +11,11 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; import org.jabref.gui.JabRefExecutorService; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.duplicationFinder.DuplicateResolverDialog.DuplicateResolverResult; @@ -131,7 +131,7 @@ private DuplicateSearchResult verifyDuplicates() { } private void askResolveStrategy(DuplicateSearchResult result, BibEntry first, BibEntry second, DuplicateResolverType resolverType) { - DuplicateResolverDialog dialog = new DuplicateResolverDialog(first, second, resolverType, frame.getCurrentBasePanel().getBibDatabaseContext(), stateManager); + DuplicateResolverDialog dialog = new DuplicateResolverDialog(first, second, resolverType, frame.getCurrentLibraryTab().getBibDatabaseContext(), stateManager); DuplicateResolverResult resolverResult = dialog.showAndWait().orElse(DuplicateResolverResult.BREAK); @@ -156,25 +156,25 @@ private void handleDuplicates(DuplicateSearchResult result) { return; } - BasePanel panel = frame.getCurrentBasePanel(); + LibraryTab libraryTab = frame.getCurrentLibraryTab(); final NamedCompound compoundEdit = new NamedCompound(Localization.lang("duplicate removal")); // Now, do the actual removal: if (!result.getToRemove().isEmpty()) { - compoundEdit.addEdit(new UndoableRemoveEntries(panel.getDatabase(), result.getToRemove())); - panel.getDatabase().removeEntries(result.getToRemove()); - panel.markBaseChanged(); + compoundEdit.addEdit(new UndoableRemoveEntries(libraryTab.getDatabase(), result.getToRemove())); + libraryTab.getDatabase().removeEntries(result.getToRemove()); + libraryTab.markBaseChanged(); } // and adding merged entries: if (!result.getToAdd().isEmpty()) { - compoundEdit.addEdit(new UndoableInsertEntries(panel.getDatabase(), result.getToAdd())); - panel.getDatabase().insertEntries(result.getToAdd()); - panel.markBaseChanged(); + compoundEdit.addEdit(new UndoableInsertEntries(libraryTab.getDatabase(), result.getToAdd())); + libraryTab.getDatabase().insertEntries(result.getToAdd()); + libraryTab.markBaseChanged(); } dialogService.notify(Localization.lang("Duplicates found") + ": " + duplicateCount.get() + ' ' + Localization.lang("pairs processed") + ": " + result.getDuplicateCount()); compoundEdit.end(); - panel.getUndoManager().addEdit(compoundEdit); + libraryTab.getUndoManager().addEdit(compoundEdit); } /** diff --git a/src/main/java/org/jabref/gui/edit/EditAction.java b/src/main/java/org/jabref/gui/edit/EditAction.java index a979e51e7ec..5cdebd07b14 100644 --- a/src/main/java/org/jabref/gui/edit/EditAction.java +++ b/src/main/java/org/jabref/gui/edit/EditAction.java @@ -44,49 +44,31 @@ public String toString() { @Override public void execute() { stateManager.getFocusOwner().ifPresent(focusOwner -> { - LOGGER.debug("EditAction - focusOwner: {}; Action: {}", focusOwner.toString(), action.getText()); + LOGGER.debug("focusOwner: {}; Action: {}", focusOwner.toString(), action.getText()); if (focusOwner instanceof TextInputControl) { // Focus is on text field -> copy/paste/cut selected text TextInputControl textInput = (TextInputControl) focusOwner; + // DELETE_ENTRY in text field should do forward delete switch (action) { - case COPY: - textInput.copy(); - break; - case CUT: - textInput.cut(); - break; - case PASTE: - textInput.paste(); - break; - case DELETE_ENTRY: - // DELETE_ENTRY in text field should do forward delete - textInput.deleteNextChar(); - break; - default: - throw new IllegalStateException("Only cut/copy/paste supported in TextInputControl but got " + action); + case COPY -> textInput.copy(); + case CUT -> textInput.cut(); + case PASTE -> textInput.paste(); + case DELETE_ENTRY -> textInput.deleteNextChar(); + default -> throw new IllegalStateException("Only cut/copy/paste supported in TextInputControl but got " + action); } } else if (!(focusOwner instanceof CodeArea)) { - LOGGER.debug("EditAction - Else: {}", frame.getCurrentBasePanel().getTabTitle()); + LOGGER.debug("Else: {}", focusOwner.getClass().getSimpleName()); // Not sure what is selected -> copy/paste/cut selected entries - // ToDo: Should be handled by BibDatabaseContext instead of BasePanel + // ToDo: Should be handled by BibDatabaseContext instead of LibraryTab switch (action) { - case COPY: - frame.getCurrentBasePanel().copy(); - break; - case CUT: - frame.getCurrentBasePanel().cut(); - break; - case PASTE: - frame.getCurrentBasePanel().paste(); - break; - case DELETE_ENTRY: - frame.getCurrentBasePanel().delete(false); - break; - default: - throw new IllegalStateException("Only cut/copy/paste supported but got " + action); + case COPY -> frame.getCurrentLibraryTab().copy(); + case CUT -> frame.getCurrentLibraryTab().cut(); + case PASTE -> frame.getCurrentLibraryTab().paste(); + case DELETE_ENTRY -> frame.getCurrentLibraryTab().delete(false); + default -> throw new IllegalStateException("Only cut/copy/paste supported but got " + action); } } }); diff --git a/src/main/java/org/jabref/gui/edit/ReplaceStringAction.java b/src/main/java/org/jabref/gui/edit/ReplaceStringAction.java index 33b016ce9e7..7b8b9d4a288 100644 --- a/src/main/java/org/jabref/gui/edit/ReplaceStringAction.java +++ b/src/main/java/org/jabref/gui/edit/ReplaceStringAction.java @@ -16,7 +16,7 @@ public ReplaceStringAction(JabRefFrame frame, StateManager stateManager) { @Override public void execute() { - ReplaceStringView dialog = new ReplaceStringView(frame.getCurrentBasePanel()); + ReplaceStringView dialog = new ReplaceStringView(frame.getCurrentLibraryTab()); dialog.showAndWait(); } } diff --git a/src/main/java/org/jabref/gui/edit/ReplaceStringView.java b/src/main/java/org/jabref/gui/edit/ReplaceStringView.java index deb4c15c3d9..a931406c99c 100644 --- a/src/main/java/org/jabref/gui/edit/ReplaceStringView.java +++ b/src/main/java/org/jabref/gui/edit/ReplaceStringView.java @@ -6,7 +6,7 @@ import javafx.scene.control.RadioButton; import javafx.scene.control.TextField; -import org.jabref.gui.BasePanel; +import org.jabref.gui.LibraryTab; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.ControlHelper; import org.jabref.gui.util.IconValidationDecorator; @@ -28,10 +28,10 @@ public class ReplaceStringView extends BaseDialog { private final ControlsFxVisualizer visualizer = new ControlsFxVisualizer(); - public ReplaceStringView(BasePanel basePanel) { + public ReplaceStringView(LibraryTab libraryTab) { this.setTitle(Localization.lang("Replace String")); - viewModel = new ReplaceStringViewModel(basePanel); + viewModel = new ReplaceStringViewModel(libraryTab); ViewLoader.view(this) .load() diff --git a/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java b/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java index a7a534c9603..04db4bf9147 100644 --- a/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java +++ b/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java @@ -9,7 +9,7 @@ import javafx.beans.property.StringProperty; import org.jabref.gui.AbstractViewModel; -import org.jabref.gui.BasePanel; +import org.jabref.gui.LibraryTab; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableFieldChange; import org.jabref.logic.l10n.Localization; @@ -22,7 +22,7 @@ public class ReplaceStringViewModel extends AbstractViewModel { private String findString; private String replaceString; private Set fields; - private BasePanel panel; + private LibraryTab panel; private StringProperty findStringProperty = new SimpleStringProperty(); private StringProperty replaceStringProperty = new SimpleStringProperty(); @@ -30,9 +30,9 @@ public class ReplaceStringViewModel extends AbstractViewModel { private BooleanProperty allFieldReplaceProperty = new SimpleBooleanProperty(); private BooleanProperty selectOnlyProperty = new SimpleBooleanProperty(); - public ReplaceStringViewModel(BasePanel basePanel) { - Objects.requireNonNull(basePanel); - this.panel = basePanel; + public ReplaceStringViewModel(LibraryTab libraryTab) { + Objects.requireNonNull(libraryTab); + this.panel = libraryTab; } public int replace() { diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index 068e9ecd41d..7f05300ff00 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -25,9 +25,9 @@ import javafx.scene.input.TransferMode; import javafx.scene.layout.BorderPane; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; +import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.citationkeypattern.GenerateCitationKeySingleAction; import org.jabref.gui.entryeditor.fileannotationtab.FileAnnotationTab; @@ -69,7 +69,7 @@ public class EntryEditor extends BorderPane { private static final Logger LOGGER = LoggerFactory.getLogger(EntryEditor.class); - private final BasePanel panel; + private final LibraryTab libraryTab; private final BibDatabaseContext databaseContext; private final EntryEditorPreferences entryEditorPreferences; private final ExternalFilesEntryLinker fileLinker; @@ -100,9 +100,9 @@ public class EntryEditor extends BorderPane { @Inject private CountingUndoManager undoManager; private final List entryEditorTabs = new LinkedList<>(); - public EntryEditor(BasePanel panel, ExternalFileTypes externalFileTypes) { - this.panel = panel; - this.databaseContext = panel.getBibDatabaseContext(); + public EntryEditor(LibraryTab libraryTab, ExternalFileTypes externalFileTypes) { + this.libraryTab = libraryTab; + this.databaseContext = libraryTab.getBibDatabaseContext(); ViewLoader.view(this) .root(this) @@ -137,18 +137,18 @@ public EntryEditor(BasePanel panel, ExternalFileTypes externalFileTypes) { if (event.getDragboard().hasContent(DataFormat.FILES)) { List files = event.getDragboard().getFiles().stream().map(File::toPath).collect(Collectors.toList()); switch (event.getTransferMode()) { - case COPY: + case COPY -> { LOGGER.debug("Mode COPY"); fileLinker.copyFilesToFileDirAndAddToEntry(entry, files); - break; - case MOVE: + } + case MOVE -> { LOGGER.debug("Mode MOVE"); fileLinker.moveFilesToFileDirAndAddToEntry(entry, files); - break; - case LINK: + } + case LINK -> { LOGGER.debug("Mode LINK"); fileLinker.addFilesToEntry(entry, files); - break; + } } success = true; } @@ -177,11 +177,11 @@ private void setupKeyBindings() { event.consume(); break; case ENTRY_EDITOR_NEXT_ENTRY: - panel.selectNextEntry(); + libraryTab.selectNextEntry(); event.consume(); break; case ENTRY_EDITOR_PREVIOUS_ENTRY: - panel.selectPreviousEntry(); + libraryTab.selectPreviousEntry(); event.consume(); break; case HELP: @@ -202,12 +202,12 @@ private void setupKeyBindings() { @FXML public void close() { - panel.entryEditorClosing(); + libraryTab.entryEditorClosing(); } @FXML private void deleteEntry() { - panel.delete(entry); + libraryTab.delete(entry); } @FXML @@ -219,12 +219,12 @@ void generateCiteKeyButton() { @FXML private void navigateToPreviousEntry() { - panel.selectPreviousEntry(); + libraryTab.selectPreviousEntry(); } @FXML private void navigateToNextEntry() { - panel.selectNextEntry(); + libraryTab.selectNextEntry(); } private List createTabs() { @@ -232,24 +232,24 @@ private List createTabs() { entryEditorTabs.add(new PreviewTab(databaseContext, dialogService, Globals.prefs, ExternalFileTypes.getInstance())); // Required fields - entryEditorTabs.add(new RequiredFieldsTab(databaseContext, panel.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); + entryEditorTabs.add(new RequiredFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); // Optional fields - entryEditorTabs.add(new OptionalFieldsTab(databaseContext, panel.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); - entryEditorTabs.add(new OptionalFields2Tab(databaseContext, panel.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); - entryEditorTabs.add(new DeprecatedFieldsTab(databaseContext, panel.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); + entryEditorTabs.add(new OptionalFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); + entryEditorTabs.add(new OptionalFields2Tab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); + entryEditorTabs.add(new DeprecatedFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); // Other fields - entryEditorTabs.add(new OtherFieldsTab(databaseContext, panel.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); + entryEditorTabs.add(new OtherFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); // General fields from preferences for (Map.Entry> tab : entryEditorPreferences.getEntryEditorTabList().entrySet()) { - entryEditorTabs.add(new UserDefinedFieldsTab(tab.getKey(), tab.getValue(), databaseContext, panel.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); + entryEditorTabs.add(new UserDefinedFieldsTab(tab.getKey(), tab.getValue(), databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); } // Special tabs entryEditorTabs.add(new MathSciNetTab()); - entryEditorTabs.add(new FileAnnotationTab(panel.getAnnotationCache())); + entryEditorTabs.add(new FileAnnotationTab(libraryTab.getAnnotationCache())); entryEditorTabs.add(new RelatedArticlesTab(this, entryEditorPreferences, dialogService)); // Source tab @@ -361,7 +361,7 @@ private void setupToolBar() { } private void fetchAndMerge(EntryBasedFetcher fetcher) { - new FetchAndMergeEntry(panel, taskExecutor).fetchAndMerge(entry, fetcher); + new FetchAndMergeEntry(libraryTab, taskExecutor).fetchAndMerge(entry, fetcher); } public void setFocusToField(Field field) { diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java index 82d2fea3b06..278c9a85427 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java @@ -14,6 +14,7 @@ public class EntryEditorPreferences { private final boolean shouldShowLatexCitationsTab; private boolean showSourceTabByDefault; private boolean enableValidation; + private double dividerPosition; public EntryEditorPreferences(Map> entryEditorTabList, boolean shouldOpenOnNewEntry, @@ -21,7 +22,8 @@ public EntryEditorPreferences(Map> entryEditorTabList, boolean isMrdlibAccepted, boolean shouldShowLatexCitationsTab, boolean showSourceTabByDefault, - boolean enableValidation) { + boolean enableValidation, + double dividerPosition) { this.entryEditorTabList = entryEditorTabList; this.shouldOpenOnNewEntry = shouldOpenOnNewEntry; @@ -30,6 +32,7 @@ public EntryEditorPreferences(Map> entryEditorTabList, this.shouldShowLatexCitationsTab = shouldShowLatexCitationsTab; this.showSourceTabByDefault = showSourceTabByDefault; this.enableValidation = enableValidation; + this.dividerPosition = dividerPosition; } public Map> getEntryEditorTabList() { @@ -59,4 +62,13 @@ public boolean shouldShowLatexCitationsTab() { public boolean isEnableValidation() { return enableValidation; } + + public double getDividerPosition() { + return dividerPosition; + } + + public EntryEditorPreferences withDividerPosition(double dividerPosition) { + this.dividerPosition = dividerPosition; + return this; + } } diff --git a/src/main/java/org/jabref/gui/entryeditor/OpenEntryEditorAction.java b/src/main/java/org/jabref/gui/entryeditor/OpenEntryEditorAction.java index e6e759880e7..4fe0a8101b4 100644 --- a/src/main/java/org/jabref/gui/entryeditor/OpenEntryEditorAction.java +++ b/src/main/java/org/jabref/gui/entryeditor/OpenEntryEditorAction.java @@ -19,7 +19,7 @@ public OpenEntryEditorAction(JabRefFrame frame, StateManager stateManager) { public void execute() { if (!stateManager.getSelectedEntries().isEmpty()) { - frame.getCurrentBasePanel().showAndEdit(stateManager.getSelectedEntries().get(0)); + frame.getCurrentLibraryTab().showAndEdit(stateManager.getSelectedEntries().get(0)); } } } diff --git a/src/main/java/org/jabref/gui/entryeditor/PreviewSwitchAction.java b/src/main/java/org/jabref/gui/entryeditor/PreviewSwitchAction.java index 03247c4fdd0..366adcc70f8 100644 --- a/src/main/java/org/jabref/gui/entryeditor/PreviewSwitchAction.java +++ b/src/main/java/org/jabref/gui/entryeditor/PreviewSwitchAction.java @@ -23,9 +23,9 @@ public PreviewSwitchAction(Direction direction, JabRefFrame frame, StateManager @Override public void execute() { if (direction == Direction.NEXT) { - frame.getCurrentBasePanel().getEntryEditor().nextPreviewStyle(); + frame.getCurrentLibraryTab().getEntryEditor().nextPreviewStyle(); } else { - frame.getCurrentBasePanel().getEntryEditor().previousPreviewStyle(); + frame.getCurrentLibraryTab().getEntryEditor().previousPreviewStyle(); } } } diff --git a/src/main/java/org/jabref/gui/exporter/ExportCommand.java b/src/main/java/org/jabref/gui/exporter/ExportCommand.java index 70f4e5adca0..ef726e448fb 100644 --- a/src/main/java/org/jabref/gui/exporter/ExportCommand.java +++ b/src/main/java/org/jabref/gui/exporter/ExportCommand.java @@ -82,16 +82,16 @@ private void export(Path file, FileChooser.ExtensionFilter selectedExtensionFilt List entries; if (selectedOnly) { // Selected entries - entries = frame.getCurrentBasePanel().getSelectedEntries(); + entries = frame.getCurrentLibraryTab().getSelectedEntries(); } else { // All entries - entries = frame.getCurrentBasePanel().getDatabase().getEntries(); + entries = frame.getCurrentLibraryTab().getDatabase().getEntries(); } // Set the global variable for this database's file directory before exporting, // so formatters can resolve linked files correctly. // (This is an ugly hack!) - Globals.prefs.fileDirForDatabase = frame.getCurrentBasePanel() + Globals.prefs.fileDirForDatabase = frame.getCurrentLibraryTab() .getBibDatabaseContext() .getFileDirectories(Globals.prefs.getFilePreferences()); @@ -103,9 +103,9 @@ private void export(Path file, FileChooser.ExtensionFilter selectedExtensionFilt final List finEntries = entries; BackgroundTask .wrap(() -> { - format.export(frame.getCurrentBasePanel().getBibDatabaseContext(), + format.export(frame.getCurrentLibraryTab().getBibDatabaseContext(), file, - frame.getCurrentBasePanel() + frame.getCurrentLibraryTab() .getBibDatabaseContext() .getMetaData() .getEncoding() diff --git a/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java b/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java index cd950e1ad2a..b5750b815e5 100644 --- a/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java +++ b/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java @@ -13,11 +13,11 @@ import javafx.scene.input.ClipboardContent; -import org.jabref.gui.BasePanel; import org.jabref.gui.ClipBoardManager; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.TaskExecutor; @@ -42,7 +42,7 @@ public class ExportToClipboardAction extends SimpleCommand { private JabRefFrame frame; private final DialogService dialogService; - private BasePanel panel; + private LibraryTab panel; private final List entries = new ArrayList<>(); private final ExporterFactory exporterFactory; private final ClipBoardManager clipBoardManager; @@ -56,7 +56,7 @@ public ExportToClipboardAction(JabRefFrame frame, DialogService dialogService, E this.taskExecutor = taskExecutor; } - public ExportToClipboardAction(BasePanel panel, DialogService dialogService, ExporterFactory exporterFactory, ClipBoardManager clipBoardManager, TaskExecutor taskExecutor) { + public ExportToClipboardAction(LibraryTab panel, DialogService dialogService, ExporterFactory exporterFactory, ClipBoardManager clipBoardManager, TaskExecutor taskExecutor) { this.panel = panel; this.dialogService = dialogService; this.exporterFactory = exporterFactory; @@ -67,7 +67,7 @@ public ExportToClipboardAction(BasePanel panel, DialogService dialogService, Exp @Override public void execute() { if (panel == null) { - panel = frame.getCurrentBasePanel(); + panel = frame.getCurrentLibraryTab(); } if (panel.getSelectedEntries().isEmpty()) { diff --git a/src/main/java/org/jabref/gui/exporter/SaveAction.java b/src/main/java/org/jabref/gui/exporter/SaveAction.java index 2a80a25148d..80061fa220c 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveAction.java @@ -30,22 +30,14 @@ public SaveAction(SaveMethod saveMethod, JabRefFrame frame, StateManager stateMa @Override public void execute() { SaveDatabaseAction saveDatabaseAction = new SaveDatabaseAction( - frame.getCurrentBasePanel(), + frame.getCurrentLibraryTab(), Globals.prefs, Globals.entryTypesManager); switch (saveMethod) { - case SAVE: - saveDatabaseAction.save(); - break; - case SAVE_AS: - saveDatabaseAction.saveAs(); - break; - case SAVE_SELECTED: - saveDatabaseAction.saveSelectedAsPlain(); - break; - default: - // Never happens + case SAVE -> saveDatabaseAction.save(); + case SAVE_AS -> saveDatabaseAction.saveAs(); + case SAVE_SELECTED -> saveDatabaseAction.saveSelectedAsPlain(); } } } diff --git a/src/main/java/org/jabref/gui/exporter/SaveAllAction.java b/src/main/java/org/jabref/gui/exporter/SaveAllAction.java index 4c2c1dd69a3..dd115d79ec8 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveAllAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveAllAction.java @@ -1,9 +1,9 @@ package org.jabref.gui.exporter; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.actions.SimpleCommand; import org.jabref.logic.l10n.Localization; @@ -21,8 +21,8 @@ public SaveAllAction(JabRefFrame frame) { public void execute() { dialogService.notify(Localization.lang("Saving all libraries...")); - for (BasePanel panel : frame.getBasePanelList()) { - SaveDatabaseAction saveDatabaseAction = new SaveDatabaseAction(panel, Globals.prefs, Globals.entryTypesManager); + for (LibraryTab libraryTab : frame.getLibraryTabs()) { + SaveDatabaseAction saveDatabaseAction = new SaveDatabaseAction(libraryTab, Globals.prefs, Globals.entryTypesManager); boolean saveResult = saveDatabaseAction.save(); if (!saveResult) { dialogService.notify(Localization.lang("Could not save file.")); diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 42c5a572c48..7e98241ad7d 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -14,9 +14,9 @@ import javafx.scene.layout.VBox; import javafx.scene.text.Text; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.dialogs.AutosaveUiManager; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.FileDialogConfiguration; @@ -49,7 +49,7 @@ public class SaveDatabaseAction { private static final Logger LOGGER = LoggerFactory.getLogger(SaveDatabaseAction.class); - private final BasePanel panel; + private final LibraryTab libraryTab; private final JabRefFrame frame; private final DialogService dialogService; private final JabRefPreferences preferences; @@ -59,20 +59,20 @@ public enum SaveDatabaseMode { SILENT, NORMAL } - public SaveDatabaseAction(BasePanel panel, JabRefPreferences preferences, BibEntryTypesManager entryTypesManager) { - this.panel = panel; - this.frame = panel.frame(); + public SaveDatabaseAction(LibraryTab libraryTab, JabRefPreferences preferences, BibEntryTypesManager entryTypesManager) { + this.libraryTab = libraryTab; + this.frame = libraryTab.frame(); this.dialogService = frame.getDialogService(); this.preferences = preferences; this.entryTypesManager = entryTypesManager; } public boolean save() { - return save(panel.getBibDatabaseContext(), SaveDatabaseMode.NORMAL); + return save(libraryTab.getBibDatabaseContext(), SaveDatabaseMode.NORMAL); } public boolean save(SaveDatabaseMode mode) { - return save(panel.getBibDatabaseContext(), mode); + return save(libraryTab.getBibDatabaseContext(), mode); } /** @@ -105,7 +105,7 @@ public void saveSelectedAsPlain() { * @return true on successful save */ boolean saveAs(Path file, SaveDatabaseMode mode) { - BibDatabaseContext context = panel.getBibDatabaseContext(); + BibDatabaseContext context = libraryTab.getBibDatabaseContext(); // Close AutosaveManager and BackupManager for original library Optional databasePath = context.getDatabasePath(); @@ -129,13 +129,13 @@ boolean saveAs(Path file, SaveDatabaseMode mode) { // we managed to successfully save the file // thus, we can store the store the path into the context context.setDatabasePath(file); - frame.refreshTitleAndTabs(); + // FIXME: We need to refresh the tab titles // Reinstall AutosaveManager and BackupManager for the new file name - panel.resetChangeMonitorAndChangePane(); + libraryTab.resetChangeMonitorAndChangePane(); if (readyForAutosave(context)) { AutosaveManager autosaver = AutosaveManager.start(context); - autosaver.registerListener(new AutosaveUiManager(panel)); + autosaver.registerListener(new AutosaveUiManager(libraryTab)); } if (readyForBackup(context)) { BackupManager.start(context, entryTypesManager, preferences); @@ -168,7 +168,7 @@ private boolean save(BibDatabaseContext bibDatabaseContext, SaveDatabaseMode mod Optional databasePath = bibDatabaseContext.getDatabasePath(); if (databasePath.isEmpty()) { Optional savePath = askForSavePath(); - if (!savePath.isPresent()) { + if (savePath.isEmpty()) { return false; } return saveAs(savePath.get(), mode); @@ -182,27 +182,21 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { dialogService.notify(String.format("%s...", Localization.lang("Saving library"))); } - panel.setSaving(true); + libraryTab.setSaving(true); try { - Charset encoding = panel.getBibDatabaseContext() - .getMetaData() - .getEncoding() - .orElse(preferences.getDefaultEncoding()); + Charset encoding = libraryTab.getBibDatabaseContext() + .getMetaData() + .getEncoding() + .orElse(preferences.getDefaultEncoding()); // Make sure to remember which encoding we used. - panel.getBibDatabaseContext().getMetaData().setEncoding(encoding, ChangePropagation.DO_NOT_POST_EVENT); + libraryTab.getBibDatabaseContext().getMetaData().setEncoding(encoding, ChangePropagation.DO_NOT_POST_EVENT); // Save the database boolean success = saveDatabase(targetPath, false, encoding, SavePreferences.DatabaseSaveType.ALL); if (success) { - panel.getUndoManager().markUnchanged(); - // After a successful save the following statement marks that the base is unchanged since last save - panel.setNonUndoableChange(false); - panel.setBaseChanged(false); - - frame.setTabTitle(panel, panel.getTabTitle(), targetPath.toAbsolutePath().toString()); - frame.setWindowTitle(); - frame.updateAllTabTitles(); + libraryTab.getUndoManager().markUnchanged(); + libraryTab.resetChangedProperties(); } return success; } catch (SaveException ex) { @@ -211,7 +205,7 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { return false; } finally { // release panel from save status - panel.setSaving(false); + libraryTab.setSaving(false); } } @@ -223,12 +217,12 @@ private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding, BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(fileWriter, preferences, entryTypesManager); if (selectedOnly) { - databaseWriter.savePartOfDatabase(panel.getBibDatabaseContext(), panel.getSelectedEntries()); + databaseWriter.savePartOfDatabase(libraryTab.getBibDatabaseContext(), libraryTab.getSelectedEntries()); } else { - databaseWriter.saveDatabase(panel.getBibDatabaseContext()); + databaseWriter.saveDatabase(libraryTab.getBibDatabaseContext()); } - panel.registerUndoableChanges(databaseWriter.getSaveActionsFieldChanges()); + libraryTab.registerUndoableChanges(databaseWriter.getSaveActionsFieldChanges()); if (fileWriter.hasEncodingProblems()) { saveWithDifferentEncoding(file, selectedOnly, preferences.getEncoding(), fileWriter.getEncodingProblems(), saveType); @@ -262,7 +256,7 @@ private void saveWithDifferentEncoding(Path file, boolean selectedOnly, Charset Optional newEncoding = dialogService.showChoiceDialogAndWait(Localization.lang("Save library"), Localization.lang("Select new encoding"), Localization.lang("Save library"), encoding, Encodings.getCharsets()); if (newEncoding.isPresent()) { // Make sure to remember which encoding we used. - panel.getBibDatabaseContext().getMetaData().setEncoding(newEncoding.get(), ChangePropagation.DO_NOT_POST_EVENT); + libraryTab.getBibDatabaseContext().getMetaData().setEncoding(newEncoding.get(), ChangePropagation.DO_NOT_POST_EVENT); saveDatabase(file, selectedOnly, newEncoding.get(), saveType); } diff --git a/src/main/java/org/jabref/gui/fieldeditors/IdentifierEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/IdentifierEditorViewModel.java index 7c18e322bfd..65659729cd0 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/IdentifierEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/IdentifierEditorViewModel.java @@ -44,7 +44,7 @@ public IdentifierEditorViewModel(Field field, SuggestionProvider suggestionPr ); validIdentifierIsNotPresent.bind( - EasyBind.map(identifier, parsedIdentifier -> !parsedIdentifier.isPresent()) + EasyBind.map(identifier, parsedIdentifier -> parsedIdentifier.isEmpty()) ); idFetcherAvailable.setValue(WebFetchers.getIdFetcherForField(field).isPresent()); @@ -87,7 +87,7 @@ public BooleanProperty identifierLookupInProgressProperty() { } public void fetchInformationByIdentifier(BibEntry entry) { - new FetchAndMergeEntry(JabRefGUI.getMainFrame().getCurrentBasePanel(), taskExecutor).fetchAndMerge(entry, field); + new FetchAndMergeEntry(JabRefGUI.getMainFrame().getCurrentLibraryTab(), taskExecutor).fetchAndMerge(entry, field); } public void lookupIdentifier(BibEntry entry) { diff --git a/src/main/java/org/jabref/gui/importer/ImportAction.java b/src/main/java/org/jabref/gui/importer/ImportAction.java index 8dae27690a6..52ea32b30e4 100644 --- a/src/main/java/org/jabref/gui/importer/ImportAction.java +++ b/src/main/java/org/jabref/gui/importer/ImportAction.java @@ -8,10 +8,10 @@ import java.util.Optional; import java.util.stream.Collectors; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.gui.util.TaskExecutor; @@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory; public class ImportAction { +// FixMe: Command pattern is broken, should extend SimpleCommand private static final Logger LOGGER = LoggerFactory.getLogger(ImportAction.class); @@ -84,9 +85,9 @@ public void automatedImport(List filenames) { }) .executeWith(taskExecutor); } else { - final BasePanel panel = frame.getCurrentBasePanel(); + final LibraryTab libraryTab = frame.getCurrentLibraryTab(); - ImportEntriesDialog dialog = new ImportEntriesDialog(panel.getBibDatabaseContext(), task); + ImportEntriesDialog dialog = new ImportEntriesDialog(libraryTab.getBibDatabaseContext(), task); dialog.setTitle(Localization.lang("Import")); dialog.showAndWait(); } @@ -97,7 +98,7 @@ private List doImport(List files) List imports = new ArrayList<>(); for (Path filename : files) { try { - if (!importer.isPresent()) { + if (importer.isEmpty()) { // Unknown format: DefaultTaskExecutor.runInJavaFXThread(() -> frame.getDialogService().notify(Localization.lang("Importing in unknown format") + "...")); // This import method never throws an IOException: diff --git a/src/main/java/org/jabref/gui/importer/ImportEntriesViewModel.java b/src/main/java/org/jabref/gui/importer/ImportEntriesViewModel.java index 7b2bdd92be2..ddd581b2da7 100644 --- a/src/main/java/org/jabref/gui/importer/ImportEntriesViewModel.java +++ b/src/main/java/org/jabref/gui/importer/ImportEntriesViewModel.java @@ -155,7 +155,7 @@ public void importEntries(List entriesToImport, boolean shouldDownload parserResult.getFile().map(File::getName).orElse("unknown"), parserResult.getDatabase().getEntries()); - JabRefGUI.getMainFrame().getCurrentBasePanel().markBaseChanged(); + JabRefGUI.getMainFrame().getCurrentLibraryTab().markBaseChanged(); } private void buildImportHandlerThenImportEntries(List entriesToImport) { diff --git a/src/main/java/org/jabref/gui/importer/NewEntryAction.java b/src/main/java/org/jabref/gui/importer/NewEntryAction.java index 6221b335413..7869393367e 100644 --- a/src/main/java/org/jabref/gui/importer/NewEntryAction.java +++ b/src/main/java/org/jabref/gui/importer/NewEntryAction.java @@ -54,16 +54,16 @@ public void execute() { } if (type.isPresent()) { - jabRefFrame.getCurrentBasePanel().insertEntry(new BibEntry(type.get())); + jabRefFrame.getCurrentLibraryTab().insertEntry(new BibEntry(type.get())); } else { - EntryTypeView typeChoiceDialog = new EntryTypeView(jabRefFrame.getCurrentBasePanel(), dialogService, preferences); + EntryTypeView typeChoiceDialog = new EntryTypeView(jabRefFrame.getCurrentLibraryTab(), dialogService, preferences); EntryType selectedType = typeChoiceDialog.showAndWait().orElse(null); if (selectedType == null) { return; } trackNewEntry(selectedType); - jabRefFrame.getCurrentBasePanel().insertEntry(new BibEntry(selectedType)); + jabRefFrame.getCurrentLibraryTab().insertEntry(new BibEntry(selectedType)); } } diff --git a/src/main/java/org/jabref/gui/importer/ParserResultWarningDialog.java b/src/main/java/org/jabref/gui/importer/ParserResultWarningDialog.java index 43a0b2f4afe..ec07cbc93b4 100644 --- a/src/main/java/org/jabref/gui/importer/ParserResultWarningDialog.java +++ b/src/main/java/org/jabref/gui/importer/ParserResultWarningDialog.java @@ -45,7 +45,7 @@ public static void showParserResultWarningDialog(final ParserResult parserResult // Switch tab if asked to do so if (dataBaseNumber >= 0) { - jabRefFrame.showBasePanelAt(dataBaseNumber); + jabRefFrame.showLibraryTabAt(dataBaseNumber); } // Generate string with warning texts @@ -59,10 +59,10 @@ public static void showParserResultWarningDialog(final ParserResult parserResult // Generate dialog title String dialogTitle; - if (dataBaseNumber < 0) { + if (dataBaseNumber < 0 || parserResult.getPath().isEmpty()) { dialogTitle = Localization.lang("Warnings"); } else { - dialogTitle = Localization.lang("Warnings") + " (" + parserResult.getFile().get().getName() + ")"; + dialogTitle = Localization.lang("Warnings") + " (" + parserResult.getPath().get().getFileName() + ")"; } // Show dialog diff --git a/src/main/java/org/jabref/gui/importer/actions/CheckForNewEntryTypesAction.java b/src/main/java/org/jabref/gui/importer/actions/CheckForNewEntryTypesAction.java index 86c50d19051..1da9f8d60de 100644 --- a/src/main/java/org/jabref/gui/importer/actions/CheckForNewEntryTypesAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/CheckForNewEntryTypesAction.java @@ -3,8 +3,8 @@ import java.util.List; import java.util.stream.Collectors; -import org.jabref.gui.BasePanel; import org.jabref.gui.Globals; +import org.jabref.gui.LibraryTab; import org.jabref.gui.importer.ImportCustomEntryTypesDialog; import org.jabref.logic.importer.ParserResult; import org.jabref.model.database.BibDatabaseMode; @@ -22,7 +22,7 @@ public boolean isActionNecessary(ParserResult parserResult) { } @Override - public void performAction(BasePanel panel, ParserResult parserResult) { + public void performAction(LibraryTab libraryTab, ParserResult parserResult) { BibDatabaseMode mode = getBibDatabaseModeFromParserResult(parserResult); ImportCustomEntryTypesDialog importBibEntryTypesDialog = new ImportCustomEntryTypesDialog(mode, getListOfUnknownAndUnequalCustomizations(parserResult)); diff --git a/src/main/java/org/jabref/gui/importer/actions/GUIPostOpenAction.java b/src/main/java/org/jabref/gui/importer/actions/GUIPostOpenAction.java index a8815ebc048..acdbd8726a7 100644 --- a/src/main/java/org/jabref/gui/importer/actions/GUIPostOpenAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/GUIPostOpenAction.java @@ -1,6 +1,6 @@ package org.jabref.gui.importer.actions; -import org.jabref.gui.BasePanel; +import org.jabref.gui.LibraryTab; import org.jabref.logic.importer.ParserResult; /** @@ -33,5 +33,5 @@ public interface GUIPostOpenAction { * @param panel The BasePanel where the database is shown. * @param pr The result of the BIB parse operation. */ - void performAction(BasePanel panel, ParserResult pr); + void performAction(LibraryTab panel, ParserResult pr); } diff --git a/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentAction.java b/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentAction.java index 3196f1c46aa..a8bb0a453f1 100644 --- a/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentAction.java @@ -2,7 +2,7 @@ import java.util.List; -import org.jabref.gui.BasePanel; +import org.jabref.gui.LibraryTab; import org.jabref.logic.importer.ParserResult; import org.jabref.migrations.MergeReviewIntoCommentMigration; import org.jabref.model.entry.BibEntry; @@ -15,12 +15,12 @@ public boolean isActionNecessary(ParserResult parserResult) { } @Override - public void performAction(BasePanel basePanel, ParserResult parserResult) { + public void performAction(LibraryTab libraryTab, ParserResult parserResult) { MergeReviewIntoCommentMigration migration = new MergeReviewIntoCommentMigration(); migration.performMigration(parserResult); List conflicts = MergeReviewIntoCommentMigration.collectConflicts(parserResult); - if (!conflicts.isEmpty() && new MergeReviewIntoCommentConfirmationDialog(basePanel.frame().getDialogService()).askUserForMerge(conflicts)) { + if (!conflicts.isEmpty() && new MergeReviewIntoCommentConfirmationDialog(libraryTab.frame().getDialogService()).askUserForMerge(conflicts)) { migration.performConflictingMigration(parserResult); } } diff --git a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java index 80d6f8fc158..8b2f79ec02d 100644 --- a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java @@ -11,11 +11,10 @@ import java.util.Objects; import java.util.Optional; -import org.jabref.gui.BasePanel; -import org.jabref.gui.BasePanelPreferences; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.dialogs.BackupUIManager; import org.jabref.gui.externalfiletype.ExternalFileTypes; @@ -60,14 +59,14 @@ public OpenDatabaseAction(JabRefFrame frame) { /** * Go through the list of post open actions, and perform those that need to be performed. * - * @param panel The BasePanel where the database is shown. + * @param libraryTab The BasePanel where the database is shown. * @param result The result of the BIB file parse operation. */ - public static void performPostOpenActions(BasePanel panel, ParserResult result) { + public static void performPostOpenActions(LibraryTab libraryTab, ParserResult result) { for (GUIPostOpenAction action : OpenDatabaseAction.POST_OPEN_ACTIONS) { if (action.isActionNecessary(result)) { - action.performAction(panel, result); - panel.frame().showBasePanel(panel); + action.performAction(libraryTab, result); + libraryTab.frame().showLibraryTab(libraryTab); } } } @@ -91,7 +90,7 @@ private Path getInitialDirectory() { if (frame.getBasePanelCount() == 0) { return Globals.prefs.getWorkingDir(); } else { - Optional databasePath = frame.getCurrentBasePanel().getBibDatabaseContext().getDatabasePath(); + Optional databasePath = frame.getCurrentLibraryTab().getBibDatabaseContext().getDatabasePath(); return databasePath.map(Path::getParent).orElse(Globals.prefs.getWorkingDir()); } } @@ -111,7 +110,7 @@ public void openFile(Path file, boolean raisePanel) { * @param filesToOpen the filesToOpen, may be null or not existing */ public void openFiles(List filesToOpen, boolean raisePanel) { - BasePanel toRaise = null; + LibraryTab toRaise = null; int initialCount = filesToOpen.size(); int removed = 0; @@ -119,17 +118,17 @@ public void openFiles(List filesToOpen, boolean raisePanel) { for (Iterator iterator = filesToOpen.iterator(); iterator.hasNext(); ) { Path file = iterator.next(); for (int i = 0; i < frame.getTabbedPane().getTabs().size(); i++) { - BasePanel basePanel = frame.getBasePanelAt(i); - if ((basePanel.getBibDatabaseContext().getDatabasePath().isPresent()) - && basePanel.getBibDatabaseContext().getDatabasePath().get().equals(file)) { + LibraryTab libraryTab = frame.getLibraryTabAt(i); + if ((libraryTab.getBibDatabaseContext().getDatabasePath().isPresent()) + && libraryTab.getBibDatabaseContext().getDatabasePath().get().equals(file)) { iterator.remove(); removed++; // See if we removed the final one. If so, we must perhaps - // raise the BasePanel in question: + // raise the LibraryTab in question: if (removed == initialCount) { - toRaise = basePanel; + toRaise = libraryTab; } - // no more bps to check, we found a matching one + // no more LibraryTabs to check, we found a matching one break; } } @@ -151,7 +150,7 @@ public void openFiles(List filesToOpen, boolean raisePanel) { } else if (toRaise != null) { // If no files are remaining to open, this could mean that a file was // already open. If so, we may have to raise the correct tab: - frame.showBasePanel(toRaise); + frame.showLibraryTab(toRaise); } } @@ -164,8 +163,8 @@ private void openTheFile(Path file, boolean raisePanel) { BackgroundTask.wrap(() -> loadDatabase(file)) .onSuccess(result -> { - BasePanel panel = addNewDatabase(result, file, raisePanel); - OpenDatabaseAction.performPostOpenActions(panel, result); + LibraryTab libraryTab = addNewDatabase(result, file, raisePanel); + OpenDatabaseAction.performPostOpenActions(libraryTab, result); }) .onFailure(ex -> dialogService.showErrorDialogAndWait(Localization.lang("Connection error"), ex.getMessage() + "\n\n" + Localization.lang("A local copy will be opened."))) @@ -202,13 +201,13 @@ private ParserResult loadDatabase(Path file) throws Exception { return result; } - private BasePanel addNewDatabase(ParserResult result, final Path file, boolean raisePanel) { + private LibraryTab addNewDatabase(ParserResult result, final Path file, boolean raisePanel) { if (result.hasWarnings()) { ParserResultWarningDialog.showParserResultWarningDialog(result, frame); } - BasePanel basePanel = new BasePanel(frame, BasePanelPreferences.from(Globals.prefs), result.getDatabaseContext(), ExternalFileTypes.getInstance()); - frame.addTab(basePanel, raisePanel); - return basePanel; + LibraryTab libraryTab = new LibraryTab(frame, Globals.prefs, result.getDatabaseContext(), ExternalFileTypes.getInstance()); + frame.addTab(libraryTab, raisePanel); + return libraryTab; } } diff --git a/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java b/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java index a51aeb51fca..773289143f5 100644 --- a/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java +++ b/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java @@ -101,7 +101,7 @@ public void search() { return; } - if (frame.getCurrentBasePanel() == null) { + if (frame.getCurrentLibraryTab() == null) { dialogService.notify(Localization.lang("Please open or start a new library before searching")); return; } @@ -120,7 +120,7 @@ public void search() { } task.onFailure(dialogService::showErrorDialogAndWait); - ImportEntriesDialog dialog = new ImportEntriesDialog(frame.getCurrentBasePanel().getBibDatabaseContext(), task); + ImportEntriesDialog dialog = new ImportEntriesDialog(frame.getCurrentLibraryTab().getBibDatabaseContext(), task); dialog.setTitle(activeFetcher.getName()); dialog.showAndWait(); } diff --git a/src/main/java/org/jabref/gui/integrity/IntegrityCheckAction.java b/src/main/java/org/jabref/gui/integrity/IntegrityCheckAction.java index 15b7baed8d2..197f2084876 100644 --- a/src/main/java/org/jabref/gui/integrity/IntegrityCheckAction.java +++ b/src/main/java/org/jabref/gui/integrity/IntegrityCheckAction.java @@ -72,7 +72,7 @@ protected List call() { if (messages.isEmpty()) { dialogService.notify(Localization.lang("No problems found.")); } else { - Dialog dialog = new IntegrityCheckDialog(messages, frame.getCurrentBasePanel()); + Dialog dialog = new IntegrityCheckDialog(messages, frame.getCurrentLibraryTab()); dialog.showAndWait(); } }); diff --git a/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java b/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java index 26de42d4bf3..7f190ebf036 100644 --- a/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java +++ b/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java @@ -12,7 +12,7 @@ import javafx.scene.input.MouseButton; import javafx.stage.Modality; -import org.jabref.gui.BasePanel; +import org.jabref.gui.LibraryTab; import org.jabref.gui.util.BaseDialog; import org.jabref.logic.integrity.IntegrityMessage; import org.jabref.logic.l10n.Localization; @@ -23,7 +23,7 @@ public class IntegrityCheckDialog extends BaseDialog { private final List messages; - private final BasePanel basePanel; + private final LibraryTab libraryTab; @FXML private TableView messagesTable; @FXML private TableColumn keyColumn; @FXML private TableColumn fieldColumn; @@ -34,9 +34,9 @@ public class IntegrityCheckDialog extends BaseDialog { private IntegrityCheckDialogViewModel viewModel; private TableFilter tableFilter; - public IntegrityCheckDialog(List messages, BasePanel basePanel) { + public IntegrityCheckDialog(List messages, LibraryTab libraryTab) { this.messages = messages; - this.basePanel = basePanel; + this.libraryTab = libraryTab; this.setTitle(Localization.lang("Check integrity")); this.initModality(Modality.NONE); @@ -48,7 +48,7 @@ public IntegrityCheckDialog(List messages, BasePanel basePanel private void onSelectionChanged(ListChangeListener.Change change) { if (change.next()) { change.getAddedSubList().stream().findFirst().ifPresent(message -> - basePanel.editEntryAndFocusField(message.getEntry(), message.getField())); + libraryTab.editEntryAndFocusField(message.getEntry(), message.getField())); } } diff --git a/src/main/java/org/jabref/gui/journals/AbbreviateAction.java b/src/main/java/org/jabref/gui/journals/AbbreviateAction.java index 6fd9421e2ac..81c5ea15322 100644 --- a/src/main/java/org/jabref/gui/journals/AbbreviateAction.java +++ b/src/main/java/org/jabref/gui/journals/AbbreviateAction.java @@ -50,18 +50,10 @@ public AbbreviateAction(StandardActions action, this.stateManager = stateManager; switch (action) { - case ABBREVIATE_DEFAULT: - abbreviationType = AbbreviationType.DEFAULT; - break; - case ABBREVIATE_MEDLINE: - abbreviationType = AbbreviationType.MEDLINE; - break; - case ABBREVIATE_SHORTEST_UNIQUE: - abbreviationType = AbbreviationType.SHORTEST_UNIQUE; - break; - default: - LOGGER.debug("Unknown action: " + action.name()); - break; + case ABBREVIATE_DEFAULT -> abbreviationType = AbbreviationType.DEFAULT; + case ABBREVIATE_MEDLINE -> abbreviationType = AbbreviationType.MEDLINE; + case ABBREVIATE_SHORTEST_UNIQUE -> abbreviationType = AbbreviationType.SHORTEST_UNIQUE; + default -> LOGGER.debug("Unknown action: " + action.name()); } this.executable.bind(ActionHelper.needsEntriesSelected(stateManager)); @@ -120,7 +112,7 @@ private String abbreviate(BibDatabaseContext databaseContext, List ent if (count > 0) { ce.end(); frame.getUndoManager().addEdit(ce); - frame.getCurrentBasePanel().markBaseChanged(); + frame.getCurrentLibraryTab().markBaseChanged(); return Localization.lang("Abbreviated %0 journal names.", String.valueOf(count)); } return Localization.lang("No journal names could be abbreviated."); @@ -136,7 +128,7 @@ private String unabbreviate(BibDatabaseContext databaseContext, List e if (count > 0) { ce.end(); frame.getUndoManager().addEdit(ce); - frame.getCurrentBasePanel().markBaseChanged(); + frame.getCurrentLibraryTab().markBaseChanged(); return Localization.lang("Unabbreviated %0 journal names.", String.valueOf(count)); } return Localization.lang("No journal names could be unabbreviated."); diff --git a/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesAction.java b/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesAction.java index e5a1960cd9f..ea8729837ee 100644 --- a/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesAction.java +++ b/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesAction.java @@ -17,7 +17,7 @@ public LibraryPropertiesAction(JabRefFrame frame, StateManager stateManager) { @Override public void execute() { - LibraryPropertiesDialogView propertiesDialog = new LibraryPropertiesDialogView(frame.getCurrentBasePanel()); + LibraryPropertiesDialogView propertiesDialog = new LibraryPropertiesDialogView(frame.getCurrentLibraryTab()); propertiesDialog.showAndWait(); } } diff --git a/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesDialogView.java b/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesDialogView.java index cada1936c9b..0a58302ae75 100644 --- a/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesDialogView.java +++ b/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesDialogView.java @@ -11,8 +11,8 @@ import javafx.scene.control.ComboBox; import javafx.scene.control.TextField; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; +import org.jabref.gui.LibraryTab; import org.jabref.gui.commonfxcontrols.FieldFormatterCleanupsPanel; import org.jabref.gui.commonfxcontrols.SaveOrderConfigPanel; import org.jabref.gui.util.BaseDialog; @@ -38,11 +38,11 @@ public class LibraryPropertiesDialogView extends BaseDialog { @Inject private PreferencesService preferencesService; @Inject private DialogService dialogService; - private final BasePanel panel; + private final LibraryTab libraryTab; private LibraryPropertiesDialogViewModel viewModel; - public LibraryPropertiesDialogView(BasePanel panel) { - this.panel = panel; + public LibraryPropertiesDialogView(LibraryTab libraryTab) { + this.libraryTab = libraryTab; ViewLoader.view(this) .load() @@ -60,7 +60,7 @@ public LibraryPropertiesDialogView(BasePanel panel) { @FXML private void initialize() { - viewModel = new LibraryPropertiesDialogViewModel(panel.getBibDatabaseContext(), dialogService, preferencesService); + viewModel = new LibraryPropertiesDialogViewModel(libraryTab.getBibDatabaseContext(), dialogService, preferencesService); encoding.disableProperty().bind(viewModel.encodingDisableProperty()); encoding.itemsProperty().bind(viewModel.encodingsProperty()); diff --git a/src/main/java/org/jabref/gui/linkedfile/AttachFileAction.java b/src/main/java/org/jabref/gui/linkedfile/AttachFileAction.java index 8892e6cc450..534de7c529f 100644 --- a/src/main/java/org/jabref/gui/linkedfile/AttachFileAction.java +++ b/src/main/java/org/jabref/gui/linkedfile/AttachFileAction.java @@ -3,8 +3,8 @@ import java.nio.file.Path; import java.util.Optional; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; +import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; @@ -21,13 +21,16 @@ public class AttachFileAction extends SimpleCommand { - private final BasePanel panel; + private final LibraryTab libraryTab; private final StateManager stateManager; private final DialogService dialogService; private final PreferencesService preferencesService; - public AttachFileAction(BasePanel panel, DialogService dialogService, StateManager stateManager, PreferencesService preferencesService) { - this.panel = panel; + public AttachFileAction(LibraryTab libraryTab, + DialogService dialogService, + StateManager stateManager, + PreferencesService preferencesService) { + this.libraryTab = libraryTab; this.stateManager = stateManager; this.dialogService = dialogService; this.preferencesService = preferencesService; @@ -70,8 +73,8 @@ public void execute() { Optional fieldChange = entry.addFile(editedLinkedFile); fieldChange.ifPresent(change -> { UndoableFieldChange ce = new UndoableFieldChange(change); - panel.getUndoManager().addEdit(ce); - panel.markBaseChanged(); + libraryTab.getUndoManager().addEdit(ce); + libraryTab.markBaseChanged(); }); }); }); diff --git a/src/main/java/org/jabref/gui/maintable/MainTable.java b/src/main/java/org/jabref/gui/maintable/MainTable.java index c1c5cab22ab..cf5c8cd2b2a 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTable.java +++ b/src/main/java/org/jabref/gui/maintable/MainTable.java @@ -24,10 +24,10 @@ import javafx.scene.input.MouseEvent; import javafx.scene.input.TransferMode; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.DragAndDropDataFormats; import org.jabref.gui.Globals; +import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.actions.StandardActions; import org.jabref.gui.edit.EditAction; @@ -57,7 +57,8 @@ public class MainTable extends TableView { private static final Logger LOGGER = LoggerFactory.getLogger(MainTable.class); - private final BasePanel panel; + private final LibraryTab libraryTab; + private final DialogService dialogService; private final BibDatabaseContext database; private final MainTableDataModel model; @@ -68,7 +69,7 @@ public class MainTable extends TableView { private String columnSearchTerm; public MainTable(MainTableDataModel model, - BasePanel panel, + LibraryTab libraryTab, BibDatabaseContext database, PreferencesService preferencesService, DialogService dialogService, @@ -77,10 +78,11 @@ public MainTable(MainTableDataModel model, KeyBindingRepository keyBindingRepository) { super(); - this.panel = panel; + this.libraryTab = libraryTab; + this.dialogService = dialogService; this.database = Objects.requireNonNull(database); this.model = model; - UndoManager undoManager = panel.getUndoManager(); + UndoManager undoManager = libraryTab.getUndoManager(); MainTablePreferences mainTablePreferences = preferencesService.getMainTablePreferences(); importHandler = new ImportHandler( @@ -93,18 +95,22 @@ public MainTable(MainTableDataModel model, localDragboard = stateManager.getLocalDragboard(); this.getColumns().addAll( - new MainTableColumnFactory(database, preferencesService, externalFileTypes, panel.getUndoManager(), dialogService) - .createColumns()); + new MainTableColumnFactory( + database, + preferencesService, + externalFileTypes, + libraryTab.getUndoManager(), + dialogService).createColumns()); new ViewModelTableRowFactory() .withOnMouseClickedEvent((entry, event) -> { if (event.getClickCount() == 2) { - panel.showAndEdit(entry.getEntry()); + libraryTab.showAndEdit(entry.getEntry()); } }) .withContextMenu(entry -> RightClickMenu.create(entry, keyBindingRepository, - panel, + libraryTab, dialogService, stateManager, preferencesService, @@ -221,7 +227,7 @@ public void copy() { if (!selectedEntries.isEmpty()) { try { Globals.clipboardManager.setContent(selectedEntries); - panel.output(panel.formatOutputMessage(Localization.lang("Copied"), selectedEntries.size())); + dialogService.notify(libraryTab.formatOutputMessage(Localization.lang("Copied"), selectedEntries.size())); } catch (IOException e) { LOGGER.error("Error while copying selected entries to clipboard", e); } @@ -230,7 +236,7 @@ public void copy() { public void cut() { copy(); - panel.delete(true); + libraryTab.delete(true); } private void setupKeyBindings(KeyBindingRepository keyBindings) { @@ -238,7 +244,7 @@ private void setupKeyBindings(KeyBindingRepository keyBindings) { if (event.getCode() == KeyCode.ENTER) { getSelectedEntries().stream() .findFirst() - .ifPresent(panel::showAndEdit); + .ifPresent(libraryTab::showAndEdit); event.consume(); return; } @@ -256,16 +262,16 @@ private void setupKeyBindings(KeyBindingRepository keyBindings) { break; case PASTE: if (!OS.OS_X) { - new EditAction(StandardActions.PASTE, panel.frame(), Globals.stateManager).execute(); + new EditAction(StandardActions.PASTE, libraryTab.frame(), Globals.stateManager).execute(); } event.consume(); break; case COPY: - new EditAction(StandardActions.COPY, panel.frame(), Globals.stateManager).execute(); + new EditAction(StandardActions.COPY, libraryTab.frame(), Globals.stateManager).execute(); event.consume(); break; case CUT: - new EditAction(StandardActions.CUT, panel.frame(), Globals.stateManager).execute(); + new EditAction(StandardActions.CUT, libraryTab.frame(), Globals.stateManager).execute(); event.consume(); break; default: @@ -292,7 +298,7 @@ public void paste(BibDatabaseMode bibDatabaseMode) { List entriesToAdd = Globals.clipboardManager.extractData(); ImportCleanup cleanup = new ImportCleanup(bibDatabaseMode); cleanup.doPostCleanup(entriesToAdd); - panel.insertEntries(entriesToAdd); + libraryTab.insertEntries(entriesToAdd); if (!entriesToAdd.isEmpty()) { this.requestFocus(); } @@ -348,29 +354,26 @@ private void handleOnDragDropped(TableRow row, BibEntryT // Different actions depending on where the user releases the drop in the target row // Bottom + top -> import entries // Center -> link files to entry + // Depending on the pressed modifier, move/copy/link files to drop target switch (ControlHelper.getDroppingMouseLocation(row, event)) { - case TOP: - case BOTTOM: - importHandler.importAsNewEntries(files); - break; - case CENTER: - // Depending on the pressed modifier, move/copy/link files to drop target + case TOP, BOTTOM -> importHandler.importAsNewEntries(files); + case CENTER -> { BibEntry entry = target.getEntry(); switch (event.getTransferMode()) { - case LINK: + case LINK -> { LOGGER.debug("Mode LINK"); // shift on win or no modifier importHandler.getLinker().addFilesToEntry(entry, files); - break; - case MOVE: + } + case MOVE -> { LOGGER.debug("Mode MOVE"); // alt on win importHandler.getLinker().moveFilesToFileDirAndAddToEntry(entry, files); - break; - case COPY: + } + case COPY -> { LOGGER.debug("Mode Copy"); // ctrl on win importHandler.getLinker().copyFilesToFileDirAndAddToEntry(entry, files); - break; + } } - break; + } } success = true; diff --git a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index f7f46026857..6305baeaea7 100644 --- a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -4,10 +4,10 @@ import javafx.scene.control.Menu; import javafx.scene.control.SeparatorMenuItem; -import org.jabref.gui.BasePanel; import org.jabref.gui.ClipBoardManager; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; +import org.jabref.gui.LibraryTab; import org.jabref.gui.SendAsEMailAction; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionFactory; @@ -33,7 +33,7 @@ public class RightClickMenu { public static ContextMenu create(BibEntryTableViewModel entry, KeyBindingRepository keyBindingRepository, - BasePanel panel, + LibraryTab libraryTab, DialogService dialogService, StateManager stateManager, PreferencesService preferencesService, @@ -41,11 +41,11 @@ public static ContextMenu create(BibEntryTableViewModel entry, ContextMenu contextMenu = new ContextMenu(); ActionFactory factory = new ActionFactory(keyBindingRepository); - contextMenu.getItems().add(factory.createMenuItem(StandardActions.COPY, new EditAction(StandardActions.COPY, panel.frame(), stateManager))); - contextMenu.getItems().add(createCopySubMenu(panel, factory, dialogService, stateManager, preferencesService, clipBoardManager)); - contextMenu.getItems().add(factory.createMenuItem(StandardActions.PASTE, new EditAction(StandardActions.PASTE, panel.frame(), stateManager))); - contextMenu.getItems().add(factory.createMenuItem(StandardActions.CUT, new EditAction(StandardActions.CUT, panel.frame(), stateManager))); - contextMenu.getItems().add(factory.createMenuItem(StandardActions.DELETE_ENTRY, new EditAction(StandardActions.DELETE_ENTRY, panel.frame(), stateManager))); + contextMenu.getItems().add(factory.createMenuItem(StandardActions.COPY, new EditAction(StandardActions.COPY, libraryTab.frame(), stateManager))); + contextMenu.getItems().add(createCopySubMenu(libraryTab, factory, dialogService, stateManager, preferencesService, clipBoardManager)); + contextMenu.getItems().add(factory.createMenuItem(StandardActions.PASTE, new EditAction(StandardActions.PASTE, libraryTab.frame(), stateManager))); + contextMenu.getItems().add(factory.createMenuItem(StandardActions.CUT, new EditAction(StandardActions.CUT, libraryTab.frame(), stateManager))); + contextMenu.getItems().add(factory.createMenuItem(StandardActions.DELETE_ENTRY, new EditAction(StandardActions.DELETE_ENTRY, libraryTab.frame(), stateManager))); contextMenu.getItems().add(new SeparatorMenuItem()); @@ -54,12 +54,12 @@ public static ContextMenu create(BibEntryTableViewModel entry, contextMenu.getItems().add(new SeparatorMenuItem()); if (preferencesService.getSpecialFieldsPreferences().isSpecialFieldsEnabled()) { - contextMenu.getItems().add(SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.RANKING, factory, panel.frame(), dialogService, stateManager)); - contextMenu.getItems().add(SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.RELEVANCE, factory, panel.frame(), dialogService, stateManager)); - contextMenu.getItems().add(SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.QUALITY, factory, panel.frame(), dialogService, stateManager)); - contextMenu.getItems().add(SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.PRINTED, factory, panel.frame(), dialogService, stateManager)); - contextMenu.getItems().add(SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.PRIORITY, factory, panel.frame(), dialogService, stateManager)); - contextMenu.getItems().add(SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.READ_STATUS, factory, panel.frame(), dialogService, stateManager)); + contextMenu.getItems().add(SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.RANKING, factory, libraryTab.frame(), dialogService, stateManager)); + contextMenu.getItems().add(SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.RELEVANCE, factory, libraryTab.frame(), dialogService, stateManager)); + contextMenu.getItems().add(SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.QUALITY, factory, libraryTab.frame(), dialogService, stateManager)); + contextMenu.getItems().add(SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.PRINTED, factory, libraryTab.frame(), dialogService, stateManager)); + contextMenu.getItems().add(SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.PRIORITY, factory, libraryTab.frame(), dialogService, stateManager)); + contextMenu.getItems().add(SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.READ_STATUS, factory, libraryTab.frame(), dialogService, stateManager)); } contextMenu.getItems().add(new SeparatorMenuItem()); @@ -71,16 +71,16 @@ public static ContextMenu create(BibEntryTableViewModel entry, contextMenu.getItems().add(new SeparatorMenuItem()); - contextMenu.getItems().add(new ChangeEntryTypeMenu().getChangeEntryTypeMenu(entry.getEntry(), panel.getBibDatabaseContext(), panel.getUndoManager())); - contextMenu.getItems().add(factory.createMenuItem(StandardActions.MERGE_WITH_FETCHED_ENTRY, new MergeWithFetchedEntryAction(panel, dialogService, stateManager))); - contextMenu.getItems().add(factory.createMenuItem(StandardActions.ATTACH_FILE, new AttachFileAction(panel, dialogService, stateManager, preferencesService))); + contextMenu.getItems().add(new ChangeEntryTypeMenu().getChangeEntryTypeMenu(entry.getEntry(), libraryTab.getBibDatabaseContext(), libraryTab.getUndoManager())); + contextMenu.getItems().add(factory.createMenuItem(StandardActions.MERGE_WITH_FETCHED_ENTRY, new MergeWithFetchedEntryAction(libraryTab, dialogService, stateManager))); + contextMenu.getItems().add(factory.createMenuItem(StandardActions.ATTACH_FILE, new AttachFileAction(libraryTab, dialogService, stateManager, preferencesService))); // ToDo: Refactor BasePanel, see ahead. - contextMenu.getItems().add(factory.createMenuItem(StandardActions.MERGE_ENTRIES, new MergeEntriesAction(panel.frame(), dialogService, stateManager))); + contextMenu.getItems().add(factory.createMenuItem(StandardActions.MERGE_ENTRIES, new MergeEntriesAction(libraryTab.frame(), dialogService, stateManager))); return contextMenu; } - private static Menu createCopySubMenu(BasePanel panel, + private static Menu createCopySubMenu(LibraryTab libraryTab, ActionFactory factory, DialogService dialogService, StateManager stateManager, @@ -108,7 +108,7 @@ private static Menu createCopySubMenu(BasePanel panel, copySpecialMenu.getItems().add(factory.createMenuItem(StandardActions.COPY_CITATION_PREVIEW, new CopyCitationAction(CitationStyleOutputFormat.HTML, dialogService, stateManager, clipBoardManager, previewPreferences))); } - copySpecialMenu.getItems().add(factory.createMenuItem(StandardActions.EXPORT_TO_CLIPBOARD, new ExportToClipboardAction(panel, dialogService, Globals.exportFactory, clipBoardManager, Globals.TASK_EXECUTOR))); + copySpecialMenu.getItems().add(factory.createMenuItem(StandardActions.EXPORT_TO_CLIPBOARD, new ExportToClipboardAction(libraryTab, dialogService, Globals.exportFactory, clipBoardManager, Globals.TASK_EXECUTOR))); return copySpecialMenu; } } diff --git a/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java b/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java index 643f3bde53c..a64c0232111 100644 --- a/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java +++ b/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java @@ -8,9 +8,9 @@ import java.util.Set; import java.util.TreeSet; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; +import org.jabref.gui.LibraryTab; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableChangeType; import org.jabref.gui.undo.UndoableFieldChange; @@ -39,13 +39,13 @@ public class FetchAndMergeEntry { // A list of all field which are supported public static List SUPPORTED_FIELDS = Arrays.asList(StandardField.DOI, StandardField.EPRINT, StandardField.ISBN); private static final Logger LOGGER = LoggerFactory.getLogger(FetchAndMergeEntry.class); - private final BasePanel panel; + private final LibraryTab libraryTab; private final DialogService dialogService; private final TaskExecutor taskExecutor; - public FetchAndMergeEntry(BasePanel panel, TaskExecutor taskExecutor) { - this.dialogService = panel.frame().getDialogService(); - this.panel = panel; + public FetchAndMergeEntry(LibraryTab libraryTab, TaskExecutor taskExecutor) { + this.dialogService = libraryTab.frame().getDialogService(); + this.libraryTab = libraryTab; this.taskExecutor = taskExecutor; } @@ -65,7 +65,7 @@ public void fetchAndMerge(BibEntry entry, List fields) { if (fetcher.isPresent()) { BackgroundTask.wrap(() -> fetcher.get().performSearchById(fieldContent.get())) .onSuccess(fetchedEntry -> { - ImportCleanup cleanup = new ImportCleanup(panel.getBibDatabaseContext().getMode()); + ImportCleanup cleanup = new ImportCleanup(libraryTab.getBibDatabaseContext().getMode()); cleanup.doPostCleanup(entry); String type = field.getDisplayName(); if (fetchedEntry.isPresent()) { @@ -116,7 +116,7 @@ private void showMergeDialog(BibEntry originalEntry, BibEntry fetchedEntry, WebF for (Field field : jointFields) { Optional originalString = originalEntry.getField(field); Optional mergedString = mergedEntry.get().getField(field); - if (!originalString.isPresent() || !originalString.equals(mergedString)) { + if (originalString.isEmpty() || !originalString.equals(mergedString)) { originalEntry.setField(field, mergedString.get()); // mergedString always present ce.addEdit(new UndoableFieldChange(originalEntry, field, originalString.orElse(null), mergedString.get())); @@ -136,7 +136,7 @@ private void showMergeDialog(BibEntry originalEntry, BibEntry fetchedEntry, WebF if (edited) { ce.end(); - panel.getUndoManager().addEdit(ce); + libraryTab.getUndoManager().addEdit(ce); dialogService.notify(Localization.lang("Updated entry with info from %0", fetcher.getName())); } else { dialogService.notify(Localization.lang("No information added")); @@ -150,7 +150,7 @@ public void fetchAndMerge(BibEntry entry, EntryBasedFetcher fetcher) { BackgroundTask.wrap(() -> fetcher.performSearch(entry).stream().findFirst()) .onSuccess(fetchedEntry -> { if (fetchedEntry.isPresent()) { - ImportCleanup cleanup = new ImportCleanup(panel.getBibDatabaseContext().getMode()); + ImportCleanup cleanup = new ImportCleanup(libraryTab.getBibDatabaseContext().getMode()); cleanup.doPostCleanup(fetchedEntry.get()); showMergeDialog(entry, fetchedEntry.get(), fetcher); } else { diff --git a/src/main/java/org/jabref/gui/mergeentries/MergeEntriesAction.java b/src/main/java/org/jabref/gui/mergeentries/MergeEntriesAction.java index ec1ba539807..0b1991cdff6 100644 --- a/src/main/java/org/jabref/gui/mergeentries/MergeEntriesAction.java +++ b/src/main/java/org/jabref/gui/mergeentries/MergeEntriesAction.java @@ -59,7 +59,7 @@ public void execute() { // ToDo: BibDatabase::insertEntry does not contain logic to mark the BasePanel as changed and to mark // entries with a timestamp, only BasePanel::insertEntry does. Workaround for the moment is to get the // BasePanel from the constructor injected JabRefFrame. Should be refactored and extracted! - frame.getCurrentBasePanel().insertEntry(mergedEntry.get()); + frame.getCurrentLibraryTab().insertEntry(mergedEntry.get()); // Create a new entry and add it to the undo stack // Remove the other two entries and add them to the undo stack (which is not working...) diff --git a/src/main/java/org/jabref/gui/mergeentries/MergeWithFetchedEntryAction.java b/src/main/java/org/jabref/gui/mergeentries/MergeWithFetchedEntryAction.java index 5ebfb67676e..11aeaac50e0 100644 --- a/src/main/java/org/jabref/gui/mergeentries/MergeWithFetchedEntryAction.java +++ b/src/main/java/org/jabref/gui/mergeentries/MergeWithFetchedEntryAction.java @@ -1,8 +1,8 @@ package org.jabref.gui.mergeentries; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; +import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; @@ -13,12 +13,12 @@ public class MergeWithFetchedEntryAction extends SimpleCommand { - private final BasePanel basePanel; + private final LibraryTab libraryTab; private final DialogService dialogService; private final StateManager stateManager; - public MergeWithFetchedEntryAction(BasePanel basePanel, DialogService dialogService, StateManager stateManager) { - this.basePanel = basePanel; + public MergeWithFetchedEntryAction(LibraryTab libraryTab, DialogService dialogService, StateManager stateManager) { + this.libraryTab = libraryTab; this.dialogService = dialogService; this.stateManager = stateManager; @@ -35,6 +35,6 @@ public void execute() { } BibEntry originalEntry = stateManager.getSelectedEntries().get(0); - new FetchAndMergeEntry(basePanel, Globals.TASK_EXECUTOR).fetchAndMerge(originalEntry); + new FetchAndMergeEntry(libraryTab, Globals.TASK_EXECUTOR).fetchAndMerge(originalEntry); } } diff --git a/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java b/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java index 921ff24d050..90827514189 100644 --- a/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java +++ b/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java @@ -28,10 +28,10 @@ import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.actions.ActionFactory; import org.jabref.gui.actions.StandardActions; import org.jabref.gui.help.HelpAction; @@ -318,11 +318,11 @@ private void exportEntries() { private List getBaseList() { List databases = new ArrayList<>(); if (ooPrefs.getUseAllDatabases()) { - for (BasePanel basePanel : frame.getBasePanelList()) { - databases.add(basePanel.getDatabase()); + for (LibraryTab libraryTab : frame.getLibraryTabs()) { + databases.add(libraryTab.getDatabase()); } } else { - databases.add(frame.getCurrentBasePanel().getDatabase()); + databases.add(frame.getCurrentLibraryTab().getDatabase()); } return databases; @@ -338,7 +338,7 @@ private void connectAutomatically() { Task taskConnectIfInstalled = new Task<>() { @Override - protected Void call() throws Exception { + protected Void call() { updateProgress(ProgressBar.INDETERMINATE_PROGRESS, ProgressBar.INDETERMINATE_PROGRESS); boolean installed = officeInstallation.isInstalled(); @@ -474,10 +474,10 @@ private void pushEntries(boolean inParenthesisIn, boolean withText, boolean addP } } - BasePanel panel = frame.getCurrentBasePanel(); - if (panel != null) { - final BibDatabase database = panel.getDatabase(); - List entries = panel.getSelectedEntries(); + LibraryTab libraryTab = frame.getCurrentLibraryTab(); + if (libraryTab != null) { + final BibDatabase database = libraryTab.getDatabase(); + List entries = libraryTab.getSelectedEntries(); if (!entries.isEmpty() && checkThatEntriesHaveKeys(entries)) { try { @@ -519,7 +519,7 @@ private boolean checkThatEntriesHaveKeys(List entries) { // Check if there are empty keys boolean emptyKeys = false; for (BibEntry entry : entries) { - if (!entry.getCitationKey().isPresent()) { + if (entry.getCitationKey().isEmpty()) { // Found one, no need to look further for now emptyKeys = true; break; @@ -537,22 +537,22 @@ private boolean checkThatEntriesHaveKeys(List entries) { Localization.lang("Generate keys"), Localization.lang("Cancel")); - BasePanel panel = frame.getCurrentBasePanel(); - if (citePressed && (panel != null)) { + LibraryTab libraryTab = frame.getCurrentLibraryTab(); + if (citePressed && (libraryTab != null)) { // Generate keys CitationKeyPatternPreferences prefs = jabRefPreferences.getCitationKeyPatternPreferences(); NamedCompound undoCompound = new NamedCompound(Localization.lang("Cite")); for (BibEntry entry : entries) { - if (!entry.getCitationKey().isPresent()) { + if (entry.getCitationKey().isEmpty()) { // Generate key - new CitationKeyGenerator(panel.getBibDatabaseContext(), prefs) + new CitationKeyGenerator(libraryTab.getBibDatabaseContext(), prefs) .generateAndSetKey(entry) .ifPresent(change -> undoCompound.addEdit(new UndoableKeyChange(change))); } } undoCompound.end(); // Add all undos - panel.getUndoManager().addEdit(undoCompound); + libraryTab.getUndoManager().addEdit(undoCompound); // Now every entry has a key return true; } else { diff --git a/src/main/java/org/jabref/gui/preferences/EntryEditorTabViewModel.java b/src/main/java/org/jabref/gui/preferences/EntryEditorTabViewModel.java index de221e7dfc8..b3ccf7c58ec 100644 --- a/src/main/java/org/jabref/gui/preferences/EntryEditorTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/EntryEditorTabViewModel.java @@ -71,15 +71,9 @@ public void setValues() { } switch (initialAutoCompletePreferences.getFirstNameMode()) { - case ONLY_ABBREVIATED: - firstNameModeAbbreviatedProperty.setValue(true); - break; - case ONLY_FULL: - firstNameModeFullProperty.setValue(true); - break; - default: - firstNameModeBothProperty.setValue(true); - break; + case ONLY_ABBREVIATED -> firstNameModeAbbreviatedProperty.setValue(true); + case ONLY_FULL -> firstNameModeFullProperty.setValue(true); + default -> firstNameModeBothProperty.setValue(true); } } @@ -92,7 +86,8 @@ public void storeSettings() { acceptRecommendationsProperty.getValue(), enableLatexCitationsTabProperty.getValue(), defaultSourceProperty.getValue(), - enableValidationProperty.getValue())); + enableValidationProperty.getValue(), + initialEntryEditorPreferences.getDividerPosition())); // default AutoCompletePreferences.NameFormat nameFormat = AutoCompletePreferences.NameFormat.BOTH; diff --git a/src/main/java/org/jabref/gui/preferences/GeneralTabViewModel.java b/src/main/java/org/jabref/gui/preferences/GeneralTabViewModel.java index 59af8c67886..777dac8e66f 100644 --- a/src/main/java/org/jabref/gui/preferences/GeneralTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/GeneralTabViewModel.java @@ -103,8 +103,8 @@ public void setValues() { selectedBiblatexModeProperty.setValue(initialGeneralPreferences.getDefaultBibDatabaseMode()); inspectionWarningDuplicateProperty.setValue(initialGeneralPreferences.isWarnAboutDuplicatesInInspection()); - confirmDeleteProperty.setValue(initialGeneralPreferences.isConfirmDelete()); - allowIntegerEditionProperty.setValue(initialGeneralPreferences.isAllowIntegerEditionBibtex()); + confirmDeleteProperty.setValue(initialGeneralPreferences.shouldConfirmDelete()); + allowIntegerEditionProperty.setValue(initialGeneralPreferences.shouldAllowIntegerEditionBibtex()); memoryStickModeProperty.setValue(initialGeneralPreferences.isMemoryStickMode()); collectTelemetryProperty.setValue(preferencesService.shouldCollectTelemetry()); showAdvancedHintsProperty.setValue(initialGeneralPreferences.shouldShowAdvancedHints()); diff --git a/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java b/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java index 0d02d553374..a02a2bcd5d2 100644 --- a/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java @@ -150,7 +150,7 @@ private void updateAfterPreferenceChanges() { PushToApplicationsManager manager = frame.getPushToApplicationsManager(); manager.updateApplicationAction(manager.getApplicationByName(externalApplicationsPreferences.getPushToApplicationName())); - frame.getBasePanelList().forEach(panel -> panel.getMainTable().getTableModel().refresh()); + frame.getLibraryTabs().forEach(panel -> panel.getMainTable().getTableModel().refresh()); } /** diff --git a/src/main/java/org/jabref/gui/preferences/PreviewTabViewModel.java b/src/main/java/org/jabref/gui/preferences/PreviewTabViewModel.java index 78956920079..87479ec4d1c 100644 --- a/src/main/java/org/jabref/gui/preferences/PreviewTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/PreviewTabViewModel.java @@ -23,10 +23,10 @@ import javafx.scene.input.Dragboard; import javafx.scene.input.TransferMode; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.DragAndDropDataFormats; import org.jabref.gui.JabRefGUI; +import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.CustomLocalDragboard; @@ -216,9 +216,9 @@ public void storeSettings() { preferences.storePreviewPreferences(newPreviewPreferences); - for (BasePanel basePanel : JabRefGUI.getMainFrame().getBasePanelList()) { + for (LibraryTab libraryTab : JabRefGUI.getMainFrame().getLibraryTabs()) { // TODO: Find a better way to update preview - basePanel.closeBottomPane(); + libraryTab.closeBottomPane(); // basePanel.getPreviewPanel().updateLayout(preferences.getPreviewPreferences()); } } diff --git a/src/main/java/org/jabref/gui/search/GlobalSearchBar.java b/src/main/java/org/jabref/gui/search/GlobalSearchBar.java index 6e674d584b4..9cc91baeee2 100644 --- a/src/main/java/org/jabref/gui/search/GlobalSearchBar.java +++ b/src/main/java/org/jabref/gui/search/GlobalSearchBar.java @@ -113,7 +113,7 @@ public GlobalSearchBar(JabRefFrame frame, StateManager stateManager, Preferences if (keyBinding.get().equals(KeyBinding.CLOSE)) { // Clear search and select first entry, if available searchField.setText(""); - frame.getCurrentBasePanel().getMainTable().getSelectionModel().selectFirst(); + frame.getCurrentLibraryTab().getMainTable().getSelectionModel().selectFirst(); event.consume(); } } diff --git a/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java b/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java index 845732f020a..18ffe5c365f 100644 --- a/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java +++ b/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java @@ -20,10 +20,10 @@ import javafx.scene.control.ButtonType; import org.jabref.gui.AbstractViewModel; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.exporter.SaveDatabaseAction; import org.jabref.gui.help.HelpAction; import org.jabref.gui.util.FileDialogConfiguration; @@ -87,9 +87,7 @@ public SharedDatabaseLoginDialogViewModel(JabRefFrame frame, DialogService dialo this.frame = frame; this.dialogService = dialogService; - EasyBind.subscribe(selectedDBMSType, selected -> { - port.setValue(Integer.toString(selected.getDefaultPort())); - }); + EasyBind.subscribe(selectedDBMSType, selected -> port.setValue(Integer.toString(selected.getDefaultPort()))); Predicate notEmpty = input -> (input != null) && !input.trim().isEmpty(); Predicate fileExists = input -> Files.exists(Path.of(input)); @@ -124,8 +122,7 @@ public boolean openDatabase() { .createDBMSConnectionProperties(); setupKeyStore(); - boolean connected = openSharedDatabase(connectionProperties); - return connected; + return openSharedDatabase(connectionProperties); } private void setupKeyStore() { @@ -161,12 +158,12 @@ private boolean openSharedDatabase(DBMSConnectionProperties connectionProperties try { SharedDatabaseUIManager manager = new SharedDatabaseUIManager(frame); - BasePanel panel = manager.openNewSharedDatabaseTab(connectionProperties); + LibraryTab libraryTab = manager.openNewSharedDatabaseTab(connectionProperties); setPreferences(); if (!folder.getValue().isEmpty()) { try { - new SaveDatabaseAction(panel, Globals.prefs, Globals.entryTypesManager).saveAs(Path.of(folder.getValue())); + new SaveDatabaseAction(libraryTab, Globals.prefs, Globals.entryTypesManager).saveAs(Path.of(folder.getValue())); } catch (Throwable e) { LOGGER.error("Error while saving the database", e); } @@ -255,7 +252,7 @@ private void applyPreferences() { } private boolean isSharedDatabaseAlreadyPresent(DBMSConnectionProperties connectionProperties) { - List panels = frame.getBasePanelList(); + List panels = frame.getLibraryTabs(); return panels.parallelStream().anyMatch(panel -> { BibDatabaseContext context = panel.getBibDatabaseContext(); @@ -271,9 +268,7 @@ public void showSaveDbToFileDialog() { .withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)) .build(); Optional exportPath = dialogService.showFileSaveDialog(fileDialogConfiguration); - exportPath.ifPresent(path -> { - folder.setValue(path.toString()); - }); + exportPath.ifPresent(path -> folder.setValue(path.toString())); } public void showOpenKeystoreFileDialog() { @@ -284,9 +279,7 @@ public void showOpenKeystoreFileDialog() { .withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)) .build(); Optional keystorePath = dialogService.showFileOpenDialog(fileDialogConfiguration); - keystorePath.ifPresent(path -> { - keystore.setValue(path.toString()); - }); + keystorePath.ifPresent(path -> keystore.setValue(path.toString())); } public StringProperty databaseproperty() { diff --git a/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java b/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java index d80128d9af5..a089aadf723 100644 --- a/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java +++ b/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java @@ -9,10 +9,10 @@ import javafx.scene.control.ButtonBar.ButtonData; import javafx.scene.control.ButtonType; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.entryeditor.EntryEditor; import org.jabref.gui.exporter.SaveDatabaseAction; import org.jabref.gui.mergeentries.MergeEntriesDialog; @@ -66,7 +66,7 @@ public void listen(ConnectionLostEvent connectionLostEvent) { new SharedDatabaseLoginDialogView(jabRefFrame).showAndWait(); } else if (answer.get().equals(workOffline)) { connectionLostEvent.getBibDatabaseContext().convertToLocalDatabase(); - jabRefFrame.refreshTitleAndTabs(); + // jabRefFrame.refreshWindowAndTabTitles(); jabRefFrame.getDialogService().notify(Localization.lang("Working offline.")); } } else { @@ -115,10 +115,10 @@ public void listen(UpdateRefusedEvent updateRefusedEvent) { @Subscribe public void listen(SharedEntriesNotPresentEvent event) { - BasePanel panel = jabRefFrame.getCurrentBasePanel(); - EntryEditor entryEditor = panel.getEntryEditor(); + LibraryTab libraryTab = jabRefFrame.getCurrentLibraryTab(); + EntryEditor entryEditor = libraryTab.getEntryEditor(); - panel.getUndoManager().addEdit(new UndoableRemoveEntries(panel.getDatabase(), event.getBibEntries())); + libraryTab.getUndoManager().addEdit(new UndoableRemoveEntries(libraryTab.getDatabase(), event.getBibEntries())); if (Objects.nonNull(entryEditor) && (event.getBibEntries().contains(entryEditor.getEntry()))) { @@ -126,7 +126,7 @@ public void listen(SharedEntriesNotPresentEvent event) { Localization.lang("The entry you currently work on has been deleted on the shared side.") + "\n" + Localization.lang("You can restore the entry using the \"Undo\" operation.")); - panel.closeBottomPane(); + libraryTab.closeBottomPane(); } } @@ -136,7 +136,7 @@ public void listen(SharedEntriesNotPresentEvent event) { * @param dbmsConnectionProperties Connection data * @return BasePanel which also used by {@link SaveDatabaseAction} */ - public BasePanel openNewSharedDatabaseTab(DBMSConnectionProperties dbmsConnectionProperties) + public LibraryTab openNewSharedDatabaseTab(DBMSConnectionProperties dbmsConnectionProperties) throws SQLException, DatabaseNotSupportedException, InvalidDBMSConnectionPropertiesException { BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(); diff --git a/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java b/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java index 40f6567342e..4701b93a58d 100644 --- a/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java +++ b/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java @@ -76,9 +76,9 @@ public void execute() { } ce.end(); if (ce.hasEdits()) { - frame.getCurrentBasePanel().getUndoManager().addEdit(ce); - frame.getCurrentBasePanel().markBaseChanged(); - frame.getCurrentBasePanel().updateEntryEditorIfShowing(); + frame.getCurrentLibraryTab().getUndoManager().addEdit(ce); + frame.getCurrentLibraryTab().markBaseChanged(); + frame.getCurrentLibraryTab().updateEntryEditorIfShowing(); String outText; if (nullFieldIfValueIsTheSame || value == null) { outText = getTextDone(specialField, Integer.toString(bes.size())); diff --git a/src/main/java/org/jabref/gui/undo/UndoRedoAction.java b/src/main/java/org/jabref/gui/undo/UndoRedoAction.java index eb2e7e7cf50..9515bda0654 100644 --- a/src/main/java/org/jabref/gui/undo/UndoRedoAction.java +++ b/src/main/java/org/jabref/gui/undo/UndoRedoAction.java @@ -3,9 +3,9 @@ import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; @@ -35,26 +35,26 @@ public UndoRedoAction(StandardActions action, JabRefFrame frame, DialogService d @Override public void execute() { - BasePanel panel = frame.getCurrentBasePanel(); + LibraryTab libraryTab = frame.getCurrentLibraryTab(); if (action == StandardActions.UNDO) { try { - panel.getUndoManager().undo(); - panel.markBaseChanged(); + libraryTab.getUndoManager().undo(); + libraryTab.markBaseChanged(); dialogService.notify(Localization.lang("Undo")); } catch (CannotUndoException ex) { dialogService.notify(Localization.lang("Nothing to undo") + '.'); } - frame.getCurrentBasePanel().markChangedOrUnChanged(); + frame.getCurrentLibraryTab().markChangedOrUnChanged(); } else if (action == StandardActions.REDO) { try { - panel.getUndoManager().redo(); - panel.markBaseChanged(); + libraryTab.getUndoManager().redo(); + libraryTab.markBaseChanged(); dialogService.notify(Localization.lang("Redo")); } catch (CannotRedoException ex) { dialogService.notify(Localization.lang("Nothing to redo") + '.'); } - panel.markChangedOrUnChanged(); + libraryTab.markChangedOrUnChanged(); } else { LOGGER.debug("No undo/redo action: " + action.name()); } diff --git a/src/main/java/org/jabref/logic/formatter/bibtexfields/NormalizePagesFormatter.java b/src/main/java/org/jabref/logic/formatter/bibtexfields/NormalizePagesFormatter.java index ff0d6d7028e..8a9163b73f5 100644 --- a/src/main/java/org/jabref/logic/formatter/bibtexfields/NormalizePagesFormatter.java +++ b/src/main/java/org/jabref/logic/formatter/bibtexfields/NormalizePagesFormatter.java @@ -62,7 +62,7 @@ public String format(String value) { // Remove pages prefix String cleanValue = value.replace("pp.", "").replace("p.", ""); - // remove unwanted literals incl. whitespace + // remove unwanted literals including en dash, em dash, and whitespace cleanValue = cleanValue.replaceAll("\u2013|\u2014", "-").replaceAll(REJECT_LITERALS, ""); // try to find pages pattern Matcher matcher = PAGES_DETECT_PATTERN.matcher(cleanValue); diff --git a/src/main/java/org/jabref/preferences/GeneralPreferences.java b/src/main/java/org/jabref/preferences/GeneralPreferences.java index 8e80440da53..0db44970816 100644 --- a/src/main/java/org/jabref/preferences/GeneralPreferences.java +++ b/src/main/java/org/jabref/preferences/GeneralPreferences.java @@ -9,7 +9,7 @@ public class GeneralPreferences { private final BibDatabaseMode defaultBibDatabaseMode; private final boolean warnAboutDuplicatesInInspection; - private final boolean confirmDelete; + private boolean confirmDelete; private final boolean allowIntegerEditionBibtex; private final boolean memoryStickMode; private final boolean collectTelemetry; @@ -45,11 +45,16 @@ public boolean isWarnAboutDuplicatesInInspection() { return warnAboutDuplicatesInInspection; } - public boolean isConfirmDelete() { + public boolean shouldConfirmDelete() { return confirmDelete; } - public boolean isAllowIntegerEditionBibtex() { + public GeneralPreferences withConfirmDelete(boolean confirmDelete) { + this.confirmDelete = confirmDelete; + return this; + } + + public boolean shouldAllowIntegerEditionBibtex() { return allowIntegerEditionBibtex; } @@ -57,7 +62,7 @@ public boolean isMemoryStickMode() { return memoryStickMode; } - public boolean isCollectTelemetry() { + public boolean shouldCollectTelemetry() { return collectTelemetry; } diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index f3e6517cce9..2e908686c38 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -1526,10 +1526,10 @@ public void storeGeneralPreferences(GeneralPreferences preferences) { put(DEFAULT_ENCODING, preferences.getDefaultEncoding().name()); putBoolean(BIBLATEX_DEFAULT_MODE, (preferences.getDefaultBibDatabaseMode() == BibDatabaseMode.BIBLATEX)); putBoolean(WARN_ABOUT_DUPLICATES_IN_INSPECTION, preferences.isWarnAboutDuplicatesInInspection()); - putBoolean(CONFIRM_DELETE, preferences.isConfirmDelete()); - putBoolean(ALLOW_INTEGER_EDITION_BIBTEX, preferences.isAllowIntegerEditionBibtex()); + putBoolean(CONFIRM_DELETE, preferences.shouldConfirmDelete()); + putBoolean(ALLOW_INTEGER_EDITION_BIBTEX, preferences.shouldAllowIntegerEditionBibtex()); putBoolean(MEMORY_STICK_MODE, preferences.isMemoryStickMode()); - setShouldCollectTelemetry(preferences.isCollectTelemetry()); + setShouldCollectTelemetry(preferences.shouldCollectTelemetry()); putBoolean(SHOW_ADVANCED_HINTS, preferences.shouldShowAdvancedHints()); } @@ -1735,7 +1735,8 @@ public EntryEditorPreferences getEntryEditorPreferences() { getBoolean(ACCEPT_RECOMMENDATIONS), getBoolean(SHOW_LATEX_CITATIONS), getBoolean(DEFAULT_SHOW_SOURCE), - getBoolean(VALIDATE_IN_ENTRY_EDITOR)); + getBoolean(VALIDATE_IN_ENTRY_EDITOR), + getDouble(ENTRY_EDITOR_HEIGHT)); } @Override @@ -1747,6 +1748,7 @@ public void storeEntryEditorPreferences(EntryEditorPreferences preferences) { putBoolean(SHOW_LATEX_CITATIONS, preferences.shouldShowLatexCitationsTab()); putBoolean(DEFAULT_SHOW_SOURCE, preferences.showSourceTabByDefault()); putBoolean(VALIDATE_IN_ENTRY_EDITOR, preferences.isEnableValidation()); + putDouble(ENTRY_EDITOR_HEIGHT, preferences.getDividerPosition()); } //************************************************************************************************************* diff --git a/src/test/java/org/jabref/gui/exporter/SaveDatabaseActionTest.java b/src/test/java/org/jabref/gui/exporter/SaveDatabaseActionTest.java index 5ee07e4d58e..d6a4e5af45d 100644 --- a/src/test/java/org/jabref/gui/exporter/SaveDatabaseActionTest.java +++ b/src/test/java/org/jabref/gui/exporter/SaveDatabaseActionTest.java @@ -10,9 +10,9 @@ import java.util.Optional; import java.util.stream.Collectors; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.undo.CountingUndoManager; import org.jabref.gui.util.FileDialogConfiguration; import org.jabref.logic.bibtex.FieldContentFormatterPreferences; @@ -48,18 +48,18 @@ class SaveDatabaseActionTest { private Path file = Path.of(TEST_BIBTEX_LIBRARY_LOCATION); private DialogService dialogService = mock(DialogService.class); private JabRefPreferences preferences = mock(JabRefPreferences.class); - private BasePanel basePanel = mock(BasePanel.class); + private LibraryTab libraryTab = mock(LibraryTab.class); private JabRefFrame jabRefFrame = mock(JabRefFrame.class); private BibDatabaseContext dbContext = spy(BibDatabaseContext.class); private SaveDatabaseAction saveDatabaseAction; @BeforeEach public void setUp() { - when(basePanel.frame()).thenReturn(jabRefFrame); - when(basePanel.getBibDatabaseContext()).thenReturn(dbContext); + when(libraryTab.frame()).thenReturn(jabRefFrame); + when(libraryTab.getBibDatabaseContext()).thenReturn(dbContext); when(jabRefFrame.getDialogService()).thenReturn(dialogService); - saveDatabaseAction = spy(new SaveDatabaseAction(basePanel, preferences, mock(BibEntryTypesManager.class))); + saveDatabaseAction = spy(new SaveDatabaseAction(libraryTab, preferences, mock(BibEntryTypesManager.class))); } @Test @@ -106,7 +106,7 @@ private SaveDatabaseAction createSaveDatabaseActionForBibDatabase(BibDatabase da SavePreferences savePreferences = mock(SavePreferences.class); // In case a "thenReturn" is modified, the whole mock has to be recreated dbContext = mock(BibDatabaseContext.class); - basePanel = mock(BasePanel.class); + libraryTab = mock(LibraryTab.class); MetaData metaData = mock(MetaData.class); when(savePreferences.withEncoding(any(Charset.class))).thenReturn(savePreferences); when(savePreferences.withSaveType(any(SavePreferences.DatabaseSaveType.class))).thenReturn(savePreferences); @@ -125,11 +125,11 @@ private SaveDatabaseAction createSaveDatabaseActionForBibDatabase(BibDatabase da when(preferences.getDefaultEncoding()).thenReturn(StandardCharsets.UTF_8); when(preferences.getFieldContentParserPreferences()).thenReturn(mock(FieldContentFormatterPreferences.class)); when(preferences.getSavePreferences()).thenReturn(savePreferences); - when(basePanel.frame()).thenReturn(jabRefFrame); - when(basePanel.getBibDatabaseContext()).thenReturn(dbContext); - when(basePanel.getUndoManager()).thenReturn(mock(CountingUndoManager.class)); - when(basePanel.getBibDatabaseContext()).thenReturn(dbContext); - saveDatabaseAction = new SaveDatabaseAction(basePanel, preferences, mock(BibEntryTypesManager.class)); + when(libraryTab.frame()).thenReturn(jabRefFrame); + when(libraryTab.getBibDatabaseContext()).thenReturn(dbContext); + when(libraryTab.getUndoManager()).thenReturn(mock(CountingUndoManager.class)); + when(libraryTab.getBibDatabaseContext()).thenReturn(dbContext); + saveDatabaseAction = new SaveDatabaseAction(libraryTab, preferences, mock(BibEntryTypesManager.class)); return saveDatabaseAction; } From 7f4c36c03629498db8e56281906241a82846e1e3 Mon Sep 17 00:00:00 2001 From: Johannes Theiner Date: Thu, 22 Oct 2020 03:13:11 +0200 Subject: [PATCH 017/201] Support for exporting to YAML format (#7007) --- CHANGELOG.md | 2 + .../logic/exporter/BlankLineBehaviour.java | 9 ++ .../logic/exporter/ExporterFactory.java | 3 +- .../logic/exporter/TemplateExporter.java | 75 ++++++------ .../org/jabref/logic/layout/LayoutEntry.java | 3 + .../jabref/logic/layout/format/CSLType.java | 21 ++++ .../jabref/logic/util/StandardFileType.java | 3 +- .../resource/layout/yaml.begin.layout | 2 + .../resources/resource/layout/yaml.end.layout | 1 + .../resources/resource/layout/yaml.layout | 14 +++ .../logic/exporter/YamlExporterTest.java | 108 ++++++++++++++++++ 11 files changed, 201 insertions(+), 40 deletions(-) create mode 100644 src/main/java/org/jabref/logic/exporter/BlankLineBehaviour.java create mode 100644 src/main/java/org/jabref/logic/layout/format/CSLType.java create mode 100644 src/main/resources/resource/layout/yaml.begin.layout create mode 100644 src/main/resources/resource/layout/yaml.end.layout create mode 100644 src/main/resources/resource/layout/yaml.layout create mode 100644 src/test/java/org/jabref/logic/exporter/YamlExporterTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index cffb38db9c2..e09adc83e2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We added a query parser and mapping layer to enable conversion of queries formulated in simplified lucene syntax by the user into api queries. [#6799](https://github.com/JabRef/jabref/pull/6799) - We added some basic functionality to customise the look of JabRef by importing a css theme file. [#5790](https://github.com/JabRef/jabref/issues/5790) - We added connection check function in network preference setting [#6560](https://github.com/JabRef/jabref/issues/6560) +- We added support for exporting to YAML. [#6974](https://github.com/JabRef/jabref/issues/6974) - We added a DOI format and organization check to detect [American Physical Society](https://journals.aps.org/) journals to copy the article ID to the page field for cases where the page numbers are missing. [#7019](https://github.com/JabRef/jabref/issues/7019) - We added a new fetcher to enable users to search jstor.org [#6627](https://github.com/JabRef/jabref/issues/6627) @@ -57,6 +58,7 @@ inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://git - We fixed an issue with the python script used by browser plugins that failed to locate JabRef if not installed in its default location. [#6963](https://github.com/JabRef/jabref/pull/6963/files) - We fixed an issue where spaces and newlines in an isbn would generate an exception. [#6456](https://github.com/JabRef/jabref/issues/6456) - We fixed an issue where identity column header had incorrect foreground color in the Dark theme. [#6796](https://github.com/JabRef/jabref/issues/6796) +- We fixed an issue where the RIS exporter added extra blank lines.[#7007](https://github.com/JabRef/jabref/pull/7007/files) - We fixed an issue where clicking on Collapse All button in the Search for Unlinked Local Files expanded the directory structure erroneously [#6848](https://github.com/JabRef/jabref/issues/6848) - We fixed an issue, when pulling changes from shared database via shortcut caused creation a new new tech report [6867](https://github.com/JabRef/jabref/issues/6867) - We fixed an issue where the JabRef GUI does not highlight the "All entries" group on start-up [#6691](https://github.com/JabRef/jabref/issues/6691) diff --git a/src/main/java/org/jabref/logic/exporter/BlankLineBehaviour.java b/src/main/java/org/jabref/logic/exporter/BlankLineBehaviour.java new file mode 100644 index 00000000000..96957869a27 --- /dev/null +++ b/src/main/java/org/jabref/logic/exporter/BlankLineBehaviour.java @@ -0,0 +1,9 @@ +package org.jabref.logic.exporter; + +/** + * This enum represents the behaviour for blank lines in {@link TemplateExporter} + */ +public enum BlankLineBehaviour { + KEEP_BLANKS, + DELETE_BLANKS +} diff --git a/src/main/java/org/jabref/logic/exporter/ExporterFactory.java b/src/main/java/org/jabref/logic/exporter/ExporterFactory.java index fbba86a6aa4..082e284a245 100644 --- a/src/main/java/org/jabref/logic/exporter/ExporterFactory.java +++ b/src/main/java/org/jabref/logic/exporter/ExporterFactory.java @@ -49,8 +49,9 @@ public static ExporterFactory create(List customFormats, exporters.add(new TemplateExporter("ISO 690", "iso690txt", "iso690", "iso690txt", StandardFileType.TXT, layoutPreferences, savePreferences)); exporters.add(new TemplateExporter("Endnote", "endnote", "EndNote", "endnote", StandardFileType.TXT, layoutPreferences, savePreferences)); exporters.add(new TemplateExporter("OpenOffice/LibreOffice CSV", "oocsv", "openoffice-csv", "openoffice", StandardFileType.CSV, layoutPreferences, savePreferences)); - exporters.add(new TemplateExporter("RIS", "ris", "ris", "ris", StandardFileType.RIS, layoutPreferences, savePreferences).withEncoding(StandardCharsets.UTF_8)); + exporters.add(new TemplateExporter("RIS", "ris", "ris", "ris", StandardFileType.RIS, layoutPreferences, savePreferences, BlankLineBehaviour.DELETE_BLANKS).withEncoding(StandardCharsets.UTF_8)); exporters.add(new TemplateExporter("MIS Quarterly", "misq", "misq", "misq", StandardFileType.RTF, layoutPreferences, savePreferences)); + exporters.add(new TemplateExporter("CSL YAML", "yaml", "yaml", null, StandardFileType.YAML, layoutPreferences, savePreferences, BlankLineBehaviour.DELETE_BLANKS)); exporters.add(new BibTeXMLExporter()); exporters.add(new OpenOfficeDocumentCreator()); exporters.add(new OpenDocumentSpreadsheetCreator()); diff --git a/src/main/java/org/jabref/logic/exporter/TemplateExporter.java b/src/main/java/org/jabref/logic/exporter/TemplateExporter.java index af075471e3e..4e3230e509d 100644 --- a/src/main/java/org/jabref/logic/exporter/TemplateExporter.java +++ b/src/main/java/org/jabref/logic/exporter/TemplateExporter.java @@ -14,12 +14,12 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.regex.Pattern; import org.jabref.logic.layout.Layout; import org.jabref.logic.layout.LayoutFormatterPreferences; import org.jabref.logic.layout.LayoutHelper; import org.jabref.logic.util.FileType; +import org.jabref.logic.util.OS; import org.jabref.logic.util.StandardFileType; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; @@ -33,15 +33,8 @@ */ public class TemplateExporter extends Exporter { + private static final String BLANK_LINE_PATTERN = "\\r\\n|\\n"; private static final String LAYOUT_PREFIX = "/resource/layout/"; - - /** - * A regular expression that matches blank lines - * - * ?m activates "multimode", which makes ^ match line starts/ends. - * \\s simply marks any whitespace character - */ - private static final Pattern BLANK_LINE_MATCHER = Pattern.compile("(?m)^\\s"); private static final String LAYOUT_EXTENSION = ".layout"; private static final String FORMATTERS_EXTENSION = ".formatters"; private static final String BEGIN_INFIX = ".begin"; @@ -55,11 +48,10 @@ public class TemplateExporter extends Exporter { private final SavePreferences savePreferences; private Charset encoding; // If this value is set, it will be used to override the default encoding for the getCurrentBasePanel. private boolean customExport; - private boolean deleteBlankLines; + private BlankLineBehaviour blankLineBehaviour; /** - * Initialize another export format based on templates stored in dir with - * layoutFile lfFilename. + * Initialize another export format based on templates stored in dir with layoutFile lfFilename. * * @param displayName Name to display to the user. * @param consoleName Name to call this format in the console. @@ -72,8 +64,7 @@ public TemplateExporter(String displayName, String consoleName, String lfFileNam } /** - * Initialize another export format based on templates stored in dir with - * layoutFile lfFilename. + * Initialize another export format based on templates stored in dir with layoutFile lfFilename. * * @param name to display to the user and to call this format in the console. * @param lfFileName Name of the main layout file. @@ -87,8 +78,7 @@ public TemplateExporter(String name, String lfFileName, String extension, Layout } /** - * Initialize another export format based on templates stored in dir with - * layoutFile lfFilename. + * Initialize another export format based on templates stored in dir with layoutFile lfFilename. * * @param displayName Name to display to the user. * @param consoleName Name to call this format in the console. @@ -112,27 +102,36 @@ public TemplateExporter(String displayName, String consoleName, String lfFileNam } /** - * Initialize another export format based on templates stored in dir with - * layoutFile lfFilename. - * The display name is automatically derived from the FileType + * Initialize another export format based on templates stored in dir with layoutFile lfFilename. * + * @param displayName Name to display to the user. * @param consoleName Name to call this format in the console. * @param lfFileName Name of the main layout file. * @param directory Directory in which to find the layout file. * @param extension Should contain the . (for instance .txt). * @param layoutPreferences Preferences for layout * @param savePreferences Preferences for saving - * @param deleteBlankLines If blank lines should be remove (default: false) + * @param blankLineBehaviour how to behave regarding blank lines. */ - public TemplateExporter(String consoleName, String lfFileName, String directory, StandardFileType extension, LayoutFormatterPreferences layoutPreferences, SavePreferences savePreferences, boolean deleteBlankLines) { - this(consoleName, consoleName, lfFileName, directory, extension, layoutPreferences, savePreferences); - this.deleteBlankLines = deleteBlankLines; + public TemplateExporter(String displayName, String consoleName, String lfFileName, String directory, FileType extension, + LayoutFormatterPreferences layoutPreferences, SavePreferences savePreferences, + BlankLineBehaviour blankLineBehaviour) { + super(consoleName, displayName, extension); + if (Objects.requireNonNull(lfFileName).endsWith(LAYOUT_EXTENSION)) { + this.lfFileName = lfFileName.substring(0, lfFileName.length() - LAYOUT_EXTENSION.length()); + } else { + this.lfFileName = lfFileName; + } + this.directory = directory; + this.layoutPreferences = layoutPreferences; + this.savePreferences = savePreferences; + this.blankLineBehaviour = blankLineBehaviour; } /** - * Indicate whether this is a custom export. A custom export looks for its - * layout files using a normal file path, while a built-in export looks in - * the classpath. + * Indicate whether this is a custom export. + * A custom export looks for its layout files using a normal file path, + * while a built-in export looks in the classpath. * * @param custom true to indicate a custom export format. */ @@ -141,8 +140,7 @@ public void setCustomExport(boolean custom) { } /** - * Set an encoding which will be used in preference to the default value - * obtained from the basepanel. + * Set an encoding which will be used in preference to the default value obtained from the basepanel. * * @param encoding The name of the encoding to use. */ @@ -152,11 +150,9 @@ public TemplateExporter withEncoding(Charset encoding) { } /** - * This method should return a reader from which the given layout file can - * be read. + * This method should return a reader from which the given layout file can be read. *

- * Subclasses of TemplateExporter are free to override and provide their own - * implementation. + * Subclasses of TemplateExporter are free to override and provide their own implementation. * * @param filename the filename * @return a newly created reader @@ -277,9 +273,13 @@ public void export(final BibDatabaseContext databaseContext, final Path file, // Write the entry if (layout != null) { - if (deleteBlankLines) { - String withoutBlankLines = BLANK_LINE_MATCHER.matcher(layout.doLayout(entry, databaseContext.getDatabase())).replaceAll(""); - ps.write(withoutBlankLines); + if (blankLineBehaviour == BlankLineBehaviour.DELETE_BLANKS) { + String[] lines = layout.doLayout(entry, databaseContext.getDatabase()).split(BLANK_LINE_PATTERN); + for (String line : lines) { + if (!line.isBlank() && !line.isEmpty()) { + ps.write(line + OS.NEWLINE); + } + } } else { ps.write(layout.doLayout(entry, databaseContext.getDatabase())); } @@ -316,9 +316,8 @@ public void export(final BibDatabaseContext databaseContext, final Path file, } /** - * See if there is a name formatter file bundled with this export format. If so, read - * all the name formatters so they can be used by the filter layouts. - * + * See if there is a name formatter file bundled with this export format. + * If so, read all the name formatters so they can be used by the filter layouts. */ private void readFormatterFile() { File formatterFile = new File(lfFileName + FORMATTERS_EXTENSION); diff --git a/src/main/java/org/jabref/logic/layout/LayoutEntry.java b/src/main/java/org/jabref/logic/layout/LayoutEntry.java index fe5632f9f5b..8a1f6e7349f 100644 --- a/src/main/java/org/jabref/logic/layout/LayoutEntry.java +++ b/src/main/java/org/jabref/logic/layout/LayoutEntry.java @@ -32,6 +32,7 @@ import org.jabref.logic.layout.format.AuthorNatBib; import org.jabref.logic.layout.format.AuthorOrgSci; import org.jabref.logic.layout.format.Authors; +import org.jabref.logic.layout.format.CSLType; import org.jabref.logic.layout.format.CompositeFormat; import org.jabref.logic.layout.format.CreateBibORDFAuthors; import org.jabref.logic.layout.format.CreateDocBook4Authors; @@ -539,6 +540,8 @@ private LayoutFormatter getLayoutFormatterByName(String name) { return new WrapFileLinks(prefs.getFileLinkPreferences()); case "Markdown": return new MarkdownFormatter(); + case "CSLType": + return new CSLType(); default: return null; } diff --git a/src/main/java/org/jabref/logic/layout/format/CSLType.java b/src/main/java/org/jabref/logic/layout/format/CSLType.java new file mode 100644 index 00000000000..aac863567fe --- /dev/null +++ b/src/main/java/org/jabref/logic/layout/format/CSLType.java @@ -0,0 +1,21 @@ +package org.jabref.logic.layout.format; + +import org.jabref.logic.layout.LayoutFormatter; +import org.jabref.model.entry.types.StandardEntryType; + +public class CSLType implements LayoutFormatter { + + @Override + public String format(String value) { + return switch (StandardEntryType.valueOf(value)) { + case Article -> "article"; + case Book -> "book"; + case Conference -> "paper-conference"; + case Report, TechReport -> "report"; + case Thesis, MastersThesis, PhdThesis -> "thesis"; + case WWW, Online -> "webpage"; + + default -> "no-type"; + }; + } +} diff --git a/src/main/java/org/jabref/logic/util/StandardFileType.java b/src/main/java/org/jabref/logic/util/StandardFileType.java index 45c48b6cf76..64cbff44d41 100644 --- a/src/main/java/org/jabref/logic/util/StandardFileType.java +++ b/src/main/java/org/jabref/logic/util/StandardFileType.java @@ -41,7 +41,8 @@ public enum StandardFileType implements FileType { JSON("json"), XMP("xmp"), ZIP("zip"), - CSS("css"); + CSS("css"), + YAML("yaml"); private final List extensions; diff --git a/src/main/resources/resource/layout/yaml.begin.layout b/src/main/resources/resource/layout/yaml.begin.layout new file mode 100644 index 00000000000..3d4a00f98c8 --- /dev/null +++ b/src/main/resources/resource/layout/yaml.begin.layout @@ -0,0 +1,2 @@ +--- +references: diff --git a/src/main/resources/resource/layout/yaml.end.layout b/src/main/resources/resource/layout/yaml.end.layout new file mode 100644 index 00000000000..ed97d539c09 --- /dev/null +++ b/src/main/resources/resource/layout/yaml.end.layout @@ -0,0 +1 @@ +--- diff --git a/src/main/resources/resource/layout/yaml.layout b/src/main/resources/resource/layout/yaml.layout new file mode 100644 index 00000000000..94e4f02a9b9 --- /dev/null +++ b/src/main/resources/resource/layout/yaml.layout @@ -0,0 +1,14 @@ +- id: \citationkey +\begin{entrytype} type: \format[CSLType]{\entrytype}\end{entrytype} +\begin{author} + author: + - literal: "\author" +\end{author} +\begin{title} title: "\title"\end{title} +\begin{shorttitle} title-short: "\shorttitle"\end{shorttitle} +\begin{date} issued: \date\end{date} +\begin{url} url: \url\end{url} +\begin{doi} doi: \doi\end{doi} +\begin{volume} volume: \volume\end{volume} +\begin{number} number: \number\end{number} +\begin{urldate} accessed: \urldate\end{urldate} diff --git a/src/test/java/org/jabref/logic/exporter/YamlExporterTest.java b/src/test/java/org/jabref/logic/exporter/YamlExporterTest.java new file mode 100644 index 00000000000..cb5268cf005 --- /dev/null +++ b/src/test/java/org/jabref/logic/exporter/YamlExporterTest.java @@ -0,0 +1,108 @@ +package org.jabref.logic.exporter; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jabref.logic.layout.LayoutFormatterPreferences; +import org.jabref.logic.xmp.XmpPreferences; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Answers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +public class YamlExporterTest { + + private static Charset charset; + private static Exporter yamlExporter; + private static BibDatabaseContext databaseContext; + + @BeforeAll + static void setUp() { + List customFormats = new ArrayList<>(); + LayoutFormatterPreferences layoutPreferences = mock(LayoutFormatterPreferences.class, Answers.RETURNS_DEEP_STUBS); + SavePreferences savePreferences = mock(SavePreferences.class); + XmpPreferences xmpPreferences = mock(XmpPreferences.class); + ExporterFactory exporterFactory = ExporterFactory.create(customFormats, layoutPreferences, savePreferences, xmpPreferences); + + databaseContext = new BibDatabaseContext(); + charset = StandardCharsets.UTF_8; + yamlExporter = exporterFactory.getExporterByName("yaml").get(); + } + + @Test + public final void exportForNoEntriesWritesNothing(@TempDir Path tempFile) throws Exception { + Path file = tempFile.resolve("ThisIsARandomlyNamedFile"); + Files.createFile(file); + yamlExporter.export(databaseContext, tempFile, charset, Collections.emptyList()); + assertEquals(Collections.emptyList(), Files.readAllLines(file)); + } + + @Test + public final void exportsCorrectContent(@TempDir Path tempFile) throws Exception { + BibEntry entry = new BibEntry(StandardEntryType.Article) + .withCitationKey("test") + .withField(StandardField.AUTHOR, "Test Author") + .withField(StandardField.TITLE, "Test Title") + .withField(StandardField.URL, "http://example.com") + .withField(StandardField.DATE, "2020-10-14"); + + Path file = tempFile.resolve("RandomFileName"); + Files.createFile(file); + yamlExporter.export(databaseContext, file, charset, Collections.singletonList(entry)); + + List expected = List.of( + "---", + "references:", + "- id: test", + " type: article", + " author:", + " - literal: \"Test Author\"", + " title: \"Test Title\"", + " issued: 2020-10-14", + " url: http://example.com", + "---"); + + assertEquals(expected, Files.readAllLines(file)); + } + + @Test + public final void formatsContentCorrect(@TempDir Path tempFile) throws Exception { + BibEntry entry = new BibEntry(StandardEntryType.Misc) + .withCitationKey("test") + .withField(StandardField.AUTHOR, "Test Author") + .withField(StandardField.TITLE, "Test Title") + .withField(StandardField.URL, "http://example.com") + .withField(StandardField.DATE, "2020-10-14"); + + Path file = tempFile.resolve("RandomFileName"); + Files.createFile(file); + yamlExporter.export(databaseContext, file, charset, Collections.singletonList(entry)); + + List expected = List.of( + "---", + "references:", + "- id: test", + " type: no-type", + " author:", + " - literal: \"Test Author\"", + " title: \"Test Title\"", + " issued: 2020-10-14", + " url: http://example.com", + "---"); + + assertEquals(expected, Files.readAllLines(file)); + } +} From 590e2c3727ea3d6b6b872875c77ee0d16ae2b154 Mon Sep 17 00:00:00 2001 From: Timucin Merdin Date: Fri, 23 Oct 2020 01:05:42 +0200 Subject: [PATCH 018/201] Added error msg when isbn entry was not found (#7036) --- CHANGELOG.md | 1 + src/main/java/org/jabref/gui/EntryTypeViewModel.java | 4 ++++ .../logic/importer/fetcher/IsbnViaOttoBibFetcher.java | 7 +++++++ 3 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e09adc83e2f..55d21265e7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We added a DOI format and organization check to detect [American Physical Society](https://journals.aps.org/) journals to copy the article ID to the page field for cases where the page numbers are missing. [#7019](https://github.com/JabRef/jabref/issues/7019) - We added a new fetcher to enable users to search jstor.org [#6627](https://github.com/JabRef/jabref/issues/6627) +- We added an error message in the New Entry dialog that is shown in case the fetcher did not find anything . [#7000](https://github.com/JabRef/jabref/issues/7000) ### Changed diff --git a/src/main/java/org/jabref/gui/EntryTypeViewModel.java b/src/main/java/org/jabref/gui/EntryTypeViewModel.java index c2d055c08bb..21f60791931 100644 --- a/src/main/java/org/jabref/gui/EntryTypeViewModel.java +++ b/src/main/java/org/jabref/gui/EntryTypeViewModel.java @@ -176,6 +176,10 @@ public void runFetcherWorker() { searchSuccesfulProperty.set(true); } else if (StringUtil.isBlank(idText.getValue())) { dialogService.showWarningDialogAndWait(Localization.lang("Empty search ID"), Localization.lang("The given search ID was empty.")); + } else if (result.isEmpty()) { + String fetcher = selectedItemProperty().getValue().getName(); + String searchId = idText.getValue(); + dialogService.showErrorDialogAndWait(Localization.lang("Fetcher '%0' did not find an entry for id '%1'.", fetcher, searchId)); } fetcherWorker = new FetcherWorker(); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/IsbnViaOttoBibFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/IsbnViaOttoBibFetcher.java index 28402523e19..30e6f8513ff 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/IsbnViaOttoBibFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/IsbnViaOttoBibFetcher.java @@ -58,6 +58,13 @@ public Optional performSearchById(String identifier) throws FetcherExc throw new FetcherException("Could not ", e); } Element textArea = html.select("textarea").first(); + + // inspect the "no results" error message (if there is one) + Optional potentialErrorMessageDiv = Optional.ofNullable((html.select("div#flash-notice.notice.add-bottom").first())); + if (potentialErrorMessageDiv.isPresent() && potentialErrorMessageDiv.get().text().contains("No Results")) { + LOGGER.error("ISBN {} not found at ottobib", identifier); + } + Optional entry = Optional.empty(); try { entry = BibtexParser.singleFromString(textArea.text(), importFormatPreferences, new DummyFileUpdateMonitor()); From c9c4554b0d3af807af204da910ef549ba4f22a85 Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Sat, 24 Oct 2020 15:10:41 +0200 Subject: [PATCH 019/201] Add missing javafx export to controlsfx for eclipse --- eclipse.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eclipse.gradle b/eclipse.gradle index ea81b0738c7..25948d44d7b 100644 --- a/eclipse.gradle +++ b/eclipse.gradle @@ -26,7 +26,7 @@ eclipse { def javafxcontrols = entries.find { isJavafxControls(it) }; javafxcontrols.entryAttributes['add-exports'] = 'javafx.controls/com.sun.javafx.scene.control=org.jabref:javafx.controls/com.sun.javafx.scene.control.behavior=org.jabref:javafx.controls/javafx.scene.control=org.jabref'; - javafxcontrols.entryAttributes['add-opens'] = 'javafx.controls/com.sun.javafx.scene.control=org.jabref:javafx.controls/com.sun.javafx.scene.control.behavior=org.jabref:javafx.controls/javafx.scene.control=org.jabref'; + javafxcontrols.entryAttributes['add-opens'] = 'javafx.controls/com.sun.javafx.scene.control=org.jabref:javafx.controls/com.sun.javafx.scene.control.behavior=org.jabref:javafx.controls/javafx.scene.control=org.jabref:javafx.controls/javafx.scene.control.skin=org.controlsfx.controls'; def javafxgraphics = entries.find { isJavafxGraphics(it) }; javafxgraphics.entryAttributes['add-opens'] = 'javafx.graphics/javafx.scene=org.controlsfx.controls'; From 2555b2df605d85d8ee8c6de2687535d2819bc395 Mon Sep 17 00:00:00 2001 From: Christoph Date: Sat, 24 Oct 2020 18:07:54 +0200 Subject: [PATCH 020/201] Add Reviewdog as checkstyle (#6996) * Add Reviewdog as checkstyle * try with dir * try dir * test witout gradle action * test checkstyle * use copy for reviewdog change to PR reporter * changke back to PR check * fix checkstyle issue --- .github/workflows/tests.yml | 7 +- config/checkstyle/checkstyle_reviewdog.xml | 141 +++++++++++++++++++++ 2 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 config/checkstyle/checkstyle_reviewdog.xml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8d4a7ee6737..4e33c3ee9d0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,7 +41,12 @@ jobs: with: path: ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} - - name: Run checkstyle + - name: Run check style reporter + uses: nikitasavinov/checkstyle-action@master + with: + reporter: github-pr-check + checkstyle_config: 'config/checkstyle/checkstyle_reviewdog.xml' + - name: Run checkstyle gradle run: ./gradlew checkstyleMain checkstyleTest checkstyleJmh - name: Run markdown-lint uses: avto-dev/markdown-lint@v1 diff --git a/config/checkstyle/checkstyle_reviewdog.xml b/config/checkstyle/checkstyle_reviewdog.xml new file mode 100644 index 00000000000..23d41f035e1 --- /dev/null +++ b/config/checkstyle/checkstyle_reviewdog.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 72614b532e7f5d76759f6d16a88f6657319fc5eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2020 09:24:08 +0100 Subject: [PATCH 021/201] Bump styfle/cancel-workflow-action from 0.5.0 to 0.6.0 (#7047) Bumps [styfle/cancel-workflow-action](https://github.com/styfle/cancel-workflow-action) from 0.5.0 to 0.6.0. - [Release notes](https://github.com/styfle/cancel-workflow-action/releases) - [Commits](https://github.com/styfle/cancel-workflow-action/compare/0.5.0...ce177499ccf9fd2aded3b0426c97e5434c2e8a73) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deployment.yml | 2 +- .github/workflows/tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 6689341b25a..d8b160f0cac 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -41,7 +41,7 @@ jobs: name: Create installer and portable version for ${{ matrix.displayName }} steps: - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.5.0 + uses: styfle/cancel-workflow-action@0.6.0 with: access_token: ${{ github.token }} - name: Fetch all history for all tags and branches diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4e33c3ee9d0..8272f44b43f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.5.0 + uses: styfle/cancel-workflow-action@0.6.0 with: access_token: ${{ github.token }} - name: Checkout source From 3fe34a0ee319f49c87258f0aee0360f486107c91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2020 18:34:54 +0100 Subject: [PATCH 022/201] Bump mockito-core from 3.5.13 to 3.5.15 (#7044) Bumps [mockito-core](https://github.com/mockito/mockito) from 3.5.13 to 3.5.15. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v3.5.13...v3.5.15) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f1e260f51bf..6af71fbaa62 100644 --- a/build.gradle +++ b/build.gradle @@ -207,7 +207,7 @@ dependencies { testImplementation 'net.bytebuddy:byte-buddy-parent:1.10.17' testRuntime group: 'org.apache.logging.log4j', name: 'log4j-core', version: '3.0.0-SNAPSHOT' testRuntime group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '3.0.0-SNAPSHOT' - testImplementation 'org.mockito:mockito-core:3.5.13' + testImplementation 'org.mockito:mockito-core:3.5.15' testImplementation 'org.xmlunit:xmlunit-core:2.7.0' testImplementation 'org.xmlunit:xmlunit-matchers:2.7.0' testRuntime 'com.tngtech.archunit:archunit-junit5-engine:0.14.1' From 47fd56298537607e6a6647b52b6d990751dab04e Mon Sep 17 00:00:00 2001 From: Niffler Date: Wed, 28 Oct 2020 10:34:14 +0100 Subject: [PATCH 023/201] Add online-link detection to FileFieldParser (#7043) * Add online-link detection to FileFieldParser * Remove double space * Remove try-catch block from control structure * Add missing space * Fix checkstyle violations --- .../logic/importer/util/FileFieldParser.java | 17 ++++++++++++++- .../org/jabref/model/entry/LinkedFile.java | 2 +- .../logic/bibtex/FileFieldWriterTest.java | 21 +++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java b/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java index cdbb1818807..9c51c7468fc 100644 --- a/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java +++ b/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java @@ -1,5 +1,7 @@ package org.jabref.logic.importer.util; +import java.net.MalformedURLException; +import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; @@ -65,7 +67,20 @@ private static LinkedFile convert(List entry) { while (entry.size() < 3) { entry.add(""); } - LinkedFile field = new LinkedFile(entry.get(0), Path.of(entry.get(1)), entry.get(2)); + + LinkedFile field = null; + if (LinkedFile.isOnlineLink(entry.get(1))) { + try { + field = new LinkedFile(new URL(entry.get(1)), entry.get(2)); + } catch (MalformedURLException ignored) { + // ignored + } + } + + if (field == null) { + field = new LinkedFile(entry.get(0), Path.of(entry.get(1)), entry.get(2)); + } + // link is only mandatory field if (field.getDescription().isEmpty() && field.getLink().isEmpty() && !field.getFileType().isEmpty()) { field = new LinkedFile("", Path.of(field.getFileType()), ""); diff --git a/src/main/java/org/jabref/model/entry/LinkedFile.java b/src/main/java/org/jabref/model/entry/LinkedFile.java index d06e4d956e2..ba98a8ba14e 100644 --- a/src/main/java/org/jabref/model/entry/LinkedFile.java +++ b/src/main/java/org/jabref/model/entry/LinkedFile.java @@ -133,7 +133,7 @@ private void readObject(ObjectInputStream in) throws IOException { * @param toCheck The String to check * @return true, if it starts with "http://", "https://" or contains "www."; false otherwise */ - private boolean isOnlineLink(String toCheck) { + public static boolean isOnlineLink(String toCheck) { String normalizedFilePath = toCheck.trim().toLowerCase(); return normalizedFilePath.startsWith("http://") || normalizedFilePath.startsWith("https://") || normalizedFilePath.contains("www."); } diff --git a/src/test/java/org/jabref/logic/bibtex/FileFieldWriterTest.java b/src/test/java/org/jabref/logic/bibtex/FileFieldWriterTest.java index 38fdbc465ff..7305ebbcfb3 100644 --- a/src/test/java/org/jabref/logic/bibtex/FileFieldWriterTest.java +++ b/src/test/java/org/jabref/logic/bibtex/FileFieldWriterTest.java @@ -1,8 +1,11 @@ package org.jabref.logic.bibtex; +import java.net.MalformedURLException; +import java.net.URL; import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; +import java.util.List; import org.jabref.logic.importer.util.FileFieldParser; import org.jabref.model.entry.LinkedFile; @@ -31,6 +34,24 @@ public void parseCorrectInput() { FileFieldParser.parse(input)); } + @Test + public void parseCorrectOnlineInput() throws MalformedURLException { + String input = ":http\\://arxiv.org/pdf/2010.08497v1:PDF"; + String inputURL = "http://arxiv.org/pdf/2010.08497v1"; + List expected = Collections.singletonList(new LinkedFile(new URL(inputURL), "PDF")); + + assertEquals(expected, FileFieldParser.parse(input)); + } + + @Test + public void parseFaultyOnlineInput() { + String input = ":htt\\://arxiv.org/pdf/2010.08497v1:PDF"; + String inputURL = "htt://arxiv.org/pdf/2010.08497v1"; + List expected = Collections.singletonList(new LinkedFile("", Path.of(inputURL), "PDF")); + + assertEquals(expected, FileFieldParser.parse(input)); + } + @Test public void ingoreMissingDescription() { String input = ":wei2005ahp.pdf:PDF"; From bfd9840f90c23adee9c78e03f95dd022cec71eff Mon Sep 17 00:00:00 2001 From: Galileo Sartor Date: Wed, 28 Oct 2020 10:34:40 +0100 Subject: [PATCH 024/201] Add awt native open commands (#7037) * Add awt native open commands * Revert to xdg-open if the native awt operation fails. This is used in the confined linux packages to open URLs * Fix AllowedToUseAwt annotation use * Use Thread to avoid swing dependency * Use JabRefExecutorService to open files in Linux * Add line in changelog --- CHANGELOG.md | 1 + snap/snapcraft.yaml | 6 +- .../java/org/jabref/gui/desktop/os/Linux.java | 63 +++++++++++++------ 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55d21265e7d..7ff64277a52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://git - We changed the name of a group type from "Searching for keywords" to "Searching for a keyword". [6995](https://github.com/JabRef/jabref/pull/6995) - We changed the way JabRef displays the title of a tab and of the window. [4161](https://github.com/JabRef/jabref/issues/4161) - We changed connect timeouts for server requests to 30 seconds in general and 5 seconds for GROBID server (special) and improved user notifications on connection issues. [7026](https://github.com/JabRef/jabref/pull/7026) +- We changed the way linked files are opened on Linux to use the native openFile method, compatible with confined packages. [7037](https://github.com/JabRef/jabref/pull/7037) ### Fixed diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 714a569d6ae..ba412df5b6f 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -15,11 +15,8 @@ architectures: - build-on: amd64 plugs: - desktop: - desktop-legacy: - wayland: - unity7: home: + unity7: opengl: network-bind: removable-media: @@ -46,6 +43,7 @@ apps: environment: _JAVA_OPTIONS: "-Duser.home=$SNAP_USER_DATA" + GTK_USE_PORTAL: "1" parts: jabref: diff --git a/src/main/java/org/jabref/gui/desktop/os/Linux.java b/src/main/java/org/jabref/gui/desktop/os/Linux.java index e501d3ba620..e4a73faa177 100644 --- a/src/main/java/org/jabref/gui/desktop/os/Linux.java +++ b/src/main/java/org/jabref/gui/desktop/os/Linux.java @@ -1,5 +1,6 @@ package org.jabref.gui.desktop.os; +import java.awt.Desktop; import java.io.BufferedReader; import java.io.File; import java.io.IOException; @@ -8,6 +9,7 @@ import java.util.Locale; import java.util.Optional; +import org.jabref.architecture.AllowedToUseAwt; import org.jabref.gui.JabRefExecutorService; import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; @@ -16,10 +18,31 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@AllowedToUseAwt("Requires AWT to open a file with the native method") public class Linux implements NativeDesktop { private static final Logger LOGGER = LoggerFactory.getLogger(Linux.class); + private void nativeOpenFile(String filePath) { + JabRefExecutorService.INSTANCE.execute(() -> { + try { + File file = new File(filePath); + Desktop.getDesktop().open(file); + System.out.println("Open file in default application with Desktop integration"); + } catch (IllegalArgumentException e) { + System.out.println("Fail back to xdg-open"); + try { + String[] cmd = {"xdg-open", filePath}; + Runtime.getRuntime().exec(cmd); + } catch (Exception e2) { + System.out.println("Open operation not successful: " + e2); + } + } catch (IOException e) { + System.out.println("Native open operation not successful: " + e); + } + }); + } + @Override public void openFile(String filePath, String fileType) throws IOException { Optional type = ExternalFileTypes.getInstance().getExternalFileTypeByExt(fileType); @@ -27,16 +50,16 @@ public void openFile(String filePath, String fileType) throws IOException { if (type.isPresent() && !type.get().getOpenWithApplication().isEmpty()) { viewer = type.get().getOpenWithApplication(); + ProcessBuilder processBuilder = new ProcessBuilder(viewer, filePath); + Process process = processBuilder.start(); + StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), LOGGER::debug); + StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), LOGGER::debug); + + JabRefExecutorService.INSTANCE.execute(streamGobblerInput); + JabRefExecutorService.INSTANCE.execute(streamGobblerError); } else { - viewer = "xdg-open"; + nativeOpenFile(filePath); } - ProcessBuilder processBuilder = new ProcessBuilder(viewer, filePath); - Process process = processBuilder.start(); - StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), LOGGER::debug); - StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), LOGGER::debug); - - JabRefExecutorService.INSTANCE.execute(streamGobblerInput); - JabRefExecutorService.INSTANCE.execute(streamGobblerError); } @Override @@ -45,21 +68,21 @@ public void openFileWithApplication(String filePath, String application) throws String[] openWith; if ((application != null) && !application.isEmpty()) { openWith = application.split(" "); - } else { - openWith = new String[] {"xdg-open"}; - } - String[] cmdArray = new String[openWith.length + 1]; - System.arraycopy(openWith, 0, cmdArray, 0, openWith.length); - cmdArray[cmdArray.length - 1] = filePath; + String[] cmdArray = new String[openWith.length + 1]; + System.arraycopy(openWith, 0, cmdArray, 0, openWith.length); + cmdArray[cmdArray.length - 1] = filePath; - ProcessBuilder processBuilder = new ProcessBuilder(cmdArray); - Process process = processBuilder.start(); + ProcessBuilder processBuilder = new ProcessBuilder(cmdArray); + Process process = processBuilder.start(); - StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), LOGGER::debug); - StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), LOGGER::debug); + StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), LOGGER::debug); + StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), LOGGER::debug); - JabRefExecutorService.INSTANCE.execute(streamGobblerInput); - JabRefExecutorService.INSTANCE.execute(streamGobblerError); + JabRefExecutorService.INSTANCE.execute(streamGobblerInput); + JabRefExecutorService.INSTANCE.execute(streamGobblerError); + } else { + nativeOpenFile(filePath); + } } @Override From e08ac4e881275f41cbfde958a7281adf511c714c Mon Sep 17 00:00:00 2001 From: Johannes Theiner Date: Thu, 29 Oct 2020 21:31:59 +0100 Subject: [PATCH 025/201] add short date formatter (#7039) * add short date formatter. fixes #6579 * Update src/main/java/org/jabref/logic/layout/format/ShortMonthFormatter.java Co-authored-by: Christoph * remove setArgument * fixing imports Co-authored-by: Christoph --- CHANGELOG.md | 1 + .../org/jabref/logic/layout/LayoutEntry.java | 3 +++ .../layout/format/ShortMonthFormatter.java | 15 ++++++++++++ .../format/ShortMonthFormatterTest.java | 24 +++++++++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 src/main/java/org/jabref/logic/layout/format/ShortMonthFormatter.java create mode 100644 src/test/java/org/jabref/logic/layout/format/ShortMonthFormatterTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ff64277a52..3ac0763c54e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve to the page field for cases where the page numbers are missing. [#7019](https://github.com/JabRef/jabref/issues/7019) - We added a new fetcher to enable users to search jstor.org [#6627](https://github.com/JabRef/jabref/issues/6627) - We added an error message in the New Entry dialog that is shown in case the fetcher did not find anything . [#7000](https://github.com/JabRef/jabref/issues/7000) +- We added a new formatter to output shorthand month format. [#6579](https://github.com/JabRef/jabref/issues/6579) ### Changed diff --git a/src/main/java/org/jabref/logic/layout/LayoutEntry.java b/src/main/java/org/jabref/logic/layout/LayoutEntry.java index 8a1f6e7349f..658232eb72d 100644 --- a/src/main/java/org/jabref/logic/layout/LayoutEntry.java +++ b/src/main/java/org/jabref/logic/layout/LayoutEntry.java @@ -74,6 +74,7 @@ import org.jabref.logic.layout.format.RisAuthors; import org.jabref.logic.layout.format.RisKeywords; import org.jabref.logic.layout.format.RisMonth; +import org.jabref.logic.layout.format.ShortMonthFormatter; import org.jabref.logic.layout.format.ToLowerCase; import org.jabref.logic.layout.format.ToUpperCase; import org.jabref.logic.layout.format.WrapContent; @@ -542,6 +543,8 @@ private LayoutFormatter getLayoutFormatterByName(String name) { return new MarkdownFormatter(); case "CSLType": return new CSLType(); + case "ShortMonth": + return new ShortMonthFormatter(); default: return null; } diff --git a/src/main/java/org/jabref/logic/layout/format/ShortMonthFormatter.java b/src/main/java/org/jabref/logic/layout/format/ShortMonthFormatter.java new file mode 100644 index 00000000000..63bef135801 --- /dev/null +++ b/src/main/java/org/jabref/logic/layout/format/ShortMonthFormatter.java @@ -0,0 +1,15 @@ +package org.jabref.logic.layout.format; + +import java.util.Optional; + +import org.jabref.logic.layout.LayoutFormatter; +import org.jabref.model.entry.Month; + +public class ShortMonthFormatter implements LayoutFormatter { + + @Override + public String format(String fieldText) { + Optional month = Month.parse(fieldText); + return month.map(Month::getShortName).orElse(""); + } +} diff --git a/src/test/java/org/jabref/logic/layout/format/ShortMonthFormatterTest.java b/src/test/java/org/jabref/logic/layout/format/ShortMonthFormatterTest.java new file mode 100644 index 00000000000..bcd17b9e518 --- /dev/null +++ b/src/test/java/org/jabref/logic/layout/format/ShortMonthFormatterTest.java @@ -0,0 +1,24 @@ +package org.jabref.logic.layout.format; + +import org.jabref.logic.layout.LayoutFormatter; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ShortMonthFormatterTest { + + private LayoutFormatter formatter; + + @BeforeEach + public void setUp() { + formatter = new ShortMonthFormatter(); + } + + @Test + public void testFormat() { + assertEquals("jan", formatter.format("01")); + assertEquals("jan", formatter.format("Januar")); + } +} From 07026487e40ae689915bcb834838b3d8c7d160c6 Mon Sep 17 00:00:00 2001 From: github actions Date: Sun, 1 Nov 2020 02:17:49 +0000 Subject: [PATCH 026/201] Squashed 'src/main/resources/csl-styles/' changes from 5297abd6fc..5c376b838b MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 5c376b838b Create karlstad-universitet-kau-harvard-swedish.csl (#5083) 6a62c24862 Create ucl-institute-of-education-harvard.csl (#5084) 4d1bbb009b Update ferdinand-porsche-fern-fachhochschule.csl (#5073) 3a9f494d57 Update cureus.csl (#5087) 181c0ddd82 Create apa-no.csl (#5059) 651ce48c1a Some fixes for Advanced Functional Materials (#5080) 4e3297ad48 Acta Zoologica Hungarica: Don't demote nd particles c659df790b Create medicina-delle-dipendenze-italian-journal-of-the-addictions.csl (#5078) d243055098 Update geografia-fisica-e-dinamica-quaternaria.csl (#5079) 1ee380933c Update mcgill-en.csl (#5076) 949cdc6c19 Update mcgill-fr.csl (#5075) ee7d4e1647 Create chem-catalysis.csl (#5074) ec9faec562 Switch Molecular Therapy to Cell (#5070) cf6034cd3a Switch Elsevier to their own ad-vancouver (#5072) 78cca139cd Updates in research-institute-for-nature-and-forest.csl (#4958) 68b747879b Update journal-of-neolithic-archaeology.csl (#5065) 524ba934ca Create offa.csl (#5066) 9713a82e96 Update associacao-brasileira-de-normas-tecnicas-ufrgs-note-initials-with-ibid (#5067) 44449df8e5 Uodate associacao-brasileira-de-normas-tecnicas-ufrgs-initials.csl (#5061) baf9efa56f Create İstanbul Üniversitesi Sosyal Bilimler Enstitüsü Ali Ekber Çına… (#5064) c5c3531066 Fix CORR 853a8049bb Major Update mcgill-fr.csl to 9th ed. (#5057) d3e2cd9b90 Update mcgill-en.csl (#5056) git-subtree-dir: src/main/resources/csl-styles git-subtree-split: 5c376b838b108d8d0d90e59363a553dbcb909e66 --- ...ogica-academiae-scientiarum-hungaricae.csl | 2 +- advanced-functional-materials.csl | 86 +- ...eira-de-normas-tecnicas-ufrgs-initials.csl | 970 ++++++---- ...tecnicas-ufrgs-note-initials-with-ibid.csl | 1679 +++++++++++------ ...ical-orthopaedics-and-related-research.csl | 6 +- cureus.csl | 2 +- dependent/animal-nutrition.csl | 4 +- dependent/chem-catalysis.csl | 16 + dependent/clinical-neurophysiology.csl | 4 +- ...journal-of-technology-law-and-practice.csl | 6 +- dependent/computers-and-security.csl | 4 +- ...ic-microbiology-and-infectious-disease.csl | 4 +- ...ational-journal-of-infectious-diseases.csl | 4 +- ...national-journal-of-womens-dermatology.csl | 4 +- ...tive-dermatology-symposium-proceedings.csl | 4 +- dependent/journal-of-orthopaedic-science.csl | 4 +- ...mechanics-and-geotechnical-engineering.csl | 4 +- ...erapy-methods-and-clinical-development.csl | 4 +- dependent/molecular-therapy-nucleic-acids.csl | 4 +- dependent/molecular-therapy-oncolytics.csl | 4 +- dependent/molecular-therapy.csl | 17 + ...ista-brasileira-de-ciencias-do-esporte.csl | 4 +- dependent/sexologies.csl | 4 +- ...atial-and-spatio-temporal-epidemiology.csl | 4 +- dependent/spine-deformity.csl | 11 +- ferdinand-porsche-fern-fachhochschule.csl | 93 +- geografia-fisica-e-dinamica-quaternaria.csl | 6 +- ...-polymer-analysis-and-characterization.csl | 2 +- ...universitesi-sosyal-bilimler-enstitusu.csl | 1536 +++++++++++++++ journal-of-neolithic-archaeology.csl | 65 +- karlstad-universitet-harvard.csl | 360 ++++ mcgill-en.csl | 235 ++- mcgill-fr.csl | 1475 +++++---------- ...enze-italian-journal-of-the-addictions.csl | 183 ++ molecular-therapy.csl | 116 -- norsk-apa-manual.csl | 1652 ++++++++++++++++ offa.csl | 425 +++++ research-institute-for-nature-and-forest.csl | 166 +- ucl-institute-of-education-harvard.csl | 305 +++ 39 files changed, 7129 insertions(+), 2345 deletions(-) create mode 100644 dependent/chem-catalysis.csl create mode 100644 dependent/molecular-therapy.csl create mode 100644 istanbul-universitesi-sosyal-bilimler-enstitusu.csl create mode 100644 karlstad-universitet-harvard.csl create mode 100644 medicina-delle-dipendenze-italian-journal-of-the-addictions.csl delete mode 100644 molecular-therapy.csl create mode 100644 norsk-apa-manual.csl create mode 100644 offa.csl create mode 100644 ucl-institute-of-education-harvard.csl diff --git a/acta-zoologica-academiae-scientiarum-hungaricae.csl b/acta-zoologica-academiae-scientiarum-hungaricae.csl index 5d79a65202e..f28ad88a18a 100644 --- a/acta-zoologica-academiae-scientiarum-hungaricae.csl +++ b/acta-zoologica-academiae-scientiarum-hungaricae.csl @@ -1,5 +1,5 @@ - diff --git a/dependent/chem-catalysis.csl b/dependent/chem-catalysis.csl new file mode 100644 index 00000000000..a9f989fb74a --- /dev/null +++ b/dependent/chem-catalysis.csl @@ -0,0 +1,16 @@ + + diff --git a/dependent/clinical-neurophysiology.csl b/dependent/clinical-neurophysiology.csl index 28a02508edb..b432a5b61f2 100644 --- a/dependent/clinical-neurophysiology.csl +++ b/dependent/clinical-neurophysiology.csl @@ -5,10 +5,10 @@ Clinical Neurophysiology http://www.zotero.org/styles/clinical-neurophysiology - + 1388-2457 - 2018-02-16T12:00:00+00:00 + 2017-06-04T12:00:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License diff --git a/dependent/computer-law-and-security-review-the-international-journal-of-technology-law-and-practice.csl b/dependent/computer-law-and-security-review-the-international-journal-of-technology-law-and-practice.csl index 1e74667efd0..ba3e2f0aa02 100644 --- a/dependent/computer-law-and-security-review-the-international-journal-of-technology-law-and-practice.csl +++ b/dependent/computer-law-and-security-review-the-international-journal-of-technology-law-and-practice.csl @@ -5,10 +5,10 @@ Computer Law & Security Review: The International Journal of Technology Law and Practice http://www.zotero.org/styles/computer-law-and-security-review-the-international-journal-of-technology-law-and-practice - - + + 0267-3649 - 2018-02-16T12:00:00+00:00 + 2017-06-04T12:00:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License diff --git a/dependent/computers-and-security.csl b/dependent/computers-and-security.csl index b863fc0a93d..07a2b2e7eb6 100644 --- a/dependent/computers-and-security.csl +++ b/dependent/computers-and-security.csl @@ -5,10 +5,10 @@ Computers & Security http://www.zotero.org/styles/computers-and-security - + 0167-4048 - 2018-02-16T12:00:00+00:00 + 2017-06-04T12:00:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License diff --git a/dependent/diagnostic-microbiology-and-infectious-disease.csl b/dependent/diagnostic-microbiology-and-infectious-disease.csl index 628f044d452..5774df37be3 100644 --- a/dependent/diagnostic-microbiology-and-infectious-disease.csl +++ b/dependent/diagnostic-microbiology-and-infectious-disease.csl @@ -5,10 +5,10 @@ Diagnostic Microbiology & Infectious Disease http://www.zotero.org/styles/diagnostic-microbiology-and-infectious-disease - + 0732-8893 - 2018-02-16T12:00:00+00:00 + 2017-06-04T12:00:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License diff --git a/dependent/international-journal-of-infectious-diseases.csl b/dependent/international-journal-of-infectious-diseases.csl index 5a27d4c606b..9e3a446ab83 100644 --- a/dependent/international-journal-of-infectious-diseases.csl +++ b/dependent/international-journal-of-infectious-diseases.csl @@ -5,10 +5,10 @@ International Journal of Infectious Diseases http://www.zotero.org/styles/international-journal-of-infectious-diseases - + 1201-9712 - 2018-02-16T12:00:00+00:00 + 2017-06-04T12:00:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License diff --git a/dependent/international-journal-of-womens-dermatology.csl b/dependent/international-journal-of-womens-dermatology.csl index dab6266f6c1..801a7283c6a 100644 --- a/dependent/international-journal-of-womens-dermatology.csl +++ b/dependent/international-journal-of-womens-dermatology.csl @@ -5,10 +5,10 @@ International Journal of Women's Dermatology http://www.zotero.org/styles/international-journal-of-womens-dermatology - + 2352-6475 - 2018-02-16T12:00:00+00:00 + 2017-06-04T12:00:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License diff --git a/dependent/journal-of-investigative-dermatology-symposium-proceedings.csl b/dependent/journal-of-investigative-dermatology-symposium-proceedings.csl index db11d73afa4..f611c16537c 100644 --- a/dependent/journal-of-investigative-dermatology-symposium-proceedings.csl +++ b/dependent/journal-of-investigative-dermatology-symposium-proceedings.csl @@ -5,10 +5,10 @@ Journal of Investigative Dermatology Symposium Proceedings http://www.zotero.org/styles/journal-of-investigative-dermatology-symposium-proceedings - + 1087-0024 - 2018-02-16T12:00:00+00:00 + 2017-06-04T12:00:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License diff --git a/dependent/journal-of-orthopaedic-science.csl b/dependent/journal-of-orthopaedic-science.csl index dba9ff20ddc..24885d013db 100644 --- a/dependent/journal-of-orthopaedic-science.csl +++ b/dependent/journal-of-orthopaedic-science.csl @@ -5,10 +5,10 @@ Journal of Orthopaedic Science http://www.zotero.org/styles/journal-of-orthopaedic-science - + 0949-2658 - 2018-02-16T12:00:00+00:00 + 2017-06-04T12:00:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License diff --git a/dependent/journal-of-rock-mechanics-and-geotechnical-engineering.csl b/dependent/journal-of-rock-mechanics-and-geotechnical-engineering.csl index 52e9374f693..0e1de996433 100644 --- a/dependent/journal-of-rock-mechanics-and-geotechnical-engineering.csl +++ b/dependent/journal-of-rock-mechanics-and-geotechnical-engineering.csl @@ -5,10 +5,10 @@ Journal of Rock Mechanics and Geotechnical Engineering http://www.zotero.org/styles/journal-of-rock-mechanics-and-geotechnical-engineering - + 1674-7755 - 2018-02-16T12:00:00+00:00 + 2017-06-04T12:00:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License diff --git a/dependent/molecular-therapy-methods-and-clinical-development.csl b/dependent/molecular-therapy-methods-and-clinical-development.csl index 924f29eb55e..41348ff90ee 100644 --- a/dependent/molecular-therapy-methods-and-clinical-development.csl +++ b/dependent/molecular-therapy-methods-and-clinical-development.csl @@ -1,11 +1,11 @@ diff --git a/dependent/revista-brasileira-de-ciencias-do-esporte.csl b/dependent/revista-brasileira-de-ciencias-do-esporte.csl index 98032282ac3..b8aded24875 100644 --- a/dependent/revista-brasileira-de-ciencias-do-esporte.csl +++ b/dependent/revista-brasileira-de-ciencias-do-esporte.csl @@ -5,10 +5,10 @@ Revista Brasileira de Ciências do Esporte http://www.zotero.org/styles/revista-brasileira-de-ciencias-do-esporte - + 0101-3289 - 2018-02-16T12:00:00+00:00 + 2017-06-04T12:00:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License diff --git a/dependent/sexologies.csl b/dependent/sexologies.csl index 500f8b2c85f..078ea1e534c 100644 --- a/dependent/sexologies.csl +++ b/dependent/sexologies.csl @@ -5,10 +5,10 @@ Sexologies http://www.zotero.org/styles/sexologies - + 1158-1360 - 2018-02-16T12:00:00+00:00 + 2017-06-04T12:00:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License diff --git a/dependent/spatial-and-spatio-temporal-epidemiology.csl b/dependent/spatial-and-spatio-temporal-epidemiology.csl index 07c66e18896..43fc3932a3f 100644 --- a/dependent/spatial-and-spatio-temporal-epidemiology.csl +++ b/dependent/spatial-and-spatio-temporal-epidemiology.csl @@ -5,10 +5,10 @@ Spatial and Spatio-temporal Epidemiology http://www.zotero.org/styles/spatial-and-spatio-temporal-epidemiology - + 1877-5845 - 2018-02-16T12:00:00+00:00 + 2017-06-04T12:00:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License diff --git a/dependent/spine-deformity.csl b/dependent/spine-deformity.csl index 0eed83b209c..5df186e8f6f 100644 --- a/dependent/spine-deformity.csl +++ b/dependent/spine-deformity.csl @@ -1,14 +1,17 @@ diff --git a/ferdinand-porsche-fern-fachhochschule.csl b/ferdinand-porsche-fern-fachhochschule.csl index 721482bf22a..49f485b0304 100644 --- a/ferdinand-porsche-fern-fachhochschule.csl +++ b/ferdinand-porsche-fern-fachhochschule.csl @@ -1,14 +1,18 @@ diff --git a/international-journal-of-polymer-analysis-and-characterization.csl b/international-journal-of-polymer-analysis-and-characterization.csl index 9eb53315fa0..d1ace27cb0d 100644 --- a/international-journal-of-polymer-analysis-and-characterization.csl +++ b/international-journal-of-polymer-analysis-and-characterization.csl @@ -5,7 +5,7 @@ IJPAC http://www.zotero.org/styles/international-journal-of-polymer-analysis-and-characterization - + Patrick O'Brien diff --git a/istanbul-universitesi-sosyal-bilimler-enstitusu.csl b/istanbul-universitesi-sosyal-bilimler-enstitusu.csl new file mode 100644 index 00000000000..df960811c66 --- /dev/null +++ b/istanbul-universitesi-sosyal-bilimler-enstitusu.csl @@ -0,0 +1,1536 @@ + + diff --git a/journal-of-neolithic-archaeology.csl b/journal-of-neolithic-archaeology.csl index bf39c55d5bb..2eb459d526a 100644 --- a/journal-of-neolithic-archaeology.csl +++ b/journal-of-neolithic-archaeology.csl @@ -25,19 +25,21 @@ 2364-3676 2197-649X

Author-date style meeting citation specifications provided by JNA. Based on the DAI style. - 2019-11-10T12:54:34+00:00 + 2020-10-11T23:58:34+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License rev. of talk + accessed: Rez. zu Vortrag + Zugriff: @@ -111,25 +113,18 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + @@ -317,11 +312,14 @@ - - + + - + + + + @@ -329,14 +327,7 @@ - - - - - - - - + @@ -408,7 +399,7 @@ - + @@ -437,8 +428,14 @@ - - + + + + + + + + diff --git a/karlstad-universitet-harvard.csl b/karlstad-universitet-harvard.csl new file mode 100644 index 00000000000..dc48e5de7f1 --- /dev/null +++ b/karlstad-universitet-harvard.csl @@ -0,0 +1,360 @@ + + diff --git a/mcgill-en.csl b/mcgill-en.csl index ba9caaf08b9..bb827ad478e 100644 --- a/mcgill-en.csl +++ b/mcgill-en.csl @@ -24,7 +24,7 @@ - 2020-01-06T22:32:01+00:00 + 2020-10-21T20:45:43+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -42,9 +42,14 @@ at para at paras - + + sub verbo + sub verbis + + + Art + Arts + s ss @@ -59,7 +64,7 @@ - @@ -159,6 +164,28 @@ all the other tests should have been done... --> + + + + + + + + + + + + + + + + + + + + + + @@ -252,7 +279,6 @@ all the other tests should have been done... --> - @@ -275,18 +301,22 @@ all the other tests should have been done... --> - - - - - - - - - - - - + + + + + + + + + + + + + + + + @@ -318,54 +348,21 @@ all the other tests should have been done... --> + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + @@ -377,14 +374,23 @@ all the other tests should have been done... --> + + + + + - @@ -462,54 +468,112 @@ Not implemented: "cited to" for cases construct short casenames adding ref to ar - + - + + + + + + + + + + + + + + + + + + + + - + + + + + + + - + + + + + + + - + - + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + - @@ -519,13 +583,13 @@ Not implemented: "cited to" for cases construct short casenames adding ref to ar + + - - @@ -548,40 +612,59 @@ Not implemented: "cited to" for cases construct short casenames adding ref to ar - - + + + + + + - - + + + + + + + + + + + + + + + + + @@ -591,9 +674,11 @@ Not implemented: "cited to" for cases construct short casenames adding ref to ar + + diff --git a/mcgill-fr.csl b/mcgill-fr.csl index 0d4737f34e9..c824096a50e 100644 --- a/mcgill-fr.csl +++ b/mcgill-fr.csl @@ -1,12 +1,10 @@ - diff --git a/molecular-therapy.csl b/molecular-therapy.csl deleted file mode 100644 index 0350368d31a..00000000000 --- a/molecular-therapy.csl +++ /dev/null @@ -1,116 +0,0 @@ - - diff --git a/norsk-apa-manual.csl b/norsk-apa-manual.csl new file mode 100644 index 00000000000..144f4398cf9 --- /dev/null +++ b/norsk-apa-manual.csl @@ -0,0 +1,1652 @@ + + diff --git a/offa.csl b/offa.csl new file mode 100644 index 00000000000..e197526d792 --- /dev/null +++ b/offa.csl @@ -0,0 +1,425 @@ + + diff --git a/research-institute-for-nature-and-forest.csl b/research-institute-for-nature-and-forest.csl index d3da0597aeb..3281b7d8aab 100644 --- a/research-institute-for-nature-and-forest.csl +++ b/research-institute-for-nature-and-forest.csl @@ -5,65 +5,94 @@ INBO http://www.zotero.org/styles/research-institute-for-nature-and-forest + Maarten Stevens - http://www.mendeley.com/profiles/maarten-stevens/ + https://www.mendeley.com/profiles/maarten-stevens/ - - Thierry Onkelinx - http://www.mendeley.com/profiles/thierry-onkelinx/ - - + Floris Vanderhaeghe floris.vanderhaeghe@inbo.be + + + Thierry Onkelinx + https://www.mendeley.com/profiles/thierry-onkelinx/ - 2019-08-01T00:00:00 + + + 2020-10-12T12:01:18+02:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License - In + edn. + s.d. - In et al. + + ed. + eds. + + ed. + s.d. - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - - - - + - - - - - + - + @@ -162,16 +179,16 @@ - + - + - + @@ -184,36 +201,32 @@ - + - + - - + + - - - - - - - + + + + - + - @@ -223,7 +236,6 @@ - @@ -253,11 +265,22 @@ - - - - - + + + + + + + + + + + + + + + + @@ -265,3 +288,4 @@ + diff --git a/ucl-institute-of-education-harvard.csl b/ucl-institute-of-education-harvard.csl new file mode 100644 index 00000000000..8e19c052ee8 --- /dev/null +++ b/ucl-institute-of-education-harvard.csl @@ -0,0 +1,305 @@ + + From afa8c60ccd399e5d421d3a2ee0c34becaef2352c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 21:17:33 +0100 Subject: [PATCH 027/201] Bump controlsfx from 11.0.2 to 11.0.3 (#7066) Bumps [controlsfx](https://bitbucket.org/controlsfx/controlsfx) from 11.0.2 to 11.0.3. - [Commits](https://bitbucket.org/controlsfx/controlsfx/commits) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6af71fbaa62..6e2728a74a2 100644 --- a/build.gradle +++ b/build.gradle @@ -167,7 +167,7 @@ dependencies { implementation 'org.fxmisc.richtext:richtextfx:0.10.4' implementation group: 'org.glassfish.hk2.external', name: 'jakarta.inject', version: '2.6.1' implementation 'com.jfoenix:jfoenix:9.0.10' - implementation 'org.controlsfx:controlsfx:11.0.2' + implementation 'org.controlsfx:controlsfx:11.0.3' implementation 'org.jsoup:jsoup:1.13.1' implementation 'com.konghq:unirest-java:3.11.02' From 8abd2360cb75e6ca9d8d6b79c24bb3c51b7df9a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 21:18:13 +0100 Subject: [PATCH 028/201] Bump mockito-core from 3.5.15 to 3.6.0 (#7067) Bumps [mockito-core](https://github.com/mockito/mockito) from 3.5.15 to 3.6.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v3.5.15...v3.6.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6e2728a74a2..377a2b1c9ac 100644 --- a/build.gradle +++ b/build.gradle @@ -207,7 +207,7 @@ dependencies { testImplementation 'net.bytebuddy:byte-buddy-parent:1.10.17' testRuntime group: 'org.apache.logging.log4j', name: 'log4j-core', version: '3.0.0-SNAPSHOT' testRuntime group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '3.0.0-SNAPSHOT' - testImplementation 'org.mockito:mockito-core:3.5.15' + testImplementation 'org.mockito:mockito-core:3.6.0' testImplementation 'org.xmlunit:xmlunit-core:2.7.0' testImplementation 'org.xmlunit:xmlunit-matchers:2.7.0' testRuntime 'com.tngtech.archunit:archunit-junit5-engine:0.14.1' From c5d11bde6201e0d2b25b53f792ae992579c58a8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 21:20:40 +0100 Subject: [PATCH 029/201] Bump checkstyle from 8.36.2 to 8.37 (#7064) Bumps [checkstyle](https://github.com/checkstyle/checkstyle) from 8.36.2 to 8.37. - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-8.36.2...checkstyle-8.37) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 377a2b1c9ac..d26a04c6154 100644 --- a/build.gradle +++ b/build.gradle @@ -216,7 +216,7 @@ dependencies { testImplementation "org.testfx:testfx-junit5:4.0.17-alpha-SNAPSHOT" testImplementation "org.hamcrest:hamcrest-library:2.2" - checkstyle 'com.puppycrawl.tools:checkstyle:8.36.2' + checkstyle 'com.puppycrawl.tools:checkstyle:8.37' xjc group: 'org.glassfish.jaxb', name: 'jaxb-xjc', version: '2.3.3' jython 'org.python:jython-standalone:2.7.2' } From 271f1197e6a179830dd5168d2d4acbebc3a2f9cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 21:22:09 +0100 Subject: [PATCH 030/201] Bump bcprov-jdk15on from 1.66 to 1.67 (#7063) Bumps [bcprov-jdk15on](https://github.com/bcgit/bc-java) from 1.66 to 1.67. - [Release notes](https://github.com/bcgit/bc-java/releases) - [Changelog](https://github.com/bcgit/bc-java/blob/master/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d26a04c6154..c40b28e65eb 100644 --- a/build.gradle +++ b/build.gradle @@ -115,7 +115,7 @@ dependencies { implementation group: 'org.apache.tika', name: 'tika-core', version: '1.24.1' // required for reading write-protected PDFs - see https://github.com/JabRef/jabref/pull/942#issuecomment-209252635 - implementation 'org.bouncycastle:bcprov-jdk15on:1.66' + implementation 'org.bouncycastle:bcprov-jdk15on:1.67' implementation 'commons-cli:commons-cli:1.4' From 8acca82b7e7037edae93271d3ff2f86af6e32ba2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 21:23:38 +0100 Subject: [PATCH 031/201] Bump java-diff-utils from 4.8 to 4.9 (#7061) Bumps [java-diff-utils](https://github.com/java-diff-utils/java-diff-utils) from 4.8 to 4.9. - [Release notes](https://github.com/java-diff-utils/java-diff-utils/releases) - [Changelog](https://github.com/java-diff-utils/java-diff-utils/blob/master/CHANGELOG.md) - [Commits](https://github.com/java-diff-utils/java-diff-utils/compare/java-diff-utils-parent-4.8...java-diff-utils-parent-4.9) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c40b28e65eb..936816dd557 100644 --- a/build.gradle +++ b/build.gradle @@ -128,7 +128,7 @@ dependencies { libreoffice 'org.libreoffice:ridl:6.4.3' libreoffice 'org.libreoffice:unoil:6.4.3' - implementation 'io.github.java-diff-utils:java-diff-utils:4.8' + implementation 'io.github.java-diff-utils:java-diff-utils:4.9' implementation 'info.debatty:java-string-similarity:2.0.0' antlr3 'org.antlr:antlr:3.5.2' From 18dba8eb4258b49a5ed060224fac327a717680d8 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 2 Nov 2020 22:06:27 +0100 Subject: [PATCH 032/201] Fix 4040 link --- docs/adr/0003-use-gradle-as-build-tool.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/adr/0003-use-gradle-as-build-tool.md b/docs/adr/0003-use-gradle-as-build-tool.md index 5dac1018032..6da32ce9570 100644 --- a/docs/adr/0003-use-gradle-as-build-tool.md +++ b/docs/adr/0003-use-gradle-as-build-tool.md @@ -19,9 +19,9 @@ Chosen option: "Gradle", because it is lean and fits our development style. ### Maven * Good, because [there is a plugin for almost everything](https://www.slant.co/versus/2107/11592/~apache-maven_vs_gradle) -* Good, because [it has good integration with third party tools](http://pages.zeroturnaround.com/rs/zeroturnaround/images/java-build-tools-part-2.pdf) -* Good, because [it has robust performance](http://pages.zeroturnaround.com/rs/zeroturnaround/images/java-build-tools-part-2.pdf) -* Good, because [it has a high popularity](http://pages.zeroturnaround.com/rs/zeroturnaround/images/java-build-tools-part-2.pdf) +* Good, because [it has good integration with third party tools](https://fdocuments.us/reader/full/java-build-tools-part-2) +* Good, because [it has robust performance](https://fdocuments.us/reader/full/java-build-tools-part-2) +* Good, because [it has a high popularity](https://fdocuments.us/reader/full/java-build-tools-part-2) * Good, [if one favors declarative over imperative](https://www.slant.co/versus/2107/11592/~apache-maven_vs_gradle) * Bad, because [getting a dependency list is not straight forward](https://stackoverflow.com/q/1677473/873282) * Bad, because [it based on a fixed and linear model of phases](https://dzone.com/articles/gradle-vs-maven) @@ -34,7 +34,7 @@ Chosen option: "Gradle", because it is lean and fits our development style. * Good, because [its build scripts are short](https://technologyconversations.com/2014/06/18/build-tools/) * Good, because [it follows the convention over configuration approach](https://www.safaribooksonline.com/library/view/building-and-testing/9781449306816/ch04.html) * Good, because [it offers a graph-based task dependencies](https://dzone.com/articles/gradle-vs-maven) -* Good, because [it is easy to customize](http://pages.zeroturnaround.com/rs/zeroturnaround/images/java-build-tools-part-2.pdf) +* Good, because [it is easy to customize](https://fdocuments.us/reader/full/java-build-tools-part-2) * Good, because [it offers custom dependency scopes](https://gradle.org/maven-vs-gradle/) * Good, because [it has good community support](https://linuxhint.com/ant-vs-maven-vs-gradle/) * Good, because [its performance can be 100 times more than maven's performance](https://gradle.org/gradle-vs-maven-performance/). From 7bfdcdbba01801b0adbcef37975f6baea7b90757 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2020 13:34:41 +0100 Subject: [PATCH 033/201] Bump xmlunit-core from 2.7.0 to 2.8.0 (#7065) Bumps [xmlunit-core](https://github.com/xmlunit/xmlunit) from 2.7.0 to 2.8.0. - [Release notes](https://github.com/xmlunit/xmlunit/releases) - [Changelog](https://github.com/xmlunit/xmlunit/blob/main/RELEASE_NOTES.md) - [Commits](https://github.com/xmlunit/xmlunit/compare/v2.7.0...v2.8.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 936816dd557..4744aec91ac 100644 --- a/build.gradle +++ b/build.gradle @@ -208,7 +208,7 @@ dependencies { testRuntime group: 'org.apache.logging.log4j', name: 'log4j-core', version: '3.0.0-SNAPSHOT' testRuntime group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '3.0.0-SNAPSHOT' testImplementation 'org.mockito:mockito-core:3.6.0' - testImplementation 'org.xmlunit:xmlunit-core:2.7.0' + testImplementation 'org.xmlunit:xmlunit-core:2.8.0' testImplementation 'org.xmlunit:xmlunit-matchers:2.7.0' testRuntime 'com.tngtech.archunit:archunit-junit5-engine:0.14.1' testImplementation 'com.tngtech.archunit:archunit-junit5-api:0.14.1' From 177567ee1a9edaf6f732697d3abfbc34d39f068a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2020 13:43:39 +0100 Subject: [PATCH 034/201] Bump xmlunit-matchers from 2.7.0 to 2.8.0 (#7062) Bumps [xmlunit-matchers](https://github.com/xmlunit/xmlunit) from 2.7.0 to 2.8.0. - [Release notes](https://github.com/xmlunit/xmlunit/releases) - [Changelog](https://github.com/xmlunit/xmlunit/blob/main/RELEASE_NOTES.md) - [Commits](https://github.com/xmlunit/xmlunit/compare/v2.7.0...v2.8.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4744aec91ac..3fc2c50d6a0 100644 --- a/build.gradle +++ b/build.gradle @@ -209,7 +209,7 @@ dependencies { testRuntime group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '3.0.0-SNAPSHOT' testImplementation 'org.mockito:mockito-core:3.6.0' testImplementation 'org.xmlunit:xmlunit-core:2.8.0' - testImplementation 'org.xmlunit:xmlunit-matchers:2.7.0' + testImplementation 'org.xmlunit:xmlunit-matchers:2.8.0' testRuntime 'com.tngtech.archunit:archunit-junit5-engine:0.14.1' testImplementation 'com.tngtech.archunit:archunit-junit5-api:0.14.1' testImplementation "org.testfx:testfx-core:4.0.17-alpha-SNAPSHOT" From 9958b11194ee620b4ac2c2901d2c325bf0d133df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2020 13:44:37 +0100 Subject: [PATCH 035/201] Bump unirest-java from 3.11.02 to 3.11.03 (#7060) Bumps [unirest-java](https://github.com/Kong/unirest-java) from 3.11.02 to 3.11.03. - [Release notes](https://github.com/Kong/unirest-java/releases) - [Changelog](https://github.com/Kong/unirest-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/Kong/unirest-java/compare/v3.11.02...v3.11.03) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3fc2c50d6a0..d60add7d268 100644 --- a/build.gradle +++ b/build.gradle @@ -170,7 +170,7 @@ dependencies { implementation 'org.controlsfx:controlsfx:11.0.3' implementation 'org.jsoup:jsoup:1.13.1' - implementation 'com.konghq:unirest-java:3.11.02' + implementation 'com.konghq:unirest-java:3.11.03' implementation 'org.slf4j:slf4j-api:2.0.0-alpha1' implementation group: 'org.apache.logging.log4j', name: 'log4j-jcl', version: '3.0.0-SNAPSHOT' From 73a819362bc7318aa2d44750b779bde3f2627c28 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2020 19:01:38 +0100 Subject: [PATCH 036/201] Bump byte-buddy-parent from 1.10.17 to 1.10.18 (#7059) Bumps [byte-buddy-parent](https://github.com/raphw/byte-buddy) from 1.10.17 to 1.10.18. - [Release notes](https://github.com/raphw/byte-buddy/releases) - [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md) - [Commits](https://github.com/raphw/byte-buddy/compare/byte-buddy-1.10.17...byte-buddy-1.10.18) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d60add7d268..e58b659c2a7 100644 --- a/build.gradle +++ b/build.gradle @@ -204,7 +204,7 @@ dependencies { testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.7.0' testImplementation 'org.junit.platform:junit-platform-launcher:1.7.0' - testImplementation 'net.bytebuddy:byte-buddy-parent:1.10.17' + testImplementation 'net.bytebuddy:byte-buddy-parent:1.10.18' testRuntime group: 'org.apache.logging.log4j', name: 'log4j-core', version: '3.0.0-SNAPSHOT' testRuntime group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '3.0.0-SNAPSHOT' testImplementation 'org.mockito:mockito-core:3.6.0' From b5bcea4506b034975de3363a12efb7d56caa9f01 Mon Sep 17 00:00:00 2001 From: Christoph Date: Tue, 3 Nov 2020 19:54:54 +0100 Subject: [PATCH 037/201] Fix Shared Database Tests (#7040) * Fix Shared Database Tests Fix FieldChangeEvent Delta computation * Use length directly foir comparision * upgrade postgres to 13 rename delta to majorCharacterChange * revert unrelated change --- .github/workflows/tests.yml | 2 +- .../jabref/logic/util/CoarseChangeFilter.java | 2 +- .../model/entry/event/FieldChangedEvent.java | 20 ++++++++++--------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8272f44b43f..fe57e60d3d0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -91,7 +91,7 @@ jobs: runs-on: ubuntu-latest services: postgres: - image: postgres:10.8 + image: postgres:13-alpine env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres diff --git a/src/main/java/org/jabref/logic/util/CoarseChangeFilter.java b/src/main/java/org/jabref/logic/util/CoarseChangeFilter.java index ead72efe4d5..ed3ef873aaa 100644 --- a/src/main/java/org/jabref/logic/util/CoarseChangeFilter.java +++ b/src/main/java/org/jabref/logic/util/CoarseChangeFilter.java @@ -46,7 +46,7 @@ public synchronized void listen(BibDatabaseContextChangedEvent event) { boolean isChangedEntry = lastEntryChanged.filter(e -> !e.equals(fieldChange.getBibEntry())).isPresent(); boolean isEditChanged = !isNewEdit && (isChangedField || isChangedEntry); // Only deltas of 1 when typing in manually, major change means pasting something (more than one character) - boolean isMajorChange = fieldChange.getDelta() > 1; + boolean isMajorChange = fieldChange.getMajorCharacterChange() > 1; fieldChange.setFilteredOut(!(isEditChanged || isMajorChange)); // Post each FieldChangedEvent - even the ones being marked as "filtered" diff --git a/src/main/java/org/jabref/model/entry/event/FieldChangedEvent.java b/src/main/java/org/jabref/model/entry/event/FieldChangedEvent.java index 45d9ae2ec1d..3f4eda3838e 100644 --- a/src/main/java/org/jabref/model/entry/event/FieldChangedEvent.java +++ b/src/main/java/org/jabref/model/entry/event/FieldChangedEvent.java @@ -12,7 +12,7 @@ public class FieldChangedEvent extends EntryChangedEvent { private final Field field; private final String newValue; private final String oldValue; - private int delta = 0; + private int majorCharacterChange = 0; /** * @param bibEntry Affected BibEntry object @@ -27,7 +27,7 @@ public FieldChangedEvent(BibEntry bibEntry, Field field, String newValue, String this.field = field; this.newValue = newValue; this.oldValue = oldValue; - this.delta = computeDelta(oldValue, newValue); + this.majorCharacterChange = computeMajorCharacterChange(oldValue, newValue); } /** @@ -40,7 +40,7 @@ public FieldChangedEvent(BibEntry bibEntry, Field field, String newValue, String this.field = field; this.newValue = newValue; this.oldValue = oldValue; - this.delta = computeDelta(oldValue, newValue); + this.majorCharacterChange = computeMajorCharacterChange(oldValue, newValue); } /** @@ -51,20 +51,22 @@ public FieldChangedEvent(FieldChange fieldChange, EntriesEventSource location) { this.field = fieldChange.getField(); this.newValue = fieldChange.getNewValue(); this.oldValue = fieldChange.getOldValue(); - this.delta = computeDelta(oldValue, newValue); + this.majorCharacterChange = computeMajorCharacterChange(oldValue, newValue); } public FieldChangedEvent(FieldChange fieldChange) { this(fieldChange, EntriesEventSource.LOCAL); } - private int computeDelta(String oldValue, String newValue) { + private int computeMajorCharacterChange(String oldValue, String newValue) { if (oldValue == newValue) { return 0; - } else if (oldValue == null && newValue != null) { + } else if ((oldValue == null) && (newValue != null)) { return newValue.length(); - } else if (newValue == null && oldValue != null) { + } else if ((newValue == null) && (oldValue != null)) { return oldValue.length(); + } else if ((oldValue.length() == newValue.length()) && !oldValue.equals(newValue)) { + return newValue.length(); } else { return Math.abs(newValue.length() - oldValue.length()); } @@ -82,7 +84,7 @@ public String getOldValue() { return oldValue; } - public int getDelta() { - return delta; + public int getMajorCharacterChange() { + return majorCharacterChange; } } From 67f67a15419466ab566e4822af2561cafb61b9aa Mon Sep 17 00:00:00 2001 From: Christoph Date: Wed, 4 Nov 2020 08:38:27 +0100 Subject: [PATCH 038/201] Fix custom theme styles not applied to the entry preview (#7071) * Fix custom theme styles not applied to the entry preview Make comment more precise * cbheckstyle * Update CHANGELOG.md --- CHANGELOG.md | 3 ++- .../java/org/jabref/gui/preview/PreviewViewer.java | 12 ++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ac0763c54e..4f83695e2a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,8 +63,9 @@ inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://git - We fixed an issue where identity column header had incorrect foreground color in the Dark theme. [#6796](https://github.com/JabRef/jabref/issues/6796) - We fixed an issue where the RIS exporter added extra blank lines.[#7007](https://github.com/JabRef/jabref/pull/7007/files) - We fixed an issue where clicking on Collapse All button in the Search for Unlinked Local Files expanded the directory structure erroneously [#6848](https://github.com/JabRef/jabref/issues/6848) -- We fixed an issue, when pulling changes from shared database via shortcut caused creation a new new tech report [6867](https://github.com/JabRef/jabref/issues/6867) +- We fixed an issue, when pulling changes from shared database via shortcut caused creation of a new tech report [6867](https://github.com/JabRef/jabref/issues/6867) - We fixed an issue where the JabRef GUI does not highlight the "All entries" group on start-up [#6691](https://github.com/JabRef/jabref/issues/6691) +- We fixed an issue where a custom dark theme was not applied to the entry preview tab [7068](https://github.com/JabRef/jabref/issues/7068) ### Removed diff --git a/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/src/main/java/org/jabref/gui/preview/PreviewViewer.java index 7e2fae665cf..c6515d61e67 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewViewer.java +++ b/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -1,5 +1,6 @@ package org.jabref.gui.preview; +import java.net.MalformedURLException; import java.net.URL; import java.util.Base64; import java.util.Objects; @@ -119,12 +120,19 @@ public PreviewViewer(BibDatabaseContext database, DialogService dialogService, S public void setTheme(Theme theme) { if (theme.getType() == Theme.Type.DARK) { // We need to load the css file manually, due to a bug in the jdk - // TODO: Remove this workaround as soon as https://github.com/openjdk/jfx/pull/22 is merged + // https://bugs.openjdk.java.net/browse/JDK-8240969 + // TODO: Remove this workaround as soon as openjfx 16 is released URL url = JabRefFrame.class.getResource(theme.getPath().getFileName().toString()); String dataUrl = "data:text/css;charset=utf-8;base64," + Base64.getEncoder().encodeToString(StringUtil.getResourceFileAsString(url).getBytes()); previewView.getEngine().setUserStyleSheetLocation(dataUrl); + } else if (theme.getType() != Theme.Type.LIGHT) { + try { + previewView.getEngine().setUserStyleSheetLocation(theme.getPath().toUri().toURL().toExternalForm()); + } catch (MalformedURLException ex) { + LOGGER.error("Cannot set custom theme, invalid url", ex); + } } } @@ -171,7 +179,7 @@ public void setEntry(BibEntry newEntry) { } private void update() { - if (entry.isEmpty() || layout == null) { + if (entry.isEmpty() || (layout == null)) { // Nothing to do return; } From cdc1e40909c4c28497ebd32d10e829da277333d8 Mon Sep 17 00:00:00 2001 From: Christoph Date: Wed, 4 Nov 2020 09:01:25 +0100 Subject: [PATCH 039/201] Fix more 404 links --- docs/adr/0003-use-gradle-as-build-tool.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/adr/0003-use-gradle-as-build-tool.md b/docs/adr/0003-use-gradle-as-build-tool.md index 6da32ce9570..72dca30407c 100644 --- a/docs/adr/0003-use-gradle-as-build-tool.md +++ b/docs/adr/0003-use-gradle-as-build-tool.md @@ -39,8 +39,8 @@ Chosen option: "Gradle", because it is lean and fits our development style. * Good, because [it has good community support](https://linuxhint.com/ant-vs-maven-vs-gradle/) * Good, because [its performance can be 100 times more than maven's performance](https://gradle.org/gradle-vs-maven-performance/). * Bad, because [not that many plugins are available/maintained yet](https://phauer.com/2018/moving-back-from-gradle-to-maven/) -* Bad, because [it lacks a wide variety of application server integrations](http://pages.zeroturnaround.com/rs/zeroturnaround/images/java-build-tools-part-2.pdf) -* Bad, because [it has a medium popularity](http://pages.zeroturnaround.com/rs/zeroturnaround/images/java-build-tools-part-2.pdf) +* Bad, because [it lacks a wide variety of application server integrations](https://fdocuments.us/reader/full/java-build-tools-part-2) +* Bad, because [it has a medium popularity](https://fdocuments.us/reader/full/java-build-tools-part-2) * Bad, because [it allows custom build scripts which need to be debugged](https://www.softwareyoga.com/10-reasons-why-we-chose-maven-over-gradle/) ### Ant @@ -51,8 +51,8 @@ Chosen option: "Gradle", because it is lean and fits our development style. * Bad, because [build scripts can quickly become huge](https://technologyconversations.com/2014/06/18/build-tools/) * Bad, because [everything has to be written from scratch](http://www.baeldung.com/ant-maven-gradle) * Bad, because [no conventions are enforced which can make it hard to understand someone else's build script](http://www.baeldung.com/ant-maven-gradle) -* Bad, because [it has nearly no community support](http://pages.zeroturnaround.com/rs/zeroturnaround/images/java-build-tools-part-2.pdf) -* Bad, because [it has a low popularity](http://pages.zeroturnaround.com/rs/zeroturnaround/images/java-build-tools-part-2.pdf) +* Bad, because [it has nearly no community support](https://fdocuments.us/reader/full/java-build-tools-part-2) +* Bad, because [it has a low popularity](https://fdocuments.us/reader/full/java-build-tools-part-2) * Bad, because [it offers too much freedom](https://www.slant.co/versus/2106/2107/~apache-ant_vs_apache-maven) ## Links From b7e755211c4898f7e6de2b8fb2824cb38106ecbe Mon Sep 17 00:00:00 2001 From: Jonatan Asketorp <2598631+k3KAW8Pnf7mkmdSMPHz27@users.noreply.github.com> Date: Wed, 4 Nov 2020 13:30:17 -0500 Subject: [PATCH 040/201] Fix JavaScript regexp creation --- src/main/java/org/jabref/gui/preview/PreviewViewer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/src/main/java/org/jabref/gui/preview/PreviewViewer.java index c6515d61e67..fe157615b32 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewViewer.java +++ b/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -144,7 +144,7 @@ private void highlightSearchPattern() { "var markInstance = new Mark(document.getElementById(\"content\"));" + "markInstance.unmark({" + " done: function(){" + - " markInstance.markRegExp(/" + pattern + "/gmi);" + + " markInstance.markRegExp(new RegExp('" + pattern + "', 'gmi'));" + " }" + " });" ); From 7dafcd86eef3b9b9cb26e031951c6ea9e3649aaf Mon Sep 17 00:00:00 2001 From: github actions Date: Wed, 4 Nov 2020 19:24:44 +0000 Subject: [PATCH 041/201] Squashed 'src/main/resources/csl-styles/' changes from 5c376b838b..f4399aa413 f4399aa413 APA: match="any" bugfixes (#5092) d0cc2c434e Add APA numeric and update BioEssays (#5089) git-subtree-dir: src/main/resources/csl-styles git-subtree-split: f4399aa4137a2f740ff87dd2b08d15c7f4cfa736 --- apa-6th-edition-no-ampersand.csl | 6 +- apa-6th-edition.csl | 6 +- apa-annotated-bibliography.csl | 10 +- apa-cv.csl | 6 +- apa-no-ampersand.csl | 10 +- apa-no-doi-no-issue.csl | 6 +- apa-numeric-superscript-brackets.csl | 1711 ++++++++++++++++++++++++++ apa-numeric-superscript.csl | 1711 ++++++++++++++++++++++++++ apa-old-doi-prefix.csl | 6 +- apa-single-spaced.csl | 10 +- apa-with-abstract.csl | 10 +- apa.csl | 10 +- bioessays.csl | 81 -- dependent/bioessays.csl | 16 + dependent/biotechnology-journal.csl | 17 + norsk-apa-manual.csl | 10 +- spec/spec_helper.rb | 5 + 17 files changed, 3505 insertions(+), 126 deletions(-) create mode 100644 apa-numeric-superscript-brackets.csl create mode 100644 apa-numeric-superscript.csl delete mode 100644 bioessays.csl create mode 100644 dependent/bioessays.csl create mode 100644 dependent/biotechnology-journal.csl diff --git a/apa-6th-edition-no-ampersand.csl b/apa-6th-edition-no-ampersand.csl index c3b16aabf24..3b4d4b6f42a 100644 --- a/apa-6th-edition-no-ampersand.csl +++ b/apa-6th-edition-no-ampersand.csl @@ -290,7 +290,7 @@ - + @@ -401,7 +401,7 @@ - + @@ -1381,7 +1381,7 @@ - + diff --git a/apa-6th-edition.csl b/apa-6th-edition.csl index f49c6b13532..4114521112b 100644 --- a/apa-6th-edition.csl +++ b/apa-6th-edition.csl @@ -289,7 +289,7 @@ - + @@ -400,7 +400,7 @@ - + @@ -1380,7 +1380,7 @@ - + diff --git a/apa-annotated-bibliography.csl b/apa-annotated-bibliography.csl index fc8408bc01a..2ea93096231 100644 --- a/apa-annotated-bibliography.csl +++ b/apa-annotated-bibliography.csl @@ -387,7 +387,7 @@ - + @@ -543,7 +543,7 @@ - + @@ -1508,7 +1508,7 @@ - + diff --git a/apa-cv.csl b/apa-cv.csl index a04e8b77942..3c5a048c148 100644 --- a/apa-cv.csl +++ b/apa-cv.csl @@ -499,7 +499,7 @@ - + @@ -565,7 +565,7 @@ - + @@ -1255,7 +1255,7 @@ - + diff --git a/apa-no-ampersand.csl b/apa-no-ampersand.csl index 4743fc42f36..70d3b885695 100644 --- a/apa-no-ampersand.csl +++ b/apa-no-ampersand.csl @@ -387,7 +387,7 @@ - + @@ -543,7 +543,7 @@ - + @@ -1508,7 +1508,7 @@ - + diff --git a/apa-no-doi-no-issue.csl b/apa-no-doi-no-issue.csl index 03ca7ff86be..38fdd454a98 100644 --- a/apa-no-doi-no-issue.csl +++ b/apa-no-doi-no-issue.csl @@ -290,7 +290,7 @@ - + @@ -401,7 +401,7 @@ - + @@ -1394,7 +1394,7 @@ - + diff --git a/apa-numeric-superscript-brackets.csl b/apa-numeric-superscript-brackets.csl new file mode 100644 index 00000000000..836b4a003e3 --- /dev/null +++ b/apa-numeric-superscript-brackets.csl @@ -0,0 +1,1711 @@ + + \ No newline at end of file diff --git a/apa-numeric-superscript.csl b/apa-numeric-superscript.csl new file mode 100644 index 00000000000..f1398664d5d --- /dev/null +++ b/apa-numeric-superscript.csl @@ -0,0 +1,1711 @@ + + diff --git a/apa-old-doi-prefix.csl b/apa-old-doi-prefix.csl index a7ef73be8aa..1c1dec0426a 100644 --- a/apa-old-doi-prefix.csl +++ b/apa-old-doi-prefix.csl @@ -290,7 +290,7 @@ - + @@ -401,7 +401,7 @@ - + @@ -1381,7 +1381,7 @@ - + diff --git a/apa-single-spaced.csl b/apa-single-spaced.csl index 0b5a864b3aa..6f8d6b58238 100644 --- a/apa-single-spaced.csl +++ b/apa-single-spaced.csl @@ -387,7 +387,7 @@ - + @@ -543,7 +543,7 @@ - + @@ -1508,7 +1508,7 @@ - + diff --git a/apa-with-abstract.csl b/apa-with-abstract.csl index 491f4463c81..de72d5c31ca 100644 --- a/apa-with-abstract.csl +++ b/apa-with-abstract.csl @@ -387,7 +387,7 @@ - + @@ -543,7 +543,7 @@ - + @@ -1508,7 +1508,7 @@ - + diff --git a/apa.csl b/apa.csl index a5841ca76f9..3389918a90a 100644 --- a/apa.csl +++ b/apa.csl @@ -387,7 +387,7 @@ - + @@ -543,7 +543,7 @@ - + @@ -1508,7 +1508,7 @@ - + diff --git a/bioessays.csl b/bioessays.csl deleted file mode 100644 index 0160c3bc9f3..00000000000 --- a/bioessays.csl +++ /dev/null @@ -1,81 +0,0 @@ - - diff --git a/dependent/bioessays.csl b/dependent/bioessays.csl new file mode 100644 index 00000000000..3b0baaf197d --- /dev/null +++ b/dependent/bioessays.csl @@ -0,0 +1,16 @@ + + diff --git a/dependent/biotechnology-journal.csl b/dependent/biotechnology-journal.csl new file mode 100644 index 00000000000..fadacd1f586 --- /dev/null +++ b/dependent/biotechnology-journal.csl @@ -0,0 +1,17 @@ + + diff --git a/norsk-apa-manual.csl b/norsk-apa-manual.csl index 144f4398cf9..b0b0cecb8d5 100644 --- a/norsk-apa-manual.csl +++ b/norsk-apa-manual.csl @@ -123,7 +123,7 @@ - + @@ -279,7 +279,7 @@ - + @@ -1244,7 +1244,7 @@ - + diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 96d9e89fbba..1be62a88899 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -29,6 +29,11 @@ # These styles are ignored when checking for unused macros UNUSED_MACROS_FILTER = %w{ + apa-annotated-bibliography + apa-cv + apa-numeric-superscript + apa-numeric-superscript-brackets + apa-with-abstract chicago-annotated-bibliography chicago-author-date chicago-author-date-16th-edition chicago-library-list chicago-note-bibliography-16th-edition chicago-note-bibliography-with-ibid From 09483a186e065fbe0fb3e7f6722833b21e9502de Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Wed, 4 Nov 2020 20:27:21 +0100 Subject: [PATCH 042/201] Remove unused supportsPaging --- .../org/jabref/logic/importer/PagedSearchBasedFetcher.java | 5 ----- .../java/org/jabref/logic/importer/SearchBasedFetcher.java | 4 ---- 2 files changed, 9 deletions(-) diff --git a/src/main/java/org/jabref/logic/importer/PagedSearchBasedFetcher.java b/src/main/java/org/jabref/logic/importer/PagedSearchBasedFetcher.java index 9e173d4f829..4532ed42f2b 100644 --- a/src/main/java/org/jabref/logic/importer/PagedSearchBasedFetcher.java +++ b/src/main/java/org/jabref/logic/importer/PagedSearchBasedFetcher.java @@ -18,9 +18,4 @@ public interface PagedSearchBasedFetcher extends SearchBasedFetcher { default int getPageSize() { return 20; } - - @Override - default boolean supportsPaging() { - return true; - } } diff --git a/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java b/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java index 310ea75d9ca..8aa8dcf04f1 100644 --- a/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java +++ b/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java @@ -19,10 +19,6 @@ public interface SearchBasedFetcher extends WebFetcher { */ List performSearch(String query) throws FetcherException; - default boolean supportsPaging() { - return false; - } - /** * This method is used to send complex queries using fielded search. * From 09f8098cde1cafc0b60ad48660d76c089bc53f92 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Thu, 5 Nov 2020 22:27:42 +0100 Subject: [PATCH 043/201] Add link to existing documentation of filed types --- src/main/java/org/jabref/model/entry/field/StandardField.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/jabref/model/entry/field/StandardField.java b/src/main/java/org/jabref/model/entry/field/StandardField.java index a6f0a998b5f..f86feca4a57 100644 --- a/src/main/java/org/jabref/model/entry/field/StandardField.java +++ b/src/main/java/org/jabref/model/entry/field/StandardField.java @@ -6,8 +6,12 @@ import java.util.Optional; import java.util.Set; +import org.jabref.gui.fieldeditors.FieldNameLabel; + /** * Standard BibTeX and BibLaTeX fields, as well as "normal" JabRef specific fields. + * + * See {@link FieldNameLabel#getDescription(org.jabref.model.entry.field.Field)} for a description of each field. */ public enum StandardField implements Field { From 2f27dd3c68cce231aef9125e213d06a5641132e0 Mon Sep 17 00:00:00 2001 From: Christoph Date: Fri, 6 Nov 2020 09:36:08 +0100 Subject: [PATCH 044/201] Fix preview settings not saved due to l10n (#7077) * Fix preview settings not saved due to l10n Introduce internal name for finding layout Fixes #6447 * rename getInternalName --- CHANGELOG.md | 1 + .../jabref/gui/preferences/PreviewTabView.java | 6 +++--- .../gui/preferences/PreviewTabViewModel.java | 15 +++++++++------ .../java/org/jabref/gui/preview/PreviewPanel.java | 2 +- .../org/jabref/gui/preview/PreviewViewer.java | 2 +- .../org/jabref/logic/bst/BstPreviewLayout.java | 5 +++++ .../citationstyle/CitationStylePreviewLayout.java | 9 +++++++-- .../logic/layout/TextBasedPreviewLayout.java | 8 +++++++- .../org/jabref/logic/preview/PreviewLayout.java | 2 ++ .../org/jabref/preferences/JabRefPreferences.java | 2 +- 10 files changed, 37 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f83695e2a1..8fd7bb6f77c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,7 @@ inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://git - We fixed an issue, when pulling changes from shared database via shortcut caused creation of a new tech report [6867](https://github.com/JabRef/jabref/issues/6867) - We fixed an issue where the JabRef GUI does not highlight the "All entries" group on start-up [#6691](https://github.com/JabRef/jabref/issues/6691) - We fixed an issue where a custom dark theme was not applied to the entry preview tab [7068](https://github.com/JabRef/jabref/issues/7068) +- We fixed an issue where modifications to the Custom preview layout in the preferences were not saved [#6447](https://github.com/JabRef/jabref/issues/6447) ### Removed diff --git a/src/main/java/org/jabref/gui/preferences/PreviewTabView.java b/src/main/java/org/jabref/gui/preferences/PreviewTabView.java index 8ddafe09a47..50cb774c6fd 100644 --- a/src/main/java/org/jabref/gui/preferences/PreviewTabView.java +++ b/src/main/java/org/jabref/gui/preferences/PreviewTabView.java @@ -118,7 +118,7 @@ public void initialize() { availableListView.itemsProperty().bindBidirectional(viewModel.availableListProperty()); viewModel.availableSelectionModelProperty().setValue(availableListView.getSelectionModel()); new ViewModelListCellFactory() - .withText(PreviewLayout::getName) + .withText(PreviewLayout::getDisplayName) .install(availableListView); availableListView.setOnDragOver(this::dragOver); availableListView.setOnDragDetected(this::dragDetectedInAvailable); @@ -129,7 +129,7 @@ public void initialize() { chosenListView.itemsProperty().bindBidirectional(viewModel.chosenListProperty()); viewModel.chosenSelectionModelProperty().setValue(chosenListView.getSelectionModel()); new ViewModelListCellFactory() - .withText(PreviewLayout::getName) + .withText(PreviewLayout::getDisplayName) .setOnDragDropped(this::dragDroppedInChosenCell) .install(chosenListView); chosenListView.setOnDragOver(this::dragOver); @@ -197,7 +197,7 @@ private void jumpToSearchKey(ListView list, KeyEvent keypressed) lastKeyPressTime = System.currentTimeMillis(); - list.getItems().stream().filter(item -> item.getName().toLowerCase().startsWith(listSearchTerm)) + list.getItems().stream().filter(item -> item.getDisplayName().toLowerCase().startsWith(listSearchTerm)) .findFirst().ifPresent(list::scrollTo); } diff --git a/src/main/java/org/jabref/gui/preferences/PreviewTabViewModel.java b/src/main/java/org/jabref/gui/preferences/PreviewTabViewModel.java index 87479ec4d1c..1d879c5258f 100644 --- a/src/main/java/org/jabref/gui/preferences/PreviewTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/PreviewTabViewModel.java @@ -91,8 +91,9 @@ public PreviewTabViewModel(DialogService dialogService, PreferencesService prefe initialPreviewPreferences = preferences.getPreviewPreferences(); sourceTextProperty.addListener((observable, oldValue, newValue) -> { - if (getCurrentLayout() instanceof TextBasedPreviewLayout) { - ((TextBasedPreviewLayout) getCurrentLayout()).setText(sourceTextProperty.getValue().replace("\n", "__NEWLINE__")); + var currentLayout = getCurrentLayout(); + if (currentLayout instanceof TextBasedPreviewLayout) { + ((TextBasedPreviewLayout) currentLayout).setText(sourceTextProperty.getValue().replace("\n", "__NEWLINE__")); } }); @@ -112,6 +113,7 @@ public BooleanProperty showAsExtraTabProperty() { return showAsExtraTab; } + @Override public void setValues() { showAsExtraTab.set(initialPreviewPreferences.showPreviewAsExtraTab()); chosenListProperty().getValue().clear(); @@ -174,13 +176,14 @@ private PreviewLayout findLayoutByName(String name) { private PreviewLayout getCurrentLayout() { if (!chosenSelectionModelProperty.getValue().getSelectedItems().isEmpty()) { return chosenSelectionModelProperty.getValue().getSelectedItems().get(0); + } if (!chosenListProperty.getValue().isEmpty()) { return chosenListProperty.getValue().get(0); } - PreviewLayout layout = findLayoutByName("Preview"); + PreviewLayout layout = findLayoutByName(TextBasedPreviewLayout.NAME); if (layout == null) { layout = initialPreviewPreferences.getTextBasedPreviewLayout(); } @@ -196,7 +199,7 @@ public void storeSettings() { chosenListProperty.add(previewPreferences.getTextBasedPreviewLayout()); } - PreviewLayout previewStyle = findLayoutByName("Preview"); + PreviewLayout previewStyle = findLayoutByName(TextBasedPreviewLayout.NAME); if (previewStyle == null) { previewStyle = previewPreferences.getTextBasedPreviewLayout(); } @@ -257,7 +260,7 @@ public void removeFromChosen() { chosenSelectionModelProperty.getValue().clearSelection(); chosenListProperty.removeAll(selected); availableListProperty.addAll(selected); - availableListProperty.sort((a, b) -> a.getName().compareToIgnoreCase(b.getName())); + availableListProperty.sort((a, b) -> a.getDisplayName().compareToIgnoreCase(b.getDisplayName())); } public void selectedInChosenUp() { @@ -413,7 +416,7 @@ public boolean dragDropped(ListProperty targetList, Dragboard dra success = true; if (targetList == availableListProperty) { - targetList.getValue().sort((a, b) -> a.getName().compareToIgnoreCase(b.getName())); + targetList.getValue().sort((a, b) -> a.getDisplayName().compareToIgnoreCase(b.getDisplayName())); } } } diff --git a/src/main/java/org/jabref/gui/preview/PreviewPanel.java b/src/main/java/org/jabref/gui/preview/PreviewPanel.java index 113e65fe0c0..aa77683129c 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewPanel.java +++ b/src/main/java/org/jabref/gui/preview/PreviewPanel.java @@ -111,7 +111,7 @@ private void updateLayout(PreviewPreferences previewPreferences, boolean init) { previewView.setLayout(currentPreviewStyle); preferences.storePreviewPreferences(previewPreferences); if (!init) { - dialogService.notify(Localization.lang("Preview style changed to: %0", currentPreviewStyle.getName())); + dialogService.notify(Localization.lang("Preview style changed to: %0", currentPreviewStyle.getDisplayName())); } } diff --git a/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/src/main/java/org/jabref/gui/preview/PreviewViewer.java index c6515d61e67..5d3e526985d 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewViewer.java +++ b/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -188,7 +188,7 @@ private void update() { BackgroundTask .wrap(() -> layout.generatePreview(entry.get(), database.getDatabase())) - .onRunning(() -> setPreviewText("" + Localization.lang("Processing %0", Localization.lang("Citation Style")) + ": " + layout.getName() + " ..." + "")) + .onRunning(() -> setPreviewText("" + Localization.lang("Processing %0", Localization.lang("Citation Style")) + ": " + layout.getDisplayName() + " ..." + "")) .onSuccess(this::setPreviewText) .onFailure(exception -> { LOGGER.error("Error while generating citation style", exception); diff --git a/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java b/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java index 312df6bbf98..545dce5ba6d 100644 --- a/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java +++ b/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java @@ -76,6 +76,11 @@ public String generatePreview(BibEntry originalEntry, BibDatabase database) { return result; } + @Override + public String getDisplayName() { + return name; + } + @Override public String getName() { return name; diff --git a/src/main/java/org/jabref/logic/citationstyle/CitationStylePreviewLayout.java b/src/main/java/org/jabref/logic/citationstyle/CitationStylePreviewLayout.java index d40e86c7414..82f7df56f2b 100644 --- a/src/main/java/org/jabref/logic/citationstyle/CitationStylePreviewLayout.java +++ b/src/main/java/org/jabref/logic/citationstyle/CitationStylePreviewLayout.java @@ -5,7 +5,7 @@ import org.jabref.model.entry.BibEntry; public class CitationStylePreviewLayout implements PreviewLayout { - private CitationStyle citationStyle; + private final CitationStyle citationStyle; public CitationStylePreviewLayout(CitationStyle citationStyle) { this.citationStyle = citationStyle; @@ -17,7 +17,7 @@ public String generatePreview(BibEntry entry, BibDatabase database) { } @Override - public String getName() { + public String getDisplayName() { return citationStyle.getTitle(); } @@ -28,4 +28,9 @@ public String getSource() { public String getFilePath() { return citationStyle.getFilePath(); } + + @Override + public String getName() { + return citationStyle.getTitle(); + } } diff --git a/src/main/java/org/jabref/logic/layout/TextBasedPreviewLayout.java b/src/main/java/org/jabref/logic/layout/TextBasedPreviewLayout.java index 07d1b7a82ba..a3bd00c9f30 100644 --- a/src/main/java/org/jabref/logic/layout/TextBasedPreviewLayout.java +++ b/src/main/java/org/jabref/logic/layout/TextBasedPreviewLayout.java @@ -15,8 +15,9 @@ * Implements the preview based JabRef's Custom export fitlters. */ public class TextBasedPreviewLayout implements PreviewLayout { - private static final Logger LOGGER = LoggerFactory.getLogger(TextBasedPreviewLayout.class); + public static final String NAME = "PREVIEW"; + private static final Logger LOGGER = LoggerFactory.getLogger(TextBasedPreviewLayout.class); private Layout layout; private String text; private LayoutFormatterPreferences layoutFormatterPreferences; @@ -56,6 +57,11 @@ public String getText() { @Override public String getName() { + return NAME; + } + + @Override + public String getDisplayName() { return Localization.lang("Customized preview style"); } } diff --git a/src/main/java/org/jabref/logic/preview/PreviewLayout.java b/src/main/java/org/jabref/logic/preview/PreviewLayout.java index 3f6626924b0..0fbd70f7839 100644 --- a/src/main/java/org/jabref/logic/preview/PreviewLayout.java +++ b/src/main/java/org/jabref/logic/preview/PreviewLayout.java @@ -10,5 +10,7 @@ public interface PreviewLayout { String generatePreview(BibEntry entry, BibDatabase database); + String getDisplayName(); + String getName(); } diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 2e908686c38..0febc154239 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -2409,7 +2409,7 @@ public void storePreviewPreferences(PreviewPreferences previewPreferences) { if (layout instanceof CitationStylePreviewLayout) { return ((CitationStylePreviewLayout) layout).getFilePath(); } else { - return layout.getName(); + return layout.getDisplayName(); } }).collect(Collectors.toList())); putDouble(PREVIEW_PANEL_HEIGHT, previewPreferences.getPreviewPanelDividerPosition().doubleValue()); From 5761b131dd66c3c5f3a3c9c1283d6f8eb9a08307 Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Fri, 6 Nov 2020 18:41:41 +0100 Subject: [PATCH 045/201] Follow up fix for 7077 Reset preview layouts on clear --- src/main/java/org/jabref/preferences/JabRefPreferences.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 0febc154239..691c666ea52 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -967,6 +967,7 @@ public void putColor(String key, Color color) { public void clear() throws BackingStoreException { clearAllBibEntryTypes(); clearCitationKeyPatterns(); + this.previewPreferences = null; prefs.clear(); new SharedDatabasePreferences().clear(); } From b32d381899dfb096735ece25bf933cc2fa7c82e2 Mon Sep 17 00:00:00 2001 From: Jonatan Asketorp <2598631+k3KAW8Pnf7mkmdSMPHz27@users.noreply.github.com> Date: Fri, 6 Nov 2020 15:44:27 -0500 Subject: [PATCH 046/201] Fix GrammarBasedSearchRule with regexp --- .../org/jabref/model/search/rules/GrammarBasedSearchRule.java | 2 +- src/main/java/org/jabref/model/search/rules/SearchRules.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jabref/model/search/rules/GrammarBasedSearchRule.java b/src/main/java/org/jabref/model/search/rules/GrammarBasedSearchRule.java index f70a05b1576..98e12978963 100644 --- a/src/main/java/org/jabref/model/search/rules/GrammarBasedSearchRule.java +++ b/src/main/java/org/jabref/model/search/rules/GrammarBasedSearchRule.java @@ -232,7 +232,7 @@ public Boolean visitComparison(SearchParser.ComparisonContext context) { if (fieldDescriptor.isPresent()) { return comparison(fieldDescriptor.get().getText(), ComparisonOperator.build(context.operator.getText()), right); } else { - return new ContainBasedSearchRule(caseSensitive).applyRule(right, entry); + return SearchRules.getSearchRule(caseSensitive, regex).applyRule(right, entry); } } diff --git a/src/main/java/org/jabref/model/search/rules/SearchRules.java b/src/main/java/org/jabref/model/search/rules/SearchRules.java index 88268f6d2b7..dcde2b66227 100644 --- a/src/main/java/org/jabref/model/search/rules/SearchRules.java +++ b/src/main/java/org/jabref/model/search/rules/SearchRules.java @@ -31,7 +31,7 @@ private static boolean isSimpleQuery(String query) { return SIMPLE_EXPRESSION.matcher(query).matches(); } - private static SearchRule getSearchRule(boolean caseSensitive, boolean regex) { + static SearchRule getSearchRule(boolean caseSensitive, boolean regex) { if (regex) { return new RegexBasedSearchRule(caseSensitive); } else { From 128b41fd3d19986be66dc4772293df712f10ef08 Mon Sep 17 00:00:00 2001 From: Jonatan Asketorp <2598631+k3KAW8Pnf7mkmdSMPHz27@users.noreply.github.com> Date: Fri, 6 Nov 2020 16:05:02 -0500 Subject: [PATCH 047/201] Fix JavaScript regex creation --- src/main/java/org/jabref/gui/preview/PreviewViewer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/src/main/java/org/jabref/gui/preview/PreviewViewer.java index fe157615b32..0c311433a73 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewViewer.java +++ b/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -144,7 +144,7 @@ private void highlightSearchPattern() { "var markInstance = new Mark(document.getElementById(\"content\"));" + "markInstance.unmark({" + " done: function(){" + - " markInstance.markRegExp(new RegExp('" + pattern + "', 'gmi'));" + + " markInstance.markRegExp(/" + pattern.replaceAll("/", "\\/") + "/gmi));" + " }" + " });" ); From 4429ea1ae9f9ed6059a88e4362cb2e9ee96f9fea Mon Sep 17 00:00:00 2001 From: Jonatan Asketorp <2598631+k3KAW8Pnf7mkmdSMPHz27@users.noreply.github.com> Date: Fri, 6 Nov 2020 16:18:12 -0500 Subject: [PATCH 048/201] Readability improvement? --- .../org/jabref/logic/search/SearchQuery.java | 64 ++++++++++--------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/jabref/logic/search/SearchQuery.java b/src/main/java/org/jabref/logic/search/SearchQuery.java index 67f79cb5289..b1bed8e19c8 100644 --- a/src/main/java/org/jabref/logic/search/SearchQuery.java +++ b/src/main/java/org/jabref/logic/search/SearchQuery.java @@ -4,8 +4,9 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.StringJoiner; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntry; @@ -17,12 +18,6 @@ import org.jabref.model.search.rules.SentenceAnalyzer; public class SearchQuery implements SearchMatcher { - - /** - * Regex pattern for escaping special characters in javascript regular expressions - */ - public static final Pattern JAVASCRIPT_ESCAPED_CHARS_PATTERN = Pattern.compile("[\\.\\*\\+\\?\\^\\$\\{\\}\\(\\)\\|\\[\\]\\\\/]"); - /** * The mode of escaping special characters in regular expressions */ @@ -30,11 +25,34 @@ private enum EscapeMode { /** * using \Q and \E marks */ - JAVA, + JAVA { + @Override + String format(String regex) { + return Pattern.quote(regex); + } + }, /** * escaping all javascript regex special characters separately */ - JAVASCRIPT + JAVASCRIPT { + @Override + String format(String regex) { + return JAVASCRIPT_ESCAPED_CHARS_PATTERN.matcher(regex).replaceAll("\\\\$0"); + } + }; + + /** + * Regex pattern for escaping special characters in javascript regular expressions + */ + private static final Pattern JAVASCRIPT_ESCAPED_CHARS_PATTERN = Pattern.compile("[.*+?^${}()|\\[\\]\\\\/]"); + + /** + * Attempt to escape all regex special characters. + * + * @param regex a string containing a regex expression + * @return a regex with all special characters escaped + */ + abstract String format(String regex); } private final String query; @@ -128,8 +146,7 @@ public boolean isRegularExpression() { } /** - * Returns a list of words this query searches for. - * The returned strings can be a regular expression. + * Returns a list of words this query searches for. The returned strings can be a regular expression. */ public List getSearchWords() { if (isRegularExpression()) { @@ -151,7 +168,9 @@ public Optional getJavaScriptPatternForWords() { return joinWordsToPattern(EscapeMode.JAVASCRIPT); } - /** Returns a regular expression pattern in the form (w1)|(w2)| ... wi are escaped if no regular expression search is enabled + /** + * Returns a regular expression pattern in the form (w1)|(w2)| ... wi are escaped if no regular expression search is enabled + * * @param escapeMode the mode of escaping special characters in wi */ private Optional joinWordsToPattern(EscapeMode escapeMode) { @@ -162,24 +181,11 @@ private Optional joinWordsToPattern(EscapeMode escapeMode) { } // compile the words to a regular expression in the form (w1)|(w2)|(w3) - StringJoiner joiner = new StringJoiner(")|(", "(", ")"); - for (String word : words) { - if (regularExpression) { - joiner.add(word); - } else { - switch (escapeMode) { - case JAVA: - joiner.add(Pattern.quote(word)); - break; - case JAVASCRIPT: - joiner.add(JAVASCRIPT_ESCAPED_CHARS_PATTERN.matcher(word).replaceAll("\\\\$0")); - break; - default: - throw new IllegalArgumentException("Unknown special characters escape mode: " + escapeMode); - } - } + Stream joiner = words.stream(); + if (regularExpression) { + joiner = joiner.map(escapeMode::format); } - String searchPattern = joiner.toString(); + String searchPattern = joiner.collect(Collectors.joining(")|(", "(", ")")); if (caseSensitive) { return Optional.of(Pattern.compile(searchPattern)); From 7abe68296b2ab88dcd9d8702c495ac936ea10835 Mon Sep 17 00:00:00 2001 From: Jonatan Asketorp <2598631+k3KAW8Pnf7mkmdSMPHz27@users.noreply.github.com> Date: Fri, 6 Nov 2020 16:31:56 -0500 Subject: [PATCH 049/201] Add missed negation --- src/main/java/org/jabref/logic/search/SearchQuery.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/logic/search/SearchQuery.java b/src/main/java/org/jabref/logic/search/SearchQuery.java index b1bed8e19c8..5138edcf178 100644 --- a/src/main/java/org/jabref/logic/search/SearchQuery.java +++ b/src/main/java/org/jabref/logic/search/SearchQuery.java @@ -182,7 +182,8 @@ private Optional joinWordsToPattern(EscapeMode escapeMode) { // compile the words to a regular expression in the form (w1)|(w2)|(w3) Stream joiner = words.stream(); - if (regularExpression) { + if (!regularExpression) { + // Reformat string when we are looking for a literal match joiner = joiner.map(escapeMode::format); } String searchPattern = joiner.collect(Collectors.joining(")|(", "(", ")")); From 979fa6e40b55e5c2d01bb55a8f13d71b7e85b9da Mon Sep 17 00:00:00 2001 From: Jonatan Asketorp <2598631+k3KAW8Pnf7mkmdSMPHz27@users.noreply.github.com> Date: Fri, 6 Nov 2020 16:45:37 -0500 Subject: [PATCH 050/201] Fix JavaScript regex pattern --- src/main/java/org/jabref/gui/preview/PreviewViewer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/src/main/java/org/jabref/gui/preview/PreviewViewer.java index 0c311433a73..491768d1dd4 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewViewer.java +++ b/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -144,7 +144,7 @@ private void highlightSearchPattern() { "var markInstance = new Mark(document.getElementById(\"content\"));" + "markInstance.unmark({" + " done: function(){" + - " markInstance.markRegExp(/" + pattern.replaceAll("/", "\\/") + "/gmi));" + + " markInstance.markRegExp(/" + pattern.replaceAll("/", "\\\\/") + "/gmi);" + " }" + " });" ); From cc1bb8339311c8bd313113c503b2a48be3bf1b99 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Fri, 6 Nov 2020 22:59:10 +0100 Subject: [PATCH 051/201] Special field code maintenance (#7078) --- src/main/java/org/jabref/gui/JabRefFrame.java | 14 +++--- .../org/jabref/gui/maintable/CellFactory.java | 15 ++++--- .../org/jabref/gui/maintable/MainTable.java | 1 + .../gui/maintable/MainTableColumnFactory.java | 4 +- .../jabref/gui/maintable/RightClickMenu.java | 17 ++++--- .../maintable/columns/SpecialFieldColumn.java | 9 ++-- .../gui/specialfields/SpecialFieldAction.java | 16 +++++-- .../SpecialFieldMenuItemFactory.java | 37 ++++++++++++---- .../specialfields/SpecialFieldViewModel.java | 27 ++++++++---- .../java/org/jabref/gui/util/FieldsUtil.java | 2 +- .../model/entry/field/SpecialFieldValue.java | 44 +++++++------------ 11 files changed, 110 insertions(+), 76 deletions(-) diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 4855a16e3ca..963df97bb57 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -740,18 +740,18 @@ private MenuBar createMenu() { new SeparatorMenuItem(), // ToDo: SpecialField needs the active BasePanel to mark it as changed. // Refactor BasePanel, should mark the BibDatabaseContext or the UndoManager as dirty instead! - SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.RANKING, factory, this, dialogService, stateManager), - SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.RELEVANCE, factory, this, dialogService, stateManager), - SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.QUALITY, factory, this, dialogService, stateManager), - SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.PRINTED, factory, this, dialogService, stateManager), - SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.PRIORITY, factory, this, dialogService, stateManager), - SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.READ_STATUS, factory, this, dialogService, stateManager) + SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.RANKING, factory, this, dialogService, prefs, undoManager, stateManager), + SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.RELEVANCE, factory, this, dialogService, prefs, undoManager, stateManager), + SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.QUALITY, factory, this, dialogService, prefs, undoManager, stateManager), + SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.PRINTED, factory, this, dialogService, prefs, undoManager, stateManager), + SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.PRIORITY, factory, this, dialogService, prefs, undoManager, stateManager), + SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.READ_STATUS, factory, this, dialogService, prefs, undoManager, stateManager) ); } // @formatter:off library.getItems().addAll( - factory.createMenuItem(StandardActions.NEW_ENTRY, new NewEntryAction(this, dialogService, Globals.prefs, stateManager)), + factory.createMenuItem(StandardActions.NEW_ENTRY, new NewEntryAction(this, dialogService, prefs, stateManager)), factory.createMenuItem(StandardActions.NEW_ENTRY_FROM_PLAIN_TEXT, new ExtractBibtexAction(stateManager)), factory.createMenuItem(StandardActions.DELETE_ENTRY, new EditAction(StandardActions.DELETE_ENTRY, this, stateManager)), diff --git a/src/main/java/org/jabref/gui/maintable/CellFactory.java b/src/main/java/org/jabref/gui/maintable/CellFactory.java index 29b164f657e..9f0940831d4 100644 --- a/src/main/java/org/jabref/gui/maintable/CellFactory.java +++ b/src/main/java/org/jabref/gui/maintable/CellFactory.java @@ -16,12 +16,13 @@ import org.jabref.model.entry.field.SpecialField; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.field.UnknownField; +import org.jabref.preferences.PreferencesService; public class CellFactory { private final Map TABLE_ICONS = new HashMap<>(); - public CellFactory(ExternalFileTypes externalFileTypes, UndoManager undoManager) { + public CellFactory(ExternalFileTypes externalFileTypes, PreferencesService preferencesService, UndoManager undoManager) { JabRefIcon icon; icon = IconTheme.JabRefIcons.PDF_FILE; // icon.setToo(Localization.lang("Open") + " PDF"); @@ -61,36 +62,36 @@ public CellFactory(ExternalFileTypes externalFileTypes, UndoManager undoManager) TABLE_ICONS.put(fileType.getField(), icon); } - SpecialFieldViewModel relevanceViewModel = new SpecialFieldViewModel(SpecialField.RELEVANCE, undoManager); + SpecialFieldViewModel relevanceViewModel = new SpecialFieldViewModel(SpecialField.RELEVANCE, preferencesService, undoManager); icon = relevanceViewModel.getIcon(); // icon.setToolTipText(relevanceViewModel.getLocalization()); TABLE_ICONS.put(SpecialField.RELEVANCE, icon); - SpecialFieldViewModel qualityViewModel = new SpecialFieldViewModel(SpecialField.QUALITY, undoManager); + SpecialFieldViewModel qualityViewModel = new SpecialFieldViewModel(SpecialField.QUALITY, preferencesService, undoManager); icon = qualityViewModel.getIcon(); // icon.setToolTipText(qualityViewModel.getLocalization()); TABLE_ICONS.put(SpecialField.QUALITY, icon); // Ranking item in the menu uses one star - SpecialFieldViewModel rankViewModel = new SpecialFieldViewModel(SpecialField.RANKING, undoManager); + SpecialFieldViewModel rankViewModel = new SpecialFieldViewModel(SpecialField.RANKING, preferencesService, undoManager); icon = rankViewModel.getIcon(); // icon.setToolTipText(rankViewModel.getLocalization()); TABLE_ICONS.put(SpecialField.RANKING, icon); // Priority icon used for the menu - SpecialFieldViewModel priorityViewModel = new SpecialFieldViewModel(SpecialField.PRIORITY, undoManager); + SpecialFieldViewModel priorityViewModel = new SpecialFieldViewModel(SpecialField.PRIORITY, preferencesService, undoManager); icon = priorityViewModel.getIcon(); // icon.setToolTipText(priorityViewModel.getLocalization()); TABLE_ICONS.put(SpecialField.PRIORITY, icon); // Read icon used for menu - SpecialFieldViewModel readViewModel = new SpecialFieldViewModel(SpecialField.READ_STATUS, undoManager); + SpecialFieldViewModel readViewModel = new SpecialFieldViewModel(SpecialField.READ_STATUS, preferencesService, undoManager); icon = readViewModel.getIcon(); // icon.setToolTipText(readViewModel.getLocalization()); TABLE_ICONS.put(SpecialField.READ_STATUS, icon); // Print icon used for menu - SpecialFieldViewModel printedViewModel = new SpecialFieldViewModel(SpecialField.PRINTED, undoManager); + SpecialFieldViewModel printedViewModel = new SpecialFieldViewModel(SpecialField.PRINTED, preferencesService, undoManager); icon = printedViewModel.getIcon(); // icon.setToolTipText(printedViewModel.getLocalization()); TABLE_ICONS.put(SpecialField.PRINTED, icon); diff --git a/src/main/java/org/jabref/gui/maintable/MainTable.java b/src/main/java/org/jabref/gui/maintable/MainTable.java index cf5c8cd2b2a..d0e42e0221c 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTable.java +++ b/src/main/java/org/jabref/gui/maintable/MainTable.java @@ -114,6 +114,7 @@ public MainTable(MainTableDataModel model, dialogService, stateManager, preferencesService, + undoManager, Globals.clipboardManager)) .setOnDragDetected(this::handleOnDragDetected) .setOnDragDropped(this::handleOnDragDropped) diff --git a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java index 167c12989f1..8ff3dd7efc9 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java @@ -68,7 +68,7 @@ public MainTableColumnFactory(BibDatabaseContext database, this.columnPreferences = preferencesService.getColumnPreferences(); this.externalFileTypes = Objects.requireNonNull(externalFileTypes); this.dialogService = dialogService; - this.cellFactory = new CellFactory(externalFileTypes, undoManager); + this.cellFactory = new CellFactory(externalFileTypes, preferencesService, undoManager); this.undoManager = undoManager; } @@ -214,7 +214,7 @@ private TableColumn> createIdentifier * Creates a column that displays a {@link SpecialField} */ private TableColumn> createSpecialFieldColumn(MainTableColumnModel columnModel) { - return new SpecialFieldColumn(columnModel, undoManager); + return new SpecialFieldColumn(columnModel, preferencesService, undoManager); } /** diff --git a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index 6305baeaea7..a1bd6ff7fc9 100644 --- a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -1,5 +1,7 @@ package org.jabref.gui.maintable; +import javax.swing.undo.UndoManager; + import javafx.scene.control.ContextMenu; import javafx.scene.control.Menu; import javafx.scene.control.SeparatorMenuItem; @@ -37,6 +39,7 @@ public static ContextMenu create(BibEntryTableViewModel entry, DialogService dialogService, StateManager stateManager, PreferencesService preferencesService, + UndoManager undoManager, ClipBoardManager clipBoardManager) { ContextMenu contextMenu = new ContextMenu(); ActionFactory factory = new ActionFactory(keyBindingRepository); @@ -54,12 +57,12 @@ public static ContextMenu create(BibEntryTableViewModel entry, contextMenu.getItems().add(new SeparatorMenuItem()); if (preferencesService.getSpecialFieldsPreferences().isSpecialFieldsEnabled()) { - contextMenu.getItems().add(SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.RANKING, factory, libraryTab.frame(), dialogService, stateManager)); - contextMenu.getItems().add(SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.RELEVANCE, factory, libraryTab.frame(), dialogService, stateManager)); - contextMenu.getItems().add(SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.QUALITY, factory, libraryTab.frame(), dialogService, stateManager)); - contextMenu.getItems().add(SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.PRINTED, factory, libraryTab.frame(), dialogService, stateManager)); - contextMenu.getItems().add(SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.PRIORITY, factory, libraryTab.frame(), dialogService, stateManager)); - contextMenu.getItems().add(SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.READ_STATUS, factory, libraryTab.frame(), dialogService, stateManager)); + contextMenu.getItems().add(SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.RANKING, factory, libraryTab.frame(), dialogService, preferencesService, undoManager, stateManager)); + contextMenu.getItems().add(SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.RELEVANCE, factory, libraryTab.frame(), dialogService, preferencesService, undoManager, stateManager)); + contextMenu.getItems().add(SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.QUALITY, factory, libraryTab.frame(), dialogService, preferencesService, undoManager, stateManager)); + contextMenu.getItems().add(SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.PRINTED, factory, libraryTab.frame(), dialogService, preferencesService, undoManager, stateManager)); + contextMenu.getItems().add(SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.PRIORITY, factory, libraryTab.frame(), dialogService, preferencesService, undoManager, stateManager)); + contextMenu.getItems().add(SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.READ_STATUS, factory, libraryTab.frame(), dialogService, preferencesService, undoManager, stateManager)); } contextMenu.getItems().add(new SeparatorMenuItem()); @@ -94,7 +97,7 @@ private static Menu createCopySubMenu(LibraryTab libraryTab, copySpecialMenu.getItems().add(factory.createMenuItem(StandardActions.COPY_KEY_AND_LINK, new CopyMoreAction(StandardActions.COPY_KEY_AND_LINK, dialogService, stateManager, clipBoardManager, preferencesService))); // the submenu will behave dependent on what style is currently selected (citation/preview) - PreviewPreferences previewPreferences = Globals.prefs.getPreviewPreferences(); + PreviewPreferences previewPreferences = preferencesService.getPreviewPreferences(); PreviewLayout style = previewPreferences.getCurrentPreviewStyle(); if (style instanceof CitationStylePreviewLayout) { copySpecialMenu.getItems().add(factory.createMenuItem(StandardActions.COPY_CITATION_HTML, new CopyCitationAction(CitationStyleOutputFormat.HTML, dialogService, stateManager, clipBoardManager, previewPreferences))); diff --git a/src/main/java/org/jabref/gui/maintable/columns/SpecialFieldColumn.java b/src/main/java/org/jabref/gui/maintable/columns/SpecialFieldColumn.java index cb28ee91a3f..190f66374eb 100644 --- a/src/main/java/org/jabref/gui/maintable/columns/SpecialFieldColumn.java +++ b/src/main/java/org/jabref/gui/maintable/columns/SpecialFieldColumn.java @@ -26,6 +26,7 @@ import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.SpecialField; import org.jabref.model.entry.field.SpecialFieldValue; +import org.jabref.preferences.PreferencesService; import com.tobiasdiez.easybind.EasyBind; import org.controlsfx.control.Rating; @@ -35,14 +36,16 @@ */ public class SpecialFieldColumn extends MainTableColumn> { + private final PreferencesService preferencesService; private final UndoManager undoManager; - public SpecialFieldColumn(MainTableColumnModel model, UndoManager undoManager) { + public SpecialFieldColumn(MainTableColumnModel model, PreferencesService preferencesService, UndoManager undoManager) { super(model); + this.preferencesService = preferencesService; this.undoManager = undoManager; SpecialField specialField = (SpecialField) FieldFactory.parseField(model.getQualifier()); - SpecialFieldViewModel specialFieldViewModel = new SpecialFieldViewModel(specialField, undoManager); + SpecialFieldViewModel specialFieldViewModel = new SpecialFieldViewModel(specialField, preferencesService, undoManager); Node headerGraphic = specialFieldViewModel.getIcon().getGraphicNode(); Tooltip.install(headerGraphic, new Tooltip(specialFieldViewModel.getLocalization())); @@ -98,7 +101,7 @@ private Rating createSpecialRating(BibEntryTableViewModel entry, SpecialFieldVal Rating ranking = new Rating(); ranking.setRating(value.getValue().toRating()); EasyBind.subscribe(ranking.ratingProperty(), rating -> - new SpecialFieldViewModel(SpecialField.RANKING, undoManager) + new SpecialFieldViewModel(SpecialField.RANKING, preferencesService, undoManager) .setSpecialFieldValue(entry.getEntry(), SpecialFieldValue.getRating(rating.intValue()))); return ranking; diff --git a/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java b/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java index 4701b93a58d..17b04069192 100644 --- a/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java +++ b/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java @@ -3,8 +3,9 @@ import java.util.List; import java.util.Objects; +import javax.swing.undo.UndoManager; + import org.jabref.gui.DialogService; -import org.jabref.gui.Globals; import org.jabref.gui.JabRefFrame; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; @@ -16,6 +17,7 @@ import org.jabref.model.FieldChange; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.SpecialField; +import org.jabref.preferences.PreferencesService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,6 +31,8 @@ public class SpecialFieldAction extends SimpleCommand { private final boolean nullFieldIfValueIsTheSame; private final String undoText; private final DialogService dialogService; + private final PreferencesService preferencesService; + private final UndoManager undoManager; private final StateManager stateManager; /** @@ -40,6 +44,8 @@ public SpecialFieldAction(JabRefFrame frame, boolean nullFieldIfValueIsTheSame, String undoText, DialogService dialogService, + PreferencesService preferencesService, + UndoManager undoManager, StateManager stateManager) { this.frame = frame; this.specialField = specialField; @@ -47,6 +53,8 @@ public SpecialFieldAction(JabRefFrame frame, this.nullFieldIfValueIsTheSame = nullFieldIfValueIsTheSame; this.undoText = undoText; this.dialogService = dialogService; + this.preferencesService = preferencesService; + this.undoManager = undoManager; this.stateManager = stateManager; this.executable.bind(ActionHelper.needsEntriesSelected(stateManager)); @@ -67,8 +75,8 @@ public void execute() { value, bibEntry, nullFieldIfValueIsTheSame, - Globals.prefs.getSpecialFieldsPreferences().isKeywordSyncEnabled(), - Globals.prefs.getKeywordDelimiter()); + preferencesService.getSpecialFieldsPreferences().isKeywordSyncEnabled(), + preferencesService.getKeywordDelimiter()); for (FieldChange change : changes) { ce.addEdit(new UndoableFieldChange(change)); @@ -97,7 +105,7 @@ public void execute() { private String getTextDone(SpecialField field, String... params) { Objects.requireNonNull(params); - SpecialFieldViewModel viewModel = new SpecialFieldViewModel(field, frame.getUndoManager()); + SpecialFieldViewModel viewModel = new SpecialFieldViewModel(field, preferencesService, undoManager); if (field.isSingleValueField() && (params.length == 1) && (params[0] != null)) { // Single value fields can be toggled only diff --git a/src/main/java/org/jabref/gui/specialfields/SpecialFieldMenuItemFactory.java b/src/main/java/org/jabref/gui/specialfields/SpecialFieldMenuItemFactory.java index a32fe926312..488d7ad637c 100644 --- a/src/main/java/org/jabref/gui/specialfields/SpecialFieldMenuItemFactory.java +++ b/src/main/java/org/jabref/gui/specialfields/SpecialFieldMenuItemFactory.java @@ -8,30 +8,51 @@ import javafx.scene.control.MenuItem; import org.jabref.gui.DialogService; -import org.jabref.gui.Globals; import org.jabref.gui.JabRefFrame; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionFactory; import org.jabref.model.entry.field.SpecialField; import org.jabref.model.entry.field.SpecialFieldValue; +import org.jabref.preferences.PreferencesService; import de.saxsys.mvvmfx.utils.commands.Command; public class SpecialFieldMenuItemFactory { - public static MenuItem getSpecialFieldSingleItem(SpecialField field, ActionFactory factory, JabRefFrame frame, DialogService dialogService, StateManager stateManager) { + public static MenuItem getSpecialFieldSingleItem(SpecialField field, + ActionFactory factory, + JabRefFrame frame, + DialogService dialogService, + PreferencesService preferencesService, + UndoManager undoManager, + StateManager stateManager) { SpecialFieldValueViewModel specialField = new SpecialFieldValueViewModel(field.getValues().get(0)); + return factory.createMenuItem(specialField.getAction(), - new SpecialFieldViewModel(field, Globals.undoManager).getSpecialFieldAction(field.getValues().get(0), frame, dialogService, stateManager)); + new SpecialFieldViewModel(field, preferencesService, undoManager) + .getSpecialFieldAction(field.getValues().get(0), frame, dialogService, stateManager)); } - public static Menu createSpecialFieldMenu(SpecialField field, ActionFactory factory, JabRefFrame frame, DialogService dialogService, StateManager stateManager) { - return createSpecialFieldMenu(field, factory, Globals.undoManager, specialField -> - new SpecialFieldViewModel(field, Globals.undoManager).getSpecialFieldAction(specialField.getValue(), frame, dialogService, stateManager)); + public static Menu createSpecialFieldMenu(SpecialField field, + ActionFactory factory, + JabRefFrame frame, + DialogService dialogService, + PreferencesService preferencesService, + UndoManager undoManager, + StateManager stateManager) { + + return createSpecialFieldMenu(field, factory, preferencesService, undoManager, specialField -> + new SpecialFieldViewModel(field, preferencesService, undoManager) + .getSpecialFieldAction(specialField.getValue(), frame, dialogService, stateManager)); } - public static Menu createSpecialFieldMenu(SpecialField field, ActionFactory factory, UndoManager undoManager, Function commandFactory) { - SpecialFieldViewModel viewModel = new SpecialFieldViewModel(field, undoManager); + public static Menu createSpecialFieldMenu(SpecialField field, + ActionFactory factory, + PreferencesService preferencesService, + UndoManager undoManager, + Function commandFactory) { + SpecialFieldViewModel viewModel = new SpecialFieldViewModel(field, preferencesService, undoManager); Menu menu = factory.createMenu(viewModel.getAction()); + for (SpecialFieldValue Value : field.getValues()) { SpecialFieldValueViewModel valueViewModel = new SpecialFieldValueViewModel(Value); menu.getItems().add(factory.createMenuItem(valueViewModel.getAction(), commandFactory.apply(valueViewModel))); diff --git a/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java b/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java index 4a9f6cd4a45..a60f1621bff 100644 --- a/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java +++ b/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java @@ -7,7 +7,6 @@ import javax.swing.undo.UndoManager; import org.jabref.gui.DialogService; -import org.jabref.gui.Globals; import org.jabref.gui.JabRefFrame; import org.jabref.gui.StateManager; import org.jabref.gui.actions.Action; @@ -19,14 +18,17 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.SpecialField; import org.jabref.model.entry.field.SpecialFieldValue; +import org.jabref.preferences.PreferencesService; public class SpecialFieldViewModel { private final SpecialField field; - private UndoManager undoManager; + private final PreferencesService preferencesService; + private final UndoManager undoManager; - public SpecialFieldViewModel(SpecialField field, UndoManager undoManager) { + public SpecialFieldViewModel(SpecialField field, PreferencesService preferencesService, UndoManager undoManager) { this.field = Objects.requireNonNull(field); + this.preferencesService = Objects.requireNonNull(preferencesService); this.undoManager = Objects.requireNonNull(undoManager); } @@ -34,13 +36,20 @@ public SpecialField getField() { return field; } - public SpecialFieldAction getSpecialFieldAction(SpecialFieldValue value, JabRefFrame frame, DialogService dialogService, StateManager stateManager) { - return new SpecialFieldAction(frame, field, value.getFieldValue().orElse(null), - // if field contains only one value, it has to be nulled - // otherwise, another setting does not empty the field + public SpecialFieldAction getSpecialFieldAction(SpecialFieldValue value, + JabRefFrame frame, + DialogService dialogService, + StateManager stateManager) { + return new SpecialFieldAction( + frame, + field, + value.getFieldValue().orElse(null), + // if field contains only one value, it has to be nulled, as another setting does not empty the field field.getValues().size() == 1, getLocalization(), dialogService, + preferencesService, + undoManager, stateManager); } @@ -79,8 +88,8 @@ public void setSpecialFieldValue(BibEntry bibEntry, SpecialFieldValue value) { value.getFieldValue().orElse(null), bibEntry, getField().isSingleValueField(), - Globals.prefs.getSpecialFieldsPreferences().isKeywordSyncEnabled(), - Globals.prefs.getKeywordDelimiter()); + preferencesService.getSpecialFieldsPreferences().isKeywordSyncEnabled(), + preferencesService.getKeywordDelimiter()); for (FieldChange change : changes) { undoManager.addEdit(new UndoableFieldChange(change)); diff --git a/src/main/java/org/jabref/gui/util/FieldsUtil.java b/src/main/java/org/jabref/gui/util/FieldsUtil.java index 11e23e6ed0b..a6d6afe03f8 100644 --- a/src/main/java/org/jabref/gui/util/FieldsUtil.java +++ b/src/main/java/org/jabref/gui/util/FieldsUtil.java @@ -32,7 +32,7 @@ public Field fromString(String string) { public static String getNameWithType(Field field) { if (field instanceof SpecialField) { - return new SpecialFieldViewModel((SpecialField) field, Globals.undoManager).getLocalization() + return new SpecialFieldViewModel((SpecialField) field, Globals.prefs, Globals.undoManager).getLocalization() + " (" + Localization.lang("Special") + ")"; } else if (field instanceof IEEEField) { return field.getDisplayName() + " (" + Localization.lang("IEEE") + ")"; diff --git a/src/main/java/org/jabref/model/entry/field/SpecialFieldValue.java b/src/main/java/org/jabref/model/entry/field/SpecialFieldValue.java index 6a4014a4b91..d4c1f8df7d5 100644 --- a/src/main/java/org/jabref/model/entry/field/SpecialFieldValue.java +++ b/src/main/java/org/jabref/model/entry/field/SpecialFieldValue.java @@ -38,20 +38,14 @@ public enum SpecialFieldValue { } public static SpecialFieldValue getRating(int ranking) { - switch (ranking) { - case 1: - return RANK_1; - case 2: - return RANK_2; - case 3: - return RANK_3; - case 4: - return RANK_4; - case 5: - return RANK_5; - default: - throw new UnsupportedOperationException(ranking + "is not a valid ranking"); - } + return switch (ranking) { + case 1 -> RANK_1; + case 2 -> RANK_2; + case 3 -> RANK_3; + case 4 -> RANK_4; + case 5 -> RANK_5; + default -> throw new UnsupportedOperationException(ranking + "is not a valid ranking"); + }; } public Optional getKeyword() { @@ -63,19 +57,13 @@ public Optional getFieldValue() { } public int toRating() { - switch (this) { - case RANK_1: - return 1; - case RANK_2: - return 2; - case RANK_3: - return 3; - case RANK_4: - return 4; - case RANK_5: - return 5; - default: - throw new UnsupportedOperationException(this + "is not a valid ranking"); - } + return switch (this) { + case RANK_1 -> 1; + case RANK_2 -> 2; + case RANK_3 -> 3; + case RANK_4 -> 4; + case RANK_5 -> 5; + default -> throw new UnsupportedOperationException(this + "is not a valid ranking"); + }; } } From 82e555d931ef42aa45273de6723b6887a7c17b06 Mon Sep 17 00:00:00 2001 From: muachilin <32566798+muachilin@users.noreply.github.com> Date: Sat, 7 Nov 2020 06:24:45 +0800 Subject: [PATCH 052/201] Implement Emacs key bindings (#6037) Co-authored-by: Felix Luthman <34520175+felixlut@users.noreply.github.com> Co-authored-by: Tommy Samuelsson Co-authored-by: muachilin <32566798+muachilin@users.noreply.github.com> Co-authored-by: Kristoffer Gunnarsson Co-authored-by: David Stevens Co-authored-by: Carl Christian Snethlage --- CHANGELOG.md | 1 + src/main/java/org/jabref/gui/JabRefGUI.java | 21 ++- .../org/jabref/gui/entryeditor/SourceTab.java | 19 +- .../gui/keyboard/CodeAreaKeyBindings.java | 113 ++++++++++++ .../org/jabref/gui/keyboard/KeyBinding.java | 18 ++ .../gui/keyboard/KeyBindingCategory.java | 5 +- .../gui/keyboard/KeyBindingViewModel.java | 20 +- .../gui/keyboard/KeyBindingsDialog.fxml | 14 +- .../gui/keyboard/KeyBindingsDialogView.java | 19 +- .../keyboard/KeyBindingsDialogViewModel.java | 21 +++ .../gui/keyboard/TextInputKeyBindings.java | 97 ++++++++++ .../presets/BashKeyBindingPreset.java | 42 +++++ .../keyboard/presets/KeyBindingPreset.java | 11 ++ .../logic/util/strings/StringManipulator.java | 174 ++++++++++++++++++ .../model/util/ResultingStringState.java | 11 ++ .../jabref/preferences/JabRefPreferences.java | 7 +- src/main/resources/l10n/JabRef_en.properties | 22 +++ .../util/strings/StringManipulatorTest.java | 157 ++++++++++++++++ 18 files changed, 735 insertions(+), 37 deletions(-) create mode 100644 src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java create mode 100644 src/main/java/org/jabref/gui/keyboard/TextInputKeyBindings.java create mode 100644 src/main/java/org/jabref/gui/keyboard/presets/BashKeyBindingPreset.java create mode 100644 src/main/java/org/jabref/gui/keyboard/presets/KeyBindingPreset.java create mode 100644 src/main/java/org/jabref/logic/util/strings/StringManipulator.java create mode 100644 src/main/java/org/jabref/model/util/ResultingStringState.java create mode 100644 src/test/java/org/jabref/logic/util/strings/StringManipulatorTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fd7bb6f77c..7fd7a2dca8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ to the page field for cases where the page numbers are missing. [#7019](https:// - We added a new fetcher to enable users to search jstor.org [#6627](https://github.com/JabRef/jabref/issues/6627) - We added an error message in the New Entry dialog that is shown in case the fetcher did not find anything . [#7000](https://github.com/JabRef/jabref/issues/7000) - We added a new formatter to output shorthand month format. [#6579](https://github.com/JabRef/jabref/issues/6579) +- We reintroduced emacs/bash-like keybindings. [#6017](https://github.com/JabRef/jabref/issues/6017) ### Changed diff --git a/src/main/java/org/jabref/gui/JabRefGUI.java b/src/main/java/org/jabref/gui/JabRefGUI.java index f9c9d646282..a4dc5d1b284 100644 --- a/src/main/java/org/jabref/gui/JabRefGUI.java +++ b/src/main/java/org/jabref/gui/JabRefGUI.java @@ -8,6 +8,7 @@ import javafx.application.Platform; import javafx.scene.Scene; +import javafx.scene.input.KeyEvent; import javafx.stage.Screen; import javafx.stage.Stage; @@ -16,6 +17,7 @@ import org.jabref.gui.icon.IconTheme; import org.jabref.gui.importer.ParserResultWarningDialog; import org.jabref.gui.importer.actions.OpenDatabaseAction; +import org.jabref.gui.keyboard.TextInputKeyBindings; import org.jabref.gui.shared.SharedDatabaseUIManager; import org.jabref.logic.autosaveandbackup.BackupManager; import org.jabref.logic.importer.OpenDatabase; @@ -86,6 +88,10 @@ private void openWindow(Stage mainStage) { Scene scene = new Scene(root, 800, 800); Globals.prefs.getTheme().installCss(scene); + + // Handle TextEditor key bindings + scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> TextInputKeyBindings.call(scene, event)); + mainStage.setTitle(JabRefFrame.FRAME_TITLE); mainStage.getIcons().addAll(IconTheme.getLogoSetFX()); mainStage.setScene(scene); @@ -219,14 +225,13 @@ private void saveWindowState(Stage mainStage) { */ private void debugLogWindowState(Stage mainStage) { if (LOGGER.isDebugEnabled()) { - StringBuilder debugLogString = new StringBuilder(); - debugLogString.append("SCREEN DATA:"); - debugLogString.append("mainStage.WINDOW_MAXIMISED: ").append(mainStage.isMaximized()).append("\n"); - debugLogString.append("mainStage.POS_X: ").append(mainStage.getX()).append("\n"); - debugLogString.append("mainStage.POS_Y: ").append(mainStage.getY()).append("\n"); - debugLogString.append("mainStage.SIZE_X: ").append(mainStage.getWidth()).append("\n"); - debugLogString.append("mainStages.SIZE_Y: ").append(mainStage.getHeight()).append("\n"); - LOGGER.debug(debugLogString.toString()); + String debugLogString = "SCREEN DATA:" + + "mainStage.WINDOW_MAXIMISED: " + mainStage.isMaximized() + "\n" + + "mainStage.POS_X: " + mainStage.getX() + "\n" + + "mainStage.POS_Y: " + mainStage.getY() + "\n" + + "mainStage.SIZE_X: " + mainStage.getWidth() + "\n" + + "mainStages.SIZE_Y: " + mainStage.getHeight() + "\n"; + LOGGER.debug(debugLogString); } } diff --git a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java index 66c32a2ad25..9161758a58d 100644 --- a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java @@ -18,6 +18,7 @@ import javafx.scene.control.ContextMenu; import javafx.scene.control.Tooltip; import javafx.scene.input.InputMethodRequests; +import javafx.scene.input.KeyEvent; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; @@ -26,6 +27,7 @@ import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.actions.StandardActions; import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.keyboard.CodeAreaKeyBindings; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.undo.CountingUndoManager; import org.jabref.gui.undo.NamedCompound; @@ -85,18 +87,10 @@ public EditAction(StandardActions command) { @Override public void execute() { switch (command) { - case COPY: - codeArea.copy(); - break; - case CUT: - codeArea.cut(); - break; - case PASTE: - codeArea.paste(); - break; - case SELECT_ALL: - codeArea.selectAll(); - break; + case COPY -> codeArea.copy(); + case CUT -> codeArea.cut(); + case PASTE -> codeArea.paste(); + case SELECT_ALL -> codeArea.selectAll(); } codeArea.requestFocus(); } @@ -178,6 +172,7 @@ private void setupSourceEditor() { } }); codeArea.setId("bibtexSourceCodeArea"); + codeArea.addEventFilter(KeyEvent.KEY_PRESSED, event -> CodeAreaKeyBindings.call(codeArea, event)); ActionFactory factory = new ActionFactory(keyBindingRepository); ContextMenu contextMenu = new ContextMenu(); diff --git a/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java b/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java new file mode 100644 index 00000000000..d055eaed84c --- /dev/null +++ b/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java @@ -0,0 +1,113 @@ +package org.jabref.gui.keyboard; + +import javafx.scene.input.KeyEvent; + +import org.jabref.gui.Globals; +import org.jabref.logic.util.strings.StringManipulator; +import org.jabref.model.util.ResultingStringState; + +import org.fxmisc.richtext.CodeArea; +import org.fxmisc.richtext.NavigationActions; + +public class CodeAreaKeyBindings { + + public static void call(CodeArea codeArea, KeyEvent event) { + KeyBindingRepository keyBindingRepository = Globals.getKeyPrefs(); + keyBindingRepository.mapToKeyBinding(event).ifPresent(binding -> { + switch (binding) { + case EDITOR_DELETE -> { + codeArea.deleteNextChar(); + event.consume(); + } + case EDITOR_BACKWARD -> { + codeArea.previousChar(NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_FORWARD -> { + codeArea.nextChar(NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_WORD_BACKWARD -> { + codeArea.wordBreaksBackwards(2, NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_WORD_FORWARD -> { + codeArea.wordBreaksForwards(2, NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_BEGINNING_DOC -> { + codeArea.start(NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_UP -> { + codeArea.paragraphStart(NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_BEGINNING -> { + codeArea.lineStart(NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_END_DOC -> { + codeArea.end(NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_DOWN -> { + codeArea.paragraphEnd(NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_END -> { + codeArea.lineEnd(NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_CAPITALIZE -> { + int pos = codeArea.getCaretPosition(); + String text = codeArea.getText(0, codeArea.getText().length()); + ResultingStringState res = StringManipulator.capitalize(pos, text); + codeArea.replaceText(res.text); + codeArea.displaceCaret(res.caretPosition); + event.consume(); + } + case EDITOR_LOWERCASE -> { + int pos = codeArea.getCaretPosition(); + String text = codeArea.getText(0, codeArea.getText().length()); + ResultingStringState res = StringManipulator.lowercase(pos, text); + codeArea.replaceText(res.text); + codeArea.displaceCaret(res.caretPosition); + event.consume(); + } + case EDITOR_UPPERCASE -> { + int pos = codeArea.getCaretPosition(); + String text = codeArea.getText(0, codeArea.getText().length()); + ResultingStringState res = StringManipulator.uppercase(pos, text); + codeArea.clear(); + codeArea.replaceText(res.text); + codeArea.displaceCaret(res.caretPosition); + event.consume(); + } + case EDITOR_KILL_LINE -> { + int pos = codeArea.getCaretPosition(); + codeArea.replaceText(codeArea.getText(0, pos)); + codeArea.displaceCaret(pos); + event.consume(); + } + case EDITOR_KILL_WORD -> { + int pos = codeArea.getCaretPosition(); + String text = codeArea.getText(0, codeArea.getText().length()); + ResultingStringState res = StringManipulator.killWord(pos, text); + codeArea.replaceText(res.text); + codeArea.displaceCaret(res.caretPosition); + event.consume(); + } + case EDITOR_KILL_WORD_BACKWARD -> { + int pos = codeArea.getCaretPosition(); + String text = codeArea.getText(0, codeArea.getText().length()); + ResultingStringState res = StringManipulator.backwardKillWord(pos, text); + codeArea.replaceText(res.text); + codeArea.displaceCaret(res.caretPosition); + event.consume(); + } + } + }); + } +} + diff --git a/src/main/java/org/jabref/gui/keyboard/KeyBinding.java b/src/main/java/org/jabref/gui/keyboard/KeyBinding.java index 06bac3e5c89..4c3363c9c6e 100644 --- a/src/main/java/org/jabref/gui/keyboard/KeyBinding.java +++ b/src/main/java/org/jabref/gui/keyboard/KeyBinding.java @@ -3,6 +3,24 @@ import org.jabref.logic.l10n.Localization; public enum KeyBinding { + EDITOR_DELETE("Delete", Localization.lang("Delete text"), "", KeyBindingCategory.EDITOR), + // DELETE BACKWARDS = Rubout + EDITOR_BACKWARD("Move caret left", Localization.lang("Move caret left"), "", KeyBindingCategory.EDITOR), + EDITOR_FORWARD("Move caret right", Localization.lang("Move caret right"), "", KeyBindingCategory.EDITOR), + EDITOR_WORD_BACKWARD("Move caret to previous word", Localization.lang("Move caret to previous word"), "", KeyBindingCategory.EDITOR), + EDITOR_WORD_FORWARD("Move caret to next word", Localization.lang("Move caret to next word"), "", KeyBindingCategory.EDITOR), + EDITOR_BEGINNING("Move caret to beginning of line", Localization.lang("Move caret to beginning of line"), "", KeyBindingCategory.EDITOR), + EDITOR_END("Move caret to of line", Localization.lang("Move caret to end of line"), "", KeyBindingCategory.EDITOR), + EDITOR_BEGINNING_DOC("Move caret to beginning of text", Localization.lang("Move the caret to the beginning of text"), "", KeyBindingCategory.EDITOR), + EDITOR_END_DOC("Move caret to end of text", Localization.lang("Move the caret to the end of text"), "", KeyBindingCategory.EDITOR), + EDITOR_UP("Move caret up", Localization.lang("Move the caret up"), "", KeyBindingCategory.EDITOR), + EDITOR_DOWN("Move caret down", Localization.lang("Move the caret down"), "", KeyBindingCategory.EDITOR), + EDITOR_CAPITALIZE("Capitalize word", Localization.lang("Capitalize current word"), "", KeyBindingCategory.EDITOR), + EDITOR_LOWERCASE("Lowercase word", Localization.lang("Make current word lowercase"), "", KeyBindingCategory.EDITOR), + EDITOR_UPPERCASE("Uppercase word", Localization.lang("Make current word uppercase"), "", KeyBindingCategory.EDITOR), + EDITOR_KILL_LINE("Remove all characters caret to end of line", Localization.lang("Remove line after caret"), "", KeyBindingCategory.EDITOR), + EDITOR_KILL_WORD("Remove characters until next word", Localization.lang("Remove characters until next word"), "", KeyBindingCategory.EDITOR), + EDITOR_KILL_WORD_BACKWARD("Characters until previous word", Localization.lang("Remove the current word backwards"), "", KeyBindingCategory.EDITOR), ABBREVIATE("Abbreviate", Localization.lang("Abbreviate journal names"), "ctrl+alt+A", KeyBindingCategory.TOOLS), AUTOGENERATE_CITATION_KEYS("Autogenerate citation keys", Localization.lang("Autogenerate citation keys"), "ctrl+G", KeyBindingCategory.QUALITY), diff --git a/src/main/java/org/jabref/gui/keyboard/KeyBindingCategory.java b/src/main/java/org/jabref/gui/keyboard/KeyBindingCategory.java index 4f8e080b617..ae8cb0b673f 100644 --- a/src/main/java/org/jabref/gui/keyboard/KeyBindingCategory.java +++ b/src/main/java/org/jabref/gui/keyboard/KeyBindingCategory.java @@ -11,11 +11,12 @@ public enum KeyBindingCategory { VIEW(Localization.lang("View")), BIBTEX(BibDatabaseMode.BIBTEX.getFormattedName()), QUALITY(Localization.lang("Quality")), - TOOLS(Localization.lang("Tools")); + TOOLS(Localization.lang("Tools")), + EDITOR(Localization.lang("Text editor")); private final String name; - private KeyBindingCategory(String name) { + KeyBindingCategory(String name) { this.name = name; } diff --git a/src/main/java/org/jabref/gui/keyboard/KeyBindingViewModel.java b/src/main/java/org/jabref/gui/keyboard/KeyBindingViewModel.java index 9ece69df0dd..92ff3bb95f6 100644 --- a/src/main/java/org/jabref/gui/keyboard/KeyBindingViewModel.java +++ b/src/main/java/org/jabref/gui/keyboard/KeyBindingViewModel.java @@ -64,11 +64,11 @@ public String getBinding() { private void setBinding(String bind) { this.realBinding = bind; String[] parts = bind.split(" "); - String displayBind = ""; + StringBuilder displayBind = new StringBuilder(); for (String part : parts) { - displayBind += CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, part) + " "; + displayBind.append(CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, part)).append(" "); } - this.shownBinding.set(displayBind.trim().replace(" ", " + ")); + this.shownBinding.set(displayBind.toString().trim().replace(" ", " + ")); } private void setDisplayName() { @@ -135,7 +135,19 @@ public void resetToDefault() { } } - public Optional getIcon() { + public void clear() { + if (!isCategory()) { + String key = getKeyBinding().getConstant(); + keyBindingRepository.put(key, ""); + setBinding(keyBindingRepository.get(key)); + } + } + + public Optional getResetIcon() { + return isCategory() ? Optional.empty() : Optional.of(IconTheme.JabRefIcons.REFRESH); + } + + public Optional getClearIcon() { return isCategory() ? Optional.empty() : Optional.of(IconTheme.JabRefIcons.CLEANUP_ENTRIES); } } diff --git a/src/main/java/org/jabref/gui/keyboard/KeyBindingsDialog.fxml b/src/main/java/org/jabref/gui/keyboard/KeyBindingsDialog.fxml index 99983bb67aa..076e1465146 100644 --- a/src/main/java/org/jabref/gui/keyboard/KeyBindingsDialog.fxml +++ b/src/main/java/org/jabref/gui/keyboard/KeyBindingsDialog.fxml @@ -2,23 +2,29 @@ + + - + - - - + + + + + + + diff --git a/src/main/java/org/jabref/gui/keyboard/KeyBindingsDialogView.java b/src/main/java/org/jabref/gui/keyboard/KeyBindingsDialogView.java index 21170bad2f1..da62dd47d78 100644 --- a/src/main/java/org/jabref/gui/keyboard/KeyBindingsDialogView.java +++ b/src/main/java/org/jabref/gui/keyboard/KeyBindingsDialogView.java @@ -4,6 +4,8 @@ import javafx.fxml.FXML; import javafx.scene.control.ButtonType; +import javafx.scene.control.MenuButton; +import javafx.scene.control.MenuItem; import javafx.scene.control.SelectionMode; import javafx.scene.control.SelectionModel; import javafx.scene.control.TreeItem; @@ -12,6 +14,7 @@ import org.jabref.gui.DialogService; import org.jabref.gui.icon.JabRefIcon; +import org.jabref.gui.keyboard.presets.KeyBindingPreset; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.ControlHelper; import org.jabref.gui.util.RecursiveTreeItem; @@ -30,6 +33,8 @@ public class KeyBindingsDialogView extends BaseDialog { @FXML private TreeTableColumn actionColumn; @FXML private TreeTableColumn shortcutColumn; @FXML private TreeTableColumn resetColumn; + @FXML private TreeTableColumn clearColumn; + @FXML private MenuButton presetsButton; @Inject private KeyBindingRepository keyBindingRepository; @Inject private DialogService dialogService; @@ -66,9 +71,21 @@ private void initialize() { actionColumn.setCellValueFactory(cellData -> cellData.getValue().getValue().nameProperty()); shortcutColumn.setCellValueFactory(cellData -> cellData.getValue().getValue().shownBindingProperty()); new ViewModelTreeTableCellFactory() - .withGraphic(keyBinding -> keyBinding.getIcon().map(JabRefIcon::getGraphicNode).orElse(null)) + .withGraphic(keyBinding -> keyBinding.getResetIcon().map(JabRefIcon::getGraphicNode).orElse(null)) .withOnMouseClickedEvent(keyBinding -> evt -> keyBinding.resetToDefault()) .install(resetColumn); + new ViewModelTreeTableCellFactory() + .withGraphic(keyBinding -> keyBinding.getClearIcon().map(JabRefIcon::getGraphicNode).orElse(null)) + .withOnMouseClickedEvent(keyBinding -> evt -> keyBinding.clear()) + .install(clearColumn); + + viewModel.keyBindingPresets().forEach(preset -> presetsButton.getItems().add(createMenuItem(preset))); + } + + private MenuItem createMenuItem(KeyBindingPreset preset) { + MenuItem item = new MenuItem(preset.getName()); + item.setOnAction((event) -> viewModel.loadPreset(preset)); + return item; } @FXML diff --git a/src/main/java/org/jabref/gui/keyboard/KeyBindingsDialogViewModel.java b/src/main/java/org/jabref/gui/keyboard/KeyBindingsDialogViewModel.java index 6125b01230a..e88c9c7e8ba 100644 --- a/src/main/java/org/jabref/gui/keyboard/KeyBindingsDialogViewModel.java +++ b/src/main/java/org/jabref/gui/keyboard/KeyBindingsDialogViewModel.java @@ -3,8 +3,11 @@ import java.util.Objects; import java.util.Optional; +import javafx.beans.property.ListProperty; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleListProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; import javafx.scene.control.Alert; import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; @@ -12,6 +15,8 @@ import org.jabref.gui.AbstractViewModel; import org.jabref.gui.DialogService; +import org.jabref.gui.keyboard.presets.BashKeyBindingPreset; +import org.jabref.gui.keyboard.presets.KeyBindingPreset; import org.jabref.gui.util.OptionalObjectProperty; import org.jabref.logic.l10n.Localization; import org.jabref.preferences.PreferencesService; @@ -22,6 +27,8 @@ public class KeyBindingsDialogViewModel extends AbstractViewModel { private final PreferencesService preferences; private final OptionalObjectProperty selectedKeyBinding = OptionalObjectProperty.empty(); private final ObjectProperty rootKeyBinding = new SimpleObjectProperty<>(); + private final ListProperty keyBindingPresets = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final DialogService dialogService; public KeyBindingsDialogViewModel(KeyBindingRepository keyBindingRepository, DialogService dialogService, PreferencesService preferences) { @@ -29,6 +36,7 @@ public KeyBindingsDialogViewModel(KeyBindingRepository keyBindingRepository, Dia this.dialogService = Objects.requireNonNull(dialogService); this.preferences = Objects.requireNonNull(preferences); populateTable(); + keyBindingPresets.add(new BashKeyBindingPreset()); } public OptionalObjectProperty selectedKeyBindingProperty() { @@ -94,4 +102,17 @@ public void resetToDefault() { } }); } + + public void loadPreset(KeyBindingPreset preset) { + if (preset == null) { + return; + } + + preset.getKeyBindings().forEach(keyBindingRepository::put); + populateTable(); + } + + public ListProperty keyBindingPresets() { + return keyBindingPresets; + } } diff --git a/src/main/java/org/jabref/gui/keyboard/TextInputKeyBindings.java b/src/main/java/org/jabref/gui/keyboard/TextInputKeyBindings.java new file mode 100644 index 00000000000..d195f0bdef8 --- /dev/null +++ b/src/main/java/org/jabref/gui/keyboard/TextInputKeyBindings.java @@ -0,0 +1,97 @@ +package org.jabref.gui.keyboard; + +import javafx.scene.Scene; +import javafx.scene.control.TextInputControl; +import javafx.scene.input.KeyEvent; + +import org.jabref.gui.Globals; +import org.jabref.logic.util.strings.StringManipulator; +import org.jabref.model.util.ResultingStringState; + +public class TextInputKeyBindings { + + public static void call(Scene scene, KeyEvent event) { + if (scene.focusOwnerProperty().get() instanceof TextInputControl) { + KeyBindingRepository keyBindingRepository = Globals.getKeyPrefs(); + TextInputControl focusedTextField = (TextInputControl) scene.focusOwnerProperty().get(); + keyBindingRepository.mapToKeyBinding(event).ifPresent(binding -> { + switch (binding) { + case EDITOR_DELETE -> { + focusedTextField.deleteNextChar(); + event.consume(); + } + case EDITOR_BACKWARD -> { + focusedTextField.backward(); + event.consume(); + } + case EDITOR_FORWARD -> { + focusedTextField.forward(); + event.consume(); + } + case EDITOR_WORD_BACKWARD -> { + focusedTextField.previousWord(); + event.consume(); + } + case EDITOR_WORD_FORWARD -> { + focusedTextField.nextWord(); + event.consume(); + } + case EDITOR_BEGINNING, EDITOR_UP, EDITOR_BEGINNING_DOC -> { + focusedTextField.home(); + event.consume(); + } + case EDITOR_END, EDITOR_DOWN, EDITOR_END_DOC -> { + focusedTextField.end(); + event.consume(); + } + case EDITOR_CAPITALIZE -> { + int pos = focusedTextField.getCaretPosition(); + String text = focusedTextField.getText(0, focusedTextField.getText().length()); + ResultingStringState res = StringManipulator.capitalize(pos, text); + focusedTextField.setText(res.text); + focusedTextField.positionCaret(res.caretPosition); + event.consume(); + } + case EDITOR_LOWERCASE -> { + int pos = focusedTextField.getCaretPosition(); + String text = focusedTextField.getText(0, focusedTextField.getText().length()); + ResultingStringState res = StringManipulator.lowercase(pos, text); + focusedTextField.setText(res.text); + focusedTextField.positionCaret(res.caretPosition); + event.consume(); + } + case EDITOR_UPPERCASE -> { + int pos = focusedTextField.getCaretPosition(); + String text = focusedTextField.getText(0, focusedTextField.getText().length()); + ResultingStringState res = StringManipulator.uppercase(pos, text); + focusedTextField.setText(res.text); + focusedTextField.positionCaret(res.caretPosition); + event.consume(); + } + case EDITOR_KILL_LINE -> { + int pos = focusedTextField.getCaretPosition(); + focusedTextField.setText(focusedTextField.getText(0, pos)); + focusedTextField.positionCaret(pos); + event.consume(); + } + case EDITOR_KILL_WORD -> { + int pos = focusedTextField.getCaretPosition(); + String text = focusedTextField.getText(0, focusedTextField.getText().length()); + ResultingStringState res = StringManipulator.killWord(pos, text); + focusedTextField.setText(res.text); + focusedTextField.positionCaret(res.caretPosition); + event.consume(); + } + case EDITOR_KILL_WORD_BACKWARD -> { + int pos = focusedTextField.getCaretPosition(); + String text = focusedTextField.getText(0, focusedTextField.getText().length()); + ResultingStringState res = StringManipulator.backwardKillWord(pos, text); + focusedTextField.setText(res.text); + focusedTextField.positionCaret(res.caretPosition); + event.consume(); + } + } + }); + } + } +} diff --git a/src/main/java/org/jabref/gui/keyboard/presets/BashKeyBindingPreset.java b/src/main/java/org/jabref/gui/keyboard/presets/BashKeyBindingPreset.java new file mode 100644 index 00000000000..15528af1023 --- /dev/null +++ b/src/main/java/org/jabref/gui/keyboard/presets/BashKeyBindingPreset.java @@ -0,0 +1,42 @@ +package org.jabref.gui.keyboard.presets; + +import java.util.HashMap; +import java.util.Map; + +import org.jabref.gui.keyboard.KeyBinding; + +public class BashKeyBindingPreset implements KeyBindingPreset { + + private static final Map KEY_BINDINGS = new HashMap<>(); + + static { + KEY_BINDINGS.put(KeyBinding.EDITOR_DELETE, "ctrl+D"); + // DELETE BACKWARDS = Rubout + KEY_BINDINGS.put(KeyBinding.EDITOR_BACKWARD, "ctrl+B"); + KEY_BINDINGS.put(KeyBinding.EDITOR_FORWARD, "ctrl+F"); + KEY_BINDINGS.put(KeyBinding.EDITOR_WORD_BACKWARD, "alt+B"); + KEY_BINDINGS.put(KeyBinding.EDITOR_WORD_FORWARD, "alt+F"); + KEY_BINDINGS.put(KeyBinding.EDITOR_BEGINNING, "ctrl+A"); + KEY_BINDINGS.put(KeyBinding.EDITOR_END, "ctrl+E"); + KEY_BINDINGS.put(KeyBinding.EDITOR_BEGINNING_DOC, "alt+LESS"); + KEY_BINDINGS.put(KeyBinding.EDITOR_END_DOC, "alt+shift+LESS"); + KEY_BINDINGS.put(KeyBinding.EDITOR_UP, "ctrl+P"); + KEY_BINDINGS.put(KeyBinding.EDITOR_DOWN, "ctrl+N"); + KEY_BINDINGS.put(KeyBinding.EDITOR_CAPITALIZE, "alt+C"); + KEY_BINDINGS.put(KeyBinding.EDITOR_LOWERCASE, "alt+L"); + KEY_BINDINGS.put(KeyBinding.EDITOR_UPPERCASE, "alt+U"); + KEY_BINDINGS.put(KeyBinding.EDITOR_KILL_LINE, "ctrl+K"); + KEY_BINDINGS.put(KeyBinding.EDITOR_KILL_WORD, "alt+D"); + KEY_BINDINGS.put(KeyBinding.EDITOR_KILL_WORD_BACKWARD, "alt+DELETE"); + } + + @Override + public String getName() { + return "Bash"; + } + + @Override + public Map getKeyBindings() { + return KEY_BINDINGS; + } +} diff --git a/src/main/java/org/jabref/gui/keyboard/presets/KeyBindingPreset.java b/src/main/java/org/jabref/gui/keyboard/presets/KeyBindingPreset.java new file mode 100644 index 00000000000..abea89b1c16 --- /dev/null +++ b/src/main/java/org/jabref/gui/keyboard/presets/KeyBindingPreset.java @@ -0,0 +1,11 @@ +package org.jabref.gui.keyboard.presets; + +import java.util.Map; + +import org.jabref.gui.keyboard.KeyBinding; + +public interface KeyBindingPreset { + String getName(); + + Map getKeyBindings(); +} diff --git a/src/main/java/org/jabref/logic/util/strings/StringManipulator.java b/src/main/java/org/jabref/logic/util/strings/StringManipulator.java new file mode 100644 index 00000000000..cb1972de5e1 --- /dev/null +++ b/src/main/java/org/jabref/logic/util/strings/StringManipulator.java @@ -0,0 +1,174 @@ +package org.jabref.logic.util.strings; + +import org.jabref.logic.formatter.casechanger.CapitalizeFormatter; +import org.jabref.logic.formatter.casechanger.LowerCaseFormatter; +import org.jabref.logic.formatter.casechanger.UpperCaseFormatter; +import org.jabref.model.util.ResultingStringState; + +public class StringManipulator { + private enum LetterCase { + UPPER, + LOWER, + CAPITALIZED + } + + enum Direction { + NEXT(1), + PREVIOUS(-1); + + public final int OFFSET; + + Direction(int offset) { + this.OFFSET = offset; + } + } + + /** + * Change word casing in a string from the given position to the next word boundary. + * + * @param text The text to manipulate. + * @param caretPosition The index to start from. + * @param targetCase The case mode the string should be changed to. + * + * @return The resulting text and caret position. + */ + private static ResultingStringState setWordCase(String text, int caretPosition, LetterCase targetCase) { + int nextWordBoundary = getNextWordBoundary(caretPosition, text, Direction.NEXT); + + // Preserve whitespaces + int wordStartPosition = caretPosition; + while (wordStartPosition < nextWordBoundary && Character.isWhitespace(text.charAt(wordStartPosition))) { + wordStartPosition++; + } + + String result = switch (targetCase) { + case UPPER -> (new UpperCaseFormatter()).format(text.substring(wordStartPosition, nextWordBoundary)); + case LOWER -> (new LowerCaseFormatter()).format(text.substring(wordStartPosition, nextWordBoundary)); + case CAPITALIZED -> (new CapitalizeFormatter()).format(text.substring(wordStartPosition, nextWordBoundary)); + }; + + return new ResultingStringState( + nextWordBoundary, + text.substring(0, wordStartPosition) + result + text.substring(nextWordBoundary)); + } + + /** + * Delete all characters in a string from the given position to the next word boundary. + * + * @param caretPosition The index to start from. + * @param text The text to manipulate. + * @param direction The direction to search. + * + * @return The resulting text and caret position. + */ + static ResultingStringState deleteUntilWordBoundary(int caretPosition, String text, Direction direction) { + // Define cutout range + int nextWordBoundary = getNextWordBoundary(caretPosition, text, direction); + + // Construct new string without cutout + return switch (direction) { + case NEXT -> new ResultingStringState( + caretPosition, + text.substring(0, caretPosition) + text.substring(nextWordBoundary)); + case PREVIOUS -> new ResultingStringState( + nextWordBoundary, + text.substring(0, nextWordBoundary) + text.substring(caretPosition)); + }; + } + + /** + * Utility method to find the next whitespace position in string after text + * @param caretPosition The current caret Position + * @param text The string to search in + * @param direction The direction to move through string + * + * @return The position of the next whitespace after a word + */ + static int getNextWordBoundary(int caretPosition, String text, Direction direction) { + int i = caretPosition; + + if (direction == Direction.PREVIOUS) { + // Swallow whitespaces + while (i > 0 && Character.isWhitespace((text.charAt(i + direction.OFFSET)))) { + i += direction.OFFSET; + } + + // Read next word + while (i > 0 && !Character.isWhitespace(text.charAt(i + direction.OFFSET))) { + i += direction.OFFSET; + } + } else if (direction == Direction.NEXT) { + // Swallow whitespaces + while (i < text.length() && Character.isWhitespace(text.charAt(i))) { + i += direction.OFFSET; + } + + // Read next word + while (i < text.length() && !Character.isWhitespace((text.charAt(i)))) { + i += direction.OFFSET; + } + } + + return i; + } + + /** + * Capitalize the word on the right side of the cursor. + * + * @param caretPosition The position of the cursor + * @param text The string to manipulate + * + * @return String The resulting text and caret position. + */ + public static ResultingStringState capitalize(int caretPosition, String text) { + return setWordCase(text, caretPosition, LetterCase.CAPITALIZED); + } + + /** + * Make all characters in the word uppercase. + * + * @param caretPosition The position of the cursor + * @param text The string to manipulate + * + * @return String The resulting text and caret position. + */ + public static ResultingStringState uppercase(int caretPosition, String text) { + return setWordCase(text, caretPosition, LetterCase.UPPER); + } + + /** + * Make all characters in the word lowercase. + * + * @param caretPosition The position of the cursor + * @param text The string to manipulate + * + * @return String The resulting text and caret position. + */ + public static ResultingStringState lowercase(int caretPosition, String text) { + return setWordCase(text, caretPosition, LetterCase.LOWER); + } + + /** + * Remove the next word on the right side of the cursor. + * + * @param caretPosition The position of the cursor + * @param text The string to manipulate + * + * @return String The resulting text and caret position. + */ + public static ResultingStringState killWord(int caretPosition, String text) { + return deleteUntilWordBoundary(caretPosition, text, Direction.NEXT); + } + + /** + * Remove the previous word on the left side of the cursor. + * + * @param caretPosition The position of the cursor + * @param text The string to manipulate + * + * @return String The resulting text and caret position. + */ + public static ResultingStringState backwardKillWord(int caretPosition, String text) { + return deleteUntilWordBoundary(caretPosition, text, Direction.PREVIOUS); + } +} diff --git a/src/main/java/org/jabref/model/util/ResultingStringState.java b/src/main/java/org/jabref/model/util/ResultingStringState.java new file mode 100644 index 00000000000..fc248cf7f8e --- /dev/null +++ b/src/main/java/org/jabref/model/util/ResultingStringState.java @@ -0,0 +1,11 @@ +package org.jabref.model.util; + +public class ResultingStringState { + public final int caretPosition; + public final String text; + + public ResultingStringState(int caretPosition, String text) { + this.caretPosition = caretPosition; + this.text = text; + } +} diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 691c666ea52..ed0c1ad6995 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -172,9 +172,7 @@ public class JabRefPreferences implements PreferencesService { public static final String IMPORT_WORKING_DIRECTORY = "importWorkingDirectory"; public static final String EXPORT_WORKING_DIRECTORY = "exportWorkingDirectory"; public static final String WORKING_DIRECTORY = "workingDirectory"; - public static final String EDITOR_EMACS_KEYBINDINGS = "editorEMACSkeyBindings"; - public static final String EDITOR_EMACS_KEYBINDINGS_REBIND_CA = "editorEMACSkeyBindingsRebindCA"; - public static final String EDITOR_EMACS_KEYBINDINGS_REBIND_CF = "editorEMACSkeyBindingsRebindCF"; + public static final String GROUPS_DEFAULT_FIELD = "groupsDefaultField"; public static final String KEYWORD_SEPARATOR = "groupKeywordSeparator"; public static final String AUTO_ASSIGN_GROUP = "autoAssignGroup"; @@ -524,9 +522,6 @@ private JabRefPreferences() { defaults.put(SEND_OS_DATA, Boolean.FALSE); defaults.put(SEND_TIMEZONE_DATA, Boolean.FALSE); defaults.put(VALIDATE_IN_ENTRY_EDITOR, Boolean.TRUE); - defaults.put(EDITOR_EMACS_KEYBINDINGS, Boolean.FALSE); - defaults.put(EDITOR_EMACS_KEYBINDINGS_REBIND_CA, Boolean.TRUE); - defaults.put(EDITOR_EMACS_KEYBINDINGS_REBIND_CF, Boolean.TRUE); defaults.put(AUTO_COMPLETE, Boolean.FALSE); defaults.put(AUTOCOMPLETER_FIRSTNAME_MODE, AutoCompleteFirstNameMode.BOTH.name()); defaults.put(AUTOCOMPLETER_FIRST_LAST, Boolean.FALSE); // "Autocomplete names in 'Firstname Lastname' format only" diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index bbb9e007210..61ed00ff4cb 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2093,6 +2093,26 @@ Required=Required Entry\ type\ cannot\ be\ empty.\ Please\ enter\ a\ name.=Entry type cannot be empty. Please enter a name. Field\ cannot\ be\ empty.\ Please\ enter\ a\ name.=Field cannot be empty. Please enter a name. +Capitalize\ current\ word=Capitalize current word +Delete\ text=Delete text +Make\ current\ word\ lowercase=Make current word lowercase +Make\ current\ word\ uppercase=Make current word uppercase +Move\ caret\ left=Move caret left +Move\ caret\ right=Move caret right +Move\ caret\ to\ previous\ word=Move caret to previous word +Move\ caret\ to\ next\ word=Move caret to next word +Move\ caret\ to\ beginning\ of\ line=Move caret to beginning of line +Move\ caret\ to\ end\ of\ line=Move caret to end of line +Move\ the\ caret\ down=Move the caret down +Move\ the\ caret\ to\ the\ beginning\ of\ text=Move the caret to the beginning of text +Move\ the\ caret\ to\ the\ end\ of\ text=Move the caret to the end of text +Move\ the\ caret\ up=Move the caret up +Remove\ line\ after\ caret=Remove line after caret +Remove\ characters\ until\ next\ word=Remove characters until next word +Remove\ the\ current\ word\ backwards=Remove the current word backwards + +Text\ editor=Text editor + Search\ ShortScience=Search ShortScience Unable\ to\ open\ ShortScience.=Unable to open ShortScience. @@ -2271,6 +2291,8 @@ The\ query\ cannot\ contain\ a\ year\ and\ year-range\ field.=The query cannot c This\ query\ uses\ unsupported\ fields.=This query uses unsupported fields. This\ query\ uses\ unsupported\ syntax.=This query uses unsupported syntax. +Presets=Presets + Check\ Proxy\ Setting=Check Proxy Setting Check\ connection=Check connection Connection\ failed\!=Connection failed\! diff --git a/src/test/java/org/jabref/logic/util/strings/StringManipulatorTest.java b/src/test/java/org/jabref/logic/util/strings/StringManipulatorTest.java new file mode 100644 index 00000000000..fce0126c0b7 --- /dev/null +++ b/src/test/java/org/jabref/logic/util/strings/StringManipulatorTest.java @@ -0,0 +1,157 @@ +package org.jabref.logic.util.strings; + +import java.util.stream.Stream; + +import org.jabref.model.util.ResultingStringState; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class StringManipulatorTest { + + @Test + public void testCapitalizePreservesNewlines() { + int caretPosition = 5; // Position of the caret, between the two ll in the first hellO" + String input = "hello\n\nhELLO"; + String expectedResult = "hello\n\nHello"; + ResultingStringState textOutput = StringManipulator.capitalize(caretPosition, input); + assertEquals(expectedResult, textOutput.text); + } + + @Test + public void testUppercasePreservesSpace() { + int caretPosition = 3; // Position of the caret, between the two ll in the first hello + String input = "hello hello"; + String expectedResult = "helLO hello"; + ResultingStringState textOutput = StringManipulator.uppercase(caretPosition, input); + assertEquals(expectedResult, textOutput.text); + } + + @Test + public void testUppercasePreservesNewlines() { + int caretPosition = 3; // Position of the caret, between the two ll in the first hello + String input = "hello\nhello"; + String expectedResult = "helLO\nhello"; + ResultingStringState textOutput = StringManipulator.uppercase(caretPosition, input); + assertEquals(expectedResult, textOutput.text); + } + + @Test + public void testUppercasePreservesTab() { + int caretPosition = 3; // Position of the caret, between the two ll in the first hello + String input = "hello\thello"; + String expectedResult = "helLO\thello"; + ResultingStringState textOutput = StringManipulator.uppercase(caretPosition, input); + assertEquals(expectedResult, textOutput.text); + } + + @Test + public void testUppercasePreservesDoubleSpace() { + int caretPosition = 5; // Position of the caret, at the first space + String input = "hello hello"; + String expectedResult = "hello HELLO"; + ResultingStringState textOutput = StringManipulator.uppercase(caretPosition, input); + assertEquals(expectedResult, textOutput.text); + } + + @Test + public void testUppercaseIgnoresTrailingWhitespace() { + int caretPosition = 5; // First space + String input = "hello "; + String expectedResult = "hello "; + ResultingStringState textOutput = StringManipulator.uppercase(caretPosition, input); + assertEquals(expectedResult, textOutput.text); + // Expected caret position is right after the last space, which is index 7 + assertEquals(7, textOutput.caretPosition); + } + + @Test + public void testKillWordTrimsTrailingWhitespace() { + int caretPosition = 5; // First space + String input = "hello "; + String expectedResult = "hello"; + ResultingStringState textOutput = StringManipulator.killWord(caretPosition, input); + assertEquals(expectedResult, textOutput.text); + assertEquals(caretPosition, textOutput.caretPosition); + } + + @Test + public void testBackwardsKillWordTrimsPreceedingWhitespace() { + int caretPosition = 1; // Second space + String input = " hello"; + // One space should be preserved since we are deleting everything preceding the second space. + String expectedResult = " hello"; + ResultingStringState textOutput = StringManipulator.backwardKillWord(caretPosition, input); + assertEquals(expectedResult, textOutput.text); + // The caret should have been moved to the start. + assertEquals(0, textOutput.caretPosition); + } + + @Test + public void testUppercasePreservesMixedSpaceNewLineTab() { + int caretPosition = 5; // Position of the caret, after first hello + String input = "hello \n\thello"; + String expectedResult = "hello \n\tHELLO"; + ResultingStringState textOutput = StringManipulator.uppercase(caretPosition, input); + assertEquals(expectedResult, textOutput.text); + } + + @Test + public void testLowercaseEditsTheNextWord() { + int caretPosition = 5; // Position of the caret, right at the space + String input = "hello HELLO"; + String expectedResult = "hello hello"; + ResultingStringState textOutput = StringManipulator.lowercase(caretPosition, input); + assertEquals(expectedResult, textOutput.text); + } + + @Test + public void testKillWordRemovesFromPositionUpToNextWord() { + int caretPosition = 3; // Position of the caret, between the two "ll in the first hello" + String input = "hello hello"; + String expectedResult = "hel hello"; + ResultingStringState textOutput = StringManipulator.killWord(caretPosition, input); + assertEquals(expectedResult, textOutput.text); + } + + @Test + public void testKillWordRemovesNextWordIfPositionIsInSpace() { + int caretPosition = 5; // Position of the caret, after the first hello" + String input = "hello person"; + String expectedResult = "hello"; + ResultingStringState textOutput = StringManipulator.killWord(caretPosition, input); + assertEquals(expectedResult, textOutput.text); + } + + @Test + public void testKillPreviousWord() { + int caretPosition = 8; + int expectedPosition = 6; + String input = "hello person"; + String expectedResult = "hello rson"; + ResultingStringState result = StringManipulator.backwardKillWord(caretPosition, input); + assertEquals(expectedResult, result.text); + assertEquals(expectedPosition, result.caretPosition); + } + + @ParameterizedTest + @MethodSource("wordBoundaryTestData") + void testGetNextWordBoundary(String text, int caretPosition, int expectedPosition, StringManipulator.Direction direction) { + int result = StringManipulator.getNextWordBoundary(caretPosition, text, direction); + assertEquals(expectedPosition, result); + } + + private static Stream wordBoundaryTestData() { + return Stream.of( + Arguments.of("hello person", 3, 0, StringManipulator.Direction.PREVIOUS), + Arguments.of("hello person", 12, 6, StringManipulator.Direction.PREVIOUS), + Arguments.of("hello person", 0, 0, StringManipulator.Direction.PREVIOUS), + Arguments.of("hello person", 0, 5, StringManipulator.Direction.NEXT), + Arguments.of("hello person", 5, 12, StringManipulator.Direction.NEXT), + Arguments.of("hello person", 12, 12, StringManipulator.Direction.NEXT)); + } +} From 470953548ef61115e585f26e661684a21bceb85d Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Fri, 6 Nov 2020 23:26:03 +0100 Subject: [PATCH 053/201] Add missing authors --- AUTHORS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AUTHORS b/AUTHORS index 4a06dd84643..fadd29f4ed8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -128,6 +128,7 @@ Fancy Zhang Fedor Bezrukov Felix Berger Felix Langner +Felix Luthman Felix Wilke Fernando Santagata ffffatgoose @@ -213,6 +214,7 @@ Kelly Click Koji Yokota KOLANICH Kolja Brix +Kristoffer Gunnarsson Krunoslav Zubrinic Krzysztof A. Kościuszkiewicz Kyle Johnson @@ -275,6 +277,7 @@ Morgan Lovato Moritz Ringler Morten Alver ms111ds +muachilin Muhammad Arsalan Badar Mélanie Tremblay Nadeem Mahmood @@ -388,6 +391,7 @@ Tobias Bouschen Tobias Denkinger Tobias Diez Tom Warnke +Tommy Samuelsson Tomás Morales de Luna Tony K Toralf Senger From df80c132f136795cabc8cc8aab3163241cae565a Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Sat, 7 Nov 2020 11:30:46 +0100 Subject: [PATCH 054/201] Feature/add abstract field to complex search (#7079) --- .../fetcher/WebSearchPaneViewModel.java | 2 +- .../jabref/logic/importer/fetcher/ArXiv.java | 1 + .../importer/fetcher/ComplexSearchQuery.java | 61 +++++++++++++------ .../jabref/logic/importer/fetcher/IEEE.java | 5 +- 4 files changed, 47 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java b/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java index 773289143f5..beafb11c060 100644 --- a/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java +++ b/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java @@ -63,7 +63,7 @@ public WebSearchPaneViewModel(ImportFormatPreferences importPreferences, JabRefF preferences.putInt(JabRefPreferences.SELECTED_FETCHER_INDEX, newIndex); }); - String allowedFields = "((author|journal|title|year|year-range):\\s?)?"; + String allowedFields = "((author|abstract|journal|title|year|year-range):\\s?)?"; // Either a single word, or a phrase with quotes, or a year-range String allowedTermText = "(((\\d{4}-\\d{4})|(\\w+)|(\"\\w+[^\"]*\"))\\s?)+"; queryPattern = Pattern.compile("^(" + allowedFields + allowedTermText + ")+$"); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ArXiv.java b/src/main/java/org/jabref/logic/importer/fetcher/ArXiv.java index 58c6bbc7498..10ab03b13e5 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ArXiv.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ArXiv.java @@ -266,6 +266,7 @@ public List performComplexSearch(ComplexSearchQuery complexSearchQuery List searchTerms = new ArrayList<>(); complexSearchQuery.getAuthors().forEach(author -> searchTerms.add("au:" + author)); complexSearchQuery.getTitlePhrases().forEach(title -> searchTerms.add("ti:" + title)); + complexSearchQuery.getTitlePhrases().forEach(abstr -> searchTerms.add("abs:" + abstr)); complexSearchQuery.getJournal().ifPresent(journal -> searchTerms.add("jr:" + journal)); // Since ArXiv API does not support year search, we ignore the year related terms complexSearchQuery.getToYear().ifPresent(year -> searchTerms.add(year.toString())); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java index c442e302f93..750a6d68e46 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.StringJoiner; import org.jabref.model.strings.StringUtil; @@ -15,15 +16,17 @@ public class ComplexSearchQuery { private final List defaultField; private final List authors; private final List titlePhrases; + private final List abstractPhrases; private final Integer fromYear; private final Integer toYear; private final Integer singleYear; private final String journal; - private ComplexSearchQuery(List defaultField, List authors, List titlePhrases, Integer fromYear, Integer toYear, Integer singleYear, String journal) { + private ComplexSearchQuery(List defaultField, List authors, List titlePhrases, List abstractPhrases, Integer fromYear, Integer toYear, Integer singleYear, String journal) { this.defaultField = defaultField; this.authors = authors; this.titlePhrases = titlePhrases; + this.abstractPhrases = abstractPhrases; this.fromYear = fromYear; // Some APIs do not support, or not fully support, year based search. In these cases, the non applicable parameters are ignored. this.toYear = toYear; @@ -38,6 +41,7 @@ public static ComplexSearchQuery fromTerms(Collection terms) { switch (term.field().toLowerCase()) { case "author" -> builder.author(termText); case "title" -> builder.titlePhrase(termText); + case "abstract" -> builder.abstractPhrase(termText); case "journal" -> builder.journal(termText); case "year" -> builder.singleYear(Integer.valueOf(termText)); case "year-range" -> builder.parseYearRange(termText); @@ -60,6 +64,10 @@ public List getTitlePhrases() { return titlePhrases; } + public List getAbstractPhrases() { + return abstractPhrases; + } + public Optional getFromYear() { return Optional.ofNullable(fromYear); } @@ -101,6 +109,9 @@ public boolean equals(Object o) { if (!(getTitlePhrases().containsAll(that.getTitlePhrases()) && that.getTitlePhrases().containsAll(getTitlePhrases()))) { return false; } + if (!(getAbstractPhrases().containsAll(that.getAbstractPhrases()) && that.getAbstractPhrases().containsAll(getAbstractPhrases()))) { + return false; + } if (getFromYear().isPresent() ? !getFromYear().equals(that.getFromYear()) : that.getFromYear().isPresent()) { return false; } @@ -115,33 +126,30 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = defaultField != null ? defaultField.hashCode() : 0; - result = 31 * result + (getAuthors() != null ? getAuthors().hashCode() : 0); - result = 31 * result + (getTitlePhrases() != null ? getTitlePhrases().hashCode() : 0); - result = 31 * result + (getFromYear().isPresent() ? getFromYear().hashCode() : 0); - result = 31 * result + (getToYear().isPresent() ? getToYear().hashCode() : 0); - result = 31 * result + (getSingleYear().isPresent() ? getSingleYear().hashCode() : 0); - result = 31 * result + (getJournal().isPresent() ? getJournal().hashCode() : 0); - return result; + return Objects.hash(defaultField, getAuthors(), getSingleYear(), getAbstractPhrases(), getFromYear(), getToYear(), getTitlePhrases(), getJournal()); } @Override public String toString() { - StringBuilder stringRepresentation = new StringBuilder(); - getSingleYear().ifPresent(singleYear -> stringRepresentation.append(singleYear).append(" ")); - getFromYear().ifPresent(fromYear -> stringRepresentation.append(fromYear).append(" ")); - getToYear().ifPresent(toYear -> stringRepresentation.append(toYear).append(" ")); - getJournal().ifPresent(journal -> stringRepresentation.append(journal).append(" ")); - stringRepresentation.append(String.join(" ", getTitlePhrases())) - .append(String.join(" ", getDefaultFieldPhrases())) - .append(String.join(" ", getAuthors())); - return stringRepresentation.toString(); + StringJoiner stringJoiner = new StringJoiner(" "); + + getSingleYear().ifPresent(singleYear -> stringJoiner.add(singleYear.toString())); + getFromYear().ifPresent(fromYear -> stringJoiner.add(fromYear.toString())); + getToYear().ifPresent(toYear -> stringJoiner.add(toYear.toString())); + getJournal().ifPresent(stringJoiner::add); + stringJoiner.add(String.join(" ", getTitlePhrases())) + .add(String.join(" ", getDefaultFieldPhrases())) + .add(String.join(" ", getAuthors())) + .add(String.join(" ", getAbstractPhrases())); + + return stringJoiner.toString(); } public static class ComplexSearchQueryBuilder { private List defaultFieldPhrases = new ArrayList<>(); private List authors = new ArrayList<>(); private List titlePhrases = new ArrayList<>(); + private List abstractPhrases = new ArrayList<>(); private String journal; private Integer fromYear; private Integer toYear; @@ -183,6 +191,18 @@ public ComplexSearchQueryBuilder titlePhrase(String titlePhrase) { return this; } + /** + * Adds abstract phrase and wraps it in quotes + */ + public ComplexSearchQueryBuilder abstractPhrase(String abstractPhrase) { + if (Objects.requireNonNull(abstractPhrase).isBlank()) { + throw new IllegalArgumentException("Parameter must not be blank"); + } + // Strip all quotes before wrapping + this.titlePhrases.add(String.format("\"%s\"", abstractPhrase.replace("\"", ""))); + return this; + } + public ComplexSearchQueryBuilder fromYearAndToYear(Integer fromYear, Integer toYear) { if (Objects.nonNull(singleYear)) { throw new IllegalArgumentException("You can not use single year and year range search."); @@ -214,6 +234,7 @@ public ComplexSearchQueryBuilder terms(Collection terms) { switch (term.field().toLowerCase()) { case "author" -> this.author(termText); case "title" -> this.titlePhrase(termText); + case "abstract" -> this.abstractPhrase(termText); case "journal" -> this.journal(termText); case "year" -> this.singleYear(Integer.valueOf(termText)); case "year-range" -> this.parseYearRange(termText); @@ -235,7 +256,7 @@ public ComplexSearchQuery build() throws IllegalStateException { if (textSearchFieldsAndYearFieldsAreEmpty()) { throw new IllegalStateException("At least one text field has to be set"); } - return new ComplexSearchQuery(defaultFieldPhrases, authors, titlePhrases, fromYear, toYear, singleYear, journal); + return new ComplexSearchQuery(defaultFieldPhrases, authors, titlePhrases, abstractPhrases, fromYear, toYear, singleYear, journal); } void parseYearRange(String termText) { @@ -259,7 +280,7 @@ void parseYearRange(String termText) { private boolean textSearchFieldsAndYearFieldsAreEmpty() { return this.stringListIsBlank(defaultFieldPhrases) && this.stringListIsBlank(titlePhrases) && - this.stringListIsBlank(authors) && StringUtil.isBlank(journal) && yearFieldsAreEmpty(); + this.stringListIsBlank(authors) && this.stringListIsBlank(abstractPhrases) && StringUtil.isBlank(journal) && yearFieldsAreEmpty(); } private boolean yearFieldsAreEmpty() { diff --git a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java index 2b1b0a0f33e..d4329fb3d7d 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java @@ -242,7 +242,10 @@ public URL getComplexQueryURL(ComplexSearchQuery complexSearchQuery) throws URIS if (!complexSearchQuery.getAuthors().isEmpty()) { uriBuilder.addParameter("author", String.join(" AND ", complexSearchQuery.getAuthors())); } - if (!complexSearchQuery.getAuthors().isEmpty()) { + if (!complexSearchQuery.getAbstractPhrases().isEmpty()) { + uriBuilder.addParameter("abstract", String.join(" AND ", complexSearchQuery.getAbstractPhrases())); + } + if (!complexSearchQuery.getTitlePhrases().isEmpty()) { uriBuilder.addParameter("article_title", String.join(" AND ", complexSearchQuery.getTitlePhrases())); } complexSearchQuery.getJournal().ifPresent(journalTitle -> uriBuilder.addParameter("publication_title", journalTitle)); From b77fcaafa0216fef42212be775c99b603d860856 Mon Sep 17 00:00:00 2001 From: Jonatan Asketorp <2598631+k3KAW8Pnf7mkmdSMPHz27@users.noreply.github.com> Date: Sun, 8 Nov 2020 16:54:01 -0500 Subject: [PATCH 055/201] Fix only replacing unescaped forward slashes --- src/main/java/org/jabref/gui/preview/PreviewViewer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/src/main/java/org/jabref/gui/preview/PreviewViewer.java index 491768d1dd4..8f6f91364d7 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewViewer.java +++ b/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -144,7 +144,7 @@ private void highlightSearchPattern() { "var markInstance = new Mark(document.getElementById(\"content\"));" + "markInstance.unmark({" + " done: function(){" + - " markInstance.markRegExp(/" + pattern.replaceAll("/", "\\\\/") + "/gmi);" + + " markInstance.markRegExp(/" + pattern.replaceAll("(? Date: Mon, 9 Nov 2020 05:35:47 +0000 Subject: [PATCH 056/201] Bump jakarta.annotation-api from 1.3.5 to 2.0.0 Bumps [jakarta.annotation-api](https://github.com/eclipse-ee4j/common-annotations-api) from 1.3.5 to 2.0.0. - [Release notes](https://github.com/eclipse-ee4j/common-annotations-api/releases) - [Commits](https://github.com/eclipse-ee4j/common-annotations-api/compare/1.3.5...2.0.0) Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e58b659c2a7..28cd3123aed 100644 --- a/build.gradle +++ b/build.gradle @@ -155,7 +155,7 @@ dependencies { exclude module: "jsr305" } - implementation group: 'jakarta.annotation', name: 'jakarta.annotation-api', version: '1.3.5' + implementation group: 'jakarta.annotation', name: 'jakarta.annotation-api', version: '2.0.0' // JavaFX stuff implementation 'de.jensd:fontawesomefx-commons:11.0' From fcc68924caeff3a90a47e538eaca7a4d39d59f5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2020 09:10:36 +0100 Subject: [PATCH 057/201] Bump gittools/actions from v0.9.4 to v0.9.5 (#7091) Bumps [gittools/actions](https://github.com/gittools/actions) from v0.9.4 to v0.9.5. - [Release notes](https://github.com/gittools/actions/releases) - [Commits](https://github.com/gittools/actions/compare/v0.9.4...ea3b60afaadc72494fe5522daa59c7a23cdc6bea) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deployment.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index d8b160f0cac..4ff79211d53 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -49,12 +49,12 @@ jobs: with: fetch-depth: 0 - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v0.9.4 + uses: gittools/actions/gitversion/setup@v0.9.5 with: versionSpec: "5.3.7" - name: Run GitVersion id: gitversion - uses: gittools/actions/gitversion/execute@v0.9.4 + uses: gittools/actions/gitversion/execute@v0.9.5 - name: Set up JDK 15 for linux and mac uses: actions/setup-java@v1 with: @@ -161,12 +161,12 @@ jobs: - name: Fetch all history for all tags and branches run: git fetch --prune --unshallow - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v0.9.4 + uses: gittools/actions/gitversion/setup@v0.9.5 with: versionSpec: '5.2.x' - name: Run GitVersion id: gitversion - uses: gittools/actions/gitversion/execute@v0.9.4 + uses: gittools/actions/gitversion/execute@v0.9.5 - name: Get linux binaries uses: actions/download-artifact@master with: From d7959833018d4551b4b8f979be7e5754cf4d28ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2020 09:11:03 +0100 Subject: [PATCH 058/201] Bump com.github.ben-manes.versions from 0.33.0 to 0.36.0 (#7088) Bumps com.github.ben-manes.versions from 0.33.0 to 0.36.0. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e58b659c2a7..650881efc4f 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ plugins { id 'application' id "com.simonharrer.modernizer" version '2.1.0-1' id 'me.champeau.gradle.jmh' version '0.5.2' - id 'com.github.ben-manes.versions' version '0.33.0' + id 'com.github.ben-manes.versions' version '0.36.0' id 'org.javamodularity.moduleplugin' version '1.7.0' id 'org.openjfx.javafxplugin' version '0.0.9' id 'org.beryx.jlink' version '2.22.1' From b80e4ed87b378696325cc94050e4ac046ad9b502 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2020 09:15:56 +0100 Subject: [PATCH 059/201] Bump unirest-java from 3.11.03 to 3.11.05 (#7087) Bumps [unirest-java](https://github.com/Kong/unirest-java) from 3.11.03 to 3.11.05. - [Release notes](https://github.com/Kong/unirest-java/releases) - [Changelog](https://github.com/Kong/unirest-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/Kong/unirest-java/compare/v3.11.03...v3.11.05) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 650881efc4f..80b3c71c39a 100644 --- a/build.gradle +++ b/build.gradle @@ -170,7 +170,7 @@ dependencies { implementation 'org.controlsfx:controlsfx:11.0.3' implementation 'org.jsoup:jsoup:1.13.1' - implementation 'com.konghq:unirest-java:3.11.03' + implementation 'com.konghq:unirest-java:3.11.05' implementation 'org.slf4j:slf4j-api:2.0.0-alpha1' implementation group: 'org.apache.logging.log4j', name: 'log4j-jcl', version: '3.0.0-SNAPSHOT' From 848da816305f58a1ab60f8a74c7c3f53f0e2c1f2 Mon Sep 17 00:00:00 2001 From: Galileo Sartor Date: Mon, 9 Nov 2020 17:23:10 +0100 Subject: [PATCH 060/201] Add support for Microsoft Edge browser in Windows and Linux builds (#7056) * Add support for Microsoft Edge browser in Windows and Linux builds * Add support for Microsoft Edge browser in Mac builds * Add microsoft edge support in CHANGELOG.md Co-authored-by: Christoph --- CHANGELOG.md | 1 + build.gradle | 2 +- .../chromium/org.jabref.jabref.json | 3 ++- buildres/linux/postinst | 1 + buildres/linux/postrm | 3 ++- .../chromium/org.jabref.jabref.json | 3 ++- buildres/mac/postinstall | 2 ++ buildres/windows/JabRef-post-image.wsf | 2 +- buildres/windows/jabref-chrome.json | 3 ++- .../connect-plug-etc-opt-edge-native-messaging-jabref | 9 +++++++++ .../disconnect-plug-etc-opt-edge-native-messaging-jabref | 7 +++++++ snap/snapcraft.yaml | 6 ++++-- 12 files changed, 34 insertions(+), 8 deletions(-) create mode 100755 snap/hooks/connect-plug-etc-opt-edge-native-messaging-jabref create mode 100755 snap/hooks/disconnect-plug-etc-opt-edge-native-messaging-jabref diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fd7a2dca8e..bab791252dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ to the page field for cases where the page numbers are missing. [#7019](https:// - We added a new fetcher to enable users to search jstor.org [#6627](https://github.com/JabRef/jabref/issues/6627) - We added an error message in the New Entry dialog that is shown in case the fetcher did not find anything . [#7000](https://github.com/JabRef/jabref/issues/7000) - We added a new formatter to output shorthand month format. [#6579](https://github.com/JabRef/jabref/issues/6579) +- We added support for the new Microsoft Edge browser in all platforms. [#7056](https://github.com/JabRef/jabref/pull/7056) - We reintroduced emacs/bash-like keybindings. [#6017](https://github.com/JabRef/jabref/issues/6017) ### Changed diff --git a/build.gradle b/build.gradle index 80b3c71c39a..d46f140be55 100644 --- a/build.gradle +++ b/build.gradle @@ -714,7 +714,7 @@ if (OperatingSystem.current().isWindows()) { tasks.jpackageImage.doLast { copy { from("${projectDir}/buildres/windows") { - include "jabref.json", "jabref-chrome.json", "JabRefHost.bat", "JabRefHost.ps1" + include "jabref-firefox.json", "jabref-chrome.json", "JabRefHost.bat", "JabRefHost.ps1" } into "$buildDir/distribution/JabRef" } diff --git a/buildres/linux/native-messaging-host/chromium/org.jabref.jabref.json b/buildres/linux/native-messaging-host/chromium/org.jabref.jabref.json index 6283016eafe..902c8d50d14 100644 --- a/buildres/linux/native-messaging-host/chromium/org.jabref.jabref.json +++ b/buildres/linux/native-messaging-host/chromium/org.jabref.jabref.json @@ -4,6 +4,7 @@ "path": "/opt/jabref/lib/jabrefHost.py", "type": "stdio", "allowed_origins": [ - "chrome-extension://bifehkofibaamoeaopjglfkddgkijdlh/" + "chrome-extension://bifehkofibaamoeaopjglfkddgkijdlh/", + "chrome-extension://pgkajmkfgbehiomipedjhoddkejohfna/" ] } diff --git a/buildres/linux/postinst b/buildres/linux/postinst index a26e18b43cc..b24ded8eeb1 100644 --- a/buildres/linux/postinst +++ b/buildres/linux/postinst @@ -23,6 +23,7 @@ case "$1" in install -D -m0755 /opt/jabref/lib/native-messaging-host/firefox/org.jabref.jabref.json /usr/lib/mozilla/native-messaging-hosts/org.jabref.jabref.json install -D -m0755 /opt/jabref/lib/native-messaging-host/chromium/org.jabref.jabref.json /etc/chromium/native-messaging-hosts/org.jabref.jabref.json install -D -m0755 /opt/jabref/lib/native-messaging-host/chromium/org.jabref.jabref.json /etc/opt/chrome/native-messaging-hosts/org.jabref.jabref.json + install -D -m0755 /opt/jabref/lib/native-messaging-host/chromium/org.jabref.jabref.json /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json # Trigger an auto-install of the browser addon for chrome/chromium browsers install -D -m0644 /opt/jabref/lib/native-messaging-host/chromium/bifehkofibaamoeaopjglfkddgkijdlh.json /opt/google/chrome/extensions/bifehkofibaamoeaopjglfkddgkijdlh.json install -D -m0644 /opt/jabref/lib/native-messaging-host/chromium/bifehkofibaamoeaopjglfkddgkijdlh.json /usr/share/google-chrome/extensions/bifehkofibaamoeaopjglfkddgkijdlh.json diff --git a/buildres/linux/postrm b/buildres/linux/postrm index 5708fd243d9..abcd727ac76 100644 --- a/buildres/linux/postrm +++ b/buildres/linux/postrm @@ -22,7 +22,8 @@ case "$1" in # Remove the native-messaging hosts script only if relative to the deb package for NATIVE_MESSAGING_JSON in "/usr/lib/mozilla/native-messaging-hosts/org.jabref.jabref.json"\ "/etc/chromium/native-messaging-hosts/org.jabref.jabref.json"\ - "/etc/opt/chrome/native-messaging-hosts/org.jabref.jabref.json"; do + "/etc/opt/chrome/native-messaging-hosts/org.jabref.jabref.json"\ + "/etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json"; do if [ -e $NATIVE_MESSAGING_JSON ] && grep --quiet '"path": "/opt' $NATIVE_MESSAGING_JSON; then rm $NATIVE_MESSAGING_JSON fi diff --git a/buildres/mac/native-messaging-host/chromium/org.jabref.jabref.json b/buildres/mac/native-messaging-host/chromium/org.jabref.jabref.json index 84ce13113d7..e07d6237487 100644 --- a/buildres/mac/native-messaging-host/chromium/org.jabref.jabref.json +++ b/buildres/mac/native-messaging-host/chromium/org.jabref.jabref.json @@ -4,6 +4,7 @@ "path": "/Applications/JabRef.app/Contents/Resources/jabrefHost.py", "type": "stdio", "allowed_origins": [ - "chrome-extension://bifehkofibaamoeaopjglfkddgkijdlh/" + "chrome-extension://bifehkofibaamoeaopjglfkddgkijdlh/", + "chrome-extension://pgkajmkfgbehiomipedjhoddkejohfna/" ] } diff --git a/buildres/mac/postinstall b/buildres/mac/postinstall index 235feb0ca5e..7fd28171458 100755 --- a/buildres/mac/postinstall +++ b/buildres/mac/postinstall @@ -14,5 +14,7 @@ install -d /Library/Application\ Support/Chromium/NativeMessagingHosts/ install -m0755 /Applications/JabRef.app/Contents/Resources/native-messaging-host/chromium/org.jabref.jabref.json /Library/Application\ Support/Chromium/NativeMessagingHosts/org.jabref.jabref.json install -d /Library/Google/Chrome/NativeMessagingHosts/ install -m0755 /Applications/JabRef.app/Contents/Resources/native-messaging-host/chromium/org.jabref.jabref.json /Library/Google/Chrome/NativeMessagingHosts/org.jabref.jabref.json +install -d /Library/Microsoft/Edge/NativeMessagingHosts/ +install -m0755 /Applications/JabRef.app/Contents/Resources/native-messaging-host/chromium/org.jabref.jabref.json /Library/Microsoft/Edge/NativeMessagingHosts/org.jabref.jabref.json exit 0 diff --git a/buildres/windows/JabRef-post-image.wsf b/buildres/windows/JabRef-post-image.wsf index a614c82460f..afdf92ef0ad 100644 --- a/buildres/windows/JabRef-post-image.wsf +++ b/buildres/windows/JabRef-post-image.wsf @@ -21,7 +21,7 @@ wxsFile.Close(); // Add registry values for JabRef Browser Extension - contents = contents.replace("", ""); + contents = contents.replace("", ""); // Specify banner contents = contents.replace("", ""); diff --git a/buildres/windows/jabref-chrome.json b/buildres/windows/jabref-chrome.json index 39dd8d35455..a8d3020cc2d 100644 --- a/buildres/windows/jabref-chrome.json +++ b/buildres/windows/jabref-chrome.json @@ -4,6 +4,7 @@ "path": "JabRefHost.bat", "type": "stdio", "allowed_origins": [ - "chrome-extension://bifehkofibaamoeaopjglfkddgkijdlh/" + "chrome-extension://bifehkofibaamoeaopjglfkddgkijdlh/", + "chrome-extension://pgkajmkfgbehiomipedjhoddkejohfna/" ] } diff --git a/snap/hooks/connect-plug-etc-opt-edge-native-messaging-jabref b/snap/hooks/connect-plug-etc-opt-edge-native-messaging-jabref new file mode 100755 index 00000000000..c3dca7ea72b --- /dev/null +++ b/snap/hooks/connect-plug-etc-opt-edge-native-messaging-jabref @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ ! -d /etc/opt/edge/native-messaging-hosts ]; then + echo "Missing directory, create it manually then try again:" + echo "sudo mkdir -p /etc/opt/edge/native-messaging-hosts" + exit 1 +fi + +cp "$SNAP/lib/native-messaging-host/chromium/org.jabref.jabref.json" /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json diff --git a/snap/hooks/disconnect-plug-etc-opt-edge-native-messaging-jabref b/snap/hooks/disconnect-plug-etc-opt-edge-native-messaging-jabref new file mode 100755 index 00000000000..cfb9af5905a --- /dev/null +++ b/snap/hooks/disconnect-plug-etc-opt-edge-native-messaging-jabref @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ ! -f /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json ]; then + exit 0 +elif grep --quiet '"path": "/snap' /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json; then + rm /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json +fi diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index ba412df5b6f..9e73b004280 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -28,6 +28,10 @@ plugs: interface: system-files write: - /etc/opt/chrome/native-messaging-hosts/org.jabref.jabref.json + etc-opt-edge-native-messaging-jabref: + interface: system-files + write: + - /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json etc-chromium-native-messaging-jabref: interface: system-files write: @@ -48,8 +52,6 @@ environment: parts: jabref: plugin: dump - # source: build/distribution/JabRef-5.2-portable_linux.tar.gz - # Use this source for debug purposes: source: https://builds.jabref.org/master/JabRef-5.2-portable_linux.tar.gz stage-packages: - x11-utils From f1e7edf2d62fa5aeb4316c02a261b92e4499d413 Mon Sep 17 00:00:00 2001 From: Galileo Sartor Date: Tue, 10 Nov 2020 08:03:48 +0100 Subject: [PATCH 061/201] Rename Firefox extension file in Windows (#7092) --- buildres/windows/{jabref.json => jabref-firefox.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename buildres/windows/{jabref.json => jabref-firefox.json} (100%) diff --git a/buildres/windows/jabref.json b/buildres/windows/jabref-firefox.json similarity index 100% rename from buildres/windows/jabref.json rename to buildres/windows/jabref-firefox.json From 8be000d9886bdda7fc0ccd32e534368bf1086a56 Mon Sep 17 00:00:00 2001 From: Christoph Date: Tue, 10 Nov 2020 08:56:10 +0100 Subject: [PATCH 062/201] Fix endnote importer when keyword style is null (#7084) --- CHANGELOG.md | 2 + .../java/org/jabref/gui/DefaultInjector.java | 3 + .../org/jabref/gui/importer/ImportAction.java | 6 +- .../gui/importer/ImportEntriesDialog.java | 4 +- .../gui/importer/ImportEntriesViewModel.java | 21 +++-- .../fileformat/EndnoteXmlImporter.java | 5 +- src/main/resources/l10n/JabRef_en.properties | 2 + ...dnoteXmlImporterTest_EmptyKeywordStyle.bib | 16 ++++ ...dnoteXmlImporterTest_EmptyKeywordStyle.xml | 84 +++++++++++++++++++ 9 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 src/test/resources/org/jabref/logic/importer/fileformat/EndnoteXmlImporterTest_EmptyKeywordStyle.bib create mode 100644 src/test/resources/org/jabref/logic/importer/fileformat/EndnoteXmlImporterTest_EmptyKeywordStyle.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index bab791252dd..034d99986a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,8 @@ inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://git - We fixed an issue where the JabRef GUI does not highlight the "All entries" group on start-up [#6691](https://github.com/JabRef/jabref/issues/6691) - We fixed an issue where a custom dark theme was not applied to the entry preview tab [7068](https://github.com/JabRef/jabref/issues/7068) - We fixed an issue where modifications to the Custom preview layout in the preferences were not saved [#6447](https://github.com/JabRef/jabref/issues/6447) +- We fixed an issue where errors from imports were not shown to the user [#7084](https://github.com/JabRef/jabref/pull/7084) +- We fixed an issue where the EndNote XML Import would fail on empty keywords tags [forum#2387](https://discourse.jabref.org/t/importing-in-unknown-format-fails-to-import-xml-library-from-bookends-export/2387) ### Removed diff --git a/src/main/java/org/jabref/gui/DefaultInjector.java b/src/main/java/org/jabref/gui/DefaultInjector.java index aefc1b4f232..4cb672fa553 100644 --- a/src/main/java/org/jabref/gui/DefaultInjector.java +++ b/src/main/java/org/jabref/gui/DefaultInjector.java @@ -8,6 +8,7 @@ import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.protectedterms.ProtectedTermsLoader; +import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.PreferencesService; @@ -46,6 +47,8 @@ private static Object createDependency(Class clazz) { return Globals.clipboardManager; } else if (clazz == UndoManager.class) { return Globals.undoManager; + } else if (clazz == BibEntryTypesManager.class) { + return Globals.entryTypesManager; } else { try { return clazz.newInstance(); diff --git a/src/main/java/org/jabref/gui/importer/ImportAction.java b/src/main/java/org/jabref/gui/importer/ImportAction.java index 52ea32b30e4..3f042fc31e4 100644 --- a/src/main/java/org/jabref/gui/importer/ImportAction.java +++ b/src/main/java/org/jabref/gui/importer/ImportAction.java @@ -83,7 +83,11 @@ public void automatedImport(List filenames) { frame.addTab(parserResult.getDatabaseContext(), true); dialogService.notify(Localization.lang("Imported entries") + ": " + parserResult.getDatabase().getEntries().size()); }) - .executeWith(taskExecutor); + .onFailure(ex-> { + LOGGER.error("Error importing", ex); + dialogService.notify(Localization.lang("Error importing. See the error log for details.")); + }) + .executeWith(taskExecutor); } else { final LibraryTab libraryTab = frame.getCurrentLibraryTab(); diff --git a/src/main/java/org/jabref/gui/importer/ImportEntriesDialog.java b/src/main/java/org/jabref/gui/importer/ImportEntriesDialog.java index 0d98779b57a..3cd706a19c8 100644 --- a/src/main/java/org/jabref/gui/importer/ImportEntriesDialog.java +++ b/src/main/java/org/jabref/gui/importer/ImportEntriesDialog.java @@ -35,6 +35,7 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.EntryType; import org.jabref.model.entry.types.StandardEntryType; @@ -59,6 +60,7 @@ public class ImportEntriesDialog extends BaseDialog { @Inject private UndoManager undoManager; @Inject private PreferencesService preferences; @Inject private StateManager stateManager; + @Inject private BibEntryTypesManager entryTypesManager; @Inject private FileUpdateMonitor fileUpdateMonitor; private final BibDatabaseContext database; @@ -94,7 +96,7 @@ public ImportEntriesDialog(BibDatabaseContext database, BackgroundTask entries; private final PreferencesService preferences; + private final BibEntryTypesManager entryTypesManager; /** * @param databaseContext the database to import into @@ -57,6 +63,7 @@ public ImportEntriesViewModel(BackgroundTask task, UndoManager undoManager, PreferencesService preferences, StateManager stateManager, + BibEntryTypesManager entryTypesManager, FileUpdateMonitor fileUpdateMonitor) { this.taskExecutor = taskExecutor; this.databaseContext = databaseContext; @@ -64,6 +71,7 @@ public ImportEntriesViewModel(BackgroundTask task, this.undoManager = undoManager; this.preferences = preferences; this.stateManager = stateManager; + this.entryTypesManager = entryTypesManager; this.fileUpdateMonitor = fileUpdateMonitor; this.entries = FXCollections.observableArrayList(); this.message = new SimpleStringProperty(); @@ -74,6 +82,9 @@ public ImportEntriesViewModel(BackgroundTask task, this.parserResult = parserResult; // fill in the list for the user, where one can select the entries to import entries.addAll(parserResult.getDatabase().getEntries()); + }).onFailure(ex -> { + LOGGER.error("Error importing", ex); + dialogService.showErrorDialogAndWait(ex); }).executeWith(taskExecutor); } @@ -91,7 +102,7 @@ public ObservableList getEntries() { public boolean hasDuplicate(BibEntry entry) { return findInternalDuplicate(entry).isPresent() || - new DuplicateCheck(Globals.entryTypesManager) + new DuplicateCheck(entryTypesManager) .containsDuplicate(databaseContext.getDatabase(), entry, databaseContext.getMode()).isPresent(); } @@ -122,7 +133,7 @@ public void importEntries(List entriesToImport, boolean shouldDownload } else { buildImportHandlerThenImportEntries(entriesToImport); } - }).executeWith(Globals.TASK_EXECUTOR); + }).executeWith(taskExecutor); } else { buildImportHandlerThenImportEntries(entriesToImport); } @@ -182,7 +193,7 @@ private Optional findInternalDuplicate(BibEntry entry) { if (othEntry.equals(entry)) { continue; // Don't compare the entry to itself } - if (new DuplicateCheck(Globals.entryTypesManager).isDuplicate(entry, othEntry, databaseContext.getMode())) { + if (new DuplicateCheck(entryTypesManager).isDuplicate(entry, othEntry, databaseContext.getMode())) { return Optional.of(othEntry); } } @@ -191,7 +202,7 @@ private Optional findInternalDuplicate(BibEntry entry) { public void resolveDuplicate(BibEntry entry) { // First, try to find duplicate in the existing library - Optional other = new DuplicateCheck(Globals.entryTypesManager).containsDuplicate(databaseContext.getDatabase(), entry, databaseContext.getMode()); + Optional other = new DuplicateCheck(entryTypesManager).containsDuplicate(databaseContext.getDatabase(), entry, databaseContext.getMode()); if (other.isPresent()) { DuplicateResolverDialog dialog = new DuplicateResolverDialog(other.get(), entry, DuplicateResolverDialog.DuplicateResolverType.INSPECTION, databaseContext, stateManager); diff --git a/src/main/java/org/jabref/logic/importer/fileformat/EndnoteXmlImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/EndnoteXmlImporter.java index 77a79883772..0f673938c9a 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/EndnoteXmlImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/EndnoteXmlImporter.java @@ -269,9 +269,12 @@ private Optional getUrlValue(Url url) { private List getKeywords(Record record) { Keywords keywords = record.getKeywords(); if (keywords != null) { + return keywords.getKeyword() .stream() - .map(keyword -> keyword.getStyle().getContent()) + .map(keyword -> keyword.getStyle()) + .filter(Objects::nonNull) + .map(style->style.getContent()) .collect(Collectors.toList()); } else { return Collections.emptyList(); diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 61ed00ff4cb..b58510e6d5c 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2300,3 +2300,5 @@ Connection\ successful\!=Connection successful\! Generate\ groups\ from\ keywords\ in\ the\ following\ field=Generate groups from keywords in the following field Generate\ groups\ for\ author\ last\ names=Generate groups for author last names Regular\ expression=Regular expression + +Error\ importing.\ See\ the\ error\ log\ for\ details.=Error importing. See the error log for details. diff --git a/src/test/resources/org/jabref/logic/importer/fileformat/EndnoteXmlImporterTest_EmptyKeywordStyle.bib b/src/test/resources/org/jabref/logic/importer/fileformat/EndnoteXmlImporterTest_EmptyKeywordStyle.bib new file mode 100644 index 00000000000..947dbaac727 --- /dev/null +++ b/src/test/resources/org/jabref/logic/importer/fileformat/EndnoteXmlImporterTest_EmptyKeywordStyle.bib @@ -0,0 +1,16 @@ +% Encoding: UTF-8 + +@Article{, + author = {Lim, RCH}, + title = {Painless Laser Acupuncture for Smoking Cessation.}, + doi = {10.1089/acu.2018.1295}, + note = {FFT available, not read on 7/2/18}, + number = {3}, + pages = {159-162}, + url = {https://www.ncbi.nlm.nih.gov/pubmed/29937971}, + volume = {30}, + isbn = {1933-6586}, + journal = {Med Acupunct}, + keywords = {anxiety; craving; dependency; destress; health restoration; }, + year = {2018}, +} diff --git a/src/test/resources/org/jabref/logic/importer/fileformat/EndnoteXmlImporterTest_EmptyKeywordStyle.xml b/src/test/resources/org/jabref/logic/importer/fileformat/EndnoteXmlImporterTest_EmptyKeywordStyle.xml new file mode 100644 index 00000000000..6ad699ba51b --- /dev/null +++ b/src/test/resources/org/jabref/logic/importer/fileformat/EndnoteXmlImporterTest_EmptyKeywordStyle.xml @@ -0,0 +1,84 @@ + + + + + LASER Acu.1281 mapping conv .bdb + Bookends + 55549 + 17 + + + + + + + + + + + + + <style face="normal" size="100%">Painless Laser Acupuncture for Smoking Cessation.</style> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 1ed36769873378c82b0d43743ba89368dff64d26 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Tue, 10 Nov 2020 12:16:25 +0100 Subject: [PATCH 063/201] Try removing annotations from module info --- src/main/java/module-info.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index eb7e0102e92..0ccb4b07bb5 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -40,9 +40,6 @@ requires java.xml.bind; requires jdk.xml.dom; - // Annotations (@PostConstruct) - requires java.annotation; - // Microsoft application insights requires applicationinsights.core; From f5c52a2ef6aafa3536eb4e3f93974c8219c790f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Nov 2020 12:31:25 +0100 Subject: [PATCH 064/201] Bump lucene-queryparser from 8.6.3 to 8.7.0 (#7089) Bumps lucene-queryparser from 8.6.3 to 8.7.0. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d46f140be55..cc5eeac1e68 100644 --- a/build.gradle +++ b/build.gradle @@ -137,7 +137,7 @@ dependencies { antlr4 'org.antlr:antlr4:4.8-1' implementation 'org.antlr:antlr4-runtime:4.8-1' - implementation (group: 'org.apache.lucene', name: 'lucene-queryparser', version: '8.6.3') { + implementation (group: 'org.apache.lucene', name: 'lucene-queryparser', version: '8.7.0') { exclude group: 'org.apache.lucene', module: 'lucene-sandbox' } From 07bd8cc3e534be23c22d336ad8aa85f3a767762b Mon Sep 17 00:00:00 2001 From: Jonatan Asketorp <2598631+k3KAW8Pnf7mkmdSMPHz27@users.noreply.github.com> Date: Tue, 10 Nov 2020 11:20:14 -0500 Subject: [PATCH 065/201] Extract String constants --- .../org/jabref/gui/preview/PreviewViewer.java | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/src/main/java/org/jabref/gui/preview/PreviewViewer.java index 8f6f91364d7..020a8643c33 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewViewer.java +++ b/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -68,6 +68,14 @@ public class PreviewViewer extends ScrollPane implements InvalidationListener { "function(){function n(e){t(this,n),this.ctx=e,this.ie=!1;var r=window.navigator.userAgent;(r.indexOf(\"MSIE\")>-1||r.indexOf(\"Trident\")>-1)&&(this.ie=!0)}return r(n,[{key:\"log\",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:\"debug\",r=this.opt.log;this.opt.debug&&\"object\"===e(r)&&\"function\"==typeof r[n]&&r[n](\"mark.js: \".concat(t))}},{key:\"getSeparatedKeywords\",value:function(e){var t=this,n=[];return e.forEach(function(e){t.opt.separateWordSearch?e.split(\" \").forEach(function(e){e.trim()&&-1===n.indexOf(e)&&n.push(e)}):e.trim()&&-1===n.indexOf(e)&&n.push(e)}),{keywords:n.sort(function(e,t){return t.length-e.length}),length:n.length}}},{key:\"isNumeric\",value:function(e){return Number(parseFloat(e))==e}},{key:\"checkRanges\",value:function(e){var t=this;if(!Array.isArray(e)||\"[object Object]\"!==Object.prototype.toString.call(e[0]))return this.log(\"markRanges() will only accept an array of objects\"),this.opt.noMatch(e),[];var n=[],r=0;return e.sort(function(e,t){return e.start-t.start}).forEach(function(e){var o=t.callNoMatchOnInvalidRanges(e,r),i=o.start,a=o.end;o.valid&&(e.start=i,e.length=a-i,n.push(e),r=a)}),n}},{key:\"callNoMatchOnInvalidRanges\",value:function(e,t){var n,r,o=!1;return e&&void 0!==e.start?(r=(n=parseInt(e.start,10))+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&r-t>0&&r-n>0?o=!0:(this.log(\"Ignoring invalid or overlapping range: \"+\"\".concat(JSON.stringify(e))),this.opt.noMatch(e))):(this.log(\"Ignoring invalid range: \".concat(JSON.stringify(e))),this.opt.noMatch(e)),{start:n,end:r,valid:o}}},{key:\"checkWhitespaceRanges\",value:function(e,t,n){var r,o=!0,i=n.length,a=t-i,s=parseInt(e.start,10)-a;return(r=(s=s>i?i:s)+parseInt(e.length,10))>i&&(r=i,this.log(\"End range automatically set to the max value of \".concat(i))),s<0||r-s<0||s>i||r>i?(o=!1,this.log(\"Invalid range: \".concat(JSON.stringify(e))),this.opt.noMatch(e)):\"\"===n.substring(s,r).replace(/\\s+/g,\"\")&&(o=!1,this.log(\"Skipping whitespace only range: \"+JSON.stringify(e)),this.opt.noMatch(e)),{start:s,end:r,valid:o}}},{key:\"getTextNodes\",value:function(e){var t=this,n=\"\",r=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,function(e){r.push({start:n.length,end:(n+=e.textContent).length,node:e})},function(e){return t.matchesExclude(e.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},function(){e({value:n,nodes:r})})}},{key:\"matchesExclude\",value:function(e){return i.matches(e,this.opt.exclude.concat([\"script\",\"style\",\"title\",\"head\",\"html\"]))}},{key:\"wrapRangeInTextNode\",value:function(e,t,n){var r=this.opt.element?this.opt.element:\"mark\",o=e.splitText(t),i=o.splitText(n-t),a=document.createElement(r);return a.setAttribute(\"data-markjs\",\"true\"),this.opt.className&&a.setAttribute(\"class\",this.opt.className),a.textContent=o.textContent,o.parentNode.replaceChild(a,o),i}},{key:\"wrapRangeInMappedTextNode\",value:function(e,t,n,r,o){var i=this;e.nodes.every(function(a,s){var c=e.nodes[s+1];if(void 0===c||c.start>t){if(!r(a.node))return!1;var u=t-a.start,l=(n>a.end?a.end:n)-a.start,h=e.value.substr(0,a.start),f=e.value.substr(l+a.start);if(a.node=i.wrapRangeInTextNode(a.node,u,l),e.value=h+f,e.nodes.forEach(function(t,n){n>=s&&(e.nodes[n].start>0&&n!==s&&(e.nodes[n].start-=l),e.nodes[n].end-=l)}),n-=l,o(a.node.previousSibling,a.start),!(n>a.end))return!1;t=a.end}return!0})}},{key:\"wrapGroups\",value:function(e,t,n,r){return r((e=this.wrapRangeInTextNode(e,t,t+n)).previousSibling),e}},{key:\"separateGroups\",value:function(e,t,n,r,o){for(var i=t.length,a=1;a-1&&r(t[a],e)&&(e=this.wrapGroups(e,s,t[a].length,o))}return e}},{key:\"wrapMatches\",value:function(e,t,n,r,o){var i=this,a=0===t?0:t+1;this.getTextNodes(function(t){t.nodes.forEach(function(t){var o;for(t=t.node;null!==(o=e.exec(t.textContent))&&\"\"!==o[a];){if(i.opt.separateGroups)t=i.separateGroups(t,o,a,n,r);else{if(!n(o[a],t))continue;var s=o.index;if(0!==a)for(var c=1;c\n" + ""; + private static final String JS_MARK_REG_EXP_CALLBACK = "" + + "{done: function(){" + + " markInstance.markRegExp(%s);}" + + "}"; + private static final String JS_UNMARK_WITH_CALLBACK = "" + + "var markInstance = new Mark(document.getElementById(\"content\"));" + + "markInstance.unmark(%s);"; + private static final Pattern UNESCAPED_FORWARD_SLASH = Pattern.compile("\"(? Date: Tue, 10 Nov 2020 14:02:24 -0500 Subject: [PATCH 066/201] Fix codestyle --- .../rules/ContainBasedSearchRuleTest.java | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java b/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java index 3d9f5c94314..ad7b0f645ad 100644 --- a/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java +++ b/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java @@ -6,7 +6,8 @@ import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Test case for ContainBasedSearchRule. @@ -23,32 +24,31 @@ public void testBasicSearchParsing() { String query = "marine 2001 shields"; - assertEquals(false, bsCaseSensitive.applyRule(query, be)); - assertEquals(true, bsCaseInsensitive.applyRule(query, be)); - assertEquals(false, bsCaseSensitiveRegexp.applyRule(query, be)); - assertEquals(false, bsCaseInsensitiveRegexp.applyRule(query, be)); + assertFalse(bsCaseSensitive.applyRule(query, be)); + assertTrue(bsCaseInsensitive.applyRule(query, be)); + assertFalse(bsCaseSensitiveRegexp.applyRule(query, be)); + assertFalse(bsCaseInsensitiveRegexp.applyRule(query, be)); query = "\"marine larviculture\""; - assertEquals(false, bsCaseSensitive.applyRule(query, be)); - assertEquals(false, bsCaseInsensitive.applyRule(query, be)); - assertEquals(false, bsCaseSensitiveRegexp.applyRule(query, be)); - assertEquals(false, bsCaseInsensitiveRegexp.applyRule(query, be)); + assertFalse(bsCaseSensitive.applyRule(query, be)); + assertFalse(bsCaseInsensitive.applyRule(query, be)); + assertFalse(bsCaseSensitiveRegexp.applyRule(query, be)); + assertFalse(bsCaseInsensitiveRegexp.applyRule(query, be)); query = "marine [A-Za-z]* larviculture"; - assertEquals(false, bsCaseSensitive.applyRule(query, be)); - assertEquals(false, bsCaseInsensitive.applyRule(query, be)); - assertEquals(false, bsCaseSensitiveRegexp.applyRule(query, be)); - assertEquals(true, bsCaseInsensitiveRegexp.applyRule(query, be)); + assertFalse(bsCaseSensitive.applyRule(query, be)); + assertFalse(bsCaseInsensitive.applyRule(query, be)); + assertFalse(bsCaseSensitiveRegexp.applyRule(query, be)); + assertTrue(bsCaseInsensitiveRegexp.applyRule(query, be)); } public BibEntry makeBibtexEntry() { - BibEntry e = new BibEntry(StandardEntryType.InCollection); - e.setField(StandardField.TITLE, "Marine finfish larviculture in Europe"); - e.setCitationKey("shields01"); - e.setField(StandardField.YEAR, "2001"); - e.setField(StandardField.AUTHOR, "Kevin Shields"); - return e; + return new BibEntry(StandardEntryType.InCollection) + .withCitationKey("shields01") + .withField(StandardField.TITLE, "Marine finfish larviculture in Europe") + .withField(StandardField.YEAR, "2001") + .withField(StandardField.AUTHOR, "Kevin Shields"); } } From df1a6fa07fbd557685349c349c4916d4116df9bf Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Tue, 10 Nov 2020 22:34:21 +0100 Subject: [PATCH 067/201] Refine default preview --- CHANGELOG.md | 1 + .../jabref/preferences/JabRefPreferences.java | 36 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 034d99986a0..42e375d65fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://git - We changed the way JabRef displays the title of a tab and of the window. [4161](https://github.com/JabRef/jabref/issues/4161) - We changed connect timeouts for server requests to 30 seconds in general and 5 seconds for GROBID server (special) and improved user notifications on connection issues. [7026](https://github.com/JabRef/jabref/pull/7026) - We changed the way linked files are opened on Linux to use the native openFile method, compatible with confined packages. [7037](https://github.com/JabRef/jabref/pull/7037) +- We refined the entry preview to show the full names of authors and editors, to list the editor only if no author is present, have the year ealier. [#7083](https://github.com/JabRef/jabref/issues/7083) ### Fixed diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index ed0c1ad6995..316ad39fc9d 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -661,25 +661,23 @@ private JabRefPreferences() { defaults.put(PREVIEW_PANEL_HEIGHT, 0.65); defaults.put(PREVIEW_AS_TAB, Boolean.FALSE); defaults.put(PREVIEW_STYLE, - "" - + "\\bibtextype\\begin{citationkey} (\\citationkey)" - + "\\end{citationkey}
__NEWLINE__" - + "\\begin{author} \\format[Authors(LastFirst,Initials,Semicolon,Amp),HTMLChars]{\\author}
\\end{author}__NEWLINE__" - + "\\begin{editor} \\format[Authors(LastFirst,Initials,Semicolon,Amp),HTMLChars]{\\editor} " - + "(\\format[IfPlural(Eds.,Ed.)]{\\editor})
\\end{editor}__NEWLINE__" - + "\\begin{title} \\format[HTMLChars]{\\title} \\end{title}
__NEWLINE__" - + "\\begin{chapter} \\format[HTMLChars]{\\chapter}
\\end{chapter}__NEWLINE__" - + "\\begin{journal} \\format[HTMLChars]{\\journal}, \\end{journal}__NEWLINE__" - // Include the booktitle field for @inproceedings, @proceedings, etc. - + "\\begin{booktitle} \\format[HTMLChars]{\\booktitle}, \\end{booktitle}__NEWLINE__" - + "\\begin{school} \\format[HTMLChars]{\\school}, \\end{school}__NEWLINE__" - + "\\begin{institution} \\format[HTMLChars]{\\institution}, \\end{institution}__NEWLINE__" - + "\\begin{publisher} \\format[HTMLChars]{\\publisher}, \\end{publisher}__NEWLINE__" - + "\\begin{year}\\year\\end{year}\\begin{volume}, \\volume\\end{volume}" - + "\\begin{pages}, \\format[FormatPagesForHTML]{\\pages} \\end{pages}__NEWLINE__" - + "\\begin{abstract}

Abstract: \\format[HTMLChars]{\\abstract} \\end{abstract}__NEWLINE__" - + "\\begin{comment}

Comment: \\format[Markdown,HTMLChars]{\\comment} \\end{comment}" - + "__NEWLINE__

"); + "" + + "\\bibtextype\\begin{citationkey} (\\citationkey)\\end{citationkey}__NEWLINE__" + + "\\begin{author}

\\format[Authors(LastFirst, FullName,Sep= / ,Sep= / ),HTMLChars]{\\author}\\end{author}__NEWLINE__" + + "\\begin{editor & !author}

\\format[Authors(LastFirst,FullName,Sep= / ,Sep= / ),HTMLChars]{\\editor} (\\format[IfPlural(Eds.,Ed.)]{\\editor})\\end{editor & !author}__NEWLINE__" + + "\\begin{title}
\\format[HTMLChars]{\\title} \\end{title}__NEWLINE__" + + "\\begin{year}
\\year\\end{year}__NEWLINE__" + + "\\begin{booktitle}
\\format[HTMLChars]{\\booktitle}, \\end{booktitle}__NEWLINE__" + + "\\begin{chapter} \\format[HTMLChars]{\\chapter}
\\end{chapter}" + + "\\begin{journal}

\\format[HTMLChars]{\\journal} \\end{journal} \\begin{volume}, Vol. \\volume\\end{volume}\\begin{series}
\\format[HTMLChars]{\\series}\\end{series}\\begin{number}, No. \\format[HTMLChars]{number}\\end{number}__NEWLINE__" + + "\\begin{school} \\format[HTMLChars]{\\school}, \\end{school}__NEWLINE__" + + "\\begin{institution} \\format[HTMLChars]{\\institution}, \\end{institution}__NEWLINE__" + + "\\begin{publisher}

\\format[HTMLChars]{\\publisher}\\end{publisher}\\begin{location}: \\format[HTMLChars]{\\location} \\end{location}__NEWLINE__" + + "\\begin{pages}
p. \\format[FormatPagesForHTML]{\\pages}\\end{pages}__NEWLINE__" + + "\\begin{abstract}

Abstract: \\format[HTMLChars]{\\abstract} \\end{abstract}__NEWLINE__" + + "\\begin{owncitation}

Own citation: \\format[HTMLChars]{\\owncitation} \\end{owncitation}__NEWLINE__" + + "\\begin{comment}

Comment: \\format[HTMLChars]{\\comment}\\end{comment}__NEWLINE__" + + "
__NEWLINE__"); // set default theme defaults.put(JabRefPreferences.FX_THEME, Theme.BASE_CSS); From ba1964bfc73eb186adbac3ae53eeff09d37a32f7 Mon Sep 17 00:00:00 2001 From: Jonatan Asketorp <2598631+k3KAW8Pnf7mkmdSMPHz27@users.noreply.github.com> Date: Tue, 10 Nov 2020 18:31:42 -0500 Subject: [PATCH 068/201] Remove unused local variable --- src/main/java/org/jabref/gui/preview/PreviewViewer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/src/main/java/org/jabref/gui/preview/PreviewViewer.java index 020a8643c33..7ef39accedc 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewViewer.java +++ b/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -234,7 +234,6 @@ public void print() { } public void copyPreviewToClipBoard() { - StringBuilder previewStringContent = new StringBuilder(); Document document = previewView.getEngine().getDocument(); ClipboardContent content = new ClipboardContent(); From 474c26c8c7da6c93a3635a45d3734122acce7084 Mon Sep 17 00:00:00 2001 From: Jonatan Asketorp <2598631+k3KAW8Pnf7mkmdSMPHz27@users.noreply.github.com> Date: Tue, 10 Nov 2020 18:44:15 -0500 Subject: [PATCH 069/201] Add comments to the regular expression literal --- src/main/java/org/jabref/gui/preview/PreviewViewer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/src/main/java/org/jabref/gui/preview/PreviewViewer.java index 7ef39accedc..f00fc1e5b8a 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewViewer.java +++ b/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -162,7 +162,8 @@ private void highlightSearchPattern() { */ private static String createJavaScriptRegex(Pattern regex) { String pattern = regex.pattern(); - // Create a JavaScript regex using the forward slash pattern + // Create a JavaScript regular expression literal (https://ecma-international.org/ecma-262/10.0/index.html#sec-literals-regular-expression-literals) + // Forward slashes are reserved to delimit the regular expression body. Hence, they must be escaped. pattern = UNESCAPED_FORWARD_SLASH.matcher(pattern).replaceAll("\\\\/"); return "/" + pattern + "/gmi"; } From 9abbed2e953261edbc2307f736d6b5b17ab0931e Mon Sep 17 00:00:00 2001 From: Jonatan Asketorp <2598631+k3KAW8Pnf7mkmdSMPHz27@users.noreply.github.com> Date: Fri, 13 Nov 2020 08:01:57 -0500 Subject: [PATCH 070/201] Add test for GrammarBasedSearchRule --- .../rules/GrammarBasedSearchRuleTest.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/test/java/org/jabref/model/search/rules/GrammarBasedSearchRuleTest.java diff --git a/src/test/java/org/jabref/model/search/rules/GrammarBasedSearchRuleTest.java b/src/test/java/org/jabref/model/search/rules/GrammarBasedSearchRuleTest.java new file mode 100644 index 00000000000..28f045b07fb --- /dev/null +++ b/src/test/java/org/jabref/model/search/rules/GrammarBasedSearchRuleTest.java @@ -0,0 +1,42 @@ +package org.jabref.model.search.rules; + +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test case for GrammarBasedSearchRuleTest. + */ +public class GrammarBasedSearchRuleTest { + + @Test + void applyRuleMatchesSingleTermWithRegex() { + GrammarBasedSearchRule searchRule = new GrammarBasedSearchRule(true, true); + + String query = "M[a-z]+e"; + assertTrue(searchRule.validateSearchStrings(query)); + assertTrue(searchRule.applyRule(query, makeBibtexEntry())); + } + + @Test + void applyRuleDoesNotMatchSingleTermWithRegex() { + GrammarBasedSearchRule searchRule = new GrammarBasedSearchRule(true, true); + + String query = "M[0-9]+e"; + assertTrue(searchRule.validateSearchStrings(query)); + assertFalse(searchRule.applyRule(query, makeBibtexEntry())); + } + + public BibEntry makeBibtexEntry() { + return new BibEntry(StandardEntryType.InCollection) + .withCitationKey("shields01") + .withField(StandardField.TITLE, "Marine finfish larviculture in Europe") + .withField(StandardField.YEAR, "2001") + .withField(StandardField.AUTHOR, "Kevin Shields"); + } +} From b4ceb04d81661cec540270a97a02fe4720f25b61 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage Date: Fri, 13 Nov 2020 20:30:17 +0100 Subject: [PATCH 071/201] Fixed setting title of saved new library --- src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java | 2 +- .../java/org/jabref/gui/shared/SharedDatabaseUIManager.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 7e98241ad7d..07e64ae320b 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -129,7 +129,7 @@ boolean saveAs(Path file, SaveDatabaseMode mode) { // we managed to successfully save the file // thus, we can store the store the path into the context context.setDatabasePath(file); - // FIXME: We need to refresh the tab titles + libraryTab.updateTabTitle(false); // Reinstall AutosaveManager and BackupManager for the new file name libraryTab.resetChangeMonitorAndChangePane(); diff --git a/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java b/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java index a089aadf723..9fee4372776 100644 --- a/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java +++ b/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java @@ -66,7 +66,7 @@ public void listen(ConnectionLostEvent connectionLostEvent) { new SharedDatabaseLoginDialogView(jabRefFrame).showAndWait(); } else if (answer.get().equals(workOffline)) { connectionLostEvent.getBibDatabaseContext().convertToLocalDatabase(); - // jabRefFrame.refreshWindowAndTabTitles(); + jabRefFrame.getLibraryTabs().forEach(tab -> tab.updateTabTitle(tab.isModified())); jabRefFrame.getDialogService().notify(Localization.lang("Working offline.")); } } else { From 245c6d5630bb7dcfd5661d953666cf73d9eb02dc Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Sat, 14 Nov 2020 18:12:55 +0100 Subject: [PATCH 072/201] fix antlr grammar generation --- build.gradle | 30 ++++++++++++++---------- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/build.gradle b/build.gradle index cc5eeac1e68..4f80d302d91 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,6 @@ import groovy.json.JsonSlurper import org.gradle.internal.os.OperatingSystem import org.jabref.build.JournalAbbreviationConverter -import org.jabref.build.antlr.JabRefAntlrPlugin import org.jabref.build.localization.LocalizationPlugin import org.jabref.build.xjc.XjcPlugin import org.jabref.build.xjc.XjcTask @@ -31,7 +30,6 @@ apply plugin: 'project-report' apply plugin: 'jacoco' apply plugin: 'me.champeau.gradle.jmh' apply plugin: 'checkstyle' -apply plugin: JabRefAntlrPlugin apply plugin: XjcPlugin apply plugin: LocalizationPlugin @@ -88,7 +86,8 @@ repositories { configurations { libreoffice - + antlr3 + antlr4 // TODO: Remove the following workaround for split error messages such as // error: module java.xml.bind reads package javax.annotation from both jsr305 and java.annotation compile { @@ -304,23 +303,30 @@ task generateSource(dependsOn: ["generateBstGrammarSource", description 'Generates all necessary (Java) source files.' } -task generateBstGrammarSource(type: org.jabref.build.antlr.AntlrTask) { +task generateBstGrammarSource(type: JavaExec) { + + main = "org.antlr.Tool" + classpath = configurations.antlr3 group = "JabRef" description = 'Generates BstLexer.java and BstParser.java from the Bst.g grammar file using antlr3.' - antlr = ANTLR3 - inputFile = 'src/main/antlr3/org/jabref/bst/Bst.g' - outputDir = 'src/main/generated/org/jabref/logic/bst/' + inputs.dir('src/main/antlr3/org/jabref/bst/') + //outputs.dir = '$projectDir/src/main/generated/org/jabref/logic/bst/' + + args = ["-o", "$projectDir/src/main/generated/org/jabref/logic/bst/" , "$projectDir/src/main/antlr3/org/jabref/bst/Bst.g" ] } -task generateSearchGrammarSource(type: org.jabref.build.antlr.AntlrTask) { +task generateSearchGrammarSource(type: JavaExec) { + + main = "org.antlr.v4.Tool" + classpath = configurations.antlr4 group = 'JabRef' description = "Generates java files for Search.g antlr4." + - antlr = ANTLR4 - inputFile = "src/main/antlr4/org/jabref/search/Search.g4" - outputDir = "src/main/generated/org/jabref/search" - javaPackage = "org.jabref.search" + inputs.dir("src/main/antlr4/org/jabref/search/") + //outputs.dir = "$projectDir/src/main/generated/org/jabref/search" + args = ["-o","$projectDir/src/main/generated/org/jabref/search" , "-visitor", "-no-listener", "-package", "org.jabref.search", "$projectDir/src/main/antlr4/org/jabref/search/Search.g4"] } task generateMedlineSource(type: XjcTask) { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4b4429748d..be52383ef49 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 909af80d0ba91510af9b9ddc5b225107efa59427 Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Sat, 14 Nov 2020 18:43:56 +0100 Subject: [PATCH 073/201] Remove custom Antlr plugin Update to gradle 6.5 Make @koppor happy --- build.gradle | 18 ++--- .../build/antlr/Antlr3CommandLine.groovy | 27 ------- .../build/antlr/Antlr4CommandLine.groovy | 31 -------- .../build/antlr/AntlrCommandLine.groovy | 16 ----- .../org/jabref/build/antlr/AntlrTask.groovy | 68 ------------------ .../build/antlr/JabRefAntlrPlugin.groovy | 23 ------ gradle/wrapper/gradle-wrapper.jar | Bin 58694 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 + gradlew.bat | 22 ++---- 10 files changed, 14 insertions(+), 195 deletions(-) delete mode 100644 buildSrc/src/main/groovy/org/jabref/build/antlr/Antlr3CommandLine.groovy delete mode 100644 buildSrc/src/main/groovy/org/jabref/build/antlr/Antlr4CommandLine.groovy delete mode 100644 buildSrc/src/main/groovy/org/jabref/build/antlr/AntlrCommandLine.groovy delete mode 100644 buildSrc/src/main/groovy/org/jabref/build/antlr/AntlrTask.groovy delete mode 100644 buildSrc/src/main/groovy/org/jabref/build/antlr/JabRefAntlrPlugin.groovy diff --git a/build.gradle b/build.gradle index 4f80d302d91..4e6b31353de 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,6 @@ plugins { id 'org.javamodularity.moduleplugin' version '1.7.0' id 'org.openjfx.javafxplugin' version '0.0.9' id 'org.beryx.jlink' version '2.22.1' - // nicer test outputs during running and completion // Homepage: https://github.com/radarsh/gradle-test-logger-plugin id 'com.adarshr.test-logger' version '2.1.1' @@ -86,8 +85,8 @@ repositories { configurations { libreoffice - antlr3 - antlr4 + antlr3 + antlr4 // TODO: Remove the following workaround for split error messages such as // error: module java.xml.bind reads package javax.annotation from both jsr305 and java.annotation compile { @@ -305,27 +304,24 @@ task generateSource(dependsOn: ["generateBstGrammarSource", task generateBstGrammarSource(type: JavaExec) { - main = "org.antlr.Tool" - classpath = configurations.antlr3 + main = "org.antlr.Tool" + classpath = configurations.antlr3 group = "JabRef" description = 'Generates BstLexer.java and BstParser.java from the Bst.g grammar file using antlr3.' inputs.dir('src/main/antlr3/org/jabref/bst/') - //outputs.dir = '$projectDir/src/main/generated/org/jabref/logic/bst/' - args = ["-o", "$projectDir/src/main/generated/org/jabref/logic/bst/" , "$projectDir/src/main/antlr3/org/jabref/bst/Bst.g" ] } task generateSearchGrammarSource(type: JavaExec) { - main = "org.antlr.v4.Tool" - classpath = configurations.antlr4 + main = "org.antlr.v4.Tool" + classpath = configurations.antlr4 group = 'JabRef' description = "Generates java files for Search.g antlr4." - + inputs.dir("src/main/antlr4/org/jabref/search/") - //outputs.dir = "$projectDir/src/main/generated/org/jabref/search" args = ["-o","$projectDir/src/main/generated/org/jabref/search" , "-visitor", "-no-listener", "-package", "org.jabref.search", "$projectDir/src/main/antlr4/org/jabref/search/Search.g4"] } diff --git a/buildSrc/src/main/groovy/org/jabref/build/antlr/Antlr3CommandLine.groovy b/buildSrc/src/main/groovy/org/jabref/build/antlr/Antlr3CommandLine.groovy deleted file mode 100644 index 48213e8f07e..00000000000 --- a/buildSrc/src/main/groovy/org/jabref/build/antlr/Antlr3CommandLine.groovy +++ /dev/null @@ -1,27 +0,0 @@ -package org.jabref.build.antlr - -import org.gradle.api.file.FileCollection - -class Antlr3CommandLine implements AntlrCommandLine { - - private final task - - Antlr3CommandLine(AntlrTask task) { - this.task = task - } - - @Override - String getMain() { - return "org.antlr.Tool" - } - - @Override - FileCollection getClasspath() { - return task.project.configurations.antlr3 - } - - @Override - List getArguments() { - return ["-o", task.project.file(task.outputDir).toString(), task.project.file(task.inputFile).toString()] - } -} diff --git a/buildSrc/src/main/groovy/org/jabref/build/antlr/Antlr4CommandLine.groovy b/buildSrc/src/main/groovy/org/jabref/build/antlr/Antlr4CommandLine.groovy deleted file mode 100644 index 6c74a04ad88..00000000000 --- a/buildSrc/src/main/groovy/org/jabref/build/antlr/Antlr4CommandLine.groovy +++ /dev/null @@ -1,31 +0,0 @@ -package org.jabref.build.antlr - -import org.gradle.api.file.FileCollection - -class Antlr4CommandLine implements AntlrCommandLine { - - private final AntlrTask task - - Antlr4CommandLine(AntlrTask task) { - this.task = task - } - - @Override - String getMain() { - return "org.antlr.v4.Tool" - } - - @Override - FileCollection getClasspath() { - return task.project.configurations.antlr4 - } - - @Override - List getArguments() { - return ["-o", file(task.outputDir), "-visitor", "-no-listener", "-package", task.javaPackage, file(task.inputFile)] - } - - private String file(String path) { - return task.project.file(path).toString() - } -} diff --git a/buildSrc/src/main/groovy/org/jabref/build/antlr/AntlrCommandLine.groovy b/buildSrc/src/main/groovy/org/jabref/build/antlr/AntlrCommandLine.groovy deleted file mode 100644 index bcfe0ecc371..00000000000 --- a/buildSrc/src/main/groovy/org/jabref/build/antlr/AntlrCommandLine.groovy +++ /dev/null @@ -1,16 +0,0 @@ -package org.jabref.build.antlr - -import org.gradle.api.file.FileCollection - -/** - * Encapsulates a command line call to an version of the ANTLR tools. - */ -interface AntlrCommandLine { - - String getMain() - - FileCollection getClasspath() - - List getArguments() - -} diff --git a/buildSrc/src/main/groovy/org/jabref/build/antlr/AntlrTask.groovy b/buildSrc/src/main/groovy/org/jabref/build/antlr/AntlrTask.groovy deleted file mode 100644 index c31aefc1d19..00000000000 --- a/buildSrc/src/main/groovy/org/jabref/build/antlr/AntlrTask.groovy +++ /dev/null @@ -1,68 +0,0 @@ -package org.jabref.build.antlr - -import org.gradle.api.tasks.JavaExec -import org.gradle.api.tasks.TaskAction - -class AntlrTask extends JavaExec { - - static def ANTLR3 = Antlr3CommandLine - static def ANTLR4 = Antlr4CommandLine - - private Class antlr = ANTLR3 - private String inputFile = "" - private String outputDir = "" - private String javaPackage = "" - - AntlrTask() { - project.configurations { - antlr3 - antlr4 - } - } - - @TaskAction - @Override - void exec() { - AntlrCommandLine commandLine = antlr.newInstance(this) - - main = commandLine.main - classpath = commandLine.classpath - args = commandLine.arguments - - super.exec() - } - - Class getAntlr() { - return antlr - } - - void setAntlr(Class antlr) { - this.antlr = antlr - } - - String getInputFile() { - return inputFile - } - - void setInputFile(String inputFile) { - this.inputFile = inputFile - inputs.file(inputFile) - } - - String getOutputDir() { - return outputDir - } - - void setOutputDir(String outputDir) { - this.outputDir = outputDir - outputs.dir(outputDir) - } - - String getJavaPackage() { - return javaPackage - } - - void setJavaPackage(String javaPackage) { - this.javaPackage = javaPackage - } -} diff --git a/buildSrc/src/main/groovy/org/jabref/build/antlr/JabRefAntlrPlugin.groovy b/buildSrc/src/main/groovy/org/jabref/build/antlr/JabRefAntlrPlugin.groovy deleted file mode 100644 index 0fc139d7b80..00000000000 --- a/buildSrc/src/main/groovy/org/jabref/build/antlr/JabRefAntlrPlugin.groovy +++ /dev/null @@ -1,23 +0,0 @@ -package org.jabref.build.antlr - -import org.gradle.api.Plugin -import org.gradle.api.Project - -/** - * Configures the project for use with ANTLR 3 or 4. - */ -class JabRefAntlrPlugin implements Plugin { - - public static final def ANTLR3_CONFIGURATION_NAME = "antlr3" - public static final def ANTLR4_CONFIGURATION_NAME = "antlr4" - - @Override - void apply(Project target) { - def antlr3Cfg = target.configurations.create(ANTLR3_CONFIGURATION_NAME) - antlr3Cfg.description = "Dependencies required to run the ANTLR3 tool." - - def antlr4Cfg = target.configurations.create(ANTLR4_CONFIGURATION_NAME) - antlr4Cfg.description = "Dependencies required to run the ANTLR4 tool." - } - -} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 490fda8577df6c95960ba7077c43220e5bb2c0d9..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch delta 6763 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 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell From fac9acedfdcb09e68e3f9f81b5bd80751db1d00c Mon Sep 17 00:00:00 2001 From: Houssem Nasri Date: Sat, 14 Nov 2020 21:28:25 +0100 Subject: [PATCH 074/201] Fix Group color not imported after closing the app (#7096) --- CHANGELOG.md | 1 + .../java/org/jabref/logic/importer/util/GroupsParser.java | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 034d99986a0..24bd0cf333c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://git - We fixed an issue where modifications to the Custom preview layout in the preferences were not saved [#6447](https://github.com/JabRef/jabref/issues/6447) - We fixed an issue where errors from imports were not shown to the user [#7084](https://github.com/JabRef/jabref/pull/7084) - We fixed an issue where the EndNote XML Import would fail on empty keywords tags [forum#2387](https://discourse.jabref.org/t/importing-in-unknown-format-fails-to-import-xml-library-from-bookends-export/2387) +- We fixed an issue where the color of groups of type "free search expression" not persisting after restarting the application [#6999](https://github.com/JabRef/jabref/issues/6999) ### Removed diff --git a/src/main/java/org/jabref/logic/importer/util/GroupsParser.java b/src/main/java/org/jabref/logic/importer/util/GroupsParser.java index 3669a781867..9ce459a4829 100644 --- a/src/main/java/org/jabref/logic/importer/util/GroupsParser.java +++ b/src/main/java/org/jabref/logic/importer/util/GroupsParser.java @@ -84,7 +84,7 @@ public static GroupTreeNode importGroups(List orderedData, Character key /** * Re-create a group instance from a textual representation. * - * @param s The result from the group's toString() method. + * @param s The result from the group's toString() method. * @return New instance of the encoded group. * @throws ParseException If an error occurred and a group could not be created, e.g. due to a malformed regular expression. */ @@ -278,9 +278,11 @@ private static AbstractGroup searchGroupFromString(String s) { boolean regExp = Integer.parseInt(tok.nextToken()) == 1; // version 0 contained 4 additional booleans to specify search // fields; these are ignored now, all fields are always searched - return new SearchGroup(name, + SearchGroup searchGroup = new SearchGroup(name, GroupHierarchyType.getByNumberOrDefault(context), expression, caseSensitive, regExp ); + addGroupDetails(tok, searchGroup); + return searchGroup; } private static void addGroupDetails(QuotedStringTokenizer tokenizer, AbstractGroup group) { From bc6a0b8298639c3c853f10c8bc9079caa932533a Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sat, 14 Nov 2020 21:30:44 +0100 Subject: [PATCH 075/201] Add new author --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index fadd29f4ed8..18a72d4d5de 100644 --- a/AUTHORS +++ b/AUTHORS @@ -159,6 +159,7 @@ Harinda Samarasekara hemantgs HifeFish Hollyqqqqq +Houssem Nasri hrandrianasolo Hussain Arif Igor Chernyavsky From 32761d98278285088c5b67bac012e785b9eb7b84 Mon Sep 17 00:00:00 2001 From: github actions Date: Sun, 15 Nov 2020 02:20:04 +0000 Subject: [PATCH 076/201] Squashed 'src/main/resources/csl-styles/' changes from f4399aa413..55200d0378 55200d0378 Create computational-intelligence.csl (#5102) 6d2efc3dc0 Merge branch 'master' of github.com:citation-style-language/styles 60ab0fb5b4 Add Steinbeis SMI d5e0130c51 Add AAQR Style d964f9b354 Create bitonline-publikationen.csl (#5090) 0dc1a5623a Add "APA 7th edition" to Norsk APA style titles (#5098) 3b6aaf7f08 Update norsk-apa-manual.csl (#5095) 0139c6394f Create technische-universitat-dresden-betriebswirtschaftslehre-rechnungswesen-controlling.csl (#5034) fdc4497faa Update universidade-estadual-de-alagoas-abnt (#5085) afdc2ea9d5 Create revista-espanola-de-nutricion-humana-y-dietetica.csl (#5082) 939189d7fd Create citation-compass-apa-note-no.csl (#5060) 414f5e601d brazilian-journal-of-veterinary-research: add bib sorting (#5091) dbae9d7041 Add DOI to Thieme German git-subtree-dir: src/main/resources/csl-styles git-subtree-split: 55200d037850d0848b22cd1839c0d3b11165e3ac --- aerosol-and-air-quality-research.csl | 212 +++ bitonline.csl | 479 +++++ ...veterinary-research-and-animal-science.csl | 6 +- dependent/computational-intelligence.csl | 15 + norsk-apa-manual-note.csl | 1623 ++++++++++++++++ norsk-apa-manual.csl | 2 +- ...panola-de-nutricion-humana-y-dietetica.csl | 214 +++ ...le-school-of-management-and-innovation.csl | 236 +++ ...chaftslehre-rechnungswesen-controlling.csl | 1657 +++++++++++++++++ thieme-german.csl | 32 +- universidade-estadual-de-alagoas-abnt.csl | 14 +- 11 files changed, 4473 insertions(+), 17 deletions(-) create mode 100644 aerosol-and-air-quality-research.csl create mode 100644 bitonline.csl create mode 100644 dependent/computational-intelligence.csl create mode 100644 norsk-apa-manual-note.csl create mode 100644 revista-espanola-de-nutricion-humana-y-dietetica.csl create mode 100644 steinbeis-hochschule-school-of-management-and-innovation.csl create mode 100644 technische-universitat-dresden-betriebswirtschaftslehre-rechnungswesen-controlling.csl diff --git a/aerosol-and-air-quality-research.csl b/aerosol-and-air-quality-research.csl new file mode 100644 index 00000000000..775c7133d72 --- /dev/null +++ b/aerosol-and-air-quality-research.csl @@ -0,0 +1,212 @@ + + diff --git a/bitonline.csl b/bitonline.csl new file mode 100644 index 00000000000..498d39928c8 --- /dev/null +++ b/bitonline.csl @@ -0,0 +1,479 @@ + + diff --git a/brazilian-journal-of-veterinary-research-and-animal-science.csl b/brazilian-journal-of-veterinary-research-and-animal-science.csl index e3d6c161706..3127466f190 100644 --- a/brazilian-journal-of-veterinary-research-and-animal-science.csl +++ b/brazilian-journal-of-veterinary-research-and-animal-science.csl @@ -15,7 +15,7 @@ 1413-9596 1678-4456 - 2020-03-30T16:23:02+00:00 + 2020-11-03T17:03:19+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -305,6 +305,10 @@ + + + + diff --git a/dependent/computational-intelligence.csl b/dependent/computational-intelligence.csl new file mode 100644 index 00000000000..13811de8cf4 --- /dev/null +++ b/dependent/computational-intelligence.csl @@ -0,0 +1,15 @@ + + diff --git a/norsk-apa-manual-note.csl b/norsk-apa-manual-note.csl new file mode 100644 index 00000000000..79f976483f8 --- /dev/null +++ b/norsk-apa-manual-note.csl @@ -0,0 +1,1623 @@ + + diff --git a/norsk-apa-manual.csl b/norsk-apa-manual.csl index b0b0cecb8d5..40af2ffa334 100644 --- a/norsk-apa-manual.csl +++ b/norsk-apa-manual.csl @@ -1,7 +1,7 @@ diff --git a/steinbeis-hochschule-school-of-management-and-innovation.csl b/steinbeis-hochschule-school-of-management-and-innovation.csl new file mode 100644 index 00000000000..fd1fcdb4fee --- /dev/null +++ b/steinbeis-hochschule-school-of-management-and-innovation.csl @@ -0,0 +1,236 @@ + + diff --git a/technische-universitat-dresden-betriebswirtschaftslehre-rechnungswesen-controlling.csl b/technische-universitat-dresden-betriebswirtschaftslehre-rechnungswesen-controlling.csl new file mode 100644 index 00000000000..fef53deed07 --- /dev/null +++ b/technische-universitat-dresden-betriebswirtschaftslehre-rechnungswesen-controlling.csl @@ -0,0 +1,1657 @@ + + diff --git a/thieme-german.csl b/thieme-german.csl index c2b3dbdd07e..acf1792a614 100644 --- a/thieme-german.csl +++ b/thieme-german.csl @@ -41,11 +41,11 @@ - - + @@ -58,10 +58,20 @@ - - - - + + + + + + + + + + + + + + @@ -114,15 +124,15 @@ - - - - + + + +
- + diff --git a/universidade-estadual-de-alagoas-abnt.csl b/universidade-estadual-de-alagoas-abnt.csl index 1789d030c17..e5308e61b38 100644 --- a/universidade-estadual-de-alagoas-abnt.csl +++ b/universidade-estadual-de-alagoas-abnt.csl @@ -9,13 +9,13 @@ Wellyngton Chaves Monteiro da Silva - wellyngton.silva@uneal.edu.br + wellyngton@uneal.edu.br http://www.uneal.edu.br De acordo com ABNT-NBR 10520.2002 e ABNT-NBR 6023.2018 - 2020-06-30T16:31:59+00:00 + 2020-10-29T19:02:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -139,8 +139,10 @@ do autor. Na regra da ABNT o sobrenome deve aparecer com todas as letras em caix
+ + - + @@ -334,6 +336,8 @@ em caixa alta. Utiliza-se antes do nome da conferencia a expressao "In". Segundo + + @@ -449,6 +453,8 @@ em caixa alta. Utiliza-se antes do nome da conferencia a expressao "In". Segundo + + @@ -471,7 +477,7 @@ em caixa alta. Utiliza-se antes do nome da conferencia a expressao "In". Segundo - + From 572e38f1f1bdf5fa7c207d8cdf3df78d4d1f4b2b Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Sun, 15 Nov 2020 20:05:52 +0100 Subject: [PATCH 077/201] remove spaces, udpate jlink plugin --- build.gradle | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 4e6b31353de..ec42830bd9e 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ plugins { id 'com.github.ben-manes.versions' version '0.36.0' id 'org.javamodularity.moduleplugin' version '1.7.0' id 'org.openjfx.javafxplugin' version '0.0.9' - id 'org.beryx.jlink' version '2.22.1' + id 'org.beryx.jlink' version '2.22.3' // nicer test outputs during running and completion // Homepage: https://github.com/radarsh/gradle-test-logger-plugin id 'com.adarshr.test-logger' version '2.1.1' @@ -303,7 +303,6 @@ task generateSource(dependsOn: ["generateBstGrammarSource", } task generateBstGrammarSource(type: JavaExec) { - main = "org.antlr.Tool" classpath = configurations.antlr3 group = "JabRef" @@ -314,13 +313,11 @@ task generateBstGrammarSource(type: JavaExec) { } task generateSearchGrammarSource(type: JavaExec) { - main = "org.antlr.v4.Tool" classpath = configurations.antlr4 group = 'JabRef' description = "Generates java files for Search.g antlr4." - inputs.dir("src/main/antlr4/org/jabref/search/") args = ["-o","$projectDir/src/main/generated/org/jabref/search" , "-visitor", "-no-listener", "-package", "org.jabref.search", "$projectDir/src/main/antlr4/org/jabref/search/Search.g4"] } From e9e79e31280a5ac66afe2d99481d859fe8149e2c Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Sun, 15 Nov 2020 20:34:32 +0100 Subject: [PATCH 078/201] set module name --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index ec42830bd9e..11e0c3059fa 100644 --- a/build.gradle +++ b/build.gradle @@ -40,9 +40,11 @@ version = project.findProperty('projVersion') ?: '100.0.0' java { sourceCompatibility = JavaVersion.VERSION_14 targetCompatibility = JavaVersion.VERSION_14 + modularity.inferModulePath.set(true) } application { + mainModule = "org.jabref" mainClassName = "$moduleName/org.jabref.gui.JabRefLauncher" } From 82da1a02a3c2fcd22e5632bfe880b33b3683cbcc Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Sun, 15 Nov 2020 22:46:03 +0100 Subject: [PATCH 079/201] fix tab, try with setting mac package identifier --- build.gradle | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 11e0c3059fa..2d5fc088fa9 100644 --- a/build.gradle +++ b/build.gradle @@ -304,7 +304,7 @@ task generateSource(dependsOn: ["generateBstGrammarSource", description 'Generates all necessary (Java) source files.' } -task generateBstGrammarSource(type: JavaExec) { +task generateBstGrammarSource(type: JavaExec) { main = "org.antlr.Tool" classpath = configurations.antlr3 group = "JabRef" @@ -703,6 +703,8 @@ jlink { installerOptions = [ '--verbose', '--vendor', 'JabRef', + '--mac-package-identifier', "JabRef", + '--mac-package-name', "JabRef", '--app-version', "${project.version}", '--file-associations', "${projectDir}/buildres/mac/bibtexAssociations.properties", '--resource-dir', "${projectDir}/buildres/mac" From 00e340919cee86159d3af587c8d3db60ccf2c94b Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Sun, 15 Nov 2020 23:01:39 +0100 Subject: [PATCH 080/201] Feature/enable paginated fetchers (#7082) --- .../fetcher/WebSearchPaneViewModel.java | 14 +- .../importer/PagedSearchBasedFetcher.java | 37 ++++- .../PagedSearchBasedParserFetcher.java | 66 ++++++++- .../jabref/logic/importer/QueryParser.java | 17 +-- .../logic/importer/SearchBasedFetcher.java | 23 ++-- .../importer/SearchBasedParserFetcher.java | 53 +++----- .../jabref/logic/importer/fetcher/ArXiv.java | 26 ++-- .../fetcher/AstrophysicsDataSystem.java | 44 ++---- .../importer/fetcher/ComplexSearchQuery.java | 7 +- .../fetcher/CompositeSearchBasedFetcher.java | 4 +- .../logic/importer/fetcher/GoogleScholar.java | 127 +++++++----------- .../fetcher/GrobidCitationFetcher.java | 4 +- .../jabref/logic/importer/fetcher/IEEE.java | 35 +++-- .../logic/importer/fetcher/JstorFetcher.java | 32 ++--- .../importer/fetcher/MedlineFetcher.java | 17 +-- .../importer/fetcher/SpringerFetcher.java | 14 +- .../logic/importer/QueryParserTest.java | 30 +++-- .../logic/importer/fetcher/ArXivTest.java | 20 ++- .../fetcher/AstrophysicsDataSystemTest.java | 13 +- .../CompositeSearchBasedFetcherTest.java | 6 +- .../logic/importer/fetcher/CrossRefTest.java | 2 +- .../importer/fetcher/GoogleScholarTest.java | 8 +- .../importer/fetcher/GvkFetcherTest.java | 6 +- .../logic/importer/fetcher/IEEETest.java | 8 +- .../importer/fetcher/INSPIREFetcherTest.java | 2 +- .../importer/fetcher/JstorFetcherTest.java | 9 +- .../importer/fetcher/MedlineFetcherTest.java | 2 +- .../fetcher/PagedSearchFetcherTest.java | 31 +++++ .../SearchBasedFetcherCapabilityTest.java | 8 +- .../importer/fetcher/SpringerFetcherTest.java | 16 ++- 30 files changed, 381 insertions(+), 300 deletions(-) create mode 100644 src/test/java/org/jabref/logic/importer/fetcher/PagedSearchFetcherTest.java diff --git a/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java b/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java index beafb11c060..774fcc538f7 100644 --- a/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java +++ b/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java @@ -1,6 +1,5 @@ package org.jabref.gui.importer.fetcher; -import java.util.Optional; import java.util.SortedSet; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -23,10 +22,8 @@ import org.jabref.gui.util.BackgroundTask; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ParserResult; -import org.jabref.logic.importer.QueryParser; import org.jabref.logic.importer.SearchBasedFetcher; import org.jabref.logic.importer.WebFetchers; -import org.jabref.logic.importer.fetcher.ComplexSearchQuery; import org.jabref.logic.l10n.Localization; import org.jabref.model.strings.StringUtil; import org.jabref.preferences.JabRefPreferences; @@ -109,15 +106,8 @@ public void search() { SearchBasedFetcher activeFetcher = getSelectedFetcher(); BackgroundTask task; - QueryParser queryParser = new QueryParser(); - Optional generatedQuery = queryParser.parseQueryStringIntoComplexQuery(getQuery()); - if (generatedQuery.isPresent()) { - task = BackgroundTask.wrap(() -> new ParserResult(activeFetcher.performComplexSearch(generatedQuery.get()))) - .withInitialMessage(Localization.lang("Processing %0", getQuery())); - } else { - task = BackgroundTask.wrap(() -> new ParserResult(activeFetcher.performSearch(getQuery().trim()))) - .withInitialMessage(Localization.lang("Processing %0", getQuery())); - } + task = BackgroundTask.wrap(() -> new ParserResult(activeFetcher.performSearch(getQuery().trim()))) + .withInitialMessage(Localization.lang("Processing %0", getQuery().trim())); task.onFailure(dialogService::showErrorDialogAndWait); ImportEntriesDialog dialog = new ImportEntriesDialog(frame.getCurrentLibraryTab().getBibDatabaseContext(), task); diff --git a/src/main/java/org/jabref/logic/importer/PagedSearchBasedFetcher.java b/src/main/java/org/jabref/logic/importer/PagedSearchBasedFetcher.java index 4532ed42f2b..ea547522049 100644 --- a/src/main/java/org/jabref/logic/importer/PagedSearchBasedFetcher.java +++ b/src/main/java/org/jabref/logic/importer/PagedSearchBasedFetcher.java @@ -1,16 +1,37 @@ package org.jabref.logic.importer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.jabref.logic.importer.fetcher.ComplexSearchQuery; import org.jabref.model.entry.BibEntry; import org.jabref.model.paging.Page; public interface PagedSearchBasedFetcher extends SearchBasedFetcher { /** - * @param query search query send to endpoint - * @param pageNumber requested site number + * @param complexSearchQuery the complex query defining all fielded search parameters + * @param pageNumber requested site number indexed from 0 + * @return Page with search results + */ + Page performSearchPaged(ComplexSearchQuery complexSearchQuery, int pageNumber) throws FetcherException; + + /** + * @param complexSearchQuery query string that can be parsed into a complex search query + * @param pageNumber requested site number indexed from 0 * @return Page with search results */ - Page performSearchPaged(String query, int pageNumber) throws FetcherException; + default Page performSearchPaged(String complexSearchQuery, int pageNumber) throws FetcherException { + if (complexSearchQuery.isBlank()) { + return new Page<>(complexSearchQuery, pageNumber, Collections.emptyList()); + } + QueryParser queryParser = new QueryParser(); + Optional generatedQuery = queryParser.parseQueryStringIntoComplexQuery(complexSearchQuery); + // Otherwise just use query as a default term + return this.performSearchPaged(generatedQuery.orElse(ComplexSearchQuery.builder().defaultFieldPhrase(complexSearchQuery).build()), pageNumber); + } /** * @return default pageSize @@ -18,4 +39,14 @@ public interface PagedSearchBasedFetcher extends SearchBasedFetcher { default int getPageSize() { return 20; } + + @Override + default List performSearch(ComplexSearchQuery complexSearchQuery) throws FetcherException { + return new ArrayList<>(performSearchPaged(complexSearchQuery, 0).getContent()); + } + + @Override + default List performSearch(String complexSearchQuery) throws FetcherException { + return new ArrayList<>(performSearchPaged(complexSearchQuery, 0).getContent()); + } } diff --git a/src/main/java/org/jabref/logic/importer/PagedSearchBasedParserFetcher.java b/src/main/java/org/jabref/logic/importer/PagedSearchBasedParserFetcher.java index d825d82c013..7f7b3380f0b 100644 --- a/src/main/java/org/jabref/logic/importer/PagedSearchBasedParserFetcher.java +++ b/src/main/java/org/jabref/logic/importer/PagedSearchBasedParserFetcher.java @@ -1,16 +1,72 @@ package org.jabref.logic.importer; +import java.io.IOException; +import java.io.InputStream; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; +import java.util.List; + +import org.jabref.logic.importer.fetcher.ComplexSearchQuery; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.paging.Page; public interface PagedSearchBasedParserFetcher extends SearchBasedParserFetcher, PagedSearchBasedFetcher { + @Override + default Page performSearchPaged(ComplexSearchQuery complexSearchQuery, int pageNumber) throws FetcherException { + // ADR-0014 + URL urlForQuery; + try { + urlForQuery = getComplexQueryURL(complexSearchQuery, pageNumber); + } catch (URISyntaxException | MalformedURLException e) { + throw new FetcherException("Search URI crafted from complex search query is malformed", e); + } + return new Page<>(complexSearchQuery.toString(), pageNumber, getBibEntries(urlForQuery)); + } + + private List getBibEntries(URL urlForQuery) throws FetcherException { + try (InputStream stream = getUrlDownload(urlForQuery).asInputStream()) { + List fetchedEntries = getParser().parseEntries(stream); + fetchedEntries.forEach(this::doPostCleanup); + return fetchedEntries; + } catch (IOException e) { + throw new FetcherException("A network error occurred while fetching from " + urlForQuery, e); + } catch (ParseException e) { + throw new FetcherException("An internal parser error occurred while fetching from " + urlForQuery, e); + } + } + /** * Constructs a URL based on the query, size and page number. - * @param query the search query - * @param size the size of the page - * @param pageNumber the number of the page - * */ - URL getURLForQuery(String query, int size, int pageNumber) throws URISyntaxException, MalformedURLException, FetcherException; + * + * @param query the search query + * @param pageNumber the number of the page indexed from 0 + */ + URL getURLForQuery(String query, int pageNumber) throws URISyntaxException, MalformedURLException; + + /** + * Constructs a URL based on the query, size and page number. + * + * @param complexSearchQuery the search query + * @param pageNumber the number of the page indexed from 0 + */ + default URL getComplexQueryURL(ComplexSearchQuery complexSearchQuery, int pageNumber) throws URISyntaxException, MalformedURLException { + return getURLForQuery(complexSearchQuery.toString(), pageNumber); + } + + @Override + default List performSearch(ComplexSearchQuery complexSearchQuery) throws FetcherException { + return SearchBasedParserFetcher.super.performSearch(complexSearchQuery); + } + + @Override + default URL getURLForQuery(String query) throws URISyntaxException, MalformedURLException, FetcherException { + return getURLForQuery(query, 0); + } + + @Override + default URL getURLForQuery(ComplexSearchQuery query) throws URISyntaxException, MalformedURLException, FetcherException { + return getComplexQueryURL(query, 0); + } } diff --git a/src/main/java/org/jabref/logic/importer/QueryParser.java b/src/main/java/org/jabref/logic/importer/QueryParser.java index 6fe611f442f..65359122ff2 100644 --- a/src/main/java/org/jabref/logic/importer/QueryParser.java +++ b/src/main/java/org/jabref/logic/importer/QueryParser.java @@ -16,26 +16,23 @@ import org.apache.lucene.search.QueryVisitor; /** - * This class converts a query string written in lucene syntax into a complex search query. + * This class converts a query string written in lucene syntax into a complex query. * - * For simplicity this is limited to fielded data and the boolean AND operator. + * For simplicity this is currently limited to fielded data and the boolean AND operator. */ public class QueryParser { /** * Parses the given query string into a complex query using lucene. - * Note: For unique fields, the alphabetically first instance in the query string is used in the complex query. + * Note: For unique fields, the alphabetically and numerically first instance in the query string is used in the complex query. * - * @param queryString The given query string + * @param query The given query string * @return A complex query containing all fields of the query string - * @throws QueryNodeException Error during parsing */ - public Optional parseQueryStringIntoComplexQuery(String queryString) { + public Optional parseQueryStringIntoComplexQuery(String query) { try { - ComplexSearchQuery.ComplexSearchQueryBuilder builder = ComplexSearchQuery.builder(); - StandardQueryParser parser = new StandardQueryParser(); - Query luceneQuery = parser.parse(queryString, "default"); + Query luceneQuery = parser.parse(query, "default"); Set terms = new HashSet<>(); // This implementation collects all terms from the leaves of the query tree independent of the internal boolean structure // If further capabilities are required in the future the visitor and ComplexSearchQuery has to be adapted accordingly. @@ -44,7 +41,7 @@ public Optional parseQueryStringIntoComplexQuery(String quer List sortedTerms = new ArrayList<>(terms); sortedTerms.sort(Comparator.comparing(Term::text).reversed()); - return Optional.of(ComplexSearchQuery.fromTerms(terms)); + return Optional.of(ComplexSearchQuery.fromTerms(sortedTerms)); } catch (QueryNodeException | IllegalStateException | IllegalArgumentException ex) { return Optional.empty(); } diff --git a/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java b/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java index 8aa8dcf04f1..faeb4ffa6f6 100644 --- a/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java +++ b/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java @@ -1,6 +1,8 @@ package org.jabref.logic.importer; +import java.util.Collections; import java.util.List; +import java.util.Optional; import org.jabref.logic.importer.fetcher.ComplexSearchQuery; import org.jabref.model.entry.BibEntry; @@ -12,21 +14,26 @@ public interface SearchBasedFetcher extends WebFetcher { /** - * Looks for hits which are matched by the given free-text query. + * This method is used to send complex queries using fielded search. * - * @param query search string + * @param complexSearchQuery the complex search query defining all fielded search parameters * @return a list of {@link BibEntry}, which are matched by the query (may be empty) */ - List performSearch(String query) throws FetcherException; + List performSearch(ComplexSearchQuery complexSearchQuery) throws FetcherException; /** - * This method is used to send complex queries using fielded search. + * Looks for hits which are matched by the given free-text query. * - * @param complexSearchQuery the search query defining all fielded search parameters + * @param complexSearchQuery query string that can be parsed into a complex search query * @return a list of {@link BibEntry}, which are matched by the query (may be empty) */ - default List performComplexSearch(ComplexSearchQuery complexSearchQuery) throws FetcherException { - // Default implementation behaves as perform search on all fields concatenated as query - return performSearch(complexSearchQuery.toString()); + default List performSearch(String complexSearchQuery) throws FetcherException { + if (complexSearchQuery.isBlank()) { + return Collections.emptyList(); + } + QueryParser queryParser = new QueryParser(); + Optional generatedQuery = queryParser.parseQueryStringIntoComplexQuery(complexSearchQuery); + // Otherwise just use query as a default term + return this.performSearch(generatedQuery.orElse(ComplexSearchQuery.builder().defaultFieldPhrase(complexSearchQuery).build())); } } diff --git a/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java b/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java index e8b40c963ad..d0817ade6a0 100644 --- a/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java +++ b/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java @@ -5,13 +5,11 @@ import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; -import java.util.Collections; import java.util.List; import org.jabref.logic.cleanup.Formatter; import org.jabref.logic.importer.fetcher.ComplexSearchQuery; import org.jabref.model.entry.BibEntry; -import org.jabref.model.strings.StringUtil; /** * Provides a convenient interface for search-based fetcher, which follow the usual three-step procedure: @@ -23,34 +21,6 @@ */ public interface SearchBasedParserFetcher extends SearchBasedFetcher { - /** - * Constructs a URL based on the query. - * - * @param query the search query - */ - URL getURLForQuery(String query) throws URISyntaxException, MalformedURLException, FetcherException; - - /** - * Returns the parser used to convert the response to a list of {@link BibEntry}. - */ - Parser getParser(); - - @Override - default List performSearch(String query) throws FetcherException { - if (StringUtil.isBlank(query)) { - return Collections.emptyList(); - } - - // ADR-0014 - URL urlForQuery; - try { - urlForQuery = getURLForQuery(query); - } catch (URISyntaxException | MalformedURLException | FetcherException e) { - throw new FetcherException(String.format("Search URI crafted from query %s is malformed", query), e); - } - return getBibEntries(urlForQuery); - } - /** * This method is used to send queries with advanced URL parameters. * This method is necessary as the performSearch method does not support certain URL parameters that are used for @@ -59,11 +29,11 @@ default List performSearch(String query) throws FetcherException { * @param complexSearchQuery the search query defining all fielded search parameters */ @Override - default List performComplexSearch(ComplexSearchQuery complexSearchQuery) throws FetcherException { + default List performSearch(ComplexSearchQuery complexSearchQuery) throws FetcherException { // ADR-0014 URL urlForQuery; try { - urlForQuery = getComplexQueryURL(complexSearchQuery); + urlForQuery = getURLForQuery(complexSearchQuery); } catch (URISyntaxException | MalformedURLException | FetcherException e) { throw new FetcherException("Search URI crafted from complex search query is malformed", e); } @@ -82,12 +52,23 @@ private List getBibEntries(URL urlForQuery) throws FetcherException { } } - default URL getComplexQueryURL(ComplexSearchQuery complexSearchQuery) throws URISyntaxException, MalformedURLException, FetcherException { - // Default implementation behaves as getURLForQuery using the default field phrases as query - List defaultPhrases = complexSearchQuery.getDefaultFieldPhrases(); - return this.getURLForQuery(String.join(" ", defaultPhrases)); + default URL getURLForQuery(ComplexSearchQuery query) throws URISyntaxException, MalformedURLException, FetcherException { + // Default implementation behaves as getURLForQuery treating complex query as plain string query + return this.getURLForQuery(query.toString()); } + /** + * Returns the parser used to convert the response to a list of {@link BibEntry}. + */ + Parser getParser(); + + /** + * Constructs a URL based on the query. + * + * @param query the search query + */ + URL getURLForQuery(String query) throws URISyntaxException, MalformedURLException, FetcherException; + /** * Performs a cleanup of the fetched entry. *

diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ArXiv.java b/src/main/java/org/jabref/logic/importer/fetcher/ArXiv.java index 10ab03b13e5..9b640cdd7f0 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ArXiv.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ArXiv.java @@ -22,7 +22,7 @@ import org.jabref.logic.importer.IdBasedFetcher; import org.jabref.logic.importer.IdFetcher; import org.jabref.logic.importer.ImportFormatPreferences; -import org.jabref.logic.importer.SearchBasedFetcher; +import org.jabref.logic.importer.PagedSearchBasedFetcher; import org.jabref.logic.util.io.XMLUtil; import org.jabref.logic.util.strings.StringSimilarity; import org.jabref.model.entry.BibEntry; @@ -31,6 +31,7 @@ import org.jabref.model.entry.identifier.ArXivIdentifier; import org.jabref.model.entry.identifier.DOI; import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.model.paging.Page; import org.jabref.model.strings.StringUtil; import org.jabref.model.util.OptionalUtil; @@ -52,7 +53,7 @@ * arxiv2bib which is live * dspace-portalmec */ -public class ArXiv implements FulltextFetcher, SearchBasedFetcher, IdBasedFetcher, IdFetcher { +public class ArXiv implements FulltextFetcher, PagedSearchBasedFetcher, IdBasedFetcher, IdFetcher { private static final Logger LOGGER = LoggerFactory.getLogger(ArXiv.class); @@ -153,8 +154,8 @@ private List searchForEntries(BibEntry entry) throws FetcherExceptio return Collections.emptyList(); } - private List searchForEntries(String searchQuery) throws FetcherException { - return queryApi(searchQuery, Collections.emptyList(), 0, 10); + private List searchForEntries(String searchQuery, int pageNumber) throws FetcherException { + return queryApi(searchQuery, Collections.emptyList(), getPageSize() * pageNumber, getPageSize()); } private List queryApi(String searchQuery, List ids, int start, int maxResults) @@ -248,13 +249,6 @@ public Optional getHelpPage() { return Optional.of(HelpFile.FETCHER_OAI2_ARXIV); } - @Override - public List performSearch(String query) throws FetcherException { - return searchForEntries(query).stream().map( - (arXivEntry) -> arXivEntry.toBibEntry(importFormatPreferences.getKeywordSeparator())) - .collect(Collectors.toList()); - } - /** * Constructs a complex query string using the field prefixes specified at https://arxiv.org/help/api/user-manual * @@ -262,17 +256,21 @@ public List performSearch(String query) throws FetcherException { * @return A list of entries matching the complex query */ @Override - public List performComplexSearch(ComplexSearchQuery complexSearchQuery) throws FetcherException { + public Page performSearchPaged(ComplexSearchQuery complexSearchQuery, int pageNumber) throws FetcherException { List searchTerms = new ArrayList<>(); complexSearchQuery.getAuthors().forEach(author -> searchTerms.add("au:" + author)); complexSearchQuery.getTitlePhrases().forEach(title -> searchTerms.add("ti:" + title)); - complexSearchQuery.getTitlePhrases().forEach(abstr -> searchTerms.add("abs:" + abstr)); + complexSearchQuery.getAbstractPhrases().forEach(abstr -> searchTerms.add("abs:" + abstr)); complexSearchQuery.getJournal().ifPresent(journal -> searchTerms.add("jr:" + journal)); // Since ArXiv API does not support year search, we ignore the year related terms complexSearchQuery.getToYear().ifPresent(year -> searchTerms.add(year.toString())); searchTerms.addAll(complexSearchQuery.getDefaultFieldPhrases()); String complexQueryString = String.join(" AND ", searchTerms); - return performSearch(complexQueryString); + + List searchResult = searchForEntries(complexQueryString, pageNumber).stream() + .map((arXivEntry) -> arXivEntry.toBibEntry(importFormatPreferences.getKeywordSeparator())) + .collect(Collectors.toList()); + return new Page<>(complexQueryString, pageNumber, searchResult); } @Override diff --git a/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java b/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java index 93bfd874622..a266418b351 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java @@ -83,20 +83,12 @@ public String getName() { * @return URL which points to a search request for given query */ @Override - public URL getURLForQuery(String query) throws URISyntaxException, MalformedURLException, FetcherException { + public URL getURLForQuery(String query, int pageNumber) throws URISyntaxException, MalformedURLException { URIBuilder builder = new URIBuilder(API_SEARCH_URL); builder.addParameter("q", query); builder.addParameter("fl", "bibcode"); - return builder.build().toURL(); - } - - @Override - public URL getURLForQuery(String query, int size, int pageNumber) throws URISyntaxException, MalformedURLException, FetcherException { - URIBuilder builder = new URIBuilder(API_SEARCH_URL); - builder.addParameter("q", query); - builder.addParameter("fl", "bibcode"); - builder.addParameter("rows", String.valueOf(size)); - builder.addParameter("start", String.valueOf(size * pageNumber)); + builder.addParameter("rows", String.valueOf(getPageSize())); + builder.addParameter("start", String.valueOf(getPageSize() * pageNumber)); return builder.build().toURL(); } @@ -105,7 +97,7 @@ public URL getURLForQuery(String query, int size, int pageNumber) throws URISynt * @return URL which points to a search request for given entry */ @Override - public URL getURLForEntry(BibEntry entry) throws URISyntaxException, MalformedURLException, FetcherException { + public URL getURLForEntry(BibEntry entry) throws URISyntaxException, MalformedURLException { StringBuilder stringBuilder = new StringBuilder(); Optional title = entry.getFieldOrAlias(StandardField.TITLE).map(t -> "title:\"" + t + "\""); @@ -192,23 +184,6 @@ public List performSearch(BibEntry entry) throws FetcherException { } } - @Override - public List performSearch(String query) throws FetcherException { - - if (StringUtil.isBlank(query)) { - return Collections.emptyList(); - } - - try { - List bibcodes = fetchBibcodes(getURLForQuery(query)); - return performSearchByIds(bibcodes); - } catch (URISyntaxException e) { - throw new FetcherException("Search URI is malformed", e); - } catch (IOException e) { - throw new FetcherException("A network error occurred", e); - } - } - /** * @param url search ul for which bibcode will be returned * @return list of bibcodes matching the search request. May be empty @@ -299,15 +274,12 @@ private List performSearchByIds(Collection identifiers) throws } @Override - public Page performSearchPaged(String query, int pageNumber) throws FetcherException { - - if (StringUtil.isBlank(query)) { - return new Page<>(query, pageNumber); - } + public Page performSearchPaged(ComplexSearchQuery complexSearchQuery, int pageNumber) throws FetcherException { try { - List bibcodes = fetchBibcodes(getURLForQuery(query, getPageSize(), pageNumber)); + // This is currently just interpreting the complex query as a default string query + List bibcodes = fetchBibcodes(getComplexQueryURL(complexSearchQuery, pageNumber)); Collection results = performSearchByIds(bibcodes); - return new Page<>(query, pageNumber, results); + return new Page<>(complexSearchQuery.toString(), pageNumber, results); } catch (URISyntaxException e) { throw new FetcherException("Search URI is malformed", e); } catch (IOException e) { diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java index 750a6d68e46..ade12d2d8b9 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java @@ -34,7 +34,7 @@ private ComplexSearchQuery(List defaultField, List authors, List this.singleYear = singleYear; } - public static ComplexSearchQuery fromTerms(Collection terms) { + public static ComplexSearchQuery fromTerms(List terms) { ComplexSearchQueryBuilder builder = ComplexSearchQuery.builder(); terms.forEach(term -> { String termText = term.text(); @@ -46,7 +46,8 @@ public static ComplexSearchQuery fromTerms(Collection terms) { case "year" -> builder.singleYear(Integer.valueOf(termText)); case "year-range" -> builder.parseYearRange(termText); case "default" -> builder.defaultFieldPhrase(termText); - default -> builder.defaultFieldPhrase(term.field() + ":" + termText); + // add unknown field as default field + default -> builder.defaultFieldPhrase(termText); } }); return builder.build(); @@ -248,7 +249,7 @@ public ComplexSearchQueryBuilder terms(Collection terms) { * Instantiates the AdvancesSearchConfig from the provided Builder parameters * If all text fields are empty an empty optional is returned * - * @return AdvancedSearchConfig instance with the fields set to the values defined in the building instance. + * @return ComplexSearchQuery instance with the fields set to the values defined in the building instance. * @throws IllegalStateException An IllegalStateException is thrown in case all text search fields are empty. * See: https://softwareengineering.stackexchange.com/questions/241309/builder-pattern-when-to-fail/241320#241320 */ diff --git a/src/main/java/org/jabref/logic/importer/fetcher/CompositeSearchBasedFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/CompositeSearchBasedFetcher.java index b0dbf1c5184..3ce32ebeee6 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/CompositeSearchBasedFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/CompositeSearchBasedFetcher.java @@ -36,13 +36,13 @@ public CompositeSearchBasedFetcher(Set searchBasedFetchers, } @Override - public List performSearch(String query) { + public List performSearch(ComplexSearchQuery complexSearchQuery) { ImportCleanup cleanup = new ImportCleanup(BibDatabaseMode.BIBTEX); // All entries have to be converted into one format, this is necessary for the format conversion return fetchers.parallelStream() .flatMap(searchBasedFetcher -> { try { - return searchBasedFetcher.performSearch(query).stream(); + return searchBasedFetcher.performSearch(complexSearchQuery).stream(); } catch (FetcherException e) { LOGGER.warn(String.format("%s API request failed", searchBasedFetcher.getName()), e); return Stream.empty(); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java b/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java index a35e1353373..58d6c8546b7 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java @@ -17,13 +17,14 @@ import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.FulltextFetcher; import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.PagedSearchBasedFetcher; import org.jabref.logic.importer.ParserResult; -import org.jabref.logic.importer.SearchBasedFetcher; import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.logic.l10n.Localization; import org.jabref.logic.net.URLDownload; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; +import org.jabref.model.paging.Page; import org.jabref.model.util.DummyFileUpdateMonitor; import org.apache.http.client.utils.URIBuilder; @@ -38,7 +39,7 @@ *

* Search String infos: https://scholar.google.com/intl/en/scholar/help.html#searching */ -public class GoogleScholar implements FulltextFetcher, SearchBasedFetcher { +public class GoogleScholar implements FulltextFetcher, PagedSearchBasedFetcher { private static final Logger LOGGER = LoggerFactory.getLogger(GoogleScholar.class); private static final Pattern LINK_TO_BIB_PATTERN = Pattern.compile("(https:\\/\\/scholar.googleusercontent.com\\/scholar.bib[^\"]*)"); @@ -126,83 +127,6 @@ public Optional getHelpPage() { return Optional.of(HelpFile.FETCHER_GOOGLE_SCHOLAR); } - @Override - public List performSearch(String query) throws FetcherException { - LOGGER.debug("Using URL {}", query); - obtainAndModifyCookie(); - List foundEntries = new ArrayList<>(20); - - URIBuilder uriBuilder = null; - try { - uriBuilder = new URIBuilder(BASIC_SEARCH_URL); - } catch (URISyntaxException e) { - throw new FetcherException("Error while fetching from " + getName() + " at URL " + BASIC_SEARCH_URL, e); - } - - uriBuilder.addParameter("hl", "en"); - uriBuilder.addParameter("btnG", "Search"); - uriBuilder.addParameter("q", query); - String queryURL = uriBuilder.toString(); - - try { - addHitsFromQuery(foundEntries, queryURL); - } catch (IOException e) { - // if there are too much requests from the same IP address google is answering with a 503 and redirecting to a captcha challenge - // The caught IOException looks for example like this: - // java.io.IOException: Server returned HTTP response code: 503 for URL: https://ipv4.google.com/sorry/index?continue=https://scholar.google.com/scholar%3Fhl%3Den%26btnG%3DSearch%26q%3Dbpmn&hl=en&q=CGMSBI0NBDkYuqy9wAUiGQDxp4NLQCWbIEY1HjpH5zFJhv4ANPGdWj0 - if (e.getMessage().contains("Server returned HTTP response code: 503 for URL")) { - throw new FetcherException("Fetching from Google Scholar at URL " + queryURL + " failed.", - Localization.lang("This might be caused by reaching the traffic limitation of Google Scholar (see 'Help' for details)."), e); - } else { - throw new FetcherException("Error while fetching from " + getName() + " at URL " + queryURL, e); - } - } - - return foundEntries; - } - - @Override - public List performComplexSearch(ComplexSearchQuery complexSearchQuery) throws FetcherException { - try { - obtainAndModifyCookie(); - List foundEntries = new ArrayList<>(10); - - URIBuilder uriBuilder = new URIBuilder(BASIC_SEARCH_URL); - uriBuilder.addParameter("hl", "en"); - uriBuilder.addParameter("btnG", "Search"); - uriBuilder.addParameter("q", constructComplexQueryString(complexSearchQuery)); - complexSearchQuery.getFromYear().ifPresent(year -> uriBuilder.addParameter("as_ylo", year.toString())); - complexSearchQuery.getToYear().ifPresent(year -> uriBuilder.addParameter("as_yhi", year.toString())); - complexSearchQuery.getSingleYear().ifPresent(year -> { - uriBuilder.addParameter("as_ylo", year.toString()); - uriBuilder.addParameter("as_yhi", year.toString()); - }); - - try { - addHitsFromQuery(foundEntries, uriBuilder.toString()); - - if (foundEntries.size() == 10) { - uriBuilder.addParameter("start", "10"); - addHitsFromQuery(foundEntries, uriBuilder.toString()); - } - } catch (IOException e) { - LOGGER.info("IOException for URL {}", uriBuilder.toString()); - // if there are too much requests from the same IP adress google is answering with a 503 and redirecting to a captcha challenge - // The caught IOException looks for example like this: - // java.io.IOException: Server returned HTTP response code: 503 for URL: https://ipv4.google.com/sorry/index?continue=https://scholar.google.com/scholar%3Fhl%3Den%26btnG%3DSearch%26q%3Dbpmn&hl=en&q=CGMSBI0NBDkYuqy9wAUiGQDxp4NLQCWbIEY1HjpH5zFJhv4ANPGdWj0 - if (e.getMessage().contains("Server returned HTTP response code: 503 for URL")) { - throw new FetcherException("Fetching from Google Scholar failed.", - Localization.lang("This might be caused by reaching the traffic limitation of Google Scholar (see 'Help' for details)."), e); - } else { - throw new FetcherException("Error while fetching from " + getName(), e); - } - } - return foundEntries; - } catch (URISyntaxException e) { - throw new FetcherException("Error while fetching from " + getName(), e); - } - } - private String constructComplexQueryString(ComplexSearchQuery complexSearchQuery) { List searchTerms = new ArrayList<>(); searchTerms.addAll(complexSearchQuery.getDefaultFieldPhrases()); @@ -259,4 +183,49 @@ private void obtainAndModifyCookie() throws FetcherException { throw new FetcherException("Cookie configuration for Google Scholar failed.", e); } } + + @Override + public Page performSearchPaged(ComplexSearchQuery complexSearchQuery, int pageNumber) throws FetcherException { + try { + obtainAndModifyCookie(); + List foundEntries = new ArrayList<>(10); + + String complexQueryString = constructComplexQueryString(complexSearchQuery); + URIBuilder uriBuilder = new URIBuilder(BASIC_SEARCH_URL); + uriBuilder.addParameter("hl", "en"); + uriBuilder.addParameter("btnG", "Search"); + uriBuilder.addParameter("q", complexQueryString); + uriBuilder.addParameter("start", String.valueOf(pageNumber * getPageSize())); + uriBuilder.addParameter("num", String.valueOf(getPageSize())); + complexSearchQuery.getFromYear().ifPresent(year -> uriBuilder.addParameter("as_ylo", year.toString())); + complexSearchQuery.getToYear().ifPresent(year -> uriBuilder.addParameter("as_yhi", year.toString())); + complexSearchQuery.getSingleYear().ifPresent(year -> { + uriBuilder.addParameter("as_ylo", year.toString()); + uriBuilder.addParameter("as_yhi", year.toString()); + }); + + try { + addHitsFromQuery(foundEntries, uriBuilder.toString()); + + if (foundEntries.size() == 10) { + uriBuilder.addParameter("start", "10"); + addHitsFromQuery(foundEntries, uriBuilder.toString()); + } + } catch (IOException e) { + LOGGER.info("IOException for URL {}", uriBuilder.toString()); + // if there are too much requests from the same IP adress google is answering with a 503 and redirecting to a captcha challenge + // The caught IOException looks for example like this: + // java.io.IOException: Server returned HTTP response code: 503 for URL: https://ipv4.google.com/sorry/index?continue=https://scholar.google.com/scholar%3Fhl%3Den%26btnG%3DSearch%26q%3Dbpmn&hl=en&q=CGMSBI0NBDkYuqy9wAUiGQDxp4NLQCWbIEY1HjpH5zFJhv4ANPGdWj0 + if (e.getMessage().contains("Server returned HTTP response code: 503 for URL")) { + throw new FetcherException("Fetching from Google Scholar failed.", + Localization.lang("This might be caused by reaching the traffic limitation of Google Scholar (see 'Help' for details)."), e); + } else { + throw new FetcherException("Error while fetching from " + getName(), e); + } + } + return new Page<>(complexQueryString, pageNumber, foundEntries); + } catch (URISyntaxException e) { + throw new FetcherException("Error while fetching from " + getName(), e); + } + } } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/GrobidCitationFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/GrobidCitationFetcher.java index 7a57535efe9..61218575668 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/GrobidCitationFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/GrobidCitationFetcher.java @@ -66,8 +66,10 @@ private Optional parseBibToBibEntry(String bibtexString) { } @Override - public List performSearch(String query) throws FetcherException { + public List performSearch(ComplexSearchQuery complexSearchQuery) throws FetcherException { List bibEntries = null; + // This just treats the complex query like a normal string query until it it implemented correctly + String query = complexSearchQuery.toString(); try { bibEntries = Arrays .stream(query.split("\\r\\r+|\\n\\n+|\\r\\n(\\r\\n)+")) diff --git a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java index d4329fb3d7d..d89223908a0 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java @@ -17,8 +17,8 @@ import org.jabref.logic.help.HelpFile; import org.jabref.logic.importer.FulltextFetcher; import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.PagedSearchBasedParserFetcher; import org.jabref.logic.importer.Parser; -import org.jabref.logic.importer.SearchBasedParserFetcher; import org.jabref.logic.net.URLDownload; import org.jabref.logic.util.BuildInfo; import org.jabref.logic.util.OS; @@ -41,7 +41,7 @@ * * @implNote API documentation */ -public class IEEE implements FulltextFetcher, SearchBasedParserFetcher { +public class IEEE implements FulltextFetcher, PagedSearchBasedParserFetcher { private static final Logger LOGGER = LoggerFactory.getLogger(IEEE.class); private static final String STAMP_BASE_STRING_DOCUMENT = "/stamp/stamp.jsp?tp=&arnumber="; @@ -191,17 +191,6 @@ public TrustLevel getTrustLevel() { return TrustLevel.PUBLISHER; } - @Override - public URL getURLForQuery(String query) throws URISyntaxException, MalformedURLException { - URIBuilder uriBuilder = new URIBuilder("https://ieeexploreapi.ieee.org/api/v1/search/articles"); - uriBuilder.addParameter("apikey", API_KEY); - uriBuilder.addParameter("querytext", query); - - URLDownload.bypassSSLVerification(); - - return uriBuilder.build().toURL(); - } - @Override public Parser getParser() { return inputStream -> { @@ -233,9 +222,27 @@ public Optional getHelpPage() { } @Override - public URL getComplexQueryURL(ComplexSearchQuery complexSearchQuery) throws URISyntaxException, MalformedURLException { + public URL getURLForQuery(String query, int pageNumber) throws URISyntaxException, MalformedURLException { URIBuilder uriBuilder = new URIBuilder("https://ieeexploreapi.ieee.org/api/v1/search/articles"); uriBuilder.addParameter("apikey", API_KEY); + uriBuilder.addParameter("querytext", query); + uriBuilder.addParameter("max_records", String.valueOf(getPageSize())); + // Starts to index at 1 for the first entry + uriBuilder.addParameter("start_record", String.valueOf(getPageSize() * pageNumber) + 1); + + URLDownload.bypassSSLVerification(); + + return uriBuilder.build().toURL(); + } + + @Override + public URL getComplexQueryURL(ComplexSearchQuery complexSearchQuery, int pageNumber) throws URISyntaxException, MalformedURLException { + URIBuilder uriBuilder = new URIBuilder("https://ieeexploreapi.ieee.org/api/v1/search/articles"); + uriBuilder.addParameter("apikey", API_KEY); + uriBuilder.addParameter("max_records", String.valueOf(getPageSize())); + // Starts to index at 1 for the first entry + uriBuilder.addParameter("start_record", String.valueOf(getPageSize() * pageNumber) + 1); + if (!complexSearchQuery.getDefaultFieldPhrases().isEmpty()) { uriBuilder.addParameter("querytext", String.join(" AND ", complexSearchQuery.getDefaultFieldPhrases())); } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/JstorFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/JstorFetcher.java index 9f8cb7de04a..eb50f3fef1d 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/JstorFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/JstorFetcher.java @@ -48,34 +48,34 @@ public URL getURLForQuery(String query) throws URISyntaxException, MalformedURLE } @Override - public URL getComplexQueryURL(ComplexSearchQuery complexSearchQuery) throws URISyntaxException, MalformedURLException, FetcherException { + public URL getURLForQuery(ComplexSearchQuery query) throws URISyntaxException, MalformedURLException, FetcherException { URIBuilder uriBuilder = new URIBuilder(SEARCH_HOST); StringBuilder stringBuilder = new StringBuilder(); - if (!complexSearchQuery.getDefaultFieldPhrases().isEmpty()) { - stringBuilder.append(complexSearchQuery.getDefaultFieldPhrases()); + if (!query.getDefaultFieldPhrases().isEmpty()) { + stringBuilder.append(query.getDefaultFieldPhrases()); } - if (!complexSearchQuery.getAuthors().isEmpty()) { - for (String author : complexSearchQuery.getAuthors()) { + if (!query.getAuthors().isEmpty()) { + for (String author : query.getAuthors()) { stringBuilder.append("au:").append(author); } } - if (!complexSearchQuery.getTitlePhrases().isEmpty()) { - for (String title : complexSearchQuery.getTitlePhrases()) { + if (!query.getTitlePhrases().isEmpty()) { + for (String title : query.getTitlePhrases()) { stringBuilder.append("ti:").append(title); } } - if (complexSearchQuery.getJournal().isPresent()) { - stringBuilder.append("pt:").append(complexSearchQuery.getJournal().get()); + if (query.getJournal().isPresent()) { + stringBuilder.append("pt:").append(query.getJournal().get()); } - if (complexSearchQuery.getSingleYear().isPresent()) { - uriBuilder.addParameter("sd", String.valueOf(complexSearchQuery.getSingleYear().get())); - uriBuilder.addParameter("ed", String.valueOf(complexSearchQuery.getSingleYear().get())); + if (query.getSingleYear().isPresent()) { + uriBuilder.addParameter("sd", String.valueOf(query.getSingleYear().get())); + uriBuilder.addParameter("ed", String.valueOf(query.getSingleYear().get())); } - if (complexSearchQuery.getFromYear().isPresent()) { - uriBuilder.addParameter("sd", String.valueOf(complexSearchQuery.getFromYear().get())); + if (query.getFromYear().isPresent()) { + uriBuilder.addParameter("sd", String.valueOf(query.getFromYear().get())); } - if (complexSearchQuery.getToYear().isPresent()) { - uriBuilder.addParameter("ed", String.valueOf(complexSearchQuery.getToYear().get())); + if (query.getToYear().isPresent()) { + uriBuilder.addParameter("ed", String.valueOf(query.getToYear().get())); } uriBuilder.addParameter("Query", stringBuilder.toString()); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/MedlineFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/MedlineFetcher.java index 0050bc1fbf7..a4553bc876b 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/MedlineFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/MedlineFetcher.java @@ -10,7 +10,6 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; -import java.util.LinkedList; import java.util.List; import java.util.Optional; @@ -159,16 +158,15 @@ public void doPostCleanup(BibEntry entry) { } @Override - public List performSearch(String query) throws FetcherException { - List entryList = new LinkedList<>(); + public List performSearch(ComplexSearchQuery complexSearchQuery) throws FetcherException { + List entryList; + String query = complexSearchQuery.toString(); - if (query.isEmpty()) { + if (query.isBlank()) { return Collections.emptyList(); } else { - String searchTerm = replaceCommaWithAND(query); - // searching for pubmed ids matching the query - List idList = getPubMedIdsFromQuery(searchTerm); + List idList = getPubMedIdsFromQuery(query); if (idList.isEmpty()) { LOGGER.info("No results found."); @@ -186,13 +184,12 @@ public List performSearch(String query) throws FetcherException { } } - private URL createSearchUrl(String term) throws URISyntaxException, MalformedURLException { - term = replaceCommaWithAND(term); + private URL createSearchUrl(String query) throws URISyntaxException, MalformedURLException { URIBuilder uriBuilder = new URIBuilder(SEARCH_URL); uriBuilder.addParameter("db", "pubmed"); uriBuilder.addParameter("sort", "relevance"); uriBuilder.addParameter("retmax", String.valueOf(NUMBER_TO_FETCH)); - uriBuilder.addParameter("term", term); + uriBuilder.addParameter("term", replaceCommaWithAND(query)); return uriBuilder.build().toURL(); } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java index 2b15fd4eb4e..a547dbe2175 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java @@ -11,8 +11,8 @@ import java.util.stream.Collectors; import org.jabref.logic.help.HelpFile; +import org.jabref.logic.importer.PagedSearchBasedParserFetcher; import org.jabref.logic.importer.Parser; -import org.jabref.logic.importer.SearchBasedParserFetcher; import org.jabref.logic.util.BuildInfo; import org.jabref.logic.util.OS; import org.jabref.model.entry.BibEntry; @@ -33,7 +33,7 @@ * * @implNote see API documentation for more details */ -public class SpringerFetcher implements SearchBasedParserFetcher { +public class SpringerFetcher implements PagedSearchBasedParserFetcher { private static final Logger LOGGER = LoggerFactory.getLogger(SpringerFetcher.class); @@ -158,18 +158,18 @@ public Optional getHelpPage() { } @Override - public URL getURLForQuery(String query) throws URISyntaxException, MalformedURLException { + public URL getURLForQuery(String query, int pageNumber) throws URISyntaxException, MalformedURLException { URIBuilder uriBuilder = new URIBuilder(API_URL); uriBuilder.addParameter("q", query); // Search query uriBuilder.addParameter("api_key", API_KEY); // API key - uriBuilder.addParameter("p", "20"); // Number of results to return - // uriBuilder.addParameter("s", "1"); // Start item (not needed at the moment) + uriBuilder.addParameter("s", String.valueOf(getPageSize() * pageNumber + 1)); // Start entry, starts indexing at 1 + uriBuilder.addParameter("p", String.valueOf(getPageSize())); // Page size return uriBuilder.build().toURL(); } @Override - public URL getComplexQueryURL(ComplexSearchQuery complexSearchQuery) throws URISyntaxException, MalformedURLException { - return getURLForQuery(constructComplexQueryString(complexSearchQuery)); + public URL getComplexQueryURL(ComplexSearchQuery complexSearchQuery, int pageNumber) throws URISyntaxException, MalformedURLException { + return getURLForQuery(constructComplexQueryString(complexSearchQuery), pageNumber); } private String constructComplexQueryString(ComplexSearchQuery complexSearchQuery) { diff --git a/src/test/java/org/jabref/logic/importer/QueryParserTest.java b/src/test/java/org/jabref/logic/importer/QueryParserTest.java index 624117d5289..970788e86c3 100644 --- a/src/test/java/org/jabref/logic/importer/QueryParserTest.java +++ b/src/test/java/org/jabref/logic/importer/QueryParserTest.java @@ -10,49 +10,63 @@ class QueryParserTest { QueryParser parser = new QueryParser(); @Test - public void convertAuthorField() throws Exception { + public void convertAuthorField() { ComplexSearchQuery searchQuery = parser.parseQueryStringIntoComplexQuery("author:\"Igor Steinmacher\"").get(); ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().author("\"Igor Steinmacher\"").build(); assertEquals(expectedQuery, searchQuery); } @Test - public void convertDefaultField() throws Exception { + public void convertDefaultField() { ComplexSearchQuery searchQuery = parser.parseQueryStringIntoComplexQuery("\"default value\"").get(); ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().defaultFieldPhrase("\"default value\"").build(); assertEquals(expectedQuery, searchQuery); } @Test - public void convertExplicitDefaultField() throws Exception { + public void convertExplicitDefaultField() { ComplexSearchQuery searchQuery = parser.parseQueryStringIntoComplexQuery("default:\"default value\"").get(); ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().defaultFieldPhrase("\"default value\"").build(); assertEquals(expectedQuery, searchQuery); } @Test - public void convertJournalField() throws Exception { - ComplexSearchQuery searchQuery = parser.parseQueryStringIntoComplexQuery("journal:\"Nature\"").get(); + public void convertJournalField() { + ComplexSearchQuery searchQuery = parser.parseQueryStringIntoComplexQuery("journal:Nature").get(); ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().journal("\"Nature\"").build(); assertEquals(expectedQuery, searchQuery); } @Test - public void convertYearField() throws Exception { + public void convertAlphabeticallyFirstJournalField() { + ComplexSearchQuery searchQuery = parser.parseQueryStringIntoComplexQuery("journal:Nature journal:\"Complex Networks\"").get(); + ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().journal("\"Complex Networks\"").build(); + assertEquals(expectedQuery, searchQuery); + } + + @Test + public void convertYearField() { ComplexSearchQuery searchQuery = parser.parseQueryStringIntoComplexQuery("year:2015").get(); ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().singleYear(2015).build(); assertEquals(expectedQuery, searchQuery); } @Test - public void convertYearRangeField() throws Exception { + public void convertNumericallyFirstYearField() { + ComplexSearchQuery searchQuery = parser.parseQueryStringIntoComplexQuery("year:2015 year:2014").get(); + ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().singleYear(2014).build(); + assertEquals(expectedQuery, searchQuery); + } + + @Test + public void convertYearRangeField() { ComplexSearchQuery searchQuery = parser.parseQueryStringIntoComplexQuery("year-range:2012-2015").get(); ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().fromYearAndToYear(2012, 2015).build(); assertEquals(expectedQuery, searchQuery); } @Test - public void convertMultipleValuesWithTheSameField() throws Exception { + public void convertMultipleValuesWithTheSameField() { ComplexSearchQuery searchQuery = parser.parseQueryStringIntoComplexQuery("author:\"Igor Steinmacher\" author:\"Christoph Treude\"").get(); ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().author("\"Igor Steinmacher\"").author("\"Christoph Treude\"").build(); assertEquals(expectedQuery, searchQuery); diff --git a/src/test/java/org/jabref/logic/importer/fetcher/ArXivTest.java b/src/test/java/org/jabref/logic/importer/fetcher/ArXivTest.java index 20b0b6e401e..9fdf8b15ee8 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/ArXivTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/ArXivTest.java @@ -8,6 +8,7 @@ import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.PagedSearchBasedFetcher; import org.jabref.logic.importer.SearchBasedFetcher; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; @@ -26,7 +27,7 @@ import static org.mockito.Mockito.when; @FetcherTest -class ArXivTest implements SearchBasedFetcherCapabilityTest { +class ArXivTest implements SearchBasedFetcherCapabilityTest, PagedSearchFetcherTest { private ArXiv fetcher; private BibEntry entry; private BibEntry sliceTheoremPaper; @@ -135,13 +136,13 @@ void findFullTextTrustLevel() { @Test void searchEntryByPartOfTitle() throws Exception { assertEquals(Collections.singletonList(sliceTheoremPaper), - fetcher.performSearch("ti:\"slice theorem for Frechet\"")); + fetcher.performSearch("title:\"slice theorem for Frechet\"")); } @Test void searchEntryByPartOfTitleWithAcuteAccent() throws Exception { assertEquals(Collections.singletonList(sliceTheoremPaper), - fetcher.performSearch("ti:\"slice theorem for Fréchet\"")); + fetcher.performSearch("title:\"slice theorem for Fréchet\"")); } @Test @@ -255,8 +256,8 @@ public String getTestJournal() { */ @Test public void supportsPhraseSearch() throws Exception { - List resultWithPhraseSearch = fetcher.performSearch("ti:\"Taxonomy of Distributed\""); - List resultWithOutPhraseSearch = fetcher.performSearch("ti:Taxonomy AND ti:of AND ti:Distributed"); + List resultWithPhraseSearch = fetcher.performSearch("title:\"Taxonomy of Distributed\""); + List resultWithOutPhraseSearch = fetcher.performSearch("title:Taxonomy AND title:of AND title:Distributed"); // Phrase search result has to be subset of the default search result assertTrue(resultWithOutPhraseSearch.containsAll(resultWithPhraseSearch)); } @@ -278,12 +279,17 @@ public void supportsPhraseSearchAndMatchesExact() throws Exception { .withField(StandardField.EPRINTCLASS, "cs.DC") .withField(StandardField.KEYWORDS, "cs.DC, cs.LG"); - List resultWithPhraseSearch = fetcher.performSearch("ti:\"Taxonomy of Distributed\""); + List resultWithPhraseSearch = fetcher.performSearch("title:\"Taxonomy of Distributed\""); // There is only a single paper found by searching that contains the exact sequence "Taxonomy of Distributed" in the title. assertEquals(Collections.singletonList(expected), resultWithPhraseSearch); } + @Override + public PagedSearchBasedFetcher getPagedFetcher() { + return fetcher; + } + @Test public void supportsBooleanANDSearch() throws Exception { BibEntry expected = new BibEntry(StandardEntryType.Article) @@ -299,7 +305,7 @@ public void supportsBooleanANDSearch() throws Exception { .withField(StandardField.EPRINTCLASS, "q-bio.TO") .withField(StandardField.KEYWORDS, "q-bio.TO"); - List result = fetcher.performSearch("au:\"Tobias Büscher\" AND ti:\"Instability and fingering of interfaces\""); + List result = fetcher.performSearch("author:\"Tobias Büscher\" AND title:\"Instability and fingering of interfaces\""); // There is only one paper authored by Tobias Büscher with that phrase in the title assertEquals(Collections.singletonList(expected), result); diff --git a/src/test/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystemTest.java b/src/test/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystemTest.java index 56d033e3046..7f5c708047f 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystemTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystemTest.java @@ -5,6 +5,7 @@ import org.jabref.logic.bibtex.FieldContentFormatterPreferences; import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.PagedSearchBasedFetcher; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.StandardEntryType; @@ -16,13 +17,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @FetcherTest -public class AstrophysicsDataSystemTest { +public class AstrophysicsDataSystemTest implements PagedSearchFetcherTest { private AstrophysicsDataSystem fetcher; private BibEntry diezSliceTheoremEntry; @@ -215,11 +215,8 @@ public void performSearchByQueryPaged_invalidAuthorsReturnEmptyPages() throws Ex assertEquals(0, page5.getSize(), "fetcher doesnt return empty pages for invalid author"); } - @Test - public void performSearchByQueryPaged_twoPagesNotEqual() throws Exception { - Page page = fetcher.performSearchPaged("author:\"A\"", 0); - Page page2 = fetcher.performSearchPaged("author:\"A\"", 1); - // This tests if the fetcher actually performs paging - assertNotEquals(page.getContent(), page2.getContent(), "Two consecutive pages shouldn't be equal"); + @Override + public PagedSearchBasedFetcher getPagedFetcher() { + return fetcher; } } diff --git a/src/test/java/org/jabref/logic/importer/fetcher/CompositeSearchBasedFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/CompositeSearchBasedFetcherTest.java index fb4ca119f7d..b0b326c50d5 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/CompositeSearchBasedFetcherTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/CompositeSearchBasedFetcherTest.java @@ -39,7 +39,7 @@ public void createCompositeFetcherWithNullSet() { } @Test - public void performSearchWithoutFetchers() { + public void performSearchWithoutFetchers() throws Exception { Set empty = new HashSet<>(); CompositeSearchBasedFetcher fetcher = new CompositeSearchBasedFetcher(empty, Integer.MAX_VALUE); @@ -50,7 +50,7 @@ public void performSearchWithoutFetchers() { @ParameterizedTest(name = "Perform Search on empty query.") @MethodSource("performSearchParameters") - public void performSearchOnEmptyQuery(Set fetchers) { + public void performSearchOnEmptyQuery(Set fetchers) throws Exception { CompositeSearchBasedFetcher compositeFetcher = new CompositeSearchBasedFetcher(fetchers, Integer.MAX_VALUE); List queryResult = compositeFetcher.performSearch(""); @@ -61,7 +61,7 @@ public void performSearchOnEmptyQuery(Set fetchers) { @ParameterizedTest(name = "Perform search on query \"quantum\". Using the CompositeFetcher of the following " + "Fetchers: {arguments}") @MethodSource("performSearchParameters") - public void performSearchOnNonEmptyQuery(Set fetchers) { + public void performSearchOnNonEmptyQuery(Set fetchers) throws Exception { CompositeSearchBasedFetcher compositeFetcher = new CompositeSearchBasedFetcher(fetchers, Integer.MAX_VALUE); ImportCleanup cleanup = new ImportCleanup(BibDatabaseMode.BIBTEX); diff --git a/src/test/java/org/jabref/logic/importer/fetcher/CrossRefTest.java b/src/test/java/org/jabref/logic/importer/fetcher/CrossRefTest.java index 7409b317838..130206433ec 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/CrossRefTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/CrossRefTest.java @@ -105,7 +105,7 @@ public void findByDOI() throws Exception { @Test public void findByAuthors() throws Exception { - assertEquals(Optional.of(barrosEntry), fetcher.performSearch("Barros, Alistair and Dumas, Marlon and Arthur H.M. ter Hofstede").stream().findFirst()); + assertEquals(Optional.of(barrosEntry), fetcher.performSearch("\"Barros, Alistair\" AND \"Dumas, Marlon\" AND \"Arthur H.M. ter Hofstede\"").stream().findFirst()); } @Test diff --git a/src/test/java/org/jabref/logic/importer/fetcher/GoogleScholarTest.java b/src/test/java/org/jabref/logic/importer/fetcher/GoogleScholarTest.java index 9c0ef78b411..87ff79ac608 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/GoogleScholarTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/GoogleScholarTest.java @@ -9,6 +9,7 @@ import org.jabref.logic.bibtex.FieldContentFormatterPreferences; import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.PagedSearchBasedFetcher; import org.jabref.logic.importer.SearchBasedFetcher; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; @@ -24,7 +25,7 @@ import static org.mockito.Mockito.when; @FetcherTest -class GoogleScholarTest implements SearchBasedFetcherCapabilityTest { +class GoogleScholarTest implements SearchBasedFetcherCapabilityTest, PagedSearchFetcherTest { private GoogleScholar finder; private BibEntry entry; @@ -86,6 +87,11 @@ public SearchBasedFetcher getFetcher() { return finder; } + @Override + public PagedSearchBasedFetcher getPagedFetcher() { + return finder; + } + @Override public List getTestAuthors() { return List.of("Mittermeier", "Myers"); diff --git a/src/test/java/org/jabref/logic/importer/fetcher/GvkFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/GvkFetcherTest.java index e95f87ab003..d1af17c5be0 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/GvkFetcherTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/GvkFetcherTest.java @@ -93,20 +93,20 @@ public void complexSearchQueryURLCorrect() throws MalformedURLException, URISynt @Test public void testPerformSearchMatchingMultipleEntries() throws FetcherException { - List searchResult = fetcher.performSearch("tit effective java"); + List searchResult = fetcher.performSearch("title:\"effective java\""); assertTrue(searchResult.contains(bibEntryPPN591166003)); assertTrue(searchResult.contains(bibEntryPPN66391437X)); } @Test public void testPerformSearch591166003() throws FetcherException { - List searchResult = fetcher.performSearch("ppn 591166003"); + List searchResult = fetcher.performSearch("ppn:591166003"); assertEquals(Collections.singletonList(bibEntryPPN591166003), searchResult); } @Test public void testPerformSearch66391437X() throws FetcherException { - List searchResult = fetcher.performSearch("ppn 66391437X"); + List searchResult = fetcher.performSearch("ppn:66391437X"); assertEquals(Collections.singletonList(bibEntryPPN66391437X), searchResult); } diff --git a/src/test/java/org/jabref/logic/importer/fetcher/IEEETest.java b/src/test/java/org/jabref/logic/importer/fetcher/IEEETest.java index 9aab79989ba..a063519bb8d 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/IEEETest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/IEEETest.java @@ -6,6 +6,7 @@ import java.util.Optional; import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.PagedSearchBasedFetcher; import org.jabref.logic.importer.SearchBasedFetcher; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; @@ -20,7 +21,7 @@ import static org.mockito.Mockito.when; @FetcherTest -class IEEETest implements SearchBasedFetcherCapabilityTest { +class IEEETest implements SearchBasedFetcherCapabilityTest, PagedSearchFetcherTest { private IEEE fetcher; private BibEntry entry; @@ -139,4 +140,9 @@ public List getTestAuthors() { public String getTestJournal() { return "IET Renewable Power Generation"; } + + @Override + public PagedSearchBasedFetcher getPagedFetcher() { + return fetcher; + } } diff --git a/src/test/java/org/jabref/logic/importer/fetcher/INSPIREFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/INSPIREFetcherTest.java index c29a59217b0..ff8c597705b 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/INSPIREFetcherTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/INSPIREFetcherTest.java @@ -41,7 +41,7 @@ void searchByQueryFindsEntry() throws Exception { .withField(StandardField.EPRINT, "1405.2249") .withField(StandardField.ARCHIVEPREFIX, "arXiv") .withField(StandardField.PRIMARYCLASS, "math-ph"); - List fetchedEntries = fetcher.performSearch("Fr\\'echet group actions field"); + List fetchedEntries = fetcher.performSearch("Fr\\´echet group actions field"); assertEquals(Collections.singletonList(master), fetchedEntries); } diff --git a/src/test/java/org/jabref/logic/importer/fetcher/JstorFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/JstorFetcherTest.java index a55f38def16..c8d51bd1fee 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/JstorFetcherTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/JstorFetcherTest.java @@ -42,7 +42,7 @@ public class JstorFetcherTest implements SearchBasedFetcherCapabilityTest { @Test void searchByTitle() throws Exception { - List entries = fetcher.performSearch("ti: \"Test Anxiety Analysis of Chinese College Students in Computer-based Spoken English Test\""); + List entries = fetcher.performSearch("title: \"Test Anxiety Analysis of Chinese College Students in Computer-based Spoken English Test\""); assertEquals(Collections.singletonList(bibEntry), entries); } @@ -64,6 +64,7 @@ public List getTestAuthors() { @Override public String getTestJournal() { + // Does not provide articles and journals return "Test"; } @@ -73,6 +74,12 @@ public void supportsYearRangeSearch() throws Exception { } + @Disabled("jstor does not provide articles with journals") + @Override + public void supportsJournalSearch() throws Exception { + + } + @Disabled("jstor does not support search only based on year") @Override public void supportsYearSearch() throws Exception { diff --git a/src/test/java/org/jabref/logic/importer/fetcher/MedlineFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/MedlineFetcherTest.java index f80953b9d63..843f1838917 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/MedlineFetcherTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/MedlineFetcherTest.java @@ -173,8 +173,8 @@ public void testSearchByIDSari() throws Exception { public void testMultipleEntries() throws Exception { List entryList = fetcher.performSearch("java"); entryList.forEach(entry -> entry.clearField(StandardField.ABSTRACT)); // Remove abstract due to copyright); + System.out.println(entryList); assertEquals(50, entryList.size()); - assertTrue(entryList.contains(bibEntryIchikawa)); } @Test diff --git a/src/test/java/org/jabref/logic/importer/fetcher/PagedSearchFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/PagedSearchFetcherTest.java new file mode 100644 index 00000000000..43902fe9fa2 --- /dev/null +++ b/src/test/java/org/jabref/logic/importer/fetcher/PagedSearchFetcherTest.java @@ -0,0 +1,31 @@ +package org.jabref.logic.importer.fetcher; + +import org.jabref.logic.importer.PagedSearchBasedFetcher; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.paging.Page; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +/** + * This interface provides general test methods for paged fetchers + */ +public interface PagedSearchFetcherTest { + + /** + * Ensure that different page return different entries + */ + @Test + default void pageSearchReturnsUniqueResultsPerPage() throws Exception { + String query = "Software"; + Page firstPage = getPagedFetcher().performSearchPaged(query, 0); + Page secondPage = getPagedFetcher().performSearchPaged(query, 1); + + for (BibEntry entry : firstPage.getContent()) { + assertFalse(secondPage.getContent().contains(entry)); + } + } + + PagedSearchBasedFetcher getPagedFetcher(); +} diff --git a/src/test/java/org/jabref/logic/importer/fetcher/SearchBasedFetcherCapabilityTest.java b/src/test/java/org/jabref/logic/importer/fetcher/SearchBasedFetcherCapabilityTest.java index 02760e1b91e..73afa6d63c9 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/SearchBasedFetcherCapabilityTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/SearchBasedFetcherCapabilityTest.java @@ -34,7 +34,7 @@ default void supportsAuthorSearch() throws Exception { ComplexSearchQuery.ComplexSearchQueryBuilder builder = ComplexSearchQuery.builder(); getTestAuthors().forEach(builder::author); - List result = getFetcher().performComplexSearch(builder.build()); + List result = getFetcher().performSearch(builder.build()); new ImportCleanup(BibDatabaseMode.BIBTEX).doPostCleanup(result); assertFalse(result.isEmpty()); @@ -56,7 +56,7 @@ default void supportsYearSearch() throws Exception { .singleYear(getTestYear()) .build(); - List result = getFetcher().performComplexSearch(complexSearchQuery); + List result = getFetcher().performSearch(complexSearchQuery); new ImportCleanup(BibDatabaseMode.BIBTEX).doPostCleanup(result); List differentYearsInResult = result.stream() .map(bibEntry -> bibEntry.getField(StandardField.YEAR)) @@ -77,7 +77,7 @@ default void supportsYearRangeSearch() throws Exception { List yearsInYearRange = List.of("2018", "2019", "2020"); builder.fromYearAndToYear(2018, 2020); - List result = getFetcher().performComplexSearch(builder.build()); + List result = getFetcher().performSearch(builder.build()); new ImportCleanup(BibDatabaseMode.BIBTEX).doPostCleanup(result); List differentYearsInResult = result.stream() .map(bibEntry -> bibEntry.getField(StandardField.YEAR)) @@ -96,7 +96,7 @@ default void supportsYearRangeSearch() throws Exception { default void supportsJournalSearch() throws Exception { ComplexSearchQuery.ComplexSearchQueryBuilder builder = ComplexSearchQuery.builder(); builder.journal(getTestJournal()); - List result = getFetcher().performComplexSearch(builder.build()); + List result = getFetcher().performSearch(builder.build()); new ImportCleanup(BibDatabaseMode.BIBTEX).doPostCleanup(result); assertFalse(result.isEmpty()); diff --git a/src/test/java/org/jabref/logic/importer/fetcher/SpringerFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/SpringerFetcherTest.java index 9b91714bc06..74e595c777a 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/SpringerFetcherTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/SpringerFetcherTest.java @@ -5,6 +5,7 @@ import java.util.Optional; import java.util.stream.Collectors; +import org.jabref.logic.importer.PagedSearchBasedFetcher; import org.jabref.logic.importer.SearchBasedFetcher; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; @@ -21,7 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; @FetcherTest -class SpringerFetcherTest implements SearchBasedFetcherCapabilityTest { +class SpringerFetcherTest implements SearchBasedFetcherCapabilityTest, PagedSearchFetcherTest { SpringerFetcher fetcher; @@ -108,8 +109,8 @@ public void supportsPhraseSearch() throws Exception { .withField(StandardField.FILE, ":http\\://link.springer.com/openurl/pdf?id=doi\\:10.1007/978-3-319-78105-1_75:PDF") .withField(StandardField.ABSTRACT, "The iSchool Inclusion Institute (i3) is a Research Experience for Undergraduates (REU) program in the US designed to address underrepresentation in the information sciences. i3 is a year-long, cohort-based program that prepares undergraduate students for graduate school in information science and is rooted in a research and leadership development curriculum. Using data from six years of i3 cohorts, we present in this paper a qualitative and quantitative evaluation of the program in terms of student learning, research production, and graduate school enrollment. We find that students who participate in i3 report significant learning gains in information-science- and graduate-school-related areas and that 52% of i3 participants enroll in graduate school, over 2 $$\\times $$ × the national average. Based on these and additional results, we distill recommendations for future implementations of similar programs to address underrepresentation in information science."); - List resultPhrase = fetcher.performSearch("name:\"Redmiles David\""); - List result = fetcher.performSearch("name:Redmiles David"); + List resultPhrase = fetcher.performSearch("author:\"Redmiles David\""); + List result = fetcher.performSearch("author:Redmiles David"); // Phrase search should be a subset of the normal search result. Assertions.assertTrue(result.containsAll(resultPhrase)); @@ -119,8 +120,8 @@ public void supportsPhraseSearch() throws Exception { @Test public void supportsBooleanANDSearch() throws Exception { - List resultJustByAuthor = fetcher.performSearch("name:\"Redmiles, David\""); - List result = fetcher.performSearch("name:\"Redmiles, David\" AND journal:Computer Supported Cooperative Work"); + List resultJustByAuthor = fetcher.performSearch("author:\"Redmiles, David\""); + List result = fetcher.performSearch("author:\"Redmiles, David\" AND journal:\"Computer Supported Cooperative Work\""); Assertions.assertTrue(resultJustByAuthor.containsAll(result)); List allEntriesFromCSCW = result.stream() @@ -146,4 +147,9 @@ public List getTestAuthors() { public String getTestJournal() { return "\"Clinical Research in Cardiology\""; } + + @Override + public PagedSearchBasedFetcher getPagedFetcher() { + return fetcher; + } } From 3f056d411c7461178c2727c4464d03f27e7fd682 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Nov 2020 05:34:42 +0000 Subject: [PATCH 081/201] Bump org.beryx.jlink from 2.22.1 to 2.22.3 Bumps org.beryx.jlink from 2.22.1 to 2.22.3. Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cc5eeac1e68..4b40498c1aa 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ plugins { id 'com.github.ben-manes.versions' version '0.36.0' id 'org.javamodularity.moduleplugin' version '1.7.0' id 'org.openjfx.javafxplugin' version '0.0.9' - id 'org.beryx.jlink' version '2.22.1' + id 'org.beryx.jlink' version '2.22.3' // nicer test outputs during running and completion // Homepage: https://github.com/radarsh/gradle-test-logger-plugin From a924b3d2c66b7d8ff3b9ec50b913c6cc76360797 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Nov 2020 05:53:11 +0000 Subject: [PATCH 082/201] Bump gittools/actions from v0.9.5 to v0.9.6 (#7104) --- .github/workflows/deployment.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 4ff79211d53..05ebbe3314e 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -49,12 +49,12 @@ jobs: with: fetch-depth: 0 - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v0.9.5 + uses: gittools/actions/gitversion/setup@v0.9.6 with: versionSpec: "5.3.7" - name: Run GitVersion id: gitversion - uses: gittools/actions/gitversion/execute@v0.9.5 + uses: gittools/actions/gitversion/execute@v0.9.6 - name: Set up JDK 15 for linux and mac uses: actions/setup-java@v1 with: @@ -161,12 +161,12 @@ jobs: - name: Fetch all history for all tags and branches run: git fetch --prune --unshallow - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v0.9.5 + uses: gittools/actions/gitversion/setup@v0.9.6 with: versionSpec: '5.2.x' - name: Run GitVersion id: gitversion - uses: gittools/actions/gitversion/execute@v0.9.5 + uses: gittools/actions/gitversion/execute@v0.9.6 - name: Get linux binaries uses: actions/download-artifact@master with: From e7c344b0557112d27f7373d292970c16730c72ee Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Mon, 16 Nov 2020 08:12:32 +0100 Subject: [PATCH 083/201] add mac package identifier command --- .github/workflows/deployment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 4ff79211d53..c9660ef9891 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -101,7 +101,7 @@ jobs: codesign --entitlements buildres/mac/myapp.entitlements --options runtime -vvv -f --sign "Developer ID Application: Tobias Diez (W2PU6LW5U5)" build/distribution/JabRef.app jpackage --type pkg --dest build/distribution --name JabRef --app-version "${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}" --app-image build/distribution/JabRef.app --verbose --type dmg --vendor JabRef --app-version "${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}" --file-associations buildres/mac/bibtexAssociations.properties --resource-dir buildres/mac codesign -s "Developer ID Application: Tobias Diez (W2PU6LW5U5)" --options runtime --entitlements buildres/mac/myapp.entitlements -vvvv --deep "build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.dmg" - jpackage --type pkg --dest build/distribution --name JabRef --app-version "${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}" --app-image build/distribution/JabRef.app --verbose --type pkg --vendor JabRef --app-version "${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}" --file-associations buildres/mac/bibtexAssociations.properties --resource-dir buildres/mac + jpackage --type pkg --dest build/distribution --name JabRef --mac-package-identifier JabRef --app-version "${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}" --app-image build/distribution/JabRef.app --verbose --type pkg --vendor JabRef --app-version "${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}" --file-associations buildres/mac/bibtexAssociations.properties --resource-dir buildres/mac productsign --sign "Developer ID Installer: Tobias Diez (W2PU6LW5U5)" "build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.pkg" "build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}-signed.pkg" - name: Notarize dmg and pkg installer if: matrix.os == 'macos-latest' && github.ref == 'refs/heads/master' From bb0d962fa96c904bdfdd3dc5bdd50e6fa023485c Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Tue, 17 Nov 2020 08:16:15 +0100 Subject: [PATCH 084/201] Try to fix linux build increase gradle heap --- .github/workflows/deployment.yml | 4 ++-- build.gradle | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index a8c0dacd942..72f675166a1 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -88,9 +88,9 @@ jobs: create-keychain: false keychain-password: jabref - name: Build runtime image - run: ./gradlew -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jlinkZip + run: ./gradlew -Dorg.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jlinkZip - name: Build installer - run: ./gradlew -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jpackage + run: ./gradlew -Dorg.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jpackage -Dorg.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError shell: bash - name: Resign app image for OSX and build dmg if: matrix.os == 'macos-latest' diff --git a/build.gradle b/build.gradle index 2d5fc088fa9..74606b53daf 100644 --- a/build.gradle +++ b/build.gradle @@ -40,11 +40,9 @@ version = project.findProperty('projVersion') ?: '100.0.0' java { sourceCompatibility = JavaVersion.VERSION_14 targetCompatibility = JavaVersion.VERSION_14 - modularity.inferModulePath.set(true) } application { - mainModule = "org.jabref" mainClassName = "$moduleName/org.jabref.gui.JabRefLauncher" } From 93217e1001cd99726628cf5c51e7c420b58d8cf4 Mon Sep 17 00:00:00 2001 From: Christoph Date: Tue, 17 Nov 2020 08:23:20 +0100 Subject: [PATCH 085/201] Update git Version spec Fix for the set env disable --- .github/workflows/deployment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index a8c0dacd942..e782c3d2146 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -51,7 +51,7 @@ jobs: - name: Install GitVersion uses: gittools/actions/gitversion/setup@v0.9.6 with: - versionSpec: "5.3.7" + versionSpec: "5.x" - name: Run GitVersion id: gitversion uses: gittools/actions/gitversion/execute@v0.9.6 From e9ebe30d59a3ae34a946a7b5e99217afa05c4d0c Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Tue, 17 Nov 2020 08:24:39 +0100 Subject: [PATCH 086/201] fix version spec --- .github/workflows/deployment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 72f675166a1..9777c937810 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -51,7 +51,7 @@ jobs: - name: Install GitVersion uses: gittools/actions/gitversion/setup@v0.9.6 with: - versionSpec: "5.3.7" + versionSpec: "5.x" - name: Run GitVersion id: gitversion uses: gittools/actions/gitversion/execute@v0.9.6 From 9c9bd9d3d5b9978466643691ef22285dbdf48de8 Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Tue, 17 Nov 2020 08:45:06 +0100 Subject: [PATCH 087/201] try again with memory increasing --- .github/workflows/deployment.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 9777c937810..ce73803d91f 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -88,9 +88,9 @@ jobs: create-keychain: false keychain-password: jabref - name: Build runtime image - run: ./gradlew -Dorg.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jlinkZip + run: ./gradlew -Dorg.gradle.jvmargs=-Xmx4g -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jlinkZip - name: Build installer - run: ./gradlew -Dorg.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jpackage -Dorg.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError + run: ./gradlew -Dorg.gradle.jvmargs=-Xmx4g -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jpackage -Dorg.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError shell: bash - name: Resign app image for OSX and build dmg if: matrix.os == 'macos-latest' From 4c2f0304d065e473415fb5504e5d11148039a23e Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Tue, 17 Nov 2020 08:52:54 +0100 Subject: [PATCH 088/201] and again --- .github/workflows/deployment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index ce73803d91f..cf25b5aabd0 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -90,7 +90,7 @@ jobs: - name: Build runtime image run: ./gradlew -Dorg.gradle.jvmargs=-Xmx4g -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jlinkZip - name: Build installer - run: ./gradlew -Dorg.gradle.jvmargs=-Xmx4g -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jpackage -Dorg.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError + run: ./gradlew -Dorg.gradle.jvmargs=-Xmx4g -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jpackage shell: bash - name: Resign app image for OSX and build dmg if: matrix.os == 'macos-latest' From 70628ffb63570ab17f4b1b0fce62009eb3bcc477 Mon Sep 17 00:00:00 2001 From: Christoph Date: Tue, 17 Nov 2020 08:56:29 +0100 Subject: [PATCH 089/201] Fix version spec for upload --- .github/workflows/deployment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index e782c3d2146..888b75eaa40 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -163,7 +163,7 @@ jobs: - name: Install GitVersion uses: gittools/actions/gitversion/setup@v0.9.6 with: - versionSpec: '5.2.x' + versionSpec: '5.x' - name: Run GitVersion id: gitversion uses: gittools/actions/gitversion/execute@v0.9.6 From 9578c869f92159d6218abacc617139fd6059840c Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Tue, 17 Nov 2020 09:08:17 +0100 Subject: [PATCH 090/201] remove commandline args for gradlew, seem to interfer fix version spec --- .github/workflows/deployment.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index cf25b5aabd0..9712508c1ee 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -88,9 +88,9 @@ jobs: create-keychain: false keychain-password: jabref - name: Build runtime image - run: ./gradlew -Dorg.gradle.jvmargs=-Xmx4g -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jlinkZip + run: ./gradlew -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jlinkZip - name: Build installer - run: ./gradlew -Dorg.gradle.jvmargs=-Xmx4g -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jpackage + run: ./gradlew -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jpackage shell: bash - name: Resign app image for OSX and build dmg if: matrix.os == 'macos-latest' @@ -163,7 +163,7 @@ jobs: - name: Install GitVersion uses: gittools/actions/gitversion/setup@v0.9.6 with: - versionSpec: '5.2.x' + versionSpec: '5.x' - name: Run GitVersion id: gitversion uses: gittools/actions/gitversion/execute@v0.9.6 From d61eb1c85db0e930b29c07672fea4b915ab780c1 Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Tue, 17 Nov 2020 16:08:11 +0100 Subject: [PATCH 091/201] try without module name --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 74606b53daf..0bcb3bc1f95 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,7 @@ java { } application { - mainClassName = "$moduleName/org.jabref.gui.JabRefLauncher" + mainClassName = "org.jabref.gui.JabRefLauncher" } // TODO: Ugly workaround to temporarily ignore build errors to dependencies of latex2unicode From 8dfdb6e70292b29db52c883caf2304753fd4b10a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Nov 2020 10:53:30 +0100 Subject: [PATCH 092/201] Bump xmlunit-matchers from 2.8.0 to 2.8.1 (#7101) Bumps [xmlunit-matchers](https://github.com/xmlunit/xmlunit) from 2.8.0 to 2.8.1. - [Release notes](https://github.com/xmlunit/xmlunit/releases) - [Changelog](https://github.com/xmlunit/xmlunit/blob/main/RELEASE_NOTES.md) - [Commits](https://github.com/xmlunit/xmlunit/compare/v2.8.0...v2.8.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0bcb3bc1f95..e796074c35b 100644 --- a/build.gradle +++ b/build.gradle @@ -207,7 +207,7 @@ dependencies { testRuntime group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '3.0.0-SNAPSHOT' testImplementation 'org.mockito:mockito-core:3.6.0' testImplementation 'org.xmlunit:xmlunit-core:2.8.0' - testImplementation 'org.xmlunit:xmlunit-matchers:2.8.0' + testImplementation 'org.xmlunit:xmlunit-matchers:2.8.1' testRuntime 'com.tngtech.archunit:archunit-junit5-engine:0.14.1' testImplementation 'com.tngtech.archunit:archunit-junit5-api:0.14.1' testImplementation "org.testfx:testfx-core:4.0.17-alpha-SNAPSHOT" From 7eaf2b655c0fd009d5599d68fe6877e6974a548e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Nov 2020 09:54:24 +0000 Subject: [PATCH 093/201] Bump xmlunit-core from 2.8.0 to 2.8.1 Bumps [xmlunit-core](https://github.com/xmlunit/xmlunit) from 2.8.0 to 2.8.1. - [Release notes](https://github.com/xmlunit/xmlunit/releases) - [Changelog](https://github.com/xmlunit/xmlunit/blob/main/RELEASE_NOTES.md) - [Commits](https://github.com/xmlunit/xmlunit/compare/v2.8.0...v2.8.1) Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e796074c35b..6e04f5b0b22 100644 --- a/build.gradle +++ b/build.gradle @@ -206,7 +206,7 @@ dependencies { testRuntime group: 'org.apache.logging.log4j', name: 'log4j-core', version: '3.0.0-SNAPSHOT' testRuntime group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '3.0.0-SNAPSHOT' testImplementation 'org.mockito:mockito-core:3.6.0' - testImplementation 'org.xmlunit:xmlunit-core:2.8.0' + testImplementation 'org.xmlunit:xmlunit-core:2.8.1' testImplementation 'org.xmlunit:xmlunit-matchers:2.8.1' testRuntime 'com.tngtech.archunit:archunit-junit5-engine:0.14.1' testImplementation 'com.tngtech.archunit:archunit-junit5-api:0.14.1' From 74ca49422e946d9d7fabb2f6bbce2def4bdc4e5d Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Fri, 20 Nov 2020 17:11:05 +0100 Subject: [PATCH 094/201] Add link to unit testing --- docs/getting-into-the-code/testing.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/getting-into-the-code/testing.md b/docs/getting-into-the-code/testing.md index f88560233f9..a62eea5c86a 100644 --- a/docs/getting-into-the-code/testing.md +++ b/docs/getting-into-the-code/testing.md @@ -1,12 +1,14 @@ # How to test +For details on unit testing see . + ## Database tests ### PostgreSQL To quickly host a local PostgreSQL database, execute following statement: -```text +```terminal docker run -d -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres -p 5432:5432 --name db postgres:10 postgres -c log_statement=all ``` @@ -18,7 +20,7 @@ Then, all DBMS Tests \(annotated with `@org.jabref.testutils.category.DatabaseTe A MySQL DBMS can be started using following command: -```text +```terminal docker run -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=jabref -p 3800:3307 mysql:8.0 --port=3307 ``` From 7a8adac4efea6176215ac0fc9457ea66e7066685 Mon Sep 17 00:00:00 2001 From: Christoph Date: Sat, 21 Nov 2020 17:47:57 +0100 Subject: [PATCH 095/201] Revert "Bump jakarta.annotation-api from 1.3.5 to 2.0.0" --- build.gradle | 2 +- src/main/java/module-info.java | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f23cd2ef1b4..6e04f5b0b22 100644 --- a/build.gradle +++ b/build.gradle @@ -153,7 +153,7 @@ dependencies { exclude module: "jsr305" } - implementation group: 'jakarta.annotation', name: 'jakarta.annotation-api', version: '2.0.0' + implementation group: 'jakarta.annotation', name: 'jakarta.annotation-api', version: '1.3.5' // JavaFX stuff implementation 'de.jensd:fontawesomefx-commons:11.0' diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 0ccb4b07bb5..eb7e0102e92 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -40,6 +40,9 @@ requires java.xml.bind; requires jdk.xml.dom; + // Annotations (@PostConstruct) + requires java.annotation; + // Microsoft application insights requires applicationinsights.core; From 5a4c4e8c62535aabe1d473eb87d39affe72f474c Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage Date: Sat, 21 Nov 2020 21:48:11 +0100 Subject: [PATCH 096/201] Added runLater to selection of AllEntries on Opening the GroupsPanel --- src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java index 23a9619f968..fd895a82f03 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java @@ -8,6 +8,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; +import javafx.application.Platform; import javafx.beans.property.ListProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleListProperty; @@ -131,7 +132,8 @@ private void onActiveDatabaseChanged(Optional newDatabase) { rootGroup.setValue(newRoot); if (stateManager.getSelectedGroup(newDatabase.get()).isEmpty()) { - stateManager.setSelectedGroups(newDatabase.get(), Collections.singletonList(newRoot.getGroupNode())); + // Groups panel has to initialize before selecting the all entries group + Platform.runLater(() -> stateManager.setSelectedGroups(newDatabase.get(), Collections.singletonList(newRoot.getGroupNode()))); } selectedGroups.setAll( stateManager.getSelectedGroup(newDatabase.get()).stream() From ebcd9e0fa06596b29c785a2dfa696f1b1229ebf8 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage Date: Sat, 21 Nov 2020 22:06:34 +0100 Subject: [PATCH 097/201] Fixed failing tests --- .../java/org/jabref/gui/groups/GroupTreeView.java | 14 ++++++++------ .../org/jabref/gui/groups/GroupTreeViewModel.java | 4 +--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeView.java b/src/main/java/org/jabref/gui/groups/GroupTreeView.java index ee6c69ce77f..c44087f1423 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeView.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeView.java @@ -11,6 +11,7 @@ import javax.inject.Inject; +import javafx.application.Platform; import javafx.beans.property.ObjectProperty; import javafx.css.PseudoClass; import javafx.fxml.FXML; @@ -84,12 +85,13 @@ public void initialize() { dragExpansionHandler = new DragExpansionHandler(); // Set-up bindings - BindingsHelper.bindContentBidirectional( - groupTree.getSelectionModel().getSelectedItems(), - viewModel.selectedGroupsProperty(), - (newSelectedGroups) -> newSelectedGroups.forEach(this::selectNode), - this::updateSelection - ); + Platform.runLater(() -> + BindingsHelper.bindContentBidirectional( + groupTree.getSelectionModel().getSelectedItems(), + viewModel.selectedGroupsProperty(), + (newSelectedGroups) -> newSelectedGroups.forEach(this::selectNode), + this::updateSelection + )); // We try to to prevent publishing changes in the search field directly to the search task that takes some time // for larger group structures. diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java index fd895a82f03..23a9619f968 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java @@ -8,7 +8,6 @@ import java.util.function.Predicate; import java.util.stream.Collectors; -import javafx.application.Platform; import javafx.beans.property.ListProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleListProperty; @@ -132,8 +131,7 @@ private void onActiveDatabaseChanged(Optional newDatabase) { rootGroup.setValue(newRoot); if (stateManager.getSelectedGroup(newDatabase.get()).isEmpty()) { - // Groups panel has to initialize before selecting the all entries group - Platform.runLater(() -> stateManager.setSelectedGroups(newDatabase.get(), Collections.singletonList(newRoot.getGroupNode()))); + stateManager.setSelectedGroups(newDatabase.get(), Collections.singletonList(newRoot.getGroupNode())); } selectedGroups.setAll( stateManager.getSelectedGroup(newDatabase.get()).stream() From dabeb23c3aca0236abcfb11bf11b9d7f65f0d96b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Nov 2020 06:10:06 +0000 Subject: [PATCH 098/201] Bump appleboy/ssh-action from v0.1.3 to v0.1.4 (#7118) --- .github/workflows/cleanup_pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cleanup_pr.yml b/.github/workflows/cleanup_pr.yml index 54cc1310f25..63090aba74a 100644 --- a/.github/workflows/cleanup_pr.yml +++ b/.github/workflows/cleanup_pr.yml @@ -27,7 +27,7 @@ jobs: echo "##[set-output name=branch;]$(echo ${{ github.event.pull_request.head.ref }})" - name: Delete folder on builds.jabref.org if: ${{ steps.checksecrets.outputs.secretspresent }} - uses: appleboy/ssh-action@v0.1.3 + uses: appleboy/ssh-action@v0.1.4 with: script: rm -rf /var/www/builds.jabref.org/www/${{ steps.extract_branch.outputs.branch }} || true host: build-upload.jabref.org From fc6e344a937e1b43e59e6588be00130a0250a7cf Mon Sep 17 00:00:00 2001 From: Christoph Date: Mon, 23 Nov 2020 20:41:38 +0100 Subject: [PATCH 099/201] Fix saving in codeArea without leaving the field (#7116) --- CHANGELOG.md | 1 + .../org/jabref/gui/entryeditor/SourceTab.java | 21 +++++++++++++++---- .../gui/keyboard/CodeAreaKeyBindings.java | 4 +--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 355e11dfa0c..38c9e5bd2dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://git - We fixed an issue where errors from imports were not shown to the user [#7084](https://github.com/JabRef/jabref/pull/7084) - We fixed an issue where the EndNote XML Import would fail on empty keywords tags [forum#2387](https://discourse.jabref.org/t/importing-in-unknown-format-fails-to-import-xml-library-from-bookends-export/2387) - We fixed an issue where the color of groups of type "free search expression" not persisting after restarting the application [#6999](https://github.com/JabRef/jabref/issues/6999) +- We fixed an issue where modifications in the source tab where not saved without switching to another field before saving the library [#6622](https://github.com/JabRef/jabref/issues/6622) ### Removed diff --git a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java index 9161758a58d..4f8e4d7fcc9 100644 --- a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java @@ -116,7 +116,7 @@ public SourceTab(BibDatabaseContext bibDatabaseContext, CountingUndoManager undo } private void highlightSearchPattern() { - if (searchHighlightPattern.isPresent() && codeArea != null) { + if (searchHighlightPattern.isPresent() && (codeArea != null)) { codeArea.setStyleClass(0, codeArea.getLength(), "text"); Matcher matcher = searchHighlightPattern.get().matcher(codeArea.getText()); while (matcher.find()) { @@ -172,7 +172,8 @@ private void setupSourceEditor() { } }); codeArea.setId("bibtexSourceCodeArea"); - codeArea.addEventFilter(KeyEvent.KEY_PRESSED, event -> CodeAreaKeyBindings.call(codeArea, event)); + codeArea.addEventFilter(KeyEvent.KEY_PRESSED, event -> CodeAreaKeyBindings.call(codeArea, event, keyBindingRepository)); + codeArea.addEventFilter(KeyEvent.KEY_PRESSED, this::listenForSaveKeybinding); ActionFactory factory = new ActionFactory(keyBindingRepository); ContextMenu contextMenu = new ContextMenu(); @@ -197,7 +198,7 @@ private void setupSourceEditor() { }); codeArea.focusedProperty().addListener((obs, oldValue, onFocus) -> { - if (!onFocus && currentEntry != null) { + if (!onFocus && (currentEntry != null)) { storeSource(currentEntry, codeArea.textProperty().getValue()); } }); @@ -232,7 +233,7 @@ private void updateCodeArea() { @Override protected void bindToEntry(BibEntry entry) { - if (previousEntry != null && codeArea != null) { + if ((previousEntry != null) && (codeArea != null)) { storeSource(previousEntry, codeArea.textProperty().getValue()); } this.previousEntry = entry; @@ -321,4 +322,16 @@ private void storeSource(BibEntry outOfFocusEntry, String text) { LOGGER.debug("Incorrect source", ex); } } + + private void listenForSaveKeybinding(KeyEvent event) { + keyBindingRepository.mapToKeyBinding(event).ifPresent(binding -> { + + switch (binding) { + case SAVE_DATABASE, SAVE_ALL, SAVE_DATABASE_AS -> { + storeSource(currentEntry, codeArea.textProperty().getValue()); + } + } + }); + } + } diff --git a/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java b/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java index d055eaed84c..8c199b02c97 100644 --- a/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java +++ b/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java @@ -2,7 +2,6 @@ import javafx.scene.input.KeyEvent; -import org.jabref.gui.Globals; import org.jabref.logic.util.strings.StringManipulator; import org.jabref.model.util.ResultingStringState; @@ -11,8 +10,7 @@ public class CodeAreaKeyBindings { - public static void call(CodeArea codeArea, KeyEvent event) { - KeyBindingRepository keyBindingRepository = Globals.getKeyPrefs(); + public static void call(CodeArea codeArea, KeyEvent event, KeyBindingRepository keyBindingRepository) { keyBindingRepository.mapToKeyBinding(event).ifPresent(binding -> { switch (binding) { case EDITOR_DELETE -> { From 350ec1f1ba4dc13c3e9f5a07f4fac801e85a93dc Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 23 Nov 2020 21:38:41 +0100 Subject: [PATCH 100/201] =?UTF-8?q?Welcome=20Dominik=20=E2=9C=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DEVELOPERS | 1 + 1 file changed, 1 insertion(+) diff --git a/DEVELOPERS b/DEVELOPERS index 8f6e0bf2623..d8cb8727de1 100644 --- a/DEVELOPERS +++ b/DEVELOPERS @@ -5,3 +5,4 @@ Tobias Diez (since 2015) Christoph Schwentker (since 2016) Linus Dietz (since 2017) Carl Christian Snethlage (since 2020) +Dominik Voigt (since 2020) From 9d89fb409fccf8f186373169fd9d4ab4738e8252 Mon Sep 17 00:00:00 2001 From: Linus Dietz Date: Wed, 25 Nov 2020 11:18:26 +0100 Subject: [PATCH 101/201] Update Java Version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d81ab2b909e..388b25ccdd4 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ We will discuss improvements with you and agree to merge them once the [develope If you want a step-by-step walk-through on how to set-up your workspace, please check [this guideline](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace). -To compile JabRef from source, you need a Java Development Kit 14 and `JAVA_HOME` pointing to this JDK. +To compile JabRef from source, you need a Java Development Kit 15 and `JAVA_HOME` pointing to this JDK. To run it, just execute `gradlew run`. When you want to develop, it is necessary to generate additional sources using `gradlew generateSource` and then generate the Eclipse `gradlew eclipse`. From 5ca3d0d4d49a363b8e6f02579547a839680ed1cb Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Wed, 25 Nov 2020 14:15:33 +0100 Subject: [PATCH 102/201] Add tracking --- .../guidelines-for-setting-up-a-local-workspace.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace.md b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace.md index 4d90e5b9c0c..38f335aff02 100644 --- a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace.md +++ b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace.md @@ -55,7 +55,7 @@ It is strongly recommend that you have git installed. ### IDE -We suggest [IntelliJ IDEA](https://www.jetbrains.com/idea/). +We suggest [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=jabref). For advanced users, [Eclipse](https://eclipse.org/) \(`2020-09` or newer\) is also possible. #### IntelliJ From b19c3e4700f82d90a804a5cf3467aedf00385dad Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Wed, 25 Nov 2020 17:41:30 +0100 Subject: [PATCH 103/201] =?UTF-8?q?Enable=20automated=20cross=20library=20?= =?UTF-8?q?search=20using=20a=20cross=20library=20query=20lan=E2=80=A6=20(?= =?UTF-8?q?#7124)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enable automated cross library search using a cross library query language. Signed-off-by: Dominik Voigt * Pull Global upward through constructor. * Pull Globals and ImportFormatPreferences up through constructor Signed-off-by: Dominik Voigt * Integrate requested changes and fix architecture tests by correcting test classes Signed-off-by: Dominik Voigt * Remove unused imports Signed-off-by: Dominik Voigt --- CHANGELOG.md | 1 + build.gradle | 2 + src/main/java/module-info.java | 1 + src/main/java/org/jabref/gui/JabRefFrame.java | 8 +- .../gui/StartLiteratureReviewAction.java | 81 +++++ .../jabref/gui/actions/StandardActions.java | 1 + .../org/jabref/logic/crawler/Crawler.java | 52 +++ .../LibraryEntryToFetcherConverter.java | 67 ++++ .../jabref/logic/crawler/StudyFetcher.java | 80 ++++ .../jabref/logic/crawler/StudyRepository.java | 344 ++++++++++++++++++ .../jabref/logic/crawler/git/GitHandler.java | 83 +++++ .../importer/fetcher/SpringerFetcher.java | 2 +- .../model/entry/types/EntryTypeFactory.java | 1 + ...tematicLiteratureReviewStudyEntryType.java | 33 ++ ...ratureReviewStudyEntryTypeDefinitions.java | 60 +++ .../org/jabref/model/study/FetchResult.java | 24 ++ .../org/jabref/model/study/QueryResult.java | 24 ++ .../java/org/jabref/model/study/Study.java | 98 +++++ .../model/study/StudyMetaDataField.java | 24 ++ src/main/resources/l10n/JabRef_en.properties | 19 +- .../org/jabref/logic/crawler/CrawlerTest.java | 105 ++++++ .../LibraryEntryToFetcherConverterTest.java | 69 ++++ .../logic/crawler/StudyRepositoryTest.java | 312 ++++++++++++++++ .../SearchBasedFetcherCapabilityTest.java | 2 +- .../org/jabref/model/study/StudyTest.java | 94 +++++ .../jabref/logic/crawler/ArXivQuantumMock.bib | 15 + .../crawler/SpringerCloud ComputingMock.bib | 9 + .../logic/crawler/SpringerQuantumMock.bib | 9 + .../org/jabref/logic/crawler/study.bib | 37 ++ 29 files changed, 1636 insertions(+), 21 deletions(-) create mode 100644 src/main/java/org/jabref/gui/StartLiteratureReviewAction.java create mode 100644 src/main/java/org/jabref/logic/crawler/Crawler.java create mode 100644 src/main/java/org/jabref/logic/crawler/LibraryEntryToFetcherConverter.java create mode 100644 src/main/java/org/jabref/logic/crawler/StudyFetcher.java create mode 100644 src/main/java/org/jabref/logic/crawler/StudyRepository.java create mode 100644 src/main/java/org/jabref/logic/crawler/git/GitHandler.java create mode 100644 src/main/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryType.java create mode 100644 src/main/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryTypeDefinitions.java create mode 100644 src/main/java/org/jabref/model/study/FetchResult.java create mode 100644 src/main/java/org/jabref/model/study/QueryResult.java create mode 100644 src/main/java/org/jabref/model/study/Study.java create mode 100644 src/main/java/org/jabref/model/study/StudyMetaDataField.java create mode 100644 src/test/java/org/jabref/logic/crawler/CrawlerTest.java create mode 100644 src/test/java/org/jabref/logic/crawler/LibraryEntryToFetcherConverterTest.java create mode 100644 src/test/java/org/jabref/logic/crawler/StudyRepositoryTest.java create mode 100644 src/test/java/org/jabref/model/study/StudyTest.java create mode 100644 src/test/resources/org/jabref/logic/crawler/ArXivQuantumMock.bib create mode 100644 src/test/resources/org/jabref/logic/crawler/SpringerCloud ComputingMock.bib create mode 100644 src/test/resources/org/jabref/logic/crawler/SpringerQuantumMock.bib create mode 100644 src/test/resources/org/jabref/logic/crawler/study.bib diff --git a/CHANGELOG.md b/CHANGELOG.md index 38c9e5bd2dc..bfcd7cb3ce1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ to the page field for cases where the page numbers are missing. [#7019](https:// - We added a new formatter to output shorthand month format. [#6579](https://github.com/JabRef/jabref/issues/6579) - We added support for the new Microsoft Edge browser in all platforms. [#7056](https://github.com/JabRef/jabref/pull/7056) - We reintroduced emacs/bash-like keybindings. [#6017](https://github.com/JabRef/jabref/issues/6017) +- We added a feature to provide automated cross library search using a cross library query language. This provides support for the search step of systematic literature reviews (SLRs). [koppor#369](https://github.com/koppor/jabref/issues/369) ### Changed diff --git a/build.gradle b/build.gradle index 6e04f5b0b22..6e2326393b8 100644 --- a/build.gradle +++ b/build.gradle @@ -139,6 +139,8 @@ dependencies { exclude group: 'org.apache.lucene', module: 'lucene-sandbox' } + implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '5.9.0.202009080501-r' + implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.7.0' implementation 'org.postgresql:postgresql:42.2.18' diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index eb7e0102e92..c080fccc99e 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -91,4 +91,5 @@ requires com.h2database.mvstore; requires lucene.queryparser; requires lucene.core; + requires org.eclipse.jgit; } diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 963df97bb57..d44f6307084 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -815,7 +815,9 @@ private MenuBar createMenu() { new SeparatorMenuItem(), factory.createMenuItem(StandardActions.SEND_AS_EMAIL, new SendAsEMailAction(dialogService, stateManager)), - pushToApplicationMenuItem + pushToApplicationMenuItem, + new SeparatorMenuItem(), + factory.createMenuItem(StandardActions.START_SYSTEMATIC_LITERATURE_REVIEW, new StartLiteratureReviewAction(this, Globals.getFileUpdateMonitor(), Globals.prefs.getWorkingDir(), Globals.TASK_EXECUTOR)) ); SidePaneComponent webSearch = sidePaneManager.getComponent(SidePaneType.WEB_SEARCH); @@ -992,7 +994,7 @@ public void addParserResult(ParserResult parserResult, boolean focusPanel) { * This method causes all open LibraryTabs to set up their tables anew. When called from PreferencesDialogViewModel, * this updates to the new settings. * We need to notify all tabs about the changes to avoid problems when changing the column set. - * */ + */ public void setupAllTables() { tabbedPane.getTabs().forEach(tab -> { LibraryTab libraryTab = (LibraryTab) tab; @@ -1013,7 +1015,7 @@ private ContextMenu createTabContextMenu(KeyBindingRepository keyBindingReposito new SeparatorMenuItem(), factory.createMenuItem(StandardActions.OPEN_DATABASE_FOLDER, new OpenDatabaseFolder()), factory.createMenuItem(StandardActions.OPEN_CONSOLE, new OpenConsoleAction(stateManager)) - ); + ); return contextMenu; } diff --git a/src/main/java/org/jabref/gui/StartLiteratureReviewAction.java b/src/main/java/org/jabref/gui/StartLiteratureReviewAction.java new file mode 100644 index 00000000000..d05d0f817f5 --- /dev/null +++ b/src/main/java/org/jabref/gui/StartLiteratureReviewAction.java @@ -0,0 +1,81 @@ +package org.jabref.gui; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.gui.importer.actions.OpenDatabaseAction; +import org.jabref.gui.util.BackgroundTask; +import org.jabref.gui.util.FileDialogConfiguration; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.crawler.Crawler; +import org.jabref.logic.importer.ParseException; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.util.FileUpdateMonitor; +import org.jabref.preferences.JabRefPreferences; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class StartLiteratureReviewAction extends SimpleCommand { + private static final Logger LOGGER = LoggerFactory.getLogger(StartLiteratureReviewAction.class); + private final JabRefFrame frame; + private final DialogService dialogService; + private final FileUpdateMonitor fileUpdateMonitor; + private final Path workingDirectory; + private final TaskExecutor taskExecutor; + + public StartLiteratureReviewAction(JabRefFrame frame, FileUpdateMonitor fileUpdateMonitor, Path standardWorkingDirectory, TaskExecutor taskExecutor) { + this.frame = frame; + this.dialogService = frame.getDialogService(); + this.fileUpdateMonitor = fileUpdateMonitor; + this.workingDirectory = getInitialDirectory(standardWorkingDirectory); + this.taskExecutor = taskExecutor; + } + + @Override + public void execute() { + FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() + .withInitialDirectory(workingDirectory) + .build(); + + Optional studyDefinitionFile = dialogService.showFileOpenDialog(fileDialogConfiguration); + if (studyDefinitionFile.isEmpty()) { + // Do nothing if selection was canceled + return; + } + final Crawler crawler; + try { + crawler = new Crawler(studyDefinitionFile.get(), fileUpdateMonitor, JabRefPreferences.getInstance().getImportFormatPreferences(), JabRefPreferences.getInstance().getSavePreferences(), new BibEntryTypesManager()); + } catch (IOException | ParseException | GitAPIException e) { + LOGGER.error("Error during reading of study definition file.", e); + dialogService.showErrorDialogAndWait(Localization.lang("Error during reading of study definition file."), e); + return; + } + BackgroundTask.wrap(() -> { + crawler.performCrawl(); + return 0; // Return any value to make this a callable instead of a runnable. This allows throwing exceptions. + }) + .onFailure(e -> { + LOGGER.error("Error during persistence of crawling results."); + dialogService.showErrorDialogAndWait(Localization.lang("Error during persistence of crawling results."), e); + }) + .onSuccess(unused -> new OpenDatabaseAction(frame).openFile(Path.of(studyDefinitionFile.get().getParent().toString(), "studyResult.bib"), true)) + .executeWith(taskExecutor); + } + + /** + * @return Path of current panel database directory or the standard working directory + */ + private Path getInitialDirectory(Path standardWorkingDirectory) { + if (frame.getBasePanelCount() == 0) { + return standardWorkingDirectory; + } else { + Optional databasePath = frame.getCurrentLibraryTab().getBibDatabaseContext().getDatabasePath(); + return databasePath.map(Path::getParent).orElse(standardWorkingDirectory); + } + } +} diff --git a/src/main/java/org/jabref/gui/actions/StandardActions.java b/src/main/java/org/jabref/gui/actions/StandardActions.java index 2b316dca80a..0767d4021bb 100644 --- a/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -88,6 +88,7 @@ public enum StandardActions implements Action { PARSE_LATEX(Localization.lang("Search for citations in LaTeX files..."), IconTheme.JabRefIcons.LATEX_CITATIONS), NEW_SUB_LIBRARY_FROM_AUX(Localization.lang("New sublibrary based on AUX file") + "...", Localization.lang("New BibTeX sublibrary") + Localization.lang("This feature generates a new library based on which entries are needed in an existing LaTeX document."), IconTheme.JabRefIcons.NEW), WRITE_XMP(Localization.lang("Write XMP metadata to PDFs"), Localization.lang("Will write XMP metadata to the PDFs linked from selected entries."), KeyBinding.WRITE_XMP), + START_SYSTEMATIC_LITERATURE_REVIEW(Localization.lang("Start systematic literature review")), OPEN_DATABASE_FOLDER(Localization.lang("Reveal in file explorer")), OPEN_FOLDER(Localization.lang("Open folder"), Localization.lang("Open folder"), KeyBinding.OPEN_FOLDER), OPEN_FILE(Localization.lang("Open file"), Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE), diff --git a/src/main/java/org/jabref/logic/crawler/Crawler.java b/src/main/java/org/jabref/logic/crawler/Crawler.java new file mode 100644 index 00000000000..eade3b55a59 --- /dev/null +++ b/src/main/java/org/jabref/logic/crawler/Crawler.java @@ -0,0 +1,52 @@ +package org.jabref.logic.crawler; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import org.jabref.logic.crawler.git.GitHandler; +import org.jabref.logic.exporter.SavePreferences; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.ParseException; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.study.QueryResult; +import org.jabref.model.study.Study; +import org.jabref.model.util.FileUpdateMonitor; + +import org.eclipse.jgit.api.errors.GitAPIException; + +/** + * This class provides a service for SLR support by conducting an automated search and persistance + * of studies using the queries and E-Libraries specified in the provided study definition file. + * + * It composes a StudyRepository for repository management, + * and a StudyFetcher that manages the crawling over the selected E-Libraries. + */ +public class Crawler { + private final StudyRepository studyRepository; + private final StudyFetcher studyFetcher; + + /** + * Creates a crawler for retrieving studies from E-Libraries + * + * @param studyDefinitionFile The path to the study definition file that contains the list of targeted E-Libraries and used cross-library queries + */ + public Crawler(Path studyDefinitionFile, FileUpdateMonitor fileUpdateMonitor, ImportFormatPreferences importFormatPreferences, SavePreferences savePreferences, BibEntryTypesManager bibEntryTypesManager) throws IllegalArgumentException, IOException, ParseException, GitAPIException { + Path studyRepositoryRoot = studyDefinitionFile.getParent(); + studyRepository = new StudyRepository(studyRepositoryRoot, new GitHandler(studyRepositoryRoot), importFormatPreferences, fileUpdateMonitor, savePreferences, bibEntryTypesManager); + Study study = studyRepository.getStudy(); + LibraryEntryToFetcherConverter libraryEntryToFetcherConverter = new LibraryEntryToFetcherConverter(study.getActiveLibraryEntries(), importFormatPreferences); + this.studyFetcher = new StudyFetcher(libraryEntryToFetcherConverter.getActiveFetchers(), study.getSearchQueryStrings()); + } + + /** + * This methods performs the crawling of the active libraries defined in the study definition file. + * This method also persists the results in the same folder the study definition file is stored in. + * + * @throws IOException Thrown if a problem occurred during the persistence of the result. + */ + public void performCrawl() throws IOException, GitAPIException { + List results = studyFetcher.crawl(); + studyRepository.persist(results); + } +} diff --git a/src/main/java/org/jabref/logic/crawler/LibraryEntryToFetcherConverter.java b/src/main/java/org/jabref/logic/crawler/LibraryEntryToFetcherConverter.java new file mode 100644 index 00000000000..cadf5b2978e --- /dev/null +++ b/src/main/java/org/jabref/logic/crawler/LibraryEntryToFetcherConverter.java @@ -0,0 +1,67 @@ +package org.jabref.logic.crawler; + +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.SearchBasedFetcher; +import org.jabref.logic.importer.WebFetchers; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.UnknownField; + +import static org.jabref.model.entry.types.SystematicLiteratureReviewStudyEntryType.LIBRARY_ENTRY; + +/** + * Converts library entries from the given study into their corresponding fetchers. + */ +class LibraryEntryToFetcherConverter { + private final List libraryEntries; + private final ImportFormatPreferences importFormatPreferences; + + public LibraryEntryToFetcherConverter(List libraryEntries, ImportFormatPreferences importFormatPreferences) { + this.libraryEntries = libraryEntries; + this.importFormatPreferences = importFormatPreferences; + } + + /** + * Returns a list of instances of all active library fetchers. + * + * A fetcher is considered active if there exists an library entry of the library the fetcher is associated with that is enabled. + * + * @return Instances of all active fetchers defined in the study definition. + */ + public List getActiveFetchers() { + return getFetchersFromLibraryEntries(this.libraryEntries); + } + + /** + * Transforms a list of libraryEntries into a list of SearchBasedFetcher instances. + * + * @param libraryEntries List of entries + * @return List of fetcher instances + */ + private List getFetchersFromLibraryEntries(List libraryEntries) { + return libraryEntries.parallelStream() + .filter(bibEntry -> bibEntry.getType().getName().equals(LIBRARY_ENTRY.getName())) + .map(this::createFetcherFromLibraryEntry) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + /** + * Transforms a library entry into a SearchBasedFetcher instance. This only works if the library entry specifies a supported fetcher. + * + * @param libraryEntry the entry that will be converted + * @return An instance of the fetcher defined by the library entry. + */ + private SearchBasedFetcher createFetcherFromLibraryEntry(BibEntry libraryEntry) { + Set searchBasedFetchers = WebFetchers.getSearchBasedFetchers(importFormatPreferences); + String libraryNameFromFetcher = libraryEntry.getField(new UnknownField("name")).orElse(""); + return searchBasedFetchers.stream() + .filter(searchBasedFetcher -> searchBasedFetcher.getName().toLowerCase().equals(libraryNameFromFetcher.toLowerCase())) + .findAny() + .orElse(null); + } +} diff --git a/src/main/java/org/jabref/logic/crawler/StudyFetcher.java b/src/main/java/org/jabref/logic/crawler/StudyFetcher.java new file mode 100644 index 00000000000..c39ba7efe52 --- /dev/null +++ b/src/main/java/org/jabref/logic/crawler/StudyFetcher.java @@ -0,0 +1,80 @@ +package org.jabref.logic.crawler; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.jabref.logic.importer.FetcherException; +import org.jabref.logic.importer.PagedSearchBasedFetcher; +import org.jabref.logic.importer.SearchBasedFetcher; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.study.FetchResult; +import org.jabref.model.study.QueryResult; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Delegates the search of the provided set of targeted E-Libraries with the provided queries to the E-Library specific fetchers, + * and aggregates the results returned by the fetchers by query and E-Library. + */ +class StudyFetcher { + private static final Logger LOGGER = LoggerFactory.getLogger(StudyFetcher.class); + private static final int MAX_AMOUNT_OF_RESULTS_PER_FETCHER = 100; + + private final List activeFetchers; + private final List searchQueries; + + StudyFetcher(List activeFetchers, List searchQueries) throws IllegalArgumentException { + this.searchQueries = searchQueries; + this.activeFetchers = activeFetchers; + } + + /** + * Each Map Entry contains the results for one search term for all libraries. + * Each entry of the internal map contains the results for a given library. + * If any library API is not available, its corresponding entry is missing from the internal map. + */ + public List crawl() { + return searchQueries.parallelStream() + .map(this::getQueryResult) + .collect(Collectors.toList()); + } + + private QueryResult getQueryResult(String searchQuery) { + return new QueryResult(searchQuery, performSearchOnQuery(searchQuery)); + } + + /** + * Queries all Databases on the given searchQuery. + * + * @param searchQuery The query the search is performed for. + * @return Mapping of each fetcher by name and all their retrieved publications as a BibDatabase + */ + private List performSearchOnQuery(String searchQuery) { + return activeFetchers.parallelStream() + .map(fetcher -> performSearchOnQueryForFetcher(searchQuery, fetcher)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private FetchResult performSearchOnQueryForFetcher(String searchQuery, SearchBasedFetcher fetcher) { + try { + List fetchResult = new ArrayList<>(); + if (fetcher instanceof PagedSearchBasedFetcher) { + int pages = ((int) Math.ceil(((double) MAX_AMOUNT_OF_RESULTS_PER_FETCHER) / ((PagedSearchBasedFetcher) fetcher).getPageSize())); + for (int page = 0; page < pages; page++) { + fetchResult.addAll(((PagedSearchBasedFetcher) fetcher).performSearchPaged(searchQuery, page).getContent()); + } + } else { + fetchResult = fetcher.performSearch(searchQuery); + } + return new FetchResult(fetcher.getName(), new BibDatabase(fetchResult)); + } catch (FetcherException e) { + LOGGER.warn(String.format("%s API request failed", fetcher.getName()), e); + return null; + } + } +} diff --git a/src/main/java/org/jabref/logic/crawler/StudyRepository.java b/src/main/java/org/jabref/logic/crawler/StudyRepository.java new file mode 100644 index 00000000000..b302e065946 --- /dev/null +++ b/src/main/java/org/jabref/logic/crawler/StudyRepository.java @@ -0,0 +1,344 @@ +package org.jabref.logic.crawler; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.jabref.logic.citationkeypattern.CitationKeyGenerator; +import org.jabref.logic.crawler.git.GitHandler; +import org.jabref.logic.database.DatabaseMerger; +import org.jabref.logic.exporter.BibtexDatabaseWriter; +import org.jabref.logic.exporter.SavePreferences; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.OpenDatabase; +import org.jabref.logic.importer.ParseException; +import org.jabref.logic.importer.SearchBasedFetcher; +import org.jabref.logic.importer.fileformat.BibtexParser; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.field.UnknownField; +import org.jabref.model.entry.types.SystematicLiteratureReviewStudyEntryType; +import org.jabref.model.study.FetchResult; +import org.jabref.model.study.QueryResult; +import org.jabref.model.study.Study; +import org.jabref.model.util.FileUpdateMonitor; +import org.jabref.preferences.JabRefPreferences; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class manages all aspects of the study process related to the repository. + * + * It includes the parsing of the study definition file (study.bib) into a Study instance, + * the structured persistence of the crawling results for the study within the file based repository, + * as well as the sharing, and versioning of results using git. + */ +class StudyRepository { + // Tests work with study.bib + private static final String STUDY_DEFINITION_FILE_NAME = "study.bib"; + private static final Logger LOGGER = LoggerFactory.getLogger(StudyRepository.class); + private static final Pattern MATCHCOLON = Pattern.compile(":"); + private static final Pattern MATCHILLEGALCHARACTERS = Pattern.compile("[^A-Za-z0-9_.\\s=-]"); + + private final Path repositoryPath; + private final Path studyDefinitionBib; + private final GitHandler gitHandler; + private final Study study; + private final ImportFormatPreferences importFormatPreferences; + private final FileUpdateMonitor fileUpdateMonitor; + private final SavePreferences savePreferences; + private final BibEntryTypesManager bibEntryTypesManager; + + /** + * Creates a study repository. + * + * @param pathToRepository Where the repository root is located. + * @param gitHandler The git handler that managages any interaction with the remote repository + * @throws IllegalArgumentException If the repository root directory does not exist, or the root directory does not contain the study definition file. + * @throws IOException Thrown if the given repository does not exists, or the study definition file does not exist + * @throws ParseException Problem parsing the study definition file. + */ + public StudyRepository(Path pathToRepository, GitHandler gitHandler, ImportFormatPreferences importFormatPreferences, FileUpdateMonitor fileUpdateMonitor, SavePreferences savePreferences, BibEntryTypesManager bibEntryTypesManager) throws IOException, ParseException, GitAPIException { + this.repositoryPath = pathToRepository; + this.gitHandler = gitHandler; + try { + gitHandler.updateLocalRepository(); + } catch (GitAPIException e) { + LOGGER.error("Updating repository from remote failed"); + } + this.importFormatPreferences = importFormatPreferences; + this.fileUpdateMonitor = fileUpdateMonitor; + this.studyDefinitionBib = Path.of(repositoryPath.toString(), STUDY_DEFINITION_FILE_NAME); + this.savePreferences = savePreferences; + this.bibEntryTypesManager = bibEntryTypesManager; + + if (Files.notExists(repositoryPath)) { + throw new IOException("The given repository does not exists."); + } else if (Files.notExists(studyDefinitionBib)) { + throw new IOException("The study definition file does not exist in the given repository."); + } + study = parseStudyFile(); + this.setUpRepositoryStructure(); + } + + /** + * Returns entries stored in the repository for a certain query and fetcher + */ + public BibDatabaseContext getFetcherResultEntries(String query, String fetcherName) throws IOException { + return OpenDatabase.loadDatabase(getPathToFetcherResultFile(query, fetcherName), importFormatPreferences, fileUpdateMonitor).getDatabaseContext(); + } + + /** + * Returns the merged entries stored in the repository for a certain query + */ + public BibDatabaseContext getQueryResultEntries(String query) throws IOException { + return OpenDatabase.loadDatabase(getPathToQueryResultFile(query), importFormatPreferences, fileUpdateMonitor).getDatabaseContext(); + } + + /** + * Returns the merged entries stored in the repository for all queries + */ + public BibDatabaseContext getStudyResultEntries() throws IOException { + return OpenDatabase.loadDatabase(getPathToStudyResultFile(), importFormatPreferences, fileUpdateMonitor).getDatabaseContext(); + } + + /** + * The study definition file contains all the definitions of a study. This method extracts the BibEntries from the study BiB file. + * + * @return Returns the BibEntries parsed from the study definition file. + * @throws IOException Problem opening the input stream. + * @throws ParseException Problem parsing the study definition file. + */ + private Study parseStudyFile() throws IOException, ParseException { + BibtexParser parser = new BibtexParser(importFormatPreferences, fileUpdateMonitor); + List parsedEntries = new ArrayList<>(); + try (InputStream inputStream = Files.newInputStream(studyDefinitionBib)) { + parsedEntries.addAll(parser.parseEntries(inputStream)); + } + + BibEntry studyEntry = parsedEntries.parallelStream() + .filter(bibEntry -> bibEntry.getType().equals(SystematicLiteratureReviewStudyEntryType.STUDY_ENTRY)).findAny() + .orElseThrow(() -> new ParseException("Study definition file does not contain a study entry")); + List queryEntries = parsedEntries.parallelStream() + .filter(bibEntry -> bibEntry.getType().equals(SystematicLiteratureReviewStudyEntryType.SEARCH_QUERY_ENTRY)) + .collect(Collectors.toList()); + List libraryEntries = parsedEntries.parallelStream() + .filter(bibEntry -> bibEntry.getType().equals(SystematicLiteratureReviewStudyEntryType.LIBRARY_ENTRY)) + .collect(Collectors.toList()); + + return new Study(studyEntry, queryEntries, libraryEntries); + } + + public Study getStudy() { + return study; + } + + public void persist(List crawlResults) throws IOException, GitAPIException { + try { + gitHandler.updateLocalRepository(); + } catch (GitAPIException e) { + LOGGER.error("Updating repository from remote failed"); + } + persistResults(crawlResults); + study.setLastSearchDate(LocalDate.now()); + persistStudy(); + try { + gitHandler.updateRemoteRepository("Conducted search " + LocalDate.now()); + } catch (GitAPIException e) { + LOGGER.error("Updating remote repository failed"); + } + } + + private void persistStudy() throws IOException { + writeResultToFile(studyDefinitionBib, new BibDatabase(study.getAllEntries())); + } + + /** + * Create for each query a folder, and for each fetcher a bib file in the query folder to store its results. + */ + private void setUpRepositoryStructure() throws IOException { + // Cannot use stream here since IOException has to be thrown + LibraryEntryToFetcherConverter converter = new LibraryEntryToFetcherConverter(study.getActiveLibraryEntries(), importFormatPreferences); + for (String query : study.getSearchQueryStrings()) { + createQueryResultFolder(query); + converter.getActiveFetchers() + .forEach(searchBasedFetcher -> createFetcherResultFile(query, searchBasedFetcher)); + createQueryResultFile(query); + } + createStudyResultFile(); + } + + /** + * Creates a folder using the query and its corresponding query id. + * This folder name is unique for each query, as long as the query id in the study definition is unique for each query. + * + * @param query The query the folder is created for + */ + private void createQueryResultFolder(String query) throws IOException { + Path queryResultFolder = getPathToQueryDirectory(query); + createFolder(queryResultFolder); + } + + private void createFolder(Path folder) throws IOException { + if (Files.notExists(folder)) { + Files.createDirectory(folder); + } + } + + private void createFetcherResultFile(String query, SearchBasedFetcher searchBasedFetcher) { + String fetcherName = searchBasedFetcher.getName(); + Path fetcherResultFile = getPathToFetcherResultFile(query, fetcherName); + createBibFile(fetcherResultFile); + } + + private void createQueryResultFile(String query) { + Path queryResultFile = getPathToFetcherResultFile(query, "result"); + createBibFile(queryResultFile); + } + + private void createStudyResultFile() { + createBibFile(getPathToStudyResultFile()); + } + + private void createBibFile(Path file) { + if (Files.notExists(file)) { + try { + Files.createFile(file); + } catch (IOException e) { + throw new IllegalStateException("Error during creation of repository structure.", e); + } + } + } + + /** + * Returns a string that can be used as a folder name. + * This removes all characters from the query that are illegal for directory names. + * Structure: ID-trimmed query + * + * Examples: + * Input: '(title: test-title AND abstract: Test)' as a query entry with id 1 + * Output: '1 - title= test-title AND abstract= Test' + * + * Input: 'abstract: Test*' as a query entry with id 1 + * Output: '1 - abstract= Test' + * + * Input: '"test driven"' as a query entry with id 1 + * Output: '1 - test driven' + * + * @param query that is trimmed and combined with its query id + * @return a unique folder name for any query. + */ + private String trimNameAndAddID(String query) { + // Replace all field: with field= for folder name + String trimmedNamed = MATCHCOLON.matcher(query).replaceAll("="); + trimmedNamed = MATCHILLEGALCHARACTERS.matcher(trimmedNamed).replaceAll(""); + if (query.length() > 240) { + trimmedNamed = query.substring(0, 240); + } + String id = findQueryIDByQueryString(query); + return id + " - " + trimmedNamed; + } + + /** + * Helper to find the query id for folder name creation. + * Returns the id of the first SearchQuery BibEntry with a query field that matches the given query. + * + * @param query The query whose ID is searched + * @return ID of the query defined in the study definition. + */ + private String findQueryIDByQueryString(String query) { + String queryField = "query"; + return study.getSearchQueryEntries() + .parallelStream() + .filter(bibEntry -> bibEntry.getField(new UnknownField(queryField)).orElse("").equals(query)) + .map(BibEntry::getCitationKey) + .filter(Optional::isPresent) + .map(Optional::get) + .findFirst() + .orElseThrow() + .replaceFirst(queryField, ""); + } + + /** + * Persists the crawling results in the local file based repository. + * + * @param crawlResults The results that shall be persisted. + */ + private void persistResults(List crawlResults) throws IOException { + DatabaseMerger merger = new DatabaseMerger(); + BibDatabase newStudyResultEntries = new BibDatabase(); + + for (QueryResult result : crawlResults) { + BibDatabase queryResultEntries = new BibDatabase(); + for (FetchResult fetcherResult : result.getResultsPerFetcher()) { + BibDatabase fetcherEntries = fetcherResult.getFetchResult(); + BibDatabaseContext existingFetcherResult = getFetcherResultEntries(result.getQuery(), fetcherResult.getFetcherName()); + + // Create citation keys for all entries that do not have one + generateCiteKeys(existingFetcherResult, fetcherEntries); + + // Merge new entries into fetcher result file + merger.merge(existingFetcherResult.getDatabase(), fetcherEntries); + // Aggregate each fetcher result into the query result + merger.merge(queryResultEntries, fetcherEntries); + + writeResultToFile(getPathToFetcherResultFile(result.getQuery(), fetcherResult.getFetcherName()), existingFetcherResult.getDatabase()); + } + BibDatabase existingQueryEntries = getQueryResultEntries(result.getQuery()).getDatabase(); + + // Merge new entries into query result file + merger.merge(existingQueryEntries, queryResultEntries); + // Aggregate all new entries for every query into the study result + merger.merge(newStudyResultEntries, queryResultEntries); + + writeResultToFile(getPathToQueryResultFile(result.getQuery()), existingQueryEntries); + } + BibDatabase existingStudyResultEntries = getStudyResultEntries().getDatabase(); + + // Merge new entries into study result file + merger.merge(existingStudyResultEntries, newStudyResultEntries); + + writeResultToFile(getPathToStudyResultFile(), existingStudyResultEntries); + } + + private void generateCiteKeys(BibDatabaseContext existingEntries, BibDatabase targetEntries) { + CitationKeyGenerator citationKeyGenerator = new CitationKeyGenerator(existingEntries, JabRefPreferences.getInstance().getCitationKeyPatternPreferences()); + targetEntries.getEntries().stream().filter(bibEntry -> !bibEntry.hasCitationKey()).forEach(citationKeyGenerator::generateAndSetKey); + } + + private void writeResultToFile(Path pathToFile, BibDatabase entries) throws IOException { + try (Writer fileWriter = new FileWriter(pathToFile.toFile())) { + BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(fileWriter, savePreferences, bibEntryTypesManager); + databaseWriter.saveDatabase(new BibDatabaseContext(entries)); + } + } + + private Path getPathToFetcherResultFile(String query, String fetcherName) { + return Path.of(repositoryPath.toString(), trimNameAndAddID(query), fetcherName + ".bib"); + } + + private Path getPathToQueryResultFile(String query) { + return Path.of(repositoryPath.toString(), trimNameAndAddID(query), "result.bib"); + } + + private Path getPathToStudyResultFile() { + return Path.of(repositoryPath.toString(), "studyResult.bib"); + } + + private Path getPathToQueryDirectory(String query) { + return Path.of(repositoryPath.toString(), trimNameAndAddID(query)); + } +} diff --git a/src/main/java/org/jabref/logic/crawler/git/GitHandler.java b/src/main/java/org/jabref/logic/crawler/git/GitHandler.java new file mode 100644 index 00000000000..439f08dfccd --- /dev/null +++ b/src/main/java/org/jabref/logic/crawler/git/GitHandler.java @@ -0,0 +1,83 @@ +package org.jabref.logic.crawler.git; + +import java.io.IOException; +import java.nio.file.Path; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.RmCommand; +import org.eclipse.jgit.api.Status; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class handles the updating of the local and remote git repository that is located at the repository path + */ +public class GitHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(GitHandler.class); + private final Path repositoryPath; + private final CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(System.getenv("GIT_EMAIL"), System.getenv("GIT_PW")); + + /** + * Initialize the handler for the given repository + * + * @param repositoryPath The root of the intialized git repository + */ + public GitHandler(Path repositoryPath) { + this.repositoryPath = repositoryPath; + } + + /** + * Updates the local repository based on the main branch of the original remote repository + */ + public void updateLocalRepository() throws IOException, GitAPIException { + try (Git git = Git.open(this.repositoryPath.toFile())) { + git.pull() + .setRemote("origin") + .setRemoteBranchName("main") + .setCredentialsProvider(credentialsProvider) + .call(); + } + } + + /** + * Adds all the added, changed, and removed files to the index and updates the remote origin repository + * If pushiong to remote fails it fails silently + * + * @param commitMessage The commit message used for the commit to the remote repository + */ + public void updateRemoteRepository(String commitMessage) throws IOException, GitAPIException { + // First get up to date + this.updateLocalRepository(); + try (Git git = Git.open(this.repositoryPath.toFile())) { + Status status = git.status().call(); + if (!status.isClean()) { + // Add new and changed files to index + git.add() + .addFilepattern(".") + .call(); + // Add all removed files to index + if (!status.getMissing().isEmpty()) { + RmCommand removeCommand = git.rm() + .setCached(true); + status.getMissing().forEach(removeCommand::addFilepattern); + removeCommand.call(); + } + git.commit() + .setAllowEmpty(false) + .setMessage(commitMessage) + .call(); + try { + + git.push() + .setCredentialsProvider(credentialsProvider) + .call(); + } catch (GitAPIException e) { + LOGGER.info("Failed to push"); + } + } + } + } +} diff --git a/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java index a547dbe2175..1064a7f272e 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java @@ -178,7 +178,7 @@ private String constructComplexQueryString(ComplexSearchQuery complexSearchQuery complexSearchQuery.getTitlePhrases().forEach(title -> searchTerms.add("title:" + title)); complexSearchQuery.getJournal().ifPresent(journal -> searchTerms.add("journal:" + journal)); // Since Springer API does not support year range search, we ignore formYear and toYear and use "singleYear" only - complexSearchQuery.getSingleYear().ifPresent(year -> searchTerms.add("year:" + year.toString())); + complexSearchQuery.getSingleYear().ifPresent(year -> searchTerms.add("date:" + year.toString() + "*")); searchTerms.addAll(complexSearchQuery.getDefaultFieldPhrases()); return String.join(" AND ", searchTerms); } diff --git a/src/main/java/org/jabref/model/entry/types/EntryTypeFactory.java b/src/main/java/org/jabref/model/entry/types/EntryTypeFactory.java index 1ecf238382f..29422891f4e 100644 --- a/src/main/java/org/jabref/model/entry/types/EntryTypeFactory.java +++ b/src/main/java/org/jabref/model/entry/types/EntryTypeFactory.java @@ -50,6 +50,7 @@ public static EntryType parse(String typeName) { List types = new ArrayList<>(Arrays.asList(StandardEntryType.values())); types.addAll(Arrays.asList(IEEETranEntryType.values())); + types.addAll(Arrays.asList(SystematicLiteratureReviewStudyEntryType.values())); return types.stream().filter(type -> type.getName().equals(typeName.toLowerCase(Locale.ENGLISH))).findFirst().orElse(new UnknownEntryType(typeName)); } diff --git a/src/main/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryType.java b/src/main/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryType.java new file mode 100644 index 00000000000..1d9bd4be112 --- /dev/null +++ b/src/main/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryType.java @@ -0,0 +1,33 @@ +package org.jabref.model.entry.types; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Optional; + +public enum SystematicLiteratureReviewStudyEntryType implements EntryType { + STUDY_ENTRY("Study"), + SEARCH_QUERY_ENTRY("SearchQuery"), + LIBRARY_ENTRY("Library"); + + private final String displayName; + + SystematicLiteratureReviewStudyEntryType(String displayName) { + this.displayName = displayName; + } + + public static Optional fromName(String name) { + return Arrays.stream(SystematicLiteratureReviewStudyEntryType.values()) + .filter(field -> field.getName().equalsIgnoreCase(name)) + .findAny(); + } + + @Override + public String getName() { + return displayName.toLowerCase(Locale.ENGLISH); + } + + @Override + public String getDisplayName() { + return displayName; + } +} diff --git a/src/main/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryTypeDefinitions.java b/src/main/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryTypeDefinitions.java new file mode 100644 index 00000000000..5d1bf665bfe --- /dev/null +++ b/src/main/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryTypeDefinitions.java @@ -0,0 +1,60 @@ +package org.jabref.model.entry.types; + +import java.util.Arrays; +import java.util.List; + +import org.jabref.model.entry.BibEntryType; +import org.jabref.model.entry.BibEntryTypeBuilder; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.field.UnknownField; + +/** + * This class represents all supported entry types used in a study definition file + */ +public class SystematicLiteratureReviewStudyEntryTypeDefinitions { + + /** + * Entry type used for study meta data within a study definition file + * + *

    + *
  • Required fields: author, lastsearchdate, name, enabled
  • + *
  • Optional fields:
  • + *
+ */ + private static final BibEntryType STUDY_ENTRY = new BibEntryTypeBuilder() + .withType(SystematicLiteratureReviewStudyEntryType.STUDY_ENTRY) + .withRequiredFields(StandardField.AUTHOR, new UnknownField("lastsearchdate"), new UnknownField("name"), new UnknownField("researchquestions")) + .build(); + + /** + * Entry type for the queries within the study definition file + * + *
    + *
  • Required fields: query
  • + *
  • Optional fields:
  • + *
+ */ + private static final BibEntryType SEARCH_QUERY_ENTRY = new BibEntryTypeBuilder() + .withType(SystematicLiteratureReviewStudyEntryType.SEARCH_QUERY_ENTRY) + .withRequiredFields(new UnknownField("query")) + .build(); + + /** + * Entry type for the targeted libraries within a study definition file + * + *
    + *
  • Required fields: name, enabled
  • + *
  • Optional fields: comment
  • + *
+ */ + private static final BibEntryType LIBRARY_ENTRY = new BibEntryTypeBuilder() + .withType(SystematicLiteratureReviewStudyEntryType.STUDY_ENTRY) + .withRequiredFields(new UnknownField("name"), new UnknownField("enabled")) + .withImportantFields(StandardField.COMMENT) + .build(); + + public static final List ALL = Arrays.asList(STUDY_ENTRY, SEARCH_QUERY_ENTRY, LIBRARY_ENTRY); + + private SystematicLiteratureReviewStudyEntryTypeDefinitions() { + } +} diff --git a/src/main/java/org/jabref/model/study/FetchResult.java b/src/main/java/org/jabref/model/study/FetchResult.java new file mode 100644 index 00000000000..80637feb4ab --- /dev/null +++ b/src/main/java/org/jabref/model/study/FetchResult.java @@ -0,0 +1,24 @@ +package org.jabref.model.study; + +import org.jabref.model.database.BibDatabase; + +/** + * Represents the result of fetching the results for a query for a specific library + */ +public class FetchResult { + private final String fetcherName; + private final BibDatabase fetchResult; + + public FetchResult(String fetcherName, BibDatabase fetcherResult) { + this.fetcherName = fetcherName; + this.fetchResult = fetcherResult; + } + + public String getFetcherName() { + return fetcherName; + } + + public BibDatabase getFetchResult() { + return fetchResult; + } +} diff --git a/src/main/java/org/jabref/model/study/QueryResult.java b/src/main/java/org/jabref/model/study/QueryResult.java new file mode 100644 index 00000000000..2976b5224fe --- /dev/null +++ b/src/main/java/org/jabref/model/study/QueryResult.java @@ -0,0 +1,24 @@ +package org.jabref.model.study; + +import java.util.List; + +/** + * Represents the result of fetching the results from all active fetchers for a specific query. + */ +public class QueryResult { + private final String query; + private final List resultsPerLibrary; + + public QueryResult(String query, List resultsPerLibrary) { + this.query = query; + this.resultsPerLibrary = resultsPerLibrary; + } + + public String getQuery() { + return query; + } + + public List getResultsPerFetcher() { + return resultsPerLibrary; + } +} diff --git a/src/main/java/org/jabref/model/study/Study.java b/src/main/java/org/jabref/model/study/Study.java new file mode 100644 index 00000000000..37ed6e2328a --- /dev/null +++ b/src/main/java/org/jabref/model/study/Study.java @@ -0,0 +1,98 @@ +package org.jabref.model.study; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.UnknownField; + +/** + * This class represents a scientific study. + * + * This class defines all aspects of a scientific study relevant to the application. It is a proxy for the file based study definition. + */ +public class Study { + private static final String SEARCH_QUERY_FIELD_NAME = "query"; + + private final BibEntry studyEntry; + private final List queryEntries; + private final List libraryEntries; + + public Study(BibEntry studyEntry, List queryEntries, List libraryEntries) { + this.studyEntry = studyEntry; + this.queryEntries = queryEntries; + this.libraryEntries = libraryEntries; + } + + public List getAllEntries() { + List allEntries = new ArrayList<>(); + allEntries.add(studyEntry); + allEntries.addAll(queryEntries); + allEntries.addAll(libraryEntries); + return allEntries; + } + + /** + * Returns all query strings + * + * @return List of all queries as Strings. + */ + public List getSearchQueryStrings() { + return queryEntries.parallelStream() + .map(bibEntry -> bibEntry.getField(new UnknownField(SEARCH_QUERY_FIELD_NAME))) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + } + + /** + * This method returns the SearchQuery entries. + * This is required when the BibKey of the search term entry is required in combination with the search query (e.g. + * for the creation of the study repository structure). + */ + public List getSearchQueryEntries() { + return queryEntries; + } + + /** + * Returns a meta data entry of the first study entry found in the study definition file of the provided type. + * + * @param metaDataField The type of requested meta-data + * @return returns the requested meta data type of the first found study entry + * @throws IllegalArgumentException If the study file does not contain a study entry. + */ + public Optional getStudyMetaDataField(StudyMetaDataField metaDataField) throws IllegalArgumentException { + return studyEntry.getField(metaDataField.toField()); + } + + /** + * Sets the lastSearchDate field of the study entry + * + * @param date date the last time a search was conducted + */ + public void setLastSearchDate(LocalDate date) { + studyEntry.setField(StudyMetaDataField.STUDY_LAST_SEARCH.toField(), date.toString()); + } + + /** + * Extracts all active LibraryEntries from the BibEntries. + * + * @return List of BibEntries of type Library + * @throws IllegalArgumentException If a transformation from Library entry to LibraryDefinition fails + */ + public List getActiveLibraryEntries() throws IllegalArgumentException { + return libraryEntries + .parallelStream() + .filter(bibEntry -> { + // If enabled is not defined, the fetcher is active. + return bibEntry.getField(new UnknownField("enabled")) + .map(enabled -> enabled.equals("true")) + .orElse(true); + }) + .collect(Collectors.toList()); + } +} + diff --git a/src/main/java/org/jabref/model/study/StudyMetaDataField.java b/src/main/java/org/jabref/model/study/StudyMetaDataField.java new file mode 100644 index 00000000000..6dbea2a2dc8 --- /dev/null +++ b/src/main/java/org/jabref/model/study/StudyMetaDataField.java @@ -0,0 +1,24 @@ +package org.jabref.model.study; + +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.field.UnknownField; + +/** + * This enum represents the different fields in the study entry + */ +public enum StudyMetaDataField { + STUDY_NAME(new UnknownField("name")), STUDY_RESEARCH_QUESTIONS(new UnknownField("researchQuestions")), + STUDY_AUTHORS(StandardField.AUTHOR), STUDY_GIT_REPOSITORY(new UnknownField("gitRepositoryURL")), + STUDY_LAST_SEARCH(new UnknownField("lastSearchDate")); + + private final Field field; + + StudyMetaDataField(Field field) { + this.field = field; + } + + public Field toField() { + return this.field; + } +} diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index b58510e6d5c..d66d978e315 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -295,32 +295,21 @@ Entry\ owner=Entry owner Entry\ preview=Entry preview Entry\ table=Entry table - Entry\ table\ columns=Entry table columns Entry\ Title\ (Required\ to\ deliver\ recommendations.)=Entry Title (Required to deliver recommendations.) - Entry\ type=Entry type - Error=Error - Error\ occurred\ when\ parsing\ entry=Error occurred when parsing entry - Error\ opening\ file=Error opening file - Error\ while\ writing=Error while writing - +Error\ during\ persistence\ of\ crawling\ results.=Error during persistence of crawling results. +Error\ during\ reading\ of\ study\ definition\ file.=Error during reading of study definition file. '%0'\ exists.\ Overwrite\ file?='%0' exists. Overwrite file? - Export=Export - Export\ preferences=Export preferences - Export\ preferences\ to\ file=Export preferences to file - Export\ to\ clipboard=Export to clipboard - Export\ to\ text\ file.=Export to text file. - Exporting=Exporting Extension=Extension @@ -644,11 +633,9 @@ Previous\ preview\ layout=Previous preview layout Available=Available Selected=Selected Selected\ Layouts\ can\ not\ be\ empty=Selected Layouts can not be empty - +Start\ systematic\ literature\ review=Start systematic literature review Reset\ default\ preview\ style=Reset default preview style - Previous\ entry=Previous entry - Primary\ sort\ criterion=Primary sort criterion Problem\ with\ parsing\ entry=Problem with parsing entry Processing\ %0=Processing %0 diff --git a/src/test/java/org/jabref/logic/crawler/CrawlerTest.java b/src/test/java/org/jabref/logic/crawler/CrawlerTest.java new file mode 100644 index 00000000000..7c6b53e85a2 --- /dev/null +++ b/src/test/java/org/jabref/logic/crawler/CrawlerTest.java @@ -0,0 +1,105 @@ +package org.jabref.logic.crawler; + +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.jabref.logic.bibtex.FieldContentFormatterPreferences; +import org.jabref.logic.exporter.SavePreferences; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.metadata.SaveOrderConfig; +import org.jabref.model.util.DummyFileUpdateMonitor; + +import org.eclipse.jgit.api.Git; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Answers; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Integration test of the components used for SLR support + */ +class CrawlerTest { + @TempDir + Path tempRepositoryDirectory; + ImportFormatPreferences importFormatPreferences; + SavePreferences savePreferences; + BibEntryTypesManager entryTypesManager; + + @Test + public void testWhetherAllFilesAreCreated() throws Exception { + setUp(); + Crawler testCrawler = new Crawler(getPathToStudyDefinitionFile(), + new DummyFileUpdateMonitor(), + importFormatPreferences, + savePreferences, + entryTypesManager + ); + + testCrawler.performCrawl(); + + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "1 - Quantum"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "2 - Cloud Computing"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "3 - TestSearchQuery3"))); + + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "1 - Quantum", "ArXiv.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "2 - Cloud Computing", "ArXiv.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "3 - TestSearchQuery3", "ArXiv.bib"))); + + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "1 - Quantum", "Springer.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "2 - Cloud Computing", "Springer.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "3 - TestSearchQuery3", "Springer.bib"))); + + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "1 - Quantum", "result.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "2 - Cloud Computing", "result.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "3 - TestSearchQuery3", "result.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "studyResult.bib"))); + } + + private Path getPathToStudyDefinitionFile() { + return tempRepositoryDirectory.resolve("study.bib"); + } + + /** + * Set up mocks and copies the study definition file into the test repository + */ + private void setUp() throws Exception { + setUpRepository(); + importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS); + savePreferences = mock(SavePreferences.class, Answers.RETURNS_DEEP_STUBS); + when(savePreferences.getSaveOrder()).thenReturn(new SaveOrderConfig()); + when(savePreferences.getEncoding()).thenReturn(null); + when(savePreferences.takeMetadataSaveOrderInAccount()).thenReturn(true); + when(importFormatPreferences.getKeywordSeparator()).thenReturn(','); + when(importFormatPreferences.getFieldContentFormatterPreferences()).thenReturn(new FieldContentFormatterPreferences()); + when(importFormatPreferences.isKeywordSyncEnabled()).thenReturn(false); + when(importFormatPreferences.getEncoding()).thenReturn(StandardCharsets.UTF_8); + entryTypesManager = new BibEntryTypesManager(); + } + + private void setUpRepository() throws Exception { + Git git = Git.init() + .setDirectory(tempRepositoryDirectory.toFile()) + .call(); + setUpTestStudyDefinitionFile(); + git.add() + .addFilepattern(".") + .call(); + git.commit() + .setMessage("Initialize") + .call(); + git.close(); + } + + private void setUpTestStudyDefinitionFile() throws Exception { + Path destination = tempRepositoryDirectory.resolve("study.bib"); + URL studyDefinition = this.getClass().getResource("study.bib"); + FileUtil.copyFile(Path.of(studyDefinition.toURI()), destination, false); + } +} diff --git a/src/test/java/org/jabref/logic/crawler/LibraryEntryToFetcherConverterTest.java b/src/test/java/org/jabref/logic/crawler/LibraryEntryToFetcherConverterTest.java new file mode 100644 index 00000000000..629fad93ec4 --- /dev/null +++ b/src/test/java/org/jabref/logic/crawler/LibraryEntryToFetcherConverterTest.java @@ -0,0 +1,69 @@ +package org.jabref.logic.crawler; + +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.List; + +import org.jabref.logic.bibtex.FieldContentFormatterPreferences; +import org.jabref.logic.crawler.git.GitHandler; +import org.jabref.logic.exporter.SavePreferences; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.SearchBasedFetcher; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.metadata.SaveOrderConfig; +import org.jabref.model.study.Study; +import org.jabref.model.util.DummyFileUpdateMonitor; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Answers; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class LibraryEntryToFetcherConverterTest { + ImportFormatPreferences importFormatPreferences; + SavePreferences savePreferences; + BibEntryTypesManager entryTypesManager; + GitHandler gitHandler; + @TempDir + Path tempRepositoryDirectory; + + @BeforeEach + void setUpMocks() { + importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS); + savePreferences = mock(SavePreferences.class, Answers.RETURNS_DEEP_STUBS); + when(savePreferences.getSaveOrder()).thenReturn(new SaveOrderConfig()); + when(savePreferences.getEncoding()).thenReturn(null); + when(savePreferences.takeMetadataSaveOrderInAccount()).thenReturn(true); + when(importFormatPreferences.getKeywordSeparator()).thenReturn(','); + when(importFormatPreferences.getFieldContentFormatterPreferences()).thenReturn(new FieldContentFormatterPreferences()); + when(importFormatPreferences.isKeywordSyncEnabled()).thenReturn(false); + when(importFormatPreferences.getEncoding()).thenReturn(StandardCharsets.UTF_8); + entryTypesManager = new BibEntryTypesManager(); + gitHandler = mock(GitHandler.class, Answers.RETURNS_DEFAULTS); + } + + @Test + public void getActiveFetcherInstances() throws Exception { + Path studyDefinition = tempRepositoryDirectory.resolve("study.bib"); + copyTestStudyDefinitionFileIntoDirectory(studyDefinition); + + Study study = new StudyRepository(tempRepositoryDirectory, gitHandler, importFormatPreferences, new DummyFileUpdateMonitor(), savePreferences, entryTypesManager).getStudy(); + LibraryEntryToFetcherConverter converter = new LibraryEntryToFetcherConverter(study.getActiveLibraryEntries(), importFormatPreferences); + List result = converter.getActiveFetchers(); + + Assertions.assertEquals(2, result.size()); + Assertions.assertEquals(result.get(0).getName(), "Springer"); + Assertions.assertEquals(result.get(1).getName(), "ArXiv"); + } + + private void copyTestStudyDefinitionFileIntoDirectory(Path destination) throws Exception { + URL studyDefinition = this.getClass().getResource("study.bib"); + FileUtil.copyFile(Path.of(studyDefinition.toURI()), destination, false); + } +} diff --git a/src/test/java/org/jabref/logic/crawler/StudyRepositoryTest.java b/src/test/java/org/jabref/logic/crawler/StudyRepositoryTest.java new file mode 100644 index 00000000000..8a69c6d7a01 --- /dev/null +++ b/src/test/java/org/jabref/logic/crawler/StudyRepositoryTest.java @@ -0,0 +1,312 @@ +package org.jabref.logic.crawler; + +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.jabref.logic.bibtex.FieldContentFormatterPreferences; +import org.jabref.logic.citationkeypattern.CitationKeyGenerator; +import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences; +import org.jabref.logic.citationkeypattern.GlobalCitationKeyPattern; +import org.jabref.logic.crawler.git.GitHandler; +import org.jabref.logic.database.DatabaseMerger; +import org.jabref.logic.exporter.SavePreferences; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.field.UnknownField; +import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.model.metadata.SaveOrderConfig; +import org.jabref.model.study.FetchResult; +import org.jabref.model.study.QueryResult; +import org.jabref.model.study.Study; +import org.jabref.model.study.StudyMetaDataField; +import org.jabref.model.util.DummyFileUpdateMonitor; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Answers; + +import static org.jabref.logic.citationkeypattern.CitationKeyGenerator.DEFAULT_UNWANTED_CHARACTERS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class StudyRepositoryTest { + private static final String NON_EXISTING_DIRECTORY = "nonExistingTestRepositoryDirectory"; + CitationKeyPatternPreferences citationKeyPatternPreferences; + ImportFormatPreferences importFormatPreferences; + SavePreferences savePreferences; + BibEntryTypesManager entryTypesManager; + @TempDir + Path tempRepositoryDirectory; + StudyRepository studyRepository; + GitHandler gitHandler = mock(GitHandler.class, Answers.RETURNS_DEFAULTS); + + /** + * Set up mocks + */ + @BeforeEach + public void setUpMocks() { + savePreferences = mock(SavePreferences.class, Answers.RETURNS_DEEP_STUBS); + importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS); + citationKeyPatternPreferences = new CitationKeyPatternPreferences( + false, + false, + false, + CitationKeyPatternPreferences.KeySuffix.SECOND_WITH_A, + "", + "", + DEFAULT_UNWANTED_CHARACTERS, + GlobalCitationKeyPattern.fromPattern("[auth][year]"), + ','); + when(savePreferences.getSaveOrder()).thenReturn(new SaveOrderConfig()); + when(savePreferences.getEncoding()).thenReturn(null); + when(savePreferences.takeMetadataSaveOrderInAccount()).thenReturn(true); + when(importFormatPreferences.getKeywordSeparator()).thenReturn(','); + when(importFormatPreferences.getFieldContentFormatterPreferences()).thenReturn(new FieldContentFormatterPreferences()); + when(importFormatPreferences.isKeywordSyncEnabled()).thenReturn(false); + when(importFormatPreferences.getEncoding()).thenReturn(StandardCharsets.UTF_8); + entryTypesManager = new BibEntryTypesManager(); + } + + @Test + void providePathToNonExistentRepositoryThrowsException() { + Path nonExistingRepositoryDirectory = tempRepositoryDirectory.resolve(NON_EXISTING_DIRECTORY); + + assertThrows(IOException.class, () -> new StudyRepository(nonExistingRepositoryDirectory, gitHandler, importFormatPreferences, new DummyFileUpdateMonitor(), savePreferences, entryTypesManager)); + } + + @Test + void providePathToExistentRepositoryWithOutStudyDefinitionFileThrowsException() { + assertThrows(IOException.class, () -> new StudyRepository(tempRepositoryDirectory, gitHandler, importFormatPreferences, new DummyFileUpdateMonitor(), savePreferences, entryTypesManager)); + } + + /** + * Tests whether the StudyRepository correctly imports the study file. + */ + @Test + void studyFileCorrectlyImported() throws Exception { + setUpTestStudyDefinitionFile(); + List expectedSearchterms = List.of("Quantum", "Cloud Computing", "TestSearchQuery3"); + List expectedActiveFetchersByName = List.of("Springer", "ArXiv"); + + Study study = new StudyRepository(tempRepositoryDirectory, gitHandler, importFormatPreferences, new DummyFileUpdateMonitor(), savePreferences, entryTypesManager).getStudy(); + + assertEquals(expectedSearchterms, study.getSearchQueryStrings()); + assertEquals("TestStudyName", study.getStudyMetaDataField(StudyMetaDataField.STUDY_NAME).get()); + assertEquals("Jab Ref", study.getStudyMetaDataField(StudyMetaDataField.STUDY_AUTHORS).get()); + assertEquals("Question1; Question2", study.getStudyMetaDataField(StudyMetaDataField.STUDY_RESEARCH_QUESTIONS).get()); + assertEquals(expectedActiveFetchersByName, study.getActiveLibraryEntries() + .stream() + .filter(bibEntry -> bibEntry.getType().getName().equals("library")) + .map(bibEntry -> bibEntry.getField(new UnknownField("name")).orElse("")) + .collect(Collectors.toList()) + ); + } + + /** + * Tests whether the file structure of the repository is created correctly from the study definitions file. + */ + @Test + void repositoryStructureCorrectlyCreated() throws Exception { + // When repository is instantiated the directory structure is created + getTestStudyRepository(); + + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "1 - Quantum"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "2 - Cloud Computing"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "3 - TestSearchQuery3"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "1 - Quantum", "ArXiv.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "2 - Cloud Computing", "ArXiv.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "3 - TestSearchQuery3", "ArXiv.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "1 - Quantum", "Springer.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "2 - Cloud Computing", "Springer.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "3 - TestSearchQuery3", "Springer.bib"))); + assertTrue(Files.notExists(Path.of(tempRepositoryDirectory.toString(), "1 - Quantum", "IEEEXplore.bib"))); + assertTrue(Files.notExists(Path.of(tempRepositoryDirectory.toString(), "2 - Cloud Computing", "IEEEXplore.bib"))); + assertTrue(Files.notExists(Path.of(tempRepositoryDirectory.toString(), "3 - TestSearchQuery3", "IEEEXplore.bib"))); + } + + /** + * This tests whether the repository returns the stored bib entries correctly. + */ + @Test + void bibEntriesCorrectlyStored() throws Exception { + StudyRepository repository = getTestStudyRepository(); + setUpTestResultFile(); + List result = repository.getFetcherResultEntries("Quantum", "ArXiv").getEntries(); + assertEquals(getArXivQuantumMockResults(), result); + } + + @Test + void fetcherResultsPersistedCorrectly() throws Exception { + List mockResults = getMockResults(); + + getTestStudyRepository().persist(mockResults); + + assertEquals(getArXivQuantumMockResults(), getTestStudyRepository().getFetcherResultEntries("Quantum", "ArXiv").getEntries()); + assertEquals(getSpringerQuantumMockResults(), getTestStudyRepository().getFetcherResultEntries("Quantum", "Springer").getEntries()); + assertEquals(getSpringerCloudComputingMockResults(), getTestStudyRepository().getFetcherResultEntries("Cloud Computing", "Springer").getEntries()); + } + + @Test + void mergedResultsPersistedCorrectly() throws Exception { + List mockResults = getMockResults(); + List expected = new ArrayList<>(); + expected.addAll(getArXivQuantumMockResults()); + expected.add(getSpringerQuantumMockResults().get(1)); + expected.add(getSpringerQuantumMockResults().get(2)); + + getTestStudyRepository().persist(mockResults); + + // All Springer results are duplicates for "Quantum" + assertEquals(expected, getTestStudyRepository().getQueryResultEntries("Quantum").getEntries()); + assertEquals(getSpringerCloudComputingMockResults(), getTestStudyRepository().getQueryResultEntries("Cloud Computing").getEntries()); + } + + @Test + void setsLastSearchDatePersistedCorrectly() throws Exception { + List mockResults = getMockResults(); + + getTestStudyRepository().persist(mockResults); + + assertEquals(LocalDate.now().toString(), getTestStudyRepository().getStudy().getStudyMetaDataField(StudyMetaDataField.STUDY_LAST_SEARCH).get()); + } + + @Test + void studyResultsPersistedCorrectly() throws Exception { + List mockResults = getMockResults(); + + getTestStudyRepository().persist(mockResults); + + assertEquals(new HashSet<>(getNonDuplicateBibEntryResult().getEntries()), new HashSet<>(getTestStudyRepository().getStudyResultEntries().getEntries())); + } + + private StudyRepository getTestStudyRepository() throws Exception { + if (Objects.isNull(studyRepository)) { + setUpTestStudyDefinitionFile(); + studyRepository = new StudyRepository(tempRepositoryDirectory, gitHandler, importFormatPreferences, new DummyFileUpdateMonitor(), savePreferences, entryTypesManager); + } + return studyRepository; + } + + /** + * Copies the study definition file into the test repository + */ + private void setUpTestStudyDefinitionFile() throws Exception { + Path destination = tempRepositoryDirectory.resolve("study.bib"); + URL studyDefinition = this.getClass().getResource("study.bib"); + FileUtil.copyFile(Path.of(studyDefinition.toURI()), destination, false); + } + + /** + * This overwrites the existing result file in the repository with a result file containing multiple BibEntries. + * The repository has to exist before this method is called. + */ + private void setUpTestResultFile() throws Exception { + Path queryDirectory = Path.of(tempRepositoryDirectory.toString(), "1 - Quantum"); + Path resultFileLocation = Path.of(queryDirectory.toString(), "ArXiv" + ".bib"); + URL resultFile = this.getClass().getResource("ArXivQuantumMock.bib"); + FileUtil.copyFile(Path.of(resultFile.toURI()), resultFileLocation, true); + resultFileLocation = Path.of(queryDirectory.toString(), "Springer" + ".bib"); + resultFile = this.getClass().getResource("SpringerQuantumMock.bib"); + FileUtil.copyFile(Path.of(resultFile.toURI()), resultFileLocation, true); + } + + private BibDatabase getNonDuplicateBibEntryResult() { + BibDatabase mockResults = new BibDatabase(getSpringerCloudComputingMockResults()); + DatabaseMerger merger = new DatabaseMerger(); + merger.merge(mockResults, new BibDatabase(getSpringerQuantumMockResults())); + merger.merge(mockResults, new BibDatabase(getArXivQuantumMockResults())); + return mockResults; + } + + private List getMockResults() { + QueryResult resultQuantum = + new QueryResult("Quantum", List.of( + new FetchResult("ArXiv", new BibDatabase(stripCitationKeys(getArXivQuantumMockResults()))), + new FetchResult("Springer", new BibDatabase(stripCitationKeys(getSpringerQuantumMockResults()))))); + QueryResult resultCloudComputing = new QueryResult("Cloud Computing", List.of(new FetchResult("Springer", new BibDatabase(getSpringerCloudComputingMockResults())))); + return List.of(resultQuantum, resultCloudComputing); + } + + /** + * Strips the citation key from fetched entries as these normally do not have a citation key + */ + private List stripCitationKeys(List entries) { + entries.forEach(bibEntry -> bibEntry.setCitationKey("")); + return entries; + } + + private List getArXivQuantumMockResults() { + BibEntry entry1 = new BibEntry() + .withCitationKey("Blaha") + .withField(StandardField.AUTHOR, "Stephen Blaha") + .withField(StandardField.TITLE, "Quantum Computers and Quantum Computer Languages: Quantum Assembly Language and Quantum C Language"); + entry1.setType(StandardEntryType.Article); + BibEntry entry2 = new BibEntry() + .withCitationKey("Kaye") + .withField(StandardField.AUTHOR, "Phillip Kaye and Michele Mosca") + .withField(StandardField.TITLE, "Quantum Networks for Generating Arbitrary Quantum States"); + entry2.setType(StandardEntryType.Article); + BibEntry entry3 = new BibEntry() + .withCitationKey("Watrous") + .withField(StandardField.AUTHOR, "John Watrous") + .withField(StandardField.TITLE, "Quantum Computational Complexity"); + entry3.setType(StandardEntryType.Article); + + return List.of(entry1, entry2, entry3); + } + + private List getSpringerQuantumMockResults() { + // This is a duplicate of entry 1 of ArXiv + BibEntry entry1 = new BibEntry() + .withCitationKey("Blaha") + .withField(StandardField.AUTHOR, "Stephen Blaha") + .withField(StandardField.TITLE, "Quantum Computers and Quantum Computer Languages: Quantum Assembly Language and Quantum C Language"); + entry1.setType(StandardEntryType.Article); + BibEntry entry2 = new BibEntry() + .withCitationKey("Kroeger") + .withField(StandardField.AUTHOR, "H. Kröger") + .withField(StandardField.TITLE, "Nonlinear Dynamics In Quantum Physics -- Quantum Chaos and Quantum Instantons"); + entry2.setType(StandardEntryType.Article); + BibEntry entry3 = new BibEntry() + .withField(StandardField.AUTHOR, "Zieliński, Cezary") + .withField(StandardField.TITLE, "Automatic Control, Robotics, and Information Processing"); + entry3.setType(StandardEntryType.Article); + + CitationKeyGenerator citationKeyGenerator = new CitationKeyGenerator(new BibDatabaseContext(), citationKeyPatternPreferences); + citationKeyGenerator.generateAndSetKey(entry3); + + return List.of(entry1, entry2, entry3); + } + + private List getSpringerCloudComputingMockResults() { + BibEntry entry1 = new BibEntry() + .withCitationKey("Gritzalis") + .withField(StandardField.AUTHOR, "Gritzalis, Dimitris and Stergiopoulos, George and Vasilellis, Efstratios and Anagnostopoulou, Argiro") + .withField(StandardField.TITLE, "Readiness Exercises: Are Risk Assessment Methodologies Ready for the Cloud?"); + entry1.setType(StandardEntryType.Article); + BibEntry entry2 = new BibEntry() + .withCitationKey("Rangras") + .withField(StandardField.AUTHOR, "Rangras, Jimit and Bhavsar, Sejal") + .withField(StandardField.TITLE, "Design of Framework for Disaster Recovery in Cloud Computing"); + entry2.setType(StandardEntryType.Article); + return List.of(entry1, entry2); + } +} diff --git a/src/test/java/org/jabref/logic/importer/fetcher/SearchBasedFetcherCapabilityTest.java b/src/test/java/org/jabref/logic/importer/fetcher/SearchBasedFetcherCapabilityTest.java index 73afa6d63c9..c31bd348b0c 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/SearchBasedFetcherCapabilityTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/SearchBasedFetcherCapabilityTest.java @@ -21,7 +21,7 @@ /** * Defines the set of capability tests that each tests a given search capability, e.g. author based search. * The idea is to code the capabilities of a fetcher into Java code. - * This way, a) the capbilities of a fetcher are checked automatically (because they can change from time-to-time by the provider) + * This way, a) the capabilities of a fetcher are checked automatically (because they can change from time-to-time by the provider) * and b) the queries sent to the fetchers can be debugged directly without a route through to some fetcher code. */ interface SearchBasedFetcherCapabilityTest { diff --git a/src/test/java/org/jabref/model/study/StudyTest.java b/src/test/java/org/jabref/model/study/StudyTest.java new file mode 100644 index 00000000000..9ab34fcd55e --- /dev/null +++ b/src/test/java/org/jabref/model/study/StudyTest.java @@ -0,0 +1,94 @@ +package org.jabref.model.study; + +import java.time.LocalDate; +import java.util.List; + +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.field.UnknownField; +import org.jabref.model.entry.types.SystematicLiteratureReviewStudyEntryType; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class StudyTest { + Study testStudy; + + @BeforeEach + public void setUpTestStudy() { + BibEntry studyEntry = new BibEntry() + .withField(new UnknownField("name"), "TestStudyName") + .withField(StandardField.AUTHOR, "Jab Ref") + .withField(new UnknownField("researchQuestions"), "Question1; Question2") + .withField(new UnknownField("gitRepositoryURL"), "https://github.com/eclipse/jgit.git"); + studyEntry.setType(SystematicLiteratureReviewStudyEntryType.STUDY_ENTRY); + + // Create three SearchTerm entries. + BibEntry searchQuery1 = new BibEntry() + .withField(new UnknownField("query"), "TestSearchQuery1"); + searchQuery1.setType(SystematicLiteratureReviewStudyEntryType.SEARCH_QUERY_ENTRY); + searchQuery1.setCitationKey("query1"); + + BibEntry searchQuery2 = new BibEntry() + .withField(new UnknownField("query"), "TestSearchQuery2"); + searchQuery2.setType(SystematicLiteratureReviewStudyEntryType.SEARCH_QUERY_ENTRY); + searchQuery2.setCitationKey("query2"); + + BibEntry searchQuery3 = new BibEntry() + .withField(new UnknownField("query"), "TestSearchQuery3"); + searchQuery3.setType(SystematicLiteratureReviewStudyEntryType.SEARCH_QUERY_ENTRY); + searchQuery3.setCitationKey("query3"); + + // Create two Library entries + BibEntry library1 = new BibEntry() + .withField(new UnknownField("name"), "acm") + .withField(new UnknownField("enabled"), "false") + .withField(new UnknownField("comment"), "disabled, because no good results"); + library1.setType(SystematicLiteratureReviewStudyEntryType.LIBRARY_ENTRY); + library1.setCitationKey("library1"); + + BibEntry library2 = new BibEntry() + .withField(new UnknownField("name"), "arxiv") + .withField(new UnknownField("enabled"), "true") + .withField(new UnknownField("Comment"), ""); + library2.setType(SystematicLiteratureReviewStudyEntryType.LIBRARY_ENTRY); + library2.setCitationKey("library2"); + + testStudy = new Study(studyEntry, List.of(searchQuery1, searchQuery2, searchQuery3), List.of(library1, library2)); + } + + @Test + void getSearchTermsAsStrings() { + List expectedSearchTerms = List.of("TestSearchQuery1", "TestSearchQuery2", "TestSearchQuery3"); + assertEquals(expectedSearchTerms, testStudy.getSearchQueryStrings()); + } + + @Test + void setLastSearchTime() { + LocalDate date = LocalDate.now(); + testStudy.setLastSearchDate(date); + assertEquals(date.toString(), testStudy.getStudyMetaDataField(StudyMetaDataField.STUDY_LAST_SEARCH).get()); + } + + @Test + void getStudyName() { + assertEquals("TestStudyName", testStudy.getStudyMetaDataField(StudyMetaDataField.STUDY_NAME).get()); + } + + @Test + void getStudyAuthor() { + assertEquals("Jab Ref", testStudy.getStudyMetaDataField(StudyMetaDataField.STUDY_AUTHORS).get()); + } + + @Test + void getResearchQuestions() { + assertEquals("Question1; Question2", testStudy.getStudyMetaDataField(StudyMetaDataField.STUDY_RESEARCH_QUESTIONS).get()); + } + + @Test + void getGitRepositoryURL() { + assertEquals("https://github.com/eclipse/jgit.git", testStudy.getStudyMetaDataField(StudyMetaDataField.STUDY_GIT_REPOSITORY).get()); + } +} diff --git a/src/test/resources/org/jabref/logic/crawler/ArXivQuantumMock.bib b/src/test/resources/org/jabref/logic/crawler/ArXivQuantumMock.bib new file mode 100644 index 00000000000..85df0f1060b --- /dev/null +++ b/src/test/resources/org/jabref/logic/crawler/ArXivQuantumMock.bib @@ -0,0 +1,15 @@ + +@Article{Blaha, + author = {Stephen Blaha}, + title = {Quantum Computers and Quantum Computer Languages: Quantum Assembly Language and Quantum C Language}, +} + +@Article{Kaye, + author = {Phillip Kaye and Michele Mosca}, + title = {Quantum Networks for Generating Arbitrary Quantum States}, +} + +@Article{Watrous, + author = {John Watrous}, + title = {Quantum Computational Complexity}, +} diff --git a/src/test/resources/org/jabref/logic/crawler/SpringerCloud ComputingMock.bib b/src/test/resources/org/jabref/logic/crawler/SpringerCloud ComputingMock.bib new file mode 100644 index 00000000000..627166213fa --- /dev/null +++ b/src/test/resources/org/jabref/logic/crawler/SpringerCloud ComputingMock.bib @@ -0,0 +1,9 @@ +@InCollection{Gritzalis, + author = {Gritzalis, Dimitris and Stergiopoulos, George and Vasilellis, Efstratios and Anagnostopoulou, Argiro}, + title = {Readiness Exercises: Are Risk Assessment Methodologies Ready for the Cloud?}, +} + +@InCollection{Rangras, + author = {Rangras, Jimit and Bhavsar, Sejal}, + title = {Design of Framework for Disaster Recovery in Cloud Computing}, +} diff --git a/src/test/resources/org/jabref/logic/crawler/SpringerQuantumMock.bib b/src/test/resources/org/jabref/logic/crawler/SpringerQuantumMock.bib new file mode 100644 index 00000000000..3cfa2f88487 --- /dev/null +++ b/src/test/resources/org/jabref/logic/crawler/SpringerQuantumMock.bib @@ -0,0 +1,9 @@ +@Article{Zielinski, + author = {Zieliński, Cezary}, + title = {Quantum Computers and Quantum Computer Languages: Quantum Assembly Language and Quantum C Language}, +} + +@Article{Kaye, + author = {H. Kröger}, + title = {Quantum Networks for Generating Arbitrary Quantum States}, +} diff --git a/src/test/resources/org/jabref/logic/crawler/study.bib b/src/test/resources/org/jabref/logic/crawler/study.bib new file mode 100644 index 00000000000..3f9809a82e5 --- /dev/null +++ b/src/test/resources/org/jabref/logic/crawler/study.bib @@ -0,0 +1,37 @@ +% Encoding: UTF-8 + +@Study{v10, + name={TestStudyName}, + author={Jab Ref}, + researchQuestions={Question1; Question2}, +} + +@SearchQuery{query1, + query={Quantum}, +} + +@SearchQuery{query2, + query={Cloud Computing}, +} + +@SearchQuery{query3, + query={TestSearchQuery3}, +} + +@Library{library1, + name = {Springer}, + enabled = {true}, + comment = {}, +} + +@Library{library2, + name = {ArXiv}, + enabled = {true}, + comment = {}, +} + +@Library{library3, + name = {IEEEXplore}, + enabled = {false}, + comment = {}, +} From f1a2fa73b6560a8b4219f95bfc13f89644d486d8 Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Thu, 26 Nov 2020 10:09:08 +0100 Subject: [PATCH 104/201] Fix binding issue for the regex and case sensitive search buttons (#7125) Signed-off-by: Dominik Voigt --- .../java/org/jabref/gui/search/GlobalSearchBar.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/jabref/gui/search/GlobalSearchBar.java b/src/main/java/org/jabref/gui/search/GlobalSearchBar.java index 9cc91baeee2..67f19778048 100644 --- a/src/main/java/org/jabref/gui/search/GlobalSearchBar.java +++ b/src/main/java/org/jabref/gui/search/GlobalSearchBar.java @@ -126,13 +126,16 @@ public GlobalSearchBar(JabRefFrame frame, StateManager stateManager, Preferences // searchModeButton = new Button(); initSearchModifierButtons(); - BooleanBinding focusBinding = searchField.focusedProperty() - .or(regularExpressionButton.focusedProperty() - .or(caseSensitiveButton.focusedProperty())); + BooleanBinding focusedOrActive = searchField.focusedProperty() + .or(regularExpressionButton.focusedProperty()) + .or(caseSensitiveButton.focusedProperty()) + .or(searchField.textProperty() + .isNotEmpty()); + regularExpressionButton.visibleProperty().unbind(); - regularExpressionButton.visibleProperty().bind(focusBinding); + regularExpressionButton.visibleProperty().bind(focusedOrActive); caseSensitiveButton.visibleProperty().unbind(); - caseSensitiveButton.visibleProperty().bind(focusBinding); + caseSensitiveButton.visibleProperty().bind(focusedOrActive); StackPane modifierButtons = new StackPane(new HBox(regularExpressionButton, caseSensitiveButton)); modifierButtons.setAlignment(Pos.CENTER); From 9d7b93d09ca9e092790d8bcfd2bf0329176f3948 Mon Sep 17 00:00:00 2001 From: Manas Singh <39790333+kingmanas@users.noreply.github.com> Date: Sun, 29 Nov 2020 21:31:16 +0530 Subject: [PATCH 105/201] Searchbar glyph icon colors in Dark Theme [FIXED] (#7131) --- src/main/java/org/jabref/gui/Dark.css | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/org/jabref/gui/Dark.css b/src/main/java/org/jabref/gui/Dark.css index e0b19e2adc5..0fd1c188c7f 100644 --- a/src/main/java/org/jabref/gui/Dark.css +++ b/src/main/java/org/jabref/gui/Dark.css @@ -90,3 +90,13 @@ #preferencesContainer .tab-pane > .tab-header-area > .tab-header-background { -fx-background-color: -jr-background; } + +.mainToolbar .search-field .toggle-button .glyph-icon { + -fx-fill: -jr-search-text; + -fx-text-fill: -jr-search-text; +} + +.mainToolbar .search-field .toggle-button:selected .glyph-icon { + -fx-fill: derive(-jr-search-text, 80%); + -fx-text-fill: derive(-jr-search-text, 80%); +} From b05ebac4ccfcd1c57c17e4a07646c09e9446db7e Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Sun, 29 Nov 2020 17:02:54 +0100 Subject: [PATCH 106/201] Add githandler mock to crawler test to fix NPE (#7133) Pass githandler to crawler to enable mock Signed-off-by: Dominik Voigt --- src/main/java/org/jabref/gui/StartLiteratureReviewAction.java | 3 ++- src/main/java/org/jabref/logic/crawler/Crawler.java | 4 ++-- src/test/java/org/jabref/logic/crawler/CrawlerTest.java | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/gui/StartLiteratureReviewAction.java b/src/main/java/org/jabref/gui/StartLiteratureReviewAction.java index d05d0f817f5..c67328a4e19 100644 --- a/src/main/java/org/jabref/gui/StartLiteratureReviewAction.java +++ b/src/main/java/org/jabref/gui/StartLiteratureReviewAction.java @@ -10,6 +10,7 @@ import org.jabref.gui.util.FileDialogConfiguration; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.crawler.Crawler; +import org.jabref.logic.crawler.git.GitHandler; import org.jabref.logic.importer.ParseException; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntryTypesManager; @@ -49,7 +50,7 @@ public void execute() { } final Crawler crawler; try { - crawler = new Crawler(studyDefinitionFile.get(), fileUpdateMonitor, JabRefPreferences.getInstance().getImportFormatPreferences(), JabRefPreferences.getInstance().getSavePreferences(), new BibEntryTypesManager()); + crawler = new Crawler(studyDefinitionFile.get(), new GitHandler(studyDefinitionFile.get().getParent()), fileUpdateMonitor, JabRefPreferences.getInstance().getImportFormatPreferences(), JabRefPreferences.getInstance().getSavePreferences(), new BibEntryTypesManager()); } catch (IOException | ParseException | GitAPIException e) { LOGGER.error("Error during reading of study definition file.", e); dialogService.showErrorDialogAndWait(Localization.lang("Error during reading of study definition file."), e); diff --git a/src/main/java/org/jabref/logic/crawler/Crawler.java b/src/main/java/org/jabref/logic/crawler/Crawler.java index eade3b55a59..b17745e05c5 100644 --- a/src/main/java/org/jabref/logic/crawler/Crawler.java +++ b/src/main/java/org/jabref/logic/crawler/Crawler.java @@ -31,9 +31,9 @@ public class Crawler { * * @param studyDefinitionFile The path to the study definition file that contains the list of targeted E-Libraries and used cross-library queries */ - public Crawler(Path studyDefinitionFile, FileUpdateMonitor fileUpdateMonitor, ImportFormatPreferences importFormatPreferences, SavePreferences savePreferences, BibEntryTypesManager bibEntryTypesManager) throws IllegalArgumentException, IOException, ParseException, GitAPIException { + public Crawler(Path studyDefinitionFile, GitHandler gitHandler, FileUpdateMonitor fileUpdateMonitor, ImportFormatPreferences importFormatPreferences, SavePreferences savePreferences, BibEntryTypesManager bibEntryTypesManager) throws IllegalArgumentException, IOException, ParseException, GitAPIException { Path studyRepositoryRoot = studyDefinitionFile.getParent(); - studyRepository = new StudyRepository(studyRepositoryRoot, new GitHandler(studyRepositoryRoot), importFormatPreferences, fileUpdateMonitor, savePreferences, bibEntryTypesManager); + studyRepository = new StudyRepository(studyRepositoryRoot, gitHandler, importFormatPreferences, fileUpdateMonitor, savePreferences, bibEntryTypesManager); Study study = studyRepository.getStudy(); LibraryEntryToFetcherConverter libraryEntryToFetcherConverter = new LibraryEntryToFetcherConverter(study.getActiveLibraryEntries(), importFormatPreferences); this.studyFetcher = new StudyFetcher(libraryEntryToFetcherConverter.getActiveFetchers(), study.getSearchQueryStrings()); diff --git a/src/test/java/org/jabref/logic/crawler/CrawlerTest.java b/src/test/java/org/jabref/logic/crawler/CrawlerTest.java index 7c6b53e85a2..c93ab4c628d 100644 --- a/src/test/java/org/jabref/logic/crawler/CrawlerTest.java +++ b/src/test/java/org/jabref/logic/crawler/CrawlerTest.java @@ -6,6 +6,7 @@ import java.nio.file.Path; import org.jabref.logic.bibtex.FieldContentFormatterPreferences; +import org.jabref.logic.crawler.git.GitHandler; import org.jabref.logic.exporter.SavePreferences; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.util.io.FileUtil; @@ -31,11 +32,13 @@ class CrawlerTest { ImportFormatPreferences importFormatPreferences; SavePreferences savePreferences; BibEntryTypesManager entryTypesManager; + GitHandler gitHandler = mock(GitHandler.class, Answers.RETURNS_DEFAULTS); @Test public void testWhetherAllFilesAreCreated() throws Exception { setUp(); Crawler testCrawler = new Crawler(getPathToStudyDefinitionFile(), + gitHandler, new DummyFileUpdateMonitor(), importFormatPreferences, savePreferences, From e994a8f5f2a01629ffbf2c4a99e806f437571217 Mon Sep 17 00:00:00 2001 From: Christoph Date: Sun, 29 Nov 2020 17:04:52 +0100 Subject: [PATCH 107/201] Fix document viewer not showing first page (#7132) * Fix document viewer starting with second page Fixes #7108 * Fix update of total page number * Fix checkstyle --- CHANGELOG.md | 1 + .../jabref/gui/documentviewer/DocumentViewerViewModel.java | 7 +++++-- .../gui/documentviewer/PdfDocumentPageViewModel.java | 2 +- .../jabref/gui/documentviewer/PdfDocumentViewModel.java | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfcd7cb3ce1..9f8342edb4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://git - We fixed an issue where the EndNote XML Import would fail on empty keywords tags [forum#2387](https://discourse.jabref.org/t/importing-in-unknown-format-fails-to-import-xml-library-from-bookends-export/2387) - We fixed an issue where the color of groups of type "free search expression" not persisting after restarting the application [#6999](https://github.com/JabRef/jabref/issues/6999) - We fixed an issue where modifications in the source tab where not saved without switching to another field before saving the library [#6622](https://github.com/JabRef/jabref/issues/6622) +- We fixed an issue where the "Document Viewer" did not show the first page of the opened pdf document and did not show the correct total number of pages [#7108](https://github.com/JabRef/jabref/issues/7108) ### Removed diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java index dc32be00a35..59eac25ae99 100644 --- a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Objects; +import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ListProperty; @@ -55,9 +56,11 @@ public DocumentViewerViewModel(StateManager stateManager) { } }); - maxPages.bindBidirectional( + // we need to wrap this in run later so that the max pages number is correctly shown + Platform.runLater(() -> { + maxPages.bindBidirectional( EasyBind.wrapNullable(currentDocument).selectProperty(DocumentViewModel::maxPagesProperty)); - + }); setCurrentEntries(this.stateManager.getSelectedEntries()); } diff --git a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java index 5ad9a3b528c..9010ec3379c 100644 --- a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java +++ b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java @@ -60,7 +60,7 @@ public Image render(int width, int height) { @Override public int getPageNumber() { - return pageNumber; + return pageNumber + 1; } @Override diff --git a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewModel.java b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewModel.java index 9b73c87102a..7e5ae9a5969 100644 --- a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewModel.java +++ b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewModel.java @@ -26,7 +26,7 @@ public ObservableList getPages() { List pdfPages = new ArrayList<>(); // There is apparently no neat way to get the page number from a PDPage...thus this old-style for loop for (int i = 0; i < pages.getCount(); i++) { - pdfPages.add(new PdfDocumentPageViewModel(pages.get(i), i + 1, document)); + pdfPages.add(new PdfDocumentPageViewModel(pages.get(i), i, document)); } return FXCollections.observableArrayList(pdfPages); } From c3e42e0c9e13429221ceceaa2875eac0b8637c46 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 29 Nov 2020 20:09:27 +0100 Subject: [PATCH 108/201] Add missing author --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 18a72d4d5de..0802288e203 100644 --- a/AUTHORS +++ b/AUTHORS @@ -237,6 +237,7 @@ Luis Romero Mairieli Wessel Malik Atalla Malte Deiseroth +Manas Singh Manuel Siebeneicher Manuel Wtfjoke Marcel Luethi From a5d7a30ca0a2cf4c6449da59fc5ee66aad3512b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Nov 2020 13:25:09 +0100 Subject: [PATCH 109/201] Bump checkstyle from 8.37 to 8.38 (#7142) Bumps [checkstyle](https://github.com/checkstyle/checkstyle) from 8.37 to 8.38. - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-8.37...checkstyle-8.38) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6e2326393b8..4ea0f08a984 100644 --- a/build.gradle +++ b/build.gradle @@ -216,7 +216,7 @@ dependencies { testImplementation "org.testfx:testfx-junit5:4.0.17-alpha-SNAPSHOT" testImplementation "org.hamcrest:hamcrest-library:2.2" - checkstyle 'com.puppycrawl.tools:checkstyle:8.37' + checkstyle 'com.puppycrawl.tools:checkstyle:8.38' xjc group: 'org.glassfish.jaxb', name: 'jaxb-xjc', version: '2.3.3' jython 'org.python:jython-standalone:2.7.2' } From aeb69a249d216ffda94436533271c8bd0f316e03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Nov 2020 13:26:24 +0100 Subject: [PATCH 110/201] Bump gittools/actions from v0.9.6 to v0.9.7 (#7144) Bumps [gittools/actions](https://github.com/gittools/actions) from v0.9.6 to v0.9.7. - [Release notes](https://github.com/gittools/actions/releases) - [Commits](https://github.com/gittools/actions/compare/v0.9.6...62f943a7b7e1981bb87a8314f304524657551786) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deployment.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 9712508c1ee..62065768782 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -49,12 +49,12 @@ jobs: with: fetch-depth: 0 - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v0.9.6 + uses: gittools/actions/gitversion/setup@v0.9.7 with: versionSpec: "5.x" - name: Run GitVersion id: gitversion - uses: gittools/actions/gitversion/execute@v0.9.6 + uses: gittools/actions/gitversion/execute@v0.9.7 - name: Set up JDK 15 for linux and mac uses: actions/setup-java@v1 with: @@ -161,12 +161,12 @@ jobs: - name: Fetch all history for all tags and branches run: git fetch --prune --unshallow - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v0.9.6 + uses: gittools/actions/gitversion/setup@v0.9.7 with: versionSpec: '5.x' - name: Run GitVersion id: gitversion - uses: gittools/actions/gitversion/execute@v0.9.6 + uses: gittools/actions/gitversion/execute@v0.9.7 - name: Get linux binaries uses: actions/download-artifact@master with: From 1a46e84c708d7fa97acbf026c7a5e4a68c5123c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Nov 2020 13:27:24 +0100 Subject: [PATCH 111/201] Bump mockito-core from 3.6.0 to 3.6.28 (#7135) Bumps [mockito-core](https://github.com/mockito/mockito) from 3.6.0 to 3.6.28. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v3.6.0...v3.6.28) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4ea0f08a984..5a76104c914 100644 --- a/build.gradle +++ b/build.gradle @@ -207,7 +207,7 @@ dependencies { testImplementation 'net.bytebuddy:byte-buddy-parent:1.10.18' testRuntime group: 'org.apache.logging.log4j', name: 'log4j-core', version: '3.0.0-SNAPSHOT' testRuntime group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '3.0.0-SNAPSHOT' - testImplementation 'org.mockito:mockito-core:3.6.0' + testImplementation 'org.mockito:mockito-core:3.6.28' testImplementation 'org.xmlunit:xmlunit-core:2.8.1' testImplementation 'org.xmlunit:xmlunit-matchers:2.8.1' testRuntime 'com.tngtech.archunit:archunit-junit5-engine:0.14.1' From 0d3ebdc08ea994d189ffa4cc9e2ab7e5667fad9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Nov 2020 13:28:11 +0100 Subject: [PATCH 112/201] Bump classgraph from 4.8.90 to 4.8.92 (#7139) Bumps [classgraph](https://github.com/classgraph/classgraph) from 4.8.90 to 4.8.92. - [Release notes](https://github.com/classgraph/classgraph/releases) - [Commits](https://github.com/classgraph/classgraph/compare/classgraph-4.8.90...classgraph-4.8.92) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5a76104c914..86b14998cdf 100644 --- a/build.gradle +++ b/build.gradle @@ -199,7 +199,7 @@ dependencies { implementation 'com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:0.62.2' implementation 'com.vladsch.flexmark:flexmark-ext-gfm-tasklist:0.62.2' - testImplementation 'io.github.classgraph:classgraph:4.8.90' + testImplementation 'io.github.classgraph:classgraph:4.8.92' testImplementation 'org.junit.jupiter:junit-jupiter:5.7.0' testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.7.0' testImplementation 'org.junit.platform:junit-platform-launcher:1.7.0' From a4b4cb5f973e81a4c8255e8859b23d566185f334 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Nov 2020 13:28:40 +0100 Subject: [PATCH 113/201] Bump mariadb-java-client from 2.7.0 to 2.7.1 (#7134) Bumps [mariadb-java-client](https://github.com/mariadb-corporation/mariadb-connector-j) from 2.7.0 to 2.7.1. - [Release notes](https://github.com/mariadb-corporation/mariadb-connector-j/releases) - [Changelog](https://github.com/mariadb-corporation/mariadb-connector-j/blob/master/CHANGELOG.md) - [Commits](https://github.com/mariadb-corporation/mariadb-connector-j/commits) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 86b14998cdf..083ca54b71c 100644 --- a/build.gradle +++ b/build.gradle @@ -141,7 +141,7 @@ dependencies { implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '5.9.0.202009080501-r' - implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.7.0' + implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.7.1' implementation 'org.postgresql:postgresql:42.2.18' From 7c09377dea2af4a0eebe21af2d740a97ca7afe82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Nov 2020 13:58:00 +0100 Subject: [PATCH 114/201] Bump antlr4 from 4.8-1 to 4.9 (#7138) Bumps [antlr4](https://github.com/antlr/antlr4) from 4.8-1 to 4.9. - [Release notes](https://github.com/antlr/antlr4/releases) - [Changelog](https://github.com/antlr/antlr4/blob/master/CHANGES.txt) - [Commits](https://github.com/antlr/antlr4/commits/4.9) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 083ca54b71c..a817b22e02b 100644 --- a/build.gradle +++ b/build.gradle @@ -132,7 +132,7 @@ dependencies { antlr3 'org.antlr:antlr:3.5.2' implementation 'org.antlr:antlr-runtime:3.5.2' - antlr4 'org.antlr:antlr4:4.8-1' + antlr4 'org.antlr:antlr4:4.9' implementation 'org.antlr:antlr4-runtime:4.8-1' implementation (group: 'org.apache.lucene', name: 'lucene-queryparser', version: '8.7.0') { From 1e6d313edd15c2135cc884dceee894205bf4b138 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Nov 2020 19:36:55 +0100 Subject: [PATCH 115/201] Bump antlr4-runtime from 4.8-1 to 4.9 (#7136) Bumps [antlr4-runtime](https://github.com/antlr/antlr4) from 4.8-1 to 4.9. - [Release notes](https://github.com/antlr/antlr4/releases) - [Changelog](https://github.com/antlr/antlr4/blob/master/CHANGES.txt) - [Commits](https://github.com/antlr/antlr4/commits/4.9) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a817b22e02b..cb21512dc10 100644 --- a/build.gradle +++ b/build.gradle @@ -133,7 +133,7 @@ dependencies { implementation 'org.antlr:antlr-runtime:3.5.2' antlr4 'org.antlr:antlr4:4.9' - implementation 'org.antlr:antlr4-runtime:4.8-1' + implementation 'org.antlr:antlr4-runtime:4.9' implementation (group: 'org.apache.lucene', name: 'lucene-queryparser', version: '8.7.0') { exclude group: 'org.apache.lucene', module: 'lucene-sandbox' From ac1ccad12048b3753d9e4d8da7ffdff4dbc90294 Mon Sep 17 00:00:00 2001 From: github actions Date: Tue, 1 Dec 2020 02:24:17 +0000 Subject: [PATCH 116/201] Squashed 'src/main/resources/csl-styles/' changes from 55200d0378..a20406dc45 a20406dc45 Added name of the editors of a given edition (#5140) 9881fc59c8 Ping on push, not PR, document role of dist-updater (#5137) 04668cce4f Create nouvelles-perspectives-en-sciences-sociales.csl (#5063) 1d94e2188d Update bursa-uludag-universitesi-saglik-bilimleri-enstitusu.csl (#5047) 84f38939ce Add Harvard style for Metropolia University of Applied Sciences (#5086) 8e43e79ba6 Create opto-electronic-advances.csl (#5135) 36e4fba000 Update society-for-american-archaeology.csl (#5124) 69ca36088b St. Paul Canon Law new style (#5138) b490ab01e4 Update and rename st-paul-university-faculty-of-canon-law.csl to saint-paul-university-faculty-of-canon-law.csl b498116bd2 There is no en-CA locale 3c35f28651 Metadata 7059cca134 Create tu-dortmund-agvm.csl (#5088) c321c98fee Create new Citation type (#5093) a7edc8d598 Update international-organization.csl (#5103) 3d1a052a8e The AWS load balancer is messing things up (#5133) ca3839bf00 Fix sort by a single macro (#5136) 5d1a7e8adc Update chungara-revista-de-antropologia-chilena.csl (#5123) cd75d5df73 ping distribution-updater (#5132) dcf473a370 Update wirtschaftsuniversitat-wien-health-care-management.csl (#5125) a87085e064 Fix Harvard Praxisforschung Editors (#5130) d4176ca79f Switch automated tests to Github Actions (#5111) 726d0d8d1c Radiology, MPP, CORR -- small fixes: https://forums.zotero.org/discussion/85883/doi-radiology#latest https://forums.zotero.org/discussion/51058/style-request-molecular-plant-pathology#latest https://forums.zotero.org/discussion/85678/citing-style-clinical-orthopaedics-and-related-research#latest e23db686b9 Update to la-trobe-university-harvard style (#5119) c54b278426 Create wirtschaftsuniversitat-wien-health-care-management.csl (#5110) 62fb019801 Create austral-entomology.csl (#5118) afa328c8b9 Update iso690-author-date-en.csl (#5113) 5468dce019 Update iso690-author-date-fr-no-abstract.csl (#5112) 98af86cd94 Update iso690-numeric-fr.csl (#5115) 09f84c43b9 Update iso690-author-date-fr.csl (#5114) 178a9e4e60 Fix current biology to superscript 1fa5ce7af3 Create droit-belge-centre-de-droit-prive-ulb.csl (#5107) 3a6a4bc5e9 Fix file modes (#5106) 48f50e570e Create chungara-revista-de-antropologia-chilena.csl (#5096) 1e848f8efd Create the-journal-of-the-indian-law-institute.csl (#5100) 856524c61e Create molecular-biology.csl (#5101) eeebbf4724 Create harvard-harper-adams-university.csl (#5104) d90993d101 Fix tests d4037bf036 WIP: St Paul Canon Law style git-subtree-dir: src/main/resources/csl-styles git-subtree-split: a20406dc45d4e0e8a310ddb47f67174249c96461 --- .github/workflows/merge.yaml | 104 + .github/workflows/sheldon.yaml | 63 + .travis.yml | 23 - Gemfile.lock | 2 +- Rakefile | 12 +- american-chemical-society.csl | 0 austral-entomology.csl | 238 +++ beltz-padagogik.csl | 2 +- ...law-international-marketing-management.csl | 2 +- ...-polskiego-towarzystwa-jezykoznawczego.csl | 5 +- ...niversitesi-saglik-bilimleri-enstitusu.csl | 1751 ++++++++++++----- chungara-revista-de-antropologia-chilena.csl | 362 ++++ ...ical-orthopaedics-and-related-research.csl | 5 + dependent/current-biology.csl | 4 +- deutsche-sprache.csl | 5 +- ...-references-et-abreviations-juridiques.csl | 400 ++++ harper-adams-university-harvard.csl | 423 ++++ harvard-institut-fur-praxisforschung-de.csl | 3 +- hochschule-hannover-soziale-arbeit.csl | 2 +- international-organization.csl | 3 +- iso690-author-date-en.csl | 6 +- iso690-author-date-fr-no-abstract.csl | 13 +- iso690-author-date-fr.csl | 30 +- iso690-note-fr.csl | 0 iso690-numeric-fr.csl | 6 +- journal-of-the-indian-law-institute.csl | 267 +++ keel-ja-kirjandus.csl | 5 +- la-trobe-university-harvard.csl | 5 +- ...university-of-applied-sciences-harvard.csl | 441 +++++ modern-language-association.csl | 0 molecular-biology.csl | 147 ++ molecular-plant-pathology.csl | 2 +- ...lles-perspectives-en-sciences-sociales.csl | 596 ++++++ opto-electronic-advances.csl | 152 ++ radiology.csl | 25 +- ...t-paul-university-faculty-of-canon-law.csl | 267 +++ ...-meteorologi-klimatologi-dan-geofisika.csl | 219 +++ society-for-american-archaeology.csl | 12 +- ...versitat-dortmund-ag-virtual-machining.csl | 141 ++ university-of-york-chicago.csl | 0 university-of-york-harvard-archaeology.csl | 0 university-of-york-harvard-environment.csl | 0 university-of-york-harvard.csl | 0 university-of-york-ieee.csl | 0 university-of-york-mhra.csl | 0 university-of-york-oscola.csl | 0 university-of-york-vancouver.csl | 0 ...ien-abteilung-fur-bildungswissenschaft.csl | 34 +- ...niversitat-wien-health-care-management.csl | 622 ++++++ ...n-unternehmensrechnung-und-controlling.csl | 34 +- ...suniversitat-wien-wirtschaftspadagogik.csl | 34 +- zeitschrift-fur-qualitative-forschung.csl | 2 +- 52 files changed, 5906 insertions(+), 563 deletions(-) create mode 100644 .github/workflows/merge.yaml create mode 100644 .github/workflows/sheldon.yaml delete mode 100644 .travis.yml mode change 100755 => 100644 american-chemical-society.csl create mode 100644 austral-entomology.csl create mode 100644 chungara-revista-de-antropologia-chilena.csl create mode 100644 guide-des-citations-references-et-abreviations-juridiques.csl create mode 100644 harper-adams-university-harvard.csl mode change 100755 => 100644 iso690-note-fr.csl create mode 100644 journal-of-the-indian-law-institute.csl create mode 100644 metropolia-university-of-applied-sciences-harvard.csl mode change 100755 => 100644 modern-language-association.csl create mode 100644 molecular-biology.csl create mode 100644 nouvelles-perspectives-en-sciences-sociales.csl create mode 100644 opto-electronic-advances.csl create mode 100644 saint-paul-university-faculty-of-canon-law.csl create mode 100644 sekolah-tinggi-meteorologi-klimatologi-dan-geofisika.csl create mode 100644 technische-universitat-dortmund-ag-virtual-machining.csl mode change 100755 => 100644 university-of-york-chicago.csl mode change 100755 => 100644 university-of-york-harvard-archaeology.csl mode change 100755 => 100644 university-of-york-harvard-environment.csl mode change 100755 => 100644 university-of-york-harvard.csl mode change 100755 => 100644 university-of-york-ieee.csl mode change 100755 => 100644 university-of-york-mhra.csl mode change 100755 => 100644 university-of-york-oscola.csl mode change 100755 => 100644 university-of-york-vancouver.csl create mode 100644 wirtschaftsuniversitat-wien-health-care-management.csl diff --git a/.github/workflows/merge.yaml b/.github/workflows/merge.yaml new file mode 100644 index 00000000000..285fd219cee --- /dev/null +++ b/.github/workflows/merge.yaml @@ -0,0 +1,104 @@ +name: Merge to release + +on: + push: + branches: + - master + workflow_dispatch: + inputs: + commit_message: + description: Commit message + required: true + +jobs: + release: + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + DISTRIBUTION_UPDATER_TOKEN: "${{ secrets.DISTRIBUTION_UPDATER_TOKEN }}" + steps: + - uses: actions/checkout@v2 + if: github.event_name == 'push' + - uses: actions/checkout@v2 + if: github.event_name == 'workflow_dispatch' + with: + fetch-depth: 0 + + - name: Release branch version + id: release + run: echo ::set-output name=branch::v1.0.1 + - name: Checkout release branch + uses: actions/checkout@v2 + with: + ref: ${{ steps.release.outputs.branch }} + path: './release' + + - name: Check for relevant changes + if: github.event_name == 'push' + uses: dorny/paths-filter@v2 + id: update + with: + list-files: shell + filters: | + updated: + - added|modified: [ '*.csl', '*.xml' ] + deleted: + - deleted: [ '*.csl', '*.xml' ] + + - name: Changed files + if: github.event_name == 'push' + run: | + echo updated: ${{ steps.update.outputs.updated_files }} + echo deleted: ${{ steps.update.outputs.deleted_files }} + + - name: Set up Ruby + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && (steps.update.outputs.updated == 'true' || steps.update.outputs.deleted == 'true')) + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7.1 + - name: but use cache to speed that up + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && (steps.update.outputs.updated == 'true' || steps.update.outputs.deleted == 'true')) + uses: actions/cache@v2 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + - name: Bundle install + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && (steps.update.outputs.updated == 'true' || steps.update.outputs.deleted == 'true')) + run: | + bundle config path vendor/bundle + bundle update sheldon --jobs 4 --retry 3 + + - name: Populate new branch + run: bundle exec sheldon --token=$GITHUB_TOKEN --verbose --populate release + if: github.event_name == 'workflow_dispatch' + + - name: update the timestamps and add the changes + run: bundle exec sheldon --token=$GITHUB_TOKEN --verbose --release release ${{ steps.update.outputs.updated_files }} + if: github.event_name == 'push' && steps.update.outputs.updated == 'true' + + - name: delete deleted files + run: cd release && git rm ${{ steps.update.outputs.deleted_files }} + if: github.event_name == 'push' && steps.update.outputs.deleted == 'true' + + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + repository: 'release' + commit_message: Releasing ${{ steps.update.outputs.updated_files }} ${{ steps.update.outputs.deleted_files }} + if: github.event_name == 'push' && (steps.update.outputs.updated == 'true' || steps.update.outputs.deleted == 'true') + + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + repository: 'release' + commit_message: ${{ github.event.inputs.commit_message }} + if: github.event_name == 'workflow_dispatch' + + # https://styles-update.zotero.org:8826/ is for Zotero (styles page, API's citation server, client style updates, etc.) + # https://styles-update.zotero.org:8827/ is for the Zotero-run instance of https://github.com/citation-style-language/distribution-updater + # that performs the updating of the (to-be-deprecated) https://github.com/citation-style-language/styles-distribution/ repo + - name: ping Zotero servers + if: github.repository == 'citation-style-language/styles' + run: | + curl -H 'Content-Length:' -H "Authorization: $ZOTERO_UPDATE_TOKEN" -F 'payload={"type":"push","branch":"${{ steps.release.outputs.branch }}","status":0,"commit":"'$GITHUB_SHA'"}' https://styles-update.zotero.org:8826/ + curl -H 'Content-Length:' -H "Authorization: $ZOTERO_UPDATE_TOKEN" -F 'payload={"type":"push","branch":"${{ steps.release.outputs.branch }}","status":0,"commit":"'$GITHUB_SHA'"}' https://styles-update.zotero.org:8827/ diff --git a/.github/workflows/sheldon.yaml b/.github/workflows/sheldon.yaml new file mode 100644 index 00000000000..28385ac0672 --- /dev/null +++ b/.github/workflows/sheldon.yaml @@ -0,0 +1,63 @@ +name: Pull request feedback + +on: + pull_request_target: + types: [ opened, synchronize ] + +jobs: + test: + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + steps: + - uses: actions/checkout@v2 + + - name: Check for relevant changes + uses: dorny/paths-filter@v2 + id: changed + with: + list-files: shell + filters: | + style: + - '*.csl' + locale: + - '*.xml' + + - name: Changed files + run: | + echo changed: ${{ steps.changed.outputs.style_files }} ${{ steps.changed.outputs.locale_files }} + + - name: Set up Ruby + if: steps.changed.outputs.style == 'true' || steps.changed.outputs.locale == 'true' + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7.1 + - name: but use cache to speed that up + if: steps.changed.outputs.style == 'true' || steps.changed.outputs.locale == 'true' + uses: actions/cache@v2 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + - name: Bundle install + if: steps.changed.outputs.style == 'true' || steps.changed.outputs.locale == 'true' + run: | + bundle config path vendor/bundle + bundle update sheldon --jobs 4 --retry 3 + + - name: Apply the PR + if: steps.changed.outputs.style == 'true' || steps.changed.outputs.locale == 'true' + run: bundle exec sheldon --token=$GITHUB_TOKEN --apply + + - name: Welcome to a new PR + if: github.event.action == 'opened' && steps.changed.outputs.style == 'true' + run: bundle exec sheldon --token=$GITHUB_TOKEN --welcome + + - name: See if the styles/locales work + if: steps.changed.outputs.style == 'true' || steps.changed.outputs.locale == 'true' + run: bundle exec rake + + - name: report + if: (failure() || success()) && steps.changed.outputs.style == 'true' + run: bundle exec sheldon --token=$GITHUB_TOKEN --report --verbose diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 724298fd1ef..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -dist: focal -language: ruby -cache: bundler -rvm: -- 2.7.1 -install: -- bundle install --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle} -- bundle update sheldon -notifications: - email: - on_success: change - on_failure: always - recipients: - - secure: Ko9SzcdByE/HOMP7s9ddTpRdqahnO5LJ1MGJWX/cmymQc8fDRMgP9jgI6D/G5Ntgo6uGUO+xkcV5mBbOAoI5v8U2GQ5nICDXHdQrX8kJzve9JDiQJOy0c17TYi3d7bBeS1bOhy7E0TxHRax2wWWxDhqz80GwSo9JAhQbcusR/1U= - - secure: Ov0xcwOVBSbc7uuCk0Qu+ILmh3HuFNa9PnfI363at9V3aG05oJHiTRseFXvfiHgK5663Wcytcl3DU+A2vYZSz7Y3ZGzcUzhlRBLMUfebncB2nAsX3NJsieJ6FoYlkRBZdzA2lzt3FVv99hebuZnU4OiANdnHFiLmFzeWRaPZfIQ= - webhooks: - urls: - - https://shelbot.herokuapp.com/build - - https://styles-update.zotero.org:8826/ - - https://styles-update.zotero.org:8827/ - on_success: always - on_failure: always - on_start: never diff --git a/Gemfile.lock b/Gemfile.lock index 69f6fbab0b5..b220ca304d2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/citation-style-language/Sheldon.git - revision: 7822dbda11e229cbd933f84a3a1cfdbe8e75dfaf + revision: 1962fad73610a3e0b1610e0c24c16669d584196b specs: sheldon (1.0.2) citeproc-ruby diff --git a/Rakefile b/Rakefile index f284116cdca..1f29f3380df 100644 --- a/Rakefile +++ b/Rakefile @@ -7,20 +7,10 @@ rescue Bundler::BundlerError => e exit e.status_code end -if ENV['TRAVIS'] - at_exit do - system('bundle exec sheldon') - end -end - require 'rspec/core' require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) do |spec| - if ENV['TRAVIS'] - spec.rspec_opts = %w{ --require spec_helper.rb --format Fuubar --color --format json --out spec/sheldon/travis.json } - else - spec.rspec_opts = %w{ --require spec_helper.rb --format Fuubar --color --format json --out spec/sheldon/travis.json } - end + spec.rspec_opts = %w{ --require spec_helper.rb --format Fuubar --color --format json --out spec/sheldon/ci.json } end task :default => [:spec] diff --git a/american-chemical-society.csl b/american-chemical-society.csl old mode 100755 new mode 100644 diff --git a/austral-entomology.csl b/austral-entomology.csl new file mode 100644 index 00000000000..35b2171f554 --- /dev/null +++ b/austral-entomology.csl @@ -0,0 +1,238 @@ + + diff --git a/beltz-padagogik.csl b/beltz-padagogik.csl index 72438b5a92b..c36e6311d95 100644 --- a/beltz-padagogik.csl +++ b/beltz-padagogik.csl @@ -128,7 +128,7 @@ - + diff --git a/berlin-school-of-economics-and-law-international-marketing-management.csl b/berlin-school-of-economics-and-law-international-marketing-management.csl index ba1dcaaa7fa..9d9ce715834 100644 --- a/berlin-school-of-economics-and-law-international-marketing-management.csl +++ b/berlin-school-of-economics-and-law-international-marketing-management.csl @@ -146,7 +146,7 @@ - + diff --git a/biuletyn-polskiego-towarzystwa-jezykoznawczego.csl b/biuletyn-polskiego-towarzystwa-jezykoznawczego.csl index 5eea3b12b41..934b94b2f7f 100644 --- a/biuletyn-polskiego-towarzystwa-jezykoznawczego.csl +++ b/biuletyn-polskiego-towarzystwa-jezykoznawczego.csl @@ -78,6 +78,9 @@
+ + + @@ -132,7 +135,7 @@ - + diff --git a/bursa-uludag-universitesi-saglik-bilimleri-enstitusu.csl b/bursa-uludag-universitesi-saglik-bilimleri-enstitusu.csl index 191e0304c63..04227951907 100644 --- a/bursa-uludag-universitesi-saglik-bilimleri-enstitusu.csl +++ b/bursa-uludag-universitesi-saglik-bilimleri-enstitusu.csl @@ -1,664 +1,1465 @@ - diff --git a/chungara-revista-de-antropologia-chilena.csl b/chungara-revista-de-antropologia-chilena.csl new file mode 100644 index 00000000000..975f8ff3e94 --- /dev/null +++ b/chungara-revista-de-antropologia-chilena.csl @@ -0,0 +1,362 @@ + + diff --git a/clinical-orthopaedics-and-related-research.csl b/clinical-orthopaedics-and-related-research.csl index f0507271a37..4f045d06e47 100644 --- a/clinical-orthopaedics-and-related-research.csl +++ b/clinical-orthopaedics-and-related-research.csl @@ -17,6 +17,11 @@ 2012-09-27T22:06:38+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License + + + - + + diff --git a/dependent/current-biology.csl b/dependent/current-biology.csl index ebed5648014..224648bdd5e 100644 --- a/dependent/current-biology.csl +++ b/dependent/current-biology.csl @@ -5,14 +5,14 @@ Current Biology http://www.zotero.org/styles/current-biology - + 0960-9822 1879-0445 - 2016-12-13T12:00:00+00:00 + 2017-10-20T12:00:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License diff --git a/deutsche-sprache.csl b/deutsche-sprache.csl index e9d357b783a..915c5dc7dfd 100644 --- a/deutsche-sprache.csl +++ b/deutsche-sprache.csl @@ -60,6 +60,9 @@ + + + @@ -135,7 +138,7 @@ - + diff --git a/guide-des-citations-references-et-abreviations-juridiques.csl b/guide-des-citations-references-et-abreviations-juridiques.csl new file mode 100644 index 00000000000..eaaf050adca --- /dev/null +++ b/guide-des-citations-references-et-abreviations-juridiques.csl @@ -0,0 +1,400 @@ + + diff --git a/harper-adams-university-harvard.csl b/harper-adams-university-harvard.csl new file mode 100644 index 00000000000..ad36e6c6880 --- /dev/null +++ b/harper-adams-university-harvard.csl @@ -0,0 +1,423 @@ + + diff --git a/harvard-institut-fur-praxisforschung-de.csl b/harvard-institut-fur-praxisforschung-de.csl index 4addff2d2d3..100c3970c43 100644 --- a/harvard-institut-fur-praxisforschung-de.csl +++ b/harvard-institut-fur-praxisforschung-de.csl @@ -46,7 +46,8 @@ - + diff --git a/iso690-author-date-fr-no-abstract.csl b/iso690-author-date-fr-no-abstract.csl index 5cbfabb8e89..f8bf57bcb40 100644 --- a/iso690-author-date-fr-no-abstract.csl +++ b/iso690-author-date-fr-no-abstract.csl @@ -9,10 +9,14 @@ Pierre-Amiel Giraud pierre-amiel.giraud@u-bordeaux3.fr + + Raphael Grolimund + grolimur@protonmail.ch + Style based on ISO 690:2010(F), V1, derived from Mellifluo, Grolimund, Hardegger and Giraud version. - 2013-03-31T23:22:38+00:00 + 2020-11-19T21:30:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -235,7 +239,7 @@ - + @@ -285,7 +289,7 @@ - + @@ -330,7 +334,7 @@ - + @@ -427,6 +431,7 @@ + diff --git a/iso690-author-date-fr.csl b/iso690-author-date-fr.csl index 32658c37692..4b8a2825d14 100644 --- a/iso690-author-date-fr.csl +++ b/iso690-author-date-fr.csl @@ -11,7 +11,7 @@ Raphael Grolimund - raphael.grolimund@epfl.ch + grolimur@protonmail.ch Michel Hardegger @@ -24,7 +24,7 @@ Style based on ISO 690:2010(F), V1.1 - 2013-05-31T20:00:00+01:00 + 2020-11-19T21:30:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -107,15 +107,19 @@ - - + + + + + + + + + + + + - - - - - - @@ -147,7 +151,7 @@ - + @@ -168,11 +172,11 @@ - + - + diff --git a/iso690-note-fr.csl b/iso690-note-fr.csl old mode 100755 new mode 100644 diff --git a/iso690-numeric-fr.csl b/iso690-numeric-fr.csl index e031530a3b8..405705bc549 100644 --- a/iso690-numeric-fr.csl +++ b/iso690-numeric-fr.csl @@ -11,7 +11,7 @@ Raphael Grolimund - raphael.grolimund@epfl.ch + grolimur@protonmail.ch Michel Hardegger @@ -20,7 +20,7 @@ Style based on ISO 690:2010(F), V1.1 - 2013-05-31T20:00:00+01:00 + 2020-11-19T21:30:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -145,7 +145,7 @@ - + diff --git a/journal-of-the-indian-law-institute.csl b/journal-of-the-indian-law-institute.csl new file mode 100644 index 00000000000..ecd4d008f2e --- /dev/null +++ b/journal-of-the-indian-law-institute.csl @@ -0,0 +1,267 @@ + + diff --git a/keel-ja-kirjandus.csl b/keel-ja-kirjandus.csl index fad7be4b874..7997b1031d1 100644 --- a/keel-ja-kirjandus.csl +++ b/keel-ja-kirjandus.csl @@ -63,6 +63,9 @@ + + + @@ -138,7 +141,7 @@ - + diff --git a/la-trobe-university-harvard.csl b/la-trobe-university-harvard.csl index c3077e69ce8..cf3c090240a 100644 --- a/la-trobe-university-harvard.csl +++ b/la-trobe-university-harvard.csl @@ -1,5 +1,6 @@ diff --git a/modern-language-association.csl b/modern-language-association.csl old mode 100755 new mode 100644 diff --git a/molecular-biology.csl b/molecular-biology.csl new file mode 100644 index 00000000000..5ee2cfdc861 --- /dev/null +++ b/molecular-biology.csl @@ -0,0 +1,147 @@ + + diff --git a/molecular-plant-pathology.csl b/molecular-plant-pathology.csl index 118cf530c92..d04ecc5994d 100644 --- a/molecular-plant-pathology.csl +++ b/molecular-plant-pathology.csl @@ -103,7 +103,7 @@ - + diff --git a/nouvelles-perspectives-en-sciences-sociales.csl b/nouvelles-perspectives-en-sciences-sociales.csl new file mode 100644 index 00000000000..d76bf130877 --- /dev/null +++ b/nouvelles-perspectives-en-sciences-sociales.csl @@ -0,0 +1,596 @@ + + diff --git a/opto-electronic-advances.csl b/opto-electronic-advances.csl new file mode 100644 index 00000000000..6710073db28 --- /dev/null +++ b/opto-electronic-advances.csl @@ -0,0 +1,152 @@ + + diff --git a/radiology.csl b/radiology.csl index 604041f7c0e..aa9df2e75fa 100644 --- a/radiology.csl +++ b/radiology.csl @@ -5,7 +5,7 @@ http://www.zotero.org/styles/radiology - + Adam Tunis atunis@gmail.com @@ -60,12 +60,15 @@ - + + + + - + @@ -206,18 +209,20 @@ - + + + + + + - - - - + + + - - diff --git a/saint-paul-university-faculty-of-canon-law.csl b/saint-paul-university-faculty-of-canon-law.csl new file mode 100644 index 00000000000..a8d9b5902b7 --- /dev/null +++ b/saint-paul-university-faculty-of-canon-law.csl @@ -0,0 +1,267 @@ + + diff --git a/sekolah-tinggi-meteorologi-klimatologi-dan-geofisika.csl b/sekolah-tinggi-meteorologi-klimatologi-dan-geofisika.csl new file mode 100644 index 00000000000..00c3908b788 --- /dev/null +++ b/sekolah-tinggi-meteorologi-klimatologi-dan-geofisika.csl @@ -0,0 +1,219 @@ + + diff --git a/society-for-american-archaeology.csl b/society-for-american-archaeology.csl index 376552ee8b6..c8471736d51 100644 --- a/society-for-american-archaeology.csl +++ b/society-for-american-archaeology.csl @@ -19,9 +19,13 @@ Allison Grunwald agrunwa1@uwyo.edu + + Erik Marsh + erik.marsh@gmail.com + - 2020-04-17T09:23:48+00:00 + 2020-11-23T22:11:55+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -238,7 +242,7 @@ - @@ -338,6 +342,10 @@ + + + + diff --git a/technische-universitat-dortmund-ag-virtual-machining.csl b/technische-universitat-dortmund-ag-virtual-machining.csl new file mode 100644 index 00000000000..48c27640249 --- /dev/null +++ b/technische-universitat-dortmund-ag-virtual-machining.csl @@ -0,0 +1,141 @@ + + diff --git a/university-of-york-chicago.csl b/university-of-york-chicago.csl old mode 100755 new mode 100644 diff --git a/university-of-york-harvard-archaeology.csl b/university-of-york-harvard-archaeology.csl old mode 100755 new mode 100644 diff --git a/university-of-york-harvard-environment.csl b/university-of-york-harvard-environment.csl old mode 100755 new mode 100644 diff --git a/university-of-york-harvard.csl b/university-of-york-harvard.csl old mode 100755 new mode 100644 diff --git a/university-of-york-ieee.csl b/university-of-york-ieee.csl old mode 100755 new mode 100644 diff --git a/university-of-york-mhra.csl b/university-of-york-mhra.csl old mode 100755 new mode 100644 diff --git a/university-of-york-oscola.csl b/university-of-york-oscola.csl old mode 100755 new mode 100644 diff --git a/university-of-york-vancouver.csl b/university-of-york-vancouver.csl old mode 100755 new mode 100644 diff --git a/wirtschaftsuniversitat-wien-abteilung-fur-bildungswissenschaft.csl b/wirtschaftsuniversitat-wien-abteilung-fur-bildungswissenschaft.csl index a40460f98c7..d4288def807 100644 --- a/wirtschaftsuniversitat-wien-abteilung-fur-bildungswissenschaft.csl +++ b/wirtschaftsuniversitat-wien-abteilung-fur-bildungswissenschaft.csl @@ -232,6 +232,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -412,7 +442,9 @@ - + + + diff --git a/wirtschaftsuniversitat-wien-health-care-management.csl b/wirtschaftsuniversitat-wien-health-care-management.csl new file mode 100644 index 00000000000..dd8502a0dfd --- /dev/null +++ b/wirtschaftsuniversitat-wien-health-care-management.csl @@ -0,0 +1,622 @@ + + diff --git a/wirtschaftsuniversitat-wien-unternehmensrechnung-und-controlling.csl b/wirtschaftsuniversitat-wien-unternehmensrechnung-und-controlling.csl index 15d80f84dc9..9a5396d2b30 100644 --- a/wirtschaftsuniversitat-wien-unternehmensrechnung-und-controlling.csl +++ b/wirtschaftsuniversitat-wien-unternehmensrechnung-und-controlling.csl @@ -220,6 +220,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -389,7 +419,9 @@ - + + + diff --git a/wirtschaftsuniversitat-wien-wirtschaftspadagogik.csl b/wirtschaftsuniversitat-wien-wirtschaftspadagogik.csl index 9adb6c0aa80..f93c0275881 100644 --- a/wirtschaftsuniversitat-wien-wirtschaftspadagogik.csl +++ b/wirtschaftsuniversitat-wien-wirtschaftspadagogik.csl @@ -277,6 +277,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -459,7 +489,9 @@ - + + + diff --git a/zeitschrift-fur-qualitative-forschung.csl b/zeitschrift-fur-qualitative-forschung.csl index 5227c1a4d9e..d415e56e5d3 100644 --- a/zeitschrift-fur-qualitative-forschung.csl +++ b/zeitschrift-fur-qualitative-forschung.csl @@ -138,7 +138,7 @@ - + From 706865f93ed536a6f6ec60cb637cdb1ee3e896be Mon Sep 17 00:00:00 2001 From: Johannes Theiner Date: Tue, 1 Dec 2020 08:23:32 +0100 Subject: [PATCH 117/201] Add IdBasedSearchFetcher to jstor (#7145) --- .../jabref/logic/importer/WebFetchers.java | 1 + .../logic/importer/fetcher/JstorFetcher.java | 51 +++++++++++++++++-- .../importer/fetcher/JstorFetcherTest.java | 20 ++++++++ 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/jabref/logic/importer/WebFetchers.java b/src/main/java/org/jabref/logic/importer/WebFetchers.java index e2791d3c13d..b73dbf8191b 100644 --- a/src/main/java/org/jabref/logic/importer/WebFetchers.java +++ b/src/main/java/org/jabref/logic/importer/WebFetchers.java @@ -127,6 +127,7 @@ public static SortedSet getIdBasedFetchers(ImportFormatPreferenc set.add(new IacrEprintFetcher(importFormatPreferences)); set.add(new RfcFetcher(importFormatPreferences)); set.add(new Medra()); + set.add(new JstorFetcher(importFormatPreferences)); return set; } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/JstorFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/JstorFetcher.java index eb50f3fef1d..6315e708c52 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/JstorFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/JstorFetcher.java @@ -1,15 +1,20 @@ package org.jabref.logic.importer.fetcher; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.FulltextFetcher; +import org.jabref.logic.importer.IdBasedParserFetcher; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ParseException; import org.jabref.logic.importer.Parser; @@ -28,11 +33,12 @@ /** * Fetcher for jstor.org **/ -public class JstorFetcher implements SearchBasedParserFetcher, FulltextFetcher { +public class JstorFetcher implements SearchBasedParserFetcher, FulltextFetcher, IdBasedParserFetcher { private static final String HOST = "https://www.jstor.org"; private static final String SEARCH_HOST = HOST + "/open/search"; - private static final String CITE_HOST = HOST + "/citation/text"; + private static final String CITE_HOST = HOST + "/citation/text/"; + private static final String URL_QUERY_REGEX = "(?<=\\?).*"; private final ImportFormatPreferences importFormatPreferences; @@ -82,21 +88,51 @@ public URL getURLForQuery(ComplexSearchQuery query) throws URISyntaxException, M return uriBuilder.build().toURL(); } + @Override + public URL getUrlForIdentifier(String identifier) throws FetcherException { + String start = "https://www.jstor.org/citation/text/"; + if (identifier.startsWith("http")) { + identifier = identifier.replace("https://www.jstor.org/stable", ""); + identifier = identifier.replace("http://www.jstor.org/stable", ""); + } + identifier = identifier.replaceAll(URL_QUERY_REGEX, ""); + + try { + if (identifier.contains("/")) { + // if identifier links to a entry with a valid doi + return new URL(start + identifier); + } + // else use default doi start. + return new URL(start + "10.2307/" + identifier); + } catch (IOException e) { + throw new FetcherException("could not construct url for jstor", e); + } + } + @Override public Parser getParser() { return inputStream -> { + BibtexParser parser = new BibtexParser(importFormatPreferences, new DummyFileUpdateMonitor()); + String text = new BufferedReader( + new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines().collect(Collectors.joining()); + + // does the input stream contain bibtex ? + if (text.startsWith("@")) { + return parser.parseEntries(text); + } + // input stream contains html List entries; try { Document doc = Jsoup.parse(inputStream, null, HOST); - List elements = doc.body().getElementsByClass("cite-this-item"); + StringBuilder stringBuilder = new StringBuilder(); + List elements = doc.body().getElementsByClass("cite-this-item"); for (Element element : elements) { String id = element.attr("href").replace("citation/info/", ""); String data = new URLDownload(CITE_HOST + id).asString(); stringBuilder.append(data); } - BibtexParser parser = new BibtexParser(importFormatPreferences, new DummyFileUpdateMonitor()); entries = new ArrayList<>(parser.parseEntries(stringBuilder.toString())); } catch (IOException e) { throw new ParseException("Could not download data from jstor.org", e); @@ -111,7 +147,7 @@ public String getName() { } @Override - public Optional findFullText(BibEntry entry) throws IOException, FetcherException { + public Optional findFullText(BibEntry entry) throws IOException { if (entry.getField(StandardField.URL).isEmpty()) { return Optional.empty(); } @@ -133,4 +169,9 @@ public Optional findFullText(BibEntry entry) throws IOException, FetcherExc public TrustLevel getTrustLevel() { return TrustLevel.META_SEARCH; } + + @Override + public void doPostCleanup(BibEntry entry) { + // do nothing + } } diff --git a/src/test/java/org/jabref/logic/importer/fetcher/JstorFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/JstorFetcherTest.java index c8d51bd1fee..2124b8f73cc 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/JstorFetcherTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/JstorFetcherTest.java @@ -40,12 +40,32 @@ public class JstorFetcherTest implements SearchBasedFetcherCapabilityTest { .withField(StandardField.URL, "http://www.jstor.org/stable/90002164") .withField(StandardField.YEAR, "2017"); + private final BibEntry doiEntry = new BibEntry(StandardEntryType.Article) + .withCitationKey("10.1086/501484") + .withField(StandardField.AUTHOR, "Johnmarshall Reeve") + .withField(StandardField.TITLE, "Teachers as Facilitators: What Autonomy‐Supportive Teachers Do and Why Their Students Benefit") + .withField(StandardField.ISSN, "{00135984, 15548279") + .withField(StandardField.JOURNAL, "The Elementary School Journal") + .withField(StandardField.ABSTRACT, "Abstract Students are sometimes proactive and engaged in classroom learning activities, but they are also sometimes only reactive and passive. Recognizing this, in this article I argue that students’ classroom engagement depends, in part, on the supportive quality of the classroom climate in which they learn. According to the dialectical framework within self‐determination theory, students possess inner motivational resources that classroom conditions can support or frustrate. When teachers find ways to nurture these inner resources, they adopt an autonomy‐supportive motivating style. After articulating what autonomy‐supportive teachers say and do during instruction, I discuss 3 points: teachers can learn how to be more autonomy supportive toward students; teachers most engage students when they offer high levels of both autonomy support and structure; and an autonomy‐supportive motivating style is an important element to a high‐quality teacher‐student relationship.") + .withField(StandardField.PUBLISHER, "The University of Chicago Press") + .withField(StandardField.NUMBER, "3") + .withField(StandardField.PAGES, "225--236") + .withField(StandardField.VOLUME, "106") + .withField(StandardField.URL, "http://www.jstor.org/stable/10.1086/501484") + .withField(StandardField.YEAR, "2006"); + @Test void searchByTitle() throws Exception { List entries = fetcher.performSearch("title: \"Test Anxiety Analysis of Chinese College Students in Computer-based Spoken English Test\""); assertEquals(Collections.singletonList(bibEntry), entries); } + @Test + void searchById() throws FetcherException { + assertEquals(Optional.of(bibEntry), fetcher.performSearchById("90002164")); + assertEquals(Optional.of(doiEntry), fetcher.performSearchById("https://www.jstor.org/stable/10.1086/501484?seq=1")); + } + @Test void fetchPDF() throws IOException, FetcherException { Optional url = fetcher.findFullText(bibEntry); From f22968e16e273e3f209fb2d6e60ddf076e4b8ae5 Mon Sep 17 00:00:00 2001 From: Christoph Date: Wed, 2 Dec 2020 20:35:05 +0100 Subject: [PATCH 118/201] Update to libre office 7.0.3 (#7150) remove custom lib merging --- build.gradle | 20 ++---------------- lib/libreoffice.jar | Bin 2124103 -> 0 bytes src/main/java/module-info.java | 2 +- .../org/jabref/gui/openoffice/OOBibBase.java | 2 +- 4 files changed, 4 insertions(+), 20 deletions(-) delete mode 100644 lib/libreoffice.jar diff --git a/build.gradle b/build.gradle index cb21512dc10..d9fc99d0188 100644 --- a/build.gradle +++ b/build.gradle @@ -84,7 +84,6 @@ repositories { } configurations { - libreoffice antlr3 antlr4 // TODO: Remove the following workaround for split error messages such as @@ -117,14 +116,8 @@ dependencies { implementation 'commons-cli:commons-cli:1.4' - // For Java 9+ compatibility, we include a bundled version of the libreoffice libraries - // See https://bugs.documentfoundation.org/show_bug.cgi?id=117331#c8 for background information - // Use the task bundleLibreOffice to update the bundled jar - // DO NOT CHANGE THE libreoffice PREFIX - libreoffice 'org.libreoffice:juh:6.4.3' - libreoffice 'org.libreoffice:jurt:6.4.3' - libreoffice 'org.libreoffice:ridl:6.4.3' - libreoffice 'org.libreoffice:unoil:6.4.3' + implementation 'org.libreoffice:libreoffice:7.0.3' + implementation 'org.libreoffice:unoloader:7.0.3' implementation 'io.github.java-diff-utils:java-diff-utils:4.9' implementation 'info.debatty:java-string-similarity:2.0.0' @@ -767,13 +760,4 @@ task downloadDependencies { } } -task bundleLibreOffice(type: Jar) { - from configurations.libreoffice.collect { zipTree it } - manifest { - attributes 'Automatic-Module-Name': 'org.jabref.thirdparty.libreoffice' - } - - destinationDir = file('lib') - archiveName = 'libreoffice.jar' -} diff --git a/lib/libreoffice.jar b/lib/libreoffice.jar deleted file mode 100644 index 072f96ec432ca66b296098d307c177e403c6d227..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2124103 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�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%0AK%3c}| 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

2YzUT}G2sZb37(1FN_t90s9Mxl>FRW+ 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%0A&)Th1{3ZaGeGVLJ;Omu+5 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%0AK(c#y#pr`@lc+5X@Jk5ZRR-}YR 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 z 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%0A3E_So? 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%0DI@CC(adC5^L4Oki(oQZ?8k{5u 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%0DU$^S8R-~N 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