diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index b8f22170b1b..60cdc796b08 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -16,7 +16,7 @@ jobs: env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - name: Merge pull requests - uses: pascalgn/automerge-action@v0.12.0 + uses: pascalgn/automerge-action@v0.13.0 if: steps.waitforstatuschecks.outputs.status == 'success' env: MERGE_METHOD: "squash" 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 diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 6689341b25a..4bfa05d67c4 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -1,6 +1,7 @@ name: Deployment on: + workflow_dispatch: push: branches: - master @@ -41,7 +42,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 @@ -49,12 +50,12 @@ jobs: with: fetch-depth: 0 - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v0.9.4 + uses: gittools/actions/gitversion/setup@v0.9.7 with: - versionSpec: "5.3.7" + versionSpec: "5.x" - name: Run GitVersion id: gitversion - uses: gittools/actions/gitversion/execute@v0.9.4 + uses: gittools/actions/gitversion/execute@v0.9.7 - name: Set up JDK 15 for linux and mac uses: actions/setup-java@v1 with: @@ -88,7 +89,7 @@ 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 -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 shell: bash @@ -101,7 +102,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' @@ -161,12 +162,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.7 with: - versionSpec: '5.2.x' + versionSpec: '5.x' - name: Run GitVersion id: gitversion - uses: gittools/actions/gitversion/execute@v0.9.4 + uses: gittools/actions/gitversion/execute@v0.9.7 - name: Get linux binaries uses: actions/download-artifact@master with: 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 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/.github/workflows/stale.yml b/.github/workflows/stale.yml index db8e6e705ef..f227fc4bd1a 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,17 +2,27 @@ name: Mark stale issues and pull requests on: schedule: - - cron: "0 0 * * *" + - cron: "20 19 * * *" jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@master + - uses: actions/stale@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'This issue will be closed in 7 days due to inactivity :zzz: Please provide the requested information if the problem persists.' + days-before-stale: 180 + days-before-close: 14 + stale-issue-message: | + This issue has been inactive for half a year. Since JabRef is constantly evolving this issue may not be relevant any longer and it will be closed in two weeks if no further activity occurs. + + As part of an effort to ensure that the JabRef team is focusing on important and valid issues, we would like to ask if you could update the issue if it still persists. This could be in the following form: + + - If there has been a longer discussion, add a short summary of the most important points as a new comment (if not yet existing). + - Provide further steps or information on how to reproduce this issue. + - Upvote the initial post if you like to see it implemented soon. Votes are not the only metric that we use to determine the requests that are implemented, however, they do factor into our decision-making process. + - If all information is provided and still up-to-date, then just add a short comment that the issue is still relevant. + + Thank you for your contribution! stale-issue-label: 'status: stale' - days-before-stale: 30 - only-labels: 'status: waiting-for-customer-feedback' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7b376dcdff9..5651082269f 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 @@ -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 @@ -86,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 @@ -256,5 +261,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 AUTHORS 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 diff --git a/.idea/runConfigurations/JabRef_Main.xml b/.idea/runConfigurations/JabRef_Main.xml index f6e0ca898ae..4bb85cf63f1 100644 --- a/.idea/runConfigurations/JabRef_Main.xml +++ b/.idea/runConfigurations/JabRef_Main.xml @@ -1,16 +1,14 @@ - - + - + \ No newline at end of file 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..1ea7c309626 100644 --- a/AUTHORS +++ b/AUTHORS @@ -38,6 +38,7 @@ Anita Armbruster Antonio Ribeiro Arno Blouin Arthur Fröhlich +AtrusRiven Atul Kaushik August Janse Ayachi Nene @@ -47,6 +48,7 @@ Baruch Oltman Behrouz Javanmardi Benedikt Tutzer Benjamin Köhler +Benjamin Schroth Berk Gureken Bernd Kalbfuss Bernhard Tempel @@ -127,9 +129,11 @@ Fancy Zhang Fedor Bezrukov Felix Berger Felix Langner +Felix Luthman Felix Wilke Fernando Santagata ffffatgoose +Fiyinfolu Eludire Florian Beetz Florian Straßer Foivos Christoulakis @@ -157,12 +161,15 @@ Harinda Samarasekara hemantgs HifeFish Hollyqqqqq +Houssem Nasri hrandrianasolo Hussain Arif +IfIWantedTo Igor Chernyavsky Igor Steinmacher Illes Solt Ingvar Jackal +Isaac Roles Jackson Ryan Jan Frederik Maas Jan Kubovy @@ -178,6 +185,7 @@ Jeffrey Kuhn Jeffrey Sander Jens Döcke joeyzgraggen +Johan Del Valle Johannes Hupe Johannes Manner Johannes Theiner @@ -211,6 +219,7 @@ Kelly Click Koji Yokota KOLANICH Kolja Brix +Kristoffer Gunnarsson Krunoslav Zubrinic Krzysztof A. Kościuszkiewicz Kyle Johnson @@ -229,9 +238,11 @@ Lucas Beretti Luciana de Melo e Abud Lugduni Desrosiers Luis Romero +Luis Valdez Mairieli Wessel Malik Atalla Malte Deiseroth +Manas Singh Manuel Siebeneicher Manuel Wtfjoke Marcel Luethi @@ -242,6 +253,7 @@ Marius Kleiner Mark Schenk Martin Kähmer Martin Stolle +Martin W. Kirst Martina Catizone Mathias Walter Matthias Geiger @@ -272,6 +284,7 @@ Morgan Lovato Moritz Ringler Morten Alver ms111ds +muachilin Muhammad Arsalan Badar Mélanie Tremblay Nadeem Mahmood @@ -281,6 +294,7 @@ Nick Mancuso Nick S. Weatherley Nico Schlömer Nicolas Pavillon +Niffler Nikita Borovikov Niklas Schmitt nikmilpv @@ -384,6 +398,7 @@ Tobias Bouschen Tobias Denkinger Tobias Diez Tom Warnke +Tommy Samuelsson Tomás Morales de Luna Tony K Toralf Senger diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c26f8942fc..8ee5078e4fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,3 @@ - # Changelog All notable changes to this project will be documented in this file. @@ -12,15 +11,36 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve ### Added +- We added the extension support and the external application support (For Texshow, Texmaker and LyX) to the flatpak [#7248](https://github.com/JabRef/jabref/pull/7248) + +### Changed + +### Fixed + +- We fixed an issue with the style of highlighted check boxes while searching in preferences. [#7226](https://github.com/JabRef/jabref/issues/7226) +- We fixed an issue where the option "Move file to file directory" was disabled in the entry editor for all files [#7194](https://github.com/JabRef/jabref/issues/7194) + +### Removed + +## [5.2] – 2020-12-24 + +### Added + +- We added a validation to check if the current database location is shared, preventing an exception when Pulling Changes From Shared Database. [#6959](https://github.com/JabRef/jabref/issues/6959) - 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 new fetcher to enable users to search jstor.org [#6627](https://github.com/JabRef/jabref/issues/6627) +- 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 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) +- 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 -- We changed the default preferences for OpenOffice/LibreOffice integration to automatically sync the bibliography when -inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://github.com/JabRef/jabref/issues/6957) +- We changed the default preferences for OpenOffice/LibreOffice integration to automatically sync the bibliography when inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://github.com/JabRef/jabref/issues/6957) - We restructured the 'File' tab and extracted some parts into the 'Linked files' tab [#6779](https://github.com/JabRef/jabref/pull/6779) - JabRef now offers journal lists from . JabRef the lists which use a dot inside the abbreviations. [#5749](https://github.com/JabRef/jabref/pull/5749) - We removed two useless preferences in the groups preferences dialog. [#6836](https://github.com/JabRef/jabref/pull/6836) @@ -37,6 +57,11 @@ 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) +- We changed the order of the library tab context menu items. [#7171](https://github.com/JabRef/jabref/issues/7171) +- 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 @@ -53,10 +78,30 @@ 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 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) +- 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) +- 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) +- We fixed an issue where the context menu was not updated after a file link was changed. [#5777](https://github.com/JabRef/jabref/issues/5777) +- We fixed an issue where the password for a shared SQL database was not remembered [#6869](https://github.com/JabRef/jabref/issues/6869) +- We fixed an issue where newly added entires were not synced to a shared SQL database [#7176](https://github.com/JabRef/jabref/issues/7176) +- We fixed an issue where the PDF-Content importer threw an exception when no DOI number is present at the first page of the PDF document [#7203](https://github.com/JabRef/jabref/issues/7203) +- We fixed an issue where authors that only have last names were incorrectly identified as institutes when generating citation keys [#7199](https://github.com/JabRef/jabref/issues/7199) +- We fixed an issue where institutes were incorrectly identified as universities when generating citation keys [#6942](https://github.com/JabRef/jabref/issues/6942) ### Removed +- We removed the Google Scholar fetcher and the ACM fetcher do not work due to traffic limitations [#6369](https://github.com/JabRef/jabref/issues/6369) +- We removed the menu entry "Manage external file types" because it's already in 'Preferences' dialog [#6991](https://github.com/JabRef/jabref/issues/6991) +- We removed the integrity check "Abbreviation detected" for the field journal/journaltitle in the entry editor [#3925](https://github.com/JabRef/jabref/issues/3925) + ## [5.1] – 2020-08-30 ### Added @@ -454,7 +499,8 @@ The changelog of JabRef 4.x is available at the [v4.3.1 tag](https://github.com/ The changelog of JabRef 3.x is available at the [v3.8.2 tag](https://github.com/JabRef/jabref/blob/v3.8.2/CHANGELOG.md). The changelog of JabRef 2.11 and all previous versions is available as [text file in the v2.11.1 tag](https://github.com/JabRef/jabref/blob/v2.11.1/CHANGELOG). -[Unreleased]: https://github.com/JabRef/jabref/compare/v5.1...HEAD +[Unreleased]: https://github.com/JabRef/jabref/compare/v5.2...HEAD +[5.2]: https://github.com/JabRef/jabref/compare/v5.1...v5.2 [5.1]: https://github.com/JabRef/jabref/compare/v5.0...v5.1 [5.0]: https://github.com/JabRef/jabref/compare/v5.0-beta...v5.0 [5.0-beta]: https://github.com/JabRef/jabref/compare/v5.0-alpha...v5.0-beta 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) 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`. diff --git a/build.gradle b/build.gradle index d07d7af7392..3ccc888fe4a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +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 @@ -13,14 +11,13 @@ 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' - + id 'org.beryx.jlink' version '2.23.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.0' + id 'com.adarshr.test-logger' version '2.1.1' } gradle.startParameter.showStacktrace = org.gradle.api.logging.configuration.ShowStacktrace.ALWAYS @@ -31,9 +28,7 @@ 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 apply from: 'eclipse.gradle' @@ -46,7 +41,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 @@ -55,10 +50,6 @@ modularity.patchModule("test", "fastparse_2.12-1.0.0.jar") modularity.patchModule("test2", "fastparse-utils_2.12-1.0.0.jar") modularity.patchModule("test3", "sourcecode_2.12-0.1.4.jar") -// These are the Java version requirements we will check on each start of JabRef -ext.minRequiredJavaVersion = "1.8.0_171" -ext.allowJava9 = true - sourceSets { main { java { @@ -87,8 +78,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 { @@ -105,52 +96,48 @@ dependencies { // Include all jar-files in the 'lib' folder as dependencies implementation fileTree(dir: 'lib', includes: ['*.jar']) - implementation 'org.apache.pdfbox:pdfbox:2.0.21' - implementation 'org.apache.pdfbox:fontbox:2.0.21' - implementation 'org.apache.pdfbox:xmpbox:2.0.21' + implementation 'org.apache.pdfbox:pdfbox:2.0.22' + implementation 'org.apache.pdfbox:fontbox:2.0.22' + implementation 'org.apache.pdfbox:xmpbox:2.0.22' implementation group: 'org.apache.commons', name: 'commons-csv', version: '1.8' implementation 'com.h2database:h2-mvstore:1.4.200' - implementation group: 'org.apache.tika', name: 'tika-core', version: '1.24.1' + implementation group: 'org.apache.tika', name: 'tika-core', version: '1.25' // 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.68' 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.4' - 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' implementation 'org.antlr:antlr-runtime:3.5.2' - antlr4 'org.antlr:antlr4:4.8-1' - implementation 'org.antlr:antlr4-runtime:4.8-1' + antlr4 'org.antlr:antlr4:4.9' + implementation 'org.antlr:antlr4-runtime:4.9' - 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' } - implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.7.0' + implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '5.10.0.202012080955-r' + + implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.7.1' - 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 exclude module: 'oraclepki' } - implementation ('com.google.guava:guava:29.0-jre') { + implementation ('com.google.guava:guava:30.1-jre') { // TODO: Remove this as soon as https://github.com/google/guava/issues/2960 is fixed exclude module: "jsr305" } @@ -167,10 +154,10 @@ 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.01' + implementation 'com.konghq:unirest-java:3.11.09' implementation 'org.slf4j:slf4j-api:2.0.0-alpha1' implementation group: 'org.apache.logging.log4j', name: 'log4j-jcl', version: '3.0.0-SNAPSHOT' @@ -199,26 +186,25 @@ 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.98' 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' - testImplementation 'net.bytebuddy:byte-buddy-parent:1.10.17' + testImplementation 'net.bytebuddy:byte-buddy-parent:1.10.19' 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.xmlunit:xmlunit-core:2.7.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' + testImplementation 'org.mockito:mockito-core:3.6.28' + testImplementation 'org.xmlunit:xmlunit-core:2.8.2' + testImplementation 'org.xmlunit:xmlunit-matchers:2.8.2' + testRuntime 'com.tngtech.archunit:archunit-junit5-engine:0.15.0' + testImplementation 'com.tngtech.archunit:archunit-junit5-api:0.15.0' testImplementation "org.testfx:testfx-core:4.0.17-alpha-SNAPSHOT" 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.38' xjc group: 'org.glassfish.jaxb', name: 'jaxb-xjc', version: '2.3.3' - jython 'org.python:jython-standalone:2.7.2' } dependencyUpdates { @@ -281,15 +267,12 @@ processResources { "azureInstrumentationKey": System.getenv('AzureInstrumentationKey'), "springerNatureAPIKey": System.getenv('SpringerNatureAPIKey'), "astrophysicsDataSystemAPIKey": System.getenv('AstrophysicsDataSystemAPIKey'), - "ieeeAPIKey": System.getenv('IEEEAPIKey'), - "minRequiredJavaVersion": minRequiredJavaVersion, - "allowJava9": allowJava9 - + "ieeeAPIKey": System.getenv('IEEEAPIKey') ) filteringCharset = 'UTF-8' } - filesMatching("resource/**/meta.xml") { + filesMatching(["resources/resource/ods/meta.xml", "resources/resource/openoffice/meta.xml"]) { expand version: project.version } } @@ -304,23 +287,24 @@ 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/') + 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/") + 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) { @@ -328,7 +312,7 @@ task generateMedlineSource(type: XjcTask) { description = "Generates java files for the medline importer." schemaFile = "src/main/resources/xjc/medline/medline.xsd" - outputDirectory = "src/main/generated/" + outputDirectory = "src/main/generated" javaPackage = "org.jabref.logic.importer.fileformat.medline" } @@ -337,7 +321,7 @@ task generateBibtexmlSource(type: XjcTask) { description = "Generates java files for the bibtexml importer." schemaFile = "src/main/resources/xjc/bibtexml/bibtexml.xsd" - outputDirectory = "src/main/generated" + outputDirectory = "src/main/generated/" javaPackage = "org.jabref.logic.importer.fileformat.bibtexml" } @@ -433,10 +417,13 @@ javadoc { encoding = 'UTF-8' version = true author = true + addMultilineStringsOption("-add-exports").setValue([ + 'javafx.controls/com.sun.javafx.scene.control=org.jabref', + 'org.controlsfx.controls/impl.org.controlsfx.skin=org.jabref' + ]) } } -localization.script = 'scripts/syncLang.py' test { useJUnitPlatform { @@ -674,6 +661,7 @@ jlink { if (OperatingSystem.current().isLinux()) { imageOptions = [ '--icon', "${projectDir}/src/main/resources/icons/JabRef-icon-64.png", + '--app-version', "${project.version}", ] installerOptions = [ '--verbose', @@ -701,6 +689,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" @@ -713,7 +703,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" } @@ -763,13 +753,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/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/buildSrc/src/main/groovy/org/jabref/build/localization/JythonTask.groovy b/buildSrc/src/main/groovy/org/jabref/build/localization/JythonTask.groovy deleted file mode 100644 index 437083035cf..00000000000 --- a/buildSrc/src/main/groovy/org/jabref/build/localization/JythonTask.groovy +++ /dev/null @@ -1,19 +0,0 @@ -package org.jabref.build.localization - -import org.gradle.api.tasks.JavaExec -import org.gradle.api.tasks.TaskAction - -class JythonTask extends JavaExec { - - - public static final String JYTHON_MAIN = 'org.python.util.jython' - - @TaskAction - @Override - void exec() { - main JYTHON_MAIN - classpath project.configurations.getByName(LocalizationPlugin.CONFIGURATION_NAME).asPath - - super.exec() - } -} diff --git a/buildSrc/src/main/groovy/org/jabref/build/localization/LocalizationExtension.groovy b/buildSrc/src/main/groovy/org/jabref/build/localization/LocalizationExtension.groovy deleted file mode 100644 index e47ef5ef600..00000000000 --- a/buildSrc/src/main/groovy/org/jabref/build/localization/LocalizationExtension.groovy +++ /dev/null @@ -1,7 +0,0 @@ -package org.jabref.build.localization - -class LocalizationExtension { - - def script - -} diff --git a/buildSrc/src/main/groovy/org/jabref/build/localization/LocalizationPlugin.groovy b/buildSrc/src/main/groovy/org/jabref/build/localization/LocalizationPlugin.groovy deleted file mode 100644 index 2e03f8d55e1..00000000000 --- a/buildSrc/src/main/groovy/org/jabref/build/localization/LocalizationPlugin.groovy +++ /dev/null @@ -1,66 +0,0 @@ -package org.jabref.build.localization - -import org.gradle.api.Plugin -import org.gradle.api.Project - -class LocalizationPlugin implements Plugin { - - public static final def CONFIGURATION_NAME = "jython" - public static final def TASK_GROUP = 'Localization' - - @Override - void apply(Project target) { - def configuration = target.configurations.create("jython") - configuration.description = "Dependencies needed to run jython." - - target.extensions.create('localization', LocalizationExtension) - - target.afterEvaluate { project -> - initLocalizationTasks(project, project.extensions.getByType(LocalizationExtension)) - } - } - - private def initLocalizationTasks(Project project, LocalizationExtension extension) { - project.tasks.create('localizationStatusWithMarkdown', JythonTask) { - description "Creates an update file in Markdown" - group = TASK_GROUP - - args project.file(extension.script) - args "markdown" - } - - project.tasks.create('localizationStatus', JythonTask) { - description "Prints the current status" - group = TASK_GROUP - - args project.file(extension.script) - args "status" - } - - project.tasks.create('localizationStatusExtended', JythonTask) { - description "Prints the current status (extended output)" - group = TASK_GROUP - - args project.file(extension.script) - args "status" - args "--extended" - } - - project.tasks.create('localizationUpdate', JythonTask) { - description "Updates the localization files (fixes duplicates, adds missing keys, and sort them" - group = TASK_GROUP - - args project.file(extension.script) - args "update" - } - - project.tasks.create('localizationUpdateExtended', JythonTask) { - description "Updates the localization files (fixes duplicates, adds missing keys, and sort them (extended output)" - group = TASK_GROUP - - args project.file(extension.script) - args "update" - args "--extended" - } - } -} diff --git a/buildres/linux/jabrefHost.py b/buildres/linux/jabrefHost.py index ef3fd603b86..72e2eacbbfa 100755 --- a/buildres/linux/jabrefHost.py +++ b/buildres/linux/jabrefHost.py @@ -1,8 +1,4 @@ -#!/usr/bin/python3 -u - -# Note that running python with the `-u` flag is required on Windows, -# in order to ensure that stdin and stdout are opened in binary, rather -# than text, mode. +#!/usr/bin/python3 import json import logging @@ -15,17 +11,24 @@ import sys from pathlib import Path -# We assume that this python script is located in "jabref/lib" while the executable is "jabref/bin/JabRef" +# Try a set of possible launchers to execute JabRef script_dir = Path(__file__).resolve().parent.parent relpath_path = script_dir / "bin/JabRef" lowercase_path = shutil.which("jabref") uppercase_path = shutil.which("JabRef") + +# Relative path used in the portable install if relpath_path.exists(): JABREF_PATH = relpath_path +# Lowercase launcher used in deb/rpm/snap packages elif lowercase_path is not None and os.path.exists(lowercase_path): JABREF_PATH = Path(lowercase_path) +# Uppercase launcher used in Arch AUR package elif uppercase_path is not None and os.path.exists(uppercase_path): JABREF_PATH = Path(uppercase_path) +# FLatpak support +elif subprocess.run(["flatpak", "info", "org.jabref.jabref"], capture_output=True).returncode == 0: + JABREF_PATH = "flatpak run org.jabref.jabref" else: logging.error("Could not determine JABREF_PATH") sys.exit(-1) @@ -70,7 +73,7 @@ def send_message(message): def add_jabref_entry(data): """Send string via cli as literal to preserve special characters""" - cmd = [str(JABREF_PATH), "--importBibtex", r"{}".format(data)] + cmd = str(JABREF_PATH).split() + ["--importBibtex", r"{}".format(data)] logging.info("Try to execute command {}".format(cmd)) try: response = subprocess.check_output(cmd, stderr=subprocess.STDOUT) @@ -90,7 +93,7 @@ def add_jabref_entry(data): logging.info(str(message)) if "status" in message and message["status"] == "validate": - cmd = [str(JABREF_PATH), "--version"] + cmd = str(JABREF_PATH).split() + ["--version"] try: response = subprocess.check_output(cmd, stderr=subprocess.STDOUT) except subprocess.CalledProcessError as exc: 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/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 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crowdin.yml b/crowdin.yml index 54d5e1039ad..1ec4fb7a69c 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -5,3 +5,5 @@ files: two_letters_code: pt-BR: pt_BR id: in + zh-CN: zh_CN + zh-TW: zh_TW diff --git a/docs/.gitbook/assets/contribution-process-reviews (2).svg b/docs/.gitbook/assets/contribution-process-reviews (2).svg new file mode 100644 index 00000000000..39951a21137 --- /dev/null +++ b/docs/.gitbook/assets/contribution-process-reviews (2).svg @@ -0,0 +1,4 @@ + + + +ContributorCreate ContributionSubmit ContributionUpdate CodeComment Pull RequestJabRef TeamFrist DeveloperSecond DeveloperProvide FeedbackReview CodeProvide FeedbackReview CodeRequest ChangesMerge Pull ReuquestQuality OKQuality does not meet JabRef's requirementsQuality does not meet JabRef's requirementsPull Request incomingQuality OK`?Quality OK?Quality OKEverything OKChanges Requested \ No newline at end of file diff --git a/docs/.gitbook/assets/contribution-process-reviews-with-instructor.svg b/docs/.gitbook/assets/contribution-process-reviews-with-instructor (1) (1).svg similarity index 100% rename from docs/.gitbook/assets/contribution-process-reviews-with-instructor.svg rename to docs/.gitbook/assets/contribution-process-reviews-with-instructor (1) (1).svg diff --git a/docs/.gitbook/assets/contribution-process-reviews-with-instructor (1) (2).svg b/docs/.gitbook/assets/contribution-process-reviews-with-instructor (1) (2).svg new file mode 100644 index 00000000000..7e196fe5e1a --- /dev/null +++ b/docs/.gitbook/assets/contribution-process-reviews-with-instructor (1) (2).svg @@ -0,0 +1,4 @@ + + + +ContributorPrepare SubmissionSubmit and Refine ContributionJabRef TeamCheck, Support,and MergeInstructorReview and Support Submission \ No newline at end of file diff --git a/docs/.gitbook/assets/eclipse-create-run-config.png b/docs/.gitbook/assets/eclipse-create-run-config (1) (2).png similarity index 100% rename from docs/.gitbook/assets/eclipse-create-run-config.png rename to docs/.gitbook/assets/eclipse-create-run-config (1) (2).png diff --git a/docs/.gitbook/assets/eclipse-create-run-config (1) (3).png b/docs/.gitbook/assets/eclipse-create-run-config (1) (3).png new file mode 100644 index 00000000000..1412291a55a Binary files /dev/null and b/docs/.gitbook/assets/eclipse-create-run-config (1) (3).png differ diff --git a/docs/.gitbook/assets/github-flow (2).png b/docs/.gitbook/assets/github-flow (2).png new file mode 100644 index 00000000000..dc9fdeb82bc Binary files /dev/null and b/docs/.gitbook/assets/github-flow (2).png differ diff --git a/docs/.gitbook/assets/intellij-checkstyle-settings (1).png b/docs/.gitbook/assets/intellij-checkstyle-settings (1).png new file mode 100644 index 00000000000..e5f0c027190 Binary files /dev/null and b/docs/.gitbook/assets/intellij-checkstyle-settings (1).png differ diff --git a/docs/.gitbook/assets/intellij-enable-annotation-processing (1).png b/docs/.gitbook/assets/intellij-enable-annotation-processing (1).png new file mode 100644 index 00000000000..784ce6cd19e Binary files /dev/null and b/docs/.gitbook/assets/intellij-enable-annotation-processing (1).png differ diff --git a/docs/.gitbook/assets/intellij-gradle-config-ignore-buildSrc.png b/docs/.gitbook/assets/intellij-gradle-config-ignore-buildSrc (2) (1).png similarity index 100% rename from docs/.gitbook/assets/intellij-gradle-config-ignore-buildSrc.png rename to docs/.gitbook/assets/intellij-gradle-config-ignore-buildSrc (2) (1).png diff --git a/docs/.gitbook/assets/intellij-gradle-config-ignore-buildSrc (2) (2).png b/docs/.gitbook/assets/intellij-gradle-config-ignore-buildSrc (2) (2).png new file mode 100644 index 00000000000..203ad5ad207 Binary files /dev/null and b/docs/.gitbook/assets/intellij-gradle-config-ignore-buildSrc (2) (2).png differ diff --git a/docs/.gitbook/assets/intellij-run-configuration-command-line (1).png b/docs/.gitbook/assets/intellij-run-configuration-command-line (2) (1).png similarity index 100% rename from docs/.gitbook/assets/intellij-run-configuration-command-line (1).png rename to docs/.gitbook/assets/intellij-run-configuration-command-line (2) (1).png diff --git a/docs/.gitbook/assets/intellij-run-configuration-command-line.png b/docs/.gitbook/assets/intellij-run-configuration-command-line (2) (2).png similarity index 100% rename from docs/.gitbook/assets/intellij-run-configuration-command-line.png rename to docs/.gitbook/assets/intellij-run-configuration-command-line (2) (2).png diff --git a/docs/.gitbook/assets/intellij-run-configuration-command-line (2) (3).png b/docs/.gitbook/assets/intellij-run-configuration-command-line (2) (3).png new file mode 100644 index 00000000000..cc71d5d068e Binary files /dev/null and b/docs/.gitbook/assets/intellij-run-configuration-command-line (2) (3).png differ diff --git a/docs/.gitbook/assets/grafik (1) (1).png b/docs/.gitbook/assets/intellij-wrap-at-right-margin (1).png similarity index 100% rename from docs/.gitbook/assets/grafik (1) (1).png rename to docs/.gitbook/assets/intellij-wrap-at-right-margin (1).png diff --git a/docs/.gitbook/assets/grafik (1).png b/docs/.gitbook/assets/intellij-wrap-at-right-margin (2).png similarity index 100% rename from docs/.gitbook/assets/grafik (1).png rename to docs/.gitbook/assets/intellij-wrap-at-right-margin (2).png diff --git a/docs/.gitbook/assets/grafik.png b/docs/.gitbook/assets/intellij-wrap-at-right-margin (3).png similarity index 100% rename from docs/.gitbook/assets/grafik.png rename to docs/.gitbook/assets/intellij-wrap-at-right-margin (3).png diff --git a/docs/.gitbook/assets/intellij-wrap-at-right-margin (4).png b/docs/.gitbook/assets/intellij-wrap-at-right-margin (4).png new file mode 100644 index 00000000000..c3b7615fdda Binary files /dev/null and b/docs/.gitbook/assets/intellij-wrap-at-right-margin (4).png differ diff --git a/docs/README.md b/docs/README.md index 5dc3b67c95e..a542dbba954 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,7 +27,7 @@ The package `org.jabref.cli` is responsible for handling the command line option During development, one can configure IntelliJ to pass command line parameters: -![IntelliJ-run-configuration](.gitbook/assets/intellij-run-configuration-command-line%20%282%29.png) +![IntelliJ-run-configuration](.gitbook/assets/intellij-run-configuration-command-line%20%282%29%20%281%29.png) Passing command line arguments using gradle is currently not possible as all arguments \(such as `-Dfile.encoding=windows-1252`\) are passed to the application. @@ -45,9 +45,9 @@ For new ADRs, please use [template.md](https://github.com/JabRef/jabref/tree/3b3 ## FAQ -* Q: I get `java: package org.jabref.logic.journals does not exist`. +* Q: I get `java: package org.jabref.logic.journals does not exist`. - A: You have to ignore `buildSrc/src/main` as source directory in IntelliJ as indicated in our [setup guide](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace). + A: You have to ignore `buildSrc/src/main` as source directory in IntelliJ as indicated in our [setup guide](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace). - Also filed as IntelliJ issue [IDEA-240250](https://youtrack.jetbrains.com/issue/IDEA-240250). + Also filed as IntelliJ issue [IDEA-240250](https://youtrack.jetbrains.com/issue/IDEA-240250). diff --git a/docs/adr.md b/docs/adr.md index 988a2ba7593..61465e4cfb4 100644 --- a/docs/adr.md +++ b/docs/adr.md @@ -16,12 +16,13 @@ Architectural decisions for JabRef: * [ADR-0011](https://github.com/JabRef/jabref/tree/master/docs/adr/0011-test-external-links-in-documentation.md) - Test external links in documentation * [ADR-0012](https://github.com/JabRef/jabref/tree/master/docs/adr/0012-handle-different-bibEntry-formats-of-fetchers.md) - Handle different bibentry formats of fetchers by adding a layer * [ADR-0013](https://github.com/JabRef/jabref/tree/master/docs/adr/0013-add-native-support-biblatex-software.md) - Add Native Support for BibLatex-Software -fix checkstyle in adr + + fix checkstyle in adr + * [ADR-0014](https://github.com/JabRef/jabref/tree/master/docs/adr/0014-separate-URL-creation-to-enable-proper-logging.md) - Separate URL creation to enable proper logging * [ADR-0015](https://github.com/JabRef/jabref/tree/master/docs/adr/0015-support-an-abstract-query-syntax-for-query-conversion.md) - Query syntax design * [ADR-0016](https://github.com/JabRef/jabref/tree/master/docs/adr/0016-mutable-preferences-objects.md) - Mutable preferences objects * [ADR-0017](https://github.com/JabRef/jabref/tree/master/docs/adr/0017-allow-model-access-logic.md) - Allow org.jabref.model to access org.jabref.logic - For new ADRs, please use [template.md](https://github.com/JabRef/jabref/tree/master/docs/adr/template.md) as basis. More information on the used format is available at [https://adr.github.io/madr/](https://adr.github.io/madr/). General information about architectural decision records is available at [https://adr.github.io/](https://adr.github.io/). Then add them to the above list. diff --git a/docs/adr/0003-use-gradle-as-build-tool.md b/docs/adr/0003-use-gradle-as-build-tool.md index 5dac1018032..72dca30407c 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,13 +34,13 @@ 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/). * 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 diff --git a/docs/adr/0009-use-plain-junit5-for-testing.md b/docs/adr/0009-use-plain-junit5-for-testing.md index 35d58bf1097..65f4046f3b6 100644 --- a/docs/adr/0009-use-plain-junit5-for-testing.md +++ b/docs/adr/0009-use-plain-junit5-for-testing.md @@ -49,7 +49,7 @@ assertFalse(actual.contains("\n")); ### Hamcrest -Homepage: +Homepage: * Good, because offers advanced matchers (such as `contains`) * Bad, because not full fluent API diff --git a/docs/getting-into-the-code/code-howtos.md b/docs/getting-into-the-code/code-howtos.md index 8b5a452aaba..7005314618c 100644 --- a/docs/getting-into-the-code/code-howtos.md +++ b/docs/getting-into-the-code/code-howtos.md @@ -172,11 +172,22 @@ General hints: The tests check whether translation strings appear correctly in the resource bundles. -1. Add new `Localization.lang("KEY")` to Java file. Run the `LocalizationConsistencyTest`under \(src/test/org.jabref.logic.l10n\) -2. Tests fail. In the test output a snippet is generated which must be added to the English translation file. There is also a snippet generated for the non-English files, but this is irrelevant. +1. Add new `Localization.lang("KEY")` to Java file. Run the `LocalizationConsistencyTest`under \(src/test/org.jabref.logic. + + \) + +2. Tests fail. In the test output a snippet is generated which must be added to the English translation file. 3. Add snippet to English translation file located at `src/main/resources/l10n/JabRef_en.properties` 4. Please do not add translations for other languages directly in the properties. They will be overwritten by [Crowdin](https://crowdin.com/project/jabref) +## Adding a new Language + +1. Add the new Language to the Language enum in [https://github.com/JabRef/jabref/blob/master/src/main/java/org/jabref/logic/l10n/Language.java](https://github.com/JabRef/jabref/blob/master/src/main/java/org/jabref/logic/l10n/Language.java) +2. Create an empty <locale code>.properties file +3. Configure the new language in [Crowdin](https://crowdin.com/project/jabref) + +If the language is a variant of a language `zh_CN` or `pt_BR` it is necessary to add a language mapping for Crowdin to the crowdin.yml file in the root. Of course the properties file also has to be named according to the language code and locale. + ## Cleanup and Formatters We try to build a cleanup mechanism based on formatters. The idea is that we can register these actions in arbitrary places, e.g., onSave, onImport, onExport, cleanup, etc. and apply them to different fields. The formatters themself are independent of any logic and therefore easy to test. @@ -210,7 +221,7 @@ Optional file = FileHelper.expandFilename(database, fileText, preferences. ## How to work with Preferences -`model` and `logic` must not know JabRefPreferences. See `ProxyPreferences` for encapsulated preferences and [https://github.com/JabRef/jabref/pull/658](https://github.com/JabRef/jabref/pull/658) for a detailed discussion. +`model` and `logic` must not know `JabRefPreferences`. See `ProxyPreferences` for encapsulated preferences and [https://github.com/JabRef/jabref/pull/658](https://github.com/JabRef/jabref/pull/658) for a detailed discussion. See [https://github.com/JabRef/jabref/blob/master/src/main/java/org/jabref/logic/preferences/TimestampPreferences.java](https://github.com/JabRef/jabref/blob/master/src/main/java/org/jabref/logic/preferences/TimestampPreferences.java) \(via [https://github.com/JabRef/jabref/pull/3092](https://github.com/JabRef/jabref/pull/3092)\) for the current way how to deal with preferences. @@ -290,9 +301,12 @@ Or even better, try to mock the preferences and insert them via dependency injec @Test public void getTypeReturnsBibLatexArticleInBibLatexMode() { // Mock preferences - JabrefPreferences mockedPrefs = mock(JabrefPreferences.class); + PreferencesService mockedPrefs = mock(PreferencesService.class); + GeneralPreferences mockedGeneralPrefs = mock(GeneralPReferences.class); // Switch to BibLatex mode - when(mockedPrefs.getBoolean("BiblatexMode")).thenReturn(true); + when(mockedPrefs.getGeneralPrefs()).thenReturn(mockedGeneralPrefs); + when(mockedGeneralPrefs.getDefaultBibDatabaseMode()) + .thenReturn(BibDatabaseMode.BIBLATEX); // Now test EntryTypes biblatexentrytypes = new EntryTypes(mockedPrefs); 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..6fe7b1fe858 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 @@ -20,10 +20,9 @@ This section list the prerequisites you need to get started to develop JabRef. A A working Java \(Develoment Kit\) 15 installation with Java FX support is required. In the command line \(terminal in Linux, cmd in Windows\) run `javac -version` and make sure that the reported version is Java 15 \(e.g `javac 15`\). If `javac` is not found or a wrong version is reported, check your `PATH` environment variable, your `JAVA_HOME` environment variable or install the most recent JDK. -[JavaFX is not part of the default JDK any more](https://www.reddit.com/r/java/comments/82qm9x/javafx_will_be_removed_from_the_java_jdk_in_jdk_11/), it needs to be installed separately if not using a special JDK. ~~We recommend to setup the full Liberica JDK including JavaFX. You can get it from [https://bell-sw.com/pages/downloads/?version=java-15&package=jdk-full](https://bell-sw.com/pages/downloads/?version=java-15&package=jdk-full). On Windows, you can execute `choco install libericajdkfull` \(requires [installation of chocolatey - a package manager for Windows](https://chocolatey.org/install)\).~~ (Liberica JDK 15 does not include the Java compiler properly any more) +[JavaFX is not part of the default JDK any more](https://www.reddit.com/r/java/comments/82qm9x/javafx_will_be_removed_from_the_java_jdk_in_jdk_11/), it needs to be installed separately if not using a special JDK. ~~We recommend to setup the full Liberica JDK including JavaFX. You can get it from~~ [~~https://bell-sw.com/pages/downloads/?version=java-15&package=jdk-full~~](https://bell-sw.com/pages/downloads/?version=java-15&package=jdk-full)~~. On Windows, you can execute `choco install libericajdkfull` \(requires~~ [~~installation of chocolatey - a package manager for Windows~~](https://chocolatey.org/install)~~\).~~ \(Liberica JDK 15 does not include the Java compiler properly any more\) -Download and install the JDK from [https://jdk.java.net/](https://jdk.java.net/). -Afterwards, download the "jmods" JavaFX 15 zip archive from [https://gluonhq.com/products/javafx/](https://gluonhq.com/products/javafx/) and put the `.jmod` files into `C:\Program Files\OpenJDK\jdk-15\jmods`. +Download and install the JDK from [https://jdk.java.net/](https://jdk.java.net/). Afterwards, download the "jmods" JavaFX 15 zip archive from [https://gluonhq.com/products/javafx/](https://gluonhq.com/products/javafx/) and put the `.jmod` files into `C:\Program Files\OpenJDK\jdk-15\jmods`. ### GitHub Account @@ -55,8 +54,7 @@ It is strongly recommend that you have git installed. ### IDE -We suggest [IntelliJ IDEA](https://www.jetbrains.com/idea/). -For advanced users, [Eclipse](https://eclipse.org/) \(`2020-09` or newer\) is also possible. +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 @@ -129,7 +127,7 @@ To prepare IntelliJ's build system two additional steps are required: To have autoformat working properly in the context of line wrapping, "Wrap at right margin" has to be disabled as shown below. Details are found in [IntelliJ issue 240517](https://youtrack.jetbrains.com/issue/IDEA-240517). -![Disable wrapping at right margin to prevent JavaDoc to be wrapped](../.gitbook/assets/intellij-wrap-at-right-margin.png) +![Disable wrapping at right margin to prevent JavaDoc to be wrapped](../.gitbook/assets/intellij-wrap-at-right-margin%20%281%29.png) #### Using Gradle from within IntelliJ IDEA @@ -157,7 +155,7 @@ To use IntelliJ IDEA's internal build system when you build JabRef through **Bui * In **File \| Settings \| Build, Execution, Deployment \| Build Tools \| Gradle** the setting "Build and run using" and "Test using" is set to "IntelliJ IDEA". * Ignore the Gradle project "buildSrc" by clicking the button **Select Project Data To Import** in the Gradle Tool Window and unchecking the folder "buildSrc". - ![Ignore the Gradle project "buildSrc"](../.gitbook/assets/intellij-gradle-config-ignore-buildSrc%20%282%29.png) + ![Ignore the Gradle project "buildSrc"](../.gitbook/assets/intellij-gradle-config-ignore-buildSrc%20%282%29.png) * Delete `org.jabref.gui.logging.plugins.Log4jPlugins` \(location: `generated\org\jabref\gui\logging\plugins\Log4jPlugins.java`\). Otherwise, you will see following error: @@ -198,7 +196,8 @@ Finally, ensure that the checkstyle configuration file is in place: 8. Set the "Scan Scope" to "Only Java sources \(including tests\) 9. Save settings by clicking "OK" 10. Your configuration should now look like this: - ![checkstyle settings](../.gitbook/assets/intellij-checkstyle-settings.png) + + ![checkstyle settings](../.gitbook/assets/intellij-checkstyle-settings.png) ### Setup for Eclipse @@ -215,7 +214,7 @@ Make sure your Eclipse installation us up to date. 4. Create a run/debug configuration for the main class `org.jabref.gui.JabRefLauncher` and/or for `org.jabref.gui.JabRefMain` \(both can be used equivalently\) * Remark: The run/debug configuration needs to be added by right clicking the class \(e.g. JabRefLauncher or JabRefMain\) otherwise it will not work. - ![Creating the run/debug configuration by right clicking on the class](../.gitbook/assets/eclipse-create-run-config%20%281%29%20%281%29.png) + ![Creating the run/debug configuration by right clicking on the class](../.gitbook/assets/eclipse-create-run-config%20%281%29.png) * In the tab "Arguments" of the run/debug configuration, enter the following runtime VM arguments: @@ -287,3 +286,4 @@ There might be problems with building if you have openjfx libraries in local mav ``` As a workaround, you can remove all local openjfx artifacts by deleting the whole openjfx folder from specified location. + diff --git a/docs/getting-into-the-code/testing.md b/docs/getting-into-the-code/testing.md index f88560233f9..4883c8e7c37 100644 --- a/docs/getting-into-the-code/testing.md +++ b/docs/getting-into-the-code/testing.md @@ -1,5 +1,7 @@ # How to test +For details on unit testing see [https://devdocs.jabref.org/getting-into-the-code/code-howtos\#test-cases](https://devdocs.jabref.org/getting-into-the-code/code-howtos#test-cases). + ## Database tests ### PostgreSQL diff --git a/docs/readings-on-coding/javafx.md b/docs/readings-on-coding/javafx.md index 8768d897f45..b85c6f230b4 100644 --- a/docs/readings-on-coding/javafx.md +++ b/docs/readings-on-coding/javafx.md @@ -150,7 +150,7 @@ The view consists a FXML file `MyDialog.fxml` which defines the structure and th * [CSS Reference](http://docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html) * [JFoenix](https://github.com/jfoenixadmin/JFoenix) Material Designs look & feel * [FontAwesomeFX](https://bitbucket.org/Jerady/fontawesomefx/overview): supports different icon fonts -* [JavaFX Documentation project](https://fxdocs.github.io/docs/index.html): Collected information on javafx in a central place +* [JavaFX Documentation project](https://fxdocs.github.io/docs/html5/index.html): Collected information on JavaFX in a central place * [FXExperience](http://fxexperience.com/) JavaFX Links of the week ## Features missing in JavaFX 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'; diff --git a/external-libraries.md b/external-libraries.md index b497b853a4c..07380ccdada 100644 --- a/external-libraries.md +++ b/external-libraries.md @@ -5,7 +5,7 @@ This file is manually kept in sync with build.gradle and the binary jars contain One can list all dependencies by using Gradle task `dependencyReport`. It generates the file [build/reports/project/dependencies.txt](build/reports/project/dependencies.txt). -There, [one can use](https://stackoverflow.com/a/49727249/873282) `sed 's/^.* //' | sort | uniq` to flatten the dependencies. +Below, there is a howto to generate the content at "Sorted list of runtime dependencies output by gradle". ## Legend @@ -78,6 +78,13 @@ URL: https://github.com/google/j2objc License: Apache-2.0 ``` +```yaml +Id: com.googlecode.javaewah:JavaEWAH +Project: JavaEWAH +URL: https://github.com/lemire/javaewah +License: Apache-2.0 +``` + ```yaml Id: com.ibm.icu:icu4j Project: International Components for Unicode for Java (ICU4J) @@ -301,6 +308,27 @@ URL: http://logging.apache.org/log4j/2.x/ License: Apache-2.0 ``` +```yaml +Id: org.apache.lucene:lucene-core +Project: Apache Lucene +URL: https://lucene.apache.org/ +License: Apache-2.0 +``` + +```yaml +Id: org.apache.lucene:lucene-queries +Project: Apache Lucene +URL: https://lucene.apache.org/ +License: Apache-2.0 +``` + +```yaml +Id: org.apache.lucene:lucene-ueryparser +Project: Apache Lucene +URL: https://lucene.apache.org/ +License: Apache-2.0 +``` + ```yaml Id: org.apache.pdfbox:fontbox Project: Apache PDFBox @@ -357,6 +385,13 @@ URL: http://fxexperience.com/controlsfx/ License: BSD-3-Clause ``` +```yaml +Id: org.eclipse.jgit:org.eclipse.jgit +Project: Eclipse JGit +URL: https://www.eclipse.org/jgit/ +License: BSD-3-Clause +``` + ```yaml Id: org.fxmisc.flowless:flowless Project: Flowless @@ -435,31 +470,17 @@ License: GPL-2.0 WITH Classpath-exception-2.0 ``` ```yaml -Id: org.openoffice:juh -Project: OpenOffice.org -URL: http://www.openoffice.org/api/SDK -License: LGPL 3.0 +Id: org.libreoffice:libreoffice +Project: LibreOffice +URL: https://api.libreoffice.org/ +License: MPL-2.0 OR LGPL 3.0+ ``` ```yaml -Id: org.openoffice:jurt -Project: OpenOffice.org -URL: http://www.openoffice.org/api/SDK -License: Apache-2.0 -``` - -```yaml -Id: org.openoffice:ridl -Project: OpenOffice.org -URL: http://www.openoffice.org/api/SDK -License: Apache-2.0 -``` - -```yaml -Id: org.openoffice:unoil -Project: OpenOffice.org -URL: http://www.openoffice.org/api/SDK -License: Apache-2.0 +Id: org.libreoffice:unloader +Project: LibreOffice UNO Loader +URL: https://api.libreoffice.org/ +License: MPL-2.0 AND Apache-2.0 ``` ```yaml @@ -473,20 +494,21 @@ License: BSD-3-Clause 1. `gradlew dependencies > build\reports\project\dependencies.txt` 2. Manually edit depedencies.txt to contain the tree of "compileClasspath" and "implementation" only -3. sed 's/^.* //' < dependencies.txt | sort | uniq +3. `sed 's/^.* //' < build/reports/project/dependencies.txt | sort | uniq > build/dependencies-for-external-libraries.txt` ```text com.github.tomtung:latex2unicode_2.12:0.2.6 com.google.code.gson:gson:2.8.6 com.google.errorprone:error_prone_annotations:2.3.4 com.google.guava:failureaccess:1.0.1 -com.google.guava:guava:29.0-jre +com.google.guava:guava:30.1-jre com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava com.google.j2objc:j2objc-annotations:1.3 +com.googlecode.javaewah:JavaEWAH:1.1.7 com.h2database:h2-mvstore:1.4.200 com.ibm.icu:icu4j:62.1 com.jfoenix:jfoenix:9.0.10 -com.konghq:unirest-java:3.10.00 +com.konghq:unirest-java:3.11.06 com.microsoft.azure:applicationinsights-core:2.4.1 com.microsoft.azure:applicationinsights-logging-log4j2:2.4.1 com.oracle.ojdbc:ojdbc10:19.3.0.0 @@ -498,10 +520,8 @@ com.oracle.ojdbc:ucp:19.3.0.0 com.sun.istack:istack-commons-runtime:3.0.8 com.sun.xml.fastinfoset:FastInfoset:1.2.16 com.tobiasdiez:easybind:2.1.0 -com.vladsch.flexmark:flexmark:0.62.2 com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:0.62.2 com.vladsch.flexmark:flexmark-ext-gfm-tasklist:0.62.2 -com.vladsch.flexmark:flexmark-util:0.62.2 com.vladsch.flexmark:flexmark-util-ast:0.62.2 com.vladsch.flexmark:flexmark-util-builder:0.62.2 com.vladsch.flexmark:flexmark-util-collection:0.62.2 @@ -513,46 +533,52 @@ com.vladsch.flexmark:flexmark-util-misc:0.62.2 com.vladsch.flexmark:flexmark-util-options:0.62.2 com.vladsch.flexmark:flexmark-util-sequence:0.62.2 com.vladsch.flexmark:flexmark-util-visitor:0.62.2 +com.vladsch.flexmark:flexmark-util:0.62.2 +com.vladsch.flexmark:flexmark:0.62.2 commons-cli:commons-cli:1.4 commons-codec:commons-codec:1.11 commons-logging:commons-logging:1.2 de.jensd:fontawesomefx-commons:11.0 de.jensd:fontawesomefx-materialdesignfont:1.7.22-11 -de.saxsys:mvvmfx:1.8.0 de.saxsys:mvvmfx-validation:1.9.0-SNAPSHOT +de.saxsys:mvvmfx:1.8.0 de.undercouch:citeproc-java:2.1.0-SNAPSHOT eu.lestard:doc-annotations:0.2 info.debatty:java-string-similarity:2.0.0 -io.github.java-diff-utils:java-diff-utils:4.7 +io.github.java-diff-utils:java-diff-utils:4.9 jakarta.activation:jakarta.activation-api:1.2.1 jakarta.annotation:jakarta.annotation-api:1.3.5 jakarta.xml.bind:jakarta.xml.bind-api:2.3.2 net.jcip:jcip-annotations:1.0 net.jodah:typetools:0.6.1 -org.antlr:antlr4-runtime:4.8-1 org.antlr:antlr-runtime:3.5.2 +org.antlr:antlr4-runtime:4.9 org.apache.commons:commons-csv:1.8 org.apache.commons:commons-lang3:3.9 org.apache.commons:commons-text:1.8 org.apache.httpcomponents:httpasyncclient:4.1.4 -org.apache.httpcomponents:httpclient:4.5.12 -org.apache.httpcomponents:httpcore:4.4.13 +org.apache.httpcomponents:httpclient:4.5.13 org.apache.httpcomponents:httpcore-nio:4.4.13 -org.apache.httpcomponents:httpmime:4.5.12 +org.apache.httpcomponents:httpcore:4.4.13 +org.apache.httpcomponents:httpmime:4.5.13 org.apache.logging.log4j:log4j-api:3.0.0-SNAPSHOT org.apache.logging.log4j:log4j-core:3.0.0-SNAPSHOT org.apache.logging.log4j:log4j-jcl:3.0.0-SNAPSHOT org.apache.logging.log4j:log4j-plugins:3.0.0-SNAPSHOT org.apache.logging.log4j:log4j-slf4j18-impl:3.0.0-SNAPSHOT -org.apache.pdfbox:fontbox:2.0.20 -org.apache.pdfbox:pdfbox:2.0.20 -org.apache.pdfbox:xmpbox:2.0.20 -org.apache.tika:tika-core:1.24.1 -org.bouncycastle:bcprov-jdk15on:1.66 -org.checkerframework:checker-qual:2.11.1 -org.controlsfx:controlsfx:11.0.2 -org.fxmisc.flowless:flowless:0.6.1 -org.fxmisc.richtext:richtextfx:0.10.5 +org.apache.lucene:lucene-core:8.7.0 +org.apache.lucene:lucene-queries:8.7.0 +org.apache.lucene:lucene-queryparser:8.7.0 +org.apache.pdfbox:fontbox:2.0.22 +org.apache.pdfbox:pdfbox:2.0.22 +org.apache.pdfbox:xmpbox:2.0.22 +org.apache.tika:tika-core:1.25 +org.bouncycastle:bcprov-jdk15on:1.67 +org.checkerframework:checker-qual:3.5.0 +org.controlsfx:controlsfx:11.0.3 +org.eclipse.jgit:org.eclipse.jgit:5.10.0.202012080955-r +org.fxmisc.flowless:flowless:0.6.2 +org.fxmisc.richtext:richtextfx:0.10.4 org.fxmisc.undo:undofx:2.1.0 org.fxmisc.wellbehaved:wellbehavedfx:0.3.3 org.glassfish.hk2.external:jakarta.inject:2.6.1 @@ -566,20 +592,22 @@ org.jbibtex:jbibtex:1.0.17 org.jetbrains:annotations:15.0 org.jsoup:jsoup:1.13.1 org.jvnet.staxex:stax-ex:1.8.1 -org.mariadb.jdbc:mariadb-java-client:2.6.2 -org.openjfx:javafx-base:14 -org.openjfx:javafx-controls:14 -org.openjfx:javafx-fxml:14 -org.openjfx:javafx-graphics:14 -org.openjfx:javafx-media:14 -org.openjfx:javafx-swing:14 -org.openjfx:javafx-web:14 -org.ow2.asm:asm:6.2.1 +org.libreoffice:libreoffice:7.0.3 +org.libreoffice:unoloader:7.0.4 +org.mariadb.jdbc:mariadb-java-client:2.7.1 +org.openjfx:javafx-base:15 +org.openjfx:javafx-controls:15 +org.openjfx:javafx-fxml:15 +org.openjfx:javafx-graphics:15 +org.openjfx:javafx-media:15 +org.openjfx:javafx-swing:15 +org.openjfx:javafx-web:15 org.ow2.asm:asm-analysis:6.2.1 org.ow2.asm:asm-commons:6.2.1 org.ow2.asm:asm-tree:6.2.1 org.ow2.asm:asm-util:6.2.1 -org.postgresql:postgresql:42.2.16 +org.ow2.asm:asm:6.2.1 +org.postgresql:postgresql:42.2.18 org.reactfx:reactfx:2.0-M5 org.scala-lang:scala-library:2.12.8 org.slf4j:slf4j-api:2.0.0-alpha1 diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000000..08b655db0e5 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.vs.watch=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 490fda8577d..e708b1c023e 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4b4429748d..622ab64a3cb 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.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 2fe81a7d95e..4f906e0c811 100755 --- a/gradlew +++ b/gradlew @@ -82,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -129,6 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath diff --git a/gradlew.bat b/gradlew.bat index 62bd9b9ccef..107acd32c4e 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 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 diff --git a/lib/libreoffice.jar b/lib/libreoffice.jar deleted file mode 100644 index 072f96ec432..00000000000 Binary files a/lib/libreoffice.jar and /dev/null differ diff --git a/scripts/README.md b/scripts/README.md deleted file mode 100644 index e5425d52e3c..00000000000 --- a/scripts/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Script usage - -The Script has following commands available -- `$ python scripts/syncLang.py status [--extended]` - - prints the current status to the terminal - - `[--extended]` if the translations keys which create problems should be printed - - usable with Gradle tasks - - `$ gradle localizationStatus` - - `$ gradle localizationStatusExtended` - - -- `$ python scripts/syncLang.py markdown` - - Creates a markdown file of the current status and opens it - - usable with Gradle tasks - - `$ gradle localizationStatusWithMarkdown` - - -- `$ python scripts/syncLang.py update [--extended]` - - compares all the localization files against the English one and fixes unambiguous duplicates, removes obsolete keys, adds missing keys, and sorts them - - `[--extended]` if the translations keys which create problems should be printed - - usable with Gradle tasks - - `$ gradle localizationUpdate` - - `$ gradle localizationUpdateExtended` diff --git a/scripts/generate-authors.sh b/scripts/generate-authors.sh index d38d5c4ba5e..e83d6a734e9 100755 --- a/scripts/generate-authors.sh +++ b/scripts/generate-authors.sh @@ -57,6 +57,6 @@ cd "$(dirname "$(readlink -f "$BASH_SOURCE")")/.." # authors %aN = author name # co-authors - coauthors=$(git log -i --grep=co-authored-by | grep -i "co-authored-by" | sed "s/.*co-authored-by: \(.*\)/\1/I" | sed "s/ <.*//") + coauthors=$(git log -i --grep=co-authored-by | grep -i "co-authored-by" | sed "s/.*co-authored-by: \(.*\)/\1/I" | grep -v "luis-valdez" | sed "s/ <.*//") echo -e "$authors\n$(git log --format='%aN')\n$coauthors" | grep -v "\[bot\]" | grep -v "JabRef" | grep -v "Siedlerchr" | grep -v "^Christoph$" | grep -v "^Mootez$" | grep -v "oscargus" | grep -v "dependabot" | grep -v "github actions" | grep -v "igorsteinmacher" | grep -v "halirutan" | grep -v "matthiasgeiger" | grep -v "Gitter Badger" | grep -v "gdstewart" | grep -v "m-mauersberger" | grep -v "chenyuheng" | LC_ALL=C.UTF-8 sort --unique --ignore-case } > AUTHORS diff --git a/scripts/remove-git-markers-in-localization-files.rb b/scripts/remove-git-markers-in-localization-files.rb deleted file mode 100644 index 0f4a5cb1a37..00000000000 --- a/scripts/remove-git-markers-in-localization-files.rb +++ /dev/null @@ -1,4 +0,0 @@ -# call from project root via $ ruby remove-git-markers-in-localization-files.rb -Dir.glob("src/main/resources/l10n/*.properties") do |f| - File.write(f, IO.readlines(f).map { |line| line.strip.start_with?("<<<<<<<") || line.strip.start_with?("=======") || line.strip.start_with?(">>>>>>>") ? "" : line }.join("")) -end diff --git a/scripts/syncLang.py b/scripts/syncLang.py deleted file mode 100644 index 0b2386b3617..00000000000 --- a/scripts/syncLang.py +++ /dev/null @@ -1,661 +0,0 @@ -# coding=utf-8 -from __future__ import division - -import argparse # Requires Python 2.7, should be safe to use with jython. -import codecs -import datetime -import logging -import os -import subprocess -import sys - -logging.basicConfig(level=logging.INFO) - -RES_DIR = "src/main/resources/l10n" -URL_BASE = "https://github.com/JabRef/jabref/tree/master/src/main/resources/l10n/" - -try: - # Just to make sure not to break anything, in case py3.x is not supported. - import pathlib - - class PathFinder: - """ - This class is designed to automatically locate this script's path within the repository. - Once it found it's location it can easily provide paths to other important directories and files. - - requires Python 3.4 or higher - """ - - BASE_DIRECTORY_NAME = 'jabref' - - @staticmethod - def getJabRefBaseDirectory(): - """ - Searches the script's path backwards until it finds the matching base directory. - :return the path to JabRef's base directory as pathlib.Path object. - """ - cwd = pathlib.Path.cwd() - if cwd.name == PathFinder.BASE_DIRECTORY_NAME: - return cwd - - for parent in cwd.parents: - if parent.name == PathFinder.BASE_DIRECTORY_NAME: - return parent - # TODO What to do if base directory could not be found? - - # Important directories of the JabRef repository - JABREF_BASE_DIRECTORY = PathFinder.getJabRefBaseDirectory() - JABREF_SOURCE_DIRECTORY = JABREF_BASE_DIRECTORY / 'src' - JABREF_SCRIPTS_DIRECTORY = JABREF_BASE_DIRECTORY / 'scripts' - JABREF_LOCALIZATION_DIRECTORY = JABREF_SOURCE_DIRECTORY / 'main/resources/l10n' - - # Important files - JABREF_MAIN_LOCALIZATION_FILE = JABREF_LOCALIZATION_DIRECTORY / 'JabRef_en.properties' - JABREF_MAIN_MENU_LOCALIZATION_FILE = JABREF_LOCALIZATION_DIRECTORY / 'Menu_en.properties' -except ImportError: - logging.info("Unable to use PathFinder class.") - - -class Git: - - def get_current_branch(self): - """ - :return: the current git branch - """ - return self.__call_command('git rev-parse --abbrev-ref HEAD') - - def get_current_hash_short(self): - """ - :return: the current git hash (short) - """ - return self.__call_command('git rev-parse --short HEAD') - - def __call_command(self, command): - """ - :param command: a shell command - :return: the output of the shell command - """ - return subprocess.check_output(command.split(" ")).decode("utf-8").rstrip() - - -class Keys: - - def __init__(self, lines): - self.lines = lines - - def find_duplicates(self): - """ - return: list of unicode strings - """ - duplicates = [] - keys_checked = {} - for line in self.lines: - key, value = self.__extract_key_and_value(line=line) - if key: - if key in keys_checked: - duplicates.append(self.format_key_and_value(key=key, value=value)) - translation_in_list = self.format_key_and_value(key=key, value=keys_checked[key]) - if translation_in_list not in duplicates: - duplicates.append(translation_in_list) - else: - keys_checked[key] = value - return duplicates - - def fix_duplicates(self): - """ - Fixes all unambiguous duplicates - :return: (list of unicode strings, list of unicode strings): not fixed ambiguous duplicates, fixed unambiguous duplicates - """ - keys = {} - fixed = [] - not_fixed = [] - for line in self.lines: - key, value = self.__extract_key_and_value(line=line) - if key: - if key in keys: - if not keys[key]: - fixed.append(self.format_key_and_value(key=key, value=keys[key])) - keys[key] = value - elif not value: - fixed.append(self.format_key_and_value(key=key, value=value)) - elif keys[key] == value: - fixed.append(self.format_key_and_value(key=key, value=value)) - elif keys[key] != value: - not_fixed.append(self.format_key_and_value(key=key, value=value)) - not_fixed.append(self.format_key_and_value(key=key, value=keys[key])) - else: - keys[key] = value - - return keys, not_fixed, fixed - - def keys_from_lines(self): - """ - Builds a list of all translation keys in the list of lines. - - :return: list of unicode strings: the sorted keys within the lines - """ - keys = list() - for line in self.lines: - key = self.key_from_line(line) - if key: - keys.append(key) - return keys - - @staticmethod - def key_from_line(line): - """ - Tries to extract the key from the line - - :param line: unicode string - :return: unicode string: the key or None - """ - if line.find("#") != 0 or line.find("!") != 0: - index_key_end = line.find("=") - while (index_key_end > 0) and (line[index_key_end - 1] == "\\"): - index_key_end = line.find("=", index_key_end + 1) - if index_key_end > 0: - return line[0:index_key_end].strip() - return None - - def empty_keys(self): - """ - :return: list of unicode strings: the keys with empty values - """ - not_translated = list() - keys = self.translations_as_dict() - for key, value in keys.items(): - if not value: - not_translated.append(key) - return not_translated - - def translations_as_dict(self): - """ - :return: dict of unicode strings: - """ - translations = dict() - for line in self.lines: - key, value = self.__extract_key_and_value(line=line) - if key: - translations[key] = value - return translations - - @staticmethod - def __extract_key_and_value(line): - """ - Tries to extract the key and value from the line - :param line: unicode string - :return: (unicode string, unicode string) or (None, None): (key, value) - """ - if line.find("#") != 0 or line.find("!") != 0: - index_key_end = line.find("=") - while (index_key_end > 0) and (line[index_key_end - 1] == "\\"): - index_key_end = line.find("=", index_key_end + 1) - if index_key_end > 0: - return line[0:index_key_end].strip(), line[index_key_end + 1:].strip() - return None, None - - @staticmethod - def format_key_and_value(key, value): - return u"{key}={value}".format(key=key, value=value) - - -class SyncLang: - - def __init__(self, extended_logging=False): - """ - :param extended: boolean: if the keys with problems should be printed - - """ - self.extended_logging = extended_logging - self.main_jabref_preferences = os.path.join(RES_DIR, "JabRef_en.properties") - self.main_menu_preferences = os.path.join(RES_DIR, "Menu_en.properties") - - def set_extended_logging_enabled(self, value): - self.extended_logging = bool(value) - - def print_missing_keys(self): - for file in self.__other_jabref_properties(): - file_name = self.__format_filename(file) - self.print_missing_keys_for_file(file_name) - for file in self.__other_menu_properties(): - file_name = self.__format_filename(file) - self.print_missing_keys_for_file(file_name) - - def print_missing_keys_for_file(self, file_name): - file_status = self.__get_status_for_file(file_name) - if file_status: - keys_missing, _, _, _ = file_status - - if len(keys_missing) > 0: - logging.info("Printing missing keys for file: " + file_name) - self.__print_keys(keys_missing) - else: - logging.info("No missing keys found for file:" + file_name) - - def print_obsolete_keys(self): - for file in self.__other_jabref_properties(): - file_name = self.__format_filename(file) - self.print_obsolete_keys_for_file(file_name) - for file in self.__other_menu_properties(): - file_name = self.__format_filename(file) - self.print_obsolete_keys_for_file(file_name) - - - def print_obsolete_keys_for_file(self, file_name): - file_status = self.__get_status_for_file(file_name) - if file_status: - _, keys_obsolete, _, _ = file_status - - if len(keys_obsolete) > 0: - - logging.info("Printing obsolete keys for file: " + file_name) - self.__print_keys(keys_obsolete) - else: - logging.info("No obsolete keys found for file: " + file_name) - - def print_duplicate_keys(self): - for file in self.__other_jabref_properties(): - file_name = self.__format_filename(file) - self.print_duplicate_keys_for_file(file_name) - for file in self.__other_menu_properties(): - file_name = self.__format_filename(file) - self.print_duplicate_keys_for_file(file_name) - - def print_duplicate_keys_for_file(self, file_name): - file_status = self.__get_status_for_file(file_name) - if file_status: - _, _, keys_duplicate, _ = file_status - - if len(keys_duplicate) > 0: - - logging.info("Printing duplicate keys for file: " + file_name) - self.__print_keys(keys_duplicate) - else: - logging.info("No duplicate keys found for file: " + file_name) - - def status(self): - """ - prints the current status to the terminal - """ - - self.__print_status_jabref_properties() - self.__print_status_menu_properties() - - def __print_status_menu_properties(self): - self.__compare_properties(main_property_file=self.main_menu_preferences, property_files=self.__all_menu_properties()) - - def __print_status_jabref_properties(self): - self.__compare_properties(main_property_file=self.main_jabref_preferences, property_files=self.__all_jabref_properties()) - - def update_properties(self): - """ - updates all the localization files - fixing unambiguous duplicates, removing obsolete keys, adding missing keys, and sorting them - """ - - self.__update_jabref_properties() - self.__update_menu_properties() - - def __update_menu_properties(self): - self.__update_properties(main_property_file=self.main_menu_preferences, other_property_files=self.__other_menu_properties()) - - def __update_jabref_properties(self): - self.__update_properties(main_property_file=self.main_jabref_preferences, other_property_files=self.__other_jabref_properties()) - - def __get_main_file(self, file_name): - - file = os.path.join(RES_DIR, file_name) - - if file in self.__all_jabref_properties(): - return self.main_jabref_preferences - elif file in self.__all_menu_properties(): - return self.main_menu_preferences - - def __get_status_for_file(self, file_name): - main_file = self.__get_main_file(file_name) - if main_file: - main_lines = self.__read_lines_from_file(filename=main_file) - main_keys = Keys(main_lines) - - file = os.path.join(RES_DIR, file_name) - lines = self.__read_lines_from_file(file) - keys1 = Keys(lines) - keys = keys1.keys_from_lines() - - keys_missing = self.__missing_keys(main_keys.keys_from_lines(), keys) - keys_obsolete = self.__missing_keys(keys, main_keys.keys_from_lines()) - keys_duplicates = keys1.find_duplicates() - keys_not_translated = keys1.empty_keys() - - return (keys_missing, keys_obsolete, keys_duplicates, keys_not_translated) - else: - logging.debug("Unable to find main file for: " + file_name) - - def __compare_properties(self, main_property_file, property_files): - main_lines = self.__read_lines_from_file(filename=main_property_file) - main_keys = Keys(main_lines) - - # the main property file gets compared to itself, but that is OK - for file in property_files: - filename = self.__format_filename(filepath=file) - lines = self.__read_lines_from_file(file) - keys1 = Keys(lines) - keys = keys1.keys_from_lines() - - keys_missing = self.__missing_keys(main_keys.keys_from_lines(), keys) - keys_obsolete = self.__missing_keys(keys, main_keys.keys_from_lines()) - keys_duplicates = keys1.find_duplicates() - keys_not_translated = keys1.empty_keys() - - num_keys = len(keys) - num_keys_missing = len(keys_missing) - num_keys_not_translated = len(keys_not_translated) - num_keys_obsolete = len(keys_obsolete) - num_keys_duplicate = len(keys_duplicates) - num_keys_translated = num_keys - num_keys_not_translated - - log = logging.error if num_keys_missing != 0 or num_keys_not_translated != 0 or num_keys_obsolete != 0 or num_keys_duplicate != 0 else logging.info - log("Status of file '{file}' with {num_keys} Keys".format(file=filename, num_keys=num_keys)) - logging.info("\t{} translated keys".format(num_keys_translated)) - - log = logging.error if num_keys_not_translated != 0 else logging.info - log("\t{} not translated keys".format(num_keys_not_translated)) - if self.extended_logging and num_keys_not_translated != 0: - self.__print_keys(keys_not_translated) - - log = logging.error if num_keys_missing != 0 else logging.info - log("\t{} missing keys".format(num_keys_missing)) - if self.extended_logging and num_keys_missing != 0: - self.__print_keys(keys_missing) - - log = logging.error if num_keys_obsolete != 0 else logging.info - log("\t{} obsolete keys".format(num_keys_obsolete)) - if self.extended_logging and num_keys_obsolete != 0: - self.__print_keys(keysobsolete) - - log = logging.error if num_keys_duplicate != 0 else logging.info - log("\t{} duplicates".format(num_keys_duplicate)) - if self.extended_logging and num_keys_duplicate != 0: - self.__print_keys(keys_duplicates) - - def __all_menu_properties(self): - """ - :return: list of strings: all the Menu_*.preferences files with the english at the beginning - """ - menu_property_files = sorted(self.__other_menu_properties()) - menu_property_files.insert(0, self.main_menu_preferences) - return menu_property_files - - def __other_menu_properties(self): - """ - :return: list of strings: all the Menu_*.preferences files without the english one - """ - menu_property_files = [s for s in os.listdir(RES_DIR) if (s.startswith('Menu_') and not (s.startswith('Menu_en')))] - return [os.path.join(RES_DIR, file) for file in menu_property_files] - - def __all_jabref_properties(self): - """ - :return: list of strings: all the JabRef_*.preferences file paths with the english at the beginning - """ - jabref_property_files = sorted(self.__other_jabref_properties()) - jabref_property_files.insert(0, self.main_jabref_preferences) - return jabref_property_files - - def __other_jabref_properties(self): - """ - :return: list of strings: all the JabRef_*.preferences file paths without the english one - """ - jabref_property_files = [s for s in os.listdir(RES_DIR) if (s.startswith('JabRef_') and not (s.startswith('JabRef_en')))] - return [os.path.join(RES_DIR, file) for file in jabref_property_files] - - def __update_properties(self, main_property_file, other_property_files): - main_lines = self.__read_lines_from_file(filename=main_property_file) - main_keys = Keys(main_lines) - main_keys_dict = main_keys.translations_as_dict() - - main_duplicates = main_keys.find_duplicates() - num_main_duplicates = len(main_duplicates) - if num_main_duplicates != 0: - logging.error("There are {num_duplicates} duplicates in {file}, please fix them manually".format(num_duplicates=num_main_duplicates, - file=self.__format_filename( - filepath=main_property_file))) - if self.extended_logging: - self.__print_keys(main_duplicates) - return - - for other_property_file in other_property_files: - filename = self.__format_filename(filepath=other_property_file) - lines = self.__read_lines_from_file(filename=other_property_file) - keys, not_fixed, fixed = Keys(lines).fix_duplicates() - - num_keys = len(keys) - num_not_fixed = len(not_fixed) - num_fixed = len(fixed) - - if num_not_fixed != 0: - logging.error("There are {num_not_fixed_duplicates} ambiguous duplicates in {file}, please fix them manually".format( - num_not_fixed_duplicates=num_not_fixed, file=filename)) - if self.extended_logging: - self.__print_keys(not_fixed) - continue - - keys_missing = self.__missing_keys(main_keys.keys_from_lines(), keys) - keys_obsolete = self.__missing_keys(keys, main_keys.keys_from_lines()) - - num_keys_missing = len(keys_missing) - num_keys_obsolete = len(keys_obsolete) - - # for missing_key in keys_missing: - # Missing keys are added with main translation by default. - # keys[missing_key] = main_keys_dict[missing_key] - - for obsolete_key in keys_obsolete: - del keys[obsolete_key] - - other_lines_to_write = [] - for line in main_lines: - key = main_keys.key_from_line(line) - if key is not None: - if keys.has_key(key): - # Do not write empty keys - if keys[key] != "": - other_lines_to_write.append(Keys.format_key_and_value(key=key, value=keys[key]) + "\n") - else: - other_lines_to_write.append(line) - - sorted_lines = len(lines) != len(other_lines_to_write) - if not sorted_lines: - for old_line, new_lines in zip(lines, other_lines_to_write): - if old_line != new_lines: - sorted_lines = True - - self.__write_file(filename=other_property_file, content=other_lines_to_write) - - logging.info("Processing file '{file}' with {num_keys} Keys".format(file=filename, num_keys=num_keys)) - if num_fixed != 0: - logging.info("\tfixed {} unambiguous duplicates".format(num_fixed)) - if self.extended_logging: - self.__print_keys(fixed) - - if num_keys_missing != 0: - logging.info("\tadded {} missing keys".format(num_keys_missing)) - if self.extended_logging: - self.__print_keys(keys_missing) - - if num_keys_obsolete != 0: - logging.info("\tdeleted {} obsolete keys".format(num_keys_obsolete)) - if self.extended_logging: - self.__print_keys(keys_obsolete) - - if sorted_lines: - logging.info("\thas been sorted successfully") - - @staticmethod - def __format_filename(filepath): - """ - removes the res_dir path - - :param filepath: string - :return: pure file name of this file path (including file extension e.g. *.txt) - """ - return filepath.replace("{}\\".format(RES_DIR), "") - - @staticmethod - def __write_file(filename, content): - """ - writes the lines to the file in `UTF-8` - :param filename: string - :param content: list of unicode strings: the lines to write - """ - with codecs.open(filename, 'w', encoding="UTF-8") as f: - f.writelines(content) - - @staticmethod - def __read_lines_from_file(filename): - """ - :param filename: string - :param encoding: string: the encoding of the file to read (standard: `UTF-8`) - :return: list of unicode strings: the lines of the file - """ - with codecs.open(filename, 'r', encoding="UTF-8") as file: - return [u"{}\n".format(line.strip()) for line in file.readlines()] - - @staticmethod - def __missing_keys(first_list, second_list): - """ - Finds all keys in the first list that are not present in the second list - - :param first_list: list of unicode strings - :param second_list: list of unicode strings - :return: list of unicode strings - """ - return list(set(first_list).difference(second_list)) - - @staticmethod - def __print_keys(keys, logger=logging.info): - for key in keys: - logger("\t{}\n".format(key)) - - def status_create_markdown(self, markdown_file='status.md'): - """ - Creates a markdown file of the current status. - """ - - def _write_properties(output_file, property_files): - output_file.write("\n| Property file | Keys | Keys translated | Keys not translated | % translated |\n") - output_file.write("| ------------- | ---- | --------------- | ------------------- | ------------ |\n") - - for file in property_files: - lines = self.__read_lines_from_file(file) - keys = Keys(lines) - num_keys = len(keys.translations_as_dict()) - num_keys_missing_value = len(keys.empty_keys()) - num_keys_translated = num_keys - num_keys_missing_value - - output_file.write("| [%s](%s%s) | %d | %d | %d | %d |\n" % (os.path.basename(file), - URL_BASE, - os.path.basename(file), - num_keys, - num_keys_translated, - num_keys_missing_value, - _percentage(num_keys, num_keys_translated) - ) - ) - - def _percentage(whole, part): - if whole == 0: - return 0 - return int(part / whole * 100.0) - - with codecs.open(markdown_file, "w", encoding="UTF-8") as status_file: - status_file.write('### Localization files status (' + datetime.datetime.now().strftime( - "%Y-%m-%d %H:%M") + ' - Branch `' + Git().get_current_branch() + '` `' + Git().get_current_hash_short() + '`)\n\n') - status_file.write('Note: To get the current status from your local repository, run `python ./scripts/syncLang.py markdown`\n') - - _write_properties(status_file, self.__all_menu_properties()) - _write_properties(status_file, self.__all_jabref_properties()) - - logging.info('Current status written to ' + markdown_file) - - -def main(): - - syncer = SyncLang() - - def markdown_command(args): - syncer.set_extended_logging_enabled(False) - syncer.status_create_markdown() - - def status_command(args): - syncer.set_extended_logging_enabled(args.extended) - syncer.status() - - def update_command(args): - syncer.set_extended_logging_enabled(args.extended) - syncer.update_properties() - - def print_missing(args): - file_name = args.file - if file_name: - syncer.print_missing_keys_for_file(file_name) - else: - syncer.print_missing_keys() - - def print_obsolete(args): - file_name = args.file - if file_name: - syncer.print_obsolete_keys_for_file(file_name) - else: - syncer.print_obsolete_keys() - - def print_duplicates(args): - file_name = args.file - if file_name: - syncer.print_duplicate_keys_for_file(file_name) - else: - syncer.print_duplicate_keys() - - parser = argparse.ArgumentParser(add_help=True) - parser.description = "This script is used to synchronize the keys of different *.properties files." - - shared_arguments = argparse.ArgumentParser(add_help=False) - extended_argument = shared_arguments.add_argument("-e", "--extended", help="Prints extended information about the process to the terminal", required=False, action='store_true', default=False) - - subcommands = parser.add_subparsers(title="Subcommands", description="Provide different options for the user", dest="subcommand") - - # markdown parser - markdown_parser = subcommands.add_parser("markdown", description="Creates a markdown file of the current status") - markdown_parser.set_defaults(func=markdown_command) - # TODO add argument to pass a file name for the markdown file - - # status parser - status_parser = subcommands.add_parser("status", description="Prints the current status to the terminal", parents=[shared_arguments]) - status_parser.set_defaults(func=status_command) - - # update parser - update_parser = subcommands.add_parser("update", description="Compares all the localization files against the English one and fixes unambiguous duplicates, removes obsolete keys, adds missing keys, and sorts them", parents=[shared_arguments]) - update_parser.set_defaults(func=update_command) - - # print parser - print_parser = subcommands.add_parser("print", description="Prints specific status info to the console") - - shared_print_arguments = argparse.ArgumentParser(add_help=False) - file_argument = shared_print_arguments.add_argument("-f", "--file", help="Specifies a file for the command to run with", required=False, action='store') - - print_options = print_parser.add_subparsers(title="Print Options", description="Different options for printing", dest="print_option_name") - - missing_parser = print_options.add_parser("missing", description="Prints all missing keys", parents=[shared_print_arguments]) - missing_parser.set_defaults(func=print_missing) - - obsolete_parser = print_options.add_parser("obsolete", description="Prints all obsolete keys", parents=[shared_print_arguments]) - obsolete_parser.set_defaults(func=print_obsolete) - - duplicates_parser = print_options.add_parser("duplicates", description="Prints all duplicate keys", parents=[shared_print_arguments]) - duplicates_parser.set_defaults(func=print_duplicates) - - parsed_args = parser.parse_args() - parsed_args.func(parsed_args) - - -if '__main__' == __name__: - main() 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 f53d9d44f63..9e73b004280 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: @@ -31,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: @@ -46,18 +47,17 @@ apps: environment: _JAVA_OPTIONS: "-Duser.home=$SNAP_USER_DATA" + GTK_USE_PORTAL: "1" 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 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: diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index eb7e0102e92..cf4223b686d 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -47,7 +47,7 @@ requires applicationinsights.core; // Libre Office - requires org.jabref.thirdparty.libreoffice; + requires org.libreoffice.uno; // Other modules requires commons.logging; @@ -57,6 +57,7 @@ requires reactfx; requires commons.cli; requires com.github.tomtung.latex2unicode; + requires fastparse; requires jbibtex; requires citeproc.java; requires antlr.runtime; @@ -81,7 +82,7 @@ requires org.apache.commons.lang3; requires org.antlr.antlr4.runtime; requires flowless; - requires org.apache.tika.core; + requires tika.core; requires flexmark; requires flexmark.ext.gfm.strikethrough; @@ -91,4 +92,5 @@ requires com.h2database.mvstore; requires lucene.queryparser; requires lucene.core; + requires org.eclipse.jgit; } diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index eeee79c1366..e52c15bf966 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -456,8 +456,8 @@ private void exportFile(List loaded, String[] data) { private void importPreferences() { try { Globals.prefs.importPreferences(cli.getPreferencesImport()); - Globals.entryTypesManager.addCustomOrModifiedTypes(Globals.prefs.loadBibEntryTypes(BibDatabaseMode.BIBTEX), - Globals.prefs.loadBibEntryTypes(BibDatabaseMode.BIBLATEX)); + Globals.entryTypesManager.addCustomOrModifiedTypes(Globals.prefs.getBibEntryTypes(BibDatabaseMode.BIBTEX), + Globals.prefs.getBibEntryTypes(BibDatabaseMode.BIBLATEX)); List customExporters = Globals.prefs.getCustomExportFormats(Globals.journalAbbreviationRepository); LayoutFormatterPreferences layoutPreferences = Globals.prefs.getLayoutFormatterPreferences(Globals.journalAbbreviationRepository); diff --git a/src/main/java/org/jabref/gui/Base.css b/src/main/java/org/jabref/gui/Base.css index e89c9a27680..9c1635a129b 100644 --- a/src/main/java/org/jabref/gui/Base.css +++ b/src/main/java/org/jabref/gui/Base.css @@ -427,8 +427,11 @@ } .check-box > .box > .mark { - -fx-background-color: -fx-control-inner-background; -fx-padding: 0.2em 0.2em 0.2em 0.2em; +} + +.check-box:selected > .box > .mark { + -fx-background-color: -fx-control-inner-background; -fx-shape: "M6.61 11.89L3.5 8.78 2.44 9.84 6.61 14l8.95-8.95L14.5 4z"; -fx-stroke-width: 5; } 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/ClipBoardManager.java b/src/main/java/org/jabref/gui/ClipBoardManager.java index d78183a3479..d66db4c34a7 100644 --- a/src/main/java/org/jabref/gui/ClipBoardManager.java +++ b/src/main/java/org/jabref/gui/ClipBoardManager.java @@ -33,6 +33,7 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.identifier.DOI; import org.jabref.model.util.OptionalUtil; +import org.jabref.preferences.PreferencesService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,15 +48,18 @@ public class ClipBoardManager { private static Clipboard clipboard; private static java.awt.datatransfer.Clipboard primary; private static ImportFormatReader importFormatReader; + private final PreferencesService preferencesService; - public ClipBoardManager() { - this(Clipboard.getSystemClipboard(), Toolkit.getDefaultToolkit().getSystemSelection(), Globals.IMPORT_FORMAT_READER); + public ClipBoardManager(PreferencesService preferencesService) { + this(Clipboard.getSystemClipboard(), Toolkit.getDefaultToolkit().getSystemSelection(), Globals.IMPORT_FORMAT_READER, preferencesService); } - public ClipBoardManager(Clipboard clipboard, java.awt.datatransfer.Clipboard primary, ImportFormatReader importFormatReader) { + public ClipBoardManager(Clipboard clipboard, java.awt.datatransfer.Clipboard primary, ImportFormatReader importFormatReader, PreferencesService preferencesService) { ClipBoardManager.clipboard = clipboard; ClipBoardManager.primary = primary; ClipBoardManager.importFormatReader = importFormatReader; + + this.preferencesService = preferencesService; } /** @@ -154,7 +158,7 @@ public void setContent(String string) { public void setContent(List entries) throws IOException { final ClipboardContent content = new ClipboardContent(); - BibEntryWriter writer = new BibEntryWriter(new FieldWriter(Globals.prefs.getFieldWriterPreferences()), Globals.entryTypesManager); + BibEntryWriter writer = new BibEntryWriter(new FieldWriter(preferencesService.getFieldWriterPreferences()), Globals.entryTypesManager); String serializedEntries = writer.serializeAll(entries, BibDatabaseMode.BIBTEX); content.put(DragAndDropDataFormats.ENTRIES, serializedEntries); content.putString(serializedEntries); @@ -172,7 +176,7 @@ public List extractData() { } private List handleBibTeXData(String entries) { - BibtexParser parser = new BibtexParser(Globals.prefs.getImportFormatPreferences(), Globals.getFileUpdateMonitor()); + BibtexParser parser = new BibtexParser(preferencesService.getImportFormatPreferences(), Globals.getFileUpdateMonitor()); try { return parser.parseEntries(new ByteArrayInputStream(entries.getBytes(StandardCharsets.UTF_8))); } catch (ParseException ex) { @@ -206,7 +210,7 @@ private List tryImportFormats(String data) { private List fetchByDOI(DOI doi) { LOGGER.info("Found DOI in clipboard"); try { - Optional entry = new DoiFetcher(Globals.prefs.getImportFormatPreferences()).performSearchById(doi.getDOI()); + Optional entry = new DoiFetcher(preferencesService.getImportFormatPreferences()).performSearchById(doi.getDOI()); return OptionalUtil.toList(entry); } catch (FetcherException ex) { LOGGER.error("Error while fetching", ex); 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%); +} diff --git a/src/main/java/org/jabref/gui/DefaultInjector.java b/src/main/java/org/jabref/gui/DefaultInjector.java index aefc1b4f232..c79e077e23d 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; @@ -43,9 +44,11 @@ private static Object createDependency(Class clazz) { } else if (clazz == ProtectedTermsLoader.class) { return Globals.protectedTermsLoader; } else if (clazz == ClipBoardManager.class) { - return Globals.clipboardManager; + return Globals.getClipboardManager(); } 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/EntryTypeView.java b/src/main/java/org/jabref/gui/EntryTypeView.java index f304238b688..944d2d94ad9 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; @@ -33,7 +34,7 @@ import org.jabref.model.entry.types.IEEETranEntryTypeDefinitions; import org.jabref.model.entry.types.StandardEntryType; import org.jabref.model.strings.StringUtil; -import org.jabref.preferences.JabRefPreferences; +import org.jabref.preferences.PreferencesService; import com.airhacks.afterburner.views.ViewLoader; import com.tobiasdiez.easybind.EasyBind; @@ -60,18 +61,18 @@ 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; + private final PreferencesService preferencesService; private EntryType type; 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, PreferencesService preferences) { + this.libraryTab = libraryTab; this.dialogService = dialogService; - this.prefs = preferences; + this.preferencesService = preferences; this.setTitle(Localization.lang("Select entry type")); ViewLoader.view(this) @@ -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(preferencesService, 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..4ca06d9736a 100644 --- a/src/main/java/org/jabref/gui/EntryTypeViewModel.java +++ b/src/main/java/org/jabref/gui/EntryTypeViewModel.java @@ -25,7 +25,7 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntry; import org.jabref.model.strings.StringUtil; -import org.jabref.preferences.JabRefPreferences; +import org.jabref.preferences.PreferencesService; import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; import de.saxsys.mvvmfx.utils.validation.ValidationMessage; @@ -38,7 +38,7 @@ public class EntryTypeViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(EntryTypeViewModel.class); - private final JabRefPreferences prefs; + private final PreferencesService preferencesService; private final BooleanProperty searchingProperty = new SimpleBooleanProperty(); private final BooleanProperty searchSuccesfulProperty = new SimpleBooleanProperty(); private final ObjectProperty selectedItemProperty = new SimpleObjectProperty<>(); @@ -46,14 +46,17 @@ 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; - this.prefs = preferences; + public EntryTypeViewModel(PreferencesService preferences, + LibraryTab libraryTab, + DialogService dialogService, + StateManager stateManager) { + this.libraryTab = libraryTab; + this.preferencesService = preferences; this.dialogService = dialogService; this.stateManager = stateManager; fetchers.addAll(WebFetchers.getIdBasedFetchers(preferences.getImportFormatPreferences())); @@ -86,12 +89,12 @@ public BooleanProperty getFocusAndSelectAllProperty() { } public void storeSelectedFetcher() { - prefs.setIdBasedFetcherForEntryGenerator(selectedItemProperty.getValue().getName()); + preferencesService.storeIdBasedFetcherForEntryGenerator(selectedItemProperty.getValue().getName()); } private IdBasedFetcher getLastSelectedFetcher() { - return fetchers.stream().filter(fetcher -> fetcher.getName().equals(prefs.getIdBasedFetcherForEntryGenerator())) - .findFirst().orElse(new DoiFetcher(prefs.getImportFormatPreferences())); + return fetchers.stream().filter(fetcher -> fetcher.getName().equals(preferencesService.getIdBasedFetcherForEntryGenerator())) + .findFirst().orElse(new DoiFetcher(preferencesService.getImportFormatPreferences())); } public ListProperty fetcherItemsProperty() { @@ -147,22 +150,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,12 +173,16 @@ public void runFetcherWorker() { } } else { // Regenerate CiteKey of imported BibEntry - new CitationKeyGenerator(basePanel.getBibDatabaseContext(), prefs.getCitationKeyPatternPreferences()).generateAndSetKey(entry); - basePanel.insertEntry(entry); + new CitationKeyGenerator(libraryTab.getBibDatabaseContext(), preferencesService.getCitationKeyPatternPreferences()).generateAndSetKey(entry); + libraryTab.insertEntry(entry); } 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/gui/Globals.java b/src/main/java/org/jabref/gui/Globals.java index db851799580..017611aa349 100644 --- a/src/main/java/org/jabref/gui/Globals.java +++ b/src/main/java/org/jabref/gui/Globals.java @@ -73,7 +73,8 @@ public class Globals { public static ExporterFactory exportFactory; public static CountingUndoManager undoManager = new CountingUndoManager(); public static BibEntryTypesManager entryTypesManager = new BibEntryTypesManager(); - public static ClipBoardManager clipboardManager = new ClipBoardManager(); + + private static ClipBoardManager clipBoardManager = null; // Key binding preferences private static KeyBindingRepository keyBindingRepository; @@ -92,12 +93,19 @@ public static synchronized KeyBindingRepository getKeyPrefs() { return keyBindingRepository; } + public static synchronized ClipBoardManager getClipboardManager() { + if (clipBoardManager == null) { + clipBoardManager = new ClipBoardManager(prefs); + } + return clipBoardManager; + } + // Background tasks public static void startBackgroundTasks() { Globals.fileUpdateMonitor = new DefaultFileUpdateMonitor(); JabRefExecutorService.INSTANCE.executeInterruptableTask(Globals.fileUpdateMonitor, "FileUpdateMonitor"); - if (Globals.prefs.shouldCollectTelemetry() && !GraphicsEnvironment.isHeadless()) { + if (Globals.prefs.getTelemetryPreferences().shouldCollectTelemetry() && !GraphicsEnvironment.isHeadless()) { startTelemetryClient(); } } @@ -112,7 +120,7 @@ private static void stopTelemetryClient() { private static void startTelemetryClient() { TelemetryConfiguration telemetryConfiguration = TelemetryConfiguration.getActive(); telemetryConfiguration.setInstrumentationKey(Globals.BUILD_INFO.azureInstrumentationKey); - telemetryConfiguration.setTrackingIsDisabled(!Globals.prefs.shouldCollectTelemetry()); + telemetryConfiguration.setTrackingIsDisabled(!Globals.prefs.getTelemetryPreferences().shouldCollectTelemetry()); telemetryClient = new TelemetryClient(telemetryConfiguration); telemetryClient.getContext().getProperties().put("JabRef version", Globals.BUILD_INFO.version.toString()); telemetryClient.getContext().getProperties().put("Java version", StandardSystemProperty.JAVA_VERSION.value()); diff --git a/src/main/java/org/jabref/gui/JabRefDialogService.java b/src/main/java/org/jabref/gui/JabRefDialogService.java index 292486b146a..53816e402e8 100644 --- a/src/main/java/org/jabref/gui/JabRefDialogService.java +++ b/src/main/java/org/jabref/gui/JabRefDialogService.java @@ -40,7 +40,7 @@ import org.jabref.gui.util.FileDialogConfiguration; import org.jabref.gui.util.ZipFileChooser; import org.jabref.logic.l10n.Localization; -import org.jabref.preferences.JabRefPreferences; +import org.jabref.preferences.PreferencesService; import com.jfoenix.controls.JFXSnackbar; import com.jfoenix.controls.JFXSnackbar.SnackbarEvent; @@ -67,12 +67,12 @@ public class JabRefDialogService implements DialogService { private static final Duration TOAST_MESSAGE_DISPLAY_TIME = Duration.millis(3000); private static final Logger LOGGER = LoggerFactory.getLogger(JabRefDialogService.class); - private static JabRefPreferences preferences; + private static PreferencesService preferences; private final Window mainWindow; private final JFXSnackbar statusLine; - public JabRefDialogService(Window mainWindow, Pane mainPane, JabRefPreferences preferences) { + public JabRefDialogService(Window mainWindow, Pane mainPane, PreferencesService preferences) { this.mainWindow = mainWindow; this.statusLine = new JFXSnackbar(mainPane); JabRefDialogService.preferences = preferences; diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 46dbd53b54f..74ba19fd5eb 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; @@ -81,7 +83,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; @@ -119,10 +120,10 @@ import org.jabref.gui.undo.UndoRedoAction; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.DefaultTaskExecutor; +import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.autosaveandbackup.AutosaveManager; import org.jabref.logic.autosaveandbackup.BackupManager; import org.jabref.logic.citationstyle.CitationStyleOutputFormat; -import org.jabref.logic.exporter.GlobalSaveManager; import org.jabref.logic.importer.IdFetcher; import org.jabref.logic.importer.ImportCleanup; import org.jabref.logic.importer.ParserResult; @@ -133,17 +134,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 org.jabref.preferences.PreferencesService; +import org.jabref.preferences.TelemetryPreferences; 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; @@ -155,28 +154,30 @@ */ public class JabRefFrame extends BorderPane { - // Frame titles. public static final String FRAME_TITLE = "JabRef"; private static final Logger LOGGER = LoggerFactory.getLogger(JabRefFrame.class); private final SplitPane splitPane = new SplitPane(); - private final JabRefPreferences prefs = Globals.prefs; + private final PreferencesService prefs = Globals.prefs; private final GlobalSearchBar globalSearchBar = new GlobalSearchBar(this, Globals.stateManager, prefs); 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; private PopOver progressViewPopOver; + private final TaskExecutor taskExecutor; + public JabRefFrame(Stage mainStage) { this.mainStage = mainStage; this.dialogService = new JabRefDialogService(mainStage, this, prefs); @@ -184,7 +185,7 @@ 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.taskExecutor = Globals.TASK_EXECUTOR; this.setOnKeyTyped(key -> { if (this.fileHistory.isShowing()) { if (this.fileHistory.openFileByKey(key)) { @@ -194,10 +195,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"); @@ -238,7 +235,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: @@ -276,6 +273,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 @@ -291,7 +291,7 @@ private void initKeyBindings() { } private void initShowTrackingNotification() { - if (!Globals.prefs.shouldAskToCollectTelemetry()) { + if (Globals.prefs.getTelemetryPreferences().shouldAskToCollectTelemetry()) { JabRefExecutorService.INSTANCE.submit(new TimerTask() { @Override @@ -302,56 +302,20 @@ public void run() { } } - private Void showTrackingNotification() { - if (!Globals.prefs.shouldCollectTelemetry()) { - boolean shouldCollect = dialogService.showConfirmationDialogAndWait( + private void showTrackingNotification() { + TelemetryPreferences telemetryPreferences = Globals.prefs.getTelemetryPreferences(); + boolean shouldCollect = telemetryPreferences.shouldCollectTelemetry(); + + if (!telemetryPreferences.shouldCollectTelemetry()) { + shouldCollect = dialogService.showConfirmationDialogAndWait( Localization.lang("Telemetry: Help make JabRef better"), Localization.lang("To improve the user experience, we would like to collect anonymous statistics on the features you use. We will only record what features you access and how often you do it. We will neither collect any personal data nor the content of bibliographic items. If you choose to allow data collection, you can later disable it via Options -> Preferences -> General."), Localization.lang("Share anonymous statistics"), Localization.lang("Don't share")); - Globals.prefs.setShouldCollectTelemetry(shouldCollect); } - Globals.prefs.askedToCollectTelemetry(); - - 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); - } + Globals.prefs.storeTelemetryPreferences(telemetryPreferences.withCollectTelemetry(shouldCollect) + .withAskToCollectTelemetry(false)); } /** @@ -370,10 +334,6 @@ public void about() { HelpAction.getMainHelpPageCommand().execute(); } - public JabRefPreferences prefs() { - return prefs; - } - /** * Tears down all things started by JabRef *

@@ -383,18 +343,18 @@ public JabRefPreferences prefs() { * set to true */ private void tearDownJabRef(List filenames) { - // prefs.putBoolean(JabRefPreferences.WINDOW_MAXIMISED, getExtendedState() == Frame.MAXIMIZED_BOTH); - - if (prefs.getBoolean(JabRefPreferences.OPEN_LAST_EDITED)) { - // Here we store the names of all current files. If - // there is no current file, we remove any + if (prefs.getGuiPreferences().shouldOpenLastEdited()) { + // Here we store the names of all current files. If there is no current file, we remove any // previously stored filename. if (filenames.isEmpty()) { - prefs.remove(JabRefPreferences.LAST_EDITED); + prefs.clearEditedFiles(); } else { - prefs.putStringList(JabRefPreferences.LAST_EDITED, filenames); - Path focusedDatabase = getCurrentBasePanel().getBibDatabaseContext().getDatabasePath().orElse(null); - new LastFocusedTabPreferences(prefs).setLastFocusedTab(focusedDatabase); + Path focusedDatabase = getCurrentLibraryTab().getBibDatabaseContext() + .getDatabasePath() + .orElse(null); + prefs.storeGuiPreferences(prefs.getGuiPreferences() + .withLastFilesOpened(filenames) + .withLastFocusedFile(focusedDatabase)); } } @@ -433,12 +393,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) { @@ -446,13 +406,13 @@ public boolean quit() { context.getDBMSSynchronizer().closeSharedDatabase(); context.clearDBMSSynchronizer(); } - GlobalSaveManager.shutdown(context); + AutosaveManager.shutdown(context); BackupManager.shutdown(context); context.getDatabasePath().map(Path::toAbsolutePath).map(Path::toString).ifPresent(filenames::add); } WaitForSaveFinishedDialog waitForSaveFinishedDialog = new WaitForSaveFinishedDialog(dialogService); - waitForSaveFinishedDialog.showAndWait(getBasePanelList()); + waitForSaveFinishedDialog.showAndWait(getLibraryTabs()); // Good bye! tearDownJabRef(filenames); @@ -499,10 +459,11 @@ public void changed(ObservableValue observable, Boolean oldVa } private void setDividerPosition() { - splitPane.setDividerPositions(prefs.getDouble(JabRefPreferences.SIDE_PANE_WIDTH)); + splitPane.setDividerPositions(prefs.getGuiPreferences().getSidePaneWidth()); if (!splitPane.getDividers().isEmpty()) { EasyBind.subscribe(splitPane.getDividers().get(0).positionProperty(), - position -> prefs.putDouble(JabRefPreferences.SIDE_PANE_WIDTH, position.doubleValue())); + position -> prefs.storeGuiPreferences(prefs.getGuiPreferences() + .withSidePaneWidth(position.doubleValue()))); } } @@ -520,7 +481,7 @@ private Node createToolbar() { new HBox( factory.createIconButton(StandardActions.NEW_LIBRARY, new NewDatabaseAction(this, prefs)), - factory.createIconButton(StandardActions.OPEN_LIBRARY, new OpenDatabaseAction(this)), + factory.createIconButton(StandardActions.OPEN_LIBRARY, new OpenDatabaseAction(this, prefs, dialogService)), factory.createIconButton(StandardActions.SAVE_LIBRARY, new SaveAction(SaveAction.SaveMethod.SAVE, this, stateManager))), leftSpacer, @@ -530,8 +491,8 @@ private Node createToolbar() { rightSpacer, new HBox( - factory.createIconButton(StandardActions.NEW_ARTICLE, new NewEntryAction(this, StandardEntryType.Article, dialogService, Globals.prefs, stateManager)), - factory.createIconButton(StandardActions.NEW_ENTRY, new NewEntryAction(this, dialogService, Globals.prefs, stateManager)), + factory.createIconButton(StandardActions.NEW_ARTICLE, new NewEntryAction(this, StandardEntryType.Article, dialogService, prefs, stateManager)), + factory.createIconButton(StandardActions.NEW_ENTRY, new NewEntryAction(this, dialogService, prefs, stateManager)), factory.createIconButton(StandardActions.NEW_ENTRY_FROM_PLAIN_TEXT, new ExtractBibtexAction(stateManager)), factory.createIconButton(StandardActions.DELETE_ENTRY, new EditAction(StandardActions.DELETE_ENTRY, this, stateManager)) ), @@ -581,61 +542,59 @@ 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() { - sidePaneManager = new SidePaneManager(Globals.prefs, this); + sidePaneManager = new SidePaneManager(prefs, this, dialogService, stateManager); sidePane = sidePaneManager.getPane(); tabbedPane = new TabPane(); 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); } }); @@ -650,29 +609,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(); } @@ -680,11 +640,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(); } /** @@ -694,15 +654,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 */ @@ -711,14 +662,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")); @@ -742,12 +685,12 @@ private MenuBar createMenu() { new SeparatorMenuItem(), factory.createSubMenu(StandardActions.IMPORT, - factory.createMenuItem(StandardActions.IMPORT_INTO_CURRENT_LIBRARY, new ImportCommand(this, false, stateManager)), - factory.createMenuItem(StandardActions.IMPORT_INTO_NEW_LIBRARY, new ImportCommand(this, true, stateManager))), + factory.createMenuItem(StandardActions.IMPORT_INTO_CURRENT_LIBRARY, new ImportCommand(this, false, prefs, stateManager)), + factory.createMenuItem(StandardActions.IMPORT_INTO_NEW_LIBRARY, new ImportCommand(this, true, prefs, stateManager))), factory.createSubMenu(StandardActions.EXPORT, - factory.createMenuItem(StandardActions.EXPORT_ALL, new ExportCommand(this, false, Globals.prefs)), - factory.createMenuItem(StandardActions.EXPORT_SELECTED, new ExportCommand(this, true, Globals.prefs)), + factory.createMenuItem(StandardActions.EXPORT_ALL, new ExportCommand(this, false, prefs)), + factory.createMenuItem(StandardActions.EXPORT_SELECTED, new ExportCommand(this, true, prefs)), factory.createMenuItem(StandardActions.SAVE_SELECTED_AS_PLAIN_BIBTEX, new SaveAction(SaveAction.SaveMethod.SAVE_SELECTED, this, stateManager))), new SeparatorMenuItem(), @@ -773,13 +716,13 @@ private MenuBar createMenu() { factory.createMenuItem(StandardActions.COPY, new EditAction(StandardActions.COPY, this, stateManager)), factory.createSubMenu(StandardActions.COPY_MORE, - factory.createMenuItem(StandardActions.COPY_TITLE, new CopyMoreAction(StandardActions.COPY_TITLE, dialogService, stateManager, Globals.clipboardManager, prefs)), - factory.createMenuItem(StandardActions.COPY_KEY, new CopyMoreAction(StandardActions.COPY_KEY, dialogService, stateManager, Globals.clipboardManager, prefs)), - factory.createMenuItem(StandardActions.COPY_CITE_KEY, new CopyMoreAction(StandardActions.COPY_CITE_KEY, dialogService, stateManager, Globals.clipboardManager, prefs)), - factory.createMenuItem(StandardActions.COPY_KEY_AND_TITLE, new CopyMoreAction(StandardActions.COPY_KEY_AND_TITLE, dialogService, stateManager, Globals.clipboardManager, prefs)), - factory.createMenuItem(StandardActions.COPY_KEY_AND_LINK, new CopyMoreAction(StandardActions.COPY_KEY_AND_LINK, dialogService, stateManager, Globals.clipboardManager, prefs)), - factory.createMenuItem(StandardActions.COPY_CITATION_PREVIEW, new CopyCitationAction(CitationStyleOutputFormat.HTML, dialogService, stateManager, Globals.clipboardManager, prefs.getPreviewPreferences())), - factory.createMenuItem(StandardActions.EXPORT_SELECTED_TO_CLIPBOARD, new ExportToClipboardAction(this, dialogService, Globals.exportFactory, Globals.clipboardManager, Globals.TASK_EXECUTOR))), + factory.createMenuItem(StandardActions.COPY_TITLE, new CopyMoreAction(StandardActions.COPY_TITLE, dialogService, stateManager, Globals.getClipboardManager(), prefs)), + factory.createMenuItem(StandardActions.COPY_KEY, new CopyMoreAction(StandardActions.COPY_KEY, dialogService, stateManager, Globals.getClipboardManager(), prefs)), + factory.createMenuItem(StandardActions.COPY_CITE_KEY, new CopyMoreAction(StandardActions.COPY_CITE_KEY, dialogService, stateManager, Globals.getClipboardManager(), prefs)), + factory.createMenuItem(StandardActions.COPY_KEY_AND_TITLE, new CopyMoreAction(StandardActions.COPY_KEY_AND_TITLE, dialogService, stateManager, Globals.getClipboardManager(), prefs)), + factory.createMenuItem(StandardActions.COPY_KEY_AND_LINK, new CopyMoreAction(StandardActions.COPY_KEY_AND_LINK, dialogService, stateManager, Globals.getClipboardManager(), prefs)), + factory.createMenuItem(StandardActions.COPY_CITATION_PREVIEW, new CopyCitationAction(CitationStyleOutputFormat.HTML, dialogService, stateManager, Globals.getClipboardManager(), prefs.getPreviewPreferences())), + factory.createMenuItem(StandardActions.EXPORT_SELECTED_TO_CLIPBOARD, new ExportToClipboardAction(this, dialogService, Globals.exportFactory, Globals.getClipboardManager(), Globals.TASK_EXECUTOR))), factory.createMenuItem(StandardActions.PASTE, new EditAction(StandardActions.PASTE, this, stateManager)), @@ -794,23 +737,23 @@ private MenuBar createMenu() { factory.createMenuItem(StandardActions.MASS_SET_FIELDS, new MassSetFieldsAction(stateManager, dialogService, undoManager)) ); - if (Globals.prefs.getSpecialFieldsPreferences().isSpecialFieldsEnabled()) { + if (prefs.getSpecialFieldsPreferences().isSpecialFieldsEnabled()) { edit.getItems().addAll( 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)), @@ -830,7 +773,7 @@ private MenuBar createMenu() { new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.SET_FILE_LINKS, new AutoLinkFilesAction(this, prefs, stateManager, undoManager, Globals.TASK_EXECUTOR)), + factory.createMenuItem(StandardActions.SET_FILE_LINKS, new AutoLinkFilesAction(dialogService, prefs, stateManager, undoManager, Globals.TASK_EXECUTOR)), new SeparatorMenuItem(), @@ -843,7 +786,7 @@ private MenuBar createMenu() { ); Menu lookupIdentifiers = factory.createSubMenu(StandardActions.LOOKUP_DOC_IDENTIFIER); - for (IdFetcher fetcher : WebFetchers.getIdFetchers(Globals.prefs.getImportFormatPreferences())) { + for (IdFetcher fetcher : WebFetchers.getIdFetchers(prefs.getImportFormatPreferences())) { LookupIdentifierAction identifierAction = new LookupIdentifierAction<>(this, fetcher, stateManager, undoManager); lookupIdentifiers.getItems().add(factory.createMenuItem(identifierAction.getAction(), identifierAction)); } @@ -854,7 +797,7 @@ private MenuBar createMenu() { new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.FIND_UNLINKED_FILES, new FindUnlinkedFilesAction(this, stateManager)) + factory.createMenuItem(StandardActions.FIND_UNLINKED_FILES, new FindUnlinkedFilesAction(dialogService, prefs, undoManager, stateManager)) ); // PushToApplication @@ -873,8 +816,13 @@ private MenuBar createMenu() { new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.SEND_AS_EMAIL, new SendAsEMailAction(dialogService, stateManager)), + factory.createMenuItem(StandardActions.SEND_AS_EMAIL, new SendAsEMailAction(dialogService, prefs, stateManager)), pushToApplicationMenuItem + // Disabled until PR #7126 can be merged + // new SeparatorMenuItem(), + // factory.createMenuItem(StandardActions.START_SYSTEMATIC_LITERATURE_REVIEW, + // new StartLiteratureReviewAction(this, Globals.getFileUpdateMonitor(), prefs.getWorkingDir(), + // taskExecutor, prefs, prefs.getImportFormatPreferences(), prefs.getSavePreferences())) ); SidePaneComponent webSearch = sidePaneManager.getComponent(SidePaneType.WEB_SEARCH); @@ -910,7 +858,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()), @@ -1025,21 +972,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 +996,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); + tabbedPane.getTabs().forEach(tab -> { + LibraryTab libraryTab = (LibraryTab) tab; + if (libraryTab.getDatabase() != null) { + DefaultTaskExecutor.runInJavaFXThread(libraryTab::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(""); - } - } - 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) { @@ -1114,72 +1014,65 @@ private ContextMenu createTabContextMenu(KeyBindingRepository keyBindingReposito ActionFactory factory = new ActionFactory(keyBindingRepository); contextMenu.getItems().addAll( + factory.createMenuItem(StandardActions.OPEN_DATABASE_FOLDER, new OpenDatabaseFolder()), + factory.createMenuItem(StandardActions.OPEN_CONSOLE, new OpenConsoleAction(stateManager)), + new SeparatorMenuItem(), factory.createMenuItem(StandardActions.CLOSE_LIBRARY, new CloseDatabaseAction()), factory.createMenuItem(StandardActions.CLOSE_OTHER_LIBRARIES, new CloseOthersDatabaseAction()), - factory.createMenuItem(StandardActions.CLOSE_ALL_LIBRARIES, new CloseAllDatabaseAction()), - new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.OPEN_DATABASE_FOLDER, new OpenDatabaseFolder()), - factory.createMenuItem(StandardActions.OPEN_CONSOLE, new OpenConsoleAction(stateManager)) - ); + factory.createMenuItem(StandardActions.CLOSE_ALL_LIBRARIES, new CloseAllDatabaseAction()) + ); 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); + libraryTab.getDataLoadingTask().cancel(); 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(); - GlobalSaveManager manager = GlobalSaveManager.start(stateManager, prefs, Globals.entryTypesManager); + 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) { return ((context.getLocation() == DatabaseLocation.SHARED) || - ((context.getLocation() == DatabaseLocation.LOCAL) && Globals.prefs.getBoolean(JabRefPreferences.LOCAL_AUTO_SAVE))) + ((context.getLocation() == DatabaseLocation.LOCAL) && Globals.prefs.shouldAutosave())) && context.getDatabasePath().isPresent(); } @@ -1190,7 +1083,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()); @@ -1203,47 +1096,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); @@ -1257,8 +1120,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, prefs, Globals.entryTypesManager); if (saveAction.save()) { return true; } @@ -1274,17 +1136,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; } @@ -1292,31 +1154,27 @@ private void closeTab(BasePanel panel) { context.convertToLocalDatabase(); context.getDBMSSynchronizer().closeSharedDatabase(); context.clearDBMSSynchronizer(); - removeTab(panel); + removeTab(libraryTab); } else { - removeTab(panel); + removeTab(libraryTab); } - - GlobalSaveManager.shutdown(context); + 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() { - return new OpenDatabaseAction(this); + return new OpenDatabaseAction(this, prefs, dialogService); } public SidePaneManager getSidePaneManager() { @@ -1354,7 +1212,7 @@ private class CloseDatabaseAction extends SimpleCommand { @Override public void execute() { - closeTab(getCurrentBasePanel()); + closeTab(getCurrentLibraryTab()); } } @@ -1366,11 +1224,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); } } } @@ -1381,8 +1239,7 @@ private class CloseAllDatabaseAction extends SimpleCommand { @Override public void execute() { for (Tab tab : tabbedPane.getTabs()) { - BasePanel basePanel = getBasePanel(tab); - closeTab(basePanel); + closeTab((LibraryTab) tab); } } } @@ -1406,7 +1263,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..ed62b405ad5 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; @@ -24,7 +26,7 @@ import org.jabref.logic.shared.DatabaseNotSupportedException; import org.jabref.logic.shared.exception.InvalidDBMSConnectionPropertiesException; import org.jabref.logic.shared.exception.NotASharedDatabaseException; -import org.jabref.preferences.JabRefPreferences; +import org.jabref.preferences.GuiPreferences; import impl.org.controlsfx.skin.DecorationPane; import org.slf4j.Logger; @@ -60,10 +62,11 @@ private void openWindow(Stage mainStage) { LOGGER.debug("Initializing frame"); mainFrame.init(); + GuiPreferences guiPreferences = Globals.prefs.getGuiPreferences(); // Restore window location and/or maximised state - if (Globals.prefs.getBoolean(JabRefPreferences.WINDOW_MAXIMISED)) { + if (guiPreferences.isWindowMaximised()) { mainStage.setMaximized(true); - } else if (Screen.getScreens().size() == 1 && isWindowPositionOutOfBounds()) { + } else if ((Screen.getScreens().size() == 1) && isWindowPositionOutOfBounds()) { // corrects the Window, if its outside of the mainscreen LOGGER.debug("The Jabref Window is outside the Main Monitor\n"); mainStage.setX(0); @@ -72,10 +75,10 @@ private void openWindow(Stage mainStage) { mainStage.setHeight(768); correctedWindowPos = true; } else { - mainStage.setX(Globals.prefs.getDouble(JabRefPreferences.POS_X)); - mainStage.setY(Globals.prefs.getDouble(JabRefPreferences.POS_Y)); - mainStage.setWidth(Globals.prefs.getDouble(JabRefPreferences.SIZE_X)); - mainStage.setHeight(Globals.prefs.getDouble(JabRefPreferences.SIZE_Y)); + mainStage.setX(guiPreferences.getPositionX()); + mainStage.setY(guiPreferences.getPositionY()); + mainStage.setWidth(guiPreferences.getSizeX()); + mainStage.setHeight(guiPreferences.getSizeY()); } debugLogWindowState(mainStage); @@ -86,6 +89,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); @@ -114,7 +121,7 @@ private void openWindow(Stage mainStage) { private void openDatabases() { // If the option is enabled, open the last edited libraries, if any. - if (!isBlank && Globals.prefs.getBoolean(JabRefPreferences.OPEN_LAST_EDITED)) { + if (!isBlank && Globals.prefs.getGuiPreferences().shouldOpenLastEdited()) { openLastEditedDatabases(); } @@ -130,7 +137,10 @@ private void openDatabases() { .findFirst() .flatMap(ParserResult::getFile) .map(File::getAbsolutePath) - .orElse(Globals.prefs.get(JabRefPreferences.LAST_FOCUSED)); + .orElse(Globals.prefs.getGuiPreferences() + .getLastFocusedFile() + .toAbsolutePath() + .toString()); // Add all bibDatabases databases to the frame: boolean first = false; @@ -195,38 +205,43 @@ 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"); } private void saveWindowState(Stage mainStage) { - Globals.prefs.putBoolean(JabRefPreferences.WINDOW_MAXIMISED, mainStage.isMaximized()); - Globals.prefs.putDouble(JabRefPreferences.POS_X, mainStage.getX()); - Globals.prefs.putDouble(JabRefPreferences.POS_Y, mainStage.getY()); - Globals.prefs.putDouble(JabRefPreferences.SIZE_X, mainStage.getWidth()); - Globals.prefs.putDouble(JabRefPreferences.SIZE_Y, mainStage.getHeight()); + GuiPreferences preferences = Globals.prefs.getGuiPreferences(); + Globals.prefs.storeGuiPreferences(new GuiPreferences( + mainStage.getX(), + mainStage.getY(), + mainStage.getWidth(), + mainStage.getHeight(), + mainStage.isMaximized(), + preferences.shouldOpenLastEdited(), + preferences.getLastFilesOpened(), + preferences.getLastFocusedFile(), + preferences.getSidePaneWidth())); debugLogWindowState(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"); - 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); } } @@ -236,14 +251,16 @@ private void debugLogWindowState(Stage mainStage) { * @return outbounds */ private boolean isWindowPositionOutOfBounds() { - return !Screen.getPrimary().getBounds().contains(Globals.prefs.getDouble(JabRefPreferences.POS_X), Globals.prefs.getDouble(JabRefPreferences.POS_Y)); + return !Screen.getPrimary().getBounds().contains( + Globals.prefs.getGuiPreferences().getPositionX(), + Globals.prefs.getGuiPreferences().getPositionY()); } private void openLastEditedDatabases() { - if (Globals.prefs.get(JabRefPreferences.LAST_EDITED) == null) { + List lastFiles = Globals.prefs.getGuiPreferences().getLastFilesOpened(); + if (lastFiles.isEmpty()) { return; } - List lastFiles = Globals.prefs.getStringList(JabRefPreferences.LAST_EDITED); for (String fileName : lastFiles) { File dbFile = new File(fileName); diff --git a/src/main/java/org/jabref/gui/JabRefMain.java b/src/main/java/org/jabref/gui/JabRefMain.java index 826c561587f..ae0a6acbf96 100644 --- a/src/main/java/org/jabref/gui/JabRefMain.java +++ b/src/main/java/org/jabref/gui/JabRefMain.java @@ -4,7 +4,6 @@ import javafx.application.Application; import javafx.application.Platform; -import javafx.scene.control.Alert; import javafx.stage.Stage; import org.jabref.cli.ArgumentProcessor; @@ -19,12 +18,11 @@ import org.jabref.logic.protectedterms.ProtectedTermsLoader; import org.jabref.logic.remote.RemotePreferences; import org.jabref.logic.remote.client.RemoteClient; -import org.jabref.logic.util.BuildInfo; -import org.jabref.logic.util.JavaVersion; import org.jabref.logic.util.OS; import org.jabref.migrations.PreferencesMigrations; import org.jabref.model.database.BibDatabaseMode; import org.jabref.preferences.JabRefPreferences; +import org.jabref.preferences.PreferencesService; import org.apache.commons.cli.ParseException; import org.slf4j.Logger; @@ -47,8 +45,6 @@ public static void main(String[] args) { @Override public void start(Stage mainStage) { try { - // Fail on unsupported Java versions - ensureCorrectJavaVersion(); FallbackExceptionHandler.installExceptionHandler(); // Init preferences @@ -92,51 +88,6 @@ public void stop() { Globals.shutdownThreadPools(); } - /** - * Tests if we are running an acceptable Java and terminates JabRef when we are sure the version is not supported. - * This test uses the requirements for the Java version as specified in gradle.build. It is possible to - * define a minimum version including the built number and to indicate whether Java 9 can be used (which it currently - * can't). It tries to compare this version number to the version of the currently running JVM. The check is - * optimistic and will rather return true even if we could not exactly determine the version. - *

- * Note: Users with a very old version like 1.6 will not profit from this since class versions are incompatible and - * JabRef won't even start. Currently, JabRef won't start with Java 9 either, but the warning that it cannot be used - * with this version is helpful anyway to prevent users to update from an old 1.8 directly to version 9. Additionally, - * we soon might have a JabRef that does start with Java 9 but is not perfectly compatible. Therefore, we should leave - * the Java 9 check alive. - */ - private static void ensureCorrectJavaVersion() { - // Check if we are running an acceptable version of Java - final BuildInfo buildInfo = Globals.BUILD_INFO; - JavaVersion checker = new JavaVersion(); - final boolean java9Fail = !buildInfo.allowJava9 && checker.isJava9(); - final boolean versionFail = !checker.isAtLeast(buildInfo.minRequiredJavaVersion); - - if (java9Fail || versionFail) { - StringBuilder versionError = new StringBuilder( - Localization.lang("Your current Java version (%0) is not supported. Please install version %1 or higher.", - checker.getJavaVersion(), - buildInfo.minRequiredJavaVersion)); - - versionError.append("\n"); - versionError.append(Localization.lang("Your Java Runtime Environment is located at %0.", checker.getJavaInstallationDirectory())); - - if (!buildInfo.allowJava9) { - versionError.append("\n"); - versionError.append(Localization.lang("Note that currently, JabRef does not run with Java 9.")); - } - - FXDialog alert = new FXDialog(Alert.AlertType.ERROR, Localization.lang("Error"), true); - alert.setHeaderText(null); - alert.setContentText(versionError.toString()); - - // We exit on Java 9 error since this will definitely not work - if (java9Fail) { - System.exit(0); - } - } - } - private static boolean handleMultipleAppInstances(String[] args) { RemotePreferences remotePreferences = Globals.prefs.getRemotePreferences(); if (remotePreferences.useRemoteServer()) { @@ -159,27 +110,27 @@ private static boolean handleMultipleAppInstances(String[] args) { return true; } - private static void applyPreferences(JabRefPreferences preferences) { + private static void applyPreferences(PreferencesService preferences) { // Read list(s) of journal names and abbreviations - Globals.journalAbbreviationRepository = JournalAbbreviationLoader.loadRepository(Globals.prefs.getJournalAbbreviationPreferences()); + Globals.journalAbbreviationRepository = JournalAbbreviationLoader.loadRepository(preferences.getJournalAbbreviationPreferences()); // Build list of Import and Export formats - Globals.IMPORT_FORMAT_READER.resetImportFormats(Globals.prefs.getImportFormatPreferences(), - Globals.prefs.getXmpPreferences(), Globals.getFileUpdateMonitor()); - Globals.entryTypesManager.addCustomOrModifiedTypes(preferences.loadBibEntryTypes(BibDatabaseMode.BIBTEX), - preferences.loadBibEntryTypes(BibDatabaseMode.BIBLATEX)); + Globals.IMPORT_FORMAT_READER.resetImportFormats(preferences.getImportFormatPreferences(), + preferences.getXmpPreferences(), Globals.getFileUpdateMonitor()); + Globals.entryTypesManager.addCustomOrModifiedTypes(preferences.getBibEntryTypes(BibDatabaseMode.BIBTEX), + preferences.getBibEntryTypes(BibDatabaseMode.BIBLATEX)); Globals.exportFactory = ExporterFactory.create( - Globals.prefs.getCustomExportFormats(Globals.journalAbbreviationRepository), - Globals.prefs.getLayoutFormatterPreferences(Globals.journalAbbreviationRepository), - Globals.prefs.getSavePreferencesForExport(), - Globals.prefs.getXmpPreferences()); + preferences.getCustomExportFormats(Globals.journalAbbreviationRepository), + preferences.getLayoutFormatterPreferences(Globals.journalAbbreviationRepository), + preferences.getSavePreferencesForExport(), + preferences.getXmpPreferences()); // Initialize protected terms loader - Globals.protectedTermsLoader = new ProtectedTermsLoader(Globals.prefs.getProtectedTermsPreferences()); + Globals.protectedTermsLoader = new ProtectedTermsLoader(preferences.getProtectedTermsPreferences()); // Override used newline character with the one stored in the preferences // The preferences return the system newline character sequence as default - OS.NEWLINE = Globals.prefs.get(JabRefPreferences.NEWLINE); + OS.NEWLINE = preferences.getNewLineSeparator().toString(); } private static void configureProxy(ProxyPreferences proxyPreferences) { diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/LibraryTab.java similarity index 58% rename from src/main/java/org/jabref/gui/BasePanel.java rename to src/main/java/org/jabref/gui/LibraryTab.java index e98e641a73b..eb2706ad36c 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -1,24 +1,34 @@ 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.ProgressIndicator; import javafx.scene.control.SplitPane; -import javafx.scene.layout.StackPane; +import javafx.scene.control.Tab; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.BorderPane; import org.jabref.gui.autocompleter.AutoCompletePreferences; import org.jabref.gui.autocompleter.PersonNameSuggestionProvider; import org.jabref.gui.autocompleter.SuggestionProviders; import org.jabref.gui.collab.DatabaseChangeMonitor; import org.jabref.gui.collab.DatabaseChangePane; +import org.jabref.gui.dialogs.AutosaveUiManager; import org.jabref.gui.entryeditor.EntryEditor; import org.jabref.gui.externalfiletype.ExternalFileTypes; +import org.jabref.gui.importer.actions.OpenDatabaseAction; import org.jabref.gui.maintable.MainTable; import org.jabref.gui.maintable.MainTableDataModel; import org.jabref.gui.specialfields.SpecialFieldDatabaseChangeListener; @@ -27,13 +37,18 @@ import org.jabref.gui.undo.UndoableFieldChange; import org.jabref.gui.undo.UndoableInsertEntries; import org.jabref.gui.undo.UndoableRemoveEntries; +import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.DefaultTaskExecutor; +import org.jabref.logic.autosaveandbackup.AutosaveManager; +import org.jabref.logic.autosaveandbackup.BackupManager; import org.jabref.logic.citationstyle.CitationStyleCache; +import org.jabref.logic.importer.ParserResult; import org.jabref.logic.l10n.Localization; import org.jabref.logic.pdf.FileAnnotationCache; 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 +60,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,15 +68,15 @@ 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; + private BibDatabaseContext bibDatabaseContext; + private MainTableDataModel tableModel; - private final CitationStyleCache citationStyleCache; - private final FileAnnotationCache annotationCache; + private CitationStyleCache citationStyleCache; + private FileAnnotationCache annotationCache; private final JabRefFrame frame; private final CountingUndoManager undoManager; @@ -69,41 +84,50 @@ public class BasePanel extends StackPane { private final SidePaneManager sidePaneManager; private final ExternalFileTypes externalFileTypes; - private final EntryEditor entryEditor; + private 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(); + // initializing it so we prevent NullPointerException + private BackgroundTask dataLoadingTask = BackgroundTask.wrap(() -> null); - 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 +140,229 @@ 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); + + Platform.runLater(() -> { + EasyBind.subscribe(changedProperty, this::updateTabTitle); + Globals.stateManager.getOpenDatabases().addListener((ListChangeListener) c -> + updateTabTitle(changedProperty.getValue())); + }); } - @Subscribe - public void listen(BibDatabaseContextChangedEvent event) { - this.markBaseChanged(); + public void setDataLoadingTask(BackgroundTask dataLoadingTask) { + this.dataLoadingTask = dataLoadingTask; + } + + public BackgroundTask getDataLoadingTask() { + return dataLoadingTask; + } + + /* The layout to display in the tab when it's loading*/ + public Node createLoadingAnimationLayout() { + ProgressIndicator progressIndicator = new ProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS); + BorderPane pane = new BorderPane(); + pane.setCenter(progressIndicator); + + return pane; + } + + public void onDatabaseLoadingStarted() { + Node loadingLayout = createLoadingAnimationLayout(); + getMainTable().placeholderProperty().setValue(loadingLayout); + + frame.addTab(this, true); + } + + public void onDatabaseLoadingSucceed(ParserResult result) { + BibDatabaseContext context = result.getDatabaseContext(); + OpenDatabaseAction.performPostOpenActions(this, result); + + feedData(context); + // a temporary workaround to update groups pane + Globals.stateManager.activeDatabaseProperty().bind( + EasyBind.map(frame.getTabbedPane().getSelectionModel().selectedItemProperty(), + selectedTab -> Optional.ofNullable(selectedTab) + .map(tab -> (LibraryTab) tab) + .map(LibraryTab::getBibDatabaseContext))); + } + + public void onDatabaseLoadingFailed(Exception ex) { + String title = Localization.lang("Connection error"); + String content = String.format("%s\n\n%s", ex.getMessage(), Localization.lang("A local copy will be opened.")); + + dialogService.showErrorDialogAndWait(title, content, ex); + } + + public void feedData(BibDatabaseContext bibDatabaseContext) { + cleanUp(); + + this.bibDatabaseContext = Objects.requireNonNull(bibDatabaseContext); + + bibDatabaseContext.getDatabase().registerListener(this); + bibDatabaseContext.getMetaData().registerListener(this); + + this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferencesService, Globals.stateManager); + citationStyleCache = new CitationStyleCache(bibDatabaseContext); + annotationCache = new FileAnnotationCache(bibDatabaseContext, preferencesService.getFilePreferences()); + + setupMainPanel(); + setupAutoCompletion(); + + this.getDatabase().registerListener(new SearchListener()); + this.getDatabase().registerListener(new EntriesRemovedListener()); + + // ensure that at each addition of a new entry, the entry is added to the groups interface + this.bibDatabaseContext.getDatabase().registerListener(new GroupTreeListener()); + // ensure that all entry changes mark the panel as changed + this.bibDatabaseContext.getDatabase().registerListener(this); + + this.getDatabase().registerListener(new UpdateTimestampListener(preferencesService)); + + this.entryEditor = new EntryEditor(this, externalFileTypes); + + Platform.runLater(() -> { + EasyBind.subscribe(changedProperty, this::updateTabTitle); + Globals.stateManager.getOpenDatabases().addListener((ListChangeListener) c -> + updateTabTitle(changedProperty.getValue())); + }); + + if (isDatabaseReadyForAutoSave(bibDatabaseContext)) { + AutosaveManager autoSaver = AutosaveManager.start(bibDatabaseContext); + autoSaver.registerListener(new AutosaveUiManager(this)); + } + + BackupManager.start(this.bibDatabaseContext, Globals.entryTypesManager, Globals.prefs); + } + + private boolean isDatabaseReadyForAutoSave(BibDatabaseContext context) { + return ((context.getLocation() == DatabaseLocation.SHARED) || + ((context.getLocation() == DatabaseLocation.LOCAL) && preferencesService.shouldAutosave())) + && + context.getDatabasePath().isPresent(); } /** - * 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.shouldAutosave(); - public String getTabTitle() { - StringBuilder title = new StringBuilder(); - DatabaseLocation databaseLocation = this.bibDatabaseContext.getLocation(); - boolean isAutosaveEnabled = Globals.prefs.getBoolean(JabRefPreferences.LOCAL_AUTO_SAVE); + DatabaseLocation databaseLocation = bibDatabaseContext.getLocation(); + Optional file = bibDatabaseContext.getDatabasePath(); - 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")); + StringBuilder tabTitle = new StringBuilder(); + StringBuilder toolTipText = new StringBuilder(); - if (getDatabase().hasEntries()) { + 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); + + // 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(); + DefaultTaskExecutor.runInJavaFXThread(() -> { + 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 +377,6 @@ public JabRefFrame frame() { return frame; } - public void output(String s) { - dialogService.notify(s); - } - /** * Removes the selected entries from the database * @@ -210,8 +405,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(); @@ -239,9 +434,8 @@ public void insertEntry(final BibEntry bibEntry) { } /** - * This method is called from JabRefFrame when the user wants to create a new entry or entries. - * It is necessary when the user would expect the added entry or one of the added entries - * to be selected in the entry editor + * This method is called from JabRefFrame when the user wants to create a new entry or entries. It is necessary when + * the user would expect the added entry or one of the added entries to be selected in the entry editor * * @param entries The new entries. */ @@ -255,14 +449,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 +478,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 +495,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 +518,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 +529,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 +543,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 +575,8 @@ private void showBottomPane(BasePanelMode newMode) { splitPane.getItems().add(1, pane); } mode = newMode; - adjustSplitter(); + + splitPane.setDividerPositions(preferencesService.getEntryEditorPreferences().getDividerPosition()); } /** @@ -409,16 +595,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 +631,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 +648,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 +665,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 +678,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())); } } @@ -526,6 +688,8 @@ private void saveDividerLocation(Number position) { */ public void cleanUp() { changeMonitor.ifPresent(DatabaseChangeMonitor::unregister); + AutosaveManager.shutdown(bibDatabaseContext); + BackupManager.shutdown(bibDatabaseContext); } /** @@ -546,14 +710,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; } @@ -599,11 +755,11 @@ public FileAnnotationCache getAnnotationCache() { public void resetChangeMonitorAndChangePane() { changeMonitor.ifPresent(DatabaseChangeMonitor::unregister); - changeMonitor = Optional.of(new DatabaseChangeMonitor(bibDatabaseContext, Globals.getFileUpdateMonitor(), Globals.TASK_EXECUTOR)); + changeMonitor = Optional.of(new DatabaseChangeMonitor(bibDatabaseContext, Globals.getFileUpdateMonitor(), Globals.TASK_EXECUTOR, preferencesService)); changePane = new DatabaseChangePane(splitPane, bibDatabaseContext, changeMonitor.get()); - this.getChildren().setAll(changePane); + this.setContent(changePane); } public void copy() { @@ -618,6 +774,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 +810,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())); } @@ -665,4 +847,21 @@ public void listen(EntriesRemovedEvent removedEntriesEvent) { DefaultTaskExecutor.runInJavaFXThread(() -> frame.getGlobalSearchBar().performSearch()); } } + + public static class Factory { + public LibraryTab createLibraryTab(JabRefFrame frame, PreferencesService preferencesService, Path file, BackgroundTask dataLoadingTask) { + BibDatabaseContext context = new BibDatabaseContext(); + context.setDatabasePath(file); + + LibraryTab newTab = new LibraryTab(frame, preferencesService, context, ExternalFileTypes.getInstance()); + newTab.setDataLoadingTask(dataLoadingTask); + + dataLoadingTask.onRunning(newTab::onDatabaseLoadingStarted) + .onSuccess(newTab::onDatabaseLoadingSucceed) + .onFailure(newTab::onDatabaseLoadingFailed) + .executeWith(Globals.TASK_EXECUTOR); + + return newTab; + } + } } diff --git a/src/main/java/org/jabref/gui/SendAsEMailAction.java b/src/main/java/org/jabref/gui/SendAsEMailAction.java index c54dc6fa60e..373d8f7e6f6 100644 --- a/src/main/java/org/jabref/gui/SendAsEMailAction.java +++ b/src/main/java/org/jabref/gui/SendAsEMailAction.java @@ -19,7 +19,7 @@ import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; -import org.jabref.preferences.JabRefPreferences; +import org.jabref.preferences.PreferencesService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,11 +38,13 @@ public class SendAsEMailAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(SendAsEMailAction.class); - private DialogService dialogService; - private StateManager stateManager; + private final DialogService dialogService; + private final PreferencesService preferencesService; + private final StateManager stateManager; - public SendAsEMailAction(DialogService dialogService, StateManager stateManager) { + public SendAsEMailAction(DialogService dialogService, PreferencesService preferencesService, StateManager stateManager) { this.dialogService = dialogService; + this.preferencesService = preferencesService; this.stateManager = stateManager; this.executable.bind(ActionHelper.needsEntriesSelected(stateManager)); @@ -74,7 +76,7 @@ private String sendEmail() throws Exception { List entries = stateManager.getSelectedEntries(); // write the entries using sw, which is used later to form the email content - BibEntryWriter bibtexEntryWriter = new BibEntryWriter(new FieldWriter(Globals.prefs.getFieldWriterPreferences()), Globals.entryTypesManager); + BibEntryWriter bibtexEntryWriter = new BibEntryWriter(new FieldWriter(preferencesService.getFieldWriterPreferences()), Globals.entryTypesManager); for (BibEntry entry : entries) { try { @@ -88,9 +90,9 @@ private String sendEmail() throws Exception { // open folders is needed to indirectly support email programs, which cannot handle // the unofficial "mailto:attachment" property - boolean openFolders = JabRefPreferences.getInstance().getBoolean(JabRefPreferences.OPEN_FOLDERS_OF_ATTACHED_FILES); + boolean openFolders = preferencesService.getExternalApplicationsPreferences().shouldAutoOpenEmailAttachmentsFolder(); - List fileList = FileUtil.getListOfLinkedFiles(entries, databaseContext.getFileDirectories(Globals.prefs.getFilePreferences())); + List fileList = FileUtil.getListOfLinkedFiles(entries, databaseContext.getFileDirectories(preferencesService.getFilePreferences())); for (Path path : fileList) { attachments.add(path.toAbsolutePath().toString()); if (openFolders) { @@ -104,7 +106,7 @@ private String sendEmail() throws Exception { String mailTo = "?Body=".concat(rawEntries.getBuffer().toString()); mailTo = mailTo.concat("&Subject="); - mailTo = mailTo.concat(JabRefPreferences.getInstance().get(JabRefPreferences.EMAIL_SUBJECT)); + mailTo = mailTo.concat(preferencesService.getExternalApplicationsPreferences().getEmailSubject()); for (String path : attachments) { mailTo = mailTo.concat("&Attachment=\"").concat(path); mailTo = mailTo.concat("\""); diff --git a/src/main/java/org/jabref/gui/SidePaneManager.java b/src/main/java/org/jabref/gui/SidePaneManager.java index 472589044e3..f87c948b57c 100644 --- a/src/main/java/org/jabref/gui/SidePaneManager.java +++ b/src/main/java/org/jabref/gui/SidePaneManager.java @@ -11,7 +11,7 @@ import org.jabref.gui.importer.fetcher.WebSearchPane; import org.jabref.gui.openoffice.OpenOfficeSidePanel; import org.jabref.logic.openoffice.OpenOfficePreferences; -import org.jabref.preferences.JabRefPreferences; +import org.jabref.preferences.PreferencesService; /** * Manages which {@link SidePaneComponent}s are shown. @@ -21,20 +21,20 @@ public class SidePaneManager { private final SidePane sidePane; private final Map components = new LinkedHashMap<>(); private final List visibleComponents = new LinkedList<>(); - private final JabRefPreferences preferences; + private final PreferencesService preferencesService; - public SidePaneManager(JabRefPreferences preferences, JabRefFrame frame) { - this.preferences = preferences; + public SidePaneManager(PreferencesService preferencesService, JabRefFrame frame, DialogService dialogService, StateManager stateManager) { + this.preferencesService = preferencesService; this.sidePane = new SidePane(); - OpenOfficePreferences openOfficePreferences = preferences.getOpenOfficePreferences(); + OpenOfficePreferences openOfficePreferences = preferencesService.getOpenOfficePreferences(); Stream.of( - new GroupSidePane(this, preferences, frame.getDialogService()), - new WebSearchPane(this, preferences, frame), - new OpenOfficeSidePanel(this, preferences, frame)) + new GroupSidePane(this, preferencesService, dialogService), + new WebSearchPane(this, preferencesService, dialogService, stateManager), + new OpenOfficeSidePanel(this, preferencesService, frame)) .forEach(pane -> components.put(pane.getType(), pane)); - if (preferences.getBoolean(JabRefPreferences.GROUP_SIDEPANE_VISIBLE)) { + if (preferencesService.getSidePanePreferences().isGroupsPaneVisible()) { show(SidePaneType.GROUPS); } @@ -42,7 +42,7 @@ public SidePaneManager(JabRefPreferences preferences, JabRefFrame frame) { show(SidePaneType.OPEN_OFFICE); } - if (preferences.getBoolean(JabRefPreferences.WEB_SEARCH_VISIBLE)) { + if (preferencesService.getSidePanePreferences().isWebSearchPaneVisible()) { show(SidePaneType.WEB_SEARCH); } @@ -87,7 +87,7 @@ public void show(SidePaneType type) { visibleComponents.add(component); // Sort the visible components by their preferred position - visibleComponents.sort(new PreferredIndexSort()); + visibleComponents.sort(new PreferredIndexSort(preferencesService)); updateView(); @@ -114,7 +114,7 @@ public void hide(SidePaneType type) { * so that we show components at the preferred position next time. */ private void updatePreferredPositions() { - Map preferredPositions = preferences.getSidePanePreferredPositions(); + Map preferredPositions = preferencesService.getSidePanePreferences().getPreferredPositions(); // Use the currently shown positions of all visible components int index = 0; @@ -122,7 +122,7 @@ private void updatePreferredPositions() { preferredPositions.put(comp.getType(), index); index++; } - preferences.storeSidePanePreferredPositions(preferredPositions); + preferencesService.storeSidePanePreferences(preferencesService.getSidePanePreferences().withPreferredPositions(preferredPositions)); } /** @@ -164,12 +164,7 @@ public void moveDown(SidePaneComponent comp) { */ private void updateView() { sidePane.setComponents(visibleComponents); - - if (visibleComponents.isEmpty()) { - sidePane.setVisible(false); - } else { - sidePane.setVisible(true); - } + sidePane.setVisible(!visibleComponents.isEmpty()); } /** @@ -179,8 +174,8 @@ private static class PreferredIndexSort implements Comparator private final Map preferredPositions; - public PreferredIndexSort() { - preferredPositions = Globals.prefs.getSidePanePreferredPositions(); + public PreferredIndexSort(PreferencesService preferencesService) { + preferredPositions = preferencesService.getSidePanePreferences().getPreferredPositions(); } @Override 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..f6db57b4727 --- /dev/null +++ b/src/main/java/org/jabref/gui/StartLiteratureReviewAction.java @@ -0,0 +1,90 @@ +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.crawler.git.GitHandler; +import org.jabref.logic.exporter.SavePreferences; +import org.jabref.logic.importer.ImportFormatPreferences; +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.PreferencesService; + +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; + private final PreferencesService preferencesService; + private final ImportFormatPreferences importFormatPreferneces; + private final SavePreferences savePreferences; + + public StartLiteratureReviewAction(JabRefFrame frame, FileUpdateMonitor fileUpdateMonitor, Path standardWorkingDirectory, TaskExecutor taskExecutor, PreferencesService preferencesService, ImportFormatPreferences importFormatPreferences, SavePreferences savePreferences) { + this.frame = frame; + this.dialogService = frame.getDialogService(); + this.fileUpdateMonitor = fileUpdateMonitor; + this.workingDirectory = getInitialDirectory(standardWorkingDirectory); + this.taskExecutor = taskExecutor; + this.preferencesService = preferencesService; + this.importFormatPreferneces = importFormatPreferences; + this.savePreferences = savePreferences; + } + + @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(), new GitHandler(studyDefinitionFile.get().getParent()), fileUpdateMonitor, importFormatPreferneces, savePreferences, 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, preferencesService, dialogService).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/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..7af68d7a415 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().shouldIncludeTimestamps()) { + 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/actions/ActionHelper.java b/src/main/java/org/jabref/gui/actions/ActionHelper.java index acda7c7c1df..4225ca8f2bf 100644 --- a/src/main/java/org/jabref/gui/actions/ActionHelper.java +++ b/src/main/java/org/jabref/gui/actions/ActionHelper.java @@ -12,6 +12,7 @@ import javafx.scene.control.TabPane; import org.jabref.gui.StateManager; +import org.jabref.logic.shared.DatabaseLocation; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.Field; @@ -19,6 +20,7 @@ import org.jabref.preferences.PreferencesService; import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.EasyBinding; public class ActionHelper { @@ -26,6 +28,11 @@ public static BooleanExpression needsDatabase(StateManager stateManager) { return stateManager.activeDatabaseProperty().isPresent(); } + public static BooleanExpression needsSharedDatabase(StateManager stateManager) { + EasyBinding binding = EasyBind.map(stateManager.activeDatabaseProperty(), context -> context.filter(c -> c.getLocation() == DatabaseLocation.SHARED).isPresent()); + return BooleanExpression.booleanExpression(binding); + } + public static BooleanExpression needsEntriesSelected(StateManager stateManager) { return Bindings.isNotEmpty(stateManager.getSelectedEntries()); } 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/actions/StandardActions.java b/src/main/java/org/jabref/gui/actions/StandardActions.java index e3880399355..1bd099016f4 100644 --- a/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -88,8 +88,9 @@ 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_FOLDER(Localization.lang("Open folder"), Localization.lang("Open folder"), IconTheme.JabRefIcons.FOLDER, KeyBinding.OPEN_FOLDER), OPEN_FILE(Localization.lang("Open file"), Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE), OPEN_CONSOLE(Localization.lang("Open terminal here"), Localization.lang("Open terminal here"), IconTheme.JabRefIcons.CONSOLE, KeyBinding.OPEN_CONSOLE), COPY_LINKED_FILES(Localization.lang("Copy linked files to folder...")), @@ -104,7 +105,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")), @@ -140,6 +140,16 @@ public enum StandardActions implements Action { CLEANUP_ENTRIES(Localization.lang("Cleanup entries"), IconTheme.JabRefIcons.CLEANUP_ENTRIES, KeyBinding.CLEANUP), SET_FILE_LINKS(Localization.lang("Automatically set file links"), KeyBinding.AUTOMATICALLY_LINK_FILES), + EDIT_FILE_LINK(Localization.lang("Edit"), IconTheme.JabRefIcons.EDIT, KeyBinding.EDIT_ENTRY), + DOWNLOAD_FILE(Localization.lang("Download file"), IconTheme.JabRefIcons.DOWNLOAD_FILE), + RENAME_FILE_TO_PATTERN(Localization.lang("Rename file to defined pattern"), IconTheme.JabRefIcons.AUTO_RENAME), + RENAME_FILE_TO_NAME(Localization.lang("Rename file to a given name"), IconTheme.JabRefIcons.RENAME, KeyBinding.REPLACE_STRING), + MOVE_FILE_TO_FOLDER(Localization.lang("Move file to file directory"), IconTheme.JabRefIcons.MOVE_TO_FOLDER), + MOVE_FILE_TO_FOLDER_AND_RENAME(Localization.lang("Move file to file directory and rename file")), + COPY_FILE_TO_FOLDER(Localization.lang("Copy linked file to folder..."), IconTheme.JabRefIcons.COPY_TO_FOLDER, KeyBinding.COPY), + REMOVE_LINK(Localization.lang("Remove link"), IconTheme.JabRefIcons.REMOVE_LINK), + DELETE_FILE(Localization.lang("Permanently delete local file"), IconTheme.JabRefIcons.DELETE_FILE, KeyBinding.DELETE_ENTRY), + HELP(Localization.lang("Online help"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), HELP_KEY_PATTERNS(Localization.lang("Help on key patterns"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), HELP_REGEX_SEARCH(Localization.lang("Help on regular expression search"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), diff --git a/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java b/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java index 518b09efc2b..7280deb9819 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()) { @@ -91,9 +91,9 @@ private void parseActionPerformed() { @FXML private void browseButtonClicked() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.AUX) - .withDefaultExtension(StandardFileType.AUX) - .withInitialDirectory(preferences.getWorkingDir()).build(); + .addExtensionFilter(StandardFileType.AUX) + .withDefaultExtension(StandardFileType.AUX) + .withInitialDirectory(preferences.getWorkingDir()).build(); dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(file -> auxFileField.setText(file.toAbsolutePath().toString())); } } diff --git a/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java b/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java index a7181ba3ae1..a7c4d76b8b8 100644 --- a/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java +++ b/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java @@ -15,15 +15,21 @@ 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; import org.jabref.model.entry.BibEntry; import org.jabref.model.util.FileUpdateMonitor; -import org.jabref.preferences.JabRefPreferences; +import org.jabref.preferences.PreferencesService; + +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; @@ -32,20 +38,20 @@ public class BibtexExtractorViewModel { public BibtexExtractorViewModel(BibDatabaseContext bibdatabaseContext, DialogService dialogService, - JabRefPreferences jabRefPreferences, + PreferencesService preferencesService, FileUpdateMonitor fileUpdateMonitor, TaskExecutor taskExecutor, UndoManager undoManager, StateManager stateManager) { this.dialogService = dialogService; - currentCitationfetcher = new GrobidCitationFetcher(jabRefPreferences.getImportFormatPreferences()); + currentCitationfetcher = new GrobidCitationFetcher(preferencesService.getImportFormatPreferences()); this.taskExecutor = taskExecutor; this.importHandler = new ImportHandler( dialogService, bibdatabaseContext, ExternalFileTypes.getInstance(), - jabRefPreferences, + preferencesService, fileUpdateMonitor, undoManager, stateManager); @@ -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/gui/bibtexextractor/ExtractBibtexDialog.java b/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexDialog.java index 86f22264ee9..b7c33fdf15d 100644 --- a/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexDialog.java +++ b/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexDialog.java @@ -16,7 +16,7 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.util.FileUpdateMonitor; -import org.jabref.preferences.JabRefPreferences; +import org.jabref.preferences.PreferencesService; import com.airhacks.afterburner.views.ViewLoader; @@ -34,6 +34,7 @@ public class ExtractBibtexDialog extends BaseDialog { @Inject private FileUpdateMonitor fileUpdateMonitor; @Inject private TaskExecutor taskExecutor; @Inject private UndoManager undoManager; + @Inject private PreferencesService preferencesService; public ExtractBibtexDialog() { ViewLoader.view(this) @@ -52,7 +53,7 @@ public ExtractBibtexDialog() { @FXML private void initialize() { BibDatabaseContext database = stateManager.getActiveDatabase().orElseThrow(() -> new NullPointerException("Database null")); - this.viewModel = new BibtexExtractorViewModel(database, dialogService, JabRefPreferences.getInstance(), fileUpdateMonitor, taskExecutor, undoManager, stateManager); + this.viewModel = new BibtexExtractorViewModel(database, dialogService, preferencesService, fileUpdateMonitor, taskExecutor, undoManager, stateManager); input.textProperty().bindBidirectional(viewModel.inputTextProperty()); } } 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..f1d53b19015 100644 --- a/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java +++ b/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java @@ -14,7 +14,6 @@ import org.jabref.logic.citationkeypattern.CitationKeyGenerator; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntry; -import org.jabref.preferences.JabRefPreferences; public class GenerateCitationKeyAction extends SimpleCommand { @@ -51,14 +50,15 @@ public void execute() { } public static boolean confirmOverwriteKeys(DialogService dialogService) { - if (Globals.prefs.getBoolean(JabRefPreferences.WARN_BEFORE_OVERWRITING_KEY)) { + if (Globals.prefs.getCitationKeyPatternPreferences().shouldWarnBeforeOverwriteCiteKey()) { return dialogService.showConfirmationDialogWithOptOutAndWait( Localization.lang("Overwrite keys"), Localization.lang("One or more keys will be overwritten. Continue?"), Localization.lang("Overwrite keys"), Localization.lang("Cancel"), Localization.lang("Disable this confirmation dialog"), - optOut -> Globals.prefs.putBoolean(JabRefPreferences.WARN_BEFORE_OVERWRITING_KEY, !optOut)); + optOut -> Globals.prefs.storeCitationKeyPatternPreferences( + Globals.prefs.getCitationKeyPatternPreferences().withWarnBeforeOverwriteCiteKey(!optOut))); } else { // Always overwrite keys by default return true; @@ -67,7 +67,7 @@ public static boolean confirmOverwriteKeys(DialogService dialogService) { private void checkOverwriteKeysChosen() { // We don't want to generate keys for entries which already have one thus remove the entries - if (Globals.prefs.getBoolean(JabRefPreferences.AVOID_OVERWRITING_KEY)) { + if (Globals.prefs.getCitationKeyPatternPreferences().shouldAvoidOverwriteCiteKey()) { entries.removeIf(BibEntry::hasCitationKey); // if we're going to override some citation keys warn the user about it } else if (entries.parallelStream().anyMatch(BibEntry::hasCitationKey)) { @@ -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..ad7dab14172 100644 --- a/src/main/java/org/jabref/gui/cleanup/CleanupAction.java +++ b/src/main/java/org/jabref/gui/cleanup/CleanupAction.java @@ -18,7 +18,6 @@ import org.jabref.model.FieldChange; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; -import org.jabref.preferences.JabRefPreferences; import org.jabref.preferences.PreferencesService; public class CleanupAction extends SimpleCommand { @@ -31,7 +30,7 @@ public class CleanupAction extends SimpleCommand { private boolean isCanceled; private int modifiedEntriesCount; - public CleanupAction(JabRefFrame frame, JabRefPreferences preferences, DialogService dialogService, StateManager stateManager) { + public CleanupAction(JabRefFrame frame, PreferencesService preferences, DialogService dialogService, StateManager stateManager) { this.frame = frame; this.preferences = preferences; this.dialogService = dialogService; @@ -63,14 +62,14 @@ public void execute() { preferences.getFilePreferences()).showAndWait(); chosenPreset.ifPresent(preset -> { - if (preset.isRenamePDFActive() && Globals.prefs.getBoolean(JabRefPreferences.ASK_AUTO_NAMING_PDFS_AGAIN)) { + if (preset.isRenamePDFActive() && preferences.getAutoLinkPreferences().shouldAskAutoNamingPdfs()) { boolean confirmed = dialogService.showConfirmationDialogWithOptOutAndWait(Localization.lang("Autogenerate PDF Names"), Localization.lang("Auto-generating PDF-Names does not support undo. Continue?"), Localization.lang("Autogenerate PDF Names"), Localization.lang("Cancel"), Localization.lang("Disable this confirmation dialog"), - optOut -> Globals.prefs.putBoolean(JabRefPreferences.ASK_AUTO_NAMING_PDFS_AGAIN, !optOut)); - + optOut -> preferences.storeAutoLinkPreferences(preferences.getAutoLinkPreferences() + .withAskAutoNamingPdfs(!optOut))); if (!confirmed) { isCanceled = true; return; @@ -108,8 +107,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/collab/ChangeScanner.java b/src/main/java/org/jabref/gui/collab/ChangeScanner.java index f09de7c2bdd..4806d156a86 100644 --- a/src/main/java/org/jabref/gui/collab/ChangeScanner.java +++ b/src/main/java/org/jabref/gui/collab/ChangeScanner.java @@ -5,7 +5,6 @@ import java.util.Collections; import java.util.List; -import org.jabref.gui.Globals; import org.jabref.logic.bibtex.comparator.BibDatabaseDiff; import org.jabref.logic.bibtex.comparator.BibEntryDiff; import org.jabref.logic.bibtex.comparator.BibStringDiff; @@ -14,6 +13,7 @@ import org.jabref.logic.importer.ParserResult; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.util.DummyFileUpdateMonitor; +import org.jabref.preferences.PreferencesService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,9 +22,11 @@ public class ChangeScanner { private static final Logger LOGGER = LoggerFactory.getLogger(ChangeScanner.class); private final BibDatabaseContext database; + private final PreferencesService preferencesService; - public ChangeScanner(BibDatabaseContext database) { + public ChangeScanner(BibDatabaseContext database, PreferencesService preferencesService) { this.database = database; + this.preferencesService = preferencesService; } public List scanForChanges() { @@ -37,14 +39,14 @@ public List scanForChanges() { // Parse the modified file // Important: apply all post-load actions - ImportFormatPreferences importFormatPreferences = Globals.prefs.getImportFormatPreferences(); + ImportFormatPreferences importFormatPreferences = preferencesService.getImportFormatPreferences(); ParserResult result = OpenDatabase.loadDatabase(database.getDatabasePath().get(), importFormatPreferences, new DummyFileUpdateMonitor()); BibDatabaseContext databaseOnDisk = result.getDatabaseContext(); // Start looking at changes. BibDatabaseDiff differences = BibDatabaseDiff.compare(database, databaseOnDisk); differences.getMetaDataDifferences().ifPresent(diff -> { - changes.add(new MetaDataChangeViewModel(diff, Globals.prefs)); + changes.add(new MetaDataChangeViewModel(diff, preferencesService)); diff.getGroupDifferences().ifPresent(groupDiff -> changes.add(new GroupChangeViewModel(groupDiff))); }); differences.getPreambleDifferences().ifPresent(diff -> changes.add(new PreambleChangeViewModel(diff))); diff --git a/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java b/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java index 6181129601b..fe1f892ca62 100644 --- a/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java +++ b/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java @@ -9,6 +9,7 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.util.FileUpdateListener; import org.jabref.model.util.FileUpdateMonitor; +import org.jabref.preferences.PreferencesService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,11 +22,16 @@ public class DatabaseChangeMonitor implements FileUpdateListener { private final FileUpdateMonitor fileMonitor; private final List listeners; private final TaskExecutor taskExecutor; + private final PreferencesService preferencesService; - public DatabaseChangeMonitor(BibDatabaseContext database, FileUpdateMonitor fileMonitor, TaskExecutor taskExecutor) { + public DatabaseChangeMonitor(BibDatabaseContext database, + FileUpdateMonitor fileMonitor, + TaskExecutor taskExecutor, + PreferencesService preferencesService) { this.database = database; this.fileMonitor = fileMonitor; this.taskExecutor = taskExecutor; + this.preferencesService = preferencesService; this.listeners = new ArrayList<>(); this.database.getDatabasePath().ifPresent(path -> { @@ -40,7 +46,7 @@ public DatabaseChangeMonitor(BibDatabaseContext database, FileUpdateMonitor file @Override public void fileUpdated() { // File on disk has changed, thus look for notable changes and notify listeners in case there are such changes - ChangeScanner scanner = new ChangeScanner(database); + ChangeScanner scanner = new ChangeScanner(database, preferencesService); BackgroundTask.wrap(scanner::scanForChanges) .onSuccess(changes -> { if (!changes.isEmpty()) { diff --git a/src/main/java/org/jabref/gui/collab/MetaDataChangeViewModel.java b/src/main/java/org/jabref/gui/collab/MetaDataChangeViewModel.java index 1f9b2e6785e..83b6351fd78 100644 --- a/src/main/java/org/jabref/gui/collab/MetaDataChangeViewModel.java +++ b/src/main/java/org/jabref/gui/collab/MetaDataChangeViewModel.java @@ -8,14 +8,14 @@ import org.jabref.logic.bibtex.comparator.MetaDataDiff; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; -import org.jabref.preferences.JabRefPreferences; +import org.jabref.preferences.PreferencesService; class MetaDataChangeViewModel extends DatabaseChangeViewModel { private final MetaDataDiff metaDataDiff; - private final JabRefPreferences preferences; + private final PreferencesService preferences; - public MetaDataChangeViewModel(MetaDataDiff metaDataDiff, JabRefPreferences preferences) { + public MetaDataChangeViewModel(MetaDataDiff metaDataDiff, PreferencesService preferences) { super(Localization.lang("Metadata change")); this.metaDataDiff = metaDataDiff; this.preferences = preferences; 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/copyfiles/CopyFilesAction.java b/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java index 24c28e38097..07ee7470d6c 100644 --- a/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java +++ b/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java @@ -14,7 +14,6 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; -import org.jabref.preferences.JabRefPreferences; import static org.jabref.gui.actions.ActionHelper.needsDatabase; import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; @@ -46,7 +45,7 @@ public void execute() { List entries = stateManager.getSelectedEntries(); DirectoryDialogConfiguration dirDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(Path.of(Globals.prefs.get(JabRefPreferences.EXPORT_WORKING_DIRECTORY))) + .withInitialDirectory(Globals.prefs.getImportExportPreferences().getExportWorkingDirectory()) .build(); Optional exportPath = dialogService.showDirectorySelectionDialog(dirDialogConfiguration); exportPath.ifPresent(path -> { diff --git a/src/main/java/org/jabref/gui/copyfiles/CopySingleFileAction.java b/src/main/java/org/jabref/gui/copyfiles/CopySingleFileAction.java index 0981032c29a..72ec5c64266 100644 --- a/src/main/java/org/jabref/gui/copyfiles/CopySingleFileAction.java +++ b/src/main/java/org/jabref/gui/copyfiles/CopySingleFileAction.java @@ -4,48 +4,57 @@ import java.util.Optional; import java.util.function.BiFunction; +import javafx.beans.binding.Bindings; + import org.jabref.gui.DialogService; -import org.jabref.gui.Globals; +import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.util.DirectoryDialogConfiguration; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.LinkedFile; import org.jabref.model.util.OptionalUtil; -import org.jabref.preferences.JabRefPreferences; +import org.jabref.preferences.PreferencesService; -public class CopySingleFileAction { +public class CopySingleFileAction extends SimpleCommand { private final LinkedFile linkedFile; private final DialogService dialogService; private final BibDatabaseContext databaseContext; - private final BiFunction resolvePathFilename = (path, file) -> { - return path.resolve(file.getFileName()); - }; + private final PreferencesService preferencesService; + + private final BiFunction resolvePathFilename = (path, file) -> path.resolve(file.getFileName()); - public CopySingleFileAction(LinkedFile linkedFile, DialogService dialogService, BibDatabaseContext databaseContext) { + public CopySingleFileAction(LinkedFile linkedFile, DialogService dialogService, BibDatabaseContext databaseContext, PreferencesService preferencesService) { this.linkedFile = linkedFile; this.dialogService = dialogService; this.databaseContext = databaseContext; + this.preferencesService = preferencesService; + + this.executable.bind(Bindings.createBooleanBinding( + () -> !linkedFile.isOnlineLink() + && linkedFile.findIn(databaseContext, preferencesService.getFilePreferences()).isPresent(), + linkedFile.linkProperty())); } - public void copyFile() { + @Override + public void execute() { DirectoryDialogConfiguration dirDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(Path.of(Globals.prefs.get(JabRefPreferences.EXPORT_WORKING_DIRECTORY))) + .withInitialDirectory(preferencesService.getWorkingDir()) .build(); Optional exportPath = dialogService.showDirectorySelectionDialog(dirDialogConfiguration); exportPath.ifPresent(this::copyFileToDestination); } private void copyFileToDestination(Path exportPath) { - Optional fileToExport = linkedFile.findIn(databaseContext, Globals.prefs.getFilePreferences()); + Optional fileToExport = linkedFile.findIn(databaseContext, preferencesService.getFilePreferences()); Optional newPath = OptionalUtil.combine(Optional.of(exportPath), fileToExport, resolvePathFilename); if (newPath.isPresent()) { Path newFile = newPath.get(); - boolean success = FileUtil.copyFile(fileToExport.get(), newFile, false); + boolean success = fileToExport.isPresent() && FileUtil.copyFile(fileToExport.get(), newFile, false); if (success) { - dialogService.showInformationDialogAndWait(Localization.lang("Copy linked file"), Localization.lang("Sucessfully copied file to %0", newPath.map(Path::getParent).map(Path::toString).orElse(""))); + dialogService.showInformationDialogAndWait(Localization.lang("Copy linked file"), Localization.lang("Successfully copied file to %0.", newPath.map(Path::getParent).map(Path::toString).orElse(""))); } else { dialogService.showErrorDialogAndWait(Localization.lang("Copy linked file"), Localization.lang("Could not copy file to %0, maybe the file is already existing?", newPath.map(Path::getParent).map(Path::toString).orElse(""))); } diff --git a/src/main/java/org/jabref/gui/customentrytypes/CustomEntryTypeDialogViewModel.java b/src/main/java/org/jabref/gui/customentrytypes/CustomEntryTypeDialogViewModel.java index d7a034c3225..ac2ba0b697b 100644 --- a/src/main/java/org/jabref/gui/customentrytypes/CustomEntryTypeDialogViewModel.java +++ b/src/main/java/org/jabref/gui/customentrytypes/CustomEntryTypeDialogViewModel.java @@ -178,8 +178,8 @@ public void removeField(FieldViewModel focusedItem) { public void resetAllCustomEntryTypes() { entryTypesManager.clearAllCustomEntryTypes(mode); preferencesService.clearBibEntryTypes(mode); - entryTypesManager.addCustomOrModifiedTypes(preferencesService.loadBibEntryTypes(BibDatabaseMode.BIBTEX), - preferencesService.loadBibEntryTypes(BibDatabaseMode.BIBLATEX)); + entryTypesManager.addCustomOrModifiedTypes(preferencesService.getBibEntryTypes(BibDatabaseMode.BIBTEX), + preferencesService.getBibEntryTypes(BibDatabaseMode.BIBLATEX)); } public void apply() { @@ -199,9 +199,9 @@ public void apply() { entryTypesManager.removeCustomOrModifiedEntryType(entryType, mode); } - preferencesService.saveCustomEntryTypes(entryTypesManager); + preferencesService.storeCustomEntryTypes(entryTypesManager); // Reload types from preferences to make sure any modifications are present when reopening the dialog - entryTypesManager.addCustomOrModifiedTypes(preferencesService.loadBibEntryTypes(BibDatabaseMode.BIBTEX), - preferencesService.loadBibEntryTypes(BibDatabaseMode.BIBLATEX)); + entryTypesManager.addCustomOrModifiedTypes(preferencesService.getBibEntryTypes(BibDatabaseMode.BIBTEX), + preferencesService.getBibEntryTypes(BibDatabaseMode.BIBLATEX)); } } diff --git a/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java b/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java index eb12f33a2f2..9e7fab8c93a 100644 --- a/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java +++ b/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java @@ -27,7 +27,6 @@ import org.jabref.model.entry.identifier.DOI; import org.jabref.model.entry.identifier.Eprint; import org.jabref.model.util.FileHelper; -import org.jabref.preferences.JabRefPreferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -157,19 +156,19 @@ private static void openExternalFilePlatformIndependent(Optional { + 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 diff --git a/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java b/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java index 0fcf665ac25..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,22 +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; - public AutosaveUiManager(BasePanel panel) { - this.panel = panel; + private final LibraryTab libraryTab; + + public AutosaveUiManager(LibraryTab libraryTab) { + this.libraryTab = libraryTab; } @Subscribe - public void listen(@SuppressWarnings("unused") AutosaveEvent event) { + public void listen(AutosaveEvent event) { try { - SaveDatabaseAction saveAction = new SaveDatabaseAction(panel, Globals.prefs, Globals.entryTypesManager); - saveAction.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/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); } 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/CopyDoiUrlAction.java b/src/main/java/org/jabref/gui/edit/CopyDoiUrlAction.java index 459df55a595..e97b62571b1 100644 --- a/src/main/java/org/jabref/gui/edit/CopyDoiUrlAction.java +++ b/src/main/java/org/jabref/gui/edit/CopyDoiUrlAction.java @@ -27,7 +27,7 @@ public void execute() { Optional urlOptional = DOI.parse(identifier).map(DOI::getURIAsASCIIString); if (urlOptional.isPresent()) { - Globals.clipboardManager.setContent(urlOptional.get()); + Globals.getClipboardManager().setContent(urlOptional.get()); JabRefGUI.getMainFrame().getDialogService().notify(Localization.lang("The link has been copied to the clipboard.")); } else { JabRefGUI.getMainFrame().getDialogService().notify(Localization.lang("Invalid DOI: '%0'.", identifier)); diff --git a/src/main/java/org/jabref/gui/edit/CopyMoreAction.java b/src/main/java/org/jabref/gui/edit/CopyMoreAction.java index 9ef0f292594..d3b4138c4d1 100644 --- a/src/main/java/org/jabref/gui/edit/CopyMoreAction.java +++ b/src/main/java/org/jabref/gui/edit/CopyMoreAction.java @@ -20,7 +20,6 @@ import org.jabref.logic.util.OS; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; -import org.jabref.preferences.JabRefPreferences; import org.jabref.preferences.PreferencesService; import org.slf4j.Logger; @@ -52,24 +51,12 @@ public void execute() { } switch (action) { - case COPY_TITLE: - copyTitle(); - break; - case COPY_KEY: - copyKey(); - break; - case COPY_CITE_KEY: - copyCiteKey(); - break; - case COPY_KEY_AND_TITLE: - copyKeyAndTitle(); - break; - case COPY_KEY_AND_LINK: - copyKeyAndLink(); - break; - default: - LOGGER.info("Unknown copy command."); - break; + case COPY_TITLE -> copyTitle(); + case COPY_KEY -> copyKey(); + case COPY_CITE_KEY -> copyCiteKey(); + case COPY_KEY_AND_TITLE -> copyKeyAndTitle(); + case COPY_KEY_AND_LINK -> copyKeyAndLink(); + default -> LOGGER.info("Unknown copy command."); } } @@ -140,7 +127,7 @@ private void copyCiteKey() { return; } - String citeCommand = Optional.ofNullable(Globals.prefs.get(JabRefPreferences.CITE_COMMAND)) + String citeCommand = Optional.ofNullable(Globals.prefs.getExternalApplicationsPreferences().getCiteCommand()) .filter(cite -> cite.contains("\\")) // must contain \ .orElse("\\cite"); 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/DeprecatedFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java index 883bc98da1e..0c52de20ef8 100644 --- a/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java @@ -9,6 +9,7 @@ import javafx.scene.control.Tooltip; import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; import org.jabref.gui.autocompleter.SuggestionProviders; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.icon.IconTheme; @@ -20,14 +21,23 @@ import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.Field; -import org.jabref.preferences.JabRefPreferences; +import org.jabref.preferences.PreferencesService; public class DeprecatedFieldsTab extends FieldsEditorTab { private final BibEntryTypesManager entryTypesManager; - public DeprecatedFieldsTab(BibDatabaseContext databaseContext, SuggestionProviders suggestionProviders, UndoManager undoManager, DialogService dialogService, JabRefPreferences preferences, BibEntryTypesManager entryTypesManager, ExternalFileTypes externalFileTypes, TaskExecutor taskExecutor, JournalAbbreviationRepository journalAbbreviationRepository) { - super(false, databaseContext, suggestionProviders, undoManager, dialogService, preferences, externalFileTypes, taskExecutor, journalAbbreviationRepository); + public DeprecatedFieldsTab(BibDatabaseContext databaseContext, + SuggestionProviders suggestionProviders, + UndoManager undoManager, + DialogService dialogService, + PreferencesService preferences, + StateManager stateManager, + BibEntryTypesManager entryTypesManager, + ExternalFileTypes externalFileTypes, + TaskExecutor taskExecutor, + JournalAbbreviationRepository journalAbbreviationRepository) { + super(false, databaseContext, suggestionProviders, undoManager, dialogService, preferences, stateManager, externalFileTypes, taskExecutor, journalAbbreviationRepository); this.entryTypesManager = entryTypesManager; setText(Localization.lang("Deprecated fields")); diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index 068e9ecd41d..026d4ba3a5d 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,38 +219,38 @@ void generateCiteKeyButton() { @FXML private void navigateToPreviousEntry() { - panel.selectPreviousEntry(); + libraryTab.selectPreviousEntry(); } @FXML private void navigateToNextEntry() { - panel.selectNextEntry(); + libraryTab.selectNextEntry(); } private List createTabs() { // Preview tab - entryEditorTabs.add(new PreviewTab(databaseContext, dialogService, Globals.prefs, ExternalFileTypes.getInstance())); + entryEditorTabs.add(new PreviewTab(databaseContext, dialogService, preferencesService, stateManager, 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, preferencesService, stateManager, 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, preferencesService, stateManager, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); + entryEditorTabs.add(new OptionalFields2Tab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); + entryEditorTabs.add(new DeprecatedFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, 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, preferencesService, stateManager, 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, preferencesService, stateManager, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); } // Special tabs entryEditorTabs.add(new MathSciNetTab()); - entryEditorTabs.add(new FileAnnotationTab(panel.getAnnotationCache())); - entryEditorTabs.add(new RelatedArticlesTab(this, entryEditorPreferences, dialogService)); + entryEditorTabs.add(new FileAnnotationTab(libraryTab.getAnnotationCache())); + entryEditorTabs.add(new RelatedArticlesTab(this, entryEditorPreferences, preferencesService, dialogService)); // Source tab sourceTab = new SourceTab( @@ -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..440678504f7 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() { @@ -56,7 +59,16 @@ public boolean shouldShowLatexCitationsTab() { return shouldShowLatexCitationsTab; } - public boolean isEnableValidation() { + public boolean shouldEnableValidation() { 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/FieldsEditorTab.java b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java index 9c3d74f2c6a..9d037e5af2f 100644 --- a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java @@ -24,6 +24,7 @@ import javafx.scene.layout.RowConstraints; import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; import org.jabref.gui.autocompleter.SuggestionProviders; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.fieldeditors.FieldEditorFX; @@ -35,7 +36,7 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; -import org.jabref.preferences.JabRefPreferences; +import org.jabref.preferences.PreferencesService; /** * A single tab displayed in the EntryEditor holding several FieldEditors. @@ -46,16 +47,26 @@ abstract class FieldsEditorTab extends EntryEditorTab { private final boolean isCompressed; private final SuggestionProviders suggestionProviders; private final DialogService dialogService; - private final JabRefPreferences preferences; + private final PreferencesService preferences; private final ExternalFileTypes externalFileTypes; private final TaskExecutor taskExecutor; private final JournalAbbreviationRepository journalAbbreviationRepository; + private final StateManager stateManager; private PreviewPanel previewPanel; private final UndoManager undoManager; private Collection fields = new ArrayList<>(); private GridPane gridPane; - public FieldsEditorTab(boolean compressed, BibDatabaseContext databaseContext, SuggestionProviders suggestionProviders, UndoManager undoManager, DialogService dialogService, JabRefPreferences preferences, ExternalFileTypes externalFileTypes, TaskExecutor taskExecutor, JournalAbbreviationRepository journalAbbreviationRepository) { + public FieldsEditorTab(boolean compressed, + BibDatabaseContext databaseContext, + SuggestionProviders suggestionProviders, + UndoManager undoManager, + DialogService dialogService, + PreferencesService preferences, + StateManager stateManager, + ExternalFileTypes externalFileTypes, + TaskExecutor taskExecutor, + JournalAbbreviationRepository journalAbbreviationRepository) { this.isCompressed = compressed; this.databaseContext = Objects.requireNonNull(databaseContext); this.suggestionProviders = Objects.requireNonNull(suggestionProviders); @@ -65,10 +76,11 @@ public FieldsEditorTab(boolean compressed, BibDatabaseContext databaseContext, S this.externalFileTypes = Objects.requireNonNull(externalFileTypes); this.taskExecutor = Objects.requireNonNull(taskExecutor); this.journalAbbreviationRepository = Objects.requireNonNull(journalAbbreviationRepository); + this.stateManager = stateManager; } private static void addColumn(GridPane gridPane, int columnIndex, List