diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 839834cc086..ddcb00c5a40 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,18 +1,8 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/java { "name": "Java", "image": "mcr.microsoft.com/vscode/devcontainers/base:bullseye", - - // Configure tool-specific properties. "customizations": { - // Configure properties specific to VS Code. "vscode": { - // Set *default* container specific settings.json values on container create. - "settings": { - }, - - // Add the IDs of extensions you want installed when the container is created. "extensions": [ "vscjava.vscode-java-pack", "vscjava.vscode-gradle", @@ -22,23 +12,28 @@ } }, - "onCreateCommand": "gradle assemble", + // Source code generation needs to be done before hand-over to VS Code. + // Otherwise, the Java extension will go mad. + "onCreateCommand": "./gradlew testClasses --no-daemon", - // Forward the noVNC port (desktop-lite: https://github.com/devcontainers/features/tree/main/src/desktop-lite) - "forwardPorts": [6080], + // Forward the vncPort and noVNC port. + // They are provided by desktop-lite: + // https://github.com/devcontainers/features/tree/main/src/desktop-lite#options + "forwardPorts": [5901, 6080], // Need to connect as root otherwise we run into issues with gradle. // default option is "vscode". More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "root", "features": { // Adds a lightweight desktop that can be accessed using a VNC viewer or the web - "desktop-lite": "latest", + "ghcr.io/devcontainers/features/desktop-lite:1": {}, - // Install java - "java": { - "version": "20", + // Install java. + // See https://github.com/devcontainers/features/tree/main/src/java#options for details. + "ghcr.io/devcontainers/features/java:1": { + "version": "19.0.2-tem", "installGradle": false, - "jdkDistro": "sem" + "jdkDistro": "tem" } } } diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 783e39dd057..5f136688ca4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -12,8 +12,7 @@ body: attributes: label: JabRef version options: - - "5.9 (latest release)" - - "3.8.2" + - "5.10 (latest release)" - Latest development branch build (please note build date below) - Other (please describe below) description: The version as shown in the about dialog. @@ -42,7 +41,7 @@ body: - type: checkboxes attributes: - label: Checked with the latest development build + label: Checked with the latest development build (copy version output from About dialog) description: | Please always test if the bug is still reproducible in the latest development version. We are constantly improving JabRef and some bugs may already be fixed. If you already use a development version, ensure that you use the latest one. You can download the latest development build at: https://builds.jabref.org/main/ . **Please make a backup of your library before you try out this version.** diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 08445740dd0..5ce48160d2e 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -11,12 +11,10 @@ The title of the PR must not reference an issue, because GitHub does not support - [x] done; [ ] not done / not applicable --> -```[tasklist] -### Compulsory checks +### Mandatory checks - [ ] Change in `CHANGELOG.md` described in a way that is understandable for the average user (if applicable) - [ ] Tests created for changes (if applicable) - [ ] Manually tested changed features in running JabRef (always required) - [ ] Screenshots added in PR description (for UI changes) - [ ] [Checked developer's documentation](https://devdocs.jabref.org/): Is the information available and up to date? If not, I outlined it in this pull request. - [ ] [Checked documentation](https://docs.jabref.org/): Is the information available and up to date? If not, I created an issue at or, even better, I submitted a pull request to the documentation repository. -``` diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2f18e225965..d417016ae16 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ updates: schedule: interval: weekly labels: - - "type: dependencies" + - "dependencies" ignore: - dependency-name: com.microsoft.azure:applicationinsights-core versions: @@ -18,10 +18,10 @@ updates: schedule: interval: weekly labels: - - "type: dependencies" + - "dependencies" - package-ecosystem: "github-actions" directory: "/" schedule: interval: weekly labels: - - "type: dependencies" + - "dependencies" diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 78735f0ed31..886372e30cc 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -1,34 +1,22 @@ -name: Automerge Pull Requests -on: - pull_request: - types: - - opened - - synchronize - - reopened - - labeled +name: Dependabot auto-merge +on: pull_request + +permissions: + contents: write + pull-requests: write + jobs: - automerge: - name: Automerge Dependency Updates + dependabot: runs-on: ubuntu-latest - if: github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]' || contains(github.event.pull_request.labels.*.name, 'dependencies') + if: ${{ github.actor == 'dependabot[bot]' }} steps: - - name: 'Wait for status checks' - id: waitforstatuschecks - uses: "WyriHaximus/github-action-wait-for-status@v1.7.1" - with: - ignoreActions: Automerge Dependabot,Code coverage,Create snapcraft image,Deploy binaries on builds.jabref.org,codecov/project,markdown-link-check - checkInterval: 13 + - name: Approve PR + run: gh pr review --approve "$PR_URL" env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - - name: Auto approve - uses: hmarr/auto-approve-action@v3.2.1 - if: steps.waitforstatuschecks.outputs.status == 'success' - with: - github-token: "${{ secrets.GITHUB_TOKEN }}" - - name: Merge pull requests - uses: pascalgn/automerge-action@v0.15.6 - if: steps.waitforstatuschecks.outputs.status == 'success' + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + - name: Merge PR + run: gh pr merge --auto "$PR_URL" env: - MERGE_METHOD: "merge" - MERGE_LABELS: "" - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/cleanup_pr.yml b/.github/workflows/cleanup_pr.yml index f5da4a713ef..31cdefde5ce 100644 --- a/.github/workflows/cleanup_pr.yml +++ b/.github/workflows/cleanup_pr.yml @@ -7,8 +7,12 @@ on: jobs: cleanup: runs-on: ubuntu-latest - steps: + - name: Cancel deployment run + uses: styfle/cancel-workflow-action@0.11.0 + with: + ignore_sha: true + workflow_id: 9813 # workflow "Deployment" - name: Check secrets presence id: checksecrets shell: bash @@ -20,17 +24,19 @@ jobs: fi env: BUILDJABREFPRIVATEKEY: ${{ secrets.buildJabRefPrivateKey }} - - name: Extract branch name - id: extract_branch - if: ${{ steps.checksecrets.outputs.secretspresent }} - run: | - echo "branch=${{ github.event.pull_request.head.ref }}" >> $GITHUB_OUTPUT - name: Delete folder on builds.jabref.org if: steps.checksecrets.outputs.secretspresent == 'YES' - uses: appleboy/ssh-action@v0.1.10 + uses: appleboy/ssh-action@v1.0.0 with: - script: rm -rf /var/www/builds.jabref.org/www/${{ steps.extract_branch.outputs.branch }} || true + script: rm -rf /var/www/builds.jabref.org/www/pull/${{ github.event.pull_request.number }} || true host: build-upload.jabref.org port: 9922 username: jrrsync key: ${{ secrets.buildJabRefPrivateKey }} + - name: Update PR comment + if: steps.checksecrets.outputs.secretspresent == 'YES' + uses: thollander/actions-comment-pull-request@v2 + with: + comment_tag: download-link + message: The build for this PR is no longer available. Please visit for the latest build. + mode: upsert diff --git a/.github/workflows/deployment-arm64.yml b/.github/workflows/deployment-arm64.yml index 261b7cb63af..38693c6fd8b 100644 --- a/.github/workflows/deployment-arm64.yml +++ b/.github/workflows/deployment-arm64.yml @@ -2,9 +2,15 @@ name: Deployment Release for ARM64 - Run manually! on: workflow_dispatch: + inputs: + notarization: + type: boolean + required: true + default: true push: branches: - arm64mac-release + - updateArm64Notarization env: SpringerNatureAPIKey: ${{ secrets.SpringerNatureAPIKey }} @@ -29,7 +35,6 @@ jobs: - os: self-hosted displayName: macOS (Arm64) suffix: '_arm64' - archivePortable: tar -c -C build/distribution JabRef.app | pigz --rsyncable > build/distribution/JabRef-portable_macos_arm64.tar.gz && rm -R build/distribution/JabRef.app runs-on: ${{ matrix.os }} name: Create installer and portable version for ${{ matrix.displayName }} steps: @@ -37,11 +42,7 @@ jobs: id: checksecrets shell: bash run: | - if [ "$BUILDJABREFPRIVATEKEY" == "" ]; then - echo "secretspresent=NO" >> $GITHUB_OUTPUT - else - echo "secretspresent=YES" >> $GITHUB_OUTPUT - fi + [ -n "$BUILDJABREFPRIVATEKEY" ] || exit 1 env: BUILDJABREFPRIVATEKEY: ${{ secrets.buildJabRefPrivateKey }} - name: Fetch all history for all tags and branches @@ -61,114 +62,119 @@ jobs: java-version: 20 distribution: 'temurin' cache: 'gradle' + - name: setup jdk JabRef-fix mac + shell: bash + run: | + mkdir ${{runner.temp}}/jdk + wget -qO- https://files.jabref.org/jdks/jdk-macos-aarch64.tar.gz | tar xz -C ${{runner.temp}}/jdk + mv ${{runner.temp}}/jdk/jdk-21.jdk ${{runner.temp}}/jdk/jdk-21 + echo "JDK21=${{runner.temp}}/jdk/jdk-21" >> "$GITHUB_ENV" + ls ${{runner.temp}}/jdk + + echo "org.gradle.java.installations.paths=${{runner.temp}}/jdk/jdk-21" >> gradle.properties + echo "org.gradle.java.installations.auto-detect=false" >> gradle.properties + cat gradle.properties + + sed -i'.bak' -e "s/JavaLanguageVersion.of(20)/JavaLanguageVersion.of(21)/" build.gradle - name: Clean up keychain - if: (matrix.os == 'self-hosted') && (steps.checksecrets.outputs.secretspresent == 'YES') run: | - security delete-keychain signing_temp.keychain || true + security delete-keychain signing_temp.keychain ${{runner.temp}}/keychain/notarization.keychain || true - name: Setup OSX key chain on OSX - if: (matrix.os == 'macos-latest' || matrix.os == 'self-hosted') && (steps.checksecrets.outputs.secretspresent == 'YES') uses: apple-actions/import-codesign-certs@v2 with: p12-file-base64: ${{ secrets.OSX_SIGNING_CERT }} p12-password: ${{ secrets.OSX_CERT_PWD }} keychain-password: jabref - name: Setup OSX key chain on OSX for app id cert - if: (matrix.os == 'macos-latest' || matrix.os == 'self-hosted') && (steps.checksecrets.outputs.secretspresent == 'YES') uses: apple-actions/import-codesign-certs@v2 with: p12-file-base64: ${{ secrets.OSX_SIGNING_CERT_APPLICATION }} p12-password: ${{ secrets.OSX_CERT_PWD }} create-keychain: false keychain-password: jabref - - name: Build runtime image - if: (matrix.os != 'macos-latest') || (steps.checksecrets.outputs.secretspresent == 'YES') - run: ./gradlew -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jlinkZip - - name: Build installer - if: (matrix.os != 'macos-latest') || (steps.checksecrets.outputs.secretspresent == 'YES') - run: ./gradlew -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jpackage + - name: Create notarization keychain + run: | + mkdir ${{runner.temp}}/keychain + security create-keychain -p jabref ${{runner.temp}}/keychain/notarization.keychain + security set-keychain-settings ${{runner.temp}}/keychain/notarization.keychain + - name: Prepare merged jars and modules dir (macos) + run: ./gradlew -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" prepareModulesDir + - name: Build dmg (macos) shell: bash - - name: Resign app image for OSX and build dmg - if: (matrix.os == 'macos-latest' || matrix.os == 'self-hosted') && (steps.checksecrets.outputs.secretspresent == 'YES') + run: | + ${{env.JDK21}}/Contents/Home/bin/jpackage \ + --module org.jabref/org.jabref.cli.Launcher \ + --module-path ${{env.JDK21}}/Contents/Home/jmods/:build/jlinkbase/jlinkjars \ + --add-modules org.jabref,org.jabref.merged.module \ + --dest build/distribution \ + --name JabRef \ + --app-version ${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }} \ + --verbose \ + --mac-sign \ + --vendor JabRef \ + --mac-package-identifier Jabref \ + --mac-package-name JabRef \ + --type dmg --mac-signing-key-user-name "JabRef e.V. (6792V39SK3)" \ + --mac-package-signing-prefix org.jabref \ + --mac-entitlements buildres/mac/jabref.entitlements \ + --icon src/main/resources/icons/jabref.icns \ + --resource-dir buildres/mac \ + --file-associations buildres/mac/bibtexAssociations.properties \ + --jlink-options --bind-services + - name: Build pkg (macos) shell: bash run: | - 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 --mac-sign --mac-signing-key-user-name "Developer ID Installer: JabRef e.V. (6792V39SK3)" - 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 --mac-sign --mac-signing-key-user-name "Developer ID Installer: JabRef e.V. (6792V39SK3)" - productsign --sign "Developer ID Installer: JabRef e.V. (6792V39SK3)" "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' || matrix.os == 'self-hosted' ) && contains(fromJson('["refs/tags/", "refs/heads/arm64mac-release"]'), github.ref) && (steps.checksecrets.outputs.secretspresent == 'YES') + ${{env.JDK21}}/Contents/Home/bin/jpackage \ + --module org.jabref/org.jabref.cli.Launcher \ + --module-path ${{env.JDK21}}/Contents/Home/jmods/:build/jlinkbase/jlinkjars \ + --add-modules org.jabref,org.jabref.merged.module \ + --dest build/distribution \ + --name JabRef \ + --app-version ${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }} \ + --verbose \ + --mac-sign \ + --vendor JabRef \ + --mac-package-identifier Jabref \ + --mac-package-name JabRef \ + --type pkg --mac-signing-key-user-name "JabRef e.V. (6792V39SK3)" \ + --mac-package-signing-prefix org.jabref \ + --mac-entitlements buildres/mac/jabref.entitlements \ + --icon src/main/resources/icons/jabref.icns \ + --resource-dir buildres/mac \ + --file-associations buildres/mac/bibtexAssociations.properties \ + --jlink-options --bind-services + - name: Rename files with arm64 suffix as well shell: bash run: | - codesign --timestamp -s "Developer ID Application: JabRef e.V. (6792V39SK3)" --options runtime --entitlements buildres/mac/jabref.entitlements -vvvv --deep "build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.dmg" - xcrun notarytool submit build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.dmg --apple-id "${{ secrets.OSX_NOTARIZATION_APP_USERNAME }}" --team-id "6792V39SK3" --password "${{ secrets.OSX_NOTARIZATION_APP_PWD }}" --wait - xcrun stapler staple "build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.dmg" - spctl -a -t open --context context:primary-signature -vv "build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.dmg" - - name: Rename files (non-macos) - if: (matrix.os != 'macos-latest' && matrix.os != 'self-hosted') - shell: pwsh + mv build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.dmg build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}-arm64.dmg + mv build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.pkg build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}-arm64.pkg + - name: Notarize dmg + if: (startsWith(github.ref, 'refs/tags/') || (${{ inputs.notarization }})) + shell: bash run: | - get-childitem -Path build/distribution/* | rename-item -NewName {$_.name -replace "${{ steps.gitversion.outputs.AssemblySemVer }}","${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}"} - get-childitem -Path build/distribution/* | rename-item -NewName {$_.name -replace "portable","${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}-portable"} - - name: Rename files (arm64) - if: (matrix.os == 'self-hosted') && (steps.checksecrets.outputs.secretspresent == 'YES') - shell: pwsh + xcrun notarytool store-credentials "notarytool-profile" --apple-id "vorstand@jabref.org" --team-id "6792V39SK3" --password "${{ secrets.OSX_NOTARIZATION_APP_PWD }}" --keychain ${{runner.temp}}/keychain/notarization.keychain + xcrun notarytool submit build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}-arm64.dmg --keychain-profile "notarytool-profile" --keychain ${{runner.temp}}/keychain/notarization.keychain --wait + xcrun stapler staple build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}-arm64.dmg + - name: Notarize pkg + if: (startsWith(github.ref, 'refs/tags/') || (${{ inputs.notarization }})) + shell: bash run: | - get-childitem -Path build/distribution/* | rename-item -NewName {$_.name -replace ".dmg", ".${{ matrix.suffix }}.dmg"} - get-childitem -Path build/distribution/* | rename-item -NewName {$_.name -replace ".pkg", ".${{ matrix.suffix }}.pkg"} + xcrun notarytool store-credentials "notarytool-profile" --apple-id "vorstand@jabref.org" --team-id "6792V39SK3" --password "${{ secrets.OSX_NOTARIZATION_APP_PWD }}" --keychain ${{runner.temp}}/keychain/notarization.keychain + xcrun notarytool submit build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}-arm64.pkg --keychain-profile "notarytool-profile" --keychain ${{runner.temp}}/keychain/notarization.keychain --wait + xcrun stapler staple build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}-arm64.pkg + - name: Upload with rsync + if: ${{ !startsWith(github.ref, 'refs/heads/gh-readonly-queue') }} + shell: bash + run: | + mkdir ${{runner.temp}}/sshkey + if [[ -z "${{ secrets.buildJabRefPrivateKey }}" ]]; then + echo "buildJabRefPrivateKey is empty, exiting" + exit 1 + fi + rsync -Pavz --itemize-changes --stats --partial-dir=/tmp/partial --rsync-path="mkdir -p /var/www/builds.jabref.org/www/${{ steps.gitversion.outputs.branchName }} && rsync" -e 'ssh -p 9922 -i ~/.ssh/id_rsa' build/distribution/ jrrsync@build-upload.jabref.org:/var/www/builds.jabref.org/www/${{ steps.gitversion.outputs.branchName }}/ - name: Upload to GitHub workflow artifacts store - if: (matrix.os != 'macos-latest' && matrix.os !='self-hosted') || (steps.checksecrets.outputs.secretspresent == 'YES') + if: ${{ !startsWith(github.ref, 'refs/heads/gh-readonly-queue') }} uses: actions/upload-artifact@v3 with: name: JabRef-${{ matrix.displayName }} path: build/distribution - deploy: - strategy: - fail-fast: false - name: Deploy binaries on builds.jabref.org - runs-on: ubuntu-latest - needs: [build] - steps: - - name: Check secrets presence - id: checksecrets - shell: bash - run: | - if [ "$BUILDJABREFPRIVATEKEY" == "" ]; then - echo "secretspresent=NO" >> $GITHUB_OUTPUT - else - echo "secretspresent=YES" >> $GITHUB_OUTPUT - fi - env: - BUILDJABREFPRIVATEKEY: ${{ secrets.buildJabRefPrivateKey }} - - name: Checkout source - if: steps.checksecrets.outputs.secretspresent == 'YES' - uses: actions/checkout@v3 - - name: Fetch all history for all tags and branches - if: steps.checksecrets.outputs.secretspresent == 'YES' - run: git fetch --prune --unshallow - - name: Install GitVersion - if: steps.checksecrets.outputs.secretspresent == 'YES' - uses: gittools/actions/gitversion/setup@v0.10.2 - with: - versionSpec: '5.x' - - name: Run GitVersion - if: steps.checksecrets.outputs.secretspresent == 'YES' - id: gitversion - uses: gittools/actions/gitversion/execute@v0.10.2 - - name: Get macOSArm64 binaries - if: steps.checksecrets.outputs.secretspresent == 'YES' - uses: actions/download-artifact@master - with: - name: JabRef-macOS (Arm64) - path: build/distribution/ - - name: Deploy to builds.jabref.org - id: deploy - if: steps.checksecrets.outputs.secretspresent == 'YES' - uses: Pendect/action-rsyncer@v2.0.0 - env: - DEPLOY_KEY: ${{ secrets.buildJabRefPrivateKey }} - BRANCH: ${{ steps.gitversion.outputs.branchName }} - with: - flags: -vaz --itemize-changes --stats --partial-dir=/tmp/partial --rsync-path="mkdir -p /var/www/builds.jabref.org/www/${{ steps.gitversion.outputs.branchName }} && rsync" - options: '' - ssh_options: '-p 9922' - src: 'build/distribution/' - dest: jrrsync@build-upload.jabref.org:/var/www/builds.jabref.org/www/${{ steps.gitversion.outputs.branchName }}/ diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index ffca8ea3323..c439988a226 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -2,6 +2,11 @@ name: Deployment on: workflow_dispatch: + inputs: + notarization: + type: boolean + required: false + default: false push: branches: - main @@ -17,6 +22,7 @@ on: - 'docs/**' - 'src/test/**' - 'README.md' + merge_group: env: SpringerNatureAPIKey: ${{ secrets.SpringerNatureAPIKey }} @@ -26,6 +32,7 @@ env: OSXCERT: ${{ secrets.OSX_SIGNING_CERT }} GRADLE_OPTS: -Xmx4g -Dorg.gradle.daemon=false -Dorg.gradle.vfs.watch=false JAVA_OPTS: -Xmx4g + JDK21: "" concurrency: group: ${{ github.ref }} @@ -41,12 +48,14 @@ jobs: - os: ubuntu-latest displayName: linux archivePortable: tar -c -C build/distribution JabRef | pigz --rsyncable > build/distribution/JabRef-portable_linux.tar.gz && rm -R build/distribution/JabRef + eaJdk: https://files.jabref.org/jdks/jdk-linux-x64.tar.gz - os: windows-latest displayName: windows archivePortable: 7z a -r build/distribution/JabRef-portable_windows.zip ./build/distribution/JabRef && rm -R build/distribution/JabRef + eaJDK: https://files.jabref.org/jdks/jdk-windows-x64.zip - os: macos-latest displayName: macOS - archivePortable: brew install pigz && tar -c -C build/distribution JabRef.app | pigz --rsyncable > build/distribution/JabRef-portable_macos.tar.gz && rm -R build/distribution/JabRef.app + eaJDK: https://files.jabref.org/jdks/jdk-macos-x64.tar.gz runs-on: ${{ matrix.os }} name: Create installer and portable version for ${{ matrix.displayName }} steps: @@ -65,6 +74,12 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 + - name: Install pigz and cache (linux) + if: (matrix.os == 'ubuntu-latest') + uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: pigz + version: 1.0 - name: Install GitVersion uses: gittools/actions/gitversion/setup@v0.10.2 with: @@ -78,14 +93,57 @@ jobs: java-version: 20 distribution: 'temurin' cache: 'gradle' - - name: Setup OSX key chain on OSX + - name: setup jdk JabRef-fix (windows) + if: (matrix.os == 'windows-latest') + shell: bash + run: | + mkdir ${{runner.temp}}\jdk + curl -kLsS ${{matrix.eaJDK}} -o temp.zip && 7z x temp.zip -o"$(cygpath -u "$RUNNER_TEMP\jdk")" + echo "JDK21=${{runner.temp}}\jdk\jdk-21" >> "$GITHUB_ENV" + ls "$(cygpath -u "$RUNNER_TEMP\jdk\jdk-21")" + + echo "org.gradle.java.installations.paths=${{runner.temp}}\jdk\jdk-21" | sed "s/\\\\/\\\\\\\\/g" >> gradle.properties + echo "org.gradle.java.installations.auto-detect=false" >> gradle.properties + cat gradle.properties + + sed -i "s/JavaLanguageVersion.of(20)/JavaLanguageVersion.of(21)/" build.gradle + - name: setup jdk JabRef-fix (ubuntu) + if: (matrix.os == 'ubuntu-latest') + shell: bash + run: | + mkdir ${{runner.temp}}/jdk + wget -qO- ${{matrix.eaJDK}} | tar xz -C ${{runner.temp}}/jdk + echo "JDK21=${{runner.temp}}/jdk/jdk-21" >> "$GITHUB_ENV" + ls ${{runner.temp}}/jdk + + echo "org.gradle.java.installations.paths=${{runner.temp}}/jdk/jdk-21" >> gradle.properties + echo "org.gradle.java.installations.auto-detect=false" >> gradle.properties + cat gradle.properties + + sed -i "s/JavaLanguageVersion.of(20)/JavaLanguageVersion.of(21)/" build.gradle + - name: setup jdk JabRef-fix (macos) + if: (matrix.os == 'macos-latest') + shell: bash + run: | + mkdir ${{runner.temp}}/jdk + wget -qO- ${{matrix.eaJDK}} | tar xz -C ${{runner.temp}}/jdk + mv ${{runner.temp}}/jdk/jdk-21.jdk ${{runner.temp}}/jdk/jdk-21 + echo "JDK21=${{runner.temp}}/jdk/jdk-21" >> "$GITHUB_ENV" + ls ${{runner.temp}}/jdk + + echo "org.gradle.java.installations.paths=${{runner.temp}}/jdk/jdk-21" >> gradle.properties + echo "org.gradle.java.installations.auto-detect=false" >> gradle.properties + cat gradle.properties + + sed -i'.bak' -e "s/JavaLanguageVersion.of(20)/JavaLanguageVersion.of(21)/" build.gradle + - name: Setup OSX key chain (macos) if: (matrix.os == 'macos-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') uses: apple-actions/import-codesign-certs@v2 with: p12-file-base64: ${{ secrets.OSX_SIGNING_CERT }} p12-password: ${{ secrets.OSX_CERT_PWD }} keychain-password: jabref - - name: Setup OSX key chain on OSX for app id cert + - name: Setup OSX key chain on OSX for app id cert (macos) if: (matrix.os == 'macos-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') uses: apple-actions/import-codesign-certs@v2 with: @@ -93,26 +151,64 @@ jobs: p12-password: ${{ secrets.OSX_CERT_PWD }} create-keychain: false keychain-password: jabref - - name: Build runtime image - if: (matrix.os != 'macos-latest') || (steps.checksecrets.outputs.secretspresent == 'YES') + - name: Build runtime image (non-macos) + if: (matrix.os != 'macos-latest') run: ./gradlew -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jlinkZip - - name: Build installer - if: (matrix.os != 'macos-latest') || (steps.checksecrets.outputs.secretspresent == 'YES') + - name: Prepare merged jars and modules dir (macos) + if: (matrix.os == 'macos-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') + run: ./gradlew -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" prepareModulesDir + - name: Build installer (non-macos) + if: (matrix.os != 'macos-latest') + shell: bash run: ./gradlew -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jpackage + - name: Build dmg (macos) + if: (matrix.os == 'macos-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') shell: bash - - name: Resign app image for OSX and build dmg + run: | + ${{env.JDK21}}/Contents/Home/bin/jpackage \ + --module org.jabref/org.jabref.cli.Launcher \ + --module-path ${{env.JDK21}}/Contents/Home/jmods/:build/jlinkbase/jlinkjars \ + --add-modules org.jabref,org.jabref.merged.module \ + --dest build/distribution \ + --name JabRef \ + --app-version ${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }} \ + --verbose \ + --mac-sign \ + --vendor JabRef \ + --mac-package-identifier Jabref \ + --mac-package-name JabRef \ + --type dmg --mac-signing-key-user-name "JabRef e.V. (6792V39SK3)" \ + --mac-package-signing-prefix org.jabref \ + --mac-entitlements buildres/mac/jabref.entitlements \ + --icon src/main/resources/icons/jabref.icns \ + --resource-dir buildres/mac \ + --file-associations buildres/mac/bibtexAssociations.properties \ + --jlink-options --bind-services + - name: Build pkg (macos) if: (matrix.os == 'macos-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') shell: bash run: | - codesign --entitlements buildres/mac/jabref.entitlements --options runtime -vvv -f --sign "Developer ID Application: JabRef e.V. (6792V39SK3)" build/distribution/JabRef.app/Contents/runtime/Contents/MacOS/libjli.dylib - codesign --entitlements buildres/mac/jabref.entitlements --options runtime -vvv -f --sign "Developer ID Application: JabRef e.V. (6792V39SK3)" build/distribution/JabRef.app/Contents/MacOS/JabRef - codesign --entitlements buildres/mac/jabref.entitlements --options runtime -vvv -f --sign "Developer ID Application: JabRef e.V. (6792V39SK3)" 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: JabRef e.V. (6792V39SK3)" --options runtime --entitlements buildres/mac/jabref.entitlements -vvvv --deep "build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.dmg" - 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: JabRef e.V. (6792V39SK3)" "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: Package application image - if: (matrix.os != 'macos-latest') || (steps.checksecrets.outputs.secretspresent == 'YES') + ${{env.JDK21}}/Contents/Home/bin/jpackage \ + --module org.jabref/org.jabref.cli.Launcher \ + --module-path ${{env.JDK21}}/Contents/Home/jmods/:build/jlinkbase/jlinkjars \ + --add-modules org.jabref,org.jabref.merged.module \ + --dest build/distribution \ + --name JabRef \ + --app-version ${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }} \ + --verbose \ + --mac-sign \ + --vendor JabRef \ + --mac-package-identifier Jabref \ + --mac-package-name JabRef \ + --type pkg --mac-signing-key-user-name "JabRef e.V. (6792V39SK3)" \ + --mac-package-signing-prefix org.jabref \ + --mac-entitlements buildres/mac/jabref.entitlements \ + --icon src/main/resources/icons/jabref.icns \ + --resource-dir buildres/mac \ + --file-associations buildres/mac/bibtexAssociations.properties \ + --jlink-options --bind-services + - name: Package application image (non-macos) + if: (matrix.os != 'macos-latest') shell: bash run: ${{ matrix.archivePortable }} - name: Rename files @@ -132,20 +228,31 @@ jobs: ar -m -c -a sdsd jabref_${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}_amd64_repackaged.deb debian-binary control.tar.xz data.tar.xz rm debian-binary control.tar.* data.tar.* mv -f jabref_${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}_amd64_repackaged.deb jabref_${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}_amd64.deb - - name: Upload to GitHub workflow artifacts store (non-Mac) - if: (matrix.os != 'macos-latest') + - name: Upload to builds.jabref.org (ubuntu) + if: (matrix.os == 'ubuntu-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') && (!startsWith(github.ref, 'refs/heads/gh-readonly-queue')) + uses: Pendect/action-rsyncer@v2.0.0 + env: + DEPLOY_KEY: ${{ secrets.buildJabRefPrivateKey }} + with: + flags: -vaz --itemize-changes --stats --partial-dir=/tmp/partial --rsync-path="mkdir -p /var/www/builds.jabref.org/www/${{ steps.gitversion.outputs.branchName }} && rsync" + options: '' + ssh_options: '-p 9922' + src: 'build/distribution/' + dest: jrrsync@build-upload.jabref.org:/var/www/builds.jabref.org/www/${{ steps.gitversion.outputs.branchName }}/ + - name: Upload to GitHub workflow artifacts store (windows) + if: (matrix.os == 'windows-latest') && (!startsWith(github.ref, 'refs/heads/gh-readonly-queue')) uses: actions/upload-artifact@v3 with: name: JabRef-${{ matrix.displayName }} path: build/distribution - - name: Upload to GitHub workflow artifacts store (Mac) - if: (matrix.os == 'macos-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') + - name: Upload to GitHub workflow artifacts store (macos) + if: (matrix.os == 'macos-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') && (!startsWith(github.ref, 'refs/heads/gh-readonly-queue')) uses: actions/upload-artifact@v3 with: - name: JabRef-macOS-signed + # tbn = to-be-notarized + name: JabRef-macOS-tbn path: build/distribution notarize: # outsourced in a separate job to be able to rerun if this fails for timeouts - if: ${{ false }} # disable for v5.9 since apple notarization fails name: Notarize and package Mac OS binaries runs-on: macos-latest needs: [build] @@ -166,62 +273,52 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 + - name: Install GitVersion + if: steps.checksecrets.outputs.secretspresent == 'YES' + uses: gittools/actions/gitversion/setup@v0.10.2 + with: + versionSpec: "5.x" - name: Run GitVersion if: steps.checksecrets.outputs.secretspresent == 'YES' id: gitversion - shell: bash - run: | - echo "AssemblySemVer=5.9.60000" >> $GITHUB_OUTPUT - echo "InformationalVersion=5.9--`git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d'`--`git log -1 --format=%h`" >> $GITHUB_OUTPUT - echo "Major=5" >> $GITHUB_OUTPUT - echo "Minor=9" >> $GITHUB_OUTPUT + uses: gittools/actions/gitversion/execute@v0.10.2 - name: Get macOS binaries if: steps.checksecrets.outputs.secretspresent == 'YES' uses: actions/download-artifact@master with: - name: JabRef-macOS-signed + name: JabRef-macOS-tbn path: build/distribution/ - - name: Notarize dmg and pkg installer - if: startsWith(github.ref, 'refs/tags/') && (steps.checksecrets.outputs.secretspresent == 'YES') + - name: Notarize dmg + if: (steps.checksecrets.outputs.secretspresent == 'YES') && (startsWith(github.ref, 'refs/tags/') || inputs.notarization == true) + shell: bash + run: | + xcrun notarytool store-credentials "notarytool-profile" --apple-id "vorstand@jabref.org" --team-id "6792V39SK3" --password "${{ secrets.OSX_NOTARIZATION_APP_PWD }}" + xcrun notarytool submit build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.dmg --keychain-profile "notarytool-profile" --wait + xcrun stapler staple build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.dmg + - name: Notarize pkg + if: (steps.checksecrets.outputs.secretspresent == 'YES') && (startsWith(github.ref, 'refs/tags/') || inputs.notarization == true) shell: bash run: | - REQUEST_UUID_DMG=$(xcrun altool --verbose --notarize-app --primary-bundle-id "org.jabref" --username ${{ secrets.OSX_NOTARIZATION_APP_USERNAME }} --password ${{ secrets.OSX_NOTARIZATION_APP_PWD }} --asc-provider "6792V39SK3" --file "build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.dmg" | grep RequestUUID | awk '{print $3}') - while xcrun altool --notarization-info "$REQUEST_UUID_DMG" -u ${{ secrets.OSX_NOTARIZATION_APP_USERNAME }} -p ${{ secrets.OSX_NOTARIZATION_APP_PWD }} | grep "Status: in progress" > /dev/null; do - echo "Verification in progress..." - sleep 30 - done - codesign -vvv --deep --strict "build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.dmg" - codesign -dvv "build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.dmg" - REQUEST_UUID_PKG=$(xcrun altool --verbose --notarize-app --primary-bundle-id "org.jabref" --username ${{ secrets.OSX_NOTARIZATION_APP_USERNAME }} --password ${{ secrets.OSX_NOTARIZATION_APP_PWD }} --asc-provider "6792V39SK3" --file "build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}-signed.pkg" | grep RequestUUID | awk '{print $3}') - while xcrun altool --notarization-info "$REQUEST_UUID_PKG" -u ${{ secrets.OSX_NOTARIZATION_APP_USERNAME }} -p ${{ secrets.OSX_NOTARIZATION_APP_PWD }} | grep "Status: in progress" > /dev/null; do - echo "Verification in progress..." - sleep 30 - done - xcrun stapler staple "build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}-signed.pkg" - rm "build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.pkg" - mv "build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}-signed.pkg" "build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.pkg" + xcrun notarytool store-credentials "notarytool-profile" --apple-id "vorstand@jabref.org" --team-id "6792V39SK3" --password "${{ secrets.OSX_NOTARIZATION_APP_PWD }}" + xcrun notarytool submit build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.pkg --keychain-profile "notarytool-profile" --wait + xcrun stapler staple build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.pkg - name: Package application image - if: (steps.checksecrets.outputs.secretspresent == 'YES') + if: (steps.checksecrets.outputs.secretspresent == 'YES') && (matrix.os != 'macos-latest') shell: bash run: ${{ matrix.archivePortable }} - - name: Rename files - if: (steps.checksecrets.outputs.secretspresent == 'YES') - shell: pwsh - run: | - get-childitem -Path build/distribution/* | rename-item -NewName {$_.name -replace "${{ steps.gitversion.outputs.AssemblySemVer }}","${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}"} - get-childitem -Path build/distribution/* | rename-item -NewName {$_.name -replace "portable","${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}-portable"} - name: Upload to GitHub workflow artifacts store - if: (steps.checksecrets.outputs.secretspresent == 'YES') + if: (steps.checksecrets.outputs.secretspresent == 'YES') && (!startsWith(github.ref, 'refs/heads/gh-readonly-queue')) uses: actions/upload-artifact@v3 with: name: JabRef-macOS path: build/distribution - deploy: + upload: strategy: fail-fast: false - name: Deploy binaries on builds.jabref.org + name: Upload binaries on builds.jabref.org runs-on: ubuntu-latest - needs: [build] + needs: [build, notarize] + if: ${{ !startsWith(github.ref, 'refs/heads/gh-readonly-queue') }} steps: - name: Check secrets presence id: checksecrets @@ -249,34 +346,42 @@ jobs: if: steps.checksecrets.outputs.secretspresent == 'YES' id: gitversion uses: gittools/actions/gitversion/execute@v0.10.2 - - name: Get linux binaries - if: steps.checksecrets.outputs.secretspresent == 'YES' - uses: actions/download-artifact@master - with: - name: JabRef-linux - path: build/distribution - name: Get windows binaries if: steps.checksecrets.outputs.secretspresent == 'YES' uses: actions/download-artifact@master with: name: JabRef-windows path: build/distribution - - name: Get macOS binaries - if: steps.checksecrets.outputs.secretspresent == 'YES' + - name: Get macOS binaries unsigned + if: (steps.checksecrets.outputs.secretspresent == 'YES') && (inputs.notarization == false && !startsWith(github.ref, 'refs/tags/')) + uses: actions/download-artifact@master + with: + name: JabRef-macOS-tbn + path: build/distribution/ + - name: Get macOS binaries notarized + if: (steps.checksecrets.outputs.secretspresent == 'YES') && (inputs.notarization == true || startsWith(github.ref, 'refs/tags/')) uses: actions/download-artifact@master with: - name: JabRef-macOS-signed + name: JabRef-macOS path: build/distribution/ - - name: Deploy to builds.jabref.org - id: deploy + # Upload to build server using rsync + # The action runs on linux only (because it is a Dockerized action), therefore it is embedded in a separate workflow + - name: Upload to builds.jabref.org if: steps.checksecrets.outputs.secretspresent == 'YES' uses: Pendect/action-rsyncer@v2.0.0 env: DEPLOY_KEY: ${{ secrets.buildJabRefPrivateKey }} - BRANCH: ${{ steps.gitversion.outputs.branchName }} with: flags: -vaz --itemize-changes --stats --partial-dir=/tmp/partial --rsync-path="mkdir -p /var/www/builds.jabref.org/www/${{ steps.gitversion.outputs.branchName }} && rsync" options: '' ssh_options: '-p 9922' src: 'build/distribution/' dest: jrrsync@build-upload.jabref.org:/var/www/builds.jabref.org/www/${{ steps.gitversion.outputs.branchName }}/ + - name: Comment PR + if: github.event_name == 'pull_request' && steps.checksecrets.outputs.secretspresent == 'YES' + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + The build of this PR is available at . + comment_tag: download-link + mode: recreate diff --git a/.github/workflows/gource.yml b/.github/workflows/gource.yml index 05109275365..06fe6006b2b 100644 --- a/.github/workflows/gource.yml +++ b/.github/workflows/gource.yml @@ -4,10 +4,15 @@ on: push: branches: - gource + - fix-gource schedule: - - cron: '15 3 1 1,4,7,10 *' + - cron: '15 3 5 * *' workflow_dispatch: +concurrency: + group: gource + cancel-in-progress: true + jobs: action: runs-on: ubuntu-latest @@ -16,35 +21,44 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Determine dates - id: dates - run: | - echo "start_date=`date -d "$(date +%Y-%m-01) -3 months" +%Y-%m-%d`" >> $GITHUB_STATE - echo "stop_date=`date -d "$(date +%Y-%m-01) -1 day" +%Y-%m-%d`" >> $GITHUB_STATE - echo "quarter=`date -d "$(date +%Y-%m-01) -1 day" +%Y`-Q$(( ((`date -d "$(date +%Y-%m-01) -1 day" +%m`)-1)/3+1 ))" >> $GITHUB_STATE - - name: 'Development history of last quarter' - uses: nbprojekt/gource-action@v1 + - name: 'Development history of current build' + uses: BoundfoxStudios/action-gource@v2 with: - gource_title: 'JabRef | more information at contribute.jabref.org' + gource_title: 'JabRef v5.10 (in development) | more information at contribute.jabref.org' logo_url: 'https://www.jabref.org/img/JabRef-icon-256.png' + avatars_auto_fetch: true # 5s * 365 / 4 = 7.5min - gource_seconds_per_day: 5 - gource_start_date: ${{ steps.dates.outputs.start_date }} - gource_stop_date: ${{ steps.dates.outputs.stop_date }} - gource_file_filter: csl$ + gource_seconds_per_day: 1 + gource_start_date: '2023-01-06' + gource_file_filter: 'buildres/csl|\.csl' - name: 'Store video' run: | mkdir gource-videos - mv ./gource/gource.mp4 ./gource-videos/jabref-${{ steps.dates.outputs.quarter }}.mp4 + mv ./gource/gource.mp4 ./gource-videos/jabref-v5.10-dev.mp4 + - name: 'Development history of last release' + uses: BoundfoxStudios/action-gource@v2 + with: + gource_title: 'JabRef v5.9 | more information at contribute.jabref.org' + logo_url: 'https://www.jabref.org/img/JabRef-icon-256.png' + avatars_auto_fetch: true + # 5s * 365 / 4 = 7.5min + gource_seconds_per_day: 2 + gource_start_date: '2022-12-18' + gource_stop_date: '2023-01-06' + gource_file_filter: 'buildres/csl|\.csl' + - name: 'Store video' + run: | + mv ./gource/gource.mp4 ./gource-videos/jabref-v5.9.mp4 - name: 'Complete development history' - uses: nbprojekt/gource-action@v1 + uses: BoundfoxStudios/action-gource@v2 with: gource_title: 'JabRef | more information at contribute.jabref.org' logo_url: 'https://www.jabref.org/img/JabRef-icon-256.png' + avatars_auto_fetch: true # 0.01 leads to a 45 second video for the complete history until end of 2020 # 0.1 leads to a 8 minute video gource_seconds_per_day: 0.1 - gource_file_filter: csl$ + gource_file_filter: 'buildres/csl|\.csl' - name: 'Store video' run: | mv gource/gource.mp4 gource-videos/jabref-complete.mp4 @@ -52,5 +66,27 @@ jobs: uses: actions/upload-artifact@v3 with: name: Gource - path: ./gource-videos/ + path: gource-videos/ retention-days: 80 + - name: Check secrets presence + id: checksecrets + shell: bash + run: | + if [ "$BUILDJABREFPRIVATEKEY" == "" ]; then + echo "secretspresent=NO" >> $GITHUB_OUTPUT + else + echo "secretspresent=YES" >> $GITHUB_OUTPUT + fi + env: + BUILDJABREFPRIVATEKEY: ${{ secrets.buildJabRefPrivateKey }} + - name: Upload to files.jabref.org + if: steps.checksecrets.outputs.secretspresent == 'YES' + uses: Pendect/action-rsyncer@v2.0.0 + env: + DEPLOY_KEY: ${{ secrets.buildJabRefPrivateKey }} + with: + flags: -vaz --itemize-changes --stats --partial-dir=/tmp/partial + options: '' + ssh_options: '-p 9922' + src: 'gource-videos/' + dest: jrrsync@build-upload.jabref.org:/var/www/files.jabref.org/www/gource/ diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index a2dba0e0ec8..ed17df2a103 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -43,7 +43,7 @@ jobs: cd docs bundle exec jekyll build - name: Upload artifact - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v2 with: path: docs/_site/ diff --git a/.github/workflows/refresh-csl-subtrees.yml b/.github/workflows/refresh-csl-subtrees.yml index 151b3057194..bc2f74cc724 100644 --- a/.github/workflows/refresh-csl-subtrees.yml +++ b/.github/workflows/refresh-csl-subtrees.yml @@ -13,19 +13,22 @@ jobs: publish: name: Refresh Citation Style Language Files runs-on: ubuntu-latest - if: github.repository == 'JabRef/jabref' + permissions: + contents: write # for peter-evans/create-pull-request to create branch + pull-requests: write # for peter-evans/create-pull-request to create a PR + if: (github.repository == 'JabRef/jabref' || github.repository == 'koppor/jabref') steps: - - name: Checkout source - uses: actions/checkout@v3 + - uses: actions/checkout@v3 with: + persist-credentials: true ref: main fetch-depth: 0 - name: Initialize git run: | git checkout main git config --local core.editor /usr/bin/cat - git config user.name "github actions" - git config user.email "jabrefmail+webfeedback@gmail.com" + git config user.name 'github-actions[bot]' + git config user.email 'github-actions[bot]@users.noreply.github.com' - name: Add csl-styles remote run: git remote add -f csl-styles https://github.com/citation-style-language/styles.git - name: Update csl-styles @@ -48,8 +51,8 @@ jobs: git commit -m"Refresh example styles" || true - uses: peter-evans/create-pull-request@v5 with: - token: ${{ secrets.GH_TOKEN_UPDATE_GRADLE_WRAPPER }} + token: ${{ secrets.GITHUB_TOKEN }} branch: refresh-csl - commit-message: Update CSL styles title: "[Bot] Update CSL styles" + commit-message: Update CSL styles labels: dependencies diff --git a/.github/workflows/refresh-journal-lists.yml b/.github/workflows/refresh-journal-lists.yml index 3b0c2ec1445..ac6107c713c 100644 --- a/.github/workflows/refresh-journal-lists.yml +++ b/.github/workflows/refresh-journal-lists.yml @@ -1,7 +1,12 @@ name: Refresh Journal Lists on: - # Allow to run manually + push: + paths: + - '.github/workflows/refresh-journal-lists.yml' + - 'buildSrc/build.gradle' + - 'buildSrc/src/main/java/**' + - 'src/main/java/org/jabref/logic/journals/**' workflow_dispatch: permissions: @@ -47,24 +52,14 @@ jobs: cp * $GITHUB_WORKSPACE/build/journals/ # ensure that the .java classes are the most recent ones - mkdir -p $GITHUB_WORKSPACE/buildSrc/src/copied/java/org/jabref/logic/journals - cp $GITHUB_WORKSPACE/src/main/java/org/jabref/logic/journals/* $GITHUB_WORKSPACE/buildSrc/src/copied/java/org/jabref/logic/journals + cp $GITHUB_WORKSPACE/src/main/java/org/jabref/logic/journals/* $GITHUB_WORKSPACE/buildSrc/src/main/java/org/jabref/logic/journals # create .mv file cd $GITHUB_WORKSPACE ./gradlew generateJournalAbbreviationList - uses: peter-evans/create-pull-request@v5 - if: github.ref == 'refs/heads/main' with: token: ${{ secrets.GITHUB_TOKEN }} branch: update-journallist - title: "[Bot] Update Journal abbrev list" - commit-message: Update journal abbrev list - - name: Commit and push changes - uses: EndBug/add-and-commit@v9 - if: github.ref != 'refs/heads/main' - with: - message: 'Update journal abbrev list' - committer_email: actions@github.com - fetch: false - push: true + title: "[Bot] Update journal abbreviation lists" + commit-message: Update journal abbreviation lists diff --git a/.github/workflows/tests-fetchers.yml b/.github/workflows/tests-fetchers.yml index e430cf8bd37..98a21bdef09 100644 --- a/.github/workflows/tests-fetchers.yml +++ b/.github/workflows/tests-fetchers.yml @@ -7,6 +7,8 @@ on: paths: - 'src/main/java/org/jabref/logic/importer/fetcher/**' - 'src/test/java/org/jabref/logic/importer/fetcher/**' + - 'src/main/java/org/jabref/logic/crawler/**' + - 'src/test/java/org/jabref/logic/crawler/**' - '.github/workflows/tests-fetchers.yml' - 'build.gradle' pull_request: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2f6c9545f3f..d0cfa18a724 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,6 +7,7 @@ on: - main-release pull_request: # always run on pull requests + merge_group: workflow_dispatch: env: @@ -23,7 +24,7 @@ concurrency: jobs: checkstyle: - name: Checkstyle + name: Code style check runs-on: ubuntu-latest steps: - name: Checkout source @@ -34,15 +35,37 @@ jobs: java-version: 20 distribution: 'temurin' cache: 'gradle' - - name: Run check style reporter + - name: Run checkstyle reporter uses: nikitasavinov/checkstyle-action@master with: reporter: github-pr-review github_token: ${{ secrets.GITHUB_TOKEN }} checkstyle_config: 'config/checkstyle/checkstyle_reviewdog.xml' checkstyle_version: '10.3' - - name: Run checkstyle gradle + - name: Run checkstyle using gradle run: ./gradlew checkstyleMain checkstyleTest checkstyleJmh + - name: Add comment on pull request + if: ${{ failure() }} + uses: thollander/actions-comment-pull-request@v2 + with: + message: > + Your code currently does not meet JabRef's code guidelines. + The tool reviewdog already placed comments on GitHub to indicate the places. See the tab "Files" in you PR. + Please carefully follow [the setup guide for the codestyle](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-13-code-style.html). + Afterwards, please run checkstyle locally and fix the issues. + + + More information on code quality in JabRef is available at . + comment_tag: checkstyle + reactions: eyes + - name: Run OpenRewrite + run: | + ./gradlew rewriteDryRun + - name: Run modernizer + run: | + # enable failing of this task if modernizer complains + sed -i "s/failOnViolations = false/failOnViolations = true/" build.gradle + ./gradlew modernizer - name: Run markdown-lint uses: avto-dev/markdown-lint@v1 with: @@ -61,7 +84,7 @@ jobs: distribution: 'temurin' cache: 'gradle' - name: Run tests - run: xvfb-run --auto-servernum ./gradlew check -x checkstyleJmh -x checkstyleMain -x checkstyleTest + run: xvfb-run --auto-servernum ./gradlew check -x checkstyleJmh -x checkstyleMain -x checkstyleTest -x modernizer env: CI: "true" - name: Format failed test results @@ -179,3 +202,11 @@ jobs: steps: - uses: actions/checkout@v3 - uses: gradle/wrapper-validation-action@v1 + # This ensures that no git merge conflict markers (<<<, ...) are contained + merge_conflict_job: + name: Find merge conflicts + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Merge Conflict finder + uses: olivernybroe/action-conflict-finder@v4.0 diff --git a/.gitignore b/.gitignore index f2a7cad5e5e..1d3864a220b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ src/main/gen/ src/main/generated/ src-gen/ +structure101-settings.java.hsw + # private data /buildres/jabref-cert-2016.p12 @@ -475,3 +477,8 @@ lib/ojdbc.jar # do not ignore JabRef icons (they are ignored by the macos setting above) !src/main/java/org/jabref/gui/icon + +!**/autosaveandbackup/*.bak + +# generated during release process +CHANGELOG.html diff --git a/.vscode/settings.json b/.vscode/settings.json index 861ba08aff4..2094775de07 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,5 @@ "java.configuration.updateBuildConfiguration": "interactive", "java.format.settings.url": "/config/VSCode Code Style.xml", "java.checkstyle.configuration": "${workspaceFolder}/config/checkstyle/checkstyle_reviewdog.xml", - "java.checkstyle.version": "10.3.4", + "java.checkstyle.version": "10.3.4" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 29318baf986..6088e85301e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,42 +11,98 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve ### Added +- We added an error-specific message for when a download from a URL fails[#9826](https://github.com/JabRef/jabref/issues/9826). +- We added protected terms described as "Computer science". [#10222](https://github.com/JabRef/jabref/pull/10222) + +### Changed + +### Fixed + +- It is possible again to use "current table sort order" for the order of entries when saving. [#9869](https://github.com/JabRef/jabref/issues/9869) + +### Removed + +## [5.10] - 2023-09-02 + +### Added + - We added a field showing the BibTeX/biblatex source for added and deleted entries in the "External Changes Resolver" dialog. [#9509](https://github.com/JabRef/jabref/issues/9509) +- We added user-specific comment field so that multiple users can make separate comments. [#543](https://github.com/koppor/jabref/issues/543) - We added a search history list in the search field's right click menu. [#7906](https://github.com/JabRef/jabref/issues/7906) - We added a full text fetcher for IACR eprints. [#9651](https://github.com/JabRef/jabref/pull/9651) - We added "Attach file from URL" to right-click context menu to download and store a file with the reference library. [#9646](https://github.com/JabRef/jabref/issues/9646) - We enabled updating an existing entry with data from InspireHEP. [#9351](https://github.com/JabRef/jabref/issues/9351) - We added a fetcher for the Bibliotheksverbund Bayern (experimental). [#9641](https://github.com/JabRef/jabref/pull/9641) +- We added support for more biblatex date formats for parsing dates. [#2753](https://github.com/JabRef/issues/2753) - We added support for multiple languages for exporting to and importing references from MS Office. [#9699](https://github.com/JabRef/jabref/issues/9699) - We enabled scrolling in the groups list when dragging a group on another group. [#2869](https://github.com/JabRef/jabref/pull/2869) -- We added the option to automatically download online files when a new entry is created from an existing ID (e.g. DOI). The option can be disabled in the preferences under "Import and Export" [#9756](https://github.com/JabRef/jabref/issues/9756) -- We added a new Integrity check for unscaped ampersands. [koppor#585](https://github.com/koppor/jabref/issues/585) - +- We added the option to automatically download online files when a new entry is created from an existing ID (e.g., DOI). The option can be disabled in the preferences under "Import and Export". [#9756](https://github.com/JabRef/jabref/issues/9756) +- We added a new Integrity check for unescaped ampersands. [koppor#585](https://github.com/koppor/jabref/issues/585) +- We added support for parsing `$\backslash$` in file paths (as exported by Mendeley). [forum#3470](https://discourse.jabref.org/t/mendeley-bib-import-with-linked-files/3470) +- We added the possibility to automatically fetch entries when an ISBN is pasted on the main table. [#9864](https://github.com/JabRef/jabref/issues/9864) +- We added the option to disable the automatic linking of files in the entry editor [#5105](https://github.com/JabRef/jabref/issues/5105) +- We added the link icon for ISBNs in linked identifiers column. [#9819](https://github.com/JabRef/jabref/issues/9819) +- We added key binding to focus on groups alt + s [#9863](https://github.com/JabRef/jabref/issues/9863) +- We added the option to unprotect a text selection, which strips all pairs of curly braces away. [#9950](https://github.com/JabRef/jabref/issues/9950) +- We added drag and drop events for field 'Groups' in entry editor panel. [#569](https://github.com/koppor/jabref/issues/569) +- We added support for parsing MathML in the Medline importer. [#4273](https://github.com/JabRef/jabref/issues/4273) +- We added the ability to search for an identifier (DOI, ISBN, ArXiv ID) directly from 'Web Search'. [#7575](https://github.com/JabRef/jabref/issues/7575) [#9674](https://github.com/JabRef/jabref/issues/9674) +- We added a cleanup activity that identifies a URL or a last-visited-date in the `note` field and moves it to the `url` and `urldate` field respectively. [koppor#216](https://github.com/koppor/jabref/issues/216) +- We enabled the user to change the name of a field in a custom entry type by double-clicking on it. [#9840](https://github.com/JabRef/jabref/issues/9840) +- We added some preferences options to disable online activity. [#10064](https://github.com/JabRef/jabref/issues/10064) +- We integrated two mail actions ("As Email" and "To Kindle") under a new "Send" option in the right-click & Tools menus. The Kindle option creates an email targeted to the user's Kindle email, which can be set in preferences under "External programs" [#6186](https://github.com/JabRef/jabref/issues/6186) +- We added an option to clear recent libraries' history. [#10003](https://github.com/JabRef/jabref/issues/10003) +- We added an option to encrypt and remember the proxy password. [#8055](https://github.com/JabRef/jabref/issues/8055)[#10044](https://github.com/JabRef/jabref/issues/10044) +- We added support for showing journal information, via info buttons next to the `Journal` and `ISSN` fields in the entry editor. [#6189](https://github.com/JabRef/jabref/issues/6189) +- We added support for pushing citations to Sublime Text 3 [#10098](https://github.com/JabRef/jabref/issues/10098) +- We added support for the Finnish language. [#10183](https://github.com/JabRef/jabref/pull/10183) +- We added the option to automatically replaces illegal characters in the filename when adding a file to JabRef. [#10182](https://github.com/JabRef/jabref/issues/10182) +- We added a privacy policy. [#10064](https://github.com/JabRef/jabref/issues/10064) +- We added a tooltip to show the number of entries in a group [#10208](https://github.com/JabRef/jabref/issues/10208) ### Changed -- We upgraded to Lucene 9.5 for the fulltext search. The search index will be rebuild. [#9584](https://github.com/JabRef/jabref/pull/9584) +- We replaced "Close" by "Close library" and placed it after "Save all" in the File menu. [#10043](https://github.com/JabRef/jabref/pull/10043) +- We upgraded to Lucene 9.7 for the fulltext search. The search index will be rebuild. [#9584](https://github.com/JabRef/jabref/pull/10036) - 'Get full text' now also checks the file url. [#568](https://github.com/koppor/jabref/issues/568) - JabRef writes a new backup file only if there is a change. Before, JabRef created a backup upon start. [#9679](https://github.com/JabRef/jabref/pull/9679) - We modified the `Add Group` dialog to use the most recently selected group hierarchical context. [#9141](https://github.com/JabRef/jabref/issues/9141) - We refined the 'main directory not found' error message. [#9625](https://github.com/JabRef/jabref/pull/9625) - JabRef writes a new backup file only if there is a change. Before, JabRef created a backup upon start. [#9679](https://github.com/JabRef/jabref/pull/9679) -- Backups of libraries are not stored per JabRef version, but collected together. -- We streamlined the paths for logs and backups: The parent path fragement is always `logs` or `backups`. +- Backups of libraries are not stored per JabRef version, but collected together. [#9676](https://github.com/JabRef/jabref/pull/9676) +- We streamlined the paths for logs and backups: The parent path fragment is always `logs` or `backups`. - `log.txt` now contains an entry if a BibTeX entry could not be parsed. - `log.txt` now contains debug messages. Debugging needs to be enabled explicitly. [#9678](https://github.com/JabRef/jabref/pull/9678) - `log.txt` does not contain entries for non-found files during PDF indexing. [#9678](https://github.com/JabRef/jabref/pull/9678) +- The hostname is now determined using environment variables (`COMPUTERNAME`/`HOSTNAME`) first. [#9910](https://github.com/JabRef/jabref/pull/9910) - We improved the Medline importer to correctly import ISO dates for `revised`. [#9536](https://github.com/JabRef/jabref/issues/9536) - To avoid cluttering of the directory, We always delete the `.sav` file upon successful write. [#9675](https://github.com/JabRef/jabref/pull/9675) - We improved the unlinking/deletion of multiple linked files of an entry using the Delete key. [#9473](https://github.com/JabRef/jabref/issues/9473) +- The field names of customized entry types are now exchanged preserving the case. [#9993](https://github.com/JabRef/jabref/pull/9993) - We moved the custom entry types dialog into the preferences dialog. [#9760](https://github.com/JabRef/jabref/pull/9760) - We moved the manage content selectors dialog to the library properties. [#9768](https://github.com/JabRef/jabref/pull/9768) - We moved the preferences menu command from the options menu to the file menu. [#9768](https://github.com/JabRef/jabref/pull/9768) - - +- We reworked the cross ref labels in the entry editor and added a right click menu. [#10046](https://github.com/JabRef/jabref/pull/10046) +- We reorganized the order of tabs and settings in the library properties. [#9836](https://github.com/JabRef/jabref/pull/9836) +- We changed the handling of an "overflow" of authors at `[authIniN]`: JabRef uses `+` to indicate an overflow. Example: `[authIni2]` produces `A+` (instead of `AB`) for `Aachen and Berlin and Chemnitz`. [#9703](https://github.com/JabRef/jabref/pull/9703) +- We moved the preferences option to open the last edited files on startup to the 'General' tab. [#9808](https://github.com/JabRef/jabref/pull/9808) +- We improved the recognition of DOIs when pasting a link containing a DOI on the maintable. [#9864](https://github.com/JabRef/jabref/issues/9864s) +- We reordered the preferences dialog. [#9839](https://github.com/JabRef/jabref/pull/9839) +- We split the 'Import and Export' tab into 'Web Search' and 'Export'. [#9839](https://github.com/JabRef/jabref/pull/9839) +- We moved the option to run JabRef in memory stick mode into the preferences dialog toolbar. [#9866](https://github.com/JabRef/jabref/pull/9866) +- In case the library contains empty entries, they are not written to disk. [#8645](https://github.com/JabRef/jabref/issues/8645) +- The formatter `remove_unicode_ligatures` is now called `replace_unicode_ligatures`. [#9890](https://github.com/JabRef/jabref/pull/9890) +- We improved the error message when no terminal was found. [#9607](https://github.com/JabRef/jabref/issues/9607) +- In the context of the "systematic literature functionality", we changed the name "database" to "catalog" to use a separate term for online catalogs in comparison to SQL databases. [#9951](https://github.com/JabRef/jabref/pull/9951) +- We now show more fields (including Special Fields) in the dropdown selection for "Save sort order" in the library properties and for "Export sort order" in the preferences. [#10010](https://github.com/JabRef/jabref/issues/10010) +- We now encrypt and store the custom API keys in the OS native credential store. [#10044](https://github.com/JabRef/jabref/issues/10044) +- We changed the behavior of group addition/edit, so that sorting by alphabetical order is not performed by default after the modification. [#10017](https://github.com/JabRef/jabref/issues/10017) +- We fixed an issue with spacing in the cleanup dialogue. [#10081](https://github.com/JabRef/jabref/issues/10081) +- The GVK fetcher now uses the new [K10plus](https://www.bszgbv.de/services/k10plus/) database. [#10189](https://github.com/JabRef/jabref/pull/10189) ### Fixed +- We fixed an issue where clicking the group expansion pane/arrow caused the node to be selected, when it should just expand/detract the node. [#10111](https://github.com/JabRef/jabref/pull/10111) - We fixed an issue where the browser import would add ' characters before the BibTeX entry on Linux. [#9588](https://github.com/JabRef/jabref/issues/9588) - We fixed an issue where searching for a specific term with the DOAB fetcher lead to an exception. [#9571](https://github.com/JabRef/jabref/issues/9571) - We fixed an issue where the "Import" -> "Library to import to" did not show the correct library name if two opened libraries had the same suffix. [#9567](https://github.com/JabRef/jabref/issues/9567) @@ -55,26 +111,55 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We fixed an issue where custom field in the custom entry types could not be set to mulitline. [#9609](https://github.com/JabRef/jabref/issues/9609) - We fixed an issue where the Office XML exporter did not resolve BibTeX-Strings when exporting entries. [forum#3741](https://discourse.jabref.org/t/exporting-bibtex-constant-strings-to-ms-office-2007-xml/3741) - We fixed an issue where the Merge Entries Toolbar configuration was not saved after hitting 'Merge Entries' button. [#9091](https://github.com/JabRef/jabref/issues/9091) -- We fixed an issue where the password is saved locally if user wants to use proxy with authentication. [#8055](https://github.com/JabRef/jabref/issues/8055) +- We fixed an issue where the password is stored in clear text if the user wants to use a proxy with authentication. [#8055](https://github.com/JabRef/jabref/issues/8055) - JabRef is now more relaxed when parsing field content: In case a field content ended with `\`, the combination `\}` was treated as plain `}`. [#9668](https://github.com/JabRef/jabref/issues/9668) -- We resolved an issue that cut off the number of group entries when it exceedet four digits. [#8797](https://github.com/JabRef/jabref/issues/8797) +- We resolved an issue that cut off the number of group entries when it exceeded four digits. [#8797](https://github.com/JabRef/jabref/issues/8797) - We fixed the issue where the size of the global search window was not retained after closing. [#9362](https://github.com/JabRef/jabref/issues/9362) - We fixed an issue where the Global Search UI preview is still white in dark theme. [#9362](https://github.com/JabRef/jabref/issues/9362) - We fixed the double paste issue when Cmd + v is pressed on 'New entry from plaintext' dialog. [#9367](https://github.com/JabRef/jabref/issues/9367) - We fixed an issue where the pin button on the Global Search dialog was located at the bottom and not at the top. [#9362](https://github.com/JabRef/jabref/issues/9362) - We fixed the log text color in the event log console when using dark mode. [#9732](https://github.com/JabRef/jabref/issues/9732) -- We fixed an issue where searching for unlinked files would include the current library's .bib file [#9735](https://github.com/JabRef/jabref/issues/9735) -- We fixed an issue where it was no longer possible to connect to a shared mysql database due to an exception [#9761](https://github.com/JabRef/jabref/issues/9761) +- We fixed an issue where searching for unlinked files would include the current library's .bib file. [#9735](https://github.com/JabRef/jabref/issues/9735) +- We fixed an issue where it was no longer possible to connect to a shared mysql database due to an exception. [#9761](https://github.com/JabRef/jabref/issues/9761) +- We fixed an issue where an exception was thrown for the user after Ctrl+Z command. [#9737](https://github.com/JabRef/jabref/issues/9737) +- We fixed the citation key generation for [`[authors]`, `[authshort]`, `[authorsAlpha]`, `[authIniN]`, `[authEtAl]`, `[auth.etal]`](https://docs.jabref.org/setup/citationkeypatterns#special-field-markers) to handle `and others` properly. [koppor#626](https://github.com/koppor/jabref/issues/626) +- We fixed the Save/save as file type shows BIBTEX_DB instead of "Bibtex library". [#9372](https://github.com/JabRef/jabref/issues/9372) +- We fixed the default main file directory for non-English Linux users. [#8010](https://github.com/JabRef/jabref/issues/8010) +- We fixed an issue when overwriting the owner was disabled. [#9896](https://github.com/JabRef/jabref/pull/9896) +- We fixed an issue regarding recording redundant prefixes in search history. [#9685](https://github.com/JabRef/jabref/issues/9685) +- We fixed an issue where passing a URL containing a DOI led to a "No entry found" notification. [#9821](https://github.com/JabRef/jabref/issues/9821) +- We fixed some minor visual inconsistencies and issues in the preferences dialog. [#9866](https://github.com/JabRef/jabref/pull/9866) +- The order of save actions is now retained. [#9890](https://github.com/JabRef/jabref/pull/9890) +- We fixed an issue where the order of save actions was not retained in the bib file. [#9890](https://github.com/JabRef/jabref/pull/9890) +- We fixed an issue in the preferences 'External file types' tab ignoring a custom application path in the edit dialog. [#9895](https://github.com/JabRef/jabref/issues/9895) +- We fixed an issue in the preferences where custom columns could be added to the entry table with no qualifier. [#9913](https://github.com/JabRef/jabref/issues/9913) +- We fixed an issue where the encoding header in a bib file was not respected when the file contained a BOM (Byte Order Mark). [#9926](https://github.com/JabRef/jabref/issues/9926) +- We fixed an issue where cli help output for import and export format was inconsistent. [koppor#429](https://github.com/koppor/jabref/issues/429) +- We fixed an issue where the user could select multiple conflicting options for autocompletion at once. [#10181](https://github.com/JabRef/jabref/issues/10181) +- We fixed an issue where no preview could be generated for some entry types and led to an exception. [#9947](https://github.com/JabRef/jabref/issues/9947) +- We fixed an issue where the Linux terminal working directory argument was malformed and therefore ignored upon opening a terminal [#9953](https://github.com/JabRef/jabref/issues/9953) +- We fixed an issue under Linux where under some systems the file instead of the folder was opened. [#9607](https://github.com/JabRef/jabref/issues/9607) +- We fixed an issue where an Automatic Keyword Group could not be deleted in the UI. [#9778](https://github.com/JabRef/jabref/issues/9778) +- We fixed an issue where the citation key pattern `[edtrN_M]` returned the wrong editor. [#9946](https://github.com/JabRef/jabref/pull/9946) +- We fixed an issue where empty grey containers would remain in the groups panel, if displaying of group item count is turned off. [#9972](https://github.com/JabRef/jabref/issues/9972) +- We fixed an issue where fetching an ISBN could lead to application freezing when the fetcher did not return any results. [#9979](https://github.com/JabRef/jabref/issues/9979) +- We fixed an issue where closing a library containing groups and entries caused an exception [#9997](https://github.com/JabRef/jabref/issues/9997) +- We fixed a bug where the editor for strings in a bibliography file did not sort the entries by their keys [#10083](https://github.com/JabRef/jabref/pull/10083) +- We fixed an issues where clicking on the empty space of specific context menu entries would not trigger the associated action. [#8388](https://github.com/JabRef/jabref/issues/8388) +- We fixed an issue where JabRef would not remember whether the window was in fullscreen. [#4939](https://github.com/JabRef/jabref/issues/4939) +- We fixed an issue where the ACM Portal search sometimes would not return entries for some search queries when the article author had no given name. [#10107](https://github.com/JabRef/jabref/issues/10107) +- We fixed an issue that caused high CPU usage and a zombie process after quitting JabRef because of author names autocompletion. [#10159](https://github.com/JabRef/jabref/pull/10159) +- We fixed an issue where files with illegal characters in the filename could be added to JabRef. [#10182](https://github.com/JabRef/jabref/issues/10182) +- We fixed that checked-out radio buttons under "specified keywords" were not displayed as checked after closing and reopening the "edit group" window. [#10248](https://github.com/JabRef/jabref/issues/10248) +- We fixed that when editing groups, checked-out properties such as case sensitive and regular expression (under "Free search expression") were not displayed checked. [#10108](https://github.com/JabRef/jabref/issues/10108) ### Removed - We removed the support of BibTeXML. [#9540](https://github.com/JabRef/jabref/issues/9540) - We removed support for Markdown syntax for strikethrough and task lists in comment fields. [#9726](https://github.com/JabRef/jabref/pull/9726) - We removed the options menu, because the two contents were moved to the File menu or the properties of the library. [#9768](https://github.com/JabRef/jabref/pull/9768) - - - - +- We removed the 'File' tab in the preferences and moved its contents to the 'Export' tab. [#9839](https://github.com/JabRef/jabref/pull/9839) +- We removed the "[Collection of Computer Science Bibliographies](https://en.wikipedia.org/wiki/Collection_of_Computer_Science_Bibliographies)" fetcher the websits is no longer available. [#6638](https://github.com/JabRef/jabref/issues/6638) ## [5.9] - 2023-01-06 @@ -93,6 +178,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We now have more "dots" in the offered journal abbreviations. [#9504](https://github.com/JabRef/jabref/pull/9504) - We now disable the button "Full text search" in the Searchbar by default [#9527](https://github.com/JabRef/jabref/pull/9527) + ### Fixed - The tab "deprecated fields" is shown in biblatex-mode only. [#7757](https://github.com/JabRef/jabref/issues/7757) @@ -104,9 +190,9 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We fixed an issue where entering a date in the format "YYYY/MM" in the entry editor date field caused an exception. [#9492](https://github.com/JabRef/jabref/issues/9492) - For portable versions, the `.deb` file now works on plain debian again. [#9472](https://github.com/JabRef/jabref/issues/9472) - We fixed an issue where the download of linked online files failed after an import of entries for certain urls. [#9518](https://github.com/JabRef/jabref/issues/9518) -- We fixed an issue where an exception occured when manually downloading a file from an URL in the entry editor. [#9521](https://github.com/JabRef/jabref/issues/9521) -- We fixed an issue with open office csv file formatting where commas in the abstract field where not escaped. [#9087][https://github.com/JabRef/jabref/issues/9087] -- We fixed an issue with deleting groups where subgroups different from the selected group were deleted. [#9281][https://github.com/JabRef/jabref/issues/9281] +- We fixed an issue where an exception occurred when manually downloading a file from an URL in the entry editor. [#9521](https://github.com/JabRef/jabref/issues/9521) +- We fixed an issue with open office csv file formatting where commas in the abstract field where not escaped. [#9087](https://github.com/JabRef/jabref/issues/9087) +- We fixed an issue with deleting groups where subgroups different from the selected group were deleted. [#9281](https://github.com/JabRef/jabref/issues/9281) ## [5.8] - 2022-12-18 @@ -261,6 +347,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We fixed a bug that prevented external group metadata changes from being merged. [#8873](https://github.com/JabRef/jabref/issues/8873) - We fixed the shared database opening dialog to remember autosave folder and tick. [#7516](https://github.com/JabRef/jabref/issues/7516) - We fixed an issue where name formatter could not be saved. [#9120](https://github.com/JabRef/jabref/issues/9120) +- We fixed a bug where after the export of Preferences, custom exports were duplicated. [#10176](https://github.com/JabRef/jabref/issues/10176) ### Removed @@ -356,7 +443,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve ### Added -- We added confirmation dialog when user wants to close a library where any empty entires are detected. [#8096](https://github.com/JabRef/jabref/issues/8096) +- We added confirmation dialog when user wants to close a library where any empty entries are detected. [#8096](https://github.com/JabRef/jabref/issues/8096) - We added import support for CFF files. [#7945](https://github.com/JabRef/jabref/issues/7945) - We added the option to copy the DOI of an entry directly from the context menu copy submenu. [#7826](https://github.com/JabRef/jabref/issues/7826) - We added a fulltext search feature. [#2838](https://github.com/JabRef/jabref/pull/2838) @@ -403,7 +490,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve ### Fixed - We fixed an issue where an exception occurred when pasting an entry with a publication date-range of the form 1910/1917 [#7864](https://github.com/JabRef/jabref/issues/7864) -- We fixed an issue where an exception occured when a preview style was edited and afterwards another preview style selected. [#8280](https://github.com/JabRef/jabref/issues/8280) +- We fixed an issue where an exception occurred when a preview style was edited and afterwards another preview style selected. [#8280](https://github.com/JabRef/jabref/issues/8280) - We fixed an issue where the actions to move a file to a directory were incorrectly disabled. [#7908](https://github.com/JabRef/jabref/issues/7908) - We fixed an issue where an exception occurred when a linked online file was edited in the entry editor [#8008](https://github.com/JabRef/jabref/issues/8008) - We fixed an issue when checking for a new version when JabRef is used behind a corporate proxy. [#7884](https://github.com/JabRef/jabref/issues/7884) @@ -487,11 +574,11 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve ### Fixed -- We fixed an isuse where some texts (e.g. descriptionss) in dialogs could not be translated [#7854](https://github.com/JabRef/jabref/issues/7854) +- We fixed an issue where some texts (e.g. descriptions) in dialogs could not be translated [#7854](https://github.com/JabRef/jabref/issues/7854) - We fixed an issue where import hangs for ris files with "ER - " [#7737](https://github.com/JabRef/jabref/issues/7737) - We fixed an issue where getting bibliograhpic data from DOI or another identifer did not respect the library mode (BibTeX/biblatex)[#1018](https://github.com/JabRef/jabref/issues/6267) - We fixed an issue where importing entries would not respect the library mode (BibTeX/biblatex)[#1018](https://github.com/JabRef/jabref/issues/1018) -- We fixed an issue where an exception occured when importing entries from a web search [#7606](https://github.com/JabRef/jabref/issues/7606) +- We fixed an issue where an exception occurred when importing entries from a web search [#7606](https://github.com/JabRef/jabref/issues/7606) - We fixed an issue where the table column sort order was not properly stored and resulted in unsorted eports [#7524](https://github.com/JabRef/jabref/issues/7524) - We fixed an issue where the value of the field `school` or `institution` would be printed twice in the HTML Export [forum#2634](https://discourse.jabref.org/t/problem-with-exporting-techreport-phdthesis-mastersthesis-to-html/2634) - We fixed an issue preventing to connect to a shared database. [#7570](https://github.com/JabRef/jabref/pull/7570) @@ -716,11 +803,11 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We fixed an issue where long directory names created from patterns could create an exception. [#3915](https://github.com/JabRef/jabref/issues/3915) - We fixed an issue where sort on numeric cases was broken. [#6349](https://github.com/JabRef/jabref/issues/6349) - We fixed an issue where year and month fields were not cleared when converting to biblatex [#6224](https://github.com/JabRef/jabref/issues/6224) -- We fixed an issue where an "Not on FX thread" exception occured when saving on linux [#6453](https://github.com/JabRef/jabref/issues/6453) +- We fixed an issue where an "Not on FX thread" exception occurred when saving on linux [#6453](https://github.com/JabRef/jabref/issues/6453) - We fixed an issue where the library sort order was lost. [#6091](https://github.com/JabRef/jabref/issues/6091) - We fixed an issue where brackets in regular expressions were not working. [6469](https://github.com/JabRef/jabref/pull/6469) - We fixed an issue where multiple background task popups stacked over each other.. [#6472](https://github.com/JabRef/jabref/issues/6472) -- We fixed an issue where LaTeX citations for specific commands (\autocites) of biblatex-mla were not recognized. [#6476](https://github.com/JabRef/jabref/issues/6476) +- We fixed an issue where LaTeX citations for specific commands (`\autocite`s) of biblatex-mla were not recognized. [#6476](https://github.com/JabRef/jabref/issues/6476) - We fixed an issue where drag and drop was not working on empty database. [#6487](https://github.com/JabRef/jabref/issues/6487) - We fixed an issue where the name fields were not updated after the preferences changed. [#6515](https://github.com/JabRef/jabref/issues/6515) - We fixed an issue where "null" appeared in generated BibTeX keys. [#6459](https://github.com/JabRef/jabref/issues/6459) @@ -1036,7 +1123,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.9...HEAD +[Unreleased]: https://github.com/JabRef/jabref/compare/v5.10...HEAD +[5.10]: https://github.com/JabRef/jabref/compare/v5.9...v5.10 [5.9]: https://github.com/JabRef/jabref/compare/v5.8...v5.9 [5.8]: https://github.com/JabRef/jabref/compare/v5.7...v5.8 [5.7]: https://github.com/JabRef/jabref/compare/v5.6...v5.7 @@ -1049,5 +1137,4 @@ The changelog of JabRef 2.11 and all previous versions is available as [text fil [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 [5.0-alpha]: https://github.com/JabRef/jabref/compare/v4.3...v5.0-alpha - diff --git a/MAINTAINERS b/MAINTAINERS index b46d397cce9..35e4e365c15 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1,10 +1,8 @@ Oliver Kopp (since 2011) -Stefan Kolb (since 2015) -Matthias Geiger (since 2015) Tobias Diez (since 2015) Christoph Schwentker (since 2016) Carl Christian Snethlage (since 2020) -Dominik Voigt (since 2020) -Jonatan Askertop (since 2021) +Jonatan Asketorp (since 2021) Benedikt Tutzer (since 2021) Thilo Ertel (since 2021) +Houssem Nasri (since 2023) diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 00000000000..f5552fed91b --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,120 @@ +# Privacy Policy + +Last updated: 2023-08-24 + +Your privacy is a fundamental right JabRef e.V. respects and supports. +By using JabRef and its related online services, you choose to share some of your personal information. +In this Privacy Policy we explain how we collect, use, and share information about you, along with the choices you have. + +The term 'Personal information' in this policy means any information that either directly identifies you or can be somehow linked to you. 'JabRef' refers to the desktop application that is provided by JabRef e.V. + +Please remember that no method of transmission over the Internet, or method of electronic storage is absolute secure. +While we strive to use commercially acceptable means to protect your Personal Data, we cannot guarantee its absolute security. +Also, whenever you communicate through the internet, your IP-Address will always be transmitted and retained by third parties for technical and in some cases for legal reasons. + +## JabRef Desktop Application + +### Collecting information + +JabRef does not collect any personal information directly linked to you. +However, on certain occasions JabRef will send some information to the online services of JabRef e.V.: + +- On application start, JabRef will check for the latest version online (by default *enabled*). +- Information about a journal you are citing when looking for more information about this journal, using our journal database (by default *enabled*). +- A pdf document you automatically want to extract citation information from, using our GROBID service (by default *disabled*). +- Anonymized statistical data on the use of the graphical user interface for internal analysis purposes (by default *disabled*). + +### Storing information + +JabRef only stores the following personal information locally on your computer: + +- Your proxy username and password, if you decide to store them (by default *disabled*). +- Any personal API key you use to access third party online services (by default *disabled*). + +### Sharing information + +Certain operations you perform in JabRef may trigger requests to public third-party services such as Zotero, Crossref or the Library of Congress for metadata retrieval. +These third parties may log additional information besides your IP address and the search terms (e.g., DOI, ISBN or the current URL) according to their privacy policies. + +These third-party services are the following: + +| Service | Privacy Policy | +|-----------------------------------------------------------------------------------------------------------------|----------------| +| [ACM](https://www.acm.org/) | | +| [ACS Publications](https://pubs.acs.org/) | | +| [APS Advancing Physics](https://harvest.aps.org/) | | +| [arXiv.org](https://arxiv.org/) | | +| [Bibliotheksverbund Bayern](https://www.bib-bvb.de/) | | +| [Biodiversity Heritage Library](https://www.biodiversitylibrary.org/) | | +| [Collection of Computer Science Bibliographies](http://liinwww.ira.uka.de/) | **currently unavailable**, offline | +| [CrossRef](https://www.crossref.org/) | | +| [dblp](https://dblp.uni-trier.de/) | | +| [Directory of Open Access Books](https://www.doabooks.org/) | | +| [Digitala Vetenskapliga Arkivet](https://www.diva-portal.org/) | | +| [DOI Foundation](https://www.doi.org/) | | +| [Elsevier](https://www.elsevier.com/) | | +| [Google Scholar](https://scholar.google.com/) | | +| [Gemeinsamer Verbundkatalog](https://www.gbv.de/) | | +| [IACR](https://www.iacr.org/) | | +| [IEEEXplore](https://ieeexplore.ieee.org/Xplore/home.jsp) | | +| [INSPIRE](https://inspirehep.net/) | | +| [JSTOR](https://www.jstor.org/) | | +| [Library of Congress](https://lccn.loc.gov/) | | +| [National Library of Medicine](https://www.ncbi.nlm.nih.gov/) | | +| [MathSciNet](http://www.ams.org/mathscinet) | | +| [mEDRA](https://medra.org/) | | +| [Mr. DLib](https://mr-dlib.org/) [1] | | +| [Openlibrary](https://openlibrary.org) | | +| [ResearchGate](https://www.researchgate.net/) | | +| [IETF Datatracker](https://datatracker.ietf.org/) | | +| [Semantic Scholar](https://www.semanticscholar.org/), powered by [Allen Institute for AI](https://allenai.org/) | | +| [Springer Nature](https://dev.springernature.com/) | | +| [The SAO/NASA Astrophysics Data System](https://ui.adsabs.harvard.edu/) | | +| [Unpaywall](https://unpaywall.org/) | | +| [zbMATH Open](https://www.zbmath.org) | | + +[1]: *Note: The Mr. DLib service is used for the related articles tab in the entry editor and collects also your language, your browser and operating system (by default*disabled*).* + +## JabRef Browser Extension + +No personal data (like name, email address, billing address or credit card) is collected by the Browser Extension itself. +But be aware that the browser Extension uses Zotero services, where [Zotero's Privacy Policy](https://www.zotero.org/support/privacy) applies. + +### Collecting information + +When actively used, the extension has access to the current website and its content to process citation information. In particular, the following information is used: + +- the url of the current website you are visiting, +- the content of the current website. + +### Storing information + +No data will be stored by the Browser Extension. + +### Sharing information + +The Browser Extension does not share any data except with the local instance of the JabRef software application, which stores the citation data as a new entry in its library. + +## Links to other Websites + +Our Service may contain links to other websites that are not operated by us. If you click on a third party link, you will be directed to that third party's site. We strongly advise you to review the Privacy Policy of every site you visit. + +We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services. + +## Changes to this Privacy Policy + +This privacy policy may be changed eventually. +We encourage you to check this Privacy Policy periodically for any changes. +Any material change will be mentioned in the changelog of the desktop application and in our [blog](https://blog.jabref.org/). + +This privacy policy is in effect as of the day mentioned as "last updated" above and will remain in effect except with respect to any changes in its provisions in the future, which will be in effect immediately after being posted on this page. + +## Contact + +If you get in touch with us, we may aks you to provide us with certain personal information (e.g. name and email address) to stay in contact with you. +For any questions or concerns regarding the privacy policy, please send us an email to or write to + +JabRef e.V. +Josef-Lanner-Str. 9 +71069 Sindelfingen +Germany diff --git a/README.md b/README.md index 9412c88345c..f8e65405ad6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ JabRef is an open-source, cross-platform citation and reference management tool. Stay on top of your literature: JabRef helps you to collect and organize sources, find the paper you need and discover the latest research. -[![main table](docs/images/jabref-mainscreen.png)](http://www.jabref.org/img/jabref-mainscreen.png) + +![main table](docs/images/jabref-mainscreen.png) ## Features @@ -23,7 +24,7 @@ It supports you in every step of your research work. - Group your research into hierarchical collections and organize research items based on keywords/tags, search terms or your manual assignments - Advanced search and filter features -- Complete and fix bibliographic data by comparing with curated online catalogues such as Google Scholar, Springer or MathSciNet +- Complete and fix bibliographic data by comparing with curated online catalogs such as Google Scholar, Springer or MathSciNet - Customizable citation key generator - Customize and add new metadata fields or reference types - Find and merge duplicates @@ -41,7 +42,7 @@ It supports you in every step of your research work. ### Share - Many built-in export options or create your export format -- Library is saved as a simple text file and thus it is easy to share with others via Dropbox and is version-control friendly +- Library is saved as a simple text file, and thus it is easy to share with others via Dropbox and is version-control friendly - Work in a team: sync the contents of your library via a SQL database ## Installation @@ -54,7 +55,7 @@ Please see our [Installation Guide](https://docs.jabref.org/installation). ## Bug Reports, Suggestions, Other Feedback [![Donation](https://img.shields.io/badge/donate%20to-jabref-orange.svg)](https://donations.jabref.org) -[![Paypal Donate](https://img.shields.io/badge/donate-paypal-00457c.svg?logo=paypal&style=flat-square)](https://paypal.me/JabRef) +[![PayPal Donate](https://img.shields.io/badge/donate-paypal-00457c.svg?logo=paypal&style=flat-square)](https://paypal.me/JabRef) We are thankful for any bug reports or other feedback. If you have ideas for new features you want to be included in JabRef, tell us in [the feature section](http://discourse.jabref.org/c/features) of our forum! @@ -94,10 +95,10 @@ When you want to develop, it is necessary to generate additional sources using ` and then generate the Eclipse `gradlew eclipse`. For IntelliJ IDEA, just import the project via a Gradle Import by pointing at the `build.gradle`. -`gradlew test` executes all tests. We use [Github Actions](https://github.com/JabRef/jabref/actions) for executing the tests after each commit. For developing, it is sufficient to locally only run the associated test for the classes you changed. Github will report any other failure. +`gradlew test` executes all tests. We use [GitHub Actions](https://github.com/JabRef/jabref/actions) for executing the tests after each commit. For developing, it is sufficient to locally only run the associated test for the classes you changed. Github will report any other failure. ## Sponsoring -JabRef development is powered by YourKit Java Profiler [![YourKit Java Profiler](https://www.yourkit.com/images/yk_logo.png)](https://www.yourkit.com/java/profiler/) +JabRef development is powered by YourKit Java Profiler [![YourKit Java Profiler](https://www.yourkit.com/images/yk_logo.svg)](https://www.yourkit.com/java/profiler/) [JabRef]: https://www.jabref.org diff --git a/build.gradle b/build.gradle index e38208a9185..c4ef64f8788 100644 --- a/build.gradle +++ b/build.gradle @@ -8,11 +8,11 @@ plugins { id 'com.github.andygoossens.modernizer' version '1.8.0' - id 'me.champeau.gradle.jmh' version '0.5.3' + id 'me.champeau.jmh' version '0.7.1' id 'org.javamodularity.moduleplugin' version '1.8.12' - id 'org.openjfx.javafxplugin' version '0.0.13' + id 'org.openjfx.javafxplugin' version '0.0.14' id 'org.beryx.jlink' version '2.26.0' @@ -27,6 +27,8 @@ plugins { id 'project-report' id 'idea' + + id 'org.openrewrite.rewrite' version '6.1.25' } // Enable following for debugging @@ -45,6 +47,10 @@ java { // Workaround needed for Eclipse, probably because of https://github.com/gradle/gradle/issues/16922 // Should be removed as soon as Gradle 7.0.1 is released ( https://github.com/gradle/gradle/issues/16922#issuecomment-828217060 ) modularity.inferModulePath.set(false) + + toolchain { + languageVersion = JavaLanguageVersion.of(20) + } } application { @@ -82,6 +88,7 @@ repositories { maven { url 'https://oss.sonatype.org/content/groups/public' } maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' } maven { url 'https://jitpack.io' } + maven { url 'https://sandec.jfrog.io/artifactory/repo' } } configurations { @@ -104,45 +111,46 @@ javafx { } jacoco { - toolVersion = "0.8.8" + toolVersion = "0.8.10" } dependencies { // Include all jar-files in the 'lib' folder as dependencies implementation fileTree(dir: 'lib', includes: ['*.jar']) - implementation 'org.apache.pdfbox:pdfbox:3.0.0-RC1' - implementation 'org.apache.pdfbox:fontbox:3.0.0-RC1' - implementation 'org.apache.pdfbox:xmpbox:3.0.0-RC1' + implementation 'org.apache.pdfbox:pdfbox:3.0.0' + implementation 'org.apache.pdfbox:fontbox:3.0.0' + implementation 'org.apache.pdfbox:xmpbox:3.0.0' - implementation 'org.apache.lucene:lucene-core:9.5.0' - implementation 'org.apache.lucene:lucene-queryparser:9.5.0' - implementation 'org.apache.lucene:lucene-queries:9.5.0' - implementation 'org.apache.lucene:lucene-analysis-common:9.5.0' - implementation 'org.apache.lucene:lucene-highlighter:9.5.0' + implementation 'org.apache.lucene:lucene-core:9.7.0' + implementation 'org.apache.lucene:lucene-queryparser:9.7.0' + implementation 'org.apache.lucene:lucene-queries:9.7.0' + implementation 'org.apache.lucene:lucene-analysis-common:9.7.0' + implementation 'org.apache.lucene:lucene-highlighter:9.7.0' implementation group: 'org.apache.commons', name: 'commons-csv', version: '1.10.0' - implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0' - implementation 'com.h2database:h2-mvstore:2.1.214' + implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.13.0' + implementation 'com.h2database:h2-mvstore:2.2.220' // required for reading write-protected PDFs - see https://github.com/JabRef/jabref/pull/942#issuecomment-209252635 - implementation 'org.bouncycastle:bcprov-jdk18on:1.73' + implementation 'org.bouncycastle:bcprov-jdk18on:1.76' implementation 'commons-cli:commons-cli:1.5.0' - implementation 'org.libreoffice:unoloader:7.5.1' - implementation 'org.libreoffice:libreoffice:7.5.1' + implementation 'org.libreoffice:unoloader:7.6.0' + implementation 'org.libreoffice:libreoffice:7.5.3' implementation 'io.github.java-diff-utils:java-diff-utils:4.12' implementation 'info.debatty:java-string-similarity:2.0.0' + implementation 'com.github.javakeyring:java-keyring:1.0.4' - antlr4 'org.antlr:antlr4:4.12.0' - implementation 'org.antlr:antlr4-runtime:4.12.0' + antlr4 'org.antlr:antlr4:4.13.0' + implementation 'org.antlr:antlr4-runtime:4.13.0' - implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '6.5.0.202303070854-r' + implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '6.6.0.202305301015-r' - implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.14.2' - implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.14.2' + implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.15.2' + implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.15.2' implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.7.9' @@ -153,7 +161,7 @@ dependencies { exclude module: 'oraclepki' } - implementation ('com.google.guava:guava:31.1-jre') { + implementation ('com.google.guava:guava:32.1.2-jre') { // TODO: Remove this as soon as https://github.com/google/guava/issues/2960 is fixed exclude module: "jsr305" } @@ -168,18 +176,23 @@ dependencies { implementation 'com.github.sialcasa.mvvmFX:mvvmfx-validation:f195849ca9' //jitpack implementation 'de.saxsys:mvvmfx:1.8.0' implementation 'com.tobiasdiez:easybind:2.2.1-SNAPSHOT' - implementation 'org.fxmisc.flowless:flowless:0.7.0' - implementation 'org.fxmisc.richtext:richtextfx:0.11.0' - implementation 'com.jfoenix:jfoenix:9.0.10' + implementation 'org.fxmisc.flowless:flowless:0.7.1' + implementation 'org.fxmisc.richtext:richtextfx:0.11.1' + implementation (group: 'com.dlsc.gemsfx', name: 'gemsfx', version: '1.77.0') { + exclude module: 'javax.inject' // Split package, use only jakarta.inject + exclude group: 'org.apache.logging.log4j' + } + implementation 'org.controlsfx:controlsfx:11.1.2' - implementation 'org.jsoup:jsoup:1.15.4' - implementation 'com.konghq:unirest-java:3.14.2' + implementation 'org.jsoup:jsoup:1.16.1' + implementation 'com.konghq:unirest-java:3.14.5' implementation 'org.slf4j:slf4j-api:2.0.7' - implementation "org.tinylog:tinylog-api:2.6.1" - implementation "org.tinylog:slf4j-tinylog:2.6.1" - implementation "org.tinylog:tinylog-impl:2.6.1" + implementation "org.tinylog:tinylog-api:2.6.2" + implementation "org.tinylog:slf4j-tinylog:2.6.2" + implementation "org.tinylog:tinylog-impl:2.6.2" + // route all requests to java.util.logging to SLF4J (which in turn routes to tinylog) implementation 'org.slf4j:jul-to-slf4j:2.0.7' @@ -198,10 +211,11 @@ dependencies { exclude module: "log4j-core" } - implementation 'com.vladsch.flexmark:flexmark:0.64.0' + implementation 'com.vladsch.flexmark:flexmark:0.64.8' implementation group: 'net.harawata', name: 'appdirs', version: '1.2.1' + implementation group: 'org.jooq', name: 'jool', version: '0.9.15' // JAX-RS implemented by Jersey // API implementation 'jakarta.ws.rs:jakarta.ws.rs-api:3.1.0' @@ -220,23 +234,27 @@ dependencies { // Allow objects "magically" to be mapped to JSON using GSON // implementation 'org.glassfish.jersey.media:jersey-media-json-gson:3.1.1' - testImplementation 'io.github.classgraph:classgraph:4.8.157' - testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' - testImplementation 'org.junit.platform:junit-platform-launcher:1.9.2' + testImplementation 'io.github.classgraph:classgraph:4.8.162' + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' + testImplementation 'org.junit.platform:junit-platform-launcher:1.10.0' - testImplementation 'org.mockito:mockito-core:5.3.0' + testImplementation 'org.mockito:mockito-core:5.4.0' testImplementation 'org.xmlunit:xmlunit-core:2.9.1' testImplementation 'org.xmlunit:xmlunit-matchers:2.9.1' - testRuntimeOnly 'com.tngtech.archunit:archunit-junit5-engine:1.0.1' - testImplementation 'com.tngtech.archunit:archunit-junit5-api:1.0.1' + testRuntimeOnly 'com.tngtech.archunit:archunit-junit5-engine:1.1.0' + testImplementation 'com.tngtech.archunit:archunit-junit5-api:1.1.0' testImplementation "org.testfx:testfx-core:4.0.16-alpha" testImplementation "org.testfx:testfx-junit5:4.0.16-alpha" testImplementation "org.hamcrest:hamcrest-library:2.2" - checkstyle 'com.puppycrawl.tools:checkstyle:10.9.3' + checkstyle 'com.puppycrawl.tools:checkstyle:10.12.3' // xjc needs the runtime as well for the ant task, otherwise it fails xjc group: 'org.glassfish.jaxb', name: 'jaxb-xjc', version: '3.0.2' xjc group: 'org.glassfish.jaxb', name: 'jaxb-runtime', version: '3.0.2' + + rewrite(platform("org.openrewrite.recipe:rewrite-recipe-bom:2.2.1")) + rewrite("org.openrewrite.recipe:rewrite-logging-frameworks") + rewrite("org.openrewrite.recipe:rewrite-static-analysis") } clean { @@ -274,11 +292,10 @@ processResources { } } -task generateSource(dependsOn: ["generateBstGrammarSource", - "generateSearchGrammarSource", - "generateEndnoteSource", - "generateModsSource", - "generateCitaviSource"]) { +tasks.register('generateSource') { + dependsOn("generateBstGrammarSource", + "generateSearchGrammarSource", + "generateCitaviSource") group = 'JabRef' description 'Generates all necessary (Java) source files.' } @@ -295,26 +312,18 @@ tasks.register("generateBstGrammarSource", JavaExec) { } tasks.register("generateSearchGrammarSource", JavaExec) { - main = "org.antlr.v4.Tool" - classpath = configurations.antlr4 group = 'JabRef' description = "Generates java files for Search.g antlr4." + classpath = configurations.antlr4 + main = "org.antlr.v4.Tool" inputs.dir("src/main/antlr4/org/jabref/search/") outputs.dir("src-gen/main/java/org/jabref/search/") args = ["-o","src-gen/main/java/org/jabref/search" , "-visitor", "-no-listener", "-package", "org.jabref.search", "$projectDir/src/main/antlr4/org/jabref/search/Search.g4"] } -task generateEndnoteSource(type: XjcTask) { - group = 'JabRef' - description = "Generates java files for the endnote importer." - - schemaFile = "src/main/resources/xjc/endnote/endnote.xsd" - outputDirectory = "src-gen/main/java/" - javaPackage = "org.jabref.logic.importer.fileformat.endnote" -} -task generateCitaviSource(type: XjcTask) { +tasks.register('generateCitaviSource', XjcTask) { group = 'JabRef' description = "Generates java files for the citavi importer." @@ -323,18 +332,7 @@ task generateCitaviSource(type: XjcTask) { javaPackage = "org.jabref.logic.importer.fileformat.citavi" } -task generateModsSource(type: XjcTask) { - group = 'JabRef' - description = "Generates java files for the mods importer." - - schemaFile = "src/main/resources/xjc/mods/mods-3-7.xsd" - bindingFile = "src/main/resources/xjc/mods/mods-binding.xjb" - outputDirectory = "src-gen/main/java" - javaPackage = "org.jabref.logic.importer.fileformat.mods" - arguments = '-npa' -} - -task generateJournalAbbreviationList(type: JournalAbbreviationConverter) { +tasks.register('generateJournalAbbreviationList', JournalAbbreviationConverter) { group = 'JabRef' description = "Converts the comma-separated journal abbreviation file to a H2 MVStore." @@ -345,7 +343,7 @@ task generateJournalAbbreviationList(type: JournalAbbreviationConverter) { outputDir = file("src/main/resources/journals") } -tasks.withType(JavaCompile) { +tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' } @@ -371,8 +369,6 @@ run { 'javafx.controls/com.sun.javafx.scene.control' : 'org.jabref', 'org.controlsfx.controls/impl.org.controlsfx.skin' : 'org.jabref', - 'javafx.controls/com.sun.javafx.scene.control.behavior' : 'com.jfoenix', - // Not sure why we need to restate the controlfx exports // Taken from here: https://github.com/controlsfx/controlsfx/blob/9.0.0/build.gradle#L1 'javafx.graphics/com.sun.javafx.scene' : 'org.controlsfx.controls', @@ -393,12 +389,7 @@ run { 'javafx.controls/com.sun.javafx.scene.control' : 'org.jabref', 'javafx.controls/javafx.scene.control.skin' : 'org.controlsfx.controls', - 'javafx.graphics/javafx.scene' : 'org.controlsfx.controls', - - 'javafx.controls/com.sun.javafx.scene.control.behavior' : 'com.jfoenix', - 'javafx.base/com.sun.javafx.binding' : 'com.jfoenix', - 'javafx.graphics/com.sun.javafx.stage' : 'com.jfoenix', - 'javafx.base/com.sun.javafx.event' : 'com.jfoenix' + 'javafx.graphics/javafx.scene' : 'org.controlsfx.controls' ] } @@ -412,9 +403,9 @@ run { javadoc { options { encoding = 'UTF-8' - version = true - author = true - addMultilineStringsOption("-add-exports").setValue([ + version = false + author = false + addMultilineStringsOption("-add-exports").setValue([ 'javafx.controls/com.sun.javafx.scene.control=org.jabref', 'org.controlsfx.controls/impl.org.controlsfx.skin=org.jabref' ]) @@ -524,8 +515,37 @@ checkstyle { sourceSets = [] } +rewrite { + activeRecipe( + 'org.jabref.config.rewrite.cleanup' + ) + exclusion ( + "build.gradle", + "buildSrc/build.gradle", + "eclipse.gradle", + "settings.gradle", + "src-gen/**", + "src/main/resources/**", + "src/test/resources/**", + "**/module-info.java", + "**/*.py", + "**/*.xml", + "**/*.yml" + ) + plainTextMask("**/*.md") + failOnDryRunResults = true +} + +modernizer { + failOnViolations = false + includeTestClasses = true + exclusions = [ + 'java/util/Optional.get:()Ljava/lang/Object;' + ] +} + // Release tasks -task deleteInstallerTemp(type: Delete) { +tasks.register('deleteInstallerTemp', Delete) { delete "$buildDir/installer" } @@ -593,7 +613,8 @@ jlink { 'org.mariadb.jdbc.credential.env.EnvCredentialPlugin', 'org.mariadb.jdbc.credential.system.PropertiesCredentialPlugin' provides 'java.security.Provider' with 'org.bouncycastle.jce.provider.BouncyCastleProvider', - 'org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider' } + 'org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider' + } jpackage { outputDir = "distribution" @@ -615,6 +636,7 @@ jlink { '--win-menu-group', "JabRef", '--temp', "$buildDir/installer", '--resource-dir', "${projectDir}/buildres/windows", + '--license-file', "${projectDir}/buildres/LICENSE_with_Privacy.md", '--file-associations', "${projectDir}/buildres/windows/bibtexAssociations.properties" ] } @@ -644,8 +666,7 @@ jlink { '--icon', "${projectDir}/src/main/resources/icons/jabref.icns", '--resource-dir', "${projectDir}/buildres/mac" ] - // Due to a signing bug in jpackage we have to first resign the created app and therefore build the dmg manually - // See https://bugs.openjdk.java.net/browse/JDK-8251892 for details + // Notarized mac images and packages are built on the pipeline only skipInstaller = true installerOptions = [ '--verbose', diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 4d40a0a8944..2618578ed2a 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -1,4 +1,7 @@ -apply plugin: 'java' +plugins { + id 'java' + id 'org.openjfx.javafxplugin' version '0.0.14' +} repositories { mavenLocal() @@ -6,17 +9,14 @@ repositories { } dependencies { - implementation 'com.h2database:h2-mvstore:2.1.214' + implementation 'com.h2database:h2-mvstore:2.2.220' implementation 'org.apache.commons:commons-csv:1.10.0' implementation 'org.slf4j:slf4j-api:2.0.7' } -sourceSets{ - main { - java { - srcDir "src/copied" - } - } +javafx { + version = "20" + modules = [ 'javafx.base' ] } /** diff --git a/buildSrc/src/copied/java/org/jabref/logic/journals/JournalAbbreviationPreferences.java b/buildSrc/src/copied/java/org/jabref/logic/journals/JournalAbbreviationPreferences.java deleted file mode 100644 index 442fda33ddc..00000000000 --- a/buildSrc/src/copied/java/org/jabref/logic/journals/JournalAbbreviationPreferences.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.jabref.logic.journals; - -import java.nio.charset.Charset; -import java.util.List; - -public class JournalAbbreviationPreferences { - - private final Charset defaultEncoding; - private List externalJournalLists; - private boolean useFJournalField; - - public JournalAbbreviationPreferences(List externalJournalLists, Charset defaultEncoding, boolean useFJournalField) { - this.externalJournalLists = externalJournalLists; - this.defaultEncoding = defaultEncoding; - this.useFJournalField = useFJournalField; - } - - public List getExternalJournalLists() { - return externalJournalLists; - } - - public void setExternalJournalLists(List externalJournalLists) { - this.externalJournalLists = externalJournalLists; - } - - public Charset getDefaultEncoding() { - return defaultEncoding; - } - - public boolean useAMSFJournalFieldForAbbrevAndUnabbrev() { - return useFJournalField; - } - - public void setUseAMSFJournalFieldForAbbrevAndUnabbrev(boolean useFJournalField) { - this.useFJournalField = useFJournalField; - } -} diff --git a/buildSrc/src/main/java/module-info.java b/buildSrc/src/main/java/module-info.java new file mode 100644 index 00000000000..3b5988433e7 --- /dev/null +++ b/buildSrc/src/main/java/module-info.java @@ -0,0 +1,3 @@ +open module org.jabref { + requires javafx.base; +} diff --git a/buildSrc/src/copied/java/org/jabref/logic/journals/Abbreviation.java b/buildSrc/src/main/java/org/jabref/logic/journals/Abbreviation.java similarity index 100% rename from buildSrc/src/copied/java/org/jabref/logic/journals/Abbreviation.java rename to buildSrc/src/main/java/org/jabref/logic/journals/Abbreviation.java diff --git a/buildSrc/src/copied/java/org/jabref/logic/journals/AbbreviationFormat.java b/buildSrc/src/main/java/org/jabref/logic/journals/AbbreviationFormat.java similarity index 100% rename from buildSrc/src/copied/java/org/jabref/logic/journals/AbbreviationFormat.java rename to buildSrc/src/main/java/org/jabref/logic/journals/AbbreviationFormat.java diff --git a/buildSrc/src/copied/java/org/jabref/logic/journals/AbbreviationParser.java b/buildSrc/src/main/java/org/jabref/logic/journals/AbbreviationParser.java similarity index 100% rename from buildSrc/src/copied/java/org/jabref/logic/journals/AbbreviationParser.java rename to buildSrc/src/main/java/org/jabref/logic/journals/AbbreviationParser.java diff --git a/buildSrc/src/copied/java/org/jabref/logic/journals/AbbreviationWriter.java b/buildSrc/src/main/java/org/jabref/logic/journals/AbbreviationWriter.java similarity index 100% rename from buildSrc/src/copied/java/org/jabref/logic/journals/AbbreviationWriter.java rename to buildSrc/src/main/java/org/jabref/logic/journals/AbbreviationWriter.java diff --git a/buildSrc/src/copied/java/org/jabref/logic/journals/JournalAbbreviationLoader.java b/buildSrc/src/main/java/org/jabref/logic/journals/JournalAbbreviationLoader.java similarity index 96% rename from buildSrc/src/copied/java/org/jabref/logic/journals/JournalAbbreviationLoader.java rename to buildSrc/src/main/java/org/jabref/logic/journals/JournalAbbreviationLoader.java index 2c67a734a8b..1c0717a943c 100644 --- a/buildSrc/src/copied/java/org/jabref/logic/journals/JournalAbbreviationLoader.java +++ b/buildSrc/src/main/java/org/jabref/logic/journals/JournalAbbreviationLoader.java @@ -1,7 +1,6 @@ package org.jabref.logic.journals; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; @@ -62,6 +61,6 @@ public static JournalAbbreviationRepository loadRepository(JournalAbbreviationPr } public static JournalAbbreviationRepository loadBuiltInRepository() { - return loadRepository(new JournalAbbreviationPreferences(Collections.emptyList(), StandardCharsets.UTF_8, true)); + return loadRepository(new JournalAbbreviationPreferences(Collections.emptyList(), true)); } } diff --git a/buildSrc/src/main/java/org/jabref/logic/journals/JournalAbbreviationPreferences.java b/buildSrc/src/main/java/org/jabref/logic/journals/JournalAbbreviationPreferences.java new file mode 100644 index 00000000000..fb256bbe8ab --- /dev/null +++ b/buildSrc/src/main/java/org/jabref/logic/journals/JournalAbbreviationPreferences.java @@ -0,0 +1,41 @@ +package org.jabref.logic.journals; + +import java.util.List; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +public class JournalAbbreviationPreferences { + + private final ObservableList externalJournalLists; + private final BooleanProperty useFJournalField; + + public JournalAbbreviationPreferences(List externalJournalLists, + boolean useFJournalField) { + this.externalJournalLists = FXCollections.observableArrayList(externalJournalLists); + this.useFJournalField = new SimpleBooleanProperty(useFJournalField); + } + + public ObservableList getExternalJournalLists() { + return externalJournalLists; + } + + public void setExternalJournalLists(List list) { + externalJournalLists.clear(); + externalJournalLists.addAll(list); + } + + public boolean shouldUseFJournalField() { + return useFJournalField.get(); + } + + public BooleanProperty useFJournalFieldProperty() { + return useFJournalField; + } + + public void setUseFJournalField(boolean useFJournalField) { + this.useFJournalField.set(useFJournalField); + } +} diff --git a/buildSrc/src/copied/java/org/jabref/logic/journals/JournalAbbreviationRepository.java b/buildSrc/src/main/java/org/jabref/logic/journals/JournalAbbreviationRepository.java similarity index 100% rename from buildSrc/src/copied/java/org/jabref/logic/journals/JournalAbbreviationRepository.java rename to buildSrc/src/main/java/org/jabref/logic/journals/JournalAbbreviationRepository.java diff --git a/buildSrc/src/main/java/org/jabref/logic/journals/package-info.java b/buildSrc/src/main/java/org/jabref/logic/journals/package-info.java new file mode 100644 index 00000000000..4e7e2cbf82a --- /dev/null +++ b/buildSrc/src/main/java/org/jabref/logic/journals/package-info.java @@ -0,0 +1,4 @@ +/** + * This code is copied from src/main/java/org/jabref/logic/journals + */ +package org.jabref.logic.journals; diff --git a/buildres/LICENSE_with_Privacy.md b/buildres/LICENSE_with_Privacy.md new file mode 100644 index 00000000000..41264b1efcc --- /dev/null +++ b/buildres/LICENSE_with_Privacy.md @@ -0,0 +1,179 @@ +MIT License + +Copyright © 2003-2021 [JabRef Authors](https://github.com/JabRef/jabref/blob/master/AUTHORS) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- Privacy Policy ---- + +# Privacy Policy + +Last updated: 2023-08-24 + +Your privacy is a fundamental right JabRef e.V. respects and supports. +By using JabRef and its related online services, you choose to share +some of your personal information. In this Privacy Policy we explain how +we collect, use, and share information about you, along with the choices +you have. + +The term \'Personal information\' in this policy means any information +that either directly identifies you or can be somehow linked to you. +\'JabRef\' refers to the desktop application that is provided by JabRef +e.V. + +Please remember that no method of transmission over the Internet, or +method of electronic storage is absolute secure. While we strive to use +commercially acceptable means to protect your Personal Data, we cannot +guarantee its absolute security. Also, whenever you communicate through +the internet, your IP-Address will always be transmitted and retained by +third parties for technical and in some cases for legal reasons. + +## JabRef Desktop Application + +### Collecting information + +JabRef does not collect any personal information directly linked to you. +However, on certain occasions JabRef will send some information to the +online services of JabRef e.V.: + +- On application start, JabRef will check for the latest version + online (by default *enabled*). +- Information about a journal you are citing when looking for more + information about this journal, using our journal database (by + default *enabled*). +- A pdf document you automatically want to extract citation + information from, using our GROBID service (by default *disabled*). +- Anonymized statistical data on the use of the graphical user + interface for internal analysis purposes (by default *disabled*). + +### Storing information + +JabRef only stores the following personal information locally on your +computer: + +- Your proxy username and password, if you decide to store them (by + default *disabled*). +- Any personal API key you use to access third party online services + (by default *disabled*). + +### Sharing information + +Certain operations you perform in JabRef may trigger requests to public +third-party services such as Zotero, Crossref or the Library of Congress +for metadata retrieval. These third parties may log additional +information besides your IP address and the search terms (e.g., DOI, +ISBN or the current URL) according to their privacy policies. + +These third-party services are the following: + +Service;Privacy Policy +[ACM](https://www.acm.org/); +[ACS Publications](https://pubs.acs.org/); +[APS Advancing Physics](https://harvest.aps.org/); +[arXiv.org](https://arxiv.org/); +[Bibliotheksverbund Bayern](https://www.bib-bvb.de/); +[Biodiversity Heritage Library](https://www.biodiversitylibrary.org/); +[Collection of Computer Science Bibliographies](http://liinwww.ira.uka.de/);**currently unavailable**, offline +[CrossRef](https://www.crossref.org/); +[dblp](https://dblp.uni-trier.de/); +[Directory of Open Access Books](https://www.doabooks.org/); +[Digitala Vetenskapliga Arkivet](https://www.diva-portal.org/); +[DOI Foundation](https://www.doi.org/); +[Elsevier](https://www.elsevier.com/); +[Google Scholar](https://scholar.google.com/); +[Gemeinsamer Verbundkatalog](https://www.gbv.de/); +[IACR](https://www.iacr.org/); +[IEEEXplore](https://ieeexplore.ieee.org/Xplore/home.jsp); +[INSPIRE](https://inspirehep.net/); +[JSTOR](https://www.jstor.org/); +[Library of Congress](https://lccn.loc.gov/); +[National Library of Medicine](https://www.ncbi.nlm.nih.gov/); +[MathSciNet](http://www.ams.org/mathscinet); +[mEDRA](https://medra.org/); +[Mr. DLib](https://mr-dlib.org/) [1]; +[Openlibrary](https://openlibrary.org); +[ResearchGate](https://www.researchgate.net/); +[IETF Datatracker](https://datatracker.ietf.org/); +[Semantic Scholar](https://www.semanticscholar.org/), powered by [Allen Institute for AI](https://allenai.org/); +[Springer Nature](https://dev.springernature.com/); +[The SAO/NASA Astrophysics Data System](https://ui.adsabs.harvard.edu/); +[Unpaywall](https://unpaywall.org/); +[zbMATH Open](https://www.zbmath.org); + +[1]: *Note: The Mr. DLib service is used for the related articles tab in the entry editor and collects also your language, your browser and operating system (by default*disabled*).*; + +## JabRef Browser Extension + +No personal data (like name, email address, billing address or credit +card) is collected by the Browser Extension itself. But be aware that +the browser Extension uses Zotero services, where [Zotero\'s Privacy +Policy](https://www.zotero.org/support/privacy) applies. + +### Collecting information + +When actively used, the extension has access to the current website and +its content to process citation information. In particular, the +following information is used: + +- the url of the current website you are visiting, +- the content of the current website. + +### Storing information + +No data will be stored by the Browser Extension. + +### Sharing information + +The Browser Extension does not share any data except with the local +instance of the JabRef software application, which stores the citation +data as a new entry in its library. + +## Links to other Websites + +Our Service may contain links to other websites that are not operated by +us. If you click on a third party link, you will be directed to that +third party\'s site. We strongly advise you to review the Privacy Policy +of every site you visit. + +We have no control over and assume no responsibility for the content, +privacy policies or practices of any third party sites or services. + +## Changes to this Privacy Policy + +This privacy policy may be changed eventually. We encourage you to check +this Privacy Policy periodically for any changes. Any material change +will be mentioned in the changelog of the desktop application and in our +[blog](https://blog.jabref.org/). + +This privacy policy is in effect as of the day mentioned as \"last +updated\" above and will remain in effect except with respect to any +changes in its provisions in the future, which will be in effect +immediately after being posted on this page. + +## Contact + +If you get in touch with us, we may aks you to provide us with certain +personal information (e.g. name and email address) to stay in contact +with you. For any questions or concerns regarding the privacy policy, +please send us an email to or write to + +JabRef e.V.\ +Josef-Lanner-Str. 9\ +71069 Sindelfingen\ +Germany diff --git a/buildres/csl/csl-locales/locales-ca-AD.xml b/buildres/csl/csl-locales/locales-ca-AD.xml index d3ada321d48..66aaa0969fa 100644 --- a/buildres/csl/csl-locales/locales-ca-AD.xml +++ b/buildres/csl/csl-locales/locales-ca-AD.xml @@ -541,7 +541,7 @@ editat per editat per il·lustrat per - entrevistat per + entrevista feta per a per traduït per diff --git a/buildres/csl/csl-styles/Gemfile.lock b/buildres/csl/csl-styles/Gemfile.lock index 8414bfbd6a4..f48b77908e0 100644 --- a/buildres/csl/csl-styles/Gemfile.lock +++ b/buildres/csl/csl-styles/Gemfile.lock @@ -62,20 +62,20 @@ GEM ruby-progressbar (~> 1.4) git_diff (0.4.3) hashdiff (0.3.7) - mini_portile2 (2.8.0) + mini_portile2 (2.8.1) multipart-post (2.1.1) namae (1.1.1) - nokogiri (1.13.10) + nokogiri (1.14.3) mini_portile2 (~> 2.8.0) racc (~> 1.4) - nokogiri (1.13.10-x64-mingw32) + nokogiri (1.14.3-x64-mingw32) racc (~> 1.4) octokit (4.21.0) faraday (>= 0.9) sawyer (~> 0.8.0, >= 0.5.3) ostruct (0.5.2) public_suffix (4.0.6) - racc (1.6.1) + racc (1.6.2) rake (13.0.6) reverse_markdown (2.1.1) nokogiri diff --git a/buildres/csl/csl-styles/acm-sig-proceedings-long-author-list.csl b/buildres/csl/csl-styles/acm-sig-proceedings-long-author-list.csl index 567c94d5178..11d794706f1 100644 --- a/buildres/csl/csl-styles/acm-sig-proceedings-long-author-list.csl +++ b/buildres/csl/csl-styles/acm-sig-proceedings-long-author-list.csl @@ -4,7 +4,7 @@ ACM SIG Proceedings ("et al." for 15+ authors) http://www.zotero.org/styles/acm-sig-proceedings-long-author-list - + Naeem Esfahani nesfaha2@gmu.edu diff --git a/buildres/csl/csl-styles/acm-sig-proceedings.csl b/buildres/csl/csl-styles/acm-sig-proceedings.csl index 812569bd11b..41ef85c8283 100644 --- a/buildres/csl/csl-styles/acm-sig-proceedings.csl +++ b/buildres/csl/csl-styles/acm-sig-proceedings.csl @@ -4,7 +4,7 @@ ACM SIG Proceedings ("et al." for 3+ authors) http://www.zotero.org/styles/acm-sig-proceedings - + Naeem Esfahani nesfaha2@gmu.edu diff --git a/buildres/csl/csl-styles/acta-palaeontologica-polonica.csl b/buildres/csl/csl-styles/acta-palaeontologica-polonica.csl index 91a25d235ca..2c3605be38d 100644 --- a/buildres/csl/csl-styles/acta-palaeontologica-polonica.csl +++ b/buildres/csl/csl-styles/acta-palaeontologica-polonica.csl @@ -8,7 +8,8 @@ Martin R. Smith - martins@gmail.com + martin.smith@durham.ac.uk + https://smithlabdurham.github.io/ Benjamin C. Moon @@ -84,7 +85,8 @@ - + + diff --git a/buildres/csl/csl-styles/al-jamiah-journal-of-islamic-studies.csl b/buildres/csl/csl-styles/al-jamiah-journal-of-islamic-studies.csl index aa9b034ef83..f3b0720c038 100644 --- a/buildres/csl/csl-styles/al-jamiah-journal-of-islamic-studies.csl +++ b/buildres/csl/csl-styles/al-jamiah-journal-of-islamic-studies.csl @@ -1,11 +1,11 @@ - diff --git a/buildres/csl/csl-styles/apa-no-ampersand.csl b/buildres/csl/csl-styles/apa-no-ampersand.csl index 777cc6d13a6..b26a8426573 100644 --- a/buildres/csl/csl-styles/apa-no-ampersand.csl +++ b/buildres/csl/csl-styles/apa-no-ampersand.csl @@ -35,295 +35,60 @@ ca. B.C.E. C.E. - personal communication - letter issue issues - - - - - persoonlike kommunikasie - brief - - - - - اتصال شخصي - خطاب - - - - - лична комуникация - писмо - - - - - comunicació personal - carta - - - - - osobní komunikace - dopis - - - - - cyfathrebu personol - llythyr + computer software et al. - personlig kommunikation - brev et al. - persönliche Kommunikation - Brief - - - - - προσωπική επικοινωνία - επιστολή de - comunicación personal - carta - - - - - isiklik suhtlus - kiri - - - - - komunikazio pertsonala - gutuna - - - - - ارتباط شخصی - نامه - - - - - henkilökohtainen viestintä - kirje - communication personnelle - lettre éd. éds. - - - תקשורת אישית - מכתב - - - - - osobna komunikacija - pismo - - - - - személyes kommunikáció - levél - - - - - komunikasi pribadi - surat - - - - - persónuleg samskipti - bréf - - - - - comunicazione personale - lettera - - - - - 個人的なやり取り - 手紙 - - - - - 개인 서신 - 편지 - - - - - - epistula - - - - - communicationis personalis - - - - - - personīga komunikācija - vēstule - - - - - хувийн харилцаа холбоо - захиа - - et al. - personlig kommunikasjon - brev et al. - persoonlijke communicatie - brief et al. - personlig kommunikasjon - brev - - - - - osobista komunikacja - list - - - - - comunicação pessoal - carta - - - - - comunicare personală - scrisoare - - - - - личная переписка - письмо - - - - - osobná komunikácia - list - - - - - osebna komunikacija - pismo - - - - - лична комуникација - писмо - - - - - personlig kommunikation - brev - - - - - การสื่อสารส่วนบุคคล - จดหมาย - - - - - kişisel iletişim - mektup - - - - - особисте спілкування - лист - - - - - giao tiếp cá nhân - thư - - - - - 的私人交流 - 信函 - - - - - 私人通訊 - 信函 @@ -399,8 +164,7 @@ - - + @@ -501,9 +265,6 @@ - - - @@ -641,23 +402,23 @@ + + + + - + - - - - + - + - - + @@ -700,13 +461,11 @@ - - + - - + @@ -884,7 +643,7 @@ - + @@ -894,7 +653,7 @@ - + @@ -995,14 +754,7 @@ - - - - - - - - + @@ -1014,8 +766,10 @@ - - + + + + @@ -1036,7 +790,7 @@ - + @@ -1067,7 +821,7 @@ - + @@ -1175,8 +929,7 @@ - - + @@ -1225,8 +978,7 @@ - - + @@ -1274,8 +1026,7 @@ - - + @@ -1329,18 +1080,16 @@ - - + - - - + + - + @@ -1348,7 +1097,7 @@ - + @@ -1361,18 +1110,16 @@ - - + - - - + + - + @@ -1380,7 +1127,7 @@ - + @@ -1598,7 +1345,7 @@ - + @@ -1619,83 +1366,56 @@ + - - - - - - - - - - + + + + + + + + + - - + + + - - - - - + - + + + + - - + - + + - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1714,7 +1434,7 @@ - + @@ -1743,8 +1463,7 @@ - - + @@ -1761,8 +1480,7 @@ - - + @@ -1778,8 +1496,7 @@ - - + @@ -1814,8 +1531,7 @@ - - + @@ -1837,8 +1553,7 @@ - - + @@ -1864,7 +1579,7 @@ - + @@ -1879,7 +1594,7 @@ - + diff --git a/buildres/csl/csl-styles/apa-no-doi-no-issue.csl b/buildres/csl/csl-styles/apa-no-doi-no-issue.csl index 5c314996729..d3dffbf8ff1 100644 --- a/buildres/csl/csl-styles/apa-no-doi-no-issue.csl +++ b/buildres/csl/csl-styles/apa-no-doi-no-issue.csl @@ -1102,14 +1102,8 @@ - - - - - - - + diff --git a/buildres/csl/csl-styles/apa-no-initials.csl b/buildres/csl/csl-styles/apa-no-initials.csl index b844044aa18..4fc744b9b03 100644 --- a/buildres/csl/csl-styles/apa-no-initials.csl +++ b/buildres/csl/csl-styles/apa-no-initials.csl @@ -35,295 +35,60 @@ ca. B.C.E. C.E. - personal communication - letter issue issues - - - - - persoonlike kommunikasie - brief - - - - - اتصال شخصي - خطاب - - - - - лична комуникация - писмо - - - - - comunicació personal - carta - - - - - osobní komunikace - dopis - - - - - cyfathrebu personol - llythyr + computer software et al. - personlig kommunikation - brev et al. - persönliche Kommunikation - Brief - - - - - προσωπική επικοινωνία - επιστολή de - comunicación personal - carta - - - - - isiklik suhtlus - kiri - - - - - komunikazio pertsonala - gutuna - - - - - ارتباط شخصی - نامه - - - - - henkilökohtainen viestintä - kirje - communication personnelle - lettre éd. éds. - - - תקשורת אישית - מכתב - - - - - osobna komunikacija - pismo - - - - - személyes kommunikáció - levél - - - - - komunikasi pribadi - surat - - - - - persónuleg samskipti - bréf - - - - - comunicazione personale - lettera - - - - - 個人的なやり取り - 手紙 - - - - - 개인 서신 - 편지 - - - - - - epistula - - - - - communicationis personalis - - - - - - personīga komunikācija - vēstule - - - - - хувийн харилцаа холбоо - захиа - - et al. - personlig kommunikasjon - brev et al. - persoonlijke communicatie - brief et al. - personlig kommunikasjon - brev - - - - - osobista komunikacja - list - - - - - comunicação pessoal - carta - - - - - comunicare personală - scrisoare - - - - - личная переписка - письмо - - - - - osobná komunikácia - list - - - - - osebna komunikacija - pismo - - - - - лична комуникација - писмо - - - - - personlig kommunikation - brev - - - - - การสื่อสารส่วนบุคคล - จดหมาย - - - - - kişisel iletişim - mektup - - - - - особисте спілкування - лист - - - - - giao tiếp cá nhân - thư - - - - - 的私人交流 - 信函 - - - - - 私人通訊 - 信函 @@ -399,8 +164,7 @@ - - + @@ -501,9 +265,6 @@ - - - @@ -641,23 +402,23 @@ + + + + - + - - - - + - + - - + @@ -700,13 +461,11 @@ - - + - - + @@ -884,7 +643,7 @@ - + @@ -894,7 +653,7 @@ - + @@ -995,14 +754,7 @@ - - - - - - - - + @@ -1014,8 +766,10 @@ - - + + + + @@ -1036,7 +790,7 @@ - + @@ -1067,7 +821,7 @@ - + @@ -1175,8 +929,7 @@ - - + @@ -1225,8 +978,7 @@ - - + @@ -1274,8 +1026,7 @@ - - + @@ -1329,18 +1080,16 @@ - - + - - - + + - + @@ -1348,7 +1097,7 @@ - + @@ -1361,18 +1110,16 @@ - - + - - - + + - + @@ -1380,7 +1127,7 @@ - + @@ -1598,7 +1345,7 @@ - + @@ -1619,83 +1366,56 @@ + - - - - - - - - - - + + + + + + + + + - - + + + - - - - - + - + + + + - - + + - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1714,7 +1434,7 @@ - + @@ -1743,8 +1463,7 @@ - - + @@ -1761,8 +1480,7 @@ - - + @@ -1778,8 +1496,7 @@ - - + @@ -1814,8 +1531,7 @@ - - + @@ -1837,8 +1553,7 @@ - - + @@ -1864,7 +1579,7 @@ - + @@ -1879,7 +1594,7 @@ - + diff --git a/buildres/csl/csl-styles/apa-numeric-superscript-brackets.csl b/buildres/csl/csl-styles/apa-numeric-superscript-brackets.csl index d9134ea1435..53c2f69fed4 100644 --- a/buildres/csl/csl-styles/apa-numeric-superscript-brackets.csl +++ b/buildres/csl/csl-styles/apa-numeric-superscript-brackets.csl @@ -36,51 +36,30 @@ ca. B.C.E. C.E. - personal communication - letter issue issues - - - - - persoonlike kommunikasie - brief - - - - - اتصال شخصي - خطاب + computer software et al. - personlig kommunikation - brev et al. - persönliche Kommunikation - Brief de - comunicación personal - carta - communication personnelle - lettre éd. éds. @@ -90,43 +69,27 @@ et al. - personlig kommunikasjon - brev et al. - persoonlijke communicatie - brief et al. - personlig kommunikasjon - brev - - - - - личная переписка - письмо - - - - - kişisel iletişim - mektup @@ -202,8 +165,7 @@ - - + @@ -304,9 +266,6 @@ - - - @@ -444,23 +403,23 @@ + + + + - + - - + + - - - - - + @@ -503,13 +462,11 @@ - - + - - + @@ -687,7 +644,7 @@ - + @@ -697,7 +654,7 @@ - + @@ -798,14 +755,7 @@ - - - - - - - - + @@ -817,8 +767,10 @@ - - + + + + @@ -839,7 +791,7 @@ - + @@ -870,7 +822,7 @@ - + @@ -978,8 +930,7 @@ - - + @@ -1028,8 +979,7 @@ - - + @@ -1077,8 +1027,7 @@ - - + @@ -1132,18 +1081,16 @@ - - + - - - + + - + @@ -1151,7 +1098,7 @@ - + @@ -1164,18 +1111,16 @@ - - + - - - + + - + @@ -1183,7 +1128,7 @@ - + @@ -1401,7 +1346,7 @@ - + @@ -1422,83 +1367,56 @@ + - - - - - - - - - - + + + + + + + + + - - + + + - - - - - + - + + + + - - + + - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1517,7 +1435,7 @@ - + @@ -1546,8 +1464,7 @@ - - + @@ -1564,8 +1481,7 @@ - - + @@ -1581,8 +1497,7 @@ - - + @@ -1617,8 +1532,7 @@ - - + @@ -1640,8 +1554,7 @@ - - + diff --git a/buildres/csl/csl-styles/apa-numeric-superscript.csl b/buildres/csl/csl-styles/apa-numeric-superscript.csl index 42b9b90fa01..5e15fc48c4f 100644 --- a/buildres/csl/csl-styles/apa-numeric-superscript.csl +++ b/buildres/csl/csl-styles/apa-numeric-superscript.csl @@ -36,51 +36,30 @@ ca. B.C.E. C.E. - personal communication - letter issue issues - - - - - persoonlike kommunikasie - brief - - - - - اتصال شخصي - خطاب + computer software et al. - personlig kommunikation - brev et al. - persönliche Kommunikation - Brief de - comunicación personal - carta - communication personnelle - lettre éd. éds. @@ -90,43 +69,27 @@ et al. - personlig kommunikasjon - brev et al. - persoonlijke communicatie - brief et al. - personlig kommunikasjon - brev - - - - - личная переписка - письмо - - - - - kişisel iletişim - mektup @@ -202,8 +165,7 @@ - - + @@ -304,9 +266,6 @@ - - - @@ -444,23 +403,23 @@ + + + + - + - - + + - - - - - + @@ -503,13 +462,11 @@ - - + - - + @@ -687,7 +644,7 @@ - + @@ -697,7 +654,7 @@ - + @@ -798,14 +755,7 @@ - - - - - - - - + @@ -817,8 +767,10 @@ - - + + + + @@ -839,7 +791,7 @@ - + @@ -870,7 +822,7 @@ - + @@ -978,8 +930,7 @@ - - + @@ -1028,8 +979,7 @@ - - + @@ -1077,8 +1027,7 @@ - - + @@ -1132,18 +1081,16 @@ - - + - - - + + - + @@ -1151,7 +1098,7 @@ - + @@ -1164,18 +1111,16 @@ - - + - - - + + - + @@ -1183,7 +1128,7 @@ - + @@ -1401,7 +1346,7 @@ - + @@ -1422,83 +1367,56 @@ + - - - - - - - - - - + + + + + + + + + - - + + + - - - - - + - + + + + - - + + - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1517,7 +1435,7 @@ - + @@ -1546,8 +1464,7 @@ - - + @@ -1564,8 +1481,7 @@ - - + @@ -1581,8 +1497,7 @@ - - + @@ -1617,8 +1532,7 @@ - - + @@ -1640,8 +1554,7 @@ - - + diff --git a/buildres/csl/csl-styles/apa-old-doi-prefix.csl b/buildres/csl/csl-styles/apa-old-doi-prefix.csl index 2ede590912d..f25d36c0971 100644 --- a/buildres/csl/csl-styles/apa-old-doi-prefix.csl +++ b/buildres/csl/csl-styles/apa-old-doi-prefix.csl @@ -1096,14 +1096,8 @@ - - - - - - - + diff --git a/buildres/csl/csl-styles/apa-single-spaced.csl b/buildres/csl/csl-styles/apa-single-spaced.csl index be5cd95adbd..2793e6ac5eb 100644 --- a/buildres/csl/csl-styles/apa-single-spaced.csl +++ b/buildres/csl/csl-styles/apa-single-spaced.csl @@ -35,295 +35,60 @@ ca. B.C.E. C.E. - personal communication - letter issue issues - - - - - persoonlike kommunikasie - brief - - - - - اتصال شخصي - خطاب - - - - - лична комуникация - писмо - - - - - comunicació personal - carta - - - - - osobní komunikace - dopis - - - - - cyfathrebu personol - llythyr + computer software et al. - personlig kommunikation - brev et al. - persönliche Kommunikation - Brief - - - - - προσωπική επικοινωνία - επιστολή de - comunicación personal - carta - - - - - isiklik suhtlus - kiri - - - - - komunikazio pertsonala - gutuna - - - - - ارتباط شخصی - نامه - - - - - henkilökohtainen viestintä - kirje - communication personnelle - lettre éd. éds. - - - תקשורת אישית - מכתב - - - - - osobna komunikacija - pismo - - - - - személyes kommunikáció - levél - - - - - komunikasi pribadi - surat - - - - - persónuleg samskipti - bréf - - - - - comunicazione personale - lettera - - - - - 個人的なやり取り - 手紙 - - - - - 개인 서신 - 편지 - - - - - - epistula - - - - - communicationis personalis - - - - - - personīga komunikācija - vēstule - - - - - хувийн харилцаа холбоо - захиа - - et al. - personlig kommunikasjon - brev et al. - persoonlijke communicatie - brief et al. - personlig kommunikasjon - brev - - - - - osobista komunikacja - list - - - - - comunicação pessoal - carta - - - - - comunicare personală - scrisoare - - - - - личная переписка - письмо - - - - - osobná komunikácia - list - - - - - osebna komunikacija - pismo - - - - - лична комуникација - писмо - - - - - personlig kommunikation - brev - - - - - การสื่อสารส่วนบุคคล - จดหมาย - - - - - kişisel iletişim - mektup - - - - - особисте спілкування - лист - - - - - giao tiếp cá nhân - thư - - - - - 的私人交流 - 信函 - - - - - 私人通訊 - 信函 @@ -399,8 +164,7 @@ - - + @@ -501,9 +265,6 @@ - - - @@ -641,23 +402,23 @@ + + + + - + - - - - + - + - - + @@ -700,13 +461,11 @@ - - + - - + @@ -884,7 +643,7 @@ - + @@ -894,7 +653,7 @@ - + @@ -995,14 +754,7 @@ - - - - - - - - + @@ -1014,8 +766,10 @@ - - + + + + @@ -1036,7 +790,7 @@ - + @@ -1067,7 +821,7 @@ - + @@ -1175,8 +929,7 @@ - - + @@ -1225,8 +978,7 @@ - - + @@ -1274,8 +1026,7 @@ - - + @@ -1329,18 +1080,16 @@ - - + - - - + + - + @@ -1348,7 +1097,7 @@ - + @@ -1361,18 +1110,16 @@ - - + - - - + + - + @@ -1380,7 +1127,7 @@ - + @@ -1598,7 +1345,7 @@ - + @@ -1619,83 +1366,56 @@ + - - - - - - - - - - + + + + + + + + + - - + + + - - - - - + - + + + + - - + + - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1714,7 +1434,7 @@ - + @@ -1743,8 +1463,7 @@ - - + @@ -1761,8 +1480,7 @@ - - + @@ -1778,8 +1496,7 @@ - - + @@ -1814,8 +1531,7 @@ - - + @@ -1837,8 +1553,7 @@ - - + @@ -1864,7 +1579,7 @@ - + @@ -1879,7 +1594,7 @@ - + diff --git a/buildres/csl/csl-styles/apa-with-abstract.csl b/buildres/csl/csl-styles/apa-with-abstract.csl index 9524f1928d5..d16cd2cbcf7 100644 --- a/buildres/csl/csl-styles/apa-with-abstract.csl +++ b/buildres/csl/csl-styles/apa-with-abstract.csl @@ -35,295 +35,60 @@ ca. B.C.E. C.E. - personal communication - letter issue issues - - - - - persoonlike kommunikasie - brief - - - - - اتصال شخصي - خطاب - - - - - лична комуникация - писмо - - - - - comunicació personal - carta - - - - - osobní komunikace - dopis - - - - - cyfathrebu personol - llythyr + computer software et al. - personlig kommunikation - brev et al. - persönliche Kommunikation - Brief - - - - - προσωπική επικοινωνία - επιστολή de - comunicación personal - carta - - - - - isiklik suhtlus - kiri - - - - - komunikazio pertsonala - gutuna - - - - - ارتباط شخصی - نامه - - - - - henkilökohtainen viestintä - kirje - communication personnelle - lettre éd. éds. - - - תקשורת אישית - מכתב - - - - - osobna komunikacija - pismo - - - - - személyes kommunikáció - levél - - - - - komunikasi pribadi - surat - - - - - persónuleg samskipti - bréf - - - - - comunicazione personale - lettera - - - - - 個人的なやり取り - 手紙 - - - - - 개인 서신 - 편지 - - - - - - epistula - - - - - communicationis personalis - - - - - - personīga komunikācija - vēstule - - - - - хувийн харилцаа холбоо - захиа - - et al. - personlig kommunikasjon - brev et al. - persoonlijke communicatie - brief et al. - personlig kommunikasjon - brev - - - - - osobista komunikacja - list - - - - - comunicação pessoal - carta - - - - - comunicare personală - scrisoare - - - - - личная переписка - письмо - - - - - osobná komunikácia - list - - - - - osebna komunikacija - pismo - - - - - лична комуникација - писмо - - - - - personlig kommunikation - brev - - - - - การสื่อสารส่วนบุคคล - จดหมาย - - - - - kişisel iletişim - mektup - - - - - особисте спілкування - лист - - - - - giao tiếp cá nhân - thư - - - - - 的私人交流 - 信函 - - - - - 私人通訊 - 信函 @@ -399,8 +164,7 @@ - - + @@ -501,9 +265,6 @@ - - - @@ -641,23 +402,23 @@ + + + + - + - - - - + - + - - + @@ -700,13 +461,11 @@ - - + - - + @@ -884,7 +643,7 @@ - + @@ -894,7 +653,7 @@ - + @@ -995,14 +754,7 @@ - - - - - - - - + @@ -1014,8 +766,10 @@ - - + + + + @@ -1036,7 +790,7 @@ - + @@ -1067,7 +821,7 @@ - + @@ -1175,8 +929,7 @@ - - + @@ -1225,8 +978,7 @@ - - + @@ -1274,8 +1026,7 @@ - - + @@ -1329,18 +1080,16 @@ - - + - - - + + - + @@ -1348,7 +1097,7 @@ - + @@ -1361,18 +1110,16 @@ - - + - - - + + - + @@ -1380,7 +1127,7 @@ - + @@ -1598,7 +1345,7 @@ - + @@ -1619,83 +1366,56 @@ + - - - - - - - - - - + + + + + + + + + - - + + + - - - - - + - + + + + - - + + - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1714,7 +1434,7 @@ - + @@ -1743,8 +1463,7 @@ - - + @@ -1761,8 +1480,7 @@ - - + @@ -1778,8 +1496,7 @@ - - + @@ -1814,8 +1531,7 @@ - - + @@ -1837,8 +1553,7 @@ - - + @@ -1864,7 +1579,7 @@ - + @@ -1879,7 +1594,7 @@ - + diff --git a/buildres/csl/csl-styles/apa.csl b/buildres/csl/csl-styles/apa.csl index e8cd9ae64f2..772c25e3345 100644 --- a/buildres/csl/csl-styles/apa.csl +++ b/buildres/csl/csl-styles/apa.csl @@ -14,7 +14,7 @@ - 2022-01-31T14:30:00+00:00 + 2022-10-02T20:08:41+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -35,298 +35,88 @@ ca. B.C.E. C.E. - personal communication - letter issue issues - - - - - persoonlike kommunikasie - brief - - - - - اتصال شخصي - خطاب - - - - - лична комуникация - писмо - - - - - comunicació personal - carta - - - - - osobní komunikace - dopis - - - - - cyfathrebu personol - llythyr + computer software et al. - personlig kommunikation - brev et al. - persönliche Kommunikation - Brief - - - - - προσωπική επικοινωνία - επιστολή de - comunicación personal - carta - - - - - isiklik suhtlus - kiri - - - - - komunikazio pertsonala - gutuna - - - - - ارتباط شخصی - نامه - - - - - henkilökohtainen viestintä - kirje - communication personnelle - lettre éd. éds. - - - תקשורת אישית - מכתב - - - - - osobna komunikacija - pismo - - - - - személyes kommunikáció - levél - - - - - komunikasi pribadi - surat - - - - - persónuleg samskipti - bréf - - - - - comunicazione personale - lettera - - - - - 個人的なやり取り - 手紙 - - - - - 개인 서신 - 편지 - - - - - - epistula - - - - - communicationis personalis - - - - - - personīga komunikācija - vēstule - - - - - хувийн харилцаа холбоо - захиа - - et al. - personlig kommunikasjon - brev et al. - persoonlijke communicatie - brief et al. - personlig kommunikasjon - brev - - - - - osobista komunikacja - list - - - - - comunicação pessoal - carta - - - - - comunicare personală - scrisoare - - - - - личная переписка - письмо - - - - - osobná komunikácia - list - - - - - osebna komunikacija - pismo - - - - - лична комуникација - писмо - - - - - personlig kommunikation - brev - - - - - การสื่อสารส่วนบุคคล - จดหมาย - - - - - kişisel iletişim - mektup - - - - - особисте спілкування - лист - - - - - giao tiếp cá nhân - thư - - - - - 的私人交流 - 信函 - - - - - 私人通訊 - 信函 + + + + + + + @@ -355,7 +145,7 @@ - + + + @@ -399,8 +191,7 @@ - - + @@ -438,6 +229,7 @@ + @@ -476,6 +268,7 @@ + @@ -502,10 +295,8 @@ - - - + @@ -553,6 +344,7 @@ + @@ -582,6 +374,7 @@ + @@ -603,7 +396,7 @@ - + @@ -622,7 +415,7 @@ - + @@ -638,27 +431,28 @@ + + + + + - + - - - - + - + - - + @@ -689,6 +483,7 @@ + @@ -701,13 +496,11 @@ - - + - - + @@ -744,6 +537,7 @@ + @@ -762,6 +556,7 @@ + @@ -782,7 +577,9 @@ - + + + @@ -793,7 +590,9 @@ - + + + @@ -885,7 +684,7 @@ - + @@ -895,7 +694,7 @@ - + @@ -930,20 +729,20 @@ - + - + - + @@ -956,13 +755,13 @@ - + - + @@ -996,14 +795,7 @@ - - - - - - - - + @@ -1012,11 +804,14 @@ + - - + + + + @@ -1037,7 +832,7 @@ - + @@ -1058,6 +853,7 @@ + @@ -1068,7 +864,7 @@ - + @@ -1080,6 +876,8 @@ + + @@ -1116,6 +914,7 @@ + @@ -1125,7 +924,7 @@ - + @@ -1163,12 +962,13 @@ - + + @@ -1176,8 +976,7 @@ - - + @@ -1200,6 +999,7 @@ + @@ -1226,8 +1026,7 @@ - - + @@ -1250,6 +1049,7 @@ + @@ -1262,6 +1062,7 @@ + @@ -1275,8 +1076,7 @@ - - + @@ -1318,6 +1118,7 @@ + @@ -1330,18 +1131,17 @@ - + - + - - - + + - + @@ -1349,11 +1149,12 @@ - + + @@ -1362,18 +1163,17 @@ - + - + - - - + + - + @@ -1381,13 +1181,14 @@ - + + @@ -1443,6 +1244,7 @@ + @@ -1451,13 +1253,14 @@ + - + - + @@ -1486,6 +1289,7 @@ + @@ -1520,7 +1324,7 @@ - + + @@ -1598,8 +1404,9 @@ + - + @@ -1620,86 +1427,60 @@ + - - - - - - - - - - + + + + + + + + + - - + + + - - - - - + - + + + + - - + + - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -1715,7 +1496,7 @@ - + @@ -1734,6 +1515,7 @@ + @@ -1744,8 +1526,7 @@ - - + @@ -1762,8 +1543,7 @@ - - + @@ -1779,8 +1559,7 @@ - - + @@ -1815,7 +1594,7 @@ - + @@ -1838,8 +1617,7 @@ - - + @@ -1849,6 +1627,7 @@ + @@ -1865,7 +1644,7 @@ - + @@ -1880,12 +1659,13 @@ - + + diff --git a/buildres/csl/csl-styles/arachnologische-mitteilungen.csl b/buildres/csl/csl-styles/arachnologische-mitteilungen.csl new file mode 100644 index 00000000000..d7551bbebd2 --- /dev/null +++ b/buildres/csl/csl-styles/arachnologische-mitteilungen.csl @@ -0,0 +1,251 @@ + + diff --git a/buildres/csl/csl-styles/association-for-computational-linguistics.csl b/buildres/csl/csl-styles/association-for-computational-linguistics.csl index 0fd76b842e5..a8e47c05f63 100644 --- a/buildres/csl/csl-styles/association-for-computational-linguistics.csl +++ b/buildres/csl/csl-styles/association-for-computational-linguistics.csl @@ -169,8 +169,10 @@ - - + + + + diff --git a/buildres/csl/csl-styles/begell-house-apa.csl b/buildres/csl/csl-styles/begell-house-apa.csl index cd632d19ef4..425f7d514fb 100644 --- a/buildres/csl/csl-styles/begell-house-apa.csl +++ b/buildres/csl/csl-styles/begell-house-apa.csl @@ -41,8 +41,8 @@ @@ -110,7 +110,7 @@ - @@ -191,7 +191,7 @@ - @@ -224,9 +224,6 @@ - - - @@ -252,7 +249,7 @@ - @@ -997,7 +994,7 @@ - @@ -1298,7 +1295,7 @@ - @@ -1571,7 +1568,7 @@ - + @@ -1586,7 +1583,7 @@ - + diff --git a/buildres/csl/csl-styles/berghahn-books-author-date-en-gb.csl b/buildres/csl/csl-styles/berghahn-books-author-date-en-gb.csl index 9dd055b11c4..f36983db553 100644 --- a/buildres/csl/csl-styles/berghahn-books-author-date-en-gb.csl +++ b/buildres/csl/csl-styles/berghahn-books-author-date-en-gb.csl @@ -6,14 +6,14 @@ + Patrick O'Brien - obrienpat86@gmail.com - 2019-06-25T08:15:30+00:00 + 2023-08-13T15:57:25+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -33,7 +33,7 @@ - + - + - 2021-10-25T15:32:58+00:00 + 2023-05-17T07:55:23+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License et al. In + vom abgerufen am @@ -58,7 +59,7 @@ - @@ -184,6 +185,11 @@ + + + + + @@ -260,6 +266,13 @@ + + + + + + + diff --git a/buildres/csl/csl-styles/bibliothek-forschung-und-praxis.csl b/buildres/csl/csl-styles/bibliothek-forschung-und-praxis.csl index 065d25cb847..dd1f70804f2 100644 --- a/buildres/csl/csl-styles/bibliothek-forschung-und-praxis.csl +++ b/buildres/csl/csl-styles/bibliothek-forschung-und-praxis.csl @@ -425,12 +425,15 @@ + + + @@ -443,12 +446,15 @@ + + + diff --git a/buildres/csl/csl-styles/bio-protocol.csl b/buildres/csl/csl-styles/bio-protocol.csl index 83048b66b4e..32f36b13174 100644 --- a/buildres/csl/csl-styles/bio-protocol.csl +++ b/buildres/csl/csl-styles/bio-protocol.csl @@ -220,9 +220,6 @@ - - - @@ -1359,7 +1356,7 @@ - + @@ -1374,7 +1371,7 @@ - + diff --git a/buildres/csl/csl-styles/bluebook-law-review.csl b/buildres/csl/csl-styles/bluebook-law-review.csl index 0aa30af6bd5..21d9aaea164 100644 --- a/buildres/csl/csl-styles/bluebook-law-review.csl +++ b/buildres/csl/csl-styles/bluebook-law-review.csl @@ -19,7 +19,7 @@ The Bluebook legal citation style for law reviews. - 2022-03-09T09:33:23+00:00 + 2023-06-28T09:05:37+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -111,7 +111,7 @@ - + @@ -125,7 +125,7 @@ - + @@ -138,7 +138,7 @@ - + @@ -151,7 +151,7 @@ - + @@ -161,13 +161,13 @@ - + - + @@ -181,10 +181,10 @@ - + - + diff --git a/buildres/csl/csl-styles/cardiff-university-harvard.csl b/buildres/csl/csl-styles/cardiff-university-harvard.csl index 401e2abd744..68703b4f79e 100644 --- a/buildres/csl/csl-styles/cardiff-university-harvard.csl +++ b/buildres/csl/csl-styles/cardiff-university-harvard.csl @@ -14,10 +14,13 @@ Lewys Peters + + Patrick O'Brien + The Harvard author-date style - adapted for Cardiff University - 2022-08-22T08:20:08+00:00 + 2023-04-05T08:14:17+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -85,7 +88,7 @@ - + @@ -101,20 +104,16 @@ - - - - - - - - - - - - - + + + + + + + + + @@ -141,13 +140,12 @@ + + - - - - + @@ -188,69 +186,82 @@ - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - diff --git a/buildres/csl/csl-styles/chicago-author-date.csl b/buildres/csl/csl-styles/chicago-author-date.csl index 4ea0eb2cde0..ec19900821b 100644 --- a/buildres/csl/csl-styles/chicago-author-date.csl +++ b/buildres/csl/csl-styles/chicago-author-date.csl @@ -217,7 +217,7 @@ - + diff --git a/buildres/csl/csl-styles/citation-compass-apa-note.csl b/buildres/csl/csl-styles/citation-compass-apa-note.csl index 5844fa346ad..4e379fb20b4 100644 --- a/buildres/csl/csl-styles/citation-compass-apa-note.csl +++ b/buildres/csl/csl-styles/citation-compass-apa-note.csl @@ -54,8 +54,8 @@ @@ -123,7 +123,7 @@ - @@ -204,7 +204,7 @@ - @@ -237,9 +237,6 @@ - - - @@ -265,7 +262,7 @@ - @@ -1030,7 +1027,7 @@ - @@ -1310,7 +1307,7 @@ - @@ -1580,7 +1577,7 @@ - + @@ -1597,7 +1594,7 @@ - + diff --git a/buildres/csl/csl-styles/dependent/energy-research-and-social-science.csl b/buildres/csl/csl-styles/dependent/energy-research-and-social-science.csl deleted file mode 100644 index 5ccd3c089d6..00000000000 --- a/buildres/csl/csl-styles/dependent/energy-research-and-social-science.csl +++ /dev/null @@ -1,14 +0,0 @@ - - diff --git a/buildres/csl/csl-styles/dependent/harvard-the-university-of-melbourne.csl b/buildres/csl/csl-styles/dependent/harvard-the-university-of-melbourne.csl new file mode 100644 index 00000000000..eea8e1fc539 --- /dev/null +++ b/buildres/csl/csl-styles/dependent/harvard-the-university-of-melbourne.csl @@ -0,0 +1,14 @@ + + diff --git a/buildres/csl/csl-styles/dependent/jci-insight.csl b/buildres/csl/csl-styles/dependent/jci-insight.csl new file mode 100644 index 00000000000..4dc8ede77f0 --- /dev/null +++ b/buildres/csl/csl-styles/dependent/jci-insight.csl @@ -0,0 +1,15 @@ + + diff --git a/buildres/csl/csl-styles/dependent/magnetic-resonance-in-medicine.csl b/buildres/csl/csl-styles/dependent/magnetic-resonance-in-medicine.csl new file mode 100644 index 00000000000..5fdd285e808 --- /dev/null +++ b/buildres/csl/csl-styles/dependent/magnetic-resonance-in-medicine.csl @@ -0,0 +1,17 @@ + + diff --git a/buildres/csl/csl-styles/dependent/molecular-physics.csl b/buildres/csl/csl-styles/dependent/molecular-physics.csl new file mode 100644 index 00000000000..a8cc54bcf7b --- /dev/null +++ b/buildres/csl/csl-styles/dependent/molecular-physics.csl @@ -0,0 +1,16 @@ + + diff --git a/buildres/csl/csl-styles/dependent/retina.csl b/buildres/csl/csl-styles/dependent/retina.csl deleted file mode 100644 index 853f2bf8394..00000000000 --- a/buildres/csl/csl-styles/dependent/retina.csl +++ /dev/null @@ -1,16 +0,0 @@ - - diff --git a/buildres/csl/csl-styles/dependent/turabian-fullnote-bibliography.csl b/buildres/csl/csl-styles/dependent/turabian-fullnote-bibliography.csl new file mode 100644 index 00000000000..67f916a42c5 --- /dev/null +++ b/buildres/csl/csl-styles/dependent/turabian-fullnote-bibliography.csl @@ -0,0 +1,15 @@ + + diff --git a/buildres/csl/csl-styles/duale-hochschule-baden-wurttemberg-department-of-international-business.csl b/buildres/csl/csl-styles/duale-hochschule-baden-wurttemberg-department-of-international-business.csl new file mode 100644 index 00000000000..16d4c4d1454 --- /dev/null +++ b/buildres/csl/csl-styles/duale-hochschule-baden-wurttemberg-department-of-international-business.csl @@ -0,0 +1,331 @@ + + diff --git a/buildres/csl/csl-styles/ecosistemas.csl b/buildres/csl/csl-styles/ecosistemas.csl index 0c8fb185859..d7db55aa527 100644 --- a/buildres/csl/csl-styles/ecosistemas.csl +++ b/buildres/csl/csl-styles/ecosistemas.csl @@ -6,8 +6,8 @@ http://www.zotero.org/styles/ecosistemas - - + + Francisco Rodriguez-Sanchez f.rodriguez.sanc@gmail.com @@ -113,7 +113,7 @@ - + diff --git a/buildres/csl/csl-styles/energy-research-and-social-science.csl b/buildres/csl/csl-styles/energy-research-and-social-science.csl new file mode 100644 index 00000000000..e3c4a246a53 --- /dev/null +++ b/buildres/csl/csl-styles/energy-research-and-social-science.csl @@ -0,0 +1,342 @@ + + diff --git a/buildres/csl/csl-styles/environmental-and-engineering-geoscience.csl b/buildres/csl/csl-styles/environmental-and-engineering-geoscience.csl index fcf83490e56..e7235a005b8 100644 --- a/buildres/csl/csl-styles/environmental-and-engineering-geoscience.csl +++ b/buildres/csl/csl-styles/environmental-and-engineering-geoscience.csl @@ -2,9 +2,10 @@ diff --git a/buildres/csl/csl-styles/european-review-of-agricultural-economics.csl b/buildres/csl/csl-styles/european-review-of-agricultural-economics.csl index 80d5b9700d1..9945751671f 100644 --- a/buildres/csl/csl-styles/european-review-of-agricultural-economics.csl +++ b/buildres/csl/csl-styles/european-review-of-agricultural-economics.csl @@ -214,9 +214,6 @@ - - - @@ -1227,7 +1224,7 @@ - + @@ -1242,7 +1239,7 @@ - + diff --git a/buildres/csl/csl-styles/food-and-agriculture-organization-of-the-united-nations-numeric.csl b/buildres/csl/csl-styles/food-and-agriculture-organization-of-the-united-nations-numeric.csl new file mode 100644 index 00000000000..6e985542482 --- /dev/null +++ b/buildres/csl/csl-styles/food-and-agriculture-organization-of-the-united-nations-numeric.csl @@ -0,0 +1,377 @@ + + diff --git a/buildres/csl/csl-styles/fundamental-and-applied-limnology.csl b/buildres/csl/csl-styles/fundamental-and-applied-limnology.csl index 8777a232882..de3f1c499c8 100644 --- a/buildres/csl/csl-styles/fundamental-and-applied-limnology.csl +++ b/buildres/csl/csl-styles/fundamental-and-applied-limnology.csl @@ -47,8 +47,8 @@ @@ -116,7 +116,7 @@ - @@ -197,7 +197,7 @@ - @@ -230,9 +230,6 @@ - - - @@ -258,7 +255,7 @@ - @@ -1023,7 +1020,7 @@ - @@ -1303,7 +1300,7 @@ - @@ -1576,7 +1573,7 @@ - + @@ -1593,7 +1590,7 @@ - + diff --git a/buildres/csl/csl-styles/geographie-et-cultures.csl b/buildres/csl/csl-styles/geographie-et-cultures.csl index 6c7d8699c18..86a87c32fc5 100644 --- a/buildres/csl/csl-styles/geographie-et-cultures.csl +++ b/buildres/csl/csl-styles/geographie-et-cultures.csl @@ -1,5 +1,5 @@ - diff --git a/buildres/csl/csl-styles/harvard-university-of-the-west-of-england.csl b/buildres/csl/csl-styles/harvard-university-of-the-west-of-england.csl index e5bdfcb075c..408d51fd9bf 100644 --- a/buildres/csl/csl-styles/harvard-university-of-the-west-of-england.csl +++ b/buildres/csl/csl-styles/harvard-university-of-the-west-of-england.csl @@ -12,13 +12,18 @@ Harvard author-date style updated for University of the West of England (UWE) (et-al format updated to comply with UWE Harvard Referencing - 2022-04-10T15:09:45+00:00 + 2023-05-02T13:01:26+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License + + + available from + + - @@ -42,29 +47,18 @@ - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + @@ -98,12 +92,10 @@ - - - - - + + @@ -113,7 +105,7 @@ - + @@ -280,7 +272,7 @@ - + @@ -310,10 +302,10 @@ - + - + @@ -321,9 +313,9 @@ - + - + @@ -331,7 +323,7 @@ - + @@ -344,19 +336,25 @@ - + - - + + + + + + + + - + diff --git a/buildres/csl/csl-styles/harvard-xi-an-jiaotong-liverpool-university.csl b/buildres/csl/csl-styles/harvard-xi-an-jiaotong-liverpool-university.csl new file mode 100644 index 00000000000..82d137effce --- /dev/null +++ b/buildres/csl/csl-styles/harvard-xi-an-jiaotong-liverpool-university.csl @@ -0,0 +1,225 @@ + + diff --git a/buildres/csl/csl-styles/haute-ecole-de-gestion-de-geneve-iso-690.csl b/buildres/csl/csl-styles/haute-ecole-de-gestion-de-geneve-iso-690.csl index 4698051bdad..20fde1b70a0 100644 --- a/buildres/csl/csl-styles/haute-ecole-de-gestion-de-geneve-iso-690.csl +++ b/buildres/csl/csl-styles/haute-ecole-de-gestion-de-geneve-iso-690.csl @@ -7,34 +7,39 @@ - - Melissa Paez - paez.melissa@gmail.com - Raphael Grolimund grolimur@protonmail.ch + + Melissa Paez + paez.melissa@gmail.com + - 2022-03-21T10:00:00+00:00 + 2023-08-08T00:29:32+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License [sans date] in - [en ligne] - consulté le + en ligne + consulté le disponible à l'adresse trad. + no éd. + + p. + pp. + - Online + no. @@ -54,7 +59,7 @@ - + @@ -65,13 +70,13 @@ - + - + @@ -114,30 +119,33 @@ - + - - + + - + - + + + + - + - + - + @@ -152,10 +160,18 @@ - + + + + + + + - + @@ -165,38 +181,49 @@ - + - + - + - + - - + + - + - - + + + + + + + + + + + + + @@ -206,6 +233,12 @@ + + + + + + @@ -218,57 +251,108 @@ + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + - - + + + + + + - - + + + + + + - + + - - - - - - - - - + + + + + + + + + + + + + @@ -279,17 +363,7 @@ - - - - - - - - - - - + @@ -304,7 +378,7 @@ - + @@ -312,22 +386,6 @@ - - - - - - - - - - - - - - - - @@ -342,7 +400,7 @@ - + @@ -356,15 +414,12 @@ - - - - + @@ -374,11 +429,10 @@ - - + @@ -391,8 +445,8 @@ - + @@ -403,11 +457,11 @@ - - + + @@ -419,9 +473,9 @@ - + @@ -434,11 +488,11 @@ - + @@ -449,8 +503,9 @@ - + + @@ -460,23 +515,24 @@ - + + - + - + @@ -485,12 +541,12 @@ - - - + + + @@ -502,9 +558,9 @@ - + @@ -513,12 +569,11 @@ - - + @@ -527,11 +582,13 @@ - + + + @@ -542,21 +599,20 @@ - + - + + + - - - diff --git a/buildres/csl/csl-styles/howard-hughes-medical-institute.csl b/buildres/csl/csl-styles/howard-hughes-medical-institute.csl new file mode 100644 index 00000000000..b89783be917 --- /dev/null +++ b/buildres/csl/csl-styles/howard-hughes-medical-institute.csl @@ -0,0 +1,102 @@ + + diff --git a/buildres/csl/csl-styles/ieee-with-url.csl b/buildres/csl/csl-styles/ieee-with-url.csl index 26dbc82af21..63d871fbc99 100644 --- a/buildres/csl/csl-styles/ieee-with-url.csl +++ b/buildres/csl/csl-styles/ieee-with-url.csl @@ -4,7 +4,9 @@ IEEE (with URL) http://www.zotero.org/styles/ieee-with-url - + + + Michael Berkowitz @@ -25,11 +27,26 @@ Sebastian Karcher + + Giuseppe Silano + g.silano89@gmail.com + http://giuseppesilano.net + + + Patrick O'Brien + + + Brenton M. Wiernik + + + Oliver Couch + oliver.couch@gmail.com + - IEEE style with URLs as per the 2018 guidelines, V 11.12.2018. - 2019-12-20T09:20:25+00:00 + IEEE style as per the 2021 guidelines, V 01.29.2021. + 2023-04-23T03:05:51+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -45,6 +62,13 @@ + + + + + + + @@ -151,26 +175,20 @@ + - - - - - - - - + + + + - - - - - - + + + @@ -178,27 +196,41 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - @@ -212,8 +244,30 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + @@ -235,118 +289,146 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + - - - - - - - - - + - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/buildres/csl/csl-styles/ieee.csl b/buildres/csl/csl-styles/ieee.csl index 19081952c37..04c375f09b7 100644 --- a/buildres/csl/csl-styles/ieee.csl +++ b/buildres/csl/csl-styles/ieee.csl @@ -45,7 +45,7 @@ IEEE style as per the 2021 guidelines, V 01.29.2021. - 2022-10-11T00:52:46+10:00 + 2023-04-18T00:52:46+10:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -174,25 +174,20 @@ + - - - - - - + + + - - - - - - + + + diff --git a/buildres/csl/csl-styles/isara-iso-690.csl b/buildres/csl/csl-styles/isara-iso-690.csl index 115558fcbad..2a4f2cc7b99 100644 --- a/buildres/csl/csl-styles/isara-iso-690.csl +++ b/buildres/csl/csl-styles/isara-iso-690.csl @@ -16,7 +16,7 @@ Style based on ISO 690:2010(F), V1, derived from Mellifluo, Grolimund, Hardegger and Giraud version. - 2022-08-31T02:31:25+00:00 + 2023-06-23T10:13:28+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -327,7 +327,7 @@ - + diff --git a/buildres/csl/csl-styles/jci-insight.csl b/buildres/csl/csl-styles/jci-insight.csl deleted file mode 100644 index adbd5781319..00000000000 --- a/buildres/csl/csl-styles/jci-insight.csl +++ /dev/null @@ -1,152 +0,0 @@ - - diff --git a/buildres/csl/csl-styles/journal-of-dental-traumatology.csl b/buildres/csl/csl-styles/journal-of-dental-traumatology.csl new file mode 100644 index 00000000000..784a3d393e5 --- /dev/null +++ b/buildres/csl/csl-styles/journal-of-dental-traumatology.csl @@ -0,0 +1,204 @@ + + diff --git a/buildres/csl/csl-styles/journal-of-emerging-investigators.csl b/buildres/csl/csl-styles/journal-of-emerging-investigators.csl new file mode 100644 index 00000000000..81b661b22a6 --- /dev/null +++ b/buildres/csl/csl-styles/journal-of-emerging-investigators.csl @@ -0,0 +1,324 @@ + + diff --git a/buildres/csl/csl-styles/journal-of-experimental-botany.csl b/buildres/csl/csl-styles/journal-of-experimental-botany.csl index 3b2791df719..7d736286ee7 100644 --- a/buildres/csl/csl-styles/journal-of-experimental-botany.csl +++ b/buildres/csl/csl-styles/journal-of-experimental-botany.csl @@ -2,18 +2,23 @@ diff --git a/buildres/csl/csl-styles/journal-of-paleontology.csl b/buildres/csl/csl-styles/journal-of-paleontology.csl index 1f1fdcefdeb..6570974cc02 100644 --- a/buildres/csl/csl-styles/journal-of-paleontology.csl +++ b/buildres/csl/csl-styles/journal-of-paleontology.csl @@ -9,8 +9,8 @@ Martin R. Smith - martins@gmail.com - https://community.dur.ac.uk/martin.smith/ + martin.smith@durham.ac.uk + https://smithlabdurham.github.io/ @@ -107,11 +107,19 @@ - - - + + + + + + + + + + - diff --git a/buildres/csl/csl-styles/journal-of-the-american-philosophical-association.csl b/buildres/csl/csl-styles/journal-of-the-american-philosophical-association.csl new file mode 100644 index 00000000000..39ef20eab02 --- /dev/null +++ b/buildres/csl/csl-styles/journal-of-the-american-philosophical-association.csl @@ -0,0 +1,188 @@ + + diff --git a/buildres/csl/csl-styles/jurnal-teknik-mesin-indonesia.csl b/buildres/csl/csl-styles/jurnal-teknik-mesin-indonesia.csl new file mode 100644 index 00000000000..23240785f36 --- /dev/null +++ b/buildres/csl/csl-styles/jurnal-teknik-mesin-indonesia.csl @@ -0,0 +1,154 @@ + + diff --git a/buildres/csl/csl-styles/la-nouvelle-revue-du-travail.csl b/buildres/csl/csl-styles/la-nouvelle-revue-du-travail.csl new file mode 100644 index 00000000000..e244ac652a0 --- /dev/null +++ b/buildres/csl/csl-styles/la-nouvelle-revue-du-travail.csl @@ -0,0 +1,207 @@ + + diff --git a/buildres/csl/csl-styles/lethaia.csl b/buildres/csl/csl-styles/lethaia.csl index fe99d3419ed..c8043a0335f 100644 --- a/buildres/csl/csl-styles/lethaia.csl +++ b/buildres/csl/csl-styles/lethaia.csl @@ -5,29 +5,31 @@ http://www.zotero.org/styles/lethaia - + Martin R. Smith - martins@gmail.com + martin.smith@durham.ac.uk + https://smithlabdurham.github.io/ + + Patrick O'Brien + 0024-1164 1502-3931 - 2012-09-27T22:06:38+00:00 + 2023-08-28T08:59:04+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License - - - + + - - - @@ -72,18 +74,35 @@ + + + + + + + + + + - - - + + + + + + + + + + - - @@ -92,52 +111,47 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - + + + + + - - + + + + + + + + - - - - - - - - - - - - - - - - - + @@ -150,13 +164,22 @@ - - + + + + + + + + + + + + + + - - - @@ -169,13 +192,9 @@ - - - - - + @@ -186,20 +205,22 @@ - - - - - - + + + + + + + + - + diff --git a/buildres/csl/csl-styles/magnetic-resonance-in-medicine.csl b/buildres/csl/csl-styles/magnetic-resonance-in-medicine.csl deleted file mode 100644 index aef8b689b23..00000000000 --- a/buildres/csl/csl-styles/magnetic-resonance-in-medicine.csl +++ /dev/null @@ -1,172 +0,0 @@ - - diff --git a/buildres/csl/csl-styles/masarykova-univerzita-pravnicka-fakulta.csl b/buildres/csl/csl-styles/masarykova-univerzita-pravnicka-fakulta.csl index 4e714b408eb..858adf43197 100644 --- a/buildres/csl/csl-styles/masarykova-univerzita-pravnicka-fakulta.csl +++ b/buildres/csl/csl-styles/masarykova-univerzita-pravnicka-fakulta.csl @@ -9,12 +9,12 @@ Oldrich Tristan Florian oldrich.florian@gmail.com - http://otristan.com + http://otflorian.com Masaryk University, Faculty of Law - 2021-10-07T10:00:00+00:00 + 2023-07-01T10:00:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -146,11 +146,6 @@ - - - - - @@ -338,7 +333,7 @@ - + @@ -521,11 +516,11 @@ - - - - - + + + + + diff --git a/buildres/csl/csl-styles/medizinische-universitat-innsbruck-vancouver.csl b/buildres/csl/csl-styles/medizinische-universitat-innsbruck-vancouver.csl new file mode 100644 index 00000000000..b615bd3a045 --- /dev/null +++ b/buildres/csl/csl-styles/medizinische-universitat-innsbruck-vancouver.csl @@ -0,0 +1,328 @@ + + diff --git a/buildres/csl/csl-styles/modern-language-association.csl b/buildres/csl/csl-styles/modern-language-association.csl index 26b92bdff12..4530d255dad 100644 --- a/buildres/csl/csl-styles/modern-language-association.csl +++ b/buildres/csl/csl-styles/modern-language-association.csl @@ -9,10 +9,13 @@ Sebastian Karcher + + Patrick O'Brien + This style adheres to the MLA 9th edition handbook. Follows the structure of references as outlined in the MLA Manual closely - 2021-07-13T20:05:10+00:00 + 2023-07-21T20:05:10+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -195,9 +198,18 @@ - - + + + + + + + + + + + @@ -208,7 +220,7 @@ - + diff --git a/buildres/csl/csl-styles/mots.csl b/buildres/csl/csl-styles/mots.csl index f6a0b1f2ad4..f1d6d822a6a 100644 --- a/buildres/csl/csl-styles/mots.csl +++ b/buildres/csl/csl-styles/mots.csl @@ -1,5 +1,5 @@ - diff --git a/buildres/csl/csl-styles/organization-studies.csl b/buildres/csl/csl-styles/organization-studies.csl index 912345484b3..13a9e79bff9 100644 --- a/buildres/csl/csl-styles/organization-studies.csl +++ b/buildres/csl/csl-styles/organization-studies.csl @@ -211,23 +211,6 @@ - - - - - - - - - - - - - - - - - @@ -1468,7 +1451,7 @@ - + @@ -1483,7 +1466,7 @@ - + diff --git a/buildres/csl/csl-styles/palaeontology.csl b/buildres/csl/csl-styles/palaeontology.csl index f0e71ab0906..2b8452e9d98 100644 --- a/buildres/csl/csl-styles/palaeontology.csl +++ b/buildres/csl/csl-styles/palaeontology.csl @@ -12,8 +12,8 @@ Martin R. Smith - martins@gmail.com - https://community.dur.ac.uk/martin.smith/ + martin.smith@durham.ac.uk + https://smithlabdurham.github.io/ @@ -80,11 +80,18 @@ - - + + + + + + + + + - - diff --git a/buildres/csl/csl-styles/paleobiology.csl b/buildres/csl/csl-styles/paleobiology.csl index 0acc365760e..97887076167 100644 --- a/buildres/csl/csl-styles/paleobiology.csl +++ b/buildres/csl/csl-styles/paleobiology.csl @@ -8,8 +8,8 @@ Martin R. Smith - martins@gmail.com - https://plus.google.com/u/0/108450310917386384868 + martin.smith@durham.ac.uk + https://smithlabdurham.github.io/ @@ -20,14 +20,12 @@ - - + - - + @@ -79,11 +77,18 @@ - - + + + + + + + + + - - diff --git a/buildres/csl/csl-styles/polish-archives-of-internal-medicine.csl b/buildres/csl/csl-styles/polish-archives-of-internal-medicine.csl new file mode 100644 index 00000000000..1f8198d3a71 --- /dev/null +++ b/buildres/csl/csl-styles/polish-archives-of-internal-medicine.csl @@ -0,0 +1,144 @@ + + diff --git a/buildres/csl/csl-styles/publicatiewijzer-voor-de-archeologie.csl b/buildres/csl/csl-styles/publicatiewijzer-voor-de-archeologie.csl index b93dcd6f80e..2fa8e7a9374 100644 --- a/buildres/csl/csl-styles/publicatiewijzer-voor-de-archeologie.csl +++ b/buildres/csl/csl-styles/publicatiewijzer-voor-de-archeologie.csl @@ -12,9 +12,14 @@ Style as prescribed in Diepeveen-Jansen, M./J. Kaarsemaker, 2004: Publicatiewijzer voor de archeologie, Amsterdam (Themata 1). - 2022-11-28T02:10:01+00:00 + 2023-06-16T14:26:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License + + + et al. + + @@ -33,7 +38,7 @@ - + - + diff --git a/buildres/csl/csl-styles/retina.csl b/buildres/csl/csl-styles/retina.csl new file mode 100644 index 00000000000..0ea770faf4f --- /dev/null +++ b/buildres/csl/csl-styles/retina.csl @@ -0,0 +1,272 @@ + + diff --git a/buildres/csl/csl-styles/sage-harvard.csl b/buildres/csl/csl-styles/sage-harvard.csl index 488dfbc6202..60b44bcbf04 100644 --- a/buildres/csl/csl-styles/sage-harvard.csl +++ b/buildres/csl/csl-styles/sage-harvard.csl @@ -12,7 +12,7 @@ The SAGE Harvard author-date style - 2017-05-18T15:23:08+00:00 + 2023-08-31T08:13:20+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -21,6 +21,7 @@ ed. eds + Epub ahead of print @@ -74,8 +75,18 @@ - - + + + + + + + + + + + + diff --git a/buildres/csl/csl-styles/silva-fennica.csl b/buildres/csl/csl-styles/silva-fennica.csl new file mode 100644 index 00000000000..4e2233f0180 --- /dev/null +++ b/buildres/csl/csl-styles/silva-fennica.csl @@ -0,0 +1,218 @@ + + diff --git a/buildres/csl/csl-styles/slovensko-drustvo-za-medicinsko-informatiko.csl b/buildres/csl/csl-styles/slovensko-drustvo-za-medicinsko-informatiko.csl new file mode 100644 index 00000000000..c8639ec6255 --- /dev/null +++ b/buildres/csl/csl-styles/slovensko-drustvo-za-medicinsko-informatiko.csl @@ -0,0 +1,175 @@ + + diff --git a/buildres/csl/csl-styles/springer-vancouver-brackets.csl b/buildres/csl/csl-styles/springer-vancouver-brackets.csl index afceecf8bb2..41bcda899c0 100644 --- a/buildres/csl/csl-styles/springer-vancouver-brackets.csl +++ b/buildres/csl/csl-styles/springer-vancouver-brackets.csl @@ -5,7 +5,6 @@ http://www.zotero.org/styles/springer-vancouver-brackets - @@ -16,7 +15,7 @@ This style based on the NLM guidelines Citing Medicine is very similar to Vancouver and is used by a number of Springer publications. - 2018-01-29T19:38:34+00:00 + 2023-05-14T09:16:49+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -70,17 +69,21 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + @@ -118,7 +121,6 @@ - diff --git a/buildres/csl/csl-styles/studii-teologice.csl b/buildres/csl/csl-styles/studii-teologice.csl index 18089cfed74..75300e87325 100644 --- a/buildres/csl/csl-styles/studii-teologice.csl +++ b/buildres/csl/csl-styles/studii-teologice.csl @@ -1,6 +1,7 @@ diff --git a/buildres/csl/csl-styles/taylor-and-francis-aip.csl b/buildres/csl/csl-styles/taylor-and-francis-aip.csl new file mode 100644 index 00000000000..a894588435c --- /dev/null +++ b/buildres/csl/csl-styles/taylor-and-francis-aip.csl @@ -0,0 +1,158 @@ + + diff --git a/buildres/csl/csl-styles/technische-universitat-dresden-betriebswirtschaftslehre-rechnungswesen-controlling.csl b/buildres/csl/csl-styles/technische-universitat-dresden-betriebswirtschaftslehre-rechnungswesen-controlling.csl index e65048a4e50..09439bf9fb7 100644 --- a/buildres/csl/csl-styles/technische-universitat-dresden-betriebswirtschaftslehre-rechnungswesen-controlling.csl +++ b/buildres/csl/csl-styles/technische-universitat-dresden-betriebswirtschaftslehre-rechnungswesen-controlling.csl @@ -38,8 +38,8 @@ @@ -107,7 +107,7 @@ - @@ -188,7 +188,7 @@ - @@ -221,9 +221,6 @@ - - - @@ -249,7 +246,7 @@ - @@ -1014,7 +1011,7 @@ - @@ -1317,7 +1314,7 @@ - @@ -1590,7 +1587,7 @@ - + @@ -1605,7 +1602,7 @@ - + diff --git a/buildres/csl/csl-styles/the-accounting-review.csl b/buildres/csl/csl-styles/the-accounting-review.csl index 64133cc8699..de71d02fccb 100644 --- a/buildres/csl/csl-styles/the-accounting-review.csl +++ b/buildres/csl/csl-styles/the-accounting-review.csl @@ -395,7 +395,7 @@ - + diff --git a/buildres/csl/csl-styles/the-journal-of-clinical-investigation.csl b/buildres/csl/csl-styles/the-journal-of-clinical-investigation.csl index 48a0ce90c09..9212d87f9a3 100644 --- a/buildres/csl/csl-styles/the-journal-of-clinical-investigation.csl +++ b/buildres/csl/csl-styles/the-journal-of-clinical-investigation.csl @@ -2,30 +2,29 @@ diff --git a/buildres/csl/csl-styles/ucl-university-college-apa.csl b/buildres/csl/csl-styles/ucl-university-college-apa.csl index 7f514e6fbfb..532675eba82 100644 --- a/buildres/csl/csl-styles/ucl-university-college-apa.csl +++ b/buildres/csl/csl-styles/ucl-university-college-apa.csl @@ -30,8 +30,8 @@ @@ -99,7 +99,7 @@ - @@ -180,7 +180,7 @@ - @@ -213,9 +213,6 @@ - - - @@ -241,7 +238,7 @@ - @@ -1006,7 +1003,7 @@ - @@ -1286,7 +1283,7 @@ - @@ -1566,7 +1563,7 @@ - + @@ -1582,7 +1579,7 @@ - + diff --git a/buildres/csl/csl-styles/united-states-international-trade-commission.csl b/buildres/csl/csl-styles/united-states-international-trade-commission.csl index a361dab0b1c..f63961767b4 100644 --- a/buildres/csl/csl-styles/united-states-international-trade-commission.csl +++ b/buildres/csl/csl-styles/united-states-international-trade-commission.csl @@ -24,10 +24,14 @@ Peg Hausman Margaret.Hausman@usitc.gov + + Brian Rose + Brian.Rose@usitc.gov + A bibliographical style file for the United States International Trade Commission - 2022-08-17T18:21:43+00:00 + 2023-05-10T12:30:45+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -126,9 +130,12 @@ - - + + + + + @@ -146,8 +153,19 @@ + + + + + + + + + + + - + + + + + + + + + + + - + @@ -239,8 +267,7 @@ - @@ -260,24 +287,10 @@ - - - - - - - - + - - - - - - - - + @@ -290,77 +303,56 @@ - - - - - - - - + - - - - - - - - - - + + - - - - - - - - + + + + + + + + - - - - - - - - + - - - - - - - - + - - + + + + + + + + + + + - + - - - - - + + + + @@ -371,108 +363,110 @@ - + + + + - - - - - - - - + - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - @@ -482,6 +476,9 @@ + + + @@ -531,9 +528,13 @@ - + + - + + + + @@ -573,12 +574,20 @@ - + + + + + + + + + @@ -681,15 +690,11 @@ - - - - + - @@ -804,12 +809,8 @@ - - - - + - @@ -978,6 +979,7 @@ + @@ -994,6 +996,7 @@ + @@ -1028,9 +1031,7 @@ - - - + @@ -1065,7 +1066,6 @@ - @@ -1180,9 +1180,6 @@ - - - @@ -1196,7 +1193,7 @@ - + @@ -1348,7 +1345,7 @@ - + @@ -1357,6 +1354,21 @@ + + + + + + + + + + + + + + + @@ -1366,6 +1378,9 @@ + + + @@ -1380,7 +1395,7 @@ - + @@ -1393,6 +1408,22 @@ + + + + + + + + + + + + + + + + @@ -1414,12 +1445,27 @@ - + + + + + + + + + + + + + + + + @@ -1455,7 +1501,7 @@ - + @@ -1502,6 +1548,8 @@ + + @@ -1514,6 +1562,9 @@ + + + @@ -1546,9 +1597,9 @@ - + @@ -1564,60 +1615,120 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/buildres/csl/csl-styles/universitat-basel-iberoromanistik.csl b/buildres/csl/csl-styles/universitat-basel-iberoromanistik.csl index 1f62279ec14..1129be4510a 100644 --- a/buildres/csl/csl-styles/universitat-basel-iberoromanistik.csl +++ b/buildres/csl/csl-styles/universitat-basel-iberoromanistik.csl @@ -5,7 +5,7 @@ http://www.zotero.org/styles/universitat-basel-iberoromanistik - + Patrick O'Brien, PhD @@ -13,7 +13,7 @@ Citation style as per the guidelines from the 7th edition, Oct 2018. - 2021-02-09T11:21:54+00:00 + 2023-06-26T09:54:15+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -74,7 +74,10 @@ - + + + + @@ -82,7 +85,7 @@ - + @@ -142,17 +145,10 @@ - - - - - - - - - - - + + + + @@ -270,8 +266,8 @@ + - diff --git a/buildres/csl/csl-styles/universitat-bern-institut-fur-sozialanthropologie.csl b/buildres/csl/csl-styles/universitat-bern-institut-fur-sozialanthropologie.csl index ae3ec2afa17..ec26af6f39f 100644 --- a/buildres/csl/csl-styles/universitat-bern-institut-fur-sozialanthropologie.csl +++ b/buildres/csl/csl-styles/universitat-bern-institut-fur-sozialanthropologie.csl @@ -1,5 +1,5 @@ - diff --git a/buildres/csl/csl-styles/van-yuzuncu-yil-universitesi-fen-bilimleri-enstitusu.csl b/buildres/csl/csl-styles/van-yuzuncu-yil-universitesi-fen-bilimleri-enstitusu.csl new file mode 100644 index 00000000000..bf31b33b1b5 --- /dev/null +++ b/buildres/csl/csl-styles/van-yuzuncu-yil-universitesi-fen-bilimleri-enstitusu.csl @@ -0,0 +1,500 @@ + + diff --git a/buildres/csl/csl-styles/vancouver.csl b/buildres/csl/csl-styles/vancouver.csl index e39efa8d842..bd8ac3fe56f 100644 --- a/buildres/csl/csl-styles/vancouver.csl +++ b/buildres/csl/csl-styles/vancouver.csl @@ -19,7 +19,7 @@ Vancouver style as outlined by International Committee of Medical Journal Editors Uniform Requirements for Manuscripts Submitted to Biomedical Journals: Sample References - 2014-09-06T16:03:01+00:00 + 2022-09-28T11:33:04+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -50,6 +50,7 @@ + + + + + + + + + + + @@ -164,16 +176,21 @@ - - + + + - - + + + + + + + - - + @@ -283,6 +300,13 @@ + + + + + + + @@ -342,6 +366,7 @@ + diff --git a/buildres/csl/csl-styles/veterinary-clinical-pathology.csl b/buildres/csl/csl-styles/veterinary-clinical-pathology.csl new file mode 100644 index 00000000000..c2779e801e4 --- /dev/null +++ b/buildres/csl/csl-styles/veterinary-clinical-pathology.csl @@ -0,0 +1,260 @@ + + diff --git a/buildres/csl/csl-styles/zoological-journal-of-the-linnean-society.csl b/buildres/csl/csl-styles/zoological-journal-of-the-linnean-society.csl index 2b656e9182a..0786f46e0b8 100644 --- a/buildres/csl/csl-styles/zoological-journal-of-the-linnean-society.csl +++ b/buildres/csl/csl-styles/zoological-journal-of-the-linnean-society.csl @@ -9,8 +9,8 @@ Martin R. Smith - martins@gmail.com - http://sn.im/martin-smith + martin.smith@durham.ac.uk + https://smithlabdurham.github.io/ @@ -83,9 +83,9 @@ - + diff --git a/docs/Gemfile b/docs/Gemfile index 3056e5acfb5..289a8081022 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -5,7 +5,7 @@ gem "jekyll", "~> 4.3" # installed by `gem jekyll` # Homepage: https://github.com/paulrobertlloyd/jekyll-figure#jekyll-figure gem 'jekyll-figure' -gem "just-the-docs", "0.4.2" +gem "just-the-docs", "0.5.0" gem "jekyll-remote-theme" diff --git a/docs/_config.yml b/docs/_config.yml index 8683352bc58..8f1aaafc637 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,9 +1,9 @@ repository: JabRef/jabref title: "Developer Documentation" -remote_theme: just-the-docs/just-the-docs@v0.4.0.rc3 +remote_theme: just-the-docs/just-the-docs@v0.5.0 color_scheme: light -exclude: [CNAME, README.md, mkdocs-custom.css] +exclude: [_config.yml, .dockerignore, .gitignore, CNAME, Dockerfile, Gemfile, Gemfile.lock, README.md, mkdocs-custom.css] # Hint by https://github.com/just-the-docs/just-the-docs/issues/374#issuecomment-680273258 # Theme read from https://github.com/StylishThemes/Syntax-Themes/blob/master/pygments/css-github/ diff --git a/docs/code-howtos/IntelliJ.md b/docs/code-howtos/IntelliJ.md new file mode 100644 index 00000000000..9af29ef2b22 --- /dev/null +++ b/docs/code-howtos/IntelliJ.md @@ -0,0 +1,85 @@ +--- +parent: Code Howtos +--- + +# IntelliJ Hints + +{: .highlight } +Did you know that [IntelliJ allows for reformatting selected code](https://www.jetbrains.com/help/idea/reformat-and-rearrange-code.html#reformat\_code) if you press Ctrl + Alt + L? + +## Key hints for IntelliJ + +* Shift+Shift (AKA double-shift): Open the search dialog. +* Ctrl+N: Open the search dialog and select search for a class. +* Ctrl+Shift+F: Search everywhere in the code base. +* Alt+F1 and then Enter: Locate the file in the search bar on the left side. +* Ctrl+Shift+T: Navigate from a class to the test class. + +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 49 +--- + +## Other notes on IntelliJ + +{: .note} +Here, some notes on IntelliJ setup are written down. +These are intended for pro users. + +You should use IntelliJ IDEA's internal build system for compiling and running JabRef tests during development, because it is usually more responsive. +Essentially, you now have the best of both worlds: +You can run Gradle tasks using the Gradle Tool Window. +You can compile and run tests with IntelliJ's faster internal build system +(unless you haven't made changes to input files that generate sources). + +{: .important} +When using IntelliJ's build system, **it is important** that you understand that JabRef relies on generated sources which are only built through Gradle. +Therefore, to build or update these dependencies you need to run the `run` (or `assemble`) Gradle task at least once. +When you followed this guide, you should have done it in the Gradle setup. + +Running JabRef itself through IntelliJ's build system is **not** possible as we encounter difficulties when reading resources though `.class.getResource(...)`. +Although solutions are discussed in length [on stackoverflow](https://stackoverflow.com/q/26328040/873282), there is no "good" solution for us. + +## Running JabRef using IntelliJ's build system + +{ :note } +Maybe does not work + +To run JabRef from IntelliJ, we let IntelliJ create a launch configuration: + +Locate the class `Launcher`: +Press Ctrl+N. +Then, the "Search for classes dialog" pops up. +Enter `Launcher`. +Now, only one class should have been found: + +{% figure caption:"IntelliJ search for class “Launcher”" %} +![IntelliJ search for class "Launcher"](../images/intellij-search-for-launcher.png) +{% endfigure %} + +Press Enter to jump to that class. + +Hover on the green play button. + +{% figure caption:"However on green play" %} +![However on green play](../images/intellij-hover-on-play-button.png) +{% endfigure %} + +Then, click on it. +A popup menu opens. +Choose the first entry and click on it. + +{% figure caption:"Run JabRef via launcher" %} +![Popup menu - Run JabRef via launcher](../images/intellij-run-jabref-from-launcher.png) +{% endfigure %} + +Then, JabRef starts. + +You also have an entry in the Launch configurations to directly launch the JabRef GUI: + +{% figure caption:"Launch menu contains “Launcher”" %} +![Launch menu contains launcher](../images/intellij-run-launcher.png) +{% endfigure %} + +You can also click on the debug symbol next to it to enable stopping at breakpoints. diff --git a/docs/code-howtos/code-quality.md b/docs/code-howtos/code-quality.md index 350331a7602..755b0e1d500 100644 --- a/docs/code-howtos/code-quality.md +++ b/docs/code-howtos/code-quality.md @@ -7,9 +7,9 @@ We monitor the general source code quality at three places: * [codacy](https://www.codacy.com) is a hosted service to monitor code quality. It thereby combines the results of available open source code quality checkers such as [Checkstyle](https://checkstyle.sourceforge.io) or [PMD](https://pmd.github.io). The code quality analysis for JabRef is available at [https://app.codacy.com/gh/JabRef/jabref/dashboard](https://app.codacy.com/gh/JabRef/jabref/dashboard), especially the [list of open issues](https://app.codacy.com/gh/JabRef/jabref/issues/index). In case a rule feels wrong, it is most likely a PMD rule. The JabRef team can change the configuration at [https://app.codacy.com/p/306789/patterns/list?engine=9ed24812-b6ee-4a58-9004-0ed183c45b8f](https://app.codacy.com/p/306789/patterns/list?engine=9ed24812-b6ee-4a58-9004-0ed183c45b8f). * [codecov](https://codecov.io) is a solution to check code coverage of test cases. The code coverage metrics for JabRef are available at [https://codecov.io/github/JabRef/jabref](https://codecov.io/github/JabRef/jabref). -* [Teamscale](https://www.cqse.eu/de/produkte/teamscale/landing/) is a popular German product analyzing code quality. The analysis results are available at [https://demo.teamscale.com/findings.html#/jabref/?](https://demo.teamscale.com/findings.html#/jabref/?). +* [Teamscale](https://www.cqse.eu/en/teamscale/overview/) is a popular German product analyzing code quality. The analysis results are available at . -We strongly recommend to read following two books on code quality: +We strongly recommend reading following two books on code quality: * [Java by Comparison](http://java.by-comparison.com) is a book by three JabRef developers which focuses on code improvements close to single statements. It is fast to read and one gains much information from each recommendation discussed in the book. * [Effective Java](https://www.oreilly.com/library/view/effective-java-3rd/9780134686097/) is the standard book for advanced Java programming. Did you know that `enum` is the [recommended way to enforce a singleton instance of a class](https://learning.oreilly.com/library/view/effective-java-3rd/9780134686097/ch2.xhtml#lev3)? Did you know that one should [refer to objects by their interfaces](https://learning.oreilly.com/library/view/effective-java-3rd/9780134686097/ch9.xhtml#lev64)? diff --git a/docs/code-howtos/javafx.md b/docs/code-howtos/javafx.md index c08fe96b489..c5faee31d74 100644 --- a/docs/code-howtos/javafx.md +++ b/docs/code-howtos/javafx.md @@ -201,7 +201,6 @@ JabRef makes heavy use of Properties and Bindings. These are wrappers around Obs * [Validation framework](https://github.com/sialcasa/mvvmFX/wiki/Validation) * [mvvm framework](https://github.com/sialcasa/mvvmFX/wiki) * [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 * [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 * [Foojay](https://foojay.io) Java and JavaFX tutorials diff --git a/docs/code-howtos/tools.md b/docs/code-howtos/tools.md index 8d39a161c34..c4e9853a21f 100644 --- a/docs/code-howtos/tools.md +++ b/docs/code-howtos/tools.md @@ -10,7 +10,7 @@ This page lists some software we consider useful. * [Refined GitHub](https://github.com/sindresorhus/refined-github) - GitHub on steroids * [GitHub Issue Link Status](https://github.com/fregante/github-issue-link-status) - proper coloring of linked issues and PRs. * [Codecov Browser Extension](https://github.com/codecov/browser-extension) - displaying code coverage directly when browsing GitHub -* [Sourcegraph Browser Extension](https://docs.sourcegraph.com/integration/browser\_extension) - Navigate through source on github +* [Sourcegraph Browser Extension](https://docs.sourcegraph.com/integration/browser\_extension) - Navigate through source on GitHub ## git hints @@ -37,23 +37,23 @@ See also: [https://help.github.com/articles/syncing-a-fork/](https://help.github (As Administrator - one time) 1. Install [chocolatey](https://chocolatey.org) -2. `choco install git` -3. `choco install conemu clink` -4. `choco install notepadplusplus` -5. If you want to have your jdk also managed via chocolatey: `choco install temurin` +2. `choco install git.install -y --params "/GitAndUnixToolsOnPath /WindowsTerminal` +3. `choco install notepadplusplus` +4. If you want to have your JDK also managed via chocolatey: `choco install temurin` -Then, each weak do `choco upgrade all` to ensure all tooling is uptodate. +Then, each weak do `choco upgrade all` to ensure all tooling is kept updated. ### General git tooling on Windows * Use [git for windows](https://git-for-windows.github.io), no additional git tooling required - * [Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows) is included. Ensure that you include that in the installation. Aim: Store password for GitHub permanently for https repository locations + * [Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows) is included. Ensure that you include that in the installation. Aim: Store password for GitHub permanently for `https` repository locations * [Use notepad++ as editor](http://stackoverflow.com/a/2486342/873282) for `git rebase -i` ### Better console applications #### ConEmu plus clink +* `choco install conemu clink` * [ConEmu](http://conemu.github.io) -> Preview Version - Aim: Colorful console with tabs * At first start: * "Choose your startup task ...": \`{Bash::Git bash\}} @@ -61,11 +61,11 @@ Then, each weak do `choco upgrade all` to ensure all tooling is uptodate. * Upper right corner: "Settings..." (third entrry Eintrag) * Startup/Tasks: Choose task no. 7 ("Bash::Git bash"). At "Task parameters" `/dir C:\git-repositories\jabref\jabref` * `Save Settings` -* [clink](http://mridgers.github.io/clink/) - Aim: Unix keys (Alt+B, Ctrl+S, etc.) also available at the prompt of `cmd.exe` +* [clink](http://mridgers.github.io/clink/) - Aim: Unix keys (Alt+B, Ctrl+S, etc.) also available at the prompt of `cmd.exe` #### Other bundles -* [Cmder](http://cmder.net) - bundles ConEmu plus clink +* [Cmder](https://cmder.app/) - bundles ConEmu plus clink ### Tools for working with XMP @@ -76,4 +76,4 @@ Then, each weak do `choco upgrade all` to ensure all tooling is uptodate. * [AutoHotkey](http://autohotkey.com) - Preparation for the next step * [https://github.com/koppor/autohotkey-scripts](https://github.com/koppor/autohotkey-scripts) - Aim: Have Win+C opening ConEmu 1. Clone the repository locally. - 2. Then link `ConEmu.ahk` and `WindowsExplorer.ahk` at the startup menu (Link creation works with drag'n'drop using the right mouse key and then choosing "Create link" when dropping). Hint: Startup is in the folder `Startup` (German: `Autostart`) at `%APPDATA%\Microsoft\Windows\Start Menu\Programs\` - accessible via `Win+r`: `shell:startup` + 2. Then link `ConEmu.ahk` and `WindowsExplorer.ahk` at the startup menu (Link creation works with drag'n'drop using the right mouse key and then choosing "Create link" when dropping). Hint: Startup is in the folder `Startup` (German: `Autostart`) at `%APPDATA%\Microsoft\Windows\Start Menu\Programs\` - accessible via Win+R: `shell:startup` diff --git a/docs/contributing.md b/docs/contributing.md index 7b1423836d4..d3e078a2d0d 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -13,7 +13,7 @@ nav_order: 2 We welcome contributions to JabRef and encourage you to follow the GitHub workflow specified below. If you are not familiar with this type of workflow, take a look at GitHub's excellent overview on the [GitHub flow](https://guides.github.com/introduction/flow/index.html) and the explanation of [Feature Branch Workflow](https://atlassian.com/git/tutorials/comparing-workflows#feature-branch-workflow) for the idea behind this kind of development. -1. Get the JabRef code on your local machine. Detailed instructions about this step can be found in our [guidelines for setting up a local workspace](getting-into-the-code/guidelines-for-setting-up-a-local-workspace.md). +1. Get the JabRef code on your local machine. Detailed instructions about this step can be found in our [guidelines for setting up a local workspace](getting-into-the-code/guidelines-for-setting-up-a-local-workspace/). 1. Fork the JabRef into your GitHub account. 2. Clone your forked repository on your local machine. 2. **Create a new branch** (such as `fix-for-issue-121`). Be sure to create a **separate branch** for each improvement you implement. diff --git a/docs/decisions/0020-use-Jackson-to-parse-study-yml.md b/docs/decisions/0020-use-Jackson-to-parse-study-yml.md index ac58c3f7689..6f0dbee85d7 100644 --- a/docs/decisions/0020-use-Jackson-to-parse-study-yml.md +++ b/docs/decisions/0020-use-Jackson-to-parse-study-yml.md @@ -13,7 +13,7 @@ What parser should be used to parse YAML files? ## Considered Options * [Jackson](https://github.com/FasterXML/jackson-dataformat-yaml) -* [SnakeYAML Engine](https://bitbucket.org/asomov/snakeyaml) +* [SnakeYAML Engine](https://bitbucket.org/snakeyaml/snakeyaml-engine/src/master/) * [yamlbeans](https://github.com/EsotericSoftware/yamlbeans) * [eo-yaml](https://github.com/decorators-squad/eo-yaml) * Self-written parser @@ -34,7 +34,7 @@ Chosen option: Jackson, because as it is a dedicated library for parsing YAML. ` * Good, because established YAML parser library * Good, because supports YAML 1.2 -* Bad, because cannot parse YAML into Java DTOs, only into [basic Java structures](https://bitbucket.org/asomov/snakeyaml-engine/src/master/), this then has to be assembled into DTOs +* Bad, because cannot parse YAML into Java DTOs, only into [basic Java structures](https://bitbucket.org/snakeyaml/snakeyaml-engine/src/master/), this then has to be assembled into DTOs ### yamlbeans diff --git a/docs/decisions/0026-use-jna-to-determine-default-directory.md b/docs/decisions/0026-use-jna-to-determine-default-directory.md index 40b76f03e39..210be28eb59 100644 --- a/docs/decisions/0026-use-jna-to-determine-default-directory.md +++ b/docs/decisions/0026-use-jna-to-determine-default-directory.md @@ -34,8 +34,10 @@ Swing's FileChooser implemented a very decent directory determination algorithm. It thereby uses `sun.awt.shell.ShellFolder`. * Good, because provides best results on most platforms. +* Good, because also supports localization of the folder name. E.g., `~/Dokumente` in Germany. * Bad, because introduces a dependency on Swing and thereby contradicts the second decision driver. -* Bad, because GraalVM's support Swing is experimental +* Bad, because GraalVM's support Swing is experimental. +* Bad, because handles localization only on Windows. ### Use `user.home` @@ -44,25 +46,17 @@ There is `System.getProperty("user.home");`. * Bad, because "The concept of a HOME directory seems to be a bit vague when it comes to Windows". See for details. * Bad, because it does not include `Documents`: As of 2022, `System.getProperty("user.home")` returns `c:\Users\USERNAME` on Windows 10, whereas - `FileSystemView` returns `C:\Users\USERNAME\Documents`, which is the "better" directory + `FileSystemView` returns `C:\Users\USERNAME\Documents`, which is the "better" directory. ### AppDirs > AppDirs is a small java library which provides a path to the platform dependent special folder/directory. -* Good, because already used in JabRef -* Bad, because does not use `Documents` on Windows, but rather `C:\Users\\AppData\\` as basis +* Good, because already used in JabRef. +* Bad, because does not use `Documents` on Windows, but rather `C:\Users\\AppData\\` as basis. ### Java Native Access -* Good, because no additional dependency required, as it is already loaded by AppDirs -* Good, because it is well maintained and widely used -* Good, because it provides direct access to `Documents` and other system variables - -## More Information - -{You might want to provide additional evidence/confidence for the decision outcome here and/or - document the team agreement on the decision and/or - define when this decision when and how the decision should be realized and if/when it should be re-visited and/or - how the decision is validated. - Links to other decisions and resources might here appear as well.} +* Good, because no additional dependency required, as it is already loaded by AppDirs. +* Good, because it is well maintained and widely used. +* Good, because it provides direct access to `Documents` and other system variables. diff --git a/docs/getting-into-the-code/development-strategy.md b/docs/getting-into-the-code/development-strategy.md index 84a6fea798b..8fddcd79b47 100644 --- a/docs/getting-into-the-code/development-strategy.md +++ b/docs/getting-into-the-code/development-strategy.md @@ -18,7 +18,14 @@ Read on about our automated quality checks at [Code Quality](../code-howtos/code ## Continuous integration -Since end of 2019, we just use GitHub actions to execute our tests and to creates binaries. The binaries are created using [gradle](https://gradle.org) and are uploaded to [https://builds.jabref.org](https://builds.jabref.org). These binaries are created without any checks to have them available as quickly as possible, even if the localization or some fetchers are broken. Deep link: [https://github.com/JabRef/jabref/actions?workflow=Deployment](https://github.com/JabRef/jabref/actions?workflow=Deployment). +JabRef has automatic checks using GitHub actions in place. +One of them is checking for the formatting of the code. +Consistent formatting ensures more easy reading of the code. +Thus, we pay attention that JabRef's code follows the same code style. + +Binaries are created using [gradle](https://gradle.org) and are uploaded to [https://builds.jabref.org](https://builds.jabref.org). +These binaries are created without any checks to have them available as quickly as possible, even if the localization or some fetchers are broken. +Deep link to the action: [https://github.com/JabRef/jabref/actions?workflow=Deployment](https://github.com/JabRef/jabref/actions?workflow=Deployment). ## Branches 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 deleted file mode 100644 index b7a5b5ae5de..00000000000 --- a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace.md +++ /dev/null @@ -1,732 +0,0 @@ ---- -parent: Getting into the code -nav_order: 2 ---- -# Set up a local workspace - -This guide explains how to set up your environment for development of JabRef. It includes information about prerequisites, configuring your IDE, and running JabRef locally to verify your setup. - -{: .highlight } -The most important step is to configure your IDE. -If you know how to fork and check out JabRef's code, -already have an IDE installed, then -please scroll down to the [IDE setup](#Get the code into your IDE). -Otherwise, please keep on reading. - -## Prerequisites - -This section list the prerequisites you need to get started to develop JabRef. After this section, you are ready to get the code. - -### GitHub Account - -If you do not yet have a GitHub account, please [create one](https://github.com/join). - -Proposals for account names: - -* Login similar to your university account. Example: `koppor` -* Use your last name prefixed by the first letter of your first name. Example: `okopp` -* Use `firstname.lastname`. Example: `oliver.kopp` - -You can hide your email address by following the recommendations at [https://saraford.net/2017/02/19/how-to-hide-your-email-address-in-your-git-commits-but-still-get-contributions-to-show-up-on-your-github-profile-050/](https://saraford.net/2017/02/19/how-to-hide-your-email-address-in-your-git-commits-but-still-get-contributions-to-show-up-on-your-github-profile-050/). - -Most developers, though, do not hide their email address. They use one which may get public. Mostly, they create a new email account for development only. That account then be used for development mailing lists, mail exchange with other developers, etc. - -Examples: - -* Same login as in GitHub (see above). Example: `koppor@gmail.com` -* "`it`" in the name. Example: `kopp.it@gmail.com` -* Use the university login. Example: `st342435@stud.uni-stuttgart.de` - -### git - -It is strongly recommended that you have git installed. - -* On Debian-based distros: `sudo apt-get install git` -* On Windows: [Download the installer](http://git-scm.com/download/win) and install it. Using [chocolatey](https://chocolatey.org/), you can run `choco install git.install -y --params "/GitAndUnixToolsOnPath /WindowsTerminal` to a) install git and b) have Linux commands such as `grep` available in your `PATH`. -* [Official installation instructions](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) - -### Installed IDE - -We suggest [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=jabref). -The Community Edition works well. -Most contributors use the Ultimate Edition, because they are students getting that edition for free. - -For advanced users, [Eclipse](https://eclipse.org) (`2023-03` or newer) is also possible. For JDK20 you need to install the addtional [support for jdk20 as extension](https://marketplace.eclipse.org/content/java-20-support-eclipse-2023-03-427)). -On Ubuntu Linux, you can follow the [documentation from the Ubuntu Community](https://help.ubuntu.com/community/EclipseIDE#Download\_Eclipse) or the [step-by-step guideline from Krizna](https://www.krizna.com/ubuntu/install-eclipse-in-ubuntu-12-04/) to install Eclipse. -On Windows, download it from [www.eclipse.org](http://www.eclipse.org/downloads/) and run the installer. - -### Java Development Kit 20 - -For Eclipse, a working Java (Development Kit) 20 installation is required. -In the case of IntelliJ, this will be downloaded inside the IDE (if you follow the steps below). - -In the command line (terminal in Linux, cmd in Windows) run `javac -version` and make sure that the reported version is Java 20 (e.g., `javac 20`). -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. -Please head to to download JDK 20. - - -## Get the code - -This section explains how you get the JabRef code onto your machine in a form allowing you to make contributions. - -### Fork JabRef into your GitHub account - -1. Log into your GitHub account -2. Go to [https://github.com/JabRef/jabref](https://github.com/JabRef/jabref) -3. Create a fork by clicking at fork button on the right top corner -4. A fork repository will be created under your account `https://github.com/YOUR_USERNAME/jabref`. - -### Clone your forked repository on your local machine - -In a command line, navigate to the folder where you want to place the source code (parent folder of `jabref`). -To prevent issues along the way, it is strongly recommend choosing a path that does not contain any special (non-ASCII or whitespace) characters. -In the following, we will use `c:\git-repositories` as base folder: - -```cmd -cd \ -mkdir git-repositories -cd git-repositories -``` - -**Note that putting the repo jabref directly under `C:\` or any other drive letter under windows causes compile errors** - -Initial cloning might be very slow (`27.00 KiB/s`). - -To prevent this, execute the following steps: - -```cmd -git clone --depth=10 https://github.com/JabRef/jabref.git -cd jabref -git remote rename origin upstream -git remote add origin https://github.com/YOUR_USERNAME/jabref.git -``` - -The `--depth--10` is used to limit the download to \~20 MB instead of downloading the complete history (\~800 MB). -If you want to dig in our commit history, feel free to download everything. - -Now, you have two remote repositories, where `origin` is yours and `upstream` is the one of the JabRef organization. - -You can see it with `git remote -v`: - -```cmd -c:\git-repositories\jabref> git remote -v -origin https://github.com/YOURUSERNAME/jabref.git (fetch) -origin https://github.com/YOURUSERNAME/jabref.git (push) -upstream https://github.com/jabref/jabref.git (fetch) -upstream https://github.com/jabref/jabref.git (push) -``` - -### Generate source code - -{: .note } -This is required for Eclipse only. -In IntelliJ, this will be done in the IDE. - -Generate additional source code: `gradlew assemble` (on Linux/Mac: `./gradlew assemble`). - -The directory `src-gen` is now filled. - -## Get the code into your IDE - -Start IntelliJ. - -IntelliJ shows the following window: - -{% figure caption:"IntelliJ Start Window" %} -![IntelliJ Start Window](guidelines-intellij-start-window.png) -{% endfigure %} - -Click on "Open" - -Choose `build.gradle` in the root of the jabref source folder: - -{% figure caption:"Choose `build.gradle` in the “Open Project or File” dialog" %} -![Open File or Project dialog](guidelines-intellij-choose-build-gradle.png) -{% endfigure %} - -After pressing "OK", IntelliJ asks how that file should be opened. -Answer: "Open as Project" - -{% figure caption:"Choose “Open as Project” in the Open Project dialog" %} -![Open Project dialog](guidelines-choose-open-as-project.png) -{% endfigure %} - -Then, trust the project: - -{% figure caption:"Choose “Trust Project” in the “Trust and Open Project” dialog" %} -![Trust and Open Project dialog](guidelines-trust-project.png) -{% endfigure %} - -## Configuration of IntelliJ IDEA - -These steps are very important. They allow you to focus on the content and ensure that the code formatting always goes well. Did you know that [IntelliJ allows for reformatting selected code](https://www.jetbrains.com/help/idea/reformat-and-rearrange-code.html#reformat\_code) if you press Ctrl + Alt + L? - -IntelliJ IDEA fully supports Gradle as a build tool, but also has an internal build system which is usually faster. For JabRef, Gradle is required to make a full build. -Once set up, IntelliJ IDEA's internal system can be used for subsequent builds. - -In case IntelliJ's internal build system does not work, just stick with using Gradle. - -### Ensure that JDK 20 is available to IntelliJ - -Ensure you have a Java 20 SDK configured by navigating to **File > Project Structure... > Platform Settings > SDKs**. - -{% figure caption:"JDKs 11, 14, and 15 shown in available SDKs. JDK 20 is missing." %} -![Plattform Settings - SDKs](../images/intellij-choose-jdk-adoptopenjdk-on-windows-project-settings.png) -{% endfigure %} - -If there is another JDK than JDK 20 selected, click on the plus button and choose "Download JDK..." - -{% figure caption:"Download JDK..." %} -![Plattform Settings - SDKs - plus button - Download JDK...](guidelines-select-download-jdk.png) -{% endfigure %} - -Select JDK version 20 and then Eclipse Temurin (showing JDK 18 as example). - -{% figure caption:"Example for JDK 18 - Choose Eclipse Temurin" %} -![Download Eclipse Temurin](guidelines-select-jdk-18-eclipse-temurin.png) -{% endfigure %} - -After clicking "Download", IntelliJ installs Eclipse Temurin: - -{% figure caption:"IntelliJ installs Eclipse Temurin" %} -![IntelliJ installs Eclipse Temurin](guidelines-intellij-installs-temurin.png) -{% endfigure %} - -Navigate to **Project Settings > Project** and ensure that the projects' SDK is Java 20 - -{% figure caption:"Project SDK is pinned to the downloaded SDK (showing JDK 18 as example)" %} -![Project SDK is JDK 18](guidelines-intellij-project-settings-jdk18.png) -{% endfigure %} - -Click "OK" to store the changes. - -### Configure the Build System - -Navigate to **File > Settings... > Build, Execution, Deployment > Build Tools > Gradle** and select the "Project SDK" as the Gradle JVM at the bottom. If that does not exist, just select a JDK 20. - -{% figure caption:"Gradle JVM is project SDK (showing JDK 18 as example)" %} -![Gradle JVM is project SDK](guidelines-settings-gradle-gradlejvm-is-projectjvm.png) -{% endfigure %} - -To prepare IntelliJ's build system additional steps are required: - -Navigate to **Build, Execution, Deployment > Compiler > Java Compiler**, and under "Override compiler parameters per-module", click add (\[+]) and choose `JabRef.main`: - -{% figure caption:"Choose JabRef.main" %} -![Gradle JVM is project SDK](guidelines-choose-module.png) -{% endfigure %} - -Then double click inside the cell "Compilation options" and enter following parameters: - -```text ---add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref ---add-exports=org.controlsfx.controls/impl.org.controlsfx.skin=org.jabref -``` - -Press Enter to have the value really stored. -Otherwise, it seems like the setting is stored, but it is not there if you re-open this preference dialog. -Then click on "Apply" to store the setting. - -{% figure caption:"Resulting settings for module JabRef.main" %} -![Overridden compiler parameters](guidelines-overridden-compiler-parameters.png) -{% endfigure %} - -If this step is omited, you will get: `java: package com.sun.javafx.scene.control is not visible (package com.sun.javafx.scene.control is declared in module javafx.controls, which does not export it to module org.jabref)`. - -Enable annotation processors by navigating to **Build, Execution, Deployment > Compiler > Annotation processors** and check "Enable annotation processing" - -{% figure caption:"Enabled annotation processing" %} -![Enable annotation processing](guidelines-intellij-enable-annotation-processing.png) -{% endfigure %} - -### Using Gradle from within IntelliJ IDEA - -Ensuring JabRef builds with Gradle should always the first step because, e.g. it generates additional sources that are required for compiling the code. - -Open the Gradle Tool Window with the small button that can usually be found on the right side of IDEA or navigate to **View > Tool Windows > Gradle**. -In the Gradle Tool Window, press the "Reload All Gradle Projects" button to ensure that all settings are up-to-date with the setting changes. - -{% figure caption:"Reload of Gradle project" %} -![Highlighted reload button](guidelines-gradle-tool-windows-refresh.png) -{% endfigure %} - -After that, you can use the Gradle Tool Window to build all parts JabRef and run it. -To do so, expand the JabRef project in the Gradle Tool Window and navigate to Tasks. -From there, you can build and run JabRef by double-clicking **JabRef > Tasks > application > run**. - -{% figure caption:"JabRef > Tasks > application > run" %} -![JabRef > Tasks > application > run](guidelines-gradle-run.png) -{% endfigure %} - -The Gradle run window opens, shows compilation and then the output of JabRef. -The spinner will run as long as JabRef is opened. - -{% figure caption:"Gradle run Window" %} -![Gradle run window](guidelines-gradle-run-output.png) -{% endfigure %} - -You can close JabRef again. - -After that a new entry called "jabref \[run]" appears in the run configurations. -Now you can also select "jabref \[run]" and either run or debug the application from within IntelliJ. - -You can run any other development task in a similar way. -Equivalently, this can also be executed from the terminal by running `./gradlew run`. - -### Using IntelliJ's internal build system for tests - -You should use IntelliJ IDEA's internal build system for compiling and running JabRef tests during development, because it is usually more responsive. - -{: .important} -When using IntelliJ's build system, **it is important** that you understand that JabRef relies on generated sources which are only built through Gradle. -Therefore, to build or update these dependencies you need to run the `run` (or `assemble`) Gradle task at least once. -When you followed this guide, you should have done it in the Gradle setup. - -{: .note} -Running JabRef itself through IntellIJ's build system is **not** possible as we encounter difficulties when reading resources though `.class.getResource(...)`. -Although solutions are discussed in length [on stackoverflow](https://stackoverflow.com/q/26328040/873282), there is no "good" solution for us. - -In **File > Settings... > Build, Execution, Deployment > Build Tools > Gradle** the setting "Run tests using:" is set to "IntelliJ IDEA". - -{% figure caption:"IntelliJ setting: Run tests using IntelliJ" %} -![IntelliJ setting: Run tests using IntelliJ"](guidelines-intellij-settings-run-tests-using-intellij.png) -{% endfigure %} - -**In case there are difficulties later, this is the place to switch back to gradle.** - -Click "OK" to close the preference dialog. - -In the menubar, select **Build > Rebuild project**. - -IntelliJ now compiles JabRef. -This should happen without any error. - -Now you can use IntelliJ IDEA's internal build system by using **Build > Build Project**. - -To run an example test from IntelliJ, we let IntelliJ create a launch configuration: - -Locate the class `BibEntryTest`: -Press Ctrl+N. -Then, the "Search for classes dialog" pops up. -Enter `bibenrytest`. -Now, `BibEntryTest` should appear first: - -{% figure caption:"IntelliJ search for class “BibEntryTest”" %} -![IntelliJ search for class "BibEntryTest"](guidelines-intellij-locate-bibentrytest.png) -{% endfigure %} - -Press Enter to jump to that class. - -Hover on the green play button on `testDefaultConstructor`: - -{% figure caption:"However on green play button" %} -![However on green play button](guidelines-intellij-run-single-test.png) -{% endfigure %} - -Then, click on it. -A popup menu opens. -Choose the first entry "Run testDefaultConstructor" and click on it. - -{% figure caption:"Run testDefaultConstructor" %} -![Popup menu - Run testDefaultConstructor](guidelines-intellij-run-single-test-launch-config.png) -{% endfigure %} - -Then, the single test starts. - -You also have an entry in the Launch configurations to directly launch the test. -You can also click on the debug symbol next to it to enable stopping at breakpoints. - -{% figure caption:"Launch menu contains BibEntry test case" %} -![Launch menu contains BibEntry test case](guidelines-intellij-run-bibentry-test.png) -{% endfigure %} - -The tests are green after the run. -You can also use the play button there to re-execute the tests. -A right-click on "BibEntryTests" enables to start the debugger. - -{% figure caption:"Run window for the BibEntry test case" %} -![Run window for the BibEntry test case](guidelines-intellij-tests-are-green.png) -{% endfigure %} - -{: .note } -Essentially, you now have the best of both worlds: -You can run Gradle tasks using the Gradle Tool Window. -You can compile and run tests with IntelliJ's faster internal build system -(unless you haven't made changes to input files that generate sources). - -### Using JabRef's code style - -Contributions to JabRef's source code need to have a code formatting that is consistent with existing source code. For that purpose, JabRef provides code-style and check-style definitions. - -Install the [CheckStyle-IDEA plugin](http://plugins.jetbrains.com/plugin/1065?pr=idea), it can be found via the plug-in repository: -Navigate to **File > Settings... > Plugins"**. -On the top, click on "Marketplace". -Then, search for "Checkstyle". -Click on "Install" choose "CheckStyle-IDEA". - -{% figure caption:"Install CheckStyle" %} -![Install CheckStyle](guidelines-intellij-install-checkstyle.png) -{% endfigure %} - -After clicking, IntelliJ asks for confirmation: - -{% figure caption:"Third Party Plugin Privacy Notice" %} -![Third Party Plugin Privacy Notice](guidelines-intellij-checkstyle-confirmation.png) -{% endfigure %} - -If you agree, click on "Agree" and you can continue. - -Afterwards, use the "Restart IDE" button to restart IntelliJ. - -{% figure caption:"IntelliJ restart IDE" %} -![IntelliJ restart IDE](guidelines-intellij-checkstyle-restart-ide.png) -{% endfigure %} - -Click on "Restart" to finally restart. - -Wait for IntelliJ coming up again. - -Go to **File > Settings... > Editor > Code Style** - -Click on the settings wheel (next to the scheme chooser), -then click "Import Scheme >", -then click "IntelliJ IDEA code style XML" - -{% figure caption:"Location of “Import Scheme > IntelliJ IDEA code style XML”" %} -![Location of IntelliJ IDEA code style XML](guidelines-intellij-codestyle-import.png) -{% endfigure %} - -You have to browse for the directory `config` in JabRef's code. -There is an `IntelliJ Code Style.xml`. - -{% figure caption:"Browsing for `config/IntelliJ Code Style.xml`" %} -![Browsing for config/IntelliJ Code Style.xml](guidelines-intellij-codestyle-import-select-xml-file.png) -{% endfigure %} - -Click "OK". - -At following dialog is "Import Scheme". -Click there "OK", too. - -{% figure caption:"Import to JabRef" %} -![Import to JabRef](guidelines-intellij-codestyle-import-as-jabref.png) -{% endfigure %} - -Click on "Apply" to store the preferences. - -### Put JabRef's checkstyle configuration in place - -Now, put the checkstyle configuration file is in place: - -Go to **File > Settings... > Tools > Checkstyle > Configuration File** - -Trigger the import dialog of a CheckStyle style by clicking the \[+] button: - -{% figure caption:"Trigger the rule import dialog" %} -![Trigger the rule import dialog](guidelines-intellij-checkstyle-start-import.png) -{% endfigure %} - -Then: - -* Put "JabRef" as description. -* Browse for `config/checkstyle/checkstyle.xml` -* Tick "Store relative to project location" -* Click "Next" - -{% figure caption:"Filled Rule Import Dialog" %} -![Filled Rule Import Dialog](guidelines-intellij-checkstyle-import-file.png) -{% endfigure %} - -Click on "Finish" - -Activate the CheckStyle configuration file by ticking it in the list - -{% figure caption:"JabRef's checkstyle config is activated" %} -![JabRef's checkstyle config is activated](guidelines-intellij-checkstyle-jabref-active.png) -{% endfigure %} - -Ensure that the [latest CheckStyle version](https://checkstyle.org/releasenotes.html) is selected (10.3.4 or higher). -Also, set the "Scan Scope" to "Only Java sources (including tests)". - -{% figure caption:"Checkstyle is the highest version - and tests are also scanned" %} -![Checkstyle is the highest version - and tests are also scanned](guidelines-intellij-checkstyle-final-settings.png) -{% endfigure %} - -Save settings by clicking "Apply" and then "OK" - -In the lower part of IntelliJ's window, click on "Checkstyle". -In "Rules", change to "JabRef". -Then, you can run a check on all modified files. - -{% figure caption:"JabRef's style is active - and we are ready to run a check on all modified files" %} -![JabRef's style is active - and we are ready to run a check on all modified files](guidelines-intellij-checkstyle-window.png) -{% endfigure %} - -### Have auto format working properly in JavaDoc - -To have auto format working properly in the context of JavaDoc and line wrapping, "Wrap at right margin" has to be disabled. Details are found in [IntelliJ issue 240517](https://youtrack.jetbrains.com/issue/IDEA-240517). - -Go to **File > Settings... > Editor > Code Style > Java > JavaDoc**. - -At "Other", disable "Wrap at right margin" - -{% figure caption:"”Wrap at right margin” disabled" %} -!["Wrap at right margin" disabled](guidelines-intellij-editor-javadoc-do-not-wrap.png) -{% endfigure %} - -### Enable proper import cleanup - -To enable "magic" creation and auto cleanup of imports, go to **File > Settings... > Editor > General > Auto Import**. -There, enable both "Add unambiguous imports on the fly" and "Optimize imports on the fly" -(Source: [JetBrains help](https://www.jetbrains.com/help/idea/creating-and-optimizing-imports.html#automatically-add-import-statements)). - -{% figure caption:"Auto import enabled" %} -![Enable auto import](guidelines-intellij-editor-autoimport.png) -{% endfigure %} - -Press "OK". - -### Disable too advanced code folding - -Go to **File > Settings... > Editor > General > Code Folding**. -At "Java", disable "General > File header", "General > Imports", and "Java > One-line methods". - -{% figure caption:"Code foldings disabled" %} -![Code foldings disabled](guidelines-settings-intellij-code-foldings.png) -{% endfigure %} - -Press "OK". - -{: .highlight } -> Now you have configured IntelliJ completely. -> You can run the main application using Gradle and the test cases using IntelliJ. -> The code formatting rules are imported - and the most common styling issue at imports is automatically resolved by IntelliJ. -> Finally, you have Checkstyle running locally so that you can check for styling errors before submitting the pull request. -> -> We wish you a successful contribution! - -### Key hints for IntelliJ - -* Shift+Shift (AKA double-shift): Open the search dialog. -* Ctrl+N: Open the search dialog and select search for a class. -* Ctrl+Shift+F: Search everywhere in the code base. -* Alt+F1 and then Enter: Locate the file in the search bar on the left side. -* Ctrl+Shift+T: Navigate from a class to the test class. - -## Setup for Eclipse - -Make sure your Eclipse installation us up to date. - -1. Run `./gradlew run` to generate all resources and to check if JabRef runs. - * The JabRef GUI should finally appear. - * This step is only required once. -2. Run `./gradlew eclipse` - * **This must always be executed, when there are new upstream changes.** -3. Open or import the existing project in Eclipse as Java project. - * Remark: Importing it as gradle project will not work correctly. - * Refresh the project in Eclipse -4. Create a run/debug configuration for the main class `org.jabref.cli.Launcher` 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. `Launcher` or JabRefMain) otherwise it will not work. - - ![Creating the run/debug configuration by right clicking on the class](<../images/eclipse-create-run-config.png>) - * In the tab "Arguments" of the run/debug configuration, enter the following runtime VM arguments: - - ```text - --add-exports javafx.controls/com.sun.javafx.scene.control=org.jabref - --add-exports org.controlsfx.controls/impl.org.controlsfx.skin=org.jabref - --add-exports javafx.graphics/com.sun.javafx.scene=org.controlsfx.controls - --add-exports javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls - --add-exports javafx.graphics/com.sun.javafx.css=org.controlsfx.controls - --add-exports javafx.controls/com.sun.javafx.scene.control.behavior=org.controlsfx.controls - --add-exports javafx.controls/com.sun.javafx.scene.control=org.controlsfx.controls - --add-exports javafx.controls/com.sun.javafx.scene.control.inputmap=org.controlsfx.controls - --add-exports javafx.base/com.sun.javafx.event=org.controlsfx.controls - --add-exports javafx.base/com.sun.javafx.collections=org.controlsfx.controls - --add-exports javafx.base/com.sun.javafx.runtime=org.controlsfx.controls - --add-exports javafx.web/com.sun.webkit=org.controlsfx.controls - --add-exports javafx.graphics/com.sun.javafx.css=org.controlsfx.controls - --add-exports javafx.controls/com.sun.javafx.scene.control.behavior=com.jfoenix - --add-exports javafx.graphics/com.sun.javafx.stage=com.jfoenix - --add-exports com.oracle.truffle.regex/com.oracle.truffle.regex=org.graalvm.truffle - --patch-module org.jabref=build/resources/main - ``` - - * In the tab "Dependencies" of the run/debug configuration tick the checkbox "Exclude test code" -5. Optional: Install the [e(fx)clipse plugin](http://www.eclipse.org/efxclipse/index.html) from the Eclipse marketplace: 1. Help -> Eclipse Marketplace... -> Search tab 2. Enter "e(fx)clipse" in the search dialogue 3. Click "Go" 4. Click "Install" button next to the plugin 5. Click "Finish" -6. Now you can build and run/debug the application by either using `Launcher` or `JabRefMain`. This is the recommended way, since the application starts quite fast. - -### Localization Test Configuration (Eclipse) - -To run the `LocalizationConsistencyTest` you need to add some extra module information: Right-click on the file -> "Run/Debug as JUnit test". Go to the Run/debug configuration created for that file and in the arguments tab under VM-configurations add: - -```text ---add-exports javafx.graphics/com.sun.javafx.application=ALL-UNNAMED ---add-exports javafx.graphics/com.sun.javafx.stage=ALL-UNNAMED ---add-exports javafx.graphics/com.sun.javafx.stage=com.jfoenix -``` - -## Final comments - -Got it running? GREAT! You are ready to lurk the code and contribute to JabRef. Please make sure to also read our [contribution guide](https://github.com/JabRef/jabref/blob/main/CONTRIBUTING.md). - -### Other tooling - -We collected some other tooling recommendations. -We invite you to read on at our [tool recommendations](../code-howtos/tools.md). - -## Common issues - -### Java installation - -An indication that `JAVA_HOME` is not correctly set or no JDK 20 is installed is following error message: - -```text -compileJava FAILED - -FAILURE: Build failed with an exception. - -* What went wrong: -Execution failed for task ':compileJava'. -> java.lang.ExceptionInInitializerError (no error message) -``` - -Another indication is following output - -```text -java.lang.UnsupportedClassVersionError: org/javamodularity/moduleplugin/ModuleSystemPlugin has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0 -``` - -### Issues with generated source files - -In rare cases you might encounter problems due to out-dated automatically generated source files. Running `./gradlew clean` deletes these old copies. Do not forget to run at least `./gradlew eclipse` or `./gradlew build` afterwards to regenerate the source files. - -### Issues with `buildSrc` - -1. Open the context menu of `buildSrc`. -2. Select "Load/Unload modules". -3. Unload `jabRef.buildSrc`. - -### Issue with "Module org.jsoup" not found, required by org.jabref - -Following error message appears: - -```text -Error occurred during initialization of boot layer -java.lang.module.FindException: Module org.jsoup not found, required by org.jabref -``` - -This can include different modules. - -1. Go to File -> Invalidate caches... -2. Check "Clear file system cache and Local History". -3. Check "Clear VCS Log caches and indexes". -4. Uncheck the others. -5. Click on "Invalidate and Restart". -6. After IntelliJ restarted, you have to do the "buildSrc", "Log4JAppender", and "src-gen" steps again. - -### Issues with OpenJFX libraries in local maven repository - -There might be problems with building if you have OpenJFX libraries in local maven repository, resulting in errors like this: - -```text - > Could not find javafx-fxml-20-mac.jar (org.openjfx:javafx-fxml:20). - Searched in the following locations: - file:/repository/org/openjfx/javafx-fxml/20/javafx-fxml-20-mac.jar -``` - -As a workaround, you can remove all local OpenJFX artifacts by deleting the whole OpenJFX folder from specified location. - -### Issues with `JournalAbbreviationLoader` - -In case of a NPE at `Files.copy` at `org.jabref.logic.journals.JournalAbbreviationLoader.loadRepository(JournalAbbreviationLoader.java:30) ~[classes/:?]`, invalidate caches and restart IntelliJ. Then, Build -> Rebuild Project. - -If that does not help: - -1. Save/Commit all your work -2. Close IntelliJ -3. Delete all non-versioned items: `git clean -xdf`. This really destroys data -4. Execute `./gradlew run` -5. Start IntelliJ and try again. - -### Hints for IntelliJ 2021 - -In older IntelliJ setups, more things were necessary: - -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"](../images/intellij-gradle-config-ignore-buildSrc.png) - -Add `src-gen` as root: - -1. Right click on the project "jabref". -2. Select "Open Module Settings" -3. Expand "JabRef" -4. Select "main" -5. Select tab "Sources" -6. Click "+ Add Content Root" -7. Select the `src-gen` directory -8. Click "OK". When expanding "main", "java" should have been selected as source -9. Click "OK" to save the changes - -In case the above step does not work, run with gradle, import gradle project again, and try again. - -~~Note that the above steps might not work on IntelliJ 2020.x.\*\*. You have to keep using gradle for executing tasks. See~~ [~~IDEA-249391~~](https://youtrack.jetbrains.com/issue/IDEA-249391) ~~for details.~~ - -In case all steps are followed, and there are still issues with `SearchBaseVisitor` (e.g., `Error:(16, 25) java: package org.jabref.search does not exist`), you have to delete `src\main\generated\org\jabref\gui\logging\plugins\Log4jPlugins.java`. This is independent of having enabled or disabled Annotation Processing (see above at "Enable Annotation Processing"). - ---- - -In **File > Settings... > Build, Execution, Deployment > Build Tools > Gradle** the setting "Build and run using" and "Test using" is set to "IntelliJ IDEA". - -{% figure caption:"IntelliJ setting: Build and run using IntelliJ" %} -![IntelliJ setting: Build and run using IntelliJ"](guidelines-intellij-settings-build-and-run-intellij.png) -{% endfigure %} - -**In case there are difficulties later, this is the place to switch back to gradle.** - -Click "OK" to close the preference dialog. - -In the menubar, select **Build > Rebuild project**. - -IntelliJ now compiles JabRef. -This should happen without any error. - -Now you can use IntelliJ IDEA's internal build system by using **Build > Build Project**. - -To run JabRef from IntelliJ, we let IntelliJ create a launch configuration: - -Locate the class `Launcher`: -Press Ctrl+N. -Then, the "Search for classes dialog" pops up. -Enter `Launcher`. -Now, only one class should have been found: - -{% figure caption:"IntelliJ search for class “Launcher”" %} -![IntelliJ search for class "Launcher"](guidelines-intellij-search-for-launcher.png) -{% endfigure %} - -Press Enter to jump to that class. - -Hover on the green play button. - -{% figure caption:"However on green play" %} -![However on green play](guidelines-intellij-hover-on-play-button.png) -{% endfigure %} - -Then, click on it. -A popup menu opens. -Choose the first entry and click on it. - -{% figure caption:"Run JabRef via launcher" %} -![Popup menu - Run JabRef via launcher](guidelines-intellij-run-jabref-from-launcher.png) -{% endfigure %} - -Then, JabRef starts. - -You also have an entry in the Launch configurations to directly launch the JabRef GUI: - -{% figure caption:"Launch menu contains “Launcher”" %} -![Launch menu contains launcher](intellij-run-launcher.png) -{% endfigure %} - -You can also click on the debug symbol next to it to enable stopping at breakpoints. diff --git a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/eclipse.md b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/eclipse.md new file mode 100644 index 00000000000..19fcd469db1 --- /dev/null +++ b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/eclipse.md @@ -0,0 +1,70 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 90 +--- + +# Advanced: Eclipse as IDE + +For advanced users, [Eclipse](https://eclipse.org) (`2023-03` or newer) is also possible. +For JDK20 you need to install the additional [support for jdk20 as extension](https://marketplace.eclipse.org/content/java-20-support-eclipse-2023-03-427)). +On Ubuntu Linux, you can follow the [documentation from the Ubuntu Community](https://help.ubuntu.com/community/EclipseIDE#Download\_Eclipse) or the [step-by-step guideline from Krizna](https://www.krizna.com/ubuntu/install-eclipse-in-ubuntu-12-04/) to install Eclipse. +On Windows, download it from [www.eclipse.org](http://www.eclipse.org/downloads/) and run the installer. + +For Eclipse, a working Java (Development Kit) 20 installation is required. +In the case of IntelliJ, this will be downloaded inside the IDE (if you follow the steps below). + +In the command line (terminal in Linux, cmd in Windows) run `javac -version` and make sure that the reported version is Java 20 (e.g., `javac 20`). +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. +Please head to to download JDK 20. + +Always make sure your Eclipse installation us up to date. + +1. Run `./gradlew run` to generate all resources and to check if JabRef runs. + * The JabRef GUI should finally appear. + * This step is only required once. + * The directory `src-gen` is now filled. +2. Run `./gradlew eclipse` + * **This must always be executed, when there are new upstream changes.** +3. Open or import the existing project in Eclipse as Java project. + * Remark: Importing it as gradle project will not work correctly. + * Refresh the project in Eclipse +4. Create a run/debug configuration for the main class `org.jabref.cli.Launcher` 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. `Launcher` or JabRefMain) otherwise it will not work. + + ![Creating the run/debug configuration by right-clicking on the class](../../images/eclipse-create-run-config.png) + * In the tab "Arguments" of the run/debug configuration, enter the following runtime VM arguments: + + ```text + --add-exports javafx.controls/com.sun.javafx.scene.control=org.jabref + --add-exports org.controlsfx.controls/impl.org.controlsfx.skin=org.jabref + --add-exports javafx.graphics/com.sun.javafx.scene=org.controlsfx.controls + --add-exports javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls + --add-exports javafx.graphics/com.sun.javafx.css=org.controlsfx.controls + --add-exports javafx.controls/com.sun.javafx.scene.control.behavior=org.controlsfx.controls + --add-exports javafx.controls/com.sun.javafx.scene.control=org.controlsfx.controls + --add-exports javafx.controls/com.sun.javafx.scene.control.inputmap=org.controlsfx.controls + --add-exports javafx.base/com.sun.javafx.event=org.controlsfx.controls + --add-exports javafx.base/com.sun.javafx.collections=org.controlsfx.controls + --add-exports javafx.base/com.sun.javafx.runtime=org.controlsfx.controls + --add-exports javafx.web/com.sun.webkit=org.controlsfx.controls + --add-exports javafx.graphics/com.sun.javafx.css=org.controlsfx.controls + --add-exports javafx.controls/com.sun.javafx.scene.control.behavior=com.jfoenix + --add-exports javafx.graphics/com.sun.javafx.stage=com.jfoenix + --add-exports com.oracle.truffle.regex/com.oracle.truffle.regex=org.graalvm.truffle + --patch-module org.jabref=build/resources/main + ``` + + * In the tab "Dependencies" of the run/debug configuration tick the checkbox "Exclude test code" +5. Optional: Install the [e(fx)clipse plugin](http://www.eclipse.org/efxclipse/index.html) from the Eclipse marketplace: 1. Help -> Eclipse Marketplace... -> Search tab 2. Enter "e(fx)clipse" in the search dialogue 3. Click "Go" 4. Click "Install" button next to the plugin 5. Click "Finish" +6. Now you can build and run/debug the application by either using `Launcher` or `JabRefMain`. This is the recommended way, since the application starts quite fast. + +## Localization Test Configuration (Eclipse) + +To run the `LocalizationConsistencyTest` you need to add some extra module information: Right-click on the file -> "Run/Debug as JUnit test". Go to the Run/debug configuration created for that file and in the arguments tab under VM-configurations add: + +```text +--add-exports javafx.graphics/com.sun.javafx.application=ALL-UNNAMED +--add-exports javafx.graphics/com.sun.javafx.stage=ALL-UNNAMED +--add-exports javafx.graphics/com.sun.javafx.stage=com.jfoenix +``` diff --git a/docs/getting-into-the-code/guidelines-choose-module.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-choose-module.png similarity index 100% rename from docs/getting-into-the-code/guidelines-choose-module.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-choose-module.png diff --git a/docs/getting-into-the-code/guidelines-choose-open-as-project.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-choose-open-as-project.png similarity index 100% rename from docs/getting-into-the-code/guidelines-choose-open-as-project.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-choose-open-as-project.png diff --git a/docs/getting-into-the-code/guidelines-gradle-run-output.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-gradle-run-output.png similarity index 100% rename from docs/getting-into-the-code/guidelines-gradle-run-output.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-gradle-run-output.png diff --git a/docs/getting-into-the-code/guidelines-gradle-run.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-gradle-run.png similarity index 100% rename from docs/getting-into-the-code/guidelines-gradle-run.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-gradle-run.png diff --git a/docs/getting-into-the-code/guidelines-gradle-tool-windows-refresh.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-gradle-tool-windows-refresh.png similarity index 100% rename from docs/getting-into-the-code/guidelines-gradle-tool-windows-refresh.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-gradle-tool-windows-refresh.png diff --git a/docs/getting-into-the-code/guidelines-intellij-checkstyle-confirmation.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-confirmation.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-checkstyle-confirmation.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-confirmation.png diff --git a/docs/getting-into-the-code/guidelines-intellij-checkstyle-final-settings.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-final-settings.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-checkstyle-final-settings.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-final-settings.png diff --git a/docs/getting-into-the-code/guidelines-intellij-checkstyle-import-file.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-import-file.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-checkstyle-import-file.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-import-file.png diff --git a/docs/getting-into-the-code/guidelines-intellij-checkstyle-jabref-active.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-jabref-active.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-checkstyle-jabref-active.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-jabref-active.png diff --git a/docs/getting-into-the-code/guidelines-intellij-checkstyle-restart-ide.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-restart-ide.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-checkstyle-restart-ide.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-restart-ide.png diff --git a/docs/getting-into-the-code/guidelines-intellij-checkstyle-start-import.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-start-import.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-checkstyle-start-import.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-start-import.png diff --git a/docs/getting-into-the-code/guidelines-intellij-checkstyle-window.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-window.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-checkstyle-window.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-window.png diff --git a/docs/getting-into-the-code/guidelines-intellij-choose-build-gradle.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-choose-build-gradle.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-choose-build-gradle.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-choose-build-gradle.png diff --git a/docs/getting-into-the-code/guidelines-intellij-codestyle-import-as-jabref.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-codestyle-import-as-jabref.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-codestyle-import-as-jabref.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-codestyle-import-as-jabref.png diff --git a/docs/getting-into-the-code/guidelines-intellij-codestyle-import-select-xml-file.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-codestyle-import-select-xml-file.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-codestyle-import-select-xml-file.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-codestyle-import-select-xml-file.png diff --git a/docs/getting-into-the-code/guidelines-intellij-codestyle-import.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-codestyle-import.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-codestyle-import.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-codestyle-import.png diff --git a/docs/getting-into-the-code/guidelines-intellij-editor-autoimport.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-editor-autoimport.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-editor-autoimport.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-editor-autoimport.png diff --git a/docs/getting-into-the-code/guidelines-intellij-editor-javadoc-do-not-wrap.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-editor-javadoc-do-not-wrap.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-editor-javadoc-do-not-wrap.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-editor-javadoc-do-not-wrap.png diff --git a/docs/getting-into-the-code/guidelines-intellij-enable-annotation-processing.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-enable-annotation-processing.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-enable-annotation-processing.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-enable-annotation-processing.png diff --git a/docs/getting-into-the-code/guidelines-intellij-install-checkstyle.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-install-checkstyle.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-install-checkstyle.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-install-checkstyle.png diff --git a/docs/getting-into-the-code/guidelines-intellij-installs-temurin.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-installs-temurin.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-installs-temurin.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-installs-temurin.png diff --git a/docs/getting-into-the-code/guidelines-intellij-locate-bibentrytest.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-locate-bibentrytest.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-locate-bibentrytest.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-locate-bibentrytest.png diff --git a/docs/getting-into-the-code/guidelines-intellij-project-settings-jdk18.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-project-settings-jdk18.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-project-settings-jdk18.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-project-settings-jdk18.png diff --git a/docs/getting-into-the-code/guidelines-intellij-run-bibentry-test.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-run-bibentry-test.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-run-bibentry-test.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-run-bibentry-test.png diff --git a/docs/getting-into-the-code/guidelines-intellij-run-single-test-launch-config.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-run-single-test-launch-config.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-run-single-test-launch-config.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-run-single-test-launch-config.png diff --git a/docs/getting-into-the-code/guidelines-intellij-run-single-test.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-run-single-test.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-run-single-test.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-run-single-test.png diff --git a/docs/getting-into-the-code/guidelines-intellij-settings-build-and-run-intellij.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-settings-build-and-run-intellij.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-settings-build-and-run-intellij.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-settings-build-and-run-intellij.png diff --git a/docs/getting-into-the-code/guidelines-intellij-settings-run-tests-using-intellij.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-settings-run-tests-using-intellij.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-settings-run-tests-using-intellij.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-settings-run-tests-using-intellij.png diff --git a/docs/getting-into-the-code/guidelines-intellij-start-window.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-start-window.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-start-window.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-start-window.png diff --git a/docs/getting-into-the-code/guidelines-intellij-tests-are-green.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-tests-are-green.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-tests-are-green.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-tests-are-green.png diff --git a/docs/getting-into-the-code/guidelines-overridden-compiler-parameters.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-overridden-compiler-parameters.png similarity index 100% rename from docs/getting-into-the-code/guidelines-overridden-compiler-parameters.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-overridden-compiler-parameters.png diff --git a/docs/getting-into-the-code/guidelines-select-download-jdk.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-select-download-jdk.png similarity index 100% rename from docs/getting-into-the-code/guidelines-select-download-jdk.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-select-download-jdk.png diff --git a/docs/getting-into-the-code/guidelines-select-jdk-18-eclipse-temurin.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-select-jdk-18-eclipse-temurin.png similarity index 100% rename from docs/getting-into-the-code/guidelines-select-jdk-18-eclipse-temurin.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-select-jdk-18-eclipse-temurin.png diff --git a/docs/getting-into-the-code/guidelines-settings-gradle-gradlejvm-is-projectjvm.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-settings-gradle-gradlejvm-is-projectjvm.png similarity index 100% rename from docs/getting-into-the-code/guidelines-settings-gradle-gradlejvm-is-projectjvm.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-settings-gradle-gradlejvm-is-projectjvm.png diff --git a/docs/getting-into-the-code/guidelines-settings-intellij-code-foldings.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-settings-intellij-code-foldings.png similarity index 100% rename from docs/getting-into-the-code/guidelines-settings-intellij-code-foldings.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-settings-intellij-code-foldings.png diff --git a/docs/getting-into-the-code/guidelines-trust-project.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-trust-project.png similarity index 100% rename from docs/getting-into-the-code/guidelines-trust-project.png rename to docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-trust-project.png diff --git a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/index.md b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/index.md new file mode 100644 index 00000000000..cfdec7620ca --- /dev/null +++ b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/index.md @@ -0,0 +1,14 @@ +--- +parent: Getting into the code +has_children: true +nav_order: 2 +--- +# Set up a local workspace + +{: .important } +These steps are very important. They allow you to focus on the content and ensure that the code formatting always goes well. + +This guide explains how to set up your environment for development of JabRef. It includes information about prerequisites, configuring your IDE, and running JabRef locally to verify your setup. Please follow the steps one-by-one. + +First, we work on prerequisites (software, account, code fork) you need to get started to develop JabRef. +Then, we work on a proper IDE setup. diff --git a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-11-code-into-ide.md b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-11-code-into-ide.md new file mode 100644 index 00000000000..9d8edbed39e --- /dev/null +++ b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-11-code-into-ide.md @@ -0,0 +1,36 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 11 +--- + +# Step 1: Get the code into IntelliJ + +Start IntelliJ. + +IntelliJ shows the following window: + +{% figure caption:"IntelliJ Start Window" %} +![IntelliJ Start Window](guidelines-intellij-start-window.png) +{% endfigure %} + +Click on "Open" + +Choose `build.gradle` in the root of the jabref source folder: + +{% figure caption:"Choose `build.gradle` in the “Open Project or File” dialog" %} +![Open File or Project dialog](guidelines-intellij-choose-build-gradle.png) +{% endfigure %} + +After pressing "OK", IntelliJ asks how that file should be opened. +Answer: "Open as Project" + +{% figure caption:"Choose “Open as Project” in the Open Project dialog" %} +![Open Project dialog](guidelines-choose-open-as-project.png) +{% endfigure %} + +Then, trust the project: + +{% figure caption:"Choose “Trust Project” in the “Trust and Open Project” dialog" %} +![Trust and Open Project dialog](guidelines-trust-project.png) +{% endfigure %} diff --git a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-12-build.md b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-12-build.md new file mode 100644 index 00000000000..e1feb9355a7 --- /dev/null +++ b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-12-build.md @@ -0,0 +1,182 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 12 +--- + +# Step 2: Setup the build system: JDK and Gradle + +## Ensure that JDK 20 is available to IntelliJ + +Ensure you have a Java 20 SDK configured by navigating to **File > Project Structure... > Platform Settings > SDKs**. + +{% figure caption:"JDKs 11, 14, and 15 shown in available SDKs. JDK 20 is missing." %} +![Plattform Settings - SDKs](../../images/intellij-choose-jdk-adoptopenjdk-on-windows-project-settings.png) +{% endfigure %} + +If there is another JDK than JDK 20 selected, click on the plus button and choose "Download JDK..." + +{% figure caption:"Download JDK..." %} +![Plattform Settings - SDKs - plus button - Download JDK...](guidelines-select-download-jdk.png) +{% endfigure %} + +Select JDK version 20 and then Eclipse Temurin (showing JDK 18 as example). + +{% figure caption:"Example for JDK 18 - Choose Eclipse Temurin" %} +![Download Eclipse Temurin](guidelines-select-jdk-18-eclipse-temurin.png) +{% endfigure %} + +After clicking "Download", IntelliJ installs Eclipse Temurin: + +{% figure caption:"IntelliJ installs Eclipse Temurin" %} +![IntelliJ installs Eclipse Temurin](guidelines-intellij-installs-temurin.png) +{% endfigure %} + +Navigate to **Project Settings > Project** and ensure that the projects' SDK is Java 20 + +{% figure caption:"Project SDK is pinned to the downloaded SDK (showing JDK 18 as example)" %} +![Project SDK is JDK 18](guidelines-intellij-project-settings-jdk18.png) +{% endfigure %} + +Click "OK" to store the changes. + +## Configure the Build System + +Navigate to **File > Settings... > Build, Execution, Deployment > Build Tools > Gradle** and select the "Project SDK" as the Gradle JVM at the bottom. If that does not exist, just select a JDK 20. + +{% figure caption:"Gradle JVM is project SDK (showing JDK 18 as example)" %} +![Gradle JVM is project SDK](guidelines-settings-gradle-gradlejvm-is-projectjvm.png) +{% endfigure %} + +To prepare IntelliJ's build system additional steps are required: + +Navigate to **Build, Execution, Deployment > Compiler > Java Compiler**, and under "Override compiler parameters per-module", click add (\[+]) and choose `JabRef.main`: + +{% figure caption:"Choose JabRef.main" %} +![Gradle JVM is project SDK](guidelines-choose-module.png) +{% endfigure %} + +Then double click inside the cell "Compilation options" and enter following parameters: + +```text +--add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref +--add-exports=org.controlsfx.controls/impl.org.controlsfx.skin=org.jabref +``` + +Press Enter to have the value really stored. +Otherwise, it seems like the setting is stored, but it is not there if you re-open this preference dialog. +Then click on "Apply" to store the setting. + +{% figure caption:"Resulting settings for module JabRef.main" %} +![Overridden compiler parameters](guidelines-overridden-compiler-parameters.png) +{% endfigure %} + +If this step is omited, you will get: `java: package com.sun.javafx.scene.control is not visible (package com.sun.javafx.scene.control is declared in module javafx.controls, which does not export it to module org.jabref)`. + +Enable annotation processors by navigating to **Build, Execution, Deployment > Compiler > Annotation processors** and check "Enable annotation processing" + +{% figure caption:"Enabled annotation processing" %} +![Enable annotation processing](guidelines-intellij-enable-annotation-processing.png) +{% endfigure %} + +## Using Gradle from within IntelliJ IDEA + +{: .note } +Ensuring JabRef builds with Gradle should always the first step because, e.g. it generates additional sources that are required for compiling the code. + +Open the Gradle Tool Window with the small button that can usually be found on the right side of IDEA or navigate to **View > Tool Windows > Gradle**. +In the Gradle Tool Window, press the "Reload All Gradle Projects" button to ensure that all settings are up-to-date with the setting changes. + +{% figure caption:"Reload of Gradle project" %} +![Highlighted reload button](guidelines-gradle-tool-windows-refresh.png) +{% endfigure %} + +After that, you can use the Gradle Tool Window to build all parts JabRef and run it. +To do so, expand the JabRef project in the Gradle Tool Window and navigate to Tasks. +From there, you can build and run JabRef by double-clicking **JabRef > Tasks > application > run**. + +{% figure caption:"JabRef > Tasks > application > run" %} +![JabRef > Tasks > application > run](guidelines-gradle-run.png) +{% endfigure %} + +The Gradle run window opens, shows compilation and then the output of JabRef. +The spinner will run as long as JabRef is opened. + +{% figure caption:"Gradle run Window" %} +![Gradle run window](guidelines-gradle-run-output.png) +{% endfigure %} + +You can close JabRef again. + +After that a new entry called "jabref \[run]" appears in the run configurations. +Now you can also select "jabref \[run]" and either run or debug the application from within IntelliJ. + +{: .note } +You can run any other development task in a similar way. + +## Using IntelliJ's internal build system for tests + +In **File > Settings... > Build, Execution, Deployment > Build Tools > Gradle** the setting "Run tests using:" is set to "IntelliJ IDEA". + +{% figure caption:"IntelliJ setting: Run tests using IntelliJ" %} +![IntelliJ setting: Run tests using IntelliJ"](guidelines-intellij-settings-run-tests-using-intellij.png) +{% endfigure %} + +{: .note } +In case there are difficulties later, this is the place to switch back to gradle. + +Click "OK" to close the preference dialog. + +In the menubar, select **Build > Rebuild project**. + +IntelliJ now compiles JabRef. +This should happen without any error. + +Now you can use IntelliJ IDEA's internal build system by using **Build > Build Project**. + +## Final build system checks (optional) + +To run an example test from IntelliJ, we let IntelliJ create a launch configuration: + +Locate the class `BibEntryTest`: +Press Ctrl+N. +Then, the "Search for classes dialog" pops up. +Enter `bibenrytest`. +Now, `BibEntryTest` should appear first: + +{% figure caption:"IntelliJ search for class “BibEntryTest”" %} +![IntelliJ search for class "BibEntryTest"](guidelines-intellij-locate-bibentrytest.png) +{% endfigure %} + +Press Enter to jump to that class. + +Hover on the green play button on `testDefaultConstructor`: + +{% figure caption:"However on green play button" %} +![However on green play button](guidelines-intellij-run-single-test.png) +{% endfigure %} + +Then, click on it. +A popup menu opens. +Choose the first entry "Run testDefaultConstructor" and click on it. + +{% figure caption:"Run testDefaultConstructor" %} +![Popup menu - Run testDefaultConstructor](guidelines-intellij-run-single-test-launch-config.png) +{% endfigure %} + +Then, the single test starts. + +You also have an entry in the Launch configurations to directly launch the test. +You can also click on the debug symbol next to it to enable stopping at breakpoints. + +{% figure caption:"Launch menu contains BibEntry test case" %} +![Launch menu contains BibEntry test case](guidelines-intellij-run-bibentry-test.png) +{% endfigure %} + +The tests are green after the run. +You can also use the play button there to re-execute the tests. +A right-click on "BibEntryTests" enables to start the debugger. + +{% figure caption:"Run window for the BibEntry test case" %} +![Run window for the BibEntry test case](guidelines-intellij-tests-are-green.png) +{% endfigure %} diff --git a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-13-code-style.md b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-13-code-style.md new file mode 100644 index 00000000000..510edaba7c0 --- /dev/null +++ b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-13-code-style.md @@ -0,0 +1,158 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 13 +--- + +# Step 3: Using JabRef's code style + +Contributions to JabRef's source code need to have a code formatting that is consistent with existing source code. For that purpose, JabRef provides code-style and check-style definitions. + +Install the [CheckStyle-IDEA plugin](http://plugins.jetbrains.com/plugin/1065?pr=idea), it can be found via the plug-in repository: +Navigate to **File > Settings... > Plugins"**. +On the top, click on "Marketplace". +Then, search for "Checkstyle". +Click on "Install" choose "CheckStyle-IDEA". + +{% figure caption:"Install CheckStyle" %} +![Install CheckStyle](guidelines-intellij-install-checkstyle.png) +{% endfigure %} + +After clicking, IntelliJ asks for confirmation: + +{% figure caption:"Third Party Plugin Privacy Notice" %} +![Third Party Plugin Privacy Notice](guidelines-intellij-checkstyle-confirmation.png) +{% endfigure %} + +If you agree, click on "Agree" and you can continue. + +Afterwards, use the "Restart IDE" button to restart IntelliJ. + +{% figure caption:"IntelliJ restart IDE" %} +![IntelliJ restart IDE](guidelines-intellij-checkstyle-restart-ide.png) +{% endfigure %} + +Click on "Restart" to finally restart. + +Wait for IntelliJ coming up again. + +Go to **File > Settings... > Editor > Code Style** + +Click on the settings wheel (next to the scheme chooser), +then click "Import Scheme >", +then click "IntelliJ IDEA code style XML" + +{% figure caption:"Location of “Import Scheme > IntelliJ IDEA code style XML”" %} +![Location of IntelliJ IDEA code style XML](guidelines-intellij-codestyle-import.png) +{% endfigure %} + +You have to browse for the directory `config` in JabRef's code. +There is an `IntelliJ Code Style.xml`. + +{% figure caption:"Browsing for `config/IntelliJ Code Style.xml`" %} +![Browsing for config/IntelliJ Code Style.xml](guidelines-intellij-codestyle-import-select-xml-file.png) +{% endfigure %} + +Click "OK". + +At following dialog is "Import Scheme". +Click there "OK", too. + +{% figure caption:"Import to JabRef" %} +![Import to JabRef](guidelines-intellij-codestyle-import-as-jabref.png) +{% endfigure %} + +Click on "Apply" to store the preferences. + +## Put JabRef's checkstyle configuration in place + +Now, put the checkstyle configuration file is in place: + +Go to **File > Settings... > Tools > Checkstyle > Configuration File** + +Trigger the import dialog of a CheckStyle style by clicking the \[+] button: + +{% figure caption:"Trigger the rule import dialog" %} +![Trigger the rule import dialog](guidelines-intellij-checkstyle-start-import.png) +{% endfigure %} + +Then: + +* Put "JabRef" as description. +* Browse for `config/checkstyle/checkstyle.xml` +* Tick "Store relative to project location" +* Click "Next" + +{% figure caption:"Filled Rule Import Dialog" %} +![Filled Rule Import Dialog](guidelines-intellij-checkstyle-import-file.png) +{% endfigure %} + +Click on "Finish" + +Activate the CheckStyle configuration file by ticking it in the list + +{% figure caption:"JabRef's checkstyle config is activated" %} +![JabRef's checkstyle config is activated](guidelines-intellij-checkstyle-jabref-active.png) +{% endfigure %} + +Ensure that the [latest CheckStyle version](https://checkstyle.org/releasenotes.html) is selected (10.3.4 or higher). +Also, set the "Scan Scope" to "Only Java sources (including tests)". + +{% figure caption:"Checkstyle is the highest version - and tests are also scanned" %} +![Checkstyle is the highest version - and tests are also scanned](guidelines-intellij-checkstyle-final-settings.png) +{% endfigure %} + +Save settings by clicking "Apply" and then "OK" + +In the lower part of IntelliJ's window, click on "Checkstyle". +In "Rules", change to "JabRef". +Then, you can run a check on all modified files. + +{% figure caption:"JabRef's style is active - and we are ready to run a check on all modified files" %} +![JabRef's style is active - and we are ready to run a check on all modified files](guidelines-intellij-checkstyle-window.png) +{% endfigure %} + +## Have auto format working properly in JavaDoc + +To have auto format working properly in the context of JavaDoc and line wrapping, "Wrap at right margin" has to be disabled. Details are found in [IntelliJ issue 240517](https://youtrack.jetbrains.com/issue/IDEA-240517). + +Go to **File > Settings... > Editor > Code Style > Java > JavaDoc**. + +At "Other", disable "Wrap at right margin" + +{% figure caption:"”Wrap at right margin” disabled" %} +!["Wrap at right margin" disabled](guidelines-intellij-editor-javadoc-do-not-wrap.png) +{% endfigure %} + +## Enable proper import cleanup + +To enable "magic" creation and auto cleanup of imports, go to **File > Settings... > Editor > General > Auto Import**. +There, enable both "Add unambiguous imports on the fly" and "Optimize imports on the fly" +(Source: [JetBrains help](https://www.jetbrains.com/help/idea/creating-and-optimizing-imports.html#automatically-add-import-statements)). + +{% figure caption:"Auto import enabled" %} +![Enable auto import](guidelines-intellij-editor-autoimport.png) +{% endfigure %} + +Press "OK". + +## Disable too advanced code folding + +Go to **File > Settings... > Editor > General > Code Folding**. +At "Java", disable "General > File header", "General > Imports", and "Java > One-line methods". + +{% figure caption:"Code foldings disabled" %} +![Code foldings disabled](guidelines-settings-intellij-code-foldings.png) +{% endfigure %} + +Press "OK". + +## Final comments + +{: .highlight } +> Now you have configured IntelliJ completely. +> You can run the main application using Gradle and the test cases using IntelliJ. +> The code formatting rules are imported - and the most common styling issue at imports is automatically resolved by IntelliJ. +> Finally, you have Checkstyle running locally so that you can check for styling errors before submitting the pull request. + +Got it running? GREAT! You are ready to lurk the code and contribute to JabRef. Please make sure to also read our [contribution guide](https://github.com/JabRef/jabref/blob/main/CONTRIBUTING.md). diff --git a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-01-github-account.md b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-01-github-account.md new file mode 100644 index 00000000000..3b9c939b027 --- /dev/null +++ b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-01-github-account.md @@ -0,0 +1,25 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 1 +--- + +# Pre Condition 1: GitHub Account + +If you do not yet have a GitHub account, please [create one](https://github.com/join). + +Proposals for account names: + +* Login similar to your university account. Example: `koppor` +* Use your last name prefixed by the first letter of your first name. Example: `okopp` +* Use `firstname.lastname`. Example: `oliver.kopp` + +You can hide your email address by following the recommendations at [https://saraford.net/2017/02/19/how-to-hide-your-email-address-in-your-git-commits-but-still-get-contributions-to-show-up-on-your-github-profile-050/](https://saraford.net/2017/02/19/how-to-hide-your-email-address-in-your-git-commits-but-still-get-contributions-to-show-up-on-your-github-profile-050/). + +Most developers, though, do not hide their email address. They use one which may get public. Mostly, they create a new email account for development only. That account then be used for development mailing lists, mail exchange with other developers, etc. + +Examples: + +* Same login as in GitHub (see above). Example: `koppor@gmail.com` +* "`it`" in the name. Example: `kopp.it@gmail.com` +* Use the university login. Example: `st342435@stud.uni-stuttgart.de` diff --git a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-02-software.md b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-02-software.md new file mode 100644 index 00000000000..fb3cd441352 --- /dev/null +++ b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-02-software.md @@ -0,0 +1,31 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 2 +--- + +# Pre Condition 2: Required Software + +## git + +It is strongly recommended that you have git installed. + +* On Debian-based distros: `sudo apt-get install git` +* On Windows: [Download the installer](http://git-scm.com/download/win) and install it. Using [chocolatey](https://chocolatey.org/), you can run `choco install git.install -y --params "/GitAndUnixToolsOnPath /WindowsTerminal` to a) install git and b) have Linux commands such as `grep` available in your `PATH`. +* [Official installation instructions](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) + +## Installed IDE + +We suggest [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=jabref). +The Community Edition works well. +Most contributors use the Ultimate Edition, because they are students getting that edition for free. + +{: .highlight } +Currently, the 2023.1 version does **not** work for initial setup. +Please head to and get 2022.3.3. +You can update the IDE after a proper setup to the newest version. + +## Other Tooling + +We collected some other tooling recommendations. +We invite you to read on at our [tool recommendations](../../code-howtos/tools.md). diff --git a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-03-code.md b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-03-code.md new file mode 100644 index 00000000000..eaab5a062de --- /dev/null +++ b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-03-code.md @@ -0,0 +1,61 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 3 +--- + +# Pre Condition 3: Code on the local machine + +This section explains how you get the JabRef code onto your machine in a form allowing you to make contributions. + +## Fork JabRef into your GitHub account + +1. Log into your GitHub account +2. Go to [https://github.com/JabRef/jabref](https://github.com/JabRef/jabref) +3. Create a fork by clicking at fork button on the right top corner +4. A fork repository will be created under your account `https://github.com/YOUR_USERNAME/jabref`. + +## Clone your forked repository on your local machine + +In a command line, navigate to the folder where you want to place the source code (parent folder of `jabref`). +To prevent issues along the way, it is strongly recommend choosing a path that does not contain any special (non-ASCII or whitespace) characters. +In the following, we will use `c:\git-repositories` as base folder: + +```cmd +cd \ +mkdir git-repositories +cd git-repositories +git clone --depth=10 https://github.com/JabRef/jabref.git +cd jabref +git remote rename origin upstream +git remote add origin https://github.com/YOUR_USERNAME/jabref.git +git branch --set-upstream-to=origin/main main +``` + +{: .important } +> Note that putting the repo JabRef directly on `C:\` or any other drive letter on Windows causes compile errors (**negative example**: `C:\jabref`). +> +> Further, if you are building on Windows, make sure that the absolute path to the location of the clone does not contain folders starting with '`u`' (**negative example**: `C:\university\jabref`) as this may currently also cause [compile errors](https://github.com/JabRef/jabref/issues/9783). + +{: .note-title } +> Background +> +> Initial cloning might be very slow (`27.00 KiB/s`). +> +> To prevent this, first the `upstream` repository is cloned. +> This repository seems to live in the caches of GitHub. +> +> The `--depth--10` is used to limit the download to \~20 MB instead of downloading the complete history (\~800 MB). +> If you want to dig in our commit history, feel free to download everything. +> +> Now, you have two remote repositories, where `origin` is yours and `upstream` is the one of the JabRef organization. +> +> You can see it with `git remote -v`: +> +> ```cmd +> c:\git-repositories\jabref> git remote -v +> origin https://github.com/YOURUSERNAME/jabref.git (fetch) +> origin https://github.com/YOURUSERNAME/jabref.git (push) +> upstream https://github.com/jabref/jabref.git (fetch) +> upstream https://github.com/jabref/jabref.git (push) +> ``` diff --git a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/trouble-shooting.md b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/trouble-shooting.md new file mode 100644 index 00000000000..54316245203 --- /dev/null +++ b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/trouble-shooting.md @@ -0,0 +1,105 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 99 +--- + +# Trouble shooting + +## Issues with `buildSrc` + +1. Open the context menu of `buildSrc`. +2. Select "Load/Unload modules". +3. Unload `jabRef.buildSrc`. + +## Issues with generated source files + +In rare cases you might encounter problems due to out-dated automatically generated source files. Running gradle task "clean" (Command line: `./gradlew clean`) deletes these old copies. Do not forget to run at least `./gradlew assemble` or `./gradlew eclipse` afterwards to regenerate the source files. + +## Issue with "Module org.jsoup" not found, required by org.jabref + +Following error message appears: + +```text +Error occurred during initialization of boot layer +java.lang.module.FindException: Module org.jsoup not found, required by org.jabref +``` + +This can include different modules. + +1. Go to File -> Invalidate caches... +2. Check "Clear file system cache and Local History". +3. Check "Clear VCS Log caches and indexes". +4. Uncheck the others. +5. Click on "Invalidate and Restart". +6. After IntelliJ restarted, you have to do the "buildSrc", "Log4JAppender", and "src-gen" steps again. + +## Issues with OpenJFX libraries in local maven repository + +There might be problems with building if you have OpenJFX libraries in local maven repository, resulting in errors like this: + +```text + > Could not find javafx-fxml-20-mac.jar (org.openjfx:javafx-fxml:20). + Searched in the following locations: + file:/repository/org/openjfx/javafx-fxml/20/javafx-fxml-20-mac.jar +``` + +As a workaround, you can remove all local OpenJFX artifacts by deleting the whole OpenJFX folder from specified location. + +## Issues with `JournalAbbreviationLoader` + +In case of a NPE at `Files.copy` at `org.jabref.logic.journals.JournalAbbreviationLoader.loadRepository(JournalAbbreviationLoader.java:30) ~[classes/:?]`, invalidate caches and restart IntelliJ. Then, Build -> Rebuild Project. + +If that does not help: + +1. Save/Commit all your work +2. Close IntelliJ +3. Delete all non-versioned items: `git clean -xdf`. This really destroys data +4. Execute `./gradlew run` +5. Start IntelliJ and try again. + +## Hints for IntelliJ 2021 + +In older IntelliJ setups, more things were necessary: + +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"](../../images/intellij-gradle-config-ignore-buildSrc.png) + +Add `src-gen` as root: + +1. Right click on the project "jabref". +2. Select "Open Module Settings" +3. Expand "JabRef" +4. Select "main" +5. Select tab "Sources" +6. Click "+ Add Content Root" +7. Select the `src-gen` directory +8. Click "OK". When expanding "main", "java" should have been selected as source +9. Click "OK" to save the changes + +In case the above step does not work, run with gradle, import gradle project again, and try again. + +~~Note that the above steps might not work on IntelliJ 2020.x.\*\*. You have to keep using gradle for executing tasks. See~~ [~~IDEA-249391~~](https://youtrack.jetbrains.com/issue/IDEA-249391) ~~for details.~~ + +In case all steps are followed, and there are still issues with `SearchBaseVisitor` (e.g., `Error:(16, 25) java: package org.jabref.search does not exist`), you have to delete `src\main\generated\org\jabref\gui\logging\plugins\Log4jPlugins.java`. This is independent of having enabled or disabled Annotation Processing (see above at "Enable Annotation Processing"). + +## Java installation + +An indication that `JAVA_HOME` is not correctly set or no JDK 20 is installed in the IDE is following error message: + +```text +compileJava FAILED + +FAILURE: Build failed with an exception. + +* What went wrong: +Execution failed for task ':compileJava'. +> java.lang.ExceptionInInitializerError (no error message) +``` + +Another indication is following output + +```text +java.lang.UnsupportedClassVersionError: org/javamodularity/moduleplugin/ModuleSystemPlugin has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0 +``` diff --git a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/vscode.md b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/vscode.md new file mode 100644 index 00000000000..f2dfde15afc --- /dev/null +++ b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/vscode.md @@ -0,0 +1,56 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 91 +--- + +# Advanced: VS Code as IDE + +We are working on supporting VS Code for development. +There is basic support, but important things such as our code conventions are not in place. +Thus, use at your own risk. + +Quick howto: + +1. Start VS Code in the JabRef directory: `code .`. +2. There will be a poup asking "Reopen in Container". Click on that link. +3. VS Code restarts. Wait about 3 minutes until the dev container is build. You can click on "Starting Dev Container (show log)" to see the progress. +4. Afterwards, the Java project is imported. You can open the log (Click on "Check details"). Do that. +5. The terminal (tab "Java Build Status") will show some project synchronization and hang at `80% [797/1000]`. + It keeps hanging at `Importing root project: 80% Refreshing '/jabref'`. + Just wait. + Then it hangs at `Synchronizing Gradle build at /workspaces/jabref: 80%`. + Just wait. + Then it takes long for `Refreshing workspace:`. + Just wait. + **Note:** If you had the project opened in IntelliJ before, this might cause issues (as outlined at ). + Close everything, ensure that you committed your changes (if any), then execute `git clean -xdf` to wipe out all changes and created files - and start from step 1 again. +6. On the left, you will see a gradle button. +7. Click on the gradle button and open **JabRef -> Tasks -> application**. +8. Double click on **run**. +9. In the terminal, a new tab "run" opens. +10. On your desktop machine, open in a web browser. + Do not open the proposed port `6050`. + This is JabRef's remote command port. +11. Use `vscode` as password. +12. You will see an opened JabRef. + +Alternative to steps 9 to 10: + +In case interaction using the web browser is too slow, you can use a VNC connection: + +1. Install [VNC Connect](https://www.realvnc.com/en/connect/) +2. Use `vscode` as password + +## Trouble shooting + +In case there are reading errors on the file system, the docker container probably is out of order. +Close VS Code. +Stop the docker container, kill docker process in the Task Manager (if necessary). +Start docker again. +Start VS Code again. + +## Background + +We use VS Code's [Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) feature. +Thereby, we use [desktop-lite](https://github.com/devcontainers/features/tree/main/src/desktop-lite#options) to enable viewing the JabRef app. diff --git a/docs/getting-into-the-code/guidelines-intellij-hover-on-play-button.png b/docs/images/intellij-hover-on-play-button.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-hover-on-play-button.png rename to docs/images/intellij-hover-on-play-button.png diff --git a/docs/getting-into-the-code/guidelines-intellij-run-jabref-from-launcher.png b/docs/images/intellij-run-jabref-from-launcher.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-run-jabref-from-launcher.png rename to docs/images/intellij-run-jabref-from-launcher.png diff --git a/docs/getting-into-the-code/intellij-run-launcher.png b/docs/images/intellij-run-launcher.png similarity index 100% rename from docs/getting-into-the-code/intellij-run-launcher.png rename to docs/images/intellij-run-launcher.png diff --git a/docs/getting-into-the-code/guidelines-intellij-search-for-launcher.png b/docs/images/intellij-search-for-launcher.png similarity index 100% rename from docs/getting-into-the-code/guidelines-intellij-search-for-launcher.png rename to docs/images/intellij-search-for-launcher.png diff --git a/docs/teaching.md b/docs/teaching.md index d2f46159b49..8840772dc1b 100644 --- a/docs/teaching.md +++ b/docs/teaching.md @@ -106,7 +106,7 @@ Course [10915-01: Software Engineering](https://dmi.unibas.ch/de/studium/compute #### University of Stuttgart, Germany -Course "Softwarepraktikum" as part of the [BSc Informatik](https://www.f05.uni-stuttgart.de/informatik/interessierte/bachelor/informatik/) +Course "Softwarepraktikum" as part of the [BSc Informatik](https://www.uni-stuttgart.de/studium/bachelor/informatik-b.sc./) * Summary: A group of three students experienced the full software engineering process within one semester. They worked part-time for the project. * Successfully run in 2012 diff --git a/eclipse.gradle b/eclipse.gradle index dab4df54bb6..9d61901d09e 100644 --- a/eclipse.gradle +++ b/eclipse.gradle @@ -25,12 +25,11 @@ 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:javafx.controls/com.sun.javafx.scene.control.behavior=com.jfoenix'; + 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: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'; - javafxgraphics.entryAttributes['add-exports'] = 'javafx.graphics/com.sun.javafx.stage=com.jfoenix'; def javafxbase = entries.find { isJavafxBase(it) }; javafxbase.entryAttributes['add-exports'] = 'javafx.base/com.sun.javafx.event=org.controlsfx.controls:'; diff --git a/external-libraries.md b/external-libraries.md index fce62adf6fd..e71e0f24bf1 100644 --- a/external-libraries.md +++ b/external-libraries.md @@ -42,6 +42,34 @@ Note: It is important to include v1.5.54 or later as v1.5.54 is the first ver (Sorted alphabetically by Id) +```yaml +Id: at.favre.lib +Project: HMAC-based Key Derivation Function (HKDF) RFC 5869 +URL: https://github.com/patrickfav/hkdf +License: Apache-2.0 +``` + +```yaml +Id: com.dlsc.gemsfx:gemsfx +Project: GemsFX +URL: https://github.com/dlsc-software-consulting-gmbh/GemsFX +License: Apache-2.0 +``` + +```yaml +Id: com.dlsc.pickerfx:pickerfx +Project: GemsFX +URL: https://github.com/dlsc-software-consulting-gmbh/PickerFX +License: Apache-2.0 +``` + +```yaml +Id: com.dlsc.unitfx:unitfx +Project: UnitFX +URL: https://github.com/dlsc-software-consulting-gmbh/UnitFX +License: Apache-2.0 +``` + ```yaml Id: com.fasterxml.jackson Project: Jackson Project @@ -49,6 +77,27 @@ URL: https://github.com/FasterXML/jackson License: Apache-2.0 ``` +```yaml +Id: com.github.hypfvieh.dbus-java +Project: dbus-java +URL: https://github.com/hypfvieh/dbus-java +License: MIT +``` + +```yaml +Id: com.github.hypfvieh.java-utils +Project: java-utils +URL: https://github.com/hypfvieh/java-utils +License: MIT +``` + +```yaml +Id: com.github.javakeyring +Project: Java Keyring +URL: https://github.com/javakeyring/java-keyring +License: BSD-3-Clause +``` + ```yaml Id: com.github.JabRef Project: afterburner.fx @@ -99,13 +148,6 @@ URL: https://github.com/lemire/javaewah License: Apache-2.0 ``` -```yaml -Id: com.jfoenix:jfoenix -Project: JavaFX MAterial Design Library -URL: https://github.com/jfoenixadmin/JFoenix -License: Apache-2.0 -``` - ```yaml Id: com.konghq.unirest Project: Unirest for Java @@ -134,6 +176,27 @@ URL: https://repo1.maven.org/maven2/com/oracle/ojdbc/ojdbc10/19.3.0.0/ojdbc1 License: Oracle Free Use Terms and Conditions (FUTC) ``` +```yaml +Id: com.squareup.okhttp3:okhttp +Project: OkHttp +URL: https://square.github.io/okhttp/ +License: Apache-2.0 +``` + +```yaml +Id: com.squareup.okio:okio +Project: OkHttp +URL: https://github.com/square/okio/ +License: Apache-2.0 +``` + +```yaml +Id: com.squareup.retrofit2:retrofit +Project: Retrofit 2 +URL: https://github.com/square/retrofit +License: Apache-2.0 +``` + ```yaml Id: com.sun.istack:istack-commons-runtime Project: iStack Common Utility Code @@ -155,6 +218,13 @@ URL: https://github.com/vsch/flexmark-java License: BSD-2-Clause ``` +```yaml +Id: commons-beanutils:commons-beanutils +Project: Apache Commons Beanutils +URL: https://commons.apache.org/proper/commons-beanutils/ +License: Apache-2.0 +``` + ```yaml Id: commons-cli:commons-cli Project: Apache Commons CLI @@ -169,6 +239,13 @@ URL: https://commons.apache.org/proper/commons-codec/ License: Apache-2.0 ``` +```yaml +Id: commons-collections:commons-collections +Project: Apache Commons Collections +URL: https://commons.apache.org/proper/commons-collections/ +License: Apache-2.0 +``` + ```yaml Id: commons-io:commons-io Project: Apache Commons IO @@ -183,6 +260,20 @@ URL: http://commons.apache.org/logging/ License: Apache-2.0 ``` +```yaml +Id: commons-digester:commons-digester +Project: Apache Commons Digester +URL: https://commons.apache.org/proper/commons-digester/ +License: Apache-2.0 +``` + +```yaml +Id: commons-validator:commons-validator +Project: Apache Commons Validator +URL: https://commons.apache.org/proper/commons-validator/ +License: Apache-2.0 +``` + ```yaml Id: de.saxsys:mvvmfx Project: mvvm(fx) @@ -190,6 +281,13 @@ URL: https://github.com/sialcasa/mvvmFX License: Apache-2.0 ``` +```yaml +Id: de.swiesend:secret-service +Project: Secret Service +URL: https://github.com/swiesend/secret-service +License: MIT +``` + ```yaml Id: de.saxsys:mvvmfx-validation Project: mvvm(fx) @@ -330,13 +428,6 @@ URL: http://pdfbox.apache.org License: Apache-2.0 ``` -```yaml -Id: org.apache.tika:tika-core -Project: Apache Tika -URL: https://tika.apache.org/ -License: Apache-2.0 -``` - ```yaml Id: org.bouncycastle:bcprov-jdk15on Project: The Legion of the Bouncy Castle @@ -403,6 +494,13 @@ Path: lib/icu4j.jar SourcePath: lib/ic4j-src.jar ``` +```yaml +Id: org.jooq:jool +Project: JOOλ +URL: https://github.com/jOOQ/jOOL +License: Apache-2.0 +``` + ```yaml Id: org.jsoup:jsoup Project: jsoup @@ -511,10 +609,31 @@ License: Apache-2.0 ```yaml Id: org.yaml:snakeyaml Project: SnakeYAML -URL: https://bitbucket.org/asomov/snakeyaml-engine/src/master/ +URL: https://bitbucket.org/snakeyaml/snakeyaml-engine/src/master/ License: Apache-2.0 ``` +```yaml +Id: pt.davidafsilva.apple:jkeychain +Project: JKeyChain +URL: https://github.com/davidafsilva/jkeychain +License: BSD-2-Clause +``` + +```yaml +Id: tech.units:indriya +Project: Indriya - JSR 385 - Reference Implementation +URL: https://github.com/unitsofmeasurement/indriya +License: BSD-3-Clause +``` + +```yaml +Id: tech.uom.lib:uom-lib-common +Project: Units of Measurement Libraries - extending and complementing JSR 385 +URL: https://github.com/unitsofmeasurement/uom-lib +License: BSD-3-Clause +``` + ## Sorted list of runtime dependencies output by gradle 1. `gradlew dependencies > build/dependencies.txt` @@ -522,122 +641,138 @@ License: Apache-2.0 3. (on WSL) `sed 's/[^a-z]*//' < build/dependencies.txt | sed "s/\(.*\) .*/\1/" | grep -v "\->" | sort | uniq > build/dependencies-for-external-libraries.txt` ```text -com.fasterxml.jackson.core:jackson-annotations:2.14.1 -com.fasterxml.jackson.core:jackson-core:2.14.1 -com.fasterxml.jackson.core:jackson-databind:2.14.1 -com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.14.1 -com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.1 -com.fasterxml.jackson:jackson-bom:2.14.1 -com.github.JabRef:afterburner.fx:testmoduleinfo-SNAPSHOT +at.favre.lib:hkdf:1.1.0 +com.dlsc.gemsfx:gemsfx:1.77.0 +com.dlsc.pickerfx:pickerfx:1.2.0 +com.dlsc.unitfx:unitfx:1.0.10 +com.fasterxml.jackson.core:jackson-annotations:2.15.2 +com.fasterxml.jackson.core:jackson-core:2.15.2 +com.fasterxml.jackson.core:jackson-databind:2.15.2 +com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.15.2 +com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2 +com.fasterxml.jackson:jackson-bom:2.15.2 +com.github.hypfvieh:dbus-java-core:4.2.1 +com.github.hypfvieh:dbus-java-transport-native-unixsocket:4.2.1 +com.github.javakeyring:java-keyring:1.0.4 com.github.sialcasa.mvvmFX:mvvmfx-validation:f195849ca9 com.github.tomtung:latex2unicode_2.13:0.3.2 -com.google.code.gson:gson:2.9.0 -com.google.errorprone:error_prone_annotations:2.11.0 +com.google.code.gson:gson:2.10 +com.googlecode.javaewah:JavaEWAH:1.2.3 +com.google.errorprone:error_prone_annotations:2.18.0 com.google.guava:failureaccess:1.0.1 -com.google.guava:guava:31.1-jre +com.google.guava:guava:32.1.2-jre +com.google.guava:guava-parent:32.1.2-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.13 -com.h2database:h2-mvstore:2.1.214 -com.jfoenix:jfoenix:9.0.10 -com.konghq:unirest-java:3.14.1 +com.google.j2objc:j2objc-annotations:2.8 +com.h2database:h2-mvstore:2.2.220 +com.konghq:unirest-java:3.14.5 com.microsoft.azure:applicationinsights-core:2.4.1 com.microsoft.azure:applicationinsights-logging-log4j2:2.4.1 +commons-beanutils:commons-beanutils:1.9.4 +commons-cli:commons-cli:1.5.0 +commons-codec:commons-codec:1.15 +commons-collections:commons-collections:3.2.2 +commons-digester:commons-digester:2.1 +commons-logging:commons-logging:1.2 +commons-validator:commons-validator:1.7 com.oracle.ojdbc:ojdbc10:19.3.0.0 com.oracle.ojdbc:ons:19.3.0.0 com.oracle.ojdbc:osdt_cert:19.3.0.0 com.oracle.ojdbc:osdt_core:19.3.0.0 com.oracle.ojdbc:simplefan:19.3.0.0 com.oracle.ojdbc:ucp:19.3.0.0 +com.squareup.okhttp3:okhttp:3.12.0 +com.squareup.okio:okio:1.15.0 +com.squareup.retrofit2:retrofit:2.6.1 com.sun.activation:jakarta.activation:2.0.1 com.sun.istack:istack-commons-runtime:4.0.1 com.tobiasdiez:easybind:2.2.1-SNAPSHOT -com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:0.64.0 -com.vladsch.flexmark:flexmark-ext-gfm-tasklist:0.64.0 -com.vladsch.flexmark:flexmark-util-ast:0.64.0 -com.vladsch.flexmark:flexmark-util-builder:0.64.0 -com.vladsch.flexmark:flexmark-util-collection:0.64.0 -com.vladsch.flexmark:flexmark-util-data:0.64.0 -com.vladsch.flexmark:flexmark-util-dependency:0.64.0 -com.vladsch.flexmark:flexmark-util-format:0.64.0 -com.vladsch.flexmark:flexmark-util-html:0.64.0 -com.vladsch.flexmark:flexmark-util-misc:0.64.0 -com.vladsch.flexmark:flexmark-util-options:0.64.0 -com.vladsch.flexmark:flexmark-util-sequence:0.64.0 -com.vladsch.flexmark:flexmark-util-visitor:0.64.0 -com.vladsch.flexmark:flexmark-util:0.64.0 -com.vladsch.flexmark:flexmark:0.64.0 -commons-cli:commons-cli:1.5.0 -commons-codec:commons-codec:1.15 -commons-io:commons-io:2.11.0 -commons-logging:commons-logging:1.2 +com.vladsch.flexmark:flexmark:0.64.8 +com.vladsch.flexmark:flexmark-util-ast:0.64.8 +com.vladsch.flexmark:flexmark-util-builder:0.64.8 +com.vladsch.flexmark:flexmark-util-collection:0.64.8 +com.vladsch.flexmark:flexmark-util-data:0.64.8 +com.vladsch.flexmark:flexmark-util-dependency:0.64.8 +com.vladsch.flexmark:flexmark-util-format:0.64.8 +com.vladsch.flexmark:flexmark-util-html:0.64.8 +com.vladsch.flexmark:flexmark-util-misc:0.64.8 +com.vladsch.flexmark:flexmark-util-sequence:0.64.8 +com.vladsch.flexmark:flexmark-util-visitor:0.64.8 de.saxsys:mvvmfx:1.8.0 -de.undercouch:citeproc-java:3.0.0-alpha.6 +de.swiesend:secret-service:1.8.1-jdk17 +de.undercouch:citeproc-java:3.0.0-beta.2 eu.lestard:doc-annotations:0.2 info.debatty:java-string-similarity:2.0.0 io.github.java-diff-utils:java-diff-utils:4.12 jakarta.annotation:jakarta.annotation-api:2.1.1 jakarta.inject:jakarta.inject-api:2.0.1 jakarta.xml.bind:jakarta.xml.bind-api:3.0.1 +javax.measure:unit-api:2.1.2 net.harawata:appdirs:1.2.1 -net.java.dev.jna:jna-platform:5.6.0 -net.java.dev.jna:jna:5.6.0 +net.java.dev.jna:jna:5.13.0 +net.java.dev.jna:jna-platform:5.13.0 net.jcip:jcip-annotations:1.0 net.jodah:typetools:0.6.1 -org.antlr:antlr4-runtime:4.9.3 -org.apache.commons:commons-csv:1.9.0 -org.apache.commons:commons-lang3:3.12.0 +one.jpro.jproutils:tree-showing:0.2.2 +org.antlr:antlr4-runtime:4.13.0 +org.apache.commons:commons-csv:1.10.0 +org.apache.commons:commons-lang3:3.13.0 org.apache.httpcomponents:httpasyncclient:4.1.5 org.apache.httpcomponents:httpclient:4.5.13 -org.apache.httpcomponents:httpcore-nio:4.4.13 org.apache.httpcomponents:httpcore:4.4.13 +org.apache.httpcomponents:httpcore-nio:4.4.13 org.apache.httpcomponents:httpmime:4.5.13 -org.apache.lucene:lucene-analysis-common:9.4.2 -org.apache.lucene:lucene-core:9.4.1 -org.apache.lucene:lucene-core:9.4.2 -org.apache.lucene:lucene-highlighter:9.4.2 -org.apache.lucene:lucene-queries:9.4.1 -org.apache.lucene:lucene-queries:9.4.2 -org.apache.lucene:lucene-queryparser:9.4.2 -org.apache.lucene:lucene-sandbox:9.4.2 -org.apache.pdfbox:fontbox:3.0.0-RC1 -org.apache.pdfbox:pdfbox:3.0.0-RC1 -org.apache.pdfbox:xmpbox:3.0.0-RC1 -org.apache.tika:tika-core:2.6.0 -org.bouncycastle:bcprov-jdk18on:1.71.1 -org.checkerframework:checker-qual:3.12.0 -org.codehaus.mojo:animal-sniffer-annotations:1.18 +org.apache.lucene:lucene-analysis-common:9.7.0 +org.apache.lucene:lucene-core:9.7.0 +org.apache.lucene:lucene-highlighter:9.7.0 +org.apache.lucene:lucene-queries:9.7.0 +org.apache.lucene:lucene-queryparser:9.7.0 +org.apache.lucene:lucene-sandbox:9.7.0 +org.apache.pdfbox:fontbox:3.0.0 +org.apache.pdfbox:pdfbox:3.0.0 +org.apache.pdfbox:pdfbox-io:3.0.0 +org.apache.pdfbox:xmpbox:3.0.0 +org.bouncycastle:bcprov-jdk18on:1.76 +org.checkerframework:checker-qual:3.33.0 org.controlsfx:controlsfx:11.1.2 -org.eclipse.jgit:org.eclipse.jgit:6.3.0.202209071007-r -org.fxmisc.flowless:flowless:0.7.0 -org.fxmisc.richtext:richtextfx:0.11.0 +org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r +org.fxmisc.flowless:flowless:0.7.1 +org.fxmisc.richtext:richtextfx:0.11.1 org.fxmisc.undo:undofx:2.1.1 org.fxmisc.wellbehaved:wellbehavedfx:0.3.3 org.glassfish.jaxb:jaxb-core:3.0.2 org.glassfish.jaxb:jaxb-runtime:3.0.2 org.glassfish.jaxb:txw2:3.0.2 -org.jbibtex:jbibtex:1.0.19 -org.jetbrains:annotations:15.0 -org.jsoup:jsoup:1.15.3 +org.jabref:afterburner.fx:1.1.0-SNAPSHOT +org.jbibtex:jbibtex:1.0.20 +org.jetbrains:annotations:24.0.1 +org.jooq:jool:0.9.15 +org.jsoup:jsoup:1.16.1 +org.kordamp.ikonli:ikonli-bootstrapicons-pack:12.3.1 org.kordamp.ikonli:ikonli-core:12.3.1 org.kordamp.ikonli:ikonli-javafx:12.3.1 org.kordamp.ikonli:ikonli-materialdesign2-pack:12.3.1 -org.libreoffice:libreoffice:7.4.1 -org.libreoffice:unoloader:7.4.1 -org.mariadb.jdbc:mariadb-java-client:2.7.7 -org.openjfx:javafx-base:19 -org.openjfx:javafx-controls:19 -org.openjfx:javafx-fxml:19 -org.openjfx:javafx-graphics:19 -org.openjfx:javafx-media:19 -org.openjfx:javafx-swing:19 -org.openjfx:javafx-web:19 -org.postgresql:postgresql:42.5.1 +org.kordamp.ikonli:ikonli-materialdesign-pack:12.3.1 +org.kordamp.ikonli:ikonli-material-pack:12.3.1 +org.libreoffice:libreoffice:7.5.3 +org.libreoffice:unoloader:7.6.0 +org.mariadb.jdbc:mariadb-java-client:2.7.9 +org.openjfx:javafx-base:20 +org.openjfx:javafx-controls:20 +org.openjfx:javafx-fxml:20 +org.openjfx:javafx-graphics:20 +org.openjfx:javafx-media:20 +org.openjfx:javafx-swing:20 +org.openjfx:javafx-web:20 +org.postgresql:postgresql:42.6.0 org.reactfx:reactfx:2.0-M5 org.scala-lang:scala-library:2.13.8 -org.slf4j:slf4j-api:2.0.5 -org.tinylog:slf4j-tinylog:2.5.0 -org.tinylog:tinylog-api:2.5.0 -org.tinylog:tinylog-impl:2.5.0 -org.yaml:snakeyaml:1.33 +org.slf4j:slf4j-api:2.0.7 +org.tinylog:slf4j-tinylog:2.6.2 +org.tinylog:tinylog-api:2.6.2 +org.tinylog:tinylog-impl:2.6.2 +org.yaml:snakeyaml:2.0 +pt.davidafsilva.apple:jkeychain:1.1.0 +tech.units:indriya:2.1.2 +tech.uom.lib:uom-lib-common:2.1 ``` diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a79e29..7f93135c49b 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 8196ca1994f..864d6c47512 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=a62c5f99585dd9e1f95dab7b9415a0e698fa9dd1e6c38537faa81ac078f4d23e -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip +distributionSha256Sum=591855b517fc635b9e04de1d05d5e76ada3f89f5fc76f87978d1b245b4f69225 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index aeb74cbb43e..0adc8e1a532 100755 --- a/gradlew +++ b/gradlew @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -130,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. diff --git a/rewrite.yml b/rewrite.yml new file mode 100644 index 00000000000..69508f500c9 --- /dev/null +++ b/rewrite.yml @@ -0,0 +1,181 @@ +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.jabref.config.rewrite.cleanup +recipeList: + # generated by extracting class names out of https://github.com/openrewrite/rewrite-static-analysis + # + # 1. git clone https://github.com/openrewrite/rewrite-static-analysis.git + # 2. cd rewrite-static-analysis/src/main/java/org/openrewrite/staticanalysis + # 3. find . -type f -name "*.java" -exec basename {} .java \; | awk '{print " - org.openrewrite.staticanalysis." $1}' | grep -v "Visitor" | grep -v "package-info" + # + # Then, following to are removed: + # Sometimes fails to produce correct results + # - org.openrewrite.staticanalysis.FinalizePrivateFields + # Creates constructor at the end of the class which violates JabRef's code style + # - org.openrewrite.staticanalysis.HideUtilityClassConstructor + # We are not relying on serialization, thus we do not want to maintain the serialVersionUID manually + # - org.openrewrite.staticanalysis.AddSerialVersionUidToSerializable + # Leads to "if (Boolean.TRUE.equals(proxyPrefs.shouldUseProxy())) {", which reads strange + # - org.openrewrite.staticanalysis.AvoidBoxedBooleanExpressions + # Leads to exception + # - org.openrewrite.staticanalysis.CombineSemanticallyEqualCatchBlocks + # Unreadable code + # .ifPresent((Path selectedDirectory) -> { + # - org.openrewrite.staticanalysis.ExplicitLambdaArgumentTypes + # Unreable code (e.g., public final class BindingsHelper) + # - org.openrewrite.staticanalysis.FinalClass + # Unreable code + # - org.openrewrite.staticanalysis.FinalizeLocalVariables + # Unreadable code + # - org.openrewrite.staticanalysis.FinalizeMethodArguments + # Not sure about the consequences (LibraryTab). + # - org.openrewrite.staticanalysis.FixStringFormatExpressions + # Leads to unreadable code (this.shouldAutoComplete.set(shouldAutoComplete1);) + # - org.openrewrite.staticanalysis.HiddenField + # Leads to unreadable code + # - org.openrewrite.staticanalysis.InlineVariable + # Leads to unreadable code + # - org.openrewrite.staticanalysis.LambdaBlockToExpression + # Works on case-senstive file systems only + # - org.openrewrite.staticanalysis.LowercasePackage + # Needs manual intervention (not fixable at existing code) + # - org.openrewrite.staticanalysis.MethodNameCasing + # Needs manual intervention (not fixable at existing code) + # - org.openrewrite.staticanalysis.MinimumSwitchCases + # Leads to unreadable code + # - org.openrewrite.staticanalysis.NoFinalizedLocalVariables + # Contradicts JabRef's checkstyle + # - org.openrewrite.staticanalysis.NoWhitespaceAfter + # We sometimes need == instead of equals + # - org.openrewrite.staticanalysis.ReferentialEqualityToObjectEquals + # AuthorListTest uses System.gc + # - org.openrewrite.staticanalysis.RemoveCallsToSystemGc + # We need that for our CLI + # - org.openrewrite.staticanalysis.RemoveSystemOutPrintln + # Removes a field - and that wrong + # - org.openrewrite.staticanalysis.RemoveUnusedPrivateMethods + # Crashes + # - org.openrewrite.staticanalysis.RenameExceptionInEmptyCatch + # Also renames private static final varialbes (e.g., LOGGER, ALL_ENTRIES_GROUP_DEFAULT_ICON, ...) + # - org.openrewrite.staticanalysis.RenamePrivateFieldsToCamelCase + # Does something else (deleting a field in TreeNode) + # - org.openrewrite.staticanalysis.ReplaceDeprecatedRuntimeExecMethods + # Leads to compile errors + # - org.openrewrite.staticanalysis.ReplaceDuplicateStringLiterals + # Not everything correctly transformed + # - org.openrewrite.staticanalysis.ReplaceLambdaWithMethodReference + # Crashes + # - org.openrewrite.staticanalysis.ReplaceStackWithDeque + # We want to keep JDK16 (and not downgrade to JDK11) + # - org.openrewrite.staticanalysis.ReplaceStreamToListWithCollect + # Changes one item only + # - org.openrewrite.staticanalysis.ReplaceStringBuilderWithString + # We like text blocks + # - org.openrewrite.staticanalysis.ReplaceTextBlockWithString + # Hangs + # - org.openrewrite.staticanalysis.ReplaceValidateNotNullHavingVarargsWithObjectsRequireNonNull + # We need that in OOBibStyleTest + # - org.openrewrite.staticanalysis.SimplifyConstantIfBranchExecution + + # We log and wrap the exceptions + # - org.openrewrite.staticanalysis.UnnecessaryCatch + # Does not work + # - org.openrewrite.staticanalysis.UnnecessaryCloseInTryWithResources + # Does not work + # - org.openrewrite.staticanalysis.UnnecessaryExplicitTypeArguments + - org.openrewrite.staticanalysis.UnnecessaryParentheses + # Does not work + # - org.openrewrite.staticanalysis.UnnecessaryPrimitiveAnnotations + # Removes too much throws + # - org.openrewrite.staticanalysis.UnnecessaryThrows + # Crashes + # - org.openrewrite.staticanalysis.UseAsBuilder + # "Upgrades" too much, deletes fields + # - org.openrewrite.staticanalysis.UseCollectionInterfaces + # Does not work at TreeNode + # - org.openrewrite.staticanalysis.UseDiamondOperator + # States that it gains performance, but the code is more unreable + # - org.openrewrite.staticanalysis.UseForEachRemoveInsteadOfSetRemoveAll + # We voted against it + # - org.openrewrite.staticanalysis.ExplicitInitialization + + - org.openrewrite.java.ShortenFullyQualifiedTypeReferences + + - org.openrewrite.staticanalysis.AtomicPrimitiveEqualsUsesGet + - org.openrewrite.staticanalysis.BigDecimalRoundingConstantsToEnums + - org.openrewrite.staticanalysis.BooleanChecksNotInverted + - org.openrewrite.staticanalysis.CaseInsensitiveComparisonsDoNotChangeCase + - org.openrewrite.staticanalysis.CatchClauseOnlyRethrows + - org.openrewrite.staticanalysis.ChainStringBuilderAppendCalls + # Might need manual intervention, because negations are not treated properly + - org.openrewrite.staticanalysis.CompareEnumsWithEqualityOperator + - org.openrewrite.staticanalysis.ControlFlowIndentation + - org.openrewrite.staticanalysis.CovariantEquals + - org.openrewrite.staticanalysis.DeclarationSiteTypeVariance + # Needs manual intervention +# - org.openrewrite.staticanalysis.DefaultComesLast + - org.openrewrite.staticanalysis.EmptyBlock + - org.openrewrite.staticanalysis.EqualsAvoidsNull + # Needs manual intervention +# - org.openrewrite.staticanalysis.ExplicitCharsetOnStringGetBytes + - org.openrewrite.staticanalysis.ExternalizableHasNoArgsConstructor + - org.openrewrite.staticanalysis.FallThrough +# - org.openrewrite.staticanalysis.ForLoopControlVariablePostfixOperators + - org.openrewrite.staticanalysis.ForLoopIncrementInUpdate + - org.openrewrite.staticanalysis.IndexOfChecksShouldUseAStartPosition + - org.openrewrite.staticanalysis.IndexOfReplaceableByContains +# - org.openrewrite.staticanalysis.IndexOfShouldNotCompareGreaterThanZero + - org.openrewrite.staticanalysis.InstanceOfPatternMatch + - org.openrewrite.staticanalysis.IsEmptyCallOnCollections +# - org.openrewrite.staticanalysis.MissingOverrideAnnotation +# - org.openrewrite.staticanalysis.ModifierOrder + - org.openrewrite.staticanalysis.MultipleVariableDeclarations + - org.openrewrite.staticanalysis.NeedBraces + - org.openrewrite.staticanalysis.NestedEnumsAreNotStatic + - org.openrewrite.staticanalysis.NewStringBuilderBufferWithCharArgument + - org.openrewrite.staticanalysis.NoDoubleBraceInitialization +# - org.openrewrite.staticanalysis.NoEmptyCollectionWithRawType + - org.openrewrite.staticanalysis.NoEqualityInForCondition + - org.openrewrite.staticanalysis.NoFinalizer +# - org.openrewrite.staticanalysis.NoPrimitiveWrappersForToStringOrCompareTo +# - org.openrewrite.staticanalysis.NoRedundantJumpStatements + - org.openrewrite.staticanalysis.NoToStringOnStringType + - org.openrewrite.staticanalysis.NoValueOfOnStringType +# - org.openrewrite.staticanalysis.NoWhitespaceBefore + - org.openrewrite.staticanalysis.ObjectFinalizeCallsSuper +# - org.openrewrite.staticanalysis.OperatorWrap + - org.openrewrite.staticanalysis.PrimitiveWrapperClassConstructorToValueOf + - org.openrewrite.staticanalysis.RedundantFileCreation + - org.openrewrite.staticanalysis.RemoveCallsToObjectFinalize + - org.openrewrite.staticanalysis.RemoveEmptyJavaDocParameters + - org.openrewrite.staticanalysis.RemoveExtraSemicolons + - org.openrewrite.staticanalysis.RemoveJavaDocAuthorTag + - org.openrewrite.staticanalysis.RemoveRedundantTypeCast + - org.openrewrite.staticanalysis.RemoveUnneededAssertion + - org.openrewrite.staticanalysis.RemoveUnneededBlock +# - org.openrewrite.staticanalysis.RemoveUnusedLocalVariables +# - org.openrewrite.staticanalysis.RemoveUnusedPrivateFields +# - org.openrewrite.staticanalysis.RenameLocalVariablesToCamelCase + - org.openrewrite.staticanalysis.RenameMethodsNamedHashcodeEqualOrTostring + - org.openrewrite.staticanalysis.ReplaceRedundantFormatWithPrintf + - org.openrewrite.staticanalysis.ReplaceStringBuilderWithString +# - org.openrewrite.staticanalysis.ShortenFullyQualifiedTypeReferences +# - org.openrewrite.staticanalysis.SimplifyConsecutiveAssignments +# - org.openrewrite.staticanalysis.SimplifyCompoundStatement + - org.openrewrite.staticanalysis.SimplifyBooleanExpression + - org.openrewrite.staticanalysis.SimplifyBooleanReturn + - org.openrewrite.staticanalysis.SimplifyDurationCreationUnits + - org.openrewrite.staticanalysis.StaticMethodNotFinal + - org.openrewrite.staticanalysis.StringLiteralEquality + - org.openrewrite.staticanalysis.TypecastParenPad + - org.openrewrite.staticanalysis.UnwrapRepeatableAnnotations + - org.openrewrite.staticanalysis.UpperCaseLiteralSuffixes +# - org.openrewrite.staticanalysis.UseJavaStyleArrayDeclarations + - org.openrewrite.staticanalysis.UseLambdaForFunctionalInterface +# - org.openrewrite.staticanalysis.UseListSort + - org.openrewrite.staticanalysis.UseObjectNotifyAll + - org.openrewrite.staticanalysis.UseStandardCharset + - org.openrewrite.staticanalysis.UseStringReplace + - org.openrewrite.staticanalysis.UseSystemLineSeparator + - org.openrewrite.staticanalysis.WhileInsteadOfFor +# - org.openrewrite.staticanalysis.WriteOctalValuesAsDecimal diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 3a000c8a78f..eebb2ec3d62 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -10,10 +10,11 @@ description: | `snap connect jabref:removable-media` grade: stable confinement: strict -base: core20 +base: core22 compression: lzo architectures: - - build-on: amd64 + - build-on: [amd64, arm64] + build-for: [amd64] plugs: home: @@ -45,10 +46,10 @@ layout: apps: jabref: command: bin/JabRef - extensions: [gnome-3-38] + extensions: [gnome] browser-proxy: command: lib/jabrefHost.py - extensions: [gnome-3-38] + extensions: [gnome] environment: _JAVA_OPTIONS: "-Duser.home=$SNAP_USER_DATA" @@ -57,7 +58,7 @@ environment: parts: jabref: plugin: dump - source: https://builds.jabref.org/main/JabRef-5.10-portable_linux.tar.gz + source: https://builds.jabref.org/main/JabRef-5.11-portable_linux.tar.gz stage-packages: - x11-utils override-build: | @@ -80,10 +81,10 @@ parts: - jabref-launcher plugin: nil build-snaps: - - gnome-3-38-2004 + - gnome-42-2204 override-prime: | set -eux - for snap in "gnome-3-38-2004"; do # List all content-snaps you're using here + for snap in "gnome-42-2204"; do # List all content-snaps you're using here cd "/snap/$snap/current" && find . -type f,l -exec rm -f "$SNAPCRAFT_PRIME/{}" "$SNAPCRAFT_PRIME/usr/{}" \; done for CRUFT in bug lintian man; do diff --git a/src/jmh/java/org/jabref/benchmarks/Benchmarks.java b/src/jmh/java/org/jabref/benchmarks/Benchmarks.java index b0073114ac5..8eae854642c 100644 --- a/src/jmh/java/org/jabref/benchmarks/Benchmarks.java +++ b/src/jmh/java/org/jabref/benchmarks/Benchmarks.java @@ -9,9 +9,11 @@ import java.util.stream.Collectors; import org.jabref.gui.Globals; +import org.jabref.logic.bibtex.FieldPreferences; +import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences; import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.exporter.BibtexDatabaseWriter; -import org.jabref.logic.exporter.SavePreferences; +import org.jabref.logic.exporter.SelfContainedSaveConfiguration; import org.jabref.logic.formatter.bibtexfields.HtmlToLatexFormatter; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.fileformat.BibtexParser; @@ -32,8 +34,6 @@ import org.jabref.model.groups.WordKeywordGroup; import org.jabref.model.metadata.MetaData; import org.jabref.model.search.rules.SearchRules.SearchFlags; -import org.jabref.model.util.DummyFileUpdateMonitor; -import org.jabref.preferences.GeneralPreferences; import org.jabref.preferences.JabRefPreferences; import org.openjdk.jmh.Main; @@ -80,14 +80,19 @@ public void init() throws Exception { private StringWriter getOutputWriter() throws IOException { StringWriter outputWriter = new StringWriter(); BibWriter bibWriter = new BibWriter(outputWriter, OS.NEWLINE); - BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(bibWriter, mock(GeneralPreferences.class), mock(SavePreferences.class), new BibEntryTypesManager()); + BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter( + bibWriter, + mock(SelfContainedSaveConfiguration.class), + mock(FieldPreferences.class), + mock(CitationKeyPatternPreferences.class), + new BibEntryTypesManager()); databaseWriter.savePartOfDatabase(new BibDatabaseContext(database, new MetaData()), database.getEntries()); return outputWriter; } @Benchmark public ParserResult parse() throws IOException { - BibtexParser parser = new BibtexParser(Globals.prefs.getImportFormatPreferences(), new DummyFileUpdateMonitor()); + BibtexParser parser = new BibtexParser(Globals.prefs.getImportFormatPreferences()); return parser.parse(new StringReader(bibtexString)); } diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index e500c1ca5b7..e4c66f4e4eb 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -9,16 +9,13 @@ // JavaFX requires javafx.base; requires javafx.graphics; - requires javafx.swing; requires javafx.controls; requires javafx.web; requires javafx.fxml; requires afterburner.fx; - requires com.jfoenix; + requires com.dlsc.gemsfx; + uses com.dlsc.gemsfx.TagsField; requires de.saxsys.mvvmfx; - requires reactfx; - requires de.saxsys.mvvmfx.validation; - requires org.fxmisc.flowless; requires org.kordamp.ikonli.core; requires org.kordamp.ikonli.javafx; @@ -51,6 +48,7 @@ with org.jabref.gui.logging.GuiWriter, org.jabref.gui.logging.ApplicationInsightsWriter; + // Preferences and XML requires java.prefs; // Annotations (@PostConstruct) @@ -104,14 +102,20 @@ requires jbibtex; requires citeproc.java; - requires pdfbox; - requires xmpbox; + requires org.apache.pdfbox; + requires org.apache.xmpbox; requires com.ibm.icu; requires flexmark; requires flexmark.util.ast; requires flexmark.util.data; + requires com.h2database.mvstore; + + requires java.keyring; + + requires org.jooq.jool; + // fulltext search requires org.apache.lucene.core; // In case the version is updated, please also adapt SearchFieldConstants#VERSION to the newly used version @@ -131,7 +135,6 @@ uses org.eclipse.jgit.lib.GpgSigner; // other libraries - requires com.h2database.mvstore; requires org.antlr.antlr4.runtime; requires org.libreoffice.uno; diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index 88bb849569b..5d5c2c025d6 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -16,7 +16,7 @@ import org.jabref.gui.externalfiles.AutoSetFileLinksUtil; import org.jabref.gui.undo.NamedCompound; import org.jabref.logic.JabRefException; -import org.jabref.logic.bibtex.FieldWriterPreferences; +import org.jabref.logic.bibtex.FieldPreferences; import org.jabref.logic.citationkeypattern.CitationKeyGenerator; import org.jabref.logic.exporter.AtomicFileWriter; import org.jabref.logic.exporter.BibDatabaseWriter; @@ -25,7 +25,7 @@ import org.jabref.logic.exporter.EmbeddedBibFilePdfExporter; import org.jabref.logic.exporter.Exporter; import org.jabref.logic.exporter.ExporterFactory; -import org.jabref.logic.exporter.SavePreferences; +import org.jabref.logic.exporter.SelfContainedSaveConfiguration; import org.jabref.logic.exporter.XmpPdfExporter; import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.ImportException; @@ -38,6 +38,7 @@ import org.jabref.logic.importer.SearchBasedFetcher; import org.jabref.logic.importer.WebFetchers; import org.jabref.logic.importer.fileformat.BibtexParser; +import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.l10n.Localization; import org.jabref.logic.net.URLDownload; import org.jabref.logic.search.DatabaseSearcher; @@ -53,8 +54,8 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.strings.StringUtil; import org.jabref.model.util.DummyFileUpdateMonitor; +import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.FilePreferences; -import org.jabref.preferences.GeneralPreferences; import org.jabref.preferences.PreferencesService; import org.jabref.preferences.SearchPreferences; @@ -69,12 +70,20 @@ public class ArgumentProcessor { private final List parserResults; private final Mode startupMode; private final PreferencesService preferencesService; + private final FileUpdateMonitor fileUpdateMonitor; + private final BibEntryTypesManager entryTypesManager; private boolean noGUINeeded; - public ArgumentProcessor(String[] args, Mode startupMode, PreferencesService preferencesService) throws org.apache.commons.cli.ParseException { + public ArgumentProcessor(String[] args, + Mode startupMode, + PreferencesService preferencesService, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager) throws org.apache.commons.cli.ParseException { this.cli = new JabRefCLI(args); this.startupMode = startupMode; this.preferencesService = preferencesService; + this.fileUpdateMonitor = fileUpdateMonitor; + this.entryTypesManager = entryTypesManager; this.parserResults = processArguments(); } @@ -85,7 +94,7 @@ public ArgumentProcessor(String[] args, Mode startupMode, PreferencesService pre * @param argument See importFile. * @return ParserResult with setToOpenTab(true) */ - private static Optional importToOpenBase(String argument) { + private Optional importToOpenBase(String argument) { Optional result = importFile(argument); result.ifPresent(ParserResult::setToOpenTab); @@ -93,8 +102,8 @@ private static Optional importToOpenBase(String argument) { return result; } - private static Optional importBibtexToOpenBase(String argument, ImportFormatPreferences importFormatPreferences) { - BibtexParser parser = new BibtexParser(importFormatPreferences, new DummyFileUpdateMonitor()); + private Optional importBibtexToOpenBase(String argument, ImportFormatPreferences importFormatPreferences) { + BibtexParser parser = new BibtexParser(importFormatPreferences); try { List entries = parser.parseEntries(argument); ParserResult result = new ParserResult(entries); @@ -106,7 +115,7 @@ private static Optional importBibtexToOpenBase(String argument, Im } } - private static Optional importFile(String argument) { + private Optional importFile(String argument) { String[] data = argument.split(","); String address = data[0]; @@ -144,21 +153,26 @@ private static Optional importFile(String argument) { return importResult; } - private static Optional importFile(Path file, String importFormat) { + private Optional importFile(Path file, String importFormat) { try { + ImportFormatReader importFormatReader = new ImportFormatReader( + preferencesService.getImporterPreferences(), + preferencesService.getImportFormatPreferences(), + fileUpdateMonitor); + if (!"*".equals(importFormat)) { System.out.println(Localization.lang("Importing") + ": " + file); - ParserResult result = Globals.IMPORT_FORMAT_READER.importFromFile(importFormat, file); + ParserResult result = importFormatReader.importFromFile(importFormat, file); return Optional.of(result); } else { // * means "guess the format": System.out.println(Localization.lang("Importing in unknown format") + ": " + file); ImportFormatReader.UnknownFormatImport importResult = - Globals.IMPORT_FORMAT_READER.importUnknownFormat(file, new DummyFileUpdateMonitor()); + importFormatReader.importUnknownFormat(file, new DummyFileUpdateMonitor()); - System.out.println(Localization.lang("Format used") + ": " + importResult.format); - return Optional.of(importResult.parserResult); + System.out.println(Localization.lang("Format used") + ": " + importResult.format()); + return Optional.of(importResult.parserResult()); } } catch (ImportException ex) { System.err @@ -176,10 +190,6 @@ public boolean hasParserResults() { } private List processArguments() { - if (!cli.isBlank() && cli.isDebugLogging()) { - System.err.println("use java property -Dtinylog.level=debug"); - } - if ((startupMode == Mode.INITIAL_START) && cli.isShowVersion()) { cli.displayVersion(); } @@ -231,13 +241,14 @@ private List processArguments() { if (cli.isWriteMetadatatoPdf() || cli.isWriteXMPtoPdf() || cli.isEmbeddBibfileInPdf()) { if (!loaded.isEmpty()) { - writeMetadatatoPdf(loaded, + writeMetadataToPdf(loaded, cli.getWriteMetadatatoPdf(), preferencesService.getXmpPreferences(), preferencesService.getFilePreferences(), - preferencesService.getGeneralPreferences().getDefaultBibDatabaseMode(), + preferencesService.getLibraryPreferences().getDefaultBibDatabaseMode(), Globals.entryTypesManager, - preferencesService.getFieldWriterPreferences(), + preferencesService.getFieldPreferences(), + Globals.journalAbbreviationRepository, cli.isWriteXMPtoPdf() || cli.isWriteMetadatatoPdf(), cli.isEmbeddBibfileInPdf() || cli.isWriteMetadatatoPdf()); } @@ -267,21 +278,38 @@ private List processArguments() { return loaded; } - private void writeMetadatatoPdf(List loaded, String filesAndCitekeys, XmpPreferences xmpPreferences, FilePreferences filePreferences, BibDatabaseMode databaseMode, BibEntryTypesManager entryTypesManager, FieldWriterPreferences fieldWriterPreferences, boolean writeXMP, boolean embeddBibfile) { + private void writeMetadataToPdf(List loaded, + String filesAndCitekeys, + XmpPreferences xmpPreferences, + FilePreferences filePreferences, + BibDatabaseMode databaseMode, + BibEntryTypesManager entryTypesManager, + FieldPreferences fieldPreferences, + JournalAbbreviationRepository abbreviationRepository, + boolean writeXMP, + boolean embeddBibfile) { if (loaded.isEmpty()) { LOGGER.error("The write xmp option depends on a valid import option."); return; } ParserResult pr = loaded.get(loaded.size() - 1); BibDatabaseContext databaseContext = pr.getDatabaseContext(); - BibDatabase dataBase = pr.getDatabase(); XmpPdfExporter xmpPdfExporter = new XmpPdfExporter(xmpPreferences); - EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter = new EmbeddedBibFilePdfExporter(databaseMode, entryTypesManager, fieldWriterPreferences); + EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter = new EmbeddedBibFilePdfExporter(databaseMode, entryTypesManager, fieldPreferences); if ("all".equals(filesAndCitekeys)) { - for (BibEntry entry : dataBase.getEntries()) { - writeMetadatatoPDFsOfEntry(databaseContext, entry.getCitationKey().orElse(""), entry, filePreferences, xmpPdfExporter, embeddedBibFilePdfExporter, writeXMP, embeddBibfile); + for (BibEntry entry : databaseContext.getEntries()) { + writeMetadataToPDFsOfEntry( + databaseContext, + entry.getCitationKey().orElse(""), + entry, + filePreferences, + xmpPdfExporter, + embeddedBibFilePdfExporter, + abbreviationRepository, + writeXMP, + embeddBibfile); } return; } @@ -296,21 +324,45 @@ private void writeMetadatatoPdf(List loaded, String filesAndCiteke } } - writeMetadatatoPdfByCitekey(databaseContext, dataBase, citeKeys, filePreferences, xmpPdfExporter, embeddedBibFilePdfExporter, writeXMP, embeddBibfile); - writeMetadatatoPdfByFileNames(databaseContext, dataBase, pdfs, filePreferences, xmpPdfExporter, embeddedBibFilePdfExporter, writeXMP, embeddBibfile); + writeMetadataToPdfByCitekey( + databaseContext, + citeKeys, + filePreferences, + xmpPdfExporter, + embeddedBibFilePdfExporter, + abbreviationRepository, + writeXMP, + embeddBibfile); + writeMetadataToPdfByFileNames( + databaseContext, + pdfs, + filePreferences, + xmpPdfExporter, + embeddedBibFilePdfExporter, + abbreviationRepository, + writeXMP, + embeddBibfile); } - private void writeMetadatatoPDFsOfEntry(BibDatabaseContext databaseContext, String citeKey, BibEntry entry, FilePreferences filePreferences, XmpPdfExporter xmpPdfExporter, EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, boolean writeXMP, boolean embeddBibfile) { + private void writeMetadataToPDFsOfEntry(BibDatabaseContext databaseContext, + String citeKey, + BibEntry entry, + FilePreferences filePreferences, + XmpPdfExporter xmpPdfExporter, + EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, + JournalAbbreviationRepository abbreviationRepository, + boolean writeXMP, + boolean embeddBibfile) { try { if (writeXMP) { - if (xmpPdfExporter.exportToAllFilesOfEntry(databaseContext, filePreferences, entry, List.of(entry))) { + if (xmpPdfExporter.exportToAllFilesOfEntry(databaseContext, filePreferences, entry, List.of(entry), abbreviationRepository)) { System.out.printf("Successfully written XMP metadata on at least one linked file of %s%n", citeKey); } else { System.err.printf("Cannot write XMP metadata on any linked files of %s. Make sure there is at least one linked file and the path is correct.%n", citeKey); } } if (embeddBibfile) { - if (embeddedBibFilePdfExporter.exportToAllFilesOfEntry(databaseContext, filePreferences, entry, List.of(entry))) { + if (embeddedBibFilePdfExporter.exportToAllFilesOfEntry(databaseContext, filePreferences, entry, List.of(entry), abbreviationRepository)) { System.out.printf("Successfully embedded metadata on at least one linked file of %s%n", citeKey); } else { System.out.printf("Cannot embedd metadata on any linked files of %s. Make sure there is at least one linked file and the path is correct.%n", citeKey); @@ -321,20 +373,34 @@ private void writeMetadatatoPDFsOfEntry(BibDatabaseContext databaseContext, Stri } } - private void writeMetadatatoPdfByCitekey(BibDatabaseContext databaseContext, BibDatabase dataBase, List citeKeys, FilePreferences filePreferences, XmpPdfExporter xmpPdfExporter, EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, boolean writeXMP, boolean embeddBibfile) { + private void writeMetadataToPdfByCitekey(BibDatabaseContext databaseContext, + List citeKeys, + FilePreferences filePreferences, + XmpPdfExporter xmpPdfExporter, + EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, + JournalAbbreviationRepository abbreviationRepository, + boolean writeXMP, + boolean embeddBibfile) { for (String citeKey : citeKeys) { - List bibEntryList = dataBase.getEntriesByCitationKey(citeKey); + List bibEntryList = databaseContext.getDatabase().getEntriesByCitationKey(citeKey); if (bibEntryList.isEmpty()) { System.err.printf("Skipped - Cannot find %s in library.%n", citeKey); continue; } for (BibEntry entry : bibEntryList) { - writeMetadatatoPDFsOfEntry(databaseContext, citeKey, entry, filePreferences, xmpPdfExporter, embeddedBibFilePdfExporter, writeXMP, embeddBibfile); + writeMetadataToPDFsOfEntry(databaseContext, citeKey, entry, filePreferences, xmpPdfExporter, embeddedBibFilePdfExporter, abbreviationRepository, writeXMP, embeddBibfile); } } } - private void writeMetadatatoPdfByFileNames(BibDatabaseContext databaseContext, BibDatabase dataBase, List pdfs, FilePreferences filePreferences, XmpPdfExporter xmpPdfExporter, EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, boolean writeXMP, boolean embeddBibfile) { + private void writeMetadataToPdfByFileNames(BibDatabaseContext databaseContext, + List pdfs, + FilePreferences filePreferences, + XmpPdfExporter xmpPdfExporter, + EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, + JournalAbbreviationRepository abbreviationRepository, + boolean writeXMP, + boolean embeddBibfile) { for (String fileName : pdfs) { Path filePath = Path.of(fileName); if (!filePath.isAbsolute()) { @@ -343,14 +409,14 @@ private void writeMetadatatoPdfByFileNames(BibDatabaseContext databaseContext, B if (Files.exists(filePath)) { try { if (writeXMP) { - if (xmpPdfExporter.exportToFileByPath(databaseContext, dataBase, filePreferences, filePath)) { + if (xmpPdfExporter.exportToFileByPath(databaseContext, filePreferences, filePath, abbreviationRepository)) { System.out.printf("Successfully written XMP metadata of at least one entry to %s%n", fileName); } else { System.out.printf("File %s is not linked to any entry in database.%n", fileName); } } if (embeddBibfile) { - if (embeddedBibFilePdfExporter.exportToFileByPath(databaseContext, dataBase, filePreferences, filePath)) { + if (embeddedBibFilePdfExporter.exportToFileByPath(databaseContext, filePreferences, filePath, abbreviationRepository)) { System.out.printf("Successfully embedded XMP metadata of at least one entry to %s%n", fileName); } else { System.out.printf("File %s is not linked to any entry in database.%n", fileName); @@ -397,7 +463,7 @@ private boolean exportMatches(List loaded) { } } - if (formatName.equals("bib")) { + if ("bib".equals(formatName)) { // output a bib file as default or if // provided exportFormat is "bib" saveDatabase(new BibDatabase(matches), data[1]); @@ -406,8 +472,7 @@ private boolean exportMatches(List loaded) { // export new database ExporterFactory exporterFactory = ExporterFactory.create( preferencesService, - Globals.entryTypesManager, - Globals.journalAbbreviationRepository); + Globals.entryTypesManager); Optional exporter = exporterFactory.getExporterByName(formatName); if (exporter.isEmpty()) { System.err.println(Localization.lang("Unknown export format") + ": " + formatName); @@ -415,7 +480,7 @@ private boolean exportMatches(List loaded) { // We have an TemplateExporter instance: try { System.out.println(Localization.lang("Exporting") + ": " + data[1]); - exporter.get().export(databaseContext, Path.of(data[1]), matches, Collections.emptyList()); + exporter.get().export(databaseContext, Path.of(data[1]), matches, Collections.emptyList(), Globals.journalAbbreviationRepository); } catch (Exception ex) { System.err.println(Localization.lang("Could not export file") + " '" + data[1] + "': " + Throwables.getStackTraceAsString(ex)); @@ -460,7 +525,7 @@ private List importAndOpenFiles() { pr = OpenDatabase.loadDatabase( Path.of(aLeftOver), preferencesService.getImportFormatPreferences(), - Globals.getFileUpdateMonitor()); + fileUpdateMonitor); } catch (IOException ex) { pr = ParserResult.fromError(ex); LOGGER.error("Error opening file '{}'", aLeftOver, ex); @@ -531,16 +596,16 @@ private boolean generateAux(List loaded, String[] data) { private void saveDatabase(BibDatabase newBase, String subName) { try { System.out.println(Localization.lang("Saving") + ": " + subName); - GeneralPreferences generalPreferences = preferencesService.getGeneralPreferences(); - SavePreferences savePreferences = preferencesService.getSavePreferences(); try (AtomicFileWriter fileWriter = new AtomicFileWriter(Path.of(subName), StandardCharsets.UTF_8)) { BibWriter bibWriter = new BibWriter(fileWriter, OS.NEWLINE); - + SelfContainedSaveConfiguration saveConfiguration = (SelfContainedSaveConfiguration) new SelfContainedSaveConfiguration() + .withReformatOnSave(preferencesService.getLibraryPreferences().shouldAlwaysReformatOnSave()); BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter( - bibWriter, - generalPreferences, - savePreferences, - Globals.entryTypesManager); + bibWriter, + saveConfiguration, + preferencesService.getFieldPreferences(), + preferencesService.getCitationKeyPatternPreferences(), + entryTypesManager); databaseWriter.saveDatabase(new BibDatabaseContext(newBase)); // Show just a warning message if encoding did not work for all characters: @@ -579,8 +644,7 @@ private void exportFile(List loaded, String[] data) { System.out.println(Localization.lang("Exporting") + ": " + data[0]); ExporterFactory exporterFactory = ExporterFactory.create( preferencesService, - Globals.entryTypesManager, - Globals.journalAbbreviationRepository); + Globals.entryTypesManager); Optional exporter = exporterFactory.getExporterByName(data[1]); if (exporter.isEmpty()) { System.err.println(Localization.lang("Unknown export format") + ": " + data[1]); @@ -591,7 +655,8 @@ private void exportFile(List loaded, String[] data) { pr.getDatabaseContext(), Path.of(data[0]), pr.getDatabaseContext().getDatabase().getEntries(), - fileDirForDatabase); + fileDirForDatabase, + Globals.journalAbbreviationRepository); } catch (Exception ex) { System.err.println(Localization.lang("Could not export file") + " '" + data[0] + "': " + Throwables.getStackTraceAsString(ex)); diff --git a/src/main/java/org/jabref/cli/JabRefCLI.java b/src/main/java/org/jabref/cli/JabRefCLI.java index 367c929541e..484201b63ff 100644 --- a/src/main/java/org/jabref/cli/JabRefCLI.java +++ b/src/main/java/org/jabref/cli/JabRefCLI.java @@ -1,11 +1,17 @@ package org.jabref.cli; import java.util.List; +import java.util.Objects; + +import javafx.util.Pair; import org.jabref.gui.Globals; -import org.jabref.logic.exporter.Exporter; import org.jabref.logic.exporter.ExporterFactory; +import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.OS; +import org.jabref.model.strings.StringUtil; +import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.preferences.PreferencesService; import org.apache.commons.cli.CommandLine; @@ -18,6 +24,7 @@ public class JabRefCLI { private static final int WIDTH = 100; // Number of characters per line before a line break must be added. private static final String WRAPPED_LINE_PREFIX = ""; // If a line break is added, this prefix will be inserted at the beginning of the next line + private static final String STRING_TABLE_DELIMITER = " : "; private final CommandLine cl; private final List leftOver; @@ -159,7 +166,7 @@ public boolean isWriteMetadatatoPdf() { public String getWriteMetadatatoPdf() { return cl.hasOption("writeMetadatatoPdf") ? cl.getOptionValue("writeMetadatatoPdf") : cl.hasOption("writeXMPtoPdf") ? cl.getOptionValue("writeXMPtoPdf") : - cl.hasOption("embeddBibfileInPdf") ? cl.getOptionValue("embeddBibfileInPdf") : null; + cl.hasOption("embeddBibfileInPdf") ? cl.getOptionValue("embeddBibfileInPdf") : null; } private static Options getOptions() { @@ -291,16 +298,26 @@ public void displayVersion() { public static void printUsage(PreferencesService preferencesService) { String header = ""; - String importFormats = Globals.IMPORT_FORMAT_READER.getImportFormatList(); - String importFormatsList = String.format("%s:%n%s%n", Localization.lang("Available import formats"), importFormats); + ImportFormatReader importFormatReader = new ImportFormatReader( + preferencesService.getImporterPreferences(), + preferencesService.getImportFormatPreferences(), + new DummyFileUpdateMonitor()); + List> importFormats = importFormatReader + .getImportFormats().stream() + .map(format -> new Pair<>(format.getName(), format.getId())) + .toList(); + String importFormatsIntro = Localization.lang("Available import formats"); + String importFormatsList = String.format("%s:%n%s%n", importFormatsIntro, alignStringTable(importFormats)); ExporterFactory exporterFactory = ExporterFactory.create( preferencesService, - Globals.entryTypesManager, - Globals.journalAbbreviationRepository); + Globals.entryTypesManager); + List> exportFormats = exporterFactory + .getExporters().stream() + .map(format -> new Pair<>(format.getName(), format.getId())) + .toList(); String outFormatsIntro = Localization.lang("Available export formats"); - String outFormats = wrapStringList(exporterFactory.getExporters().stream().map(Exporter::getId).toList(), outFormatsIntro.length()); - String outFormatsList = String.format("%s: %s%n", outFormatsIntro, outFormats); + String outFormatsList = String.format("%s:%n%s%n", outFormatsIntro, alignStringTable(exportFormats)); String footer = '\n' + importFormatsList + outFormatsList + "\nPlease report issues at https://github.com/JabRef/jabref/issues."; @@ -316,12 +333,34 @@ public List getLeftOver() { return leftOver; } + protected static String alignStringTable(List> table) { + StringBuilder sb = new StringBuilder(); + + int maxLength = table.stream() + .mapToInt(pair -> Objects.requireNonNullElse(pair.getKey(), "").length()) + .max().orElse(0); + + for (Pair pair : table) { + int padding = Math.max(0, maxLength - pair.getKey().length()); + sb.append(WRAPPED_LINE_PREFIX); + sb.append(pair.getKey()); + + sb.append(StringUtil.repeatSpaces(padding)); + + sb.append(STRING_TABLE_DELIMITER); + sb.append(pair.getValue()); + sb.append(OS.NEWLINE); + } + + return sb.toString(); + } + /** * Creates and wraps a multi-line and colon-seperated string from a List of Strings. */ - protected static String wrapStringList(List list, int firstLineIntroLength) { + protected static String wrapStringList(List list, int firstLineIndentation) { StringBuilder builder = new StringBuilder(); - int lastBreak = -firstLineIntroLength; + int lastBreak = -firstLineIndentation; for (String line : list) { if (((builder.length() + 2 + line.length()) - lastBreak) > WIDTH) { diff --git a/src/main/java/org/jabref/cli/Launcher.java b/src/main/java/org/jabref/cli/Launcher.java index 89adc9dd43b..8fb00c4742a 100644 --- a/src/main/java/org/jabref/cli/Launcher.java +++ b/src/main/java/org/jabref/cli/Launcher.java @@ -21,14 +21,13 @@ 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.OS; import org.jabref.migrations.PreferencesMigrations; -import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.JabRefPreferences; import org.jabref.preferences.PreferencesService; -import net.harawata.appdirs.AppDirsFactory; import org.apache.commons.cli.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,19 +44,34 @@ public class Launcher { private static Logger LOGGER; private static String[] ARGUMENTS; + private static boolean isDebugEnabled; public static void main(String[] args) { routeLoggingToSlf4J(); ARGUMENTS = args; + + // We must configure logging as soon as possible, which is why we cannot wait for the usual + // argument parsing workflow to parse logging options .e.g. --debug + JabRefCLI jabRefCLI; + try { + jabRefCLI = new JabRefCLI(ARGUMENTS); + isDebugEnabled = jabRefCLI.isDebugLogging(); + } catch (ParseException e) { + isDebugEnabled = false; + } + addLogToDisk(); try { + BibEntryTypesManager entryTypesManager = new BibEntryTypesManager(); + Globals.entryTypesManager = entryTypesManager; + // Init preferences final JabRefPreferences preferences = JabRefPreferences.getInstance(); Globals.prefs = preferences; - PreferencesMigrations.runMigrations(preferences); + PreferencesMigrations.runMigrations(preferences, entryTypesManager); // Early exit in case another instance is already running - if (!handleMultipleAppInstances(args, preferences)) { + if (!handleMultipleAppInstances(ARGUMENTS, preferences)) { return; } @@ -68,15 +82,20 @@ public static void main(String[] args) { clearOldSearchIndices(); try { + FileUpdateMonitor fileUpdateMonitor = Globals.getFileUpdateMonitor(); + // Process arguments - ArgumentProcessor argumentProcessor = new ArgumentProcessor(args, ArgumentProcessor.Mode.INITIAL_START, - preferences); + ArgumentProcessor argumentProcessor = new ArgumentProcessor( + ARGUMENTS, ArgumentProcessor.Mode.INITIAL_START, + preferences, + fileUpdateMonitor, + entryTypesManager); if (argumentProcessor.shouldShutDown()) { LOGGER.debug("JabRef shut down after processing command line arguments"); return; } - MainApplication.main(argumentProcessor.getParserResults(), argumentProcessor.isBlank(), preferences, ARGUMENTS); + MainApplication.main(argumentProcessor.getParserResults(), argumentProcessor.isBlank(), preferences, fileUpdateMonitor, ARGUMENTS); } catch (ParseException e) { LOGGER.error("Problem parsing arguments", e); JabRefCLI.printUsage(preferences); @@ -97,12 +116,7 @@ private static void routeLoggingToSlf4J() { * the log configuration programmatically anymore. */ private static void addLogToDisk() { - Path directory = Path.of(AppDirsFactory.getInstance() - .getUserDataDir( - OS.APP_DIR_APP_NAME, - "logs", - OS.APP_DIR_APP_AUTHOR)) - .resolve(new BuildInfo().version.toString()); + Path directory = OS.getNativeDesktop().getLogDirectory(); try { Files.createDirectories(directory); } catch (IOException e) { @@ -114,7 +128,8 @@ private static void addLogToDisk() { // https://tinylog.org/v2/configuration/#shared-file-writer Map configuration = Map.of( "writerFile", "shared file", - "writerFile.level", "debug", + "writerFile.level", isDebugEnabled ? "debug" : "info", + "level", isDebugEnabled ? "debug" : "info", "writerFile.file", directory.resolve("log.txt").toString(), "writerFile.charset", "UTF-8"); @@ -151,12 +166,6 @@ private static void initGlobals(PreferencesService preferences) { Globals.journalAbbreviationRepository = JournalAbbreviationLoader .loadRepository(preferences.getJournalAbbreviationPreferences()); - // Build list of Import and Export formats - Globals.IMPORT_FORMAT_READER.resetImportFormats( - preferences.getImporterPreferences(), - preferences.getImportFormatPreferences(), - Globals.getFileUpdateMonitor()); - Globals.entryTypesManager = preferences.getCustomEntryTypesRepository(); Globals.protectedTermsLoader = new ProtectedTermsLoader(preferences.getProtectedTermsPreferences()); } @@ -175,7 +184,7 @@ private static void configureSSL(SSLPreferences sslPreferences) { } private static void clearOldSearchIndices() { - Path currentIndexPath = BibDatabaseContext.getFulltextIndexBasePath(); + Path currentIndexPath = OS.getNativeDesktop().getFulltextIndexBaseDirectory(); Path appData = currentIndexPath.getParent(); try { @@ -190,9 +199,9 @@ private static void clearOldSearchIndices() { && !path.equals(currentIndexPath)) { LOGGER.info("Deleting out-of-date fulltext search index at {}.", path); Files.walk(path) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); } } } catch (IOException e) { diff --git a/src/main/java/org/jabref/gui/Base.css b/src/main/java/org/jabref/gui/Base.css index bfa2e95fc47..68f96eebf28 100644 --- a/src/main/java/org/jabref/gui/Base.css +++ b/src/main/java/org/jabref/gui/Base.css @@ -372,6 +372,10 @@ TextFlow > .tooltip-text-monospaced { -fx-padding: 0.5em 1em 0.5em 1em; } +.menu-button > .label { + -fx-padding: 0 8 0 8; +} + .button:hover { -fx-background-color: rgba(0, 0, 0, 0.12); } @@ -529,6 +533,12 @@ TextFlow > .tooltip-text-monospaced { -fx-padding: 0.0em 0.666667em 0.0em 0.666667em; } +.menu-bar .menu-button { + -fx-border-color: transparent; + -fx-border-width: 0; + -fx-border-radius: 0; +} + .menu-item { -fx-padding: 0.5em 0.41777em 0.5em 0.41777em; } @@ -622,12 +632,14 @@ TextFlow > .tooltip-text-monospaced { -fx-background-color: -jr-green; } -.table-view { +.table-view, +.tree-table-view { -fx-background-insets: 0; -fx-padding: 0; } -.table-view:focused { +.table-view:focused, +.tree-table-view:focused { -fx-background-insets: 0; } @@ -751,6 +763,9 @@ TextFlow > .tooltip-text-monospaced { -fx-background-color: -fx-control-inner-background; -fx-background-insets: 0; -fx-effect: null; + -fx-border-width: 1; + -fx-border-color: -fx-outer-border; + -fx-padding: 0; } .combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell { @@ -1086,24 +1101,26 @@ We want to have a look that matches our icons in the tool-bar */ -fx-background-color: -fx-mark-highlight-color, derive(-jr-theme-text, -30%); } -.table-view { +.table-view, +.tree-table-view { -fx-border-width: 0; -fx-padding: 0; -fx-border-insets: 0; -fx-table-cell-border-color: transparent; } -.table-view .column-header-background { +.column-header-background { -fx-background-color: -fx-control-inner-background; -fx-border-width: 0; } -.table-view .column-header-background:hover { +.column-header-background:hover { -fx-background-color: -fx-outer-border; } -.table-view .column-header, -.table-view .filler { +.column-header, +.table-view .filler, +.tree-table-view .filler { -fx-background-color: transparent, -fx-control-inner-background; -fx-background-insets: 0, 0 0.02em 0 0.02em; -fx-font-weight: bold; @@ -1112,20 +1129,22 @@ We want to have a look that matches our icons in the tool-bar */ -fx-border-color: -fx-outer-border; } -.table-view .column-header > .label { +.column-header > .label { -fx-padding: 0 1em 0 1em; -fx-alignment: center-left; -fx-text-fill: -jr-head-fg; } -.table-view .column-header .glyph-icon { +.column-header .glyph-icon { -fx-alignment: baseline-center; -fx-text-fill: -jr-head-fg; -fx-fill: -jr-head-fg; } .table-cell, -.table-cell .glyph-icon { +.tree-table-cell, +.table-cell .glyph-icon, +.tree-table-cell .glyph-icon { -fx-padding: 0.5em 1em 0.5em 1em; -fx-cell-size: 4.0em; -fx-text-fill: -fx-text-background-color; @@ -1225,10 +1244,10 @@ We want to have a look that matches our icons in the tool-bar */ -fx-background-color: -jr-accent; } -.jfx-color-picker:armed, -.jfx-color-picker:hover, -.jfx-color-picker:focused, -.jfx-color-picker { +.color-picker:armed, +.color-picker:hover, +.color-picker:focused, +.color-picker { -fx-background-color: transparent, transparent, transparent, transparent; -fx-background-radius: 0px; -fx-background-insets: 0px; @@ -1368,6 +1387,27 @@ We want to have a look that matches our icons in the tool-bar */ -fx-pref-height: 30px; -fx-padding: 0px 0px 0px -8px; -fx-margin: 0em; + -fx-border-style: none; +} + +.tags-field { + -fx-pref-height: 30px; + -fx-margin: 0em; + -fx-border-style: none; +} + +.tags-field > .flow-pane > .tag-view { + -fx-background-color: -fx-default-button; + -fx-text-fill: -fx-focused-text-base-color; +} + +.tags-field > .flow-pane > .tag-view:selected { + -fx-background-color: derive(-fx-default-button, -20%); + -fx-text-fill: -fx-focused-text-base-color; +} + +.tags-field-editor { + -fx-border-width: 0; } .searchBar:invalid { diff --git a/src/main/java/org/jabref/gui/ClipBoardManager.java b/src/main/java/org/jabref/gui/ClipBoardManager.java index b91d1be858b..8ff63a59374 100644 --- a/src/main/java/org/jabref/gui/ClipBoardManager.java +++ b/src/main/java/org/jabref/gui/ClipBoardManager.java @@ -7,7 +7,6 @@ import java.awt.datatransfer.UnsupportedFlavorException; import java.io.IOException; import java.util.List; -import java.util.Optional; import javafx.application.Platform; import javafx.scene.control.TextInputControl; @@ -21,6 +20,7 @@ import org.jabref.logic.bibtex.FieldWriter; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.preferences.PreferencesService; import org.slf4j.Logger; @@ -87,10 +87,6 @@ public static String getContents() { return result; } - public Optional getBibTeXEntriesFromClipboard() { - return Optional.ofNullable(clipboard.getContent(DragAndDropDataFormats.ENTRIES)).map(String.class::cast); - } - /** * Get the String residing on the primary clipboard (if it exists). * @@ -146,11 +142,13 @@ public void setContent(String string) { setPrimaryClipboardContent(content); } - public void setContent(List entries) throws IOException { + public void setContent(List entries, BibEntryTypesManager entryTypesManager) throws IOException { final ClipboardContent content = new ClipboardContent(); - BibEntryWriter writer = new BibEntryWriter(new FieldWriter(preferencesService.getFieldWriterPreferences()), Globals.entryTypesManager); + BibEntryWriter writer = new BibEntryWriter(new FieldWriter(preferencesService.getFieldPreferences()), entryTypesManager); String serializedEntries = writer.serializeAll(entries, BibDatabaseMode.BIBTEX); - content.put(DragAndDropDataFormats.ENTRIES, serializedEntries); + // BibEntry is not Java serializable. Thus, we need to do the serialization manually + // At reading of the clipboard in JabRef, we parse the plain string in all cases, so we don't need to flag we put BibEntries here + // Furthermore, storing a string also enables other applications to work with the data content.putString(serializedEntries); clipboard.setContent(content); setPrimaryClipboardContent(content); diff --git a/src/main/java/org/jabref/gui/DefaultInjector.java b/src/main/java/org/jabref/gui/DefaultInjector.java index dd94f84ef15..b74e0efb8ff 100644 --- a/src/main/java/org/jabref/gui/DefaultInjector.java +++ b/src/main/java/org/jabref/gui/DefaultInjector.java @@ -7,7 +7,6 @@ import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.theme.ThemeManager; import org.jabref.gui.util.TaskExecutor; -import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.protectedterms.ProtectedTermsLoader; import org.jabref.model.entry.BibEntryTypesManager; @@ -53,8 +52,6 @@ private static Object createDependency(Class clazz) { return Globals.undoManager; } else if (clazz == BibEntryTypesManager.class) { return Globals.entryTypesManager; - } else if (clazz == ImportFormatReader.class) { - return Globals.IMPORT_FORMAT_READER; } 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 9ef6f563566..f33f501ebed 100644 --- a/src/main/java/org/jabref/gui/EntryTypeView.java +++ b/src/main/java/org/jabref/gui/EntryTypeView.java @@ -23,7 +23,6 @@ import org.jabref.gui.util.TaskExecutor; import org.jabref.gui.util.ViewModelListCellFactory; import org.jabref.logic.importer.IdBasedFetcher; -import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.importer.WebFetcher; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseMode; @@ -36,6 +35,7 @@ import org.jabref.model.entry.types.IEEETranEntryTypeDefinitions; import org.jabref.model.entry.types.StandardEntryType; import org.jabref.model.strings.StringUtil; +import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.PreferencesService; import com.airhacks.afterburner.views.ViewLoader; @@ -48,9 +48,9 @@ */ public class EntryTypeView extends BaseDialog { - @Inject StateManager stateManager; - @Inject ImportFormatReader importFormatReader; - @Inject TaskExecutor taskExecutor; + @Inject private StateManager stateManager; + @Inject private TaskExecutor taskExecutor; + @Inject private FileUpdateMonitor fileUpdateMonitor; @FXML private ButtonType generateButton; @FXML private TextField idTextField; @@ -89,7 +89,7 @@ public EntryTypeView(LibraryTab libraryTab, DialogService dialogService, Prefere Button btnGenerate = (Button) this.getDialogPane().lookupButton(generateButton); - btnGenerate.textProperty().bind(EasyBind.map(viewModel.searchingProperty(), searching -> (searching) ? Localization.lang("Searching...") : Localization.lang("Generate"))); + btnGenerate.textProperty().bind(EasyBind.map(viewModel.searchingProperty(), searching -> searching ? Localization.lang("Searching...") : Localization.lang("Generate"))); btnGenerate.disableProperty().bind(viewModel.idFieldValidationStatus().validProperty().not().or(viewModel.searchingProperty())); EasyBind.subscribe(viewModel.searchSuccesfulProperty(), value -> { @@ -122,7 +122,13 @@ private void addEntriesToPane(FlowPane pane, Collection @FXML public void initialize() { visualizer.setDecoration(new IconValidationDecorator()); - viewModel = new EntryTypeViewModel(preferencesService, libraryTab, dialogService, stateManager, importFormatReader, taskExecutor); + viewModel = new EntryTypeViewModel( + preferencesService, + libraryTab, + dialogService, + stateManager, + taskExecutor, + fileUpdateMonitor); idBasedFetchers.itemsProperty().bind(viewModel.fetcherItemsProperty()); idTextField.textProperty().bindBidirectional(viewModel.idTextProperty()); @@ -213,8 +219,8 @@ private void setEntryTypeForReturnAndClose(Optional entryType) { * The description is originating from biblatex manual chapter 2 Biblatex documentation is favored over the bibtex, since bibtex is a subset of biblatex and biblatex is better documented. */ public static String getDescription(EntryType selectedType) { - if (selectedType instanceof StandardEntryType) { - switch ((StandardEntryType) selectedType) { + if (selectedType instanceof StandardEntryType entryType) { + switch (entryType) { case Article -> { return Localization.lang("An article in a journal, magazine, newspaper, or other periodical which forms a self-contained unit with its own title."); } diff --git a/src/main/java/org/jabref/gui/EntryTypeViewModel.java b/src/main/java/org/jabref/gui/EntryTypeViewModel.java index 434252bae93..892229d6b3e 100644 --- a/src/main/java/org/jabref/gui/EntryTypeViewModel.java +++ b/src/main/java/org/jabref/gui/EntryTypeViewModel.java @@ -21,13 +21,13 @@ import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.FetcherServerException; import org.jabref.logic.importer.IdBasedFetcher; -import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.importer.WebFetchers; import org.jabref.logic.importer.fetcher.DoiFetcher; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.types.StandardEntryType; import org.jabref.model.strings.StringUtil; +import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.PreferencesService; import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; @@ -53,21 +53,21 @@ public class EntryTypeViewModel { private final DialogService dialogService; private final Validator idFieldValidator; private final StateManager stateManager; - private final ImportFormatReader importFormatReader; private final TaskExecutor taskExecutor; + private final FileUpdateMonitor fileUpdateMonitor; public EntryTypeViewModel(PreferencesService preferences, LibraryTab libraryTab, DialogService dialogService, StateManager stateManager, - ImportFormatReader importFormatReader, - TaskExecutor taskExecutor) { + TaskExecutor taskExecutor, + FileUpdateMonitor fileUpdateMonitor) { this.libraryTab = libraryTab; this.preferencesService = preferences; this.dialogService = dialogService; this.stateManager = stateManager; - this.importFormatReader = importFormatReader; this.taskExecutor = taskExecutor; + this.fileUpdateMonitor = fileUpdateMonitor; fetchers.addAll(WebFetchers.getIdBasedFetchers( preferences.getImportFormatPreferences(), @@ -130,7 +130,7 @@ private class FetcherWorker extends Task> { private String searchID = ""; @Override - protected Optional call() throws InterruptedException, FetcherException { + protected Optional call() throws FetcherException { searchingProperty().setValue(true); storeSelectedFetcher(); fetcher = selectedItemProperty().getValue(); @@ -159,7 +159,7 @@ public void runFetcherWorker() { dialogService.showInformationDialogAndWait(Localization.lang("Failed to import by ID"), Localization.lang("Error message %0", fetcherExceptionMessage)); } - LOGGER.error(String.format("Exception during fetching when using fetcher '%s' with entry id '%s'.", searchId, fetcher), exception); + LOGGER.error(String.format("Exception during fetching when using fetcher '%s' with entry id '%s'.", fetcher, searchId), exception); searchingProperty.set(false); fetcherWorker = new FetcherWorker(); @@ -173,11 +173,10 @@ public void runFetcherWorker() { ImportHandler handler = new ImportHandler( libraryTab.getBibDatabaseContext(), preferencesService, - Globals.getFileUpdateMonitor(), + fileUpdateMonitor, libraryTab.getUndoManager(), stateManager, dialogService, - importFormatReader, taskExecutor); handler.importEntryWithDuplicateCheck(libraryTab.getBibDatabaseContext(), entry); diff --git a/src/main/java/org/jabref/gui/Globals.java b/src/main/java/org/jabref/gui/Globals.java index 6070283d1d1..117361bb72f 100644 --- a/src/main/java/org/jabref/gui/Globals.java +++ b/src/main/java/org/jabref/gui/Globals.java @@ -13,7 +13,6 @@ import org.jabref.gui.util.DefaultFileUpdateMonitor; import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.gui.util.TaskExecutor; -import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.protectedterms.ProtectedTermsLoader; import org.jabref.logic.remote.RemotePreferences; @@ -43,12 +42,12 @@ public class Globals { public static final BuildInfo BUILD_INFO = new BuildInfo(); public static final RemoteListenerServerManager REMOTE_LISTENER = new RemoteListenerServerManager(); + /** * Manager for the state of the GUI. */ public static StateManager stateManager = new StateManager(); - public static final ImportFormatReader IMPORT_FORMAT_READER = new ImportFormatReader(); public static final TaskExecutor TASK_EXECUTOR = new DefaultTaskExecutor(stateManager); /** @@ -71,7 +70,7 @@ public class Globals { public static ProtectedTermsLoader protectedTermsLoader; public static CountingUndoManager undoManager = new CountingUndoManager(); - public static BibEntryTypesManager entryTypesManager = new BibEntryTypesManager(); + public static BibEntryTypesManager entryTypesManager; private static ClipBoardManager clipBoardManager = null; private static KeyBindingRepository keyBindingRepository; @@ -101,24 +100,30 @@ public static synchronized ClipBoardManager getClipboardManager() { public static synchronized ThemeManager getThemeManager() { if (themeManager == null) { themeManager = new ThemeManager( - prefs.getAppearancePreferences(), + prefs.getWorkspacePreferences(), getFileUpdateMonitor(), Runnable::run); } return themeManager; } + public static synchronized FileUpdateMonitor getFileUpdateMonitor() { + if (fileUpdateMonitor == null) { + fileUpdateMonitor = new DefaultFileUpdateMonitor(); + JabRefExecutorService.INSTANCE.executeInterruptableTask(fileUpdateMonitor, "FileUpdateMonitor"); + } + return fileUpdateMonitor; + } + // Background tasks public static void startBackgroundTasks() { - Globals.fileUpdateMonitor = new DefaultFileUpdateMonitor(); - JabRefExecutorService.INSTANCE.executeInterruptableTask(Globals.fileUpdateMonitor, "FileUpdateMonitor"); // TODO Currently deactivated due to incompatibilities in XML /* if (Globals.prefs.getTelemetryPreferences().shouldCollectTelemetry() && !GraphicsEnvironment.isHeadless()) { startTelemetryClient(); } */ RemotePreferences remotePreferences = prefs.getRemotePreferences(); if (remotePreferences.useRemoteServer()) { - Globals.REMOTE_LISTENER.openAndStart(new CLIMessageHandler(prefs), remotePreferences.getPort()); + Globals.REMOTE_LISTENER.openAndStart(new CLIMessageHandler(prefs, fileUpdateMonitor, entryTypesManager), remotePreferences.getPort()); } } @@ -147,10 +152,6 @@ private static void startTelemetryClient() { telemetryClient.trackSessionState(SessionState.Start); } - public static FileUpdateMonitor getFileUpdateMonitor() { - return fileUpdateMonitor; - } - public static void shutdownThreadPools() { TASK_EXECUTOR.shutdown(); if (fileUpdateMonitor != null) { diff --git a/src/main/java/org/jabref/gui/JabRefDialogService.java b/src/main/java/org/jabref/gui/JabRefDialogService.java index 9709223cc2b..6f3c333db13 100644 --- a/src/main/java/org/jabref/gui/JabRefDialogService.java +++ b/src/main/java/org/jabref/gui/JabRefDialogService.java @@ -27,7 +27,6 @@ import javafx.scene.control.Label; import javafx.scene.control.TextInputDialog; import javafx.scene.layout.HBox; -import javafx.scene.layout.Pane; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import javafx.stage.DirectoryChooser; @@ -38,7 +37,6 @@ import org.jabref.gui.help.ErrorConsoleAction; import org.jabref.gui.icon.IconTheme; -import org.jabref.gui.theme.ThemeManager; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.DefaultTaskExecutor; @@ -73,11 +71,9 @@ public class JabRefDialogService implements DialogService { private static final Logger LOGGER = LoggerFactory.getLogger(JabRefDialogService.class); private final Window mainWindow; - private final ThemeManager themeManager; - public JabRefDialogService(Window mainWindow, Pane mainPane, ThemeManager themeManager) { + public JabRefDialogService(Window mainWindow) { this.mainWindow = mainWindow; - this.themeManager = themeManager; } private FXDialog createDialog(AlertType type, String title, String content) { @@ -364,7 +360,7 @@ public void notify(String message) { .text( "(" + Localization.lang("Check the event log to see all notifications") + ")" + "\n\n" + message) - .onAction((e)-> { + .onAction(e -> { ErrorConsoleAction ec = new ErrorConsoleAction(); ec.execute(); })) diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 24cb8f44581..88a37f98927 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -54,6 +54,8 @@ import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.actions.StandardActions; +import org.jabref.gui.autosaveandbackup.AutosaveManager; +import org.jabref.gui.autosaveandbackup.BackupManager; import org.jabref.gui.auximport.NewSubLibraryAction; import org.jabref.gui.bibtexextractor.ExtractBibtexAction; import org.jabref.gui.citationkeypattern.GenerateCitationKeyAction; @@ -118,13 +120,10 @@ 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.help.HelpFile; import org.jabref.logic.importer.IdFetcher; import org.jabref.logic.importer.ImportCleanup; -import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.WebFetchers; import org.jabref.logic.l10n.Localization; @@ -135,9 +134,12 @@ import org.jabref.logic.util.OS; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.SpecialField; import org.jabref.model.entry.types.StandardEntryType; import org.jabref.model.groups.GroupTreeNode; +import org.jabref.model.util.FileUpdateMonitor; +import org.jabref.preferences.GuiPreferences; import org.jabref.preferences.PreferencesService; import org.jabref.preferences.TelemetryPreferences; @@ -170,9 +172,10 @@ public class JabRefFrame extends BorderPane { private final Stage mainStage; private final StateManager stateManager; - private final ThemeManager themeManager; private final CountingUndoManager undoManager; private final DialogService dialogService; + private final FileUpdateMonitor fileUpdateMonitor; + private final BibEntryTypesManager entryTypesManager; private final PushToApplicationCommand pushToApplicationCommand; private SidePane sidePane; private TabPane tabbedPane; @@ -182,19 +185,18 @@ public class JabRefFrame extends BorderPane { private Subscription dividerSubscription; private final TaskExecutor taskExecutor; - private final ImportFormatReader importFormatReader; public JabRefFrame(Stage mainStage) { this.mainStage = mainStage; this.stateManager = Globals.stateManager; - this.themeManager = Globals.getThemeManager(); - this.dialogService = new JabRefDialogService(mainStage, this, themeManager); + this.dialogService = new JabRefDialogService(mainStage); this.undoManager = Globals.undoManager; + this.fileUpdateMonitor = Globals.getFileUpdateMonitor(); + this.entryTypesManager = Globals.entryTypesManager; this.globalSearchBar = new GlobalSearchBar(this, stateManager, prefs, undoManager, dialogService); this.pushToApplicationCommand = new PushToApplicationCommand(stateManager, dialogService, prefs); this.fileHistory = new FileHistoryMenu(prefs.getGuiPreferences().getFileHistory(), dialogService, getOpenDatabaseAction()); this.taskExecutor = Globals.TASK_EXECUTOR; - this.importFormatReader = Globals.IMPORT_FORMAT_READER; this.setOnKeyTyped(key -> { if (this.fileHistory.isShowing()) { if (this.fileHistory.openFileByKey(key)) { @@ -255,9 +257,9 @@ private void initDragAndDrop() { Dragboard dragboard = tabDragEvent.getDragboard(); - if (DragAndDropHelper.hasBibFiles(tabDragEvent.getDragboard())) { + if (DragAndDropHelper.hasBibFiles(dragboard)) { tabbedPane.getTabs().remove(dndIndicator); - List bibFiles = DragAndDropHelper.getBibFiles(tabDragEvent.getDragboard()); + List bibFiles = DragAndDropHelper.getBibFiles(dragboard); OpenDatabaseAction openDatabaseAction = this.getOpenDatabaseAction(); openDatabaseAction.openFiles(bibFiles); tabDragEvent.setDropCompleted(true); @@ -267,8 +269,8 @@ private void initDragAndDrop() { if (libraryTab.getId().equals(destinationTabNode.getId()) && !tabbedPane.getSelectionModel().getSelectedItem().equals(libraryTab)) { LibraryTab destinationLibraryTab = (LibraryTab) libraryTab; - if (DragAndDropHelper.hasGroups(tabDragEvent.getDragboard())) { - List groupPathToSources = DragAndDropHelper.getGroups(tabDragEvent.getDragboard()); + if (DragAndDropHelper.hasGroups(dragboard)) { + List groupPathToSources = DragAndDropHelper.getGroups(dragboard); copyRootNode(destinationLibraryTab); @@ -320,6 +322,10 @@ private void initKeyBindings() { getCurrentLibraryTab().getMainTable().requestFocus(); event.consume(); break; + case FOCUS_GROUP_LIST: + sidePane.getSidePaneComponent(SidePaneType.GROUPS).requestFocus(); + event.consume(); + break; case NEXT_LIBRARY: tabbedPane.getSelectionModel().selectNext(); event.consume(); @@ -411,7 +417,7 @@ public void openAction(String filePath) { * The MacAdapter calls this method when "About" is selected from the application menu. */ public void about() { - new HelpAction(HelpFile.CONTENTS, dialogService).execute(); + new HelpAction(HelpFile.CONTENTS, dialogService, prefs.getFilePreferences()).execute(); } /** @@ -423,7 +429,7 @@ public void about() { * set to true */ private void tearDownJabRef(List filenames) { - if (prefs.getImportExportPreferences().shouldOpenLastEdited()) { + if (prefs.getWorkspacePreferences().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()) { @@ -472,13 +478,6 @@ public boolean quit() { for (int i = 0; i < tabbedPane.getTabs().size(); i++) { LibraryTab libraryTab = getLibraryTabAt(i); final BibDatabaseContext context = libraryTab.getBibDatabaseContext(); - - if (context.hasEmptyEntries()) { - if (!confirmEmptyEntry(libraryTab, context)) { - return false; - } - } - if (libraryTab.isModified() && (context.getLocation() == DatabaseLocation.LOCAL)) { tabbedPane.getSelectionModel().select(i); if (!confirmClose(libraryTab)) { @@ -490,19 +489,49 @@ public boolean quit() { context.clearDBMSSynchronizer(); } AutosaveManager.shutdown(context); - BackupManager.shutdown(context); + BackupManager.shutdown(context, prefs.getFilePreferences().getBackupDirectory(), prefs.getFilePreferences().shouldCreateBackup()); context.getDatabasePath().map(Path::toAbsolutePath).map(Path::toString).ifPresent(filenames::add); } WaitForSaveFinishedDialog waitForSaveFinishedDialog = new WaitForSaveFinishedDialog(dialogService); waitForSaveFinishedDialog.showAndWait(getLibraryTabs()); + // We call saveWindow state here again because under Mac the windowClose listener on the stage isn't triggered when using cmd + q + saveWindowState(); // Good bye! tearDownJabRef(filenames); Platform.exit(); return true; } + public void saveWindowState() { + GuiPreferences preferences = prefs.getGuiPreferences(); + preferences.setPositionX(mainStage.getX()); + preferences.setPositionY(mainStage.getY()); + preferences.setSizeX(mainStage.getWidth()); + preferences.setSizeY(mainStage.getHeight()); + preferences.setWindowMaximised(mainStage.isMaximized()); + preferences.setWindowFullScreen(mainStage.isFullScreen()); + debugLogWindowState(mainStage); + } + + /** + * outprints the Data from the Screen (only in debug mode) + * + * @param mainStage JabRefs stage + */ + private void debugLogWindowState(Stage mainStage) { + if (LOGGER.isDebugEnabled()) { + 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); + } + } + private void initLayout() { setId("frame"); @@ -567,7 +596,7 @@ private Node createToolbar() { ToolBar toolBar = new ToolBar( new HBox( factory.createIconButton(StandardActions.NEW_LIBRARY, new NewDatabaseAction(this, prefs)), - factory.createIconButton(StandardActions.OPEN_LIBRARY, new OpenDatabaseAction(this, prefs, dialogService, stateManager, themeManager)), + factory.createIconButton(StandardActions.OPEN_LIBRARY, new OpenDatabaseAction(this, prefs, dialogService, stateManager, fileUpdateMonitor, entryTypesManager)), factory.createIconButton(StandardActions.SAVE_LIBRARY, new SaveAction(SaveAction.SaveMethod.SAVE, this, prefs, stateManager))), leftSpacer, @@ -607,7 +636,7 @@ private Node createToolbar() { new Separator(Orientation.VERTICAL), new HBox( - factory.createIconButton(StandardActions.OPEN_GITHUB, new OpenBrowserAction("https://github.com/JabRef/jabref", dialogService)))); + factory.createIconButton(StandardActions.OPEN_GITHUB, new OpenBrowserAction("https://github.com/JabRef/jabref", dialogService, prefs.getFilePreferences())))); leftSpacer.setPrefWidth(50); leftSpacer.setMinWidth(Region.USE_PREF_SIZE); @@ -648,7 +677,7 @@ public void showLibraryTab(LibraryTab libraryTab) { } public void init() { - sidePane = new SidePane(prefs, taskExecutor, dialogService, stateManager, undoManager); + sidePane = new SidePane(prefs, Globals.journalAbbreviationRepository, taskExecutor, dialogService, stateManager, undoManager); tabbedPane = new TabPane(); tabbedPane.setTabDragPolicy(TabPane.TabDragPolicy.REORDER); @@ -658,7 +687,7 @@ public void init() { // Bind global state FilteredList filteredTabs = new FilteredList<>(tabbedPane.getTabs()); - filteredTabs.setPredicate(tab -> tab instanceof LibraryTab); + filteredTabs.setPredicate(LibraryTab.class::isInstance); // This variable cannot be inlined, since otherwise the list created by EasyBind is being garbage collected openDatabaseList = EasyBind.map(filteredTabs, tab -> ((LibraryTab) tab).getBibDatabaseContext()); @@ -775,12 +804,13 @@ private MenuBar createMenu() { factory.createMenuItem(StandardActions.SAVE_LIBRARY, new SaveAction(SaveAction.SaveMethod.SAVE, this, prefs, stateManager)), factory.createMenuItem(StandardActions.SAVE_LIBRARY_AS, new SaveAction(SaveAction.SaveMethod.SAVE_AS, this, prefs, stateManager)), factory.createMenuItem(StandardActions.SAVE_ALL, new SaveAllAction(this, prefs)), + factory.createMenuItem(StandardActions.CLOSE_LIBRARY, new CloseDatabaseAction()), new SeparatorMenuItem(), factory.createSubMenu(StandardActions.IMPORT, - 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.createMenuItem(StandardActions.IMPORT_INTO_CURRENT_LIBRARY, new ImportCommand(this, false, prefs, stateManager, fileUpdateMonitor)), + factory.createMenuItem(StandardActions.IMPORT_INTO_NEW_LIBRARY, new ImportCommand(this, true, prefs, stateManager, fileUpdateMonitor))), factory.createSubMenu(StandardActions.EXPORT, factory.createMenuItem(StandardActions.EXPORT_ALL, new ExportCommand(ExportCommand.ExportMethod.EXPORT_ALL, this, stateManager, dialogService, prefs)), @@ -799,7 +829,6 @@ private MenuBar createMenu() { new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.CLOSE_LIBRARY, new CloseDatabaseAction()), factory.createMenuItem(StandardActions.QUIT, new CloseAction()) ); @@ -813,12 +842,12 @@ 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.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(), Globals.TASK_EXECUTOR, prefs.getPreviewPreferences())), + factory.createMenuItem(StandardActions.COPY_TITLE, new CopyMoreAction(StandardActions.COPY_TITLE, dialogService, stateManager, Globals.getClipboardManager(), prefs, Globals.journalAbbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_KEY, new CopyMoreAction(StandardActions.COPY_KEY, dialogService, stateManager, Globals.getClipboardManager(), prefs, Globals.journalAbbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_CITE_KEY, new CopyMoreAction(StandardActions.COPY_CITE_KEY, dialogService, stateManager, Globals.getClipboardManager(), prefs, Globals.journalAbbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_KEY_AND_TITLE, new CopyMoreAction(StandardActions.COPY_KEY_AND_TITLE, dialogService, stateManager, Globals.getClipboardManager(), prefs, Globals.journalAbbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_KEY_AND_LINK, new CopyMoreAction(StandardActions.COPY_KEY_AND_LINK, dialogService, stateManager, Globals.getClipboardManager(), prefs, Globals.journalAbbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_CITATION_PREVIEW, new CopyCitationAction(CitationStyleOutputFormat.HTML, dialogService, stateManager, Globals.getClipboardManager(), Globals.TASK_EXECUTOR, prefs, Globals.journalAbbreviationRepository)), factory.createMenuItem(StandardActions.EXPORT_SELECTED_TO_CLIPBOARD, new ExportToClipboardAction(dialogService, stateManager, Globals.getClipboardManager(), Globals.TASK_EXECUTOR, prefs))), factory.createMenuItem(StandardActions.PASTE, new EditAction(StandardActions.PASTE, this, stateManager)), @@ -866,8 +895,8 @@ private MenuBar createMenu() { quality.getItems().addAll( factory.createMenuItem(StandardActions.FIND_DUPLICATES, new DuplicateSearch(this, dialogService, stateManager, prefs)), - factory.createMenuItem(StandardActions.MERGE_ENTRIES, new MergeEntriesAction(dialogService, stateManager, prefs.getBibEntryPreferences())), - factory.createMenuItem(StandardActions.CHECK_INTEGRITY, new IntegrityCheckAction(this, stateManager, Globals.TASK_EXECUTOR)), + factory.createMenuItem(StandardActions.MERGE_ENTRIES, new MergeEntriesAction(dialogService, stateManager, prefs)), + factory.createMenuItem(StandardActions.CHECK_INTEGRITY, new IntegrityCheckAction(this, prefs, dialogService, stateManager, Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)), factory.createMenuItem(StandardActions.CLEANUP_ENTRIES, new CleanupAction(this, this.prefs, dialogService, stateManager)), new SeparatorMenuItem(), @@ -908,20 +937,20 @@ private MenuBar createMenu() { new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.WRITE_METADATA_TO_PDF, new WriteMetadataToPdfAction(stateManager, Globals.entryTypesManager, prefs.getFieldWriterPreferences(), dialogService, taskExecutor, prefs.getFilePreferences(), prefs.getXmpPreferences())), + factory.createMenuItem(StandardActions.WRITE_METADATA_TO_PDF, new WriteMetadataToPdfAction(stateManager, Globals.entryTypesManager, prefs.getFieldPreferences(), dialogService, taskExecutor, prefs.getFilePreferences(), prefs.getXmpPreferences(), Globals.journalAbbreviationRepository)), factory.createMenuItem(StandardActions.COPY_LINKED_FILES, new CopyFilesAction(dialogService, prefs, stateManager)), new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.SEND_AS_EMAIL, new SendAsEMailAction(dialogService, this.prefs, stateManager)), + createSendSubMenu(factory, dialogService, stateManager, prefs), pushToApplicationMenuItem, new SeparatorMenuItem(), // Systematic Literature Review (SLR) - factory.createMenuItem(StandardActions.START_NEW_STUDY, new StartNewStudyAction(this, Globals.getFileUpdateMonitor(), Globals.TASK_EXECUTOR, prefs, stateManager, themeManager)), + factory.createMenuItem(StandardActions.START_NEW_STUDY, new StartNewStudyAction(this, fileUpdateMonitor, taskExecutor, prefs, stateManager)), factory.createMenuItem(StandardActions.EDIT_EXISTING_STUDY, new EditExistingStudyAction(this.dialogService, this.stateManager)), - factory.createMenuItem(StandardActions.UPDATE_SEARCH_RESULTS_OF_STUDY, new ExistingStudySearchAction(this, this.getOpenDatabaseAction(), this.getDialogService(), Globals.getFileUpdateMonitor(), Globals.TASK_EXECUTOR, prefs, stateManager, themeManager)), + factory.createMenuItem(StandardActions.UPDATE_SEARCH_RESULTS_OF_STUDY, new ExistingStudySearchAction(this, this.getOpenDatabaseAction(), this.getDialogService(), fileUpdateMonitor, Globals.TASK_EXECUTOR, prefs, stateManager)), new SeparatorMenuItem(), @@ -948,8 +977,8 @@ private MenuBar createMenu() { ); help.getItems().addAll( - factory.createMenuItem(StandardActions.HELP, new HelpAction(HelpFile.CONTENTS, dialogService)), - factory.createMenuItem(StandardActions.OPEN_FORUM, new OpenBrowserAction("http://discourse.jabref.org/", dialogService)), + factory.createMenuItem(StandardActions.HELP, new HelpAction(HelpFile.CONTENTS, dialogService, prefs.getFilePreferences())), + factory.createMenuItem(StandardActions.OPEN_FORUM, new OpenBrowserAction("http://discourse.jabref.org/", dialogService, prefs.getFilePreferences())), new SeparatorMenuItem(), @@ -957,19 +986,19 @@ private MenuBar createMenu() { new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.DONATE, new OpenBrowserAction("https://donations.jabref.org", dialogService)), - factory.createMenuItem(StandardActions.SEARCH_FOR_UPDATES, new SearchForUpdateAction(Globals.BUILD_INFO, prefs.getInternalPreferences(), dialogService, Globals.TASK_EXECUTOR)), + factory.createMenuItem(StandardActions.DONATE, new OpenBrowserAction("https://donations.jabref.org", dialogService, prefs.getFilePreferences())), + factory.createMenuItem(StandardActions.SEARCH_FOR_UPDATES, new SearchForUpdateAction(Globals.BUILD_INFO, prefs, dialogService, Globals.TASK_EXECUTOR)), factory.createSubMenu(StandardActions.WEB_MENU, - factory.createMenuItem(StandardActions.OPEN_WEBPAGE, new OpenBrowserAction("https://jabref.org/", dialogService)), - factory.createMenuItem(StandardActions.OPEN_BLOG, new OpenBrowserAction("https://blog.jabref.org/", dialogService)), - factory.createMenuItem(StandardActions.OPEN_FACEBOOK, new OpenBrowserAction("https://www.facebook.com/JabRef/", dialogService)), - factory.createMenuItem(StandardActions.OPEN_TWITTER, new OpenBrowserAction("https://twitter.com/jabref_org", dialogService)), - factory.createMenuItem(StandardActions.OPEN_GITHUB, new OpenBrowserAction("https://github.com/JabRef/jabref", dialogService)), + factory.createMenuItem(StandardActions.OPEN_WEBPAGE, new OpenBrowserAction("https://jabref.org/", dialogService, prefs.getFilePreferences())), + factory.createMenuItem(StandardActions.OPEN_BLOG, new OpenBrowserAction("https://blog.jabref.org/", dialogService, prefs.getFilePreferences())), + factory.createMenuItem(StandardActions.OPEN_FACEBOOK, new OpenBrowserAction("https://www.facebook.com/JabRef/", dialogService, prefs.getFilePreferences())), + factory.createMenuItem(StandardActions.OPEN_TWITTER, new OpenBrowserAction("https://twitter.com/jabref_org", dialogService, prefs.getFilePreferences())), + factory.createMenuItem(StandardActions.OPEN_GITHUB, new OpenBrowserAction("https://github.com/JabRef/jabref", dialogService, prefs.getFilePreferences())), new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.OPEN_DEV_VERSION_LINK, new OpenBrowserAction("https://builds.jabref.org/master/", dialogService)), - factory.createMenuItem(StandardActions.OPEN_CHANGELOG, new OpenBrowserAction("https://github.com/JabRef/jabref/blob/main/CHANGELOG.md", dialogService)) + factory.createMenuItem(StandardActions.OPEN_DEV_VERSION_LINK, new OpenBrowserAction("https://builds.jabref.org/master/", dialogService, prefs.getFilePreferences())), + factory.createMenuItem(StandardActions.OPEN_CHANGELOG, new OpenBrowserAction("https://github.com/JabRef/jabref/blob/main/CHANGELOG.md", dialogService, prefs.getFilePreferences())) ), factory.createMenuItem(StandardActions.ABOUT, new AboutAction()) ); @@ -1157,12 +1186,12 @@ public void addTab(LibraryTab libraryTab, boolean raisePanel) { /** * Opens a new tab with existing data. - * Asynchronous loading is done at {@link #createLibraryTab(BackgroundTask, Path, PreferencesService, StateManager, JabRefFrame, ThemeManager)}. + * Asynchronous loading is done at {@link org.jabref.gui.LibraryTab#createLibraryTab(BackgroundTask, Path, PreferencesService, StateManager, JabRefFrame, ThemeManager)}. */ public LibraryTab addTab(BibDatabaseContext databaseContext, boolean raisePanel) { Objects.requireNonNull(databaseContext); - LibraryTab libraryTab = new LibraryTab(databaseContext, this, prefs, stateManager, themeManager); + LibraryTab libraryTab = new LibraryTab(databaseContext, this, prefs, stateManager, fileUpdateMonitor, entryTypesManager); addTab(libraryTab, raisePanel); return libraryTab; } @@ -1235,55 +1264,13 @@ private boolean confirmClose(LibraryTab libraryTab) { } if (buttonType.equals(discardChanges)) { - BackupManager.discardBackup(libraryTab.getBibDatabaseContext()); + BackupManager.discardBackup(libraryTab.getBibDatabaseContext(), prefs.getFilePreferences().getBackupDirectory()); return true; } return false; } - /** - * Ask if the user really wants to remove any empty entries - */ - private Boolean confirmEmptyEntry(LibraryTab libraryTab, BibDatabaseContext context) { - String filename = libraryTab.getBibDatabaseContext() - .getDatabasePath() - .map(Path::toAbsolutePath) - .map(Path::toString) - .orElse(Localization.lang("untitled")); - - ButtonType deleteEmptyEntries = new ButtonType(Localization.lang("Delete empty entries"), ButtonBar.ButtonData.YES); - ButtonType keepEmptyEntries = new ButtonType(Localization.lang("Keep empty entries"), ButtonBar.ButtonData.NO); - ButtonType cancel = new ButtonType(Localization.lang("Return to library"), ButtonBar.ButtonData.CANCEL_CLOSE); - - Optional response = dialogService.showCustomButtonDialogAndWait(Alert.AlertType.CONFIRMATION, - Localization.lang("Empty entries"), - Localization.lang("Library '%0' has empty entries. Do you want to delete them?", filename), - deleteEmptyEntries, keepEmptyEntries, cancel); - if (response.isPresent() && response.get().equals(deleteEmptyEntries)) { - // The user wants to delete. - try { - for (BibEntry currentEntry : new ArrayList<>(context.getEntries())) { - if (currentEntry.getFields().isEmpty()) { - context.getDatabase().removeEntries(Collections.singletonList(currentEntry)); - } - } - SaveDatabaseAction saveAction = new SaveDatabaseAction(libraryTab, prefs, Globals.entryTypesManager); - if (saveAction.save()) { - return true; - } - // The action was either canceled or unsuccessful. - dialogService.notify(Localization.lang("Unable to save library")); - } catch (Throwable ex) { - LOGGER.error("A problem occurred when trying to delete the empty entries", ex); - dialogService.showErrorDialogAndWait(Localization.lang("Delete empty entries"), Localization.lang("Could not delete empty entries."), ex); - } - // Save was cancelled or an error occurred. - return false; - } - return !response.get().equals(cancel); - } - private void closeTab(LibraryTab libraryTab) { // empty tab without database if (libraryTab == null) { @@ -1291,11 +1278,6 @@ private void closeTab(LibraryTab libraryTab) { } final BibDatabaseContext context = libraryTab.getBibDatabaseContext(); - if (context.hasEmptyEntries()) { - if (!confirmEmptyEntry(libraryTab, context)) { - return; - } - } if (libraryTab.isModified() && (context.getLocation() == DatabaseLocation.LOCAL)) { if (confirmClose(libraryTab)) { @@ -1312,7 +1294,7 @@ private void closeTab(LibraryTab libraryTab) { removeTab(libraryTab); } AutosaveManager.shutdown(context); - BackupManager.shutdown(context); + BackupManager.shutdown(context, prefs.getFilePreferences().getBackupDirectory(), prefs.getFilePreferences().shouldCreateBackup()); } private void removeTab(LibraryTab libraryTab) { @@ -1327,7 +1309,7 @@ public void closeCurrentTab() { } public OpenDatabaseAction getOpenDatabaseAction() { - return new OpenDatabaseAction(this, prefs, dialogService, stateManager, themeManager); + return new OpenDatabaseAction(this, prefs, dialogService, stateManager, fileUpdateMonitor, entryTypesManager); } public GlobalSearchBar getGlobalSearchBar() { @@ -1378,6 +1360,19 @@ private void copyRootNode(LibraryTab destinationLibraryTab) { .setGroups(currentLibraryGroupRoot); } + private Menu createSendSubMenu(ActionFactory factory, + DialogService dialogService, + StateManager stateManager, + PreferencesService preferencesService) { + Menu sendMenu = factory.createMenu(StandardActions.SEND); + sendMenu.getItems().addAll( + factory.createMenuItem(StandardActions.SEND_AS_EMAIL, new SendAsStandardEmailAction(dialogService, preferencesService, stateManager, entryTypesManager)), + factory.createMenuItem(StandardActions.SEND_TO_KINDLE, new SendAsKindleEmailAction(dialogService, preferencesService, stateManager)) + ); + + return sendMenu; + } + /** * The action concerned with closing the window. */ @@ -1453,7 +1448,7 @@ public OpenDatabaseFolder(Supplier databaseContext) { public void execute() { Optional.of(databaseContext.get()).flatMap(BibDatabaseContext::getDatabasePath).ifPresent(path -> { try { - JabRefDesktop.openFolderAndSelectFile(path, prefs, dialogService); + JabRefDesktop.openFolderAndSelectFile(path, prefs.getExternalApplicationsPreferences(), dialogService); } catch (IOException e) { LOGGER.info("Could not open folder", e); } diff --git a/src/main/java/org/jabref/gui/JabRefGUI.java b/src/main/java/org/jabref/gui/JabRefGUI.java index 6f230e9eebf..f0497c11c28 100644 --- a/src/main/java/org/jabref/gui/JabRefGUI.java +++ b/src/main/java/org/jabref/gui/JabRefGUI.java @@ -4,6 +4,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import javafx.application.Platform; @@ -25,10 +26,13 @@ import org.jabref.logic.shared.exception.InvalidDBMSConnectionPropertiesException; import org.jabref.logic.shared.exception.NotASharedDatabaseException; import org.jabref.logic.util.WebViewStore; +import org.jabref.model.strings.StringUtil; +import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.GuiPreferences; import org.jabref.preferences.PreferencesService; import com.airhacks.afterburner.injection.Injector; +import com.tobiasdiez.easybind.EasyBind; import impl.org.controlsfx.skin.DecorationPane; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,6 +43,7 @@ public class JabRefGUI { private static JabRefFrame mainFrame; private final PreferencesService preferencesService; + private final FileUpdateMonitor fileUpdateMonitor; private final List bibDatabases; private final boolean isBlank; @@ -46,10 +51,15 @@ public class JabRefGUI { private final List failed = new ArrayList<>(); private final List toOpenTab = new ArrayList<>(); - public JabRefGUI(Stage mainStage, List databases, boolean isBlank, PreferencesService preferencesService) { + public JabRefGUI(Stage mainStage, + List databases, + boolean isBlank, + PreferencesService preferencesService, + FileUpdateMonitor fileUpdateMonitor) { this.bibDatabases = databases; this.isBlank = isBlank; this.preferencesService = preferencesService; + this.fileUpdateMonitor = fileUpdateMonitor; this.correctedWindowPos = false; WebViewStore.init(); @@ -58,19 +68,42 @@ public JabRefGUI(Stage mainStage, List databases, boolean isBlank, openWindow(mainStage); - new VersionWorker(Globals.BUILD_INFO.version, - mainFrame.getDialogService(), - Globals.TASK_EXECUTOR, - preferencesService.getInternalPreferences()) - .checkForNewVersionDelayed(); - - if (preferencesService.getProxyPreferences().shouldUseProxy() && preferencesService.getProxyPreferences().shouldUseAuthentication()) { - DialogService dialogService = Injector.instantiateModelOrService(DialogService.class); - dialogService.showPasswordDialogAndWait(Localization.lang("Proxy configuration"), Localization.lang("Proxy requires password"), Localization.lang("Password")) - .ifPresent(newPassword -> { - preferencesService.getProxyPreferences().setPassword(newPassword); - ProxyRegisterer.register(preferencesService.getProxyPreferences()); - }); + EasyBind.subscribe(preferencesService.getInternalPreferences().versionCheckEnabledProperty(), enabled -> { + if (enabled) { + new VersionWorker(Globals.BUILD_INFO.version, + mainFrame.getDialogService(), + Globals.TASK_EXECUTOR, + preferencesService) + .checkForNewVersionDelayed(); + } + }); + + setupProxy(); + } + + private void setupProxy() { + if (!preferencesService.getProxyPreferences().shouldUseProxy() + || !preferencesService.getProxyPreferences().shouldUseAuthentication()) { + return; + } + + if (preferencesService.getProxyPreferences().shouldPersistPassword() + && StringUtil.isNotBlank(preferencesService.getProxyPreferences().getPassword())) { + ProxyRegisterer.register(preferencesService.getProxyPreferences()); + return; + } + + DialogService dialogService = Injector.instantiateModelOrService(DialogService.class); + Optional password = dialogService.showPasswordDialogAndWait( + Localization.lang("Proxy configuration"), + Localization.lang("Proxy requires password"), + Localization.lang("Password")); + + if (password.isPresent()) { + preferencesService.getProxyPreferences().setPassword(password.get()); + ProxyRegisterer.register(preferencesService.getProxyPreferences()); + } else { + LOGGER.warn("No proxy password specified"); } } @@ -84,10 +117,14 @@ private void openWindow(Stage mainStage) { mainStage.setMinHeight(330); mainStage.setMinWidth(580); + if (guiPreferences.isWindowFullscreen()) { + mainStage.setFullScreen(true); + } // Restore window location and/or maximised state if (guiPreferences.isWindowMaximised()) { mainStage.setMaximized(true); - } else if ((Screen.getScreens().size() == 1) && isWindowPositionOutOfBounds()) { + } + if ((Screen.getScreens().size() == 1) && isWindowPositionOutOfBounds()) { // corrects the Window, if it is outside the mainscreen LOGGER.debug("The Jabref window is outside the main screen"); mainStage.setX(0); @@ -123,7 +160,7 @@ private void openWindow(Stage mainStage) { if (!correctedWindowPos) { // saves the window position only if its not corrected -> the window will rest at the old Position, // if the external Screen is connected again. - saveWindowState(mainStage); + getMainFrame().saveWindowState(); } boolean reallyQuit = mainFrame.quit(); if (!reallyQuit) { @@ -132,7 +169,7 @@ private void openWindow(Stage mainStage) { }); Platform.runLater(this::openDatabases); - if (!(Globals.getFileUpdateMonitor().isActive())) { + if (!(fileUpdateMonitor.isActive())) { getMainFrame().getDialogService() .showErrorDialogAndWait(Localization.lang("Unable to monitor file changes. Please close files " + "and processes and restart. You may encounter errors if you continue " + @@ -142,7 +179,7 @@ private void openWindow(Stage mainStage) { private void openDatabases() { // If the option is enabled, open the last edited libraries, if any. - if (!isBlank && preferencesService.getImportExportPreferences().shouldOpenLastEdited()) { + if (!isBlank && preferencesService.getWorkspacePreferences().shouldOpenLastEdited()) { openLastEditedDatabases(); } @@ -173,7 +210,8 @@ private void openDatabases() { if (pr.getDatabase().isShared()) { try { - new SharedDatabaseUIManager(mainFrame, preferencesService).openSharedDatabaseFromParserResult(pr); + new SharedDatabaseUIManager(mainFrame, preferencesService, fileUpdateMonitor) + .openSharedDatabaseFromParserResult(pr); } catch (SQLException | DatabaseNotSupportedException | InvalidDBMSConnectionPropertiesException | NotASharedDatabaseException e) { pr.getDatabaseContext().clearDatabasePath(); // do not open the original file @@ -242,6 +280,7 @@ private void saveWindowState(Stage mainStage) { preferences.setSizeX(mainStage.getWidth()); preferences.setSizeY(mainStage.getHeight()); preferences.setWindowMaximised(mainStage.isMaximized()); + preferences.setWindowFullScreen(mainStage.isFullScreen()); debugLogWindowState(mainStage); } @@ -279,7 +318,7 @@ private void openLastEditedDatabases() { return; } - List filesToOpen = lastFiles.stream().map(file -> Path.of(file)).collect(Collectors.toList()); + List filesToOpen = lastFiles.stream().map(Path::of).collect(Collectors.toList()); getMainFrame().getOpenDatabaseAction().openFiles(filesToOpen); } diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index 6edf841e0df..fa02d937aa5 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -26,13 +26,14 @@ import org.jabref.gui.autocompleter.AutoCompletePreferences; import org.jabref.gui.autocompleter.PersonNameSuggestionProvider; import org.jabref.gui.autocompleter.SuggestionProviders; +import org.jabref.gui.autosaveandbackup.AutosaveManager; +import org.jabref.gui.autosaveandbackup.BackupManager; import org.jabref.gui.collab.DatabaseChangeMonitor; import org.jabref.gui.dialogs.AutosaveUiManager; import org.jabref.gui.entryeditor.EntryEditor; import org.jabref.gui.importer.actions.OpenDatabaseAction; import org.jabref.gui.maintable.MainTable; import org.jabref.gui.maintable.MainTableDataModel; -import org.jabref.gui.theme.ThemeManager; import org.jabref.gui.undo.CountingUndoManager; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableFieldChange; @@ -40,8 +41,6 @@ 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.importer.util.FileFieldParser; @@ -60,6 +59,7 @@ import org.jabref.model.database.event.EntriesAddedEvent; import org.jabref.model.database.event.EntriesRemovedEvent; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.event.EntriesEventSource; import org.jabref.model.entry.event.EntryChangedEvent; @@ -67,6 +67,7 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.StandardField; +import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.PreferencesService; import com.google.common.eventbus.Subscribe; @@ -84,8 +85,9 @@ public class LibraryTab extends Tab { private final CountingUndoManager undoManager; private final DialogService dialogService; private final PreferencesService preferencesService; + private final FileUpdateMonitor fileUpdateMonitor; private final StateManager stateManager; - private final ThemeManager themeManager; + private final BibEntryTypesManager entryTypesManager; private final BooleanProperty changedProperty = new SimpleBooleanProperty(false); private final BooleanProperty nonUndoableChangeProperty = new SimpleBooleanProperty(false); @@ -122,14 +124,16 @@ public LibraryTab(BibDatabaseContext bibDatabaseContext, JabRefFrame frame, PreferencesService preferencesService, StateManager stateManager, - ThemeManager themeManager) { + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager) { this.frame = Objects.requireNonNull(frame); this.bibDatabaseContext = Objects.requireNonNull(bibDatabaseContext); this.undoManager = frame.getUndoManager(); this.dialogService = frame.getDialogService(); this.preferencesService = Objects.requireNonNull(preferencesService); this.stateManager = Objects.requireNonNull(stateManager); - this.themeManager = Objects.requireNonNull(themeManager); + this.fileUpdateMonitor = fileUpdateMonitor; + this.entryTypesManager = entryTypesManager; bibDatabaseContext.getDatabase().registerListener(this); bibDatabaseContext.getMetaData().registerListener(this); @@ -287,17 +291,17 @@ public void feedData(BibDatabaseContext bibDatabaseContextFromParserResult) { public void installAutosaveManagerAndBackupManager() { if (isDatabaseReadyForAutoSave(bibDatabaseContext)) { AutosaveManager autosaveManager = AutosaveManager.start(bibDatabaseContext); - autosaveManager.registerListener(new AutosaveUiManager(this)); + autosaveManager.registerListener(new AutosaveUiManager(this, preferencesService, entryTypesManager)); } - if (isDatabaseReadyForBackup(bibDatabaseContext)) { - BackupManager.start(bibDatabaseContext, Globals.entryTypesManager, preferencesService); + if (isDatabaseReadyForBackup(bibDatabaseContext) && preferencesService.getFilePreferences().shouldCreateBackup()) { + BackupManager.start(this, bibDatabaseContext, Globals.entryTypesManager, preferencesService); } } private boolean isDatabaseReadyForAutoSave(BibDatabaseContext context) { return ((context.getLocation() == DatabaseLocation.SHARED) || ((context.getLocation() == DatabaseLocation.LOCAL) - && preferencesService.getImportExportPreferences().shouldAutoSave())) + && preferencesService.getLibraryPreferences().shouldAutoSave())) && context.getDatabasePath().isPresent(); } @@ -313,7 +317,7 @@ private boolean isDatabaseReadyForBackup(BibDatabaseContext context) { * Example: *jabref-authors.bib – testbib */ public void updateTabTitle(boolean isChanged) { - boolean isAutosaveEnabled = preferencesService.getImportExportPreferences().shouldAutoSave(); + boolean isAutosaveEnabled = preferencesService.getLibraryPreferences().shouldAutoSave(); DatabaseLocation databaseLocation = bibDatabaseContext.getLocation(); Optional file = bibDatabaseContext.getDatabasePath(); @@ -469,13 +473,9 @@ public void insertEntries(final List entries) { bibDatabaseContext.getDatabase().insertEntries(entries); // Set owner and timestamp - for (BibEntry entry : entries) { - UpdateField.setAutomaticFields(entry, - true, - true, - preferencesService.getOwnerPreferences(), - preferencesService.getTimestampPreferences()); - } + UpdateField.setAutomaticFields(entries, + preferencesService.getOwnerPreferences(), + preferencesService.getTimestampPreferences()); // Create an UndoableInsertEntries object. getUndoManager().addEdit(new UndoableInsertEntries(bibDatabaseContext.getDatabase(), entries)); @@ -505,8 +505,9 @@ private void createMainTable() { stateManager, Globals.getKeyPrefs(), Globals.getClipboardManager(), - Globals.IMPORT_FORMAT_READER, - Globals.TASK_EXECUTOR); + entryTypesManager, + Globals.TASK_EXECUTOR, + fileUpdateMonitor); // 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 -> stateManager.setSelectedEntries(mainTable.getSelectedEntries())); @@ -578,6 +579,7 @@ public EntryEditor getEntryEditor() { public void showAndEdit(BibEntry entry) { showBottomPane(BasePanelMode.SHOWING_EDITOR); + // We use != instead of equals because of performance reasons if (entry != getShowing()) { entryEditor.setEntry(entry); showing = entry; @@ -667,7 +669,7 @@ public BibDatabase getDatabase() { } private boolean showDeleteConfirmationDialog(int numberOfEntries) { - if (preferencesService.getGeneralPreferences().shouldConfirmDelete()) { + if (preferencesService.getWorkspacePreferences().shouldConfirmDelete()) { String title = Localization.lang("Delete entry"); String message = Localization.lang("Really delete the selected entry?"); String okButton = Localization.lang("Delete entry"); @@ -684,7 +686,7 @@ private boolean showDeleteConfirmationDialog(int numberOfEntries) { okButton, cancelButton, Localization.lang("Do not ask again"), - optOut -> preferencesService.getGeneralPreferences().setConfirmDelete(!optOut)); + optOut -> preferencesService.getWorkspacePreferences().setConfirmDelete(!optOut)); } else { return true; } @@ -705,7 +707,7 @@ private void saveDividerLocation(Number position) { public void cleanUp() { changeMonitor.ifPresent(DatabaseChangeMonitor::unregister); AutosaveManager.shutdown(bibDatabaseContext); - BackupManager.shutdown(bibDatabaseContext); + BackupManager.shutdown(bibDatabaseContext, preferencesService.getFilePreferences().getBackupDirectory(), preferencesService.getFilePreferences().shouldCreateBackup()); } /** @@ -767,7 +769,7 @@ public FileAnnotationCache getAnnotationCache() { public void resetChangeMonitor() { changeMonitor.ifPresent(DatabaseChangeMonitor::unregister); changeMonitor = Optional.of(new DatabaseChangeMonitor(bibDatabaseContext, - Globals.getFileUpdateMonitor(), + fileUpdateMonitor, Globals.TASK_EXECUTOR, dialogService, preferencesService, @@ -819,14 +821,20 @@ public void resetChangedProperties() { /** * Creates a new library tab. Contents are loaded by the {@code dataLoadingTask}. Most of the other parameters are required by {@code resetChangeMonitor()}. * - * @param dataLoadingTask The task to execute to load the data. It is executed using {@link Globals.TASK_EXECUTOR}. + * @param dataLoadingTask The task to execute to load the data. It is executed using {@link org.jabref.gui.Globals.TASK_EXECUTOR}. * @param file the path to the file (loaded by the dataLoadingTask) */ - public static LibraryTab createLibraryTab(BackgroundTask dataLoadingTask, Path file, PreferencesService preferencesService, StateManager stateManager, JabRefFrame frame, ThemeManager themeManager) { + public static LibraryTab createLibraryTab(BackgroundTask dataLoadingTask, + Path file, + PreferencesService preferencesService, + StateManager stateManager, + JabRefFrame frame, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager) { BibDatabaseContext context = new BibDatabaseContext(); context.setDatabasePath(file); - LibraryTab newTab = new LibraryTab(context, frame, preferencesService, stateManager, themeManager); + LibraryTab newTab = new LibraryTab(context, frame, preferencesService, stateManager, fileUpdateMonitor, entryTypesManager); newTab.setDataLoadingTask(dataLoadingTask); dataLoadingTask.onRunning(newTab::onDatabaseLoadingStarted) diff --git a/src/main/java/org/jabref/gui/MainApplication.java b/src/main/java/org/jabref/gui/MainApplication.java index 6e286042d42..a187843977d 100644 --- a/src/main/java/org/jabref/gui/MainApplication.java +++ b/src/main/java/org/jabref/gui/MainApplication.java @@ -7,6 +7,7 @@ import org.jabref.gui.openoffice.OOBibBaseConnect; import org.jabref.logic.importer.ParserResult; +import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.JabRefPreferences; /** @@ -16,11 +17,17 @@ public class MainApplication extends Application { private static List parserResults; private static boolean isBlank; private static JabRefPreferences preferences; + private static FileUpdateMonitor fileUpdateMonitor; - public static void main(List parserResults, boolean blank, JabRefPreferences preferences, String[] args) { + public static void main(List parserResults, + boolean blank, + JabRefPreferences preferences, + FileUpdateMonitor fileUpdateMonitor, + String[] args) { MainApplication.parserResults = parserResults; MainApplication.isBlank = blank; MainApplication.preferences = preferences; + MainApplication.fileUpdateMonitor = fileUpdateMonitor; launch(args); } @@ -28,7 +35,7 @@ public static void main(List parserResults, boolean blank, JabRefP public void start(Stage mainStage) { FallbackExceptionHandler.installExceptionHandler(); Globals.startBackgroundTasks(); - new JabRefGUI(mainStage, parserResults, isBlank, preferences); + new JabRefGUI(mainStage, parserResults, isBlank, preferences, fileUpdateMonitor); } @Override diff --git a/src/main/java/org/jabref/gui/OpenConsoleAction.java b/src/main/java/org/jabref/gui/OpenConsoleAction.java index b470e1e3ef0..acada1d5629 100644 --- a/src/main/java/org/jabref/gui/OpenConsoleAction.java +++ b/src/main/java/org/jabref/gui/OpenConsoleAction.java @@ -47,7 +47,7 @@ public OpenConsoleAction(StateManager stateManager, PreferencesService preferenc public void execute() { Optional.ofNullable(databaseContext.get()).or(stateManager::getActiveDatabase).flatMap(BibDatabaseContext::getDatabasePath).ifPresent(path -> { try { - JabRefDesktop.openConsole(path.toFile(), preferencesService, dialogService); + JabRefDesktop.openConsole(path, preferencesService, dialogService); } catch (IOException e) { LOGGER.info("Could not open console", e); } diff --git a/src/main/java/org/jabref/gui/SendAsEMailAction.java b/src/main/java/org/jabref/gui/SendAsEMailAction.java index 0caaa5afc3b..f8a9bba6ad6 100644 --- a/src/main/java/org/jabref/gui/SendAsEMailAction.java +++ b/src/main/java/org/jabref/gui/SendAsEMailAction.java @@ -2,22 +2,17 @@ import java.awt.Desktop; import java.io.IOException; -import java.io.StringWriter; import java.net.URI; +import java.net.URISyntaxException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import org.jabref.architecture.AllowedToUseAwt; -import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.desktop.JabRefDesktop; import org.jabref.gui.util.BackgroundTask; -import org.jabref.logic.bibtex.BibEntryWriter; -import org.jabref.logic.bibtex.FieldWriter; -import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.l10n.Localization; -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; @@ -37,7 +32,7 @@ * preferences/external programs */ @AllowedToUseAwt("Requires AWT to send an email") -public class SendAsEMailAction extends SimpleCommand { +public abstract class SendAsEMailAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(SendAsEMailAction.class); private final DialogService dialogService; @@ -48,8 +43,6 @@ public SendAsEMailAction(DialogService dialogService, PreferencesService prefere this.dialogService = dialogService; this.preferencesService = preferencesService; this.stateManager = stateManager; - - this.executable.bind(ActionHelper.needsEntriesSelected(stateManager)); } @Override @@ -73,53 +66,56 @@ private String sendEmail() throws Exception { return Localization.lang("This operation requires one or more entries to be selected."); } - StringWriter rawEntries = new StringWriter(); - BibWriter bibWriter = new BibWriter(rawEntries, OS.NEWLINE); - BibDatabaseContext databaseContext = stateManager.getActiveDatabase().get(); List entries = stateManager.getSelectedEntries(); + URI uriMailTo = getUriMailTo(entries); - // write the entries via this writer to "rawEntries" (being a StringWriter), which used later to form the email content - BibEntryWriter bibtexEntryWriter = new BibEntryWriter(new FieldWriter(preferencesService.getFieldWriterPreferences()), Globals.entryTypesManager); + Desktop desktop = Desktop.getDesktop(); + desktop.mail(uriMailTo); - for (BibEntry entry : entries) { - try { - bibtexEntryWriter.write(entry, bibWriter, databaseContext.getMode()); - } catch (IOException e) { - LOGGER.warn("Problem creating BibTeX file for mailing.", e); - } + return String.format("%s: %d", Localization.lang("Entries added to an email"), entries.size()); + } + + private URI getUriMailTo(List entries) throws URISyntaxException { + StringBuilder mailTo = new StringBuilder(); + + mailTo.append(getEmailAddress()); + mailTo.append("?Body=").append(getBody()); + mailTo.append("&Subject=").append(getSubject()); + + List attachments = getAttachments(entries); + for (String path : attachments) { + mailTo.append("&Attachment=\"").append(path); + mailTo.append("\""); } - List attachments = new ArrayList<>(); + return new URI("mailto", mailTo.toString(), null); + } + private List getAttachments(List entries) { // open folders is needed to indirectly support email programs, which cannot handle // the unofficial "mailto:attachment" property boolean openFolders = preferencesService.getExternalApplicationsPreferences().shouldAutoOpenEmailAttachmentsFolder(); + BibDatabaseContext databaseContext = stateManager.getActiveDatabase().get(); List fileList = FileUtil.getListOfLinkedFiles(entries, databaseContext.getFileDirectories(preferencesService.getFilePreferences())); + + List attachments = new ArrayList<>(); for (Path path : fileList) { attachments.add(path.toAbsolutePath().toString()); if (openFolders) { try { - JabRefDesktop.openFolderAndSelectFile(path.toAbsolutePath(), preferencesService, dialogService); + JabRefDesktop.openFolderAndSelectFile(path.toAbsolutePath(), preferencesService.getExternalApplicationsPreferences(), dialogService); } catch (IOException e) { LOGGER.debug("Cannot open file", e); } } } + return attachments; + } - String mailTo = "?Body=".concat(rawEntries.toString()); - mailTo = mailTo.concat("&Subject="); - mailTo = mailTo.concat(preferencesService.getExternalApplicationsPreferences().getEmailSubject()); - for (String path : attachments) { - mailTo = mailTo.concat("&Attachment=\"").concat(path); - mailTo = mailTo.concat("\""); - } - - URI uriMailTo = new URI("mailto", mailTo, null); + protected abstract String getEmailAddress(); - Desktop desktop = Desktop.getDesktop(); - desktop.mail(uriMailTo); + protected abstract String getSubject(); - return String.format("%s: %d", Localization.lang("Entries added to an email"), entries.size()); - } + protected abstract String getBody(); } diff --git a/src/main/java/org/jabref/gui/SendAsKindleEmailAction.java b/src/main/java/org/jabref/gui/SendAsKindleEmailAction.java new file mode 100644 index 00000000000..113f6cf3c50 --- /dev/null +++ b/src/main/java/org/jabref/gui/SendAsKindleEmailAction.java @@ -0,0 +1,34 @@ +package org.jabref.gui; + +import org.jabref.gui.actions.ActionHelper; +import org.jabref.logic.l10n.Localization; +import org.jabref.preferences.PreferencesService; + +/** + * Sends attachments for selected entries to the + * configured Kindle email + */ +public class SendAsKindleEmailAction extends SendAsEMailAction { + private final PreferencesService preferencesService; + + public SendAsKindleEmailAction(DialogService dialogService, PreferencesService preferencesService, StateManager stateManager) { + super(dialogService, preferencesService, stateManager); + this.preferencesService = preferencesService; + this.executable.bind(ActionHelper.needsEntriesSelected(stateManager).and(ActionHelper.hasLinkedFileForSelectedEntries(stateManager))); + } + + @Override + protected String getEmailAddress() { + return preferencesService.getExternalApplicationsPreferences().getKindleEmail(); + } + + @Override + protected String getSubject() { + return Localization.lang("Send to Kindle"); + } + + @Override + protected String getBody() { + return Localization.lang("Send to Kindle"); + } +} diff --git a/src/main/java/org/jabref/gui/SendAsStandardEmailAction.java b/src/main/java/org/jabref/gui/SendAsStandardEmailAction.java new file mode 100644 index 00000000000..0faf359b371 --- /dev/null +++ b/src/main/java/org/jabref/gui/SendAsStandardEmailAction.java @@ -0,0 +1,67 @@ +package org.jabref.gui; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; + +import org.jabref.gui.actions.ActionHelper; +import org.jabref.logic.bibtex.BibEntryWriter; +import org.jabref.logic.bibtex.FieldWriter; +import org.jabref.logic.exporter.BibWriter; +import org.jabref.logic.util.OS; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.preferences.PreferencesService; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Sends the selected entries to any specifiable email + * by populating the email body + */ +public class SendAsStandardEmailAction extends SendAsEMailAction { + private static final Logger LOGGER = LoggerFactory.getLogger(SendAsStandardEmailAction.class); + private final PreferencesService preferencesService; + private final StateManager stateManager; + private final BibEntryTypesManager entryTypesManager; + + public SendAsStandardEmailAction(DialogService dialogService, PreferencesService preferencesService, StateManager stateManager, BibEntryTypesManager entryTypesManager) { + super(dialogService, preferencesService, stateManager); + this.preferencesService = preferencesService; + this.stateManager = stateManager; + this.entryTypesManager = entryTypesManager; + this.executable.bind(ActionHelper.needsEntriesSelected(stateManager)); + } + + @Override + protected String getEmailAddress() { + return ""; + } + + @Override + protected String getSubject() { + return preferencesService.getExternalApplicationsPreferences().getEmailSubject(); + } + + @Override + protected String getBody() { + List entries = stateManager.getSelectedEntries(); + BibDatabaseContext databaseContext = stateManager.getActiveDatabase().get(); + StringWriter rawEntries = new StringWriter(); + BibWriter bibWriter = new BibWriter(rawEntries, OS.NEWLINE); + + BibEntryWriter bibtexEntryWriter = new BibEntryWriter(new FieldWriter(preferencesService.getFieldPreferences()), entryTypesManager); + + for (BibEntry entry : entries) { + try { + bibtexEntryWriter.write(entry, bibWriter, databaseContext.getMode()); + } catch (IOException e) { + LOGGER.warn("Problem creating BibTeX file for mailing.", e); + } + } + + return rawEntries.toString(); + } +} diff --git a/src/main/java/org/jabref/gui/StateManager.java b/src/main/java/org/jabref/gui/StateManager.java index fffab647dd0..f1ff42c5785 100644 --- a/src/main/java/org/jabref/gui/StateManager.java +++ b/src/main/java/org/jabref/gui/StateManager.java @@ -62,7 +62,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 -> new Observable[] {task.getValue().progressProperty(), task.getValue().runningProperty()}); + private final ObservableList, Task>> backgroundTasks = FXCollections.observableArrayList(task -> new Observable[]{task.getValue().progressProperty(), task.getValue().runningProperty()}); private final EasyBinding anyTaskRunning = EasyBind.reduce(backgroundTasks, tasks -> tasks.map(Pair::getValue).anyMatch(Task::isRunning)); private final EasyBinding anyTasksThatWillNotBeRecoveredRunning = EasyBind.reduce(backgroundTasks, tasks -> tasks.anyMatch(task -> !task.getKey().willBeRecoveredAutomatically() && task.getValue().isRunning())); private final EasyBinding tasksProgress = EasyBind.reduce(backgroundTasks, tasks -> tasks.map(Pair::getValue).filter(Task::isRunning).mapToDouble(Task::getProgress).average().orElse(1)); @@ -169,7 +169,7 @@ public ObservableList> getBackgroundTasks() { return EasyBind.map(backgroundTasks, Pair::getValue); } - public void addBackgroundTask(BackgroundTask backgroundTask, Task task) { + public void addBackgroundTask(BackgroundTask backgroundTask, Task task) { this.backgroundTasks.add(0, new Pair<>(backgroundTask, task)); } diff --git a/src/main/java/org/jabref/gui/actions/ActionFactory.java b/src/main/java/org/jabref/gui/actions/ActionFactory.java index 7112ffc8f12..3deb6d068ef 100644 --- a/src/main/java/org/jabref/gui/actions/ActionFactory.java +++ b/src/main/java/org/jabref/gui/actions/ActionFactory.java @@ -80,9 +80,9 @@ public MenuItem configureMenuItem(Action action, Command command, MenuItem menuI setGraphic(menuItem, action); // Show tooltips - if (command instanceof SimpleCommand) { + if (command instanceof SimpleCommand simpleCommand) { EasyBind.subscribe( - ((SimpleCommand) command).statusMessageProperty(), + simpleCommand.statusMessageProperty(), message -> { Label label = getAssociatedNode(menuItem); if (label != null) { diff --git a/src/main/java/org/jabref/gui/actions/JabRefAction.java b/src/main/java/org/jabref/gui/actions/JabRefAction.java index 9d0e7217f85..abf49e3f48a 100644 --- a/src/main/java/org/jabref/gui/actions/JabRefAction.java +++ b/src/main/java/org/jabref/gui/actions/JabRefAction.java @@ -47,8 +47,7 @@ public JabRefAction(Action action, Command command, KeyBindingRepository keyBind disabledProperty().bind(command.executableProperty().not()); - if (command instanceof SimpleCommand) { - SimpleCommand ourCommand = (SimpleCommand) command; + if (command instanceof SimpleCommand ourCommand) { longTextProperty().bind(Bindings.concat(action.getDescription(), ourCommand.statusMessageProperty())); } } diff --git a/src/main/java/org/jabref/gui/actions/StandardActions.java b/src/main/java/org/jabref/gui/actions/StandardActions.java index 8d9d8249af4..99cfc029201 100644 --- a/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -25,7 +25,9 @@ public enum StandardActions implements Action { CUT(Localization.lang("Cut"), IconTheme.JabRefIcons.CUT, KeyBinding.CUT), DELETE(Localization.lang("Delete"), IconTheme.JabRefIcons.DELETE_ENTRY), DELETE_ENTRY(Localization.lang("Delete entry"), IconTheme.JabRefIcons.DELETE_ENTRY, KeyBinding.DELETE_ENTRY), - SEND_AS_EMAIL(Localization.lang("Send as email"), IconTheme.JabRefIcons.EMAIL), + SEND(Localization.lang("Send"), IconTheme.JabRefIcons.EMAIL), + SEND_AS_EMAIL(Localization.lang("As Email")), + SEND_TO_KINDLE(Localization.lang("To Kindle")), REBUILD_FULLTEXT_SEARCH_INDEX(Localization.lang("Rebuild fulltext search index"), IconTheme.JabRefIcons.FILE), OPEN_EXTERNAL_FILE(Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE), OPEN_URL(Localization.lang("Open URL or DOI"), IconTheme.JabRefIcons.WWW, KeyBinding.OPEN_URL_OR_DOI), @@ -70,7 +72,7 @@ public enum StandardActions implements Action { EXPORT_SELECTED(Localization.lang("Export selected entries"), KeyBinding.EXPORT_SELECTED), CONNECT_TO_SHARED_DB(Localization.lang("Connect to shared database"), IconTheme.JabRefIcons.CONNECT_DB), PULL_CHANGES_FROM_SHARED_DB(Localization.lang("Pull changes from shared database"), KeyBinding.PULL_CHANGES_FROM_SHARED_DATABASE), - CLOSE_LIBRARY(Localization.lang("Close"), Localization.lang("Close the current library"), IconTheme.JabRefIcons.CLOSE, KeyBinding.CLOSE_DATABASE), + CLOSE_LIBRARY(Localization.lang("Close library"), Localization.lang("Close the current library"), IconTheme.JabRefIcons.CLOSE, KeyBinding.CLOSE_DATABASE), CLOSE_OTHER_LIBRARIES(Localization.lang("Close others"), Localization.lang("Close other libraries"), IconTheme.JabRefIcons.CLOSE), CLOSE_ALL_LIBRARIES(Localization.lang("Close all"), Localization.lang("Close all libraries"), IconTheme.JabRefIcons.CLOSE), QUIT(Localization.lang("Quit"), Localization.lang("Quit JabRef"), IconTheme.JabRefIcons.CLOSE_JABREF, KeyBinding.QUIT_JABREF), diff --git a/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java b/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java index 3c47d6dd8e3..06d2abef8a8 100644 --- a/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java +++ b/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java @@ -36,7 +36,7 @@ public SuggestionProvider getForField(Field field) { return new PersonNameSuggestionProvider(field, database); } else if (fieldProperties.contains(FieldProperty.SINGLE_ENTRY_LINK) || fieldProperties.contains(FieldProperty.MULTIPLE_ENTRY_LINK)) { return new BibEntrySuggestionProvider(database); - } else if (fieldProperties.contains(FieldProperty.JOURNAL_NAME) || StandardField.PUBLISHER.equals(field)) { + } else if (fieldProperties.contains(FieldProperty.JOURNAL_NAME) || StandardField.PUBLISHER == field) { return new JournalsSuggestionProvider(field, database, abbreviationRepository); } else { return new WordSuggestionProvider(field, database); diff --git a/src/main/java/org/jabref/logic/autosaveandbackup/AutosaveManager.java b/src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java similarity index 98% rename from src/main/java/org/jabref/logic/autosaveandbackup/AutosaveManager.java rename to src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java index 990da5c2db4..72402b18dd5 100644 --- a/src/main/java/org/jabref/logic/autosaveandbackup/AutosaveManager.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java @@ -1,4 +1,4 @@ -package org.jabref.logic.autosaveandbackup; +package org.jabref.gui.autosaveandbackup; import java.util.HashSet; import java.util.Set; diff --git a/src/main/java/org/jabref/logic/autosaveandbackup/BackupManager.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java similarity index 67% rename from src/main/java/org/jabref/logic/autosaveandbackup/BackupManager.java rename to src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java index 789671c0b14..1b9017f192f 100644 --- a/src/main/java/org/jabref/logic/autosaveandbackup/BackupManager.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java @@ -1,4 +1,4 @@ -package org.jabref.logic.autosaveandbackup; +package org.jabref.gui.autosaveandbackup; import java.io.IOException; import java.io.Writer; @@ -19,18 +19,24 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import javafx.scene.control.TableColumn; + +import org.jabref.gui.LibraryTab; +import org.jabref.gui.maintable.BibEntryTableViewModel; +import org.jabref.gui.maintable.columns.MainTableColumn; import org.jabref.logic.bibtex.InvalidFieldValueException; import org.jabref.logic.exporter.AtomicFileWriter; import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.exporter.BibtexDatabaseWriter; -import org.jabref.logic.exporter.SavePreferences; +import org.jabref.logic.exporter.SelfContainedSaveConfiguration; import org.jabref.logic.util.BackupFileType; import org.jabref.logic.util.CoarseChangeFilter; import org.jabref.logic.util.io.BackupFileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.event.BibDatabaseContextChangedEvent; import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.preferences.GeneralPreferences; +import org.jabref.model.metadata.SaveOrder; +import org.jabref.model.metadata.SelfContainedSaveOrder; import org.jabref.preferences.PreferencesService; import com.google.common.eventbus.Subscribe; @@ -58,19 +64,19 @@ public class BackupManager { private final ScheduledThreadPoolExecutor executor; private final CoarseChangeFilter changeFilter; private final BibEntryTypesManager entryTypesManager; - + private final LibraryTab libraryTab; // Contains a list of all backup paths // During a write, the less recent backup file is deleted private final Queue backupFilesQueue = new LinkedBlockingQueue<>(); - private boolean needsBackup = false; - BackupManager(BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, PreferencesService preferences) { + BackupManager(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, PreferencesService preferences) { this.bibDatabaseContext = bibDatabaseContext; this.entryTypesManager = entryTypesManager; this.preferences = preferences; this.executor = new ScheduledThreadPoolExecutor(2); + this.libraryTab = libraryTab; changeFilter = new CoarseChangeFilter(bibDatabaseContext); changeFilter.registerListener(this); @@ -79,15 +85,15 @@ public class BackupManager { /** * Determines the most recent backup file name */ - static Path getBackupPathForNewBackup(Path originalPath) { - return BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(originalPath, BackupFileType.BACKUP); + static Path getBackupPathForNewBackup(Path originalPath, Path backupDir) { + return BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(originalPath, BackupFileType.BACKUP, backupDir); } /** * Determines the most recent existing backup file name */ - static Optional getLatestBackupPath(Path originalPath) { - return BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP); + static Optional getLatestBackupPath(Path originalPath, Path backupDir) { + return BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, backupDir); } /** @@ -98,9 +104,9 @@ static Optional getLatestBackupPath(Path originalPath) { * * @param bibDatabaseContext Associated {@link BibDatabaseContext} */ - public static BackupManager start(BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, PreferencesService preferences) { - BackupManager backupManager = new BackupManager(bibDatabaseContext, entryTypesManager, preferences); - backupManager.startBackupTask(); + public static BackupManager start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, PreferencesService preferences) { + BackupManager backupManager = new BackupManager(libraryTab, bibDatabaseContext, entryTypesManager, preferences); + backupManager.startBackupTask(preferences.getFilePreferences().getBackupDirectory()); runningInstances.add(backupManager); return backupManager; } @@ -110,19 +116,19 @@ public static BackupManager start(BibDatabaseContext bibDatabaseContext, BibEntr * * @param bibDatabaseContext Associated {@link BibDatabaseContext} */ - public static void discardBackup(BibDatabaseContext bibDatabaseContext) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach( - BackupManager::discardBackup); + public static void discardBackup(BibDatabaseContext bibDatabaseContext, Path backupDir) { + runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.discardBackup(backupDir)); } /** * Shuts down the BackupManager which is associated with the given {@link BibDatabaseContext}. * * @param bibDatabaseContext Associated {@link BibDatabaseContext} + * @param createBackup True, if a backup should be created + * @param backupDir The path to the backup directory */ - public static void shutdown(BibDatabaseContext bibDatabaseContext) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach( - BackupManager::shutdown); + public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup) { + runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdown(backupDir, createBackup)); runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); } @@ -138,8 +144,8 @@ public static void shutdown(BibDatabaseContext bibDatabaseContext) { * "default" return value in the good case. In case a discarded file exists, false is returned, too. * In the case of an exception true is returned to ensure that the user checks the output. */ - public static boolean backupFileDiffers(Path originalPath) { - Path discardedFile = determineDiscardedFile(originalPath); + public static boolean backupFileDiffers(Path originalPath, Path backupDir) { + Path discardedFile = determineDiscardedFile(originalPath, backupDir); if (Files.exists(discardedFile)) { try { Files.delete(discardedFile); @@ -149,10 +155,10 @@ public static boolean backupFileDiffers(Path originalPath) { } return false; } - return getLatestBackupPath(originalPath).map(latestBackupPath -> { + return getLatestBackupPath(originalPath, backupDir).map(latestBackupPath -> { FileTime latestBackupFileLastModifiedTime; try { - latestBackupFileLastModifiedTime = Files.getLastModifiedTime(latestBackupPath); + latestBackupFileLastModifiedTime = Files.getLastModifiedTime(latestBackupPath); } catch (IOException e) { LOGGER.debug("Could not get timestamp of backup file {}", latestBackupPath, e); // If we cannot get the timestamp, we do show any warning @@ -186,8 +192,8 @@ public static boolean backupFileDiffers(Path originalPath) { * * @param originalPath Path to the file which should be equalized to the backup file. */ - public static void restoreBackup(Path originalPath) { - Optional backupPath = getLatestBackupPath(originalPath); + public static void restoreBackup(Path originalPath, Path backupDir) { + Optional backupPath = getLatestBackupPath(originalPath, backupDir); if (backupPath.isEmpty()) { LOGGER.error("There is no backup file"); return; @@ -199,8 +205,8 @@ public static void restoreBackup(Path originalPath) { } } - Optional determineBackupPathForNewBackup() { - return bibDatabaseContext.getDatabasePath().map(BackupManager::getBackupPathForNewBackup); + Optional determineBackupPathForNewBackup(Path backupDir) { + return bibDatabaseContext.getDatabasePath().map(path -> BackupManager.getBackupPathForNewBackup(path, backupDir)); } /** @@ -208,7 +214,7 @@ Optional determineBackupPathForNewBackup() { * * SIDE EFFECT: Deletes oldest backup file * - * @param backupPath the path where the library should be backed up to + * @param backupPath the full path to the file where the library should be backed up to */ void performBackup(Path backupPath) { if (!needsBackup) { @@ -217,18 +223,38 @@ void performBackup(Path backupPath) { // We opted for "while" to delete backups in case there are more than 10 while (backupFilesQueue.size() >= MAXIMUM_BACKUP_FILE_COUNT) { - Path lessRecentBackupFile = backupFilesQueue.poll(); + Path oldestBackupFile = backupFilesQueue.poll(); try { - Files.delete(lessRecentBackupFile); + Files.delete(oldestBackupFile); } catch (IOException e) { - LOGGER.error("Could not delete backup file {}", lessRecentBackupFile, e); + LOGGER.error("Could not delete backup file {}", oldestBackupFile, e); } } // code similar to org.jabref.gui.exporter.SaveDatabaseAction.saveDatabase - GeneralPreferences generalPreferences = preferences.getGeneralPreferences(); - SavePreferences savePreferences = preferences.getSavePreferences() - .withMakeBackup(false); + SelfContainedSaveOrder saveOrder = bibDatabaseContext + .getMetaData().getSaveOrder() + .map(so -> { + if (so.getOrderType() == SaveOrder.OrderType.TABLE) { + // We need to "flatten out" SaveOrder.OrderType.TABLE as BibWriter does not have access to preferences + List> sortOrder = libraryTab.getMainTable().getSortOrder(); + return new SelfContainedSaveOrder( + SaveOrder.OrderType.SPECIFIED, + sortOrder.stream() + .filter(col -> col instanceof MainTableColumn) + .map(column -> ((MainTableColumn) column).getModel()) + .flatMap(model -> model.getSortCriteria().stream()) + .toList()); + } else { + return SelfContainedSaveOrder.of(so); + } + }) + .orElse(SaveOrder.getDefaultSaveOrder()); + SelfContainedSaveConfiguration saveConfiguration = (SelfContainedSaveConfiguration) new SelfContainedSaveConfiguration() + .withMakeBackup(false) + .withSaveOrder(saveOrder) + .withReformatOnSave(preferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); + Charset encoding = bibDatabaseContext.getMetaData().getEncoding().orElse(StandardCharsets.UTF_8); // We want to have successful backups only // Thus, we do not use a plain "FileWriter", but the "AtomicFileWriter" @@ -236,7 +262,12 @@ void performBackup(Path backupPath) { // This MUST NOT create a broken backup file that then jabref wants to "restore" from? try (Writer writer = new AtomicFileWriter(backupPath, encoding, false)) { BibWriter bibWriter = new BibWriter(writer, bibDatabaseContext.getDatabase().getNewLineSeparator()); - new BibtexDatabaseWriter(bibWriter, generalPreferences, savePreferences, entryTypesManager) + new BibtexDatabaseWriter( + bibWriter, + saveConfiguration, + preferences.getFieldPreferences(), + preferences.getCitationKeyPatternPreferences(), + entryTypesManager) .saveDatabase(bibDatabaseContext); backupFilesQueue.add(backupPath); @@ -248,10 +279,8 @@ void performBackup(Path backupPath) { } } - private static Path determineDiscardedFile(Path file) { - return BackupFileUtil.getAppDataBackupDir().resolve( - BackupFileUtil.getUniqueFilePrefix(file) + "--" + file.getFileName() + "--discarded" - ); + private static Path determineDiscardedFile(Path file, Path backupDir) { + return backupDir.resolve(BackupFileUtil.getUniqueFilePrefix(file) + "--" + file.getFileName() + "--discarded"); } /** @@ -260,8 +289,8 @@ private static Path determineDiscardedFile(Path file) { * We do not delete any files, because the user might want to recover old backup files. * Therefore, we mark discarded backups by a --discarded file. */ - public void discardBackup() { - Path path = determineDiscardedFile(bibDatabaseContext.getDatabasePath().get()); + public void discardBackup(Path backupDir) { + Path path = determineDiscardedFile(bibDatabaseContext.getDatabasePath().get(), backupDir); try { Files.createFile(path); } catch (IOException e) { @@ -289,19 +318,18 @@ public synchronized void listen(@SuppressWarnings("unused") BibDatabaseContextCh } } - private void startBackupTask() { - fillQueue(); + private void startBackupTask(Path backupDir) { + fillQueue(backupDir); executor.scheduleAtFixedRate( - // We need to determine the backup path on each action, because we use the timestamp in the filename - () -> determineBackupPathForNewBackup().ifPresent(this::performBackup), - DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, - DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, - TimeUnit.SECONDS); + // We need to determine the backup path on each action, because we use the timestamp in the filename + () -> determineBackupPathForNewBackup(backupDir).ifPresent(path -> this.performBackup(path)), + DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, + DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, + TimeUnit.SECONDS); } - private void fillQueue() { - Path backupDir = BackupFileUtil.getAppDataBackupDir(); + private void fillQueue(Path backupDir) { if (!Files.exists(backupDir)) { return; } @@ -323,13 +351,18 @@ private void fillQueue() { /** * Unregisters the BackupManager from the eventBus of {@link BibDatabaseContext}. * This method should only be used when closing a database/JabRef in a normal way. + * + * @param backupDir The backup directory + * @param createBackup If the backup manager should still perform a backup */ - private void shutdown() { + private void shutdown(Path backupDir, boolean createBackup) { changeFilter.unregisterListener(this); changeFilter.shutdown(); executor.shutdown(); - // Ensure that backup is a recent one - determineBackupPathForNewBackup().ifPresent(this::performBackup); + if (createBackup) { + // Ensure that backup is a recent one + determineBackupPathForNewBackup(backupDir).ifPresent(this::performBackup); + } } } diff --git a/src/main/java/org/jabref/gui/auximport/NewSubLibraryAction.java b/src/main/java/org/jabref/gui/auximport/NewSubLibraryAction.java index 36c426afb17..65cc37abfd9 100644 --- a/src/main/java/org/jabref/gui/auximport/NewSubLibraryAction.java +++ b/src/main/java/org/jabref/gui/auximport/NewSubLibraryAction.java @@ -11,6 +11,8 @@ /** * The action concerned with generate a new (sub-)database from latex AUX file. + * + * A new library is created by {@link org.jabref.gui.importer.NewDatabaseAction} */ public class NewSubLibraryAction extends SimpleCommand { diff --git a/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java b/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java index bdd2fbd2d16..502ed96740f 100644 --- a/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java +++ b/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java @@ -9,11 +9,11 @@ import javafx.scene.control.Hyperlink; import org.jabref.gui.FXDialog; -import org.jabref.gui.Globals; import org.jabref.gui.desktop.JabRefDesktop; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.BackupFileType; import org.jabref.logic.util.io.BackupFileUtil; +import org.jabref.preferences.ExternalApplicationsPreferences; import org.controlsfx.control.HyperlinkLabel; import org.slf4j.Logger; @@ -26,28 +26,22 @@ public class BackupResolverDialog extends FXDialog { private static final Logger LOGGER = LoggerFactory.getLogger(BackupResolverDialog.class); - public BackupResolverDialog(Path originalPath) { + public BackupResolverDialog(Path originalPath, Path backupDir, ExternalApplicationsPreferences externalApplicationsPreferences) { super(AlertType.CONFIRMATION, Localization.lang("Backup found"), true); setHeaderText(null); getDialogPane().setMinHeight(180); getDialogPane().getButtonTypes().setAll(RESTORE_FROM_BACKUP, REVIEW_BACKUP, IGNORE_BACKUP); - Optional backupPathOpt = BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP); + Optional backupPathOpt = BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, backupDir); String backupFilename = backupPathOpt.map(Path::getFileName).map(Path::toString).orElse(Localization.lang("File not found")); - String content = new StringBuilder() - .append(Localization.lang("A backup file for '%0' was found at [%1]", - originalPath.getFileName().toString(), - backupFilename)) - .append("\n") - .append(Localization.lang("This could indicate that JabRef did not shut down cleanly last time the file was used.")) - .append("\n\n") - .append(Localization.lang("Do you want to recover the library from the backup file?")) - .toString(); + String content = Localization.lang("A backup file for '%0' was found at [%1]", originalPath.getFileName().toString(), backupFilename) + "\n" + + Localization.lang("This could indicate that JabRef did not shut down cleanly last time the file was used.") + "\n\n" + + Localization.lang("Do you want to recover the library from the backup file?"); setContentText(content); HyperlinkLabel contentLabel = new HyperlinkLabel(content); contentLabel.setPrefWidth(360); - contentLabel.setOnAction((e) -> { + contentLabel.setOnAction(e -> { if (backupPathOpt.isPresent()) { if (!(e.getSource() instanceof Hyperlink)) { return; @@ -55,7 +49,7 @@ public BackupResolverDialog(Path originalPath) { String clickedLinkText = ((Hyperlink) (e.getSource())).getText(); if (backupFilename.equals(clickedLinkText)) { try { - JabRefDesktop.openFolderAndSelectFile(backupPathOpt.get(), Globals.prefs, null); + JabRefDesktop.openFolderAndSelectFile(backupPathOpt.get(), externalApplicationsPreferences, null); } catch (IOException ex) { LOGGER.error("Could not open backup folder", ex); } diff --git a/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java b/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java index 418706815b2..a8674044ecf 100644 --- a/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java +++ b/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java @@ -16,7 +16,6 @@ import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.importer.FetcherException; -import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.importer.fetcher.GrobidCitationFetcher; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; @@ -43,8 +42,7 @@ public BibtexExtractorViewModel(BibDatabaseContext bibdatabaseContext, FileUpdateMonitor fileUpdateMonitor, TaskExecutor taskExecutor, UndoManager undoManager, - StateManager stateManager, - ImportFormatReader importFormatReader) { + StateManager stateManager) { this.dialogService = dialogService; this.preferencesService = preferencesService; @@ -56,7 +54,6 @@ public BibtexExtractorViewModel(BibDatabaseContext bibdatabaseContext, undoManager, stateManager, dialogService, - importFormatReader, taskExecutor); } @@ -82,7 +79,7 @@ private void parseUsingGrobid() { GrobidCitationFetcher grobidCitationFetcher = new GrobidCitationFetcher(preferencesService.getGrobidPreferences(), preferencesService.getImportFormatPreferences()); BackgroundTask.wrap(() -> grobidCitationFetcher.performSearch(inputTextProperty.getValue())) .onRunning(() -> dialogService.notify(Localization.lang("Your text is being parsed..."))) - .onFailure((e) -> { + .onFailure(e -> { if (e instanceof FetcherException) { String msg = Localization.lang("There are connection issues with a JabRef server. Detailed information: %0", e.getMessage()); diff --git a/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexDialog.java b/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexDialog.java index 95d6ba5a692..c3d8638608f 100644 --- a/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexDialog.java +++ b/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexDialog.java @@ -12,7 +12,6 @@ import org.jabref.gui.StateManager; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.TaskExecutor; -import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.util.FileUpdateMonitor; @@ -36,7 +35,6 @@ public class ExtractBibtexDialog extends BaseDialog { @Inject private TaskExecutor taskExecutor; @Inject private UndoManager undoManager; @Inject private PreferencesService preferencesService; - @Inject private ImportFormatReader importFormatReader; public ExtractBibtexDialog() { ViewLoader.view(this) @@ -48,14 +46,21 @@ public ExtractBibtexDialog() { buttonParse = (Button) getDialogPane().lookupButton(parseButtonType); buttonParse.setTooltip(new Tooltip((Localization.lang("Starts the extraction and adds the resulting entries to the currently opened database")))); - buttonParse.setOnAction((event) -> viewModel.startParsing()); + buttonParse.setOnAction(event -> viewModel.startParsing()); buttonParse.disableProperty().bind(viewModel.inputTextProperty().isEmpty()); } @FXML private void initialize() { BibDatabaseContext database = stateManager.getActiveDatabase().orElseThrow(() -> new NullPointerException("Database null")); - this.viewModel = new BibtexExtractorViewModel(database, dialogService, preferencesService, fileUpdateMonitor, taskExecutor, undoManager, stateManager, importFormatReader); + 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/GenerateCitationKeyAction.java b/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java index 1cc32a198c1..5cc07a31c71 100644 --- a/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java +++ b/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java @@ -54,7 +54,7 @@ public void execute() { checkOverwriteKeysChosen(); if (!this.isCanceled) { - BackgroundTask backgroundTask = this.generateKeysInBackground(); + BackgroundTask backgroundTask = this.generateKeysInBackground(); backgroundTask.showToUser(true); backgroundTask.titleProperty().set(Localization.lang("Autogenerate citation keys")); backgroundTask.messageProperty().set(Localization.lang("%0/%1 entries", 0, entries.size())); @@ -93,8 +93,8 @@ private void checkOverwriteKeysChosen() { } } - private BackgroundTask generateKeysInBackground() { - return new BackgroundTask() { + private BackgroundTask generateKeysInBackground() { + return new BackgroundTask<>() { private NamedCompound compound; @Override diff --git a/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.fxml b/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.fxml index 2ffb576b405..2ef2300a312 100644 --- a/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.fxml +++ b/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.fxml @@ -7,36 +7,36 @@ + - - - - - - - - - - - - - - - - + - - + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java b/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java index 5c83ec5dfa8..1a0cf3d6df2 100644 --- a/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java +++ b/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java @@ -27,6 +27,7 @@ public class CleanupPresetPanel extends VBox { @FXML private Label cleanupRenamePDFLabel; @FXML private CheckBox cleanUpDOI; @FXML private CheckBox cleanUpEprint; + @FXML private CheckBox cleanUpURL; @FXML private CheckBox cleanUpISSN; @FXML private CheckBox cleanUpMovePDF; @FXML private CheckBox cleanUpMakePathsRelative; @@ -100,6 +101,7 @@ private void init(CleanupPreferences cleanupPreferences, FilePreferences filePre private void updateDisplay(CleanupPreferences preset) { cleanUpDOI.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CLEAN_UP_DOI)); cleanUpEprint.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CLEANUP_EPRINT)); + cleanUpURL.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CLEAN_UP_URL)); if (!cleanUpMovePDF.isDisabled()) { cleanUpMovePDF.setSelected(preset.isActive(CleanupPreferences.CleanupStep.MOVE_PDF)); } @@ -129,6 +131,9 @@ public CleanupPreferences getCleanupPreset() { if (cleanUpEprint.isSelected()) { activeJobs.add(CleanupPreferences.CleanupStep.CLEANUP_EPRINT); } + if (cleanUpURL.isSelected()) { + activeJobs.add(CleanupPreferences.CleanupStep.CLEAN_UP_URL); + } if (cleanUpISSN.isSelected()) { activeJobs.add(CleanupPreferences.CleanupStep.CLEAN_UP_ISSN); } diff --git a/src/main/java/org/jabref/gui/collab/ChangeScanner.java b/src/main/java/org/jabref/gui/collab/ChangeScanner.java index 72ea8768613..64936e47de4 100644 --- a/src/main/java/org/jabref/gui/collab/ChangeScanner.java +++ b/src/main/java/org/jabref/gui/collab/ChangeScanner.java @@ -28,7 +28,7 @@ public ChangeScanner(BibDatabaseContext database, PreferencesService preferencesService) { this.database = database; this.preferencesService = preferencesService; - this.databaseChangeResolverFactory = new DatabaseChangeResolverFactory(dialogService, database, preferencesService.getBibEntryPreferences()); + this.databaseChangeResolverFactory = new DatabaseChangeResolverFactory(dialogService, database, preferencesService); } public List scanForChanges() { diff --git a/src/main/java/org/jabref/gui/collab/DatabaseChangeResolverFactory.java b/src/main/java/org/jabref/gui/collab/DatabaseChangeResolverFactory.java index 2aed7bb22ed..c27c7c600db 100644 --- a/src/main/java/org/jabref/gui/collab/DatabaseChangeResolverFactory.java +++ b/src/main/java/org/jabref/gui/collab/DatabaseChangeResolverFactory.java @@ -6,22 +6,22 @@ import org.jabref.gui.collab.entrychange.EntryChange; import org.jabref.gui.collab.entrychange.EntryChangeResolver; import org.jabref.model.database.BibDatabaseContext; -import org.jabref.preferences.BibEntryPreferences; +import org.jabref.preferences.PreferencesService; public class DatabaseChangeResolverFactory { private final DialogService dialogService; private final BibDatabaseContext databaseContext; - private final BibEntryPreferences bibEntryPreferences; + private final PreferencesService preferencesService; - public DatabaseChangeResolverFactory(DialogService dialogService, BibDatabaseContext databaseContext, BibEntryPreferences bibEntryPreferences) { + public DatabaseChangeResolverFactory(DialogService dialogService, BibDatabaseContext databaseContext, PreferencesService preferencesService) { this.dialogService = dialogService; this.databaseContext = databaseContext; - this.bibEntryPreferences = bibEntryPreferences; + this.preferencesService = preferencesService; } public Optional create(DatabaseChange change) { if (change instanceof EntryChange entryChange) { - return Optional.of(new EntryChangeResolver(entryChange, dialogService, databaseContext, bibEntryPreferences)); + return Optional.of(new EntryChangeResolver(entryChange, dialogService, databaseContext, preferencesService)); } return Optional.empty(); diff --git a/src/main/java/org/jabref/gui/collab/DatabaseChangesResolverDialog.java b/src/main/java/org/jabref/gui/collab/DatabaseChangesResolverDialog.java index a0d5c3420cd..7dcb21b0bf4 100644 --- a/src/main/java/org/jabref/gui/collab/DatabaseChangesResolverDialog.java +++ b/src/main/java/org/jabref/gui/collab/DatabaseChangesResolverDialog.java @@ -88,7 +88,7 @@ public DatabaseChangesResolverDialog(List changes, BibDatabaseCo @FXML private void initialize() { - PreviewViewer previewViewer = new PreviewViewer(database, dialogService, stateManager, themeManager); + PreviewViewer previewViewer = new PreviewViewer(database, dialogService, preferencesService, stateManager, themeManager); DatabaseChangeDetailsViewFactory databaseChangeDetailsViewFactory = new DatabaseChangeDetailsViewFactory(database, dialogService, stateManager, themeManager, preferencesService, entryTypesManager, previewViewer); viewModel = new ExternalChangesResolverViewModel(changes, undoManager); diff --git a/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeDetailsView.java b/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeDetailsView.java index 7782a6919bc..880341921ef 100644 --- a/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeDetailsView.java +++ b/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeDetailsView.java @@ -30,7 +30,7 @@ public EntryChangeDetailsView(BibEntry oldEntry, BibEntry newEntry, BibDatabaseC onDisk.getStyleClass().add("lib-change-header"); // we need a copy here as we otherwise would set the same entry twice - PreviewViewer previewClone = new PreviewViewer(databaseContext, dialogService, stateManager, themeManager); + PreviewViewer previewClone = new PreviewViewer(databaseContext, dialogService, preferencesService, stateManager, themeManager); TabPane oldEntryTabPane = oldPreviewWithSourcesTab.getPreviewWithSourceTab(oldEntry, databaseContext, preferencesService, entryTypesManager, previewClone, Localization.lang("Entry Preview")); TabPane newEntryTabPane = newPreviewWithSourcesTab.getPreviewWithSourceTab(newEntry, databaseContext, preferencesService, entryTypesManager, previewViewer, Localization.lang("Entry Preview")); diff --git a/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeResolver.java b/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeResolver.java index 8e5575f4196..8555a2ec517 100644 --- a/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeResolver.java +++ b/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeResolver.java @@ -12,24 +12,24 @@ import org.jabref.gui.mergeentries.newmergedialog.toolbar.ThreeWayMergeToolbar; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; -import org.jabref.preferences.BibEntryPreferences; +import org.jabref.preferences.PreferencesService; public final class EntryChangeResolver extends DatabaseChangeResolver { private final EntryChange entryChange; private final BibDatabaseContext databaseContext; - private final BibEntryPreferences bibEntryPreferences; + private final PreferencesService preferencesService; - public EntryChangeResolver(EntryChange entryChange, DialogService dialogService, BibDatabaseContext databaseContext, BibEntryPreferences bibEntryPreferences) { + public EntryChangeResolver(EntryChange entryChange, DialogService dialogService, BibDatabaseContext databaseContext, PreferencesService preferencesService) { super(dialogService); this.entryChange = entryChange; this.databaseContext = databaseContext; - this.bibEntryPreferences = bibEntryPreferences; + this.preferencesService = preferencesService; } @Override public Optional askUserToResolveChange() { - MergeEntriesDialog mergeEntriesDialog = new MergeEntriesDialog(entryChange.getOldEntry(), entryChange.getNewEntry(), bibEntryPreferences); + MergeEntriesDialog mergeEntriesDialog = new MergeEntriesDialog(entryChange.getOldEntry(), entryChange.getNewEntry(), preferencesService); mergeEntriesDialog.setLeftHeaderText(Localization.lang("In JabRef")); mergeEntriesDialog.setRightHeaderText(Localization.lang("On disk")); mergeEntriesDialog.configureDiff(new ShowDiffConfig(ThreeWayMergeToolbar.DiffView.SPLIT, BasicDiffMethod.WORDS)); diff --git a/src/main/java/org/jabref/gui/collab/entrychange/PreviewWithSourceTab.java b/src/main/java/org/jabref/gui/collab/entrychange/PreviewWithSourceTab.java index 0a4a494fa1e..be87e21a046 100644 --- a/src/main/java/org/jabref/gui/collab/entrychange/PreviewWithSourceTab.java +++ b/src/main/java/org/jabref/gui/collab/entrychange/PreviewWithSourceTab.java @@ -8,8 +8,8 @@ import org.jabref.gui.preview.PreviewViewer; import org.jabref.logic.bibtex.BibEntryWriter; +import org.jabref.logic.bibtex.FieldPreferences; import org.jabref.logic.bibtex.FieldWriter; -import org.jabref.logic.bibtex.FieldWriterPreferences; import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.OS; @@ -52,7 +52,7 @@ public TabPane getPreviewWithSourceTab(BibEntry entry, BibDatabaseContext bibDat } try { - codeArea.appendText(getSourceString(entry, bibDatabaseContext.getMode(), preferencesService.getFieldWriterPreferences(), entryTypesManager)); + codeArea.appendText(getSourceString(entry, bibDatabaseContext.getMode(), preferencesService.getFieldPreferences(), entryTypesManager)); } catch (IOException e) { LOGGER.error("Error getting Bibtex: {}", entry); } @@ -63,10 +63,10 @@ public TabPane getPreviewWithSourceTab(BibEntry entry, BibDatabaseContext bibDat return tabPanePreviewCode; } - private String getSourceString(BibEntry entry, BibDatabaseMode type, FieldWriterPreferences fieldWriterPreferences, BibEntryTypesManager entryTypesManager) throws IOException { + private String getSourceString(BibEntry entry, BibDatabaseMode type, FieldPreferences fieldPreferences, BibEntryTypesManager entryTypesManager) throws IOException { StringWriter writer = new StringWriter(); BibWriter bibWriter = new BibWriter(writer, OS.NEWLINE); - FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldWriterPreferences); + FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldPreferences); new BibEntryWriter(fieldWriter, entryTypesManager).write(entry, bibWriter, type); return writer.toString(); } diff --git a/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChangeDetailsView.java b/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChangeDetailsView.java index cddcdf32e2d..3c2a81e3e01 100644 --- a/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChangeDetailsView.java +++ b/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChangeDetailsView.java @@ -1,6 +1,7 @@ package org.jabref.gui.collab.metedatachange; import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; import javafx.scene.layout.VBox; import org.jabref.gui.collab.DatabaseChangeDetailsView; @@ -17,20 +18,24 @@ public MetadataChangeDetailsView(MetadataChange metadataChange, PreferencesServi header.getStyleClass().add("sectionHeader"); container.getChildren().add(header); - for (MetaDataDiff.Difference change : metadataChange.getMetaDataDiff().getDifferences(preferencesService)) { - container.getChildren().add(new Label(getDifferenceString(change))); + for (MetaDataDiff.Difference diff : metadataChange.getMetaDataDiff().getDifferences(preferencesService)) { + container.getChildren().add(new Label(getDifferenceString(diff.differenceType()))); + container.getChildren().add(new Label(diff.originalObject().toString())); + container.getChildren().add(new Label(diff.newObject().toString())); } - setLeftAnchor(container, 8d); - setTopAnchor(container, 8d); - setRightAnchor(container, 8d); - setBottomAnchor(container, 8d); + ScrollPane scrollPane = new ScrollPane(container); + scrollPane.setFitToWidth(true); + getChildren().setAll(scrollPane); - getChildren().setAll(container); + setLeftAnchor(scrollPane, 8d); + setTopAnchor(scrollPane, 8d); + setRightAnchor(scrollPane, 8d); + setBottomAnchor(scrollPane, 8d); } - private String getDifferenceString(MetaDataDiff.Difference change) { - return switch (change) { + private String getDifferenceString(MetaDataDiff.DifferenceType changeType) { + return switch (changeType) { case PROTECTED -> Localization.lang("Library protection"); case GROUPS_ALTERED -> diff --git a/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanel.fxml b/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanel.fxml index 1e2b9f84822..c23fde51d2f 100644 --- a/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanel.fxml +++ b/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanel.fxml @@ -16,7 +16,7 @@ - diff --git a/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanelViewModel.java b/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanelViewModel.java index efba3ab390d..6776df54e47 100644 --- a/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanelViewModel.java +++ b/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanelViewModel.java @@ -9,7 +9,6 @@ import javafx.collections.FXCollections; import org.jabref.model.entry.field.Field; -import org.jabref.model.metadata.SaveOrderConfig; public class SaveOrderConfigPanelViewModel { @@ -24,7 +23,7 @@ public SaveOrderConfigPanelViewModel() { } public void addCriterion() { - selectedSortCriteriaProperty.add(new SortCriterionViewModel(new SaveOrderConfig.SortCriterion())); + selectedSortCriteriaProperty.add(new SortCriterionViewModel()); } public void removeCriterion(SortCriterionViewModel sortCriterionViewModel) { diff --git a/src/main/java/org/jabref/gui/commonfxcontrols/SortCriterionViewModel.java b/src/main/java/org/jabref/gui/commonfxcontrols/SortCriterionViewModel.java index 485fa2368d2..19edca1f9d9 100644 --- a/src/main/java/org/jabref/gui/commonfxcontrols/SortCriterionViewModel.java +++ b/src/main/java/org/jabref/gui/commonfxcontrols/SortCriterionViewModel.java @@ -6,18 +6,24 @@ import javafx.beans.property.SimpleObjectProperty; import org.jabref.model.entry.field.Field; -import org.jabref.model.metadata.SaveOrderConfig; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.metadata.SaveOrder; public class SortCriterionViewModel { private final ObjectProperty fieldProperty = new SimpleObjectProperty<>(); private final BooleanProperty descendingProperty = new SimpleBooleanProperty(); - public SortCriterionViewModel(SaveOrderConfig.SortCriterion criterion) { + public SortCriterionViewModel(SaveOrder.SortCriterion criterion) { this.fieldProperty.setValue(criterion.field); this.descendingProperty.setValue(criterion.descending); } + public SortCriterionViewModel() { + this.fieldProperty.setValue(StandardField.AUTHOR); + this.descendingProperty.setValue(false); + } + public ObjectProperty fieldProperty() { return fieldProperty; } @@ -26,7 +32,7 @@ public BooleanProperty descendingProperty() { return descendingProperty; } - public SaveOrderConfig.SortCriterion getCriterion() { - return new SaveOrderConfig.SortCriterion(fieldProperty.getValue(), descendingProperty.getValue()); + public SaveOrder.SortCriterion getCriterion() { + return new SaveOrder.SortCriterion(fieldProperty.getValue(), descendingProperty.getValue()); } } diff --git a/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java b/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java index 4317dbeef8e..41b47510acd 100644 --- a/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java +++ b/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java @@ -47,7 +47,7 @@ public void execute() { List entries = stateManager.getSelectedEntries(); DirectoryDialogConfiguration dirDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(preferencesService.getImportExportPreferences().getExportWorkingDirectory()) + .withInitialDirectory(preferencesService.getExportPreferences().getExportWorkingDirectory()) .build(); Optional exportPath = dialogService.showDirectorySelectionDialog(dirDialogConfiguration); exportPath.ifPresent(path -> { @@ -57,7 +57,7 @@ public void execute() { Localization.lang("Copy linked files to folder..."), exportTask); Globals.TASK_EXECUTOR.execute(exportTask); - exportTask.setOnSucceeded((e) -> showDialog(exportTask.getValue())); + exportTask.setOnSucceeded(e -> showDialog(exportTask.getValue())); }); } } diff --git a/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogView.java b/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogView.java index e8d6d20f859..7b1bfcc2f58 100644 --- a/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogView.java +++ b/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogView.java @@ -56,6 +56,6 @@ private void setupTable() { })); tvResult.setItems(viewModel.copyFilesResultListProperty()); - tvResult.setColumnResizePolicy((param) -> true); + tvResult.setColumnResizePolicy(param -> true); } } diff --git a/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java b/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java index feaf48d281a..5c4b332a029 100644 --- a/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java +++ b/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java @@ -1,6 +1,5 @@ package org.jabref.gui.desktop; -import java.io.File; import java.io.IOException; import java.net.URI; import java.nio.file.Files; @@ -13,11 +12,7 @@ import org.jabref.gui.DialogService; import org.jabref.gui.Globals; -import org.jabref.gui.desktop.os.DefaultDesktop; -import org.jabref.gui.desktop.os.Linux; import org.jabref.gui.desktop.os.NativeDesktop; -import org.jabref.gui.desktop.os.OSX; -import org.jabref.gui.desktop.os.Windows; import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.logic.importer.util.IdentifierParser; @@ -30,20 +25,22 @@ import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.identifier.DOI; import org.jabref.model.entry.identifier.Identifier; +import org.jabref.preferences.ExternalApplicationsPreferences; +import org.jabref.preferences.FilePreferences; import org.jabref.preferences.PreferencesService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * TODO: Replace by http://docs.oracle.com/javase/7/docs/api/java/awt/Desktop.html - * http://stackoverflow.com/questions/18004150/desktop-api-is-not-supported-on-the-current-platform + * See http://stackoverflow.com/questions/18004150/desktop-api-is-not-supported-on-the-current-platform for more implementation hints. + * http://docs.oracle.com/javase/7/docs/api/java/awt/Desktop.html cannot be used as we don't want to rely on AWT */ public class JabRefDesktop { private static final Logger LOGGER = LoggerFactory.getLogger(JabRefDesktop.class); - private static final NativeDesktop NATIVE_DESKTOP = getNativeDesktop(); + private static final NativeDesktop NATIVE_DESKTOP = OS.getNativeDesktop(); private static final Pattern REMOTE_LINK_PATTERN = Pattern.compile("[a-z]+://.*"); private JabRefDesktop() { @@ -51,6 +48,8 @@ private JabRefDesktop() { /** * Open a http/pdf/ps viewer for the given link string. + * + * Opening a PDF file at the file field is done at {@link org.jabref.gui.fieldeditors.LinkedFileViewModel#open} */ public static void openExternalViewer(BibDatabaseContext databaseContext, PreferencesService preferencesService, @@ -61,7 +60,7 @@ public static void openExternalViewer(BibDatabaseContext databaseContext, throws IOException { String link = initialLink; Field field = initialField; - if (StandardField.PS.equals(field) || StandardField.PDF.equals(field)) { + if ((StandardField.PS == field) || (StandardField.PDF == field)) { // Find the default directory for this field type: List directories = databaseContext.getFileDirectories(preferencesService.getFilePreferences()); @@ -83,15 +82,18 @@ public static void openExternalViewer(BibDatabaseContext databaseContext, field = StandardField.PS; } } - } else if (StandardField.DOI.equals(field)) { - openDoi(link); + } else if (StandardField.DOI == field) { + openDoi(link, preferencesService); + return; + } else if (StandardField.ISBN == field) { + openIsbn(link, preferencesService); return; - } else if (StandardField.EPRINT.equals(field)) { + } else if (StandardField.EPRINT == field) { IdentifierParser identifierParser = new IdentifierParser(entry); link = identifierParser.parse(StandardField.EPRINT) - .flatMap(Identifier::getExternalURI) - .map(URI::toASCIIString) - .orElse(link); + .flatMap(Identifier::getExternalURI) + .map(URI::toASCIIString) + .orElse(link); if (Objects.equals(link, initialLink)) { Optional eprintTypeOpt = entry.getField(StandardField.EPRINTTYPE); @@ -105,17 +107,17 @@ public static void openExternalViewer(BibDatabaseContext databaseContext, field = StandardField.URL; } - if (StandardField.URL.equals(field)) { - openBrowser(link); - } else if (StandardField.PS.equals(field)) { + if (StandardField.URL == field) { + openBrowser(link, preferencesService.getFilePreferences()); + } else if (StandardField.PS == field) { try { - NATIVE_DESKTOP.openFile(link, StandardField.PS.getName()); + NATIVE_DESKTOP.openFile(link, StandardField.PS.getName(), preferencesService.getFilePreferences()); } catch (IOException e) { LOGGER.error("An error occurred on the command: " + link, e); } - } else if (StandardField.PDF.equals(field)) { + } else if (StandardField.PDF == field) { try { - NATIVE_DESKTOP.openFile(link, StandardField.PDF.getName()); + NATIVE_DESKTOP.openFile(link, StandardField.PDF.getName(), preferencesService.getFilePreferences()); } catch (IOException e) { LOGGER.error("An error occurred on the command: " + link, e); } @@ -124,21 +126,26 @@ public static void openExternalViewer(BibDatabaseContext databaseContext, } } - private static void openDoi(String doi) throws IOException { + private static void openDoi(String doi, PreferencesService preferencesService) throws IOException { String link = DOI.parse(doi).map(DOI::getURIAsASCIIString).orElse(doi); - openBrowser(link); + openBrowser(link, preferencesService.getFilePreferences()); } public static void openCustomDoi(String link, PreferencesService preferences, DialogService dialogService) { - DOI.parse(link) - .flatMap(doi -> doi.getExternalURIWithCustomBase(preferences.getDOIPreferences().getDefaultBaseURI())) - .ifPresent(uri -> { - try { - JabRefDesktop.openBrowser(uri); - } catch (IOException e) { - dialogService.showErrorDialogAndWait(Localization.lang("Unable to open link."), e); - } - }); + DOI.parse(link) + .flatMap(doi -> doi.getExternalURIWithCustomBase(preferences.getDOIPreferences().getDefaultBaseURI())) + .ifPresent(uri -> { + try { + JabRefDesktop.openBrowser(uri, preferences.getFilePreferences()); + } catch (IOException e) { + dialogService.showErrorDialogAndWait(Localization.lang("Unable to open link."), e); + } + }); + } + + private static void openIsbn(String isbn, PreferencesService preferencesService) throws IOException { + String link = "https://openlibrary.org/isbn/" + isbn; + openBrowser(link, preferencesService.getFilePreferences()); } /** @@ -149,42 +156,42 @@ public static void openCustomDoi(String link, PreferencesService preferences, Di * @return false if the link couldn't be resolved, true otherwise. */ public static boolean openExternalFileAnyFormat(final BibDatabaseContext databaseContext, - PreferencesService preferencesService, + FilePreferences filePreferences, String link, - final Optional type) - throws IOException { - + final Optional type) throws IOException { if (REMOTE_LINK_PATTERN.matcher(link.toLowerCase(Locale.ROOT)).matches()) { - openExternalFilePlatformIndependent(type, link); + openExternalFilePlatformIndependent(type, link, filePreferences); return true; } - - Optional file = FileUtil.find(databaseContext, link, preferencesService.getFilePreferences()); + Optional file = FileUtil.find(databaseContext, link, filePreferences); if (file.isPresent() && Files.exists(file.get())) { // Open the file: String filePath = file.get().toString(); - openExternalFilePlatformIndependent(type, filePath); - } else { - // No file matched the name, try to open it directly using the given app - openExternalFilePlatformIndependent(type, link); + openExternalFilePlatformIndependent(type, filePath, filePreferences); + return true; } + + // No file matched the name, try to open it directly using the given app + openExternalFilePlatformIndependent(type, link, filePreferences); return true; } - private static void openExternalFilePlatformIndependent(Optional fileType, String filePath) + private static void openExternalFilePlatformIndependent(Optional fileType, + String filePath, + FilePreferences filePreferences) throws IOException { if (fileType.isPresent()) { String application = fileType.get().getOpenWithApplication(); if (application.isEmpty()) { - NATIVE_DESKTOP.openFile(filePath, fileType.get().getExtension()); + NATIVE_DESKTOP.openFile(filePath, fileType.get().getExtension(), filePreferences); } else { NATIVE_DESKTOP.openFileWithApplication(filePath, application); } } else { // File type is not given and therefore no application specified // Let the OS handle the opening of the file - NATIVE_DESKTOP.openFile(filePath, ""); + NATIVE_DESKTOP.openFile(filePath, "", filePreferences); } } @@ -194,67 +201,26 @@ private static void openExternalFilePlatformIndependent(Optional fileType = ExternalFileTypes.getExternalFileTypeByExt("html", Globals.prefs.getFilePreferences()); - openExternalFilePlatformIndependent(fileType, url); - } - - public static void openBrowser(URI url) throws IOException { - openBrowser(url.toASCIIString()); - } - - /** - * Opens the url with the users standard Browser. If that fails a popup will be shown to instruct the user to open the link manually and the link gets copied to the clipboard - * - * @param url the URL to open - */ - public static void openBrowserShowPopup(String url, DialogService dialogService) { - try { - openBrowser(url); - } catch (IOException exception) { - Globals.getClipboardManager().setContent(url); - LOGGER.error("Could not open browser", exception); - String couldNotOpenBrowser = Localization.lang("Could not open browser."); - String openManually = Localization.lang("Please open %0 manually.", url); - String copiedToClipboard = Localization.lang("The link has been copied to the clipboard."); - dialogService.notify(couldNotOpenBrowser); - dialogService.showErrorDialogAndWait(couldNotOpenBrowser, couldNotOpenBrowser + "\n" + openManually + "\n" + copiedToClipboard); + String absolutePath = fileLink.toAbsolutePath().getParent().toString(); + String command = externalApplicationsPreferences.getCustomFileBrowserCommand(); + if (command.isEmpty()) { + LOGGER.info("No custom file browser command defined"); + NATIVE_DESKTOP.openFolderAndSelectFile(fileLink); + return; } + executeCommand(command, absolutePath, dialogService); } /** @@ -263,51 +229,79 @@ public static void openBrowserShowPopup(String url, DialogService dialogService) * If no command is specified in {@link Globals}, the default system console will be executed. * * @param file Location the console should be opened at. - * @param dialogService + * */ - public static void openConsole(File file, PreferencesService preferencesService, DialogService dialogService) throws IOException { + public static void openConsole(Path file, PreferencesService preferencesService, DialogService dialogService) throws IOException { if (file == null) { return; } - String absolutePath = file.toPath().toAbsolutePath().getParent().toString(); + String absolutePath = file.toAbsolutePath().getParent().toString(); boolean useCustomTerminal = preferencesService.getExternalApplicationsPreferences().useCustomTerminal(); if (!useCustomTerminal) { NATIVE_DESKTOP.openConsole(absolutePath, dialogService); - } else { - String command = preferencesService.getExternalApplicationsPreferences().getCustomTerminalCommand(); - command = command.trim(); - - if (!command.isEmpty()) { - command = command.replaceAll("\\s+", " "); // normalize white spaces - command = command.replace("%DIR", absolutePath); // replace the placeholder if used + return; + } + String command = preferencesService.getExternalApplicationsPreferences().getCustomTerminalCommand(); + command = command.trim(); + if (command.isEmpty()) { + NATIVE_DESKTOP.openConsole(absolutePath, dialogService); + LOGGER.info("Preference for custom terminal is empty. Using default terminal."); + return; + } + executeCommand(command, absolutePath, dialogService); + } - String[] subcommands = command.split(" "); + private static void executeCommand(String command, String absolutePath, DialogService dialogService) { + // normalize white spaces + command = command.replaceAll("\\s+", " "); - LOGGER.info("Executing command \"" + command + "\"..."); - dialogService.notify(Localization.lang("Executing command \"%0\"...", command)); + // replace the placeholder if used + command = command.replace("%DIR", absolutePath); - try { - new ProcessBuilder(subcommands).start(); - } catch (IOException exception) { - LOGGER.error("Open console", exception); + LOGGER.info("Executing command \"{}\"...", command); + dialogService.notify(Localization.lang("Executing command \"%0\"...", command)); - dialogService.notify(Localization.lang("Error occured while executing the command \"%0\".", command)); - } - } + String[] subcommands = command.split(" "); + try { + new ProcessBuilder(subcommands).start(); + } catch (IOException exception) { + LOGGER.error("Error during command execution", exception); + dialogService.notify(Localization.lang("Error occurred while executing the command \"%0\".", command)); } } - // TODO: Move to OS.java - public static NativeDesktop getNativeDesktop() { - if (OS.WINDOWS) { - return new Windows(); - } else if (OS.OS_X) { - return new OSX(); - } else if (OS.LINUX) { - return new Linux(); + /** + * Opens the given URL using the system browser + * + * @param url the URL to open + */ + public static void openBrowser(String url, FilePreferences filePreferences) throws IOException { + Optional fileType = ExternalFileTypes.getExternalFileTypeByExt("html", filePreferences); + openExternalFilePlatformIndependent(fileType, url, filePreferences); + } + + public static void openBrowser(URI url, FilePreferences filePreferences) throws IOException { + openBrowser(url.toASCIIString(), filePreferences); + } + + /** + * Opens the url with the users standard Browser. If that fails a popup will be shown to instruct the user to open the link manually and the link gets copied to the clipboard + * + * @param url the URL to open + */ + public static void openBrowserShowPopup(String url, DialogService dialogService, FilePreferences filePreferences) { + try { + openBrowser(url, filePreferences); + } catch (IOException exception) { + Globals.getClipboardManager().setContent(url); + LOGGER.error("Could not open browser", exception); + String couldNotOpenBrowser = Localization.lang("Could not open browser."); + String openManually = Localization.lang("Please open %0 manually.", url); + String copiedToClipboard = Localization.lang("The link has been copied to the clipboard."); + dialogService.notify(couldNotOpenBrowser); + dialogService.showErrorDialogAndWait(couldNotOpenBrowser, couldNotOpenBrowser + "\n" + openManually + "\n" + copiedToClipboard); } - return new DefaultDesktop(); } } diff --git a/src/main/java/org/jabref/gui/desktop/os/DefaultDesktop.java b/src/main/java/org/jabref/gui/desktop/os/DefaultDesktop.java index 86d97fb45ae..7ab380efcb7 100644 --- a/src/main/java/org/jabref/gui/desktop/os/DefaultDesktop.java +++ b/src/main/java/org/jabref/gui/desktop/os/DefaultDesktop.java @@ -6,17 +6,23 @@ import java.nio.file.Path; import org.jabref.architecture.AllowedToUseAwt; +import org.jabref.cli.Launcher; import org.jabref.gui.DialogService; +import org.jabref.preferences.FilePreferences; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * This class contains some default implementations (if OS is neither linux, windows or osx) file directories and file/application open handling methods
+ * We cannot use a static logger instance here in this class as the Logger first needs to be configured in the {@link Launcher#addLogToDisk} + * The configuration of tinylog will become immutable as soon as the first log entry is issued. + * https://tinylog.org/v2/configuration/ + **/ @AllowedToUseAwt("Requires AWT to open a file") -public class DefaultDesktop implements NativeDesktop { - private static final Logger LOGGER = LoggerFactory.getLogger(NativeDesktop.class); +public class DefaultDesktop extends NativeDesktop { @Override - public void openFile(String filePath, String fileType) throws IOException { + public void openFile(String filePath, String fileType, FilePreferences filePreferences) throws IOException { Desktop.getDesktop().open(new File(filePath)); } @@ -33,7 +39,7 @@ public void openFolderAndSelectFile(Path filePath) throws IOException { @Override public void openConsole(String absolutePath, DialogService dialogService) throws IOException { - LOGGER.error("This feature is not supported by your Operating System."); + LoggerFactory.getLogger(DefaultDesktop.class).error("This feature is not supported by your Operating System."); } @Override @@ -45,9 +51,4 @@ public String detectProgramPath(String programName, String directoryName) { public Path getApplicationDirectory() { return getUserDirectory(); } - - @Override - public Path getDefaultFileChooserDirectory() { - return Path.of(System.getProperty("user.home")); - } } diff --git a/src/main/java/org/jabref/gui/desktop/os/Linux.java b/src/main/java/org/jabref/gui/desktop/os/Linux.java index c08ca68aa2c..eba3745c0e9 100644 --- a/src/main/java/org/jabref/gui/desktop/os/Linux.java +++ b/src/main/java/org/jabref/gui/desktop/os/Linux.java @@ -5,60 +5,67 @@ import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.Locale; -import java.util.Objects; import java.util.Optional; import org.jabref.architecture.AllowedToUseAwt; +import org.jabref.cli.Launcher; import org.jabref.gui.DialogService; -import org.jabref.gui.Globals; import org.jabref.gui.JabRefExecutorService; import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.util.StreamGobbler; import org.jabref.logic.l10n.Localization; +import org.jabref.preferences.FilePreferences; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * This class contains Linux specific implementations for file directories and file/application open handling methods
+ * We cannot use a static logger instance here in this class as the Logger first needs to be configured in the {@link Launcher#addLogToDisk} + * The configuration of tinylog will become immutable as soon as the first log entry is issued. + * https://tinylog.org/v2/configuration/ + **/ @AllowedToUseAwt("Requires AWT to open a file with the native method") -public class Linux implements NativeDesktop { +public class Linux extends NativeDesktop { - private static final Logger LOGGER = LoggerFactory.getLogger(Linux.class); + private static final String ETC_ALTERNATIVES_X_TERMINAL_EMULATOR = "/etc/alternatives/x-terminal-emulator"; private void nativeOpenFile(String filePath) { JabRefExecutorService.INSTANCE.execute(() -> { try { File file = new File(filePath); Desktop.getDesktop().open(file); - LOGGER.debug("Open file in default application with Desktop integration"); + LoggerFactory.getLogger(Linux.class).debug("Open file in default application with Desktop integration"); } catch (IllegalArgumentException e) { - LOGGER.debug("Fail back to xdg-open"); + LoggerFactory.getLogger(Linux.class).debug("Fail back to xdg-open"); try { String[] cmd = {"xdg-open", filePath}; Runtime.getRuntime().exec(cmd); } catch (Exception e2) { - LOGGER.warn("Open operation not successful: " + e2); + LoggerFactory.getLogger(Linux.class).warn("Open operation not successful: ", e2); } } catch (IOException e) { - LOGGER.warn("Native open operation not successful: " + e); + LoggerFactory.getLogger(Linux.class).warn("Native open operation not successful: ", e); } }); } @Override - public void openFile(String filePath, String fileType) throws IOException { - Optional type = ExternalFileTypes.getExternalFileTypeByExt(fileType, Globals.prefs.getFilePreferences()); + public void openFile(String filePath, String fileType, FilePreferences filePreferences) throws IOException { + Optional type = ExternalFileTypes.getExternalFileTypeByExt(fileType, filePreferences); String viewer; 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); + StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), LoggerFactory.getLogger(Linux.class)::debug); + StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), LoggerFactory.getLogger(Linux.class)::debug); JabRefExecutorService.INSTANCE.execute(streamGobblerInput); JabRefExecutorService.INSTANCE.execute(streamGobblerError); @@ -80,8 +87,8 @@ public void openFileWithApplication(String filePath, String application) throws 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(), LoggerFactory.getLogger(Linux.class)::debug); + StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), LoggerFactory.getLogger(Linux.class)::debug); JabRefExecutorService.INSTANCE.execute(streamGobblerInput); JabRefExecutorService.INSTANCE.execute(streamGobblerError); @@ -95,7 +102,7 @@ public void openFolderAndSelectFile(Path filePath) throws IOException { String desktopSession = System.getenv("DESKTOP_SESSION"); String absoluteFilePath = filePath.toAbsolutePath().toString(); - String[] cmd = {"xdg-open", absoluteFilePath}; // default command + String[] cmd = {"xdg-open", filePath.getParent().toString()}; // default is the folder of the file if (desktopSession != null) { desktopSession = desktopSession.toLowerCase(Locale.ROOT); @@ -107,13 +114,16 @@ public void openFolderAndSelectFile(Path filePath) throws IOException { cmd = new String[] {"caja", "--select", absoluteFilePath}; } else if (desktopSession.contains("cinnamon")) { cmd = new String[] {"nemo", absoluteFilePath}; // Although nemo is based on nautilus it does not support --select, it directly highlights the file + } else if (desktopSession.contains("xfce")) { + cmd = new String[] {"thunar", absoluteFilePath}; } } - ProcessBuilder processBuilder = new ProcessBuilder((cmd)); + LoggerFactory.getLogger(Linux.class).debug("Opening folder and selecting file using {}", String.join(" ", cmd)); + ProcessBuilder processBuilder = new ProcessBuilder(cmd); 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(), LoggerFactory.getLogger(Linux.class)::debug); + StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), LoggerFactory.getLogger(Linux.class)::debug); JabRefExecutorService.INSTANCE.execute(streamGobblerInput); JabRefExecutorService.INSTANCE.execute(streamGobblerError); @@ -122,12 +132,12 @@ public void openFolderAndSelectFile(Path filePath) throws IOException { @Override public void openConsole(String absolutePath, DialogService dialogService) throws IOException { - if (!Files.exists(Path.of("/etc/alternatives/x-terminal-emulator"))) { - dialogService.showErrorDialogAndWait(Localization.lang("Could not detect terminal automatically. Please define a custom terminal in the preferences.")); + if (!Files.exists(Path.of(ETC_ALTERNATIVES_X_TERMINAL_EMULATOR))) { + dialogService.showErrorDialogAndWait(Localization.lang("Could not detect terminal automatically using '%0'. Please define a custom terminal in the preferences.", ETC_ALTERNATIVES_X_TERMINAL_EMULATOR)); return; } - ProcessBuilder processBuilder = new ProcessBuilder("readlink", "/etc/alternatives/x-terminal-emulator"); + ProcessBuilder processBuilder = new ProcessBuilder("readlink", ETC_ALTERNATIVES_X_TERMINAL_EMULATOR); Process process = processBuilder.start(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { @@ -137,21 +147,24 @@ public void openConsole(String absolutePath, DialogService dialogService) throws String[] cmd; if (emulatorName.contains("gnome")) { - cmd = new String[] {"gnome-terminal", "--working-directory=", absolutePath}; + cmd = new String[] {"gnome-terminal", "--working-directory", absolutePath}; } else if (emulatorName.contains("xfce4")) { - cmd = new String[] {"xfce4-terminal", "--working-directory=", absolutePath}; + // xfce4-terminal requires "--working-directory=" format (one arg) + cmd = new String[] {"xfce4-terminal", "--working-directory=" + absolutePath}; } else if (emulatorName.contains("konsole")) { - cmd = new String[] {"konsole", "--workdir=", absolutePath}; + cmd = new String[] {"konsole", "--workdir", absolutePath}; } else { cmd = new String[] {emulatorName, absolutePath}; } + LoggerFactory.getLogger(Linux.class).debug("Opening terminal using {}", String.join(" ", cmd)); + ProcessBuilder builder = new ProcessBuilder(cmd); builder.directory(new File(absolutePath)); Process processTerminal = builder.start(); - StreamGobbler streamGobblerInput = new StreamGobbler(processTerminal.getInputStream(), LOGGER::debug); - StreamGobbler streamGobblerError = new StreamGobbler(processTerminal.getErrorStream(), LOGGER::debug); + StreamGobbler streamGobblerInput = new StreamGobbler(processTerminal.getInputStream(), LoggerFactory.getLogger(Linux.class)::debug); + StreamGobbler streamGobblerError = new StreamGobbler(processTerminal.getErrorStream(), LoggerFactory.getLogger(Linux.class)::debug); JabRefExecutorService.INSTANCE.execute(streamGobblerInput); JabRefExecutorService.INSTANCE.execute(streamGobblerError); @@ -171,9 +184,34 @@ public Path getApplicationDirectory() { @Override public Path getDefaultFileChooserDirectory() { - return Path.of(Objects.requireNonNullElse( - System.getenv("XDG_DOCUMENTS_DIR"), - System.getProperty("user.home") + "/Documents") - ); + String xdgDocumentsDir = System.getenv("XDG_DOCUMENTS_DIR"); + if (xdgDocumentsDir != null) { + return Path.of(xdgDocumentsDir); + } + + // Make use of xdg-user-dirs + // See https://www.freedesktop.org/wiki/Software/xdg-user-dirs/ for details + try { + Process process = new ProcessBuilder("xdg-user-dir", "DOCUMENTS").start(); // Package name with 's', command without + List strings = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)) + .lines().toList(); + if (strings.isEmpty()) { + LoggerFactory.getLogger(Linux.class).error("xdg-user-dir returned nothing"); + return getUserDirectory(); + } + String documentsDirectory = strings.get(0); + Path documentsPath = Path.of(documentsDirectory); + if (!Files.exists(documentsPath)) { + LoggerFactory.getLogger(Linux.class).error("xdg-user-dir returned non-existant directory {}", documentsDirectory); + return getUserDirectory(); + } + LoggerFactory.getLogger(Linux.class).debug("Got documents path {}", documentsPath); + return documentsPath; + } catch (IOException e) { + LoggerFactory.getLogger(Linux.class).error("Error while executing xdg-user-dir", e); + } + + // Fallback + return getUserDirectory(); } } diff --git a/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java b/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java index 74341d10e8a..7e6c4c8fae4 100644 --- a/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java +++ b/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java @@ -1,13 +1,36 @@ package org.jabref.gui.desktop.os; +import java.io.File; import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.file.Files; import java.nio.file.Path; +import org.jabref.cli.Launcher; import org.jabref.gui.DialogService; +import org.jabref.logic.util.BuildInfo; +import org.jabref.logic.util.OS; +import org.jabref.model.pdf.search.SearchFieldConstants; +import org.jabref.model.strings.StringUtil; +import org.jabref.preferences.FilePreferences; -public interface NativeDesktop { +import net.harawata.appdirs.AppDirsFactory; +import org.slf4j.LoggerFactory; - void openFile(String filePath, String fileType) throws IOException; +/** + * This class contains bundles OS specific implementations for file directories and file/application open handling methods. + * In case the default does not work, subclasses provide the correct behavior. + * + *

+ * We cannot use a static logger instance here in this class as the Logger first needs to be configured in the {@link Launcher#addLogToDisk} + * The configuration of tinylog will become immutable as soon as the first log entry is issued. + * https://tinylog.org/v2/configuration/ + *

+ */ +public abstract class NativeDesktop { + + public abstract void openFile(String filePath, String fileType, FilePreferences filePreferences) throws IOException; /** * Opens a file on an Operating System, using the given application. @@ -15,34 +38,91 @@ public interface NativeDesktop { * @param filePath The filename. * @param application Link to the app that opens the file. */ - void openFileWithApplication(String filePath, String application) throws IOException; + public abstract void openFileWithApplication(String filePath, String application) throws IOException; - void openFolderAndSelectFile(Path file) throws IOException; + public abstract void openFolderAndSelectFile(Path file) throws IOException; - void openConsole(String absolutePath, DialogService dialogService) throws IOException; + public abstract void openConsole(String absolutePath, DialogService dialogService) throws IOException; - String detectProgramPath(String programName, String directoryName); + public abstract String detectProgramPath(String programName, String directoryName); /** * Returns the path to the system's applications folder. * * @return the path to the applications folder. */ - Path getApplicationDirectory(); + public abstract Path getApplicationDirectory(); /** * Get the user's default file chooser directory * * @return The path to the directory */ - Path getDefaultFileChooserDirectory(); + public Path getDefaultFileChooserDirectory() { + Path userDirectory = getUserDirectory(); + Path documents = userDirectory.resolve("Documents"); + if (!Files.exists(documents)) { + return userDirectory; + } + return documents; + } /** * Returns the path to the system's user directory. * * @return the path to the user directory. */ - default Path getUserDirectory() { + public Path getUserDirectory() { return Path.of(System.getProperty("user.home")); } + + public Path getLogDirectory() { + return Path.of(AppDirsFactory.getInstance() + .getUserDataDir( + OS.APP_DIR_APP_NAME, + "logs", + OS.APP_DIR_APP_AUTHOR)) + .resolve(new BuildInfo().version.toString()); + } + + public Path getBackupDirectory() { + return Path.of(AppDirsFactory.getInstance() + .getUserDataDir( + OS.APP_DIR_APP_NAME, + "backups", + OS.APP_DIR_APP_AUTHOR)); + } + + public Path getFulltextIndexBaseDirectory() { + return Path.of(AppDirsFactory.getInstance() + .getUserDataDir(OS.APP_DIR_APP_NAME, + "lucene" + File.separator + SearchFieldConstants.VERSION, + OS.APP_DIR_APP_AUTHOR)); + } + + public Path getSslDirectory() { + return Path.of(AppDirsFactory.getInstance() + .getUserDataDir(OS.APP_DIR_APP_NAME, + "ssl", + OS.APP_DIR_APP_AUTHOR)); + } + + public String getHostName() { + String hostName; + // Following code inspired by https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/SystemUtils.html#getHostName-- + // See also https://stackoverflow.com/a/20793241/873282 + hostName = System.getenv("HOSTNAME"); + if (StringUtil.isBlank(hostName)) { + hostName = System.getenv("COMPUTERNAME"); + } + if (StringUtil.isBlank(hostName)) { + try { + hostName = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + LoggerFactory.getLogger(OS.class).info("Hostname not found. Using \"localhost\" as fallback.", e); + hostName = "localhost"; + } + } + return hostName; + } } diff --git a/src/main/java/org/jabref/gui/desktop/os/OSX.java b/src/main/java/org/jabref/gui/desktop/os/OSX.java index ece1d553d56..7adbd087927 100644 --- a/src/main/java/org/jabref/gui/desktop/os/OSX.java +++ b/src/main/java/org/jabref/gui/desktop/os/OSX.java @@ -5,17 +5,24 @@ import java.util.Optional; import org.jabref.architecture.AllowedToUseAwt; +import org.jabref.cli.Launcher; import org.jabref.gui.DialogService; -import org.jabref.gui.Globals; import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; - +import org.jabref.preferences.FilePreferences; + +/** + * This class contains macOS (OSX) specific implementations for file directories and file/application open handling methods
+ * We cannot use a static logger instance here in this class as the Logger first needs to be configured in the {@link Launcher#addLogToDisk} + * The configuration of tinylog will become immutable as soon as the first log entry is issued. + * https://tinylog.org/v2/configuration/ + **/ @AllowedToUseAwt("Requires AWT to open a file") -public class OSX implements NativeDesktop { +public class OSX extends NativeDesktop { @Override - public void openFile(String filePath, String fileType) throws IOException { - Optional type = ExternalFileTypes.getExternalFileTypeByExt(fileType, Globals.prefs.getFilePreferences()); + public void openFile(String filePath, String fileType, FilePreferences filePreferences) throws IOException { + Optional type = ExternalFileTypes.getExternalFileTypeByExt(fileType, filePreferences); if (type.isPresent() && !type.get().getOpenWithApplication().isEmpty()) { openFileWithApplication(filePath, type.get().getOpenWithApplication()); } else { @@ -52,9 +59,4 @@ public String detectProgramPath(String programName, String directoryName) { public Path getApplicationDirectory() { return Path.of("/Applications"); } - - @Override - public Path getDefaultFileChooserDirectory() { - return Path.of(System.getProperty("user.home") + "/Documents"); - } } diff --git a/src/main/java/org/jabref/gui/desktop/os/Windows.java b/src/main/java/org/jabref/gui/desktop/os/Windows.java index eb973986e0e..bacf32f6406 100644 --- a/src/main/java/org/jabref/gui/desktop/os/Windows.java +++ b/src/main/java/org/jabref/gui/desktop/os/Windows.java @@ -6,26 +6,31 @@ import java.nio.file.Path; import java.util.Optional; +import org.jabref.cli.Launcher; import org.jabref.gui.DialogService; -import org.jabref.gui.Globals; import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; +import org.jabref.preferences.FilePreferences; import com.sun.jna.platform.win32.KnownFolders; import com.sun.jna.platform.win32.Shell32Util; import com.sun.jna.platform.win32.ShlObj; import com.sun.jna.platform.win32.Win32Exception; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class Windows implements NativeDesktop { - private static final Logger LOGGER = LoggerFactory.getLogger(Windows.class); +/** + * This class contains Windows specific implementations for file directories and file/application open handling methods
+ * We cannot use a static logger instance here in this class as the Logger first needs to be configured in the {@link Launcher#addLogToDisk} + * The configuration of tinylog will become immutable as soon as the first log entry is issued. + * https://tinylog.org/v2/configuration/ + **/ +public class Windows extends NativeDesktop { private static final String DEFAULT_EXECUTABLE_EXTENSION = ".exe"; @Override - public void openFile(String filePath, String fileType) throws IOException { - Optional type = ExternalFileTypes.getExternalFileTypeByExt(fileType, Globals.prefs.getFilePreferences()); + public void openFile(String filePath, String fileType, FilePreferences filePreferences) throws IOException { + Optional type = ExternalFileTypes.getExternalFileTypeByExt(fileType, filePreferences); if (type.isPresent() && !type.get().getOpenWithApplication().isEmpty()) { openFileWithApplication(filePath, type.get().getOpenWithApplication()); @@ -89,7 +94,8 @@ public Path getDefaultFileChooserDirectory() { return Path.of(Shell32Util.getFolderPath(ShlObj.CSIDL_MYDOCUMENTS)); } } catch (Win32Exception e) { - LOGGER.error("Error accessing folder", e); + // needs to be non-static because of org.jabref.cli.Launcher.addLogToDisk + LoggerFactory.getLogger(Windows.class).error("Error accessing folder", e); return Path.of(System.getProperty("user.home")); } } diff --git a/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java b/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java index a7bc85890ee..56fe6777872 100644 --- a/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java +++ b/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java @@ -1,9 +1,10 @@ package org.jabref.gui.dialogs; -import org.jabref.gui.Globals; import org.jabref.gui.LibraryTab; import org.jabref.gui.exporter.SaveDatabaseAction; import org.jabref.model.database.event.AutosaveEvent; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.preferences.PreferencesService; import com.google.common.eventbus.Subscribe; import org.slf4j.Logger; @@ -16,10 +17,10 @@ public class AutosaveUiManager { private static final Logger LOGGER = LoggerFactory.getLogger(AutosaveUiManager.class); - private SaveDatabaseAction saveDatabaseAction; + private final SaveDatabaseAction saveDatabaseAction; - public AutosaveUiManager(LibraryTab libraryTab) { - this.saveDatabaseAction = new SaveDatabaseAction(libraryTab, Globals.prefs, Globals.entryTypesManager); + public AutosaveUiManager(LibraryTab libraryTab, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager) { + this.saveDatabaseAction = new SaveDatabaseAction(libraryTab, preferencesService, entryTypesManager); } @Subscribe diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index ceba43dd088..05ba9755db4 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -8,14 +8,13 @@ import javafx.scene.control.ButtonType; import org.jabref.gui.DialogService; -import org.jabref.gui.Globals; +import org.jabref.gui.autosaveandbackup.BackupManager; import org.jabref.gui.backup.BackupResolverDialog; import org.jabref.gui.collab.DatabaseChange; import org.jabref.gui.collab.DatabaseChangeList; import org.jabref.gui.collab.DatabaseChangeResolverFactory; import org.jabref.gui.collab.DatabaseChangesResolverDialog; import org.jabref.gui.util.DefaultTaskExecutor; -import org.jabref.logic.autosaveandbackup.BackupManager; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.OpenDatabase; import org.jabref.logic.importer.ParserResult; @@ -23,6 +22,8 @@ import org.jabref.logic.util.io.BackupFileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.util.DummyFileUpdateMonitor; +import org.jabref.model.util.FileUpdateMonitor; +import org.jabref.preferences.ExternalApplicationsPreferences; import org.jabref.preferences.PreferencesService; import org.slf4j.Logger; @@ -37,36 +38,51 @@ public class BackupUIManager { private BackupUIManager() { } - public static Optional showRestoreBackupDialog(DialogService dialogService, Path originalPath, PreferencesService preferencesService) { - var actionOpt = showBackupResolverDialog(dialogService, originalPath); + public static Optional showRestoreBackupDialog(DialogService dialogService, + Path originalPath, + PreferencesService preferencesService, + FileUpdateMonitor fileUpdateMonitor) { + var actionOpt = showBackupResolverDialog( + dialogService, + preferencesService.getExternalApplicationsPreferences(), + originalPath, + preferencesService.getFilePreferences().getBackupDirectory()); return actionOpt.flatMap(action -> { if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { - BackupManager.restoreBackup(originalPath); + BackupManager.restoreBackup(originalPath, preferencesService.getFilePreferences().getBackupDirectory()); return Optional.empty(); } else if (action == BackupResolverDialog.REVIEW_BACKUP) { - return showReviewBackupDialog(dialogService, originalPath, preferencesService); + return showReviewBackupDialog(dialogService, originalPath, preferencesService, fileUpdateMonitor); } return Optional.empty(); }); } - private static Optional showBackupResolverDialog(DialogService dialogService, Path originalPath) { - return DefaultTaskExecutor.runInJavaFXThread(() -> dialogService.showCustomDialogAndWait(new BackupResolverDialog(originalPath))); + private static Optional showBackupResolverDialog(DialogService dialogService, + ExternalApplicationsPreferences externalApplicationsPreferences, + Path originalPath, + Path backupDir) { + return DefaultTaskExecutor.runInJavaFXThread( + () -> dialogService.showCustomDialogAndWait(new BackupResolverDialog(originalPath, backupDir, externalApplicationsPreferences))); } - private static Optional showReviewBackupDialog(DialogService dialogService, Path originalPath, PreferencesService preferencesService) { + private static Optional showReviewBackupDialog( + DialogService dialogService, + Path originalPath, + PreferencesService preferencesService, + FileUpdateMonitor fileUpdateMonitor) { try { - ImportFormatPreferences importFormatPreferences = Globals.prefs.getImportFormatPreferences(); + ImportFormatPreferences importFormatPreferences = preferencesService.getImportFormatPreferences(); // The database of the originalParserResult will be modified - ParserResult originalParserResult = OpenDatabase.loadDatabase(originalPath, importFormatPreferences, Globals.getFileUpdateMonitor()); + ParserResult originalParserResult = OpenDatabase.loadDatabase(originalPath, importFormatPreferences, fileUpdateMonitor); // This will be modified by using the `DatabaseChangesResolverDialog`. BibDatabaseContext originalDatabase = originalParserResult.getDatabaseContext(); - Path backupPath = BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP).orElseThrow(); + Path backupPath = BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, preferencesService.getFilePreferences().getBackupDirectory()).orElseThrow(); BibDatabaseContext backupDatabase = OpenDatabase.loadDatabase(backupPath, importFormatPreferences, new DummyFileUpdateMonitor()).getDatabaseContext(); - DatabaseChangeResolverFactory changeResolverFactory = new DatabaseChangeResolverFactory(dialogService, originalDatabase, preferencesService.getBibEntryPreferences()); + DatabaseChangeResolverFactory changeResolverFactory = new DatabaseChangeResolverFactory(dialogService, originalDatabase, preferencesService); return DefaultTaskExecutor.runInJavaFXThread(() -> { List changes = DatabaseChangeList.compareAndGetChanges(originalDatabase, backupDatabase, changeResolverFactory); @@ -77,7 +93,7 @@ private static Optional showReviewBackupDialog(DialogService dialo var allChangesResolved = dialogService.showCustomDialogAndWait(reviewBackupDialog); if (allChangesResolved.isEmpty() || !allChangesResolved.get()) { // In case not all changes are resolved, start from scratch - return showRestoreBackupDialog(dialogService, originalPath, preferencesService); + return showRestoreBackupDialog(dialogService, originalPath, preferencesService, fileUpdateMonitor); } // This does NOT return the original ParserResult, but a modified version with all changes accepted or rejected diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentPageViewModel.java b/src/main/java/org/jabref/gui/documentviewer/DocumentPageViewModel.java index 9fe04ab71f5..78203fd9b9b 100644 --- a/src/main/java/org/jabref/gui/documentviewer/DocumentPageViewModel.java +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentPageViewModel.java @@ -9,9 +9,6 @@ public abstract class DocumentPageViewModel { /** * Renders this page and returns an image representation of itself. - * - * @param width - * @param height */ public abstract Image render(int width, int height); diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerView.java b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerView.java index e75b607e505..17a428b0c95 100644 --- a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerView.java +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerView.java @@ -51,7 +51,7 @@ public DocumentViewerView() { // Remove button bar at bottom, but add close button to keep the dialog closable by clicking the "x" window symbol getDialogPane().getButtonTypes().add(ButtonType.CLOSE); - getDialogPane().getChildren().removeIf(node -> node instanceof ButtonBar); + getDialogPane().getChildren().removeIf(ButtonBar.class::isInstance); } @FXML diff --git a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java index 9010ec3379c..607709742b1 100644 --- a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java +++ b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java @@ -5,8 +5,9 @@ import java.io.IOException; import java.util.Objects; -import javafx.embed.swing.SwingFXUtils; import javafx.scene.image.Image; +import javafx.scene.image.PixelWriter; +import javafx.scene.image.WritableImage; import org.jabref.architecture.AllowedToUseAwt; @@ -51,7 +52,7 @@ public Image render(int width, int height) { try { int resolution = 96; BufferedImage image = renderer.renderImageWithDPI(pageNumber, 2 * resolution, ImageType.RGB); - return SwingFXUtils.toFXImage(resize(image, width, height), null); + return convertToFxImage(resize(image, width, height)); } catch (IOException e) { // TODO: LOG return null; @@ -68,4 +69,19 @@ public double getAspectRatio() { PDRectangle mediaBox = page.getMediaBox(); return mediaBox.getWidth() / mediaBox.getHeight(); } + + // See https://stackoverflow.com/a/57552025/3450689 + private static Image convertToFxImage(BufferedImage image) { + WritableImage wr = null; + if (image != null) { + wr = new WritableImage(image.getWidth(), image.getHeight()); + PixelWriter pw = wr.getPixelWriter(); + for (int x = 0; x < image.getWidth(); x++) { + for (int y = 0; y < image.getHeight(); y++) { + pw.setArgb(x, y, image.getRGB(x, y)); + } + } + } + return wr; + } } diff --git a/src/main/java/org/jabref/gui/duplicationFinder/DuplicateResolverDialog.java b/src/main/java/org/jabref/gui/duplicationFinder/DuplicateResolverDialog.java index 15a27d4f810..9cdf97b0de9 100644 --- a/src/main/java/org/jabref/gui/duplicationFinder/DuplicateResolverDialog.java +++ b/src/main/java/org/jabref/gui/duplicationFinder/DuplicateResolverDialog.java @@ -77,21 +77,21 @@ private void init(BibEntry one, BibEntry two, DuplicateResolverType type) { first = new ButtonType(Localization.lang("Keep left"), ButtonData.LEFT); second = new ButtonType(Localization.lang("Keep right"), ButtonData.LEFT); both = new ButtonType(Localization.lang("Keep both"), ButtonData.LEFT); - threeWayMerge = new ThreeWayMergeView(one, two, preferencesService.getBibEntryPreferences()); + threeWayMerge = new ThreeWayMergeView(one, two, preferencesService); } case DUPLICATE_SEARCH_WITH_EXACT -> { first = new ButtonType(Localization.lang("Keep left"), ButtonData.LEFT); second = new ButtonType(Localization.lang("Keep right"), ButtonData.LEFT); both = new ButtonType(Localization.lang("Keep both"), ButtonData.LEFT); removeExactVisible = true; - threeWayMerge = new ThreeWayMergeView(one, two, preferencesService.getBibEntryPreferences()); + threeWayMerge = new ThreeWayMergeView(one, two, preferencesService); } case IMPORT_CHECK -> { first = new ButtonType(Localization.lang("Keep old entry"), ButtonData.LEFT); second = new ButtonType(Localization.lang("Keep from import"), ButtonData.LEFT); both = new ButtonType(Localization.lang("Keep both"), ButtonData.LEFT); threeWayMerge = new ThreeWayMergeView(one, two, Localization.lang("Old entry"), - Localization.lang("From import"), preferencesService.getBibEntryPreferences()); + Localization.lang("From import"), preferencesService); } default -> throw new IllegalStateException("Switch expression should be exhaustive"); } @@ -106,7 +106,7 @@ private void init(BibEntry one, BibEntry two, DuplicateResolverType type) { // Read more: https://stackoverflow.com/questions/45866249/javafx-8-alert-different-button-sizes getDialogPane().getButtonTypes().stream() .map(getDialogPane()::lookupButton) - .forEach(btn-> ButtonBar.setButtonUniformSize(btn, false)); + .forEach(btn -> ButtonBar.setButtonUniformSize(btn, false)); } // Retrieves the previous window state and sets the new dialog window size and position to match it @@ -138,7 +138,7 @@ private void init(BibEntry one, BibEntry two, DuplicateResolverType type) { return null; }); - HelpAction helpCommand = new HelpAction(HelpFile.FIND_DUPLICATES, dialogService); + HelpAction helpCommand = new HelpAction(HelpFile.FIND_DUPLICATES, dialogService, preferencesService.getFilePreferences()); Button helpButton = actionFactory.createIconButton(StandardActions.HELP, helpCommand); borderPane.setRight(helpButton); diff --git a/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java b/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java index 611240c2701..29d91ec02d1 100644 --- a/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java +++ b/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java @@ -87,8 +87,8 @@ public void execute() { } private void searchPossibleDuplicates(List entries, BibDatabaseMode databaseMode) { - for (int i = 0; (i < (entries.size() - 1)); i++) { - for (int j = i + 1; (j < entries.size()); j++) { + for (int i = 0; i < (entries.size() - 1); i++) { + for (int j = i + 1; j < entries.size(); j++) { if (Thread.interrupted()) { return; } diff --git a/src/main/java/org/jabref/gui/edit/CopyMoreAction.java b/src/main/java/org/jabref/gui/edit/CopyMoreAction.java index 2da415b3485..0f9bbe3c103 100644 --- a/src/main/java/org/jabref/gui/edit/CopyMoreAction.java +++ b/src/main/java/org/jabref/gui/edit/CopyMoreAction.java @@ -8,12 +8,12 @@ import org.jabref.gui.ClipBoardManager; import org.jabref.gui.DialogService; -import org.jabref.gui.Globals; import org.jabref.gui.JabRefDialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.actions.StandardActions; +import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.l10n.Localization; import org.jabref.logic.layout.Layout; import org.jabref.logic.layout.LayoutHelper; @@ -33,17 +33,20 @@ public class CopyMoreAction extends SimpleCommand { private final StateManager stateManager; private final ClipBoardManager clipBoardManager; private final PreferencesService preferencesService; + private final JournalAbbreviationRepository abbreviationRepository; public CopyMoreAction(StandardActions action, DialogService dialogService, StateManager stateManager, ClipBoardManager clipBoardManager, - PreferencesService preferencesService) { + PreferencesService preferencesService, + JournalAbbreviationRepository abbreviationRepository) { this.action = action; this.dialogService = dialogService; this.stateManager = stateManager; this.clipBoardManager = clipBoardManager; this.preferencesService = preferencesService; + this.abbreviationRepository = abbreviationRepository; this.executable.bind(ActionHelper.needsEntriesSelected(stateManager)); } @@ -192,7 +195,7 @@ private void copyKeyAndTitle() { StringReader layoutString = new StringReader("\\citationkey - \\begin{title}\\format[RemoveBrackets]{\\title}\\end{title}\n"); Layout layout; try { - layout = new LayoutHelper(layoutString, preferencesService.getLayoutFormatterPreferences(Globals.journalAbbreviationRepository)).getLayoutFromText(); + layout = new LayoutHelper(layoutString, preferencesService.getLayoutFormatterPreferences(), abbreviationRepository).getLayoutFromText(); } catch (IOException e) { LOGGER.info("Could not get layout.", e); return; diff --git a/src/main/java/org/jabref/gui/edit/EditAction.java b/src/main/java/org/jabref/gui/edit/EditAction.java index 9942c6b5d29..b0daa482cb0 100644 --- a/src/main/java/org/jabref/gui/edit/EditAction.java +++ b/src/main/java/org/jabref/gui/edit/EditAction.java @@ -1,6 +1,5 @@ package org.jabref.gui.edit; -import javafx.scene.control.TextField; import javafx.scene.control.TextInputControl; import javafx.scene.web.WebView; @@ -23,7 +22,6 @@ public class EditAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(EditAction.class); private final JabRefFrame frame; - private TextField text; private final StandardActions action; private final StateManager stateManager; @@ -47,35 +45,48 @@ public String toString() { @Override public void execute() { stateManager.getFocusOwner().ifPresent(focusOwner -> { - LOGGER.debug("focusOwner: {}; Action: {}", focusOwner.toString(), action.getText()); - if (focusOwner instanceof TextInputControl) { + LOGGER.debug("focusOwner: {}; Action: {}", focusOwner, action.getText()); + if (focusOwner instanceof TextInputControl textInput) { // 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 SELECT_ALL -> textInput.selectAll(); case COPY -> textInput.copy(); - case UNDO -> textInput.undo(); - case REDO -> textInput.redo(); case CUT -> textInput.cut(); case PASTE -> textInput.paste(); case DELETE -> textInput.clear(); - case SELECT_ALL -> textInput.selectAll(); case DELETE_ENTRY -> textInput.deleteNextChar(); - default -> throw new IllegalStateException("Only cut/copy/paste supported in TextInputControl but got " + action); + case UNDO -> textInput.undo(); + case REDO -> textInput.redo(); + default -> { + String message = "Only cut/copy/paste supported in TextInputControl but got " + action; + LOGGER.error(message); + throw new IllegalStateException(message); + } } } else if ((focusOwner instanceof CodeArea) || (focusOwner instanceof WebView)) { + LOGGER.debug("Ignoring request in CodeArea or WebView"); return; } else { LOGGER.debug("Else: {}", focusOwner.getClass().getSimpleName()); // Not sure what is selected -> copy/paste/cut selected entries except for Preview and CodeArea - // ToDo: Should be handled by BibDatabaseContext instead of LibraryTab switch (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); + case UNDO -> { + if (frame.getUndoManager().canUndo()) { + frame.getUndoManager().undo(); + } + } + case REDO -> { + if (frame.getUndoManager().canRedo()) { + frame.getUndoManager().redo(); + } + } + default -> LOGGER.debug("Only cut/copy/paste/deleteEntry supported but got: {} and focus owner {}", action, focusOwner); } } }); diff --git a/src/main/java/org/jabref/gui/edit/OpenBrowserAction.java b/src/main/java/org/jabref/gui/edit/OpenBrowserAction.java index c6eb7653ae8..967ffef9e42 100644 --- a/src/main/java/org/jabref/gui/edit/OpenBrowserAction.java +++ b/src/main/java/org/jabref/gui/edit/OpenBrowserAction.java @@ -3,19 +3,22 @@ import org.jabref.gui.DialogService; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.desktop.JabRefDesktop; +import org.jabref.preferences.FilePreferences; public class OpenBrowserAction extends SimpleCommand { private final String urlToOpen; private final DialogService dialogService; + private final FilePreferences filePreferences; - public OpenBrowserAction(String urlToOpen, DialogService dialogService) { + public OpenBrowserAction(String urlToOpen, DialogService dialogService, FilePreferences filePreferences) { this.urlToOpen = urlToOpen; this.dialogService = dialogService; + this.filePreferences = filePreferences; } @Override public void execute() { - JabRefDesktop.openBrowserShowPopup(urlToOpen, dialogService); + JabRefDesktop.openBrowserShowPopup(urlToOpen, dialogService, filePreferences); } } diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java index 9f6a4663d9d..b23e32788ec 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java @@ -66,7 +66,7 @@ public AutomaticFieldEditorDialog(StateManager stateManager) { // Read more: https://stackoverflow.com/questions/45866249/javafx-8-alert-different-button-sizes getDialogPane().getButtonTypes().stream() .map(getDialogPane()::lookupButton) - .forEach(btn-> ButtonBar.setButtonUniformSize(btn, false)); + .forEach(btn -> ButtonBar.setButtonUniformSize(btn, false)); } @FXML diff --git a/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java b/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java new file mode 100644 index 00000000000..269ab381a83 --- /dev/null +++ b/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java @@ -0,0 +1,91 @@ +package org.jabref.gui.entryeditor; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.swing.undo.UndoManager; + +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; +import org.jabref.gui.autocompleter.SuggestionProviders; +import org.jabref.gui.fieldeditors.FieldEditorFX; +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.theme.ThemeManager; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.journals.JournalAbbreviationRepository; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.pdf.search.indexing.IndexingTaskManager; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.field.UserSpecificCommentField; +import org.jabref.preferences.PreferencesService; + +public class CommentsTab extends FieldsEditorTab { + public static final String NAME = "Comments"; + + private final String defaultOwner; + public CommentsTab(PreferencesService preferences, + BibDatabaseContext databaseContext, + SuggestionProviders suggestionProviders, + UndoManager undoManager, + DialogService dialogService, + StateManager stateManager, + ThemeManager themeManager, + IndexingTaskManager indexingTaskManager, + TaskExecutor taskExecutor, + JournalAbbreviationRepository journalAbbreviationRepository) { + super( + false, + databaseContext, + suggestionProviders, + undoManager, + dialogService, + preferences, + stateManager, + themeManager, + taskExecutor, + journalAbbreviationRepository, + indexingTaskManager + ); + this.defaultOwner = preferences.getOwnerPreferences().getDefaultOwner(); + setText(Localization.lang("Comments")); + setGraphic(IconTheme.JabRefIcons.COMMENT.getGraphicNode()); + } + + @Override + protected Set determineFieldsToShow(BibEntry entry) { + UserSpecificCommentField defaultCommentField = new UserSpecificCommentField(defaultOwner); + + // As default: Show BibTeX comment field and the user-specific comment field of the default owner + Set comments = new LinkedHashSet<>(Set.of(defaultCommentField, StandardField.COMMENT)); + + comments.addAll(entry.getFields().stream() + .filter(field -> field instanceof UserSpecificCommentField || + field.getName().toLowerCase().contains("comment")) + .collect(Collectors.toSet())); + + return comments; + } + + @Override + protected void setupPanel(BibEntry entry, boolean compressed) { + super.setupPanel(entry, compressed); + + for (Map.Entry fieldEditorEntry : editors.entrySet()) { + Field field = fieldEditorEntry.getKey(); + FieldEditorFX editor = fieldEditorEntry.getValue(); + + if (field instanceof UserSpecificCommentField) { + if (field.getName().contains(defaultOwner)) { + editor.getNode().setDisable(false); + } + } else { + editor.getNode().setDisable(!field.getName().equals(StandardField.COMMENT.getName())); + } + } + } +} diff --git a/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java index 91d876c41b5..a40302d54c6 100644 --- a/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java @@ -30,6 +30,7 @@ public class DeprecatedFieldsTab extends FieldsEditorTab { + public static final String NAME = "Deprecated fields"; private final BibEntryTypesManager entryTypesManager; public DeprecatedFieldsTab(BibDatabaseContext databaseContext, @@ -47,7 +48,7 @@ public DeprecatedFieldsTab(BibDatabaseContext databaseContext, this.entryTypesManager = entryTypesManager; setText(Localization.lang("Deprecated fields")); - EasyBind.subscribe(preferences.getGeneralPreferences().showAdvancedHintsProperty(), advancedHints -> { + EasyBind.subscribe(preferences.getWorkspacePreferences().showAdvancedHintsProperty(), advancedHints -> { if (advancedHints) { setTooltip(new Tooltip(Localization.lang("Shows fields having a successor in biblatex.\nFor instance, the publication month should be part of the date field.\nUse the Cleanup Entries functionality to convert the entry to biblatex."))); } else { diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index f6cdc84bfe2..349f01cded2 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -3,6 +3,7 @@ import java.io.File; import java.nio.file.Path; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -121,7 +122,7 @@ public EntryEditor(LibraryTab libraryTab) { .load(); this.entryEditorPreferences = preferencesService.getEntryEditorPreferences(); - this.fileLinker = new ExternalFilesEntryLinker(preferencesService.getFilePreferences(), databaseContext); + this.fileLinker = new ExternalFilesEntryLinker(preferencesService.getFilePreferences(), databaseContext, dialogService); EasyBind.subscribe(tabbed.getSelectionModel().selectedItemProperty(), tab -> { EntryEditorTab activeTab = (EntryEditorTab) tab; @@ -146,19 +147,19 @@ public EntryEditor(LibraryTab libraryTab) { boolean success = false; if (event.getDragboard().hasContent(DataFormat.FILES)) { - List files = event.getDragboard().getFiles().stream().map(File::toPath).collect(Collectors.toList()); + List draggedFiles = event.getDragboard().getFiles().stream().map(File::toPath).collect(Collectors.toList()); switch (event.getTransferMode()) { case COPY -> { LOGGER.debug("Mode COPY"); - fileLinker.copyFilesToFileDirAndAddToEntry(entry, files, libraryTab.getIndexingTaskManager()); + fileLinker.copyFilesToFileDirAndAddToEntry(entry, draggedFiles, libraryTab.getIndexingTaskManager()); } case MOVE -> { LOGGER.debug("Mode MOVE"); - fileLinker.moveFilesToFileDirAndAddToEntry(entry, files, libraryTab.getIndexingTaskManager()); + fileLinker.moveFilesToFileDirRenameAndAddToEntry(entry, draggedFiles, libraryTab.getIndexingTaskManager()); } case LINK -> { LOGGER.debug("Mode LINK"); - fileLinker.addFilesToEntry(entry, files); + fileLinker.addFilesToEntry(entry, draggedFiles); } } success = true; @@ -196,7 +197,7 @@ private void setupKeyBindings() { event.consume(); break; case HELP: - new HelpAction(HelpFile.ENTRY_EDITOR, dialogService).execute(); + new HelpAction(HelpFile.ENTRY_EDITOR, dialogService, preferencesService.getFilePreferences()).execute(); event.consume(); break; case CLOSE: @@ -245,43 +246,55 @@ private void navigateToNextEntry() { } private List createTabs() { - // Preview tab entryEditorTabs.add(new PreviewTab(databaseContext, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager())); - // Required fields + // Required, optional, deprecated, and "other" fields entryEditorTabs.add(new RequiredFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), bibEntryTypesManager, taskExecutor, journalAbbreviationRepository)); - - // Optional fields entryEditorTabs.add(new OptionalFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), bibEntryTypesManager, taskExecutor, journalAbbreviationRepository)); entryEditorTabs.add(new OptionalFields2Tab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), bibEntryTypesManager, taskExecutor, journalAbbreviationRepository)); entryEditorTabs.add(new DeprecatedFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), bibEntryTypesManager, taskExecutor, journalAbbreviationRepository)); - - // Other fields entryEditorTabs.add(new OtherFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), bibEntryTypesManager, taskExecutor, journalAbbreviationRepository)); + // Comment Tab: Tab for general and user-specific comments + entryEditorTabs.add(new CommentsTab(preferencesService, databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), taskExecutor, journalAbbreviationRepository)); + // General fields from preferences - for (Map.Entry> tab : entryEditorPreferences.getEntryEditorTabList().entrySet()) { + // First, remove all tabs that are already handled above or below; except for the source tab (which has different titles for BibTeX and BibLaTeX mode) + Map> entryEditorTabList = new HashMap<>(entryEditorPreferences.getEntryEditorTabs()); + entryEditorTabList.remove(PreviewTab.NAME); + entryEditorTabList.remove(RequiredFieldsTab.NAME); + entryEditorTabList.remove(OptionalFieldsTab.NAME); + entryEditorTabList.remove(OptionalFields2Tab.NAME); + entryEditorTabList.remove(DeprecatedFieldsTab.NAME); + entryEditorTabList.remove(CommentsTab.NAME); + entryEditorTabList.remove(MathSciNetTab.NAME); + entryEditorTabList.remove(FileAnnotationTab.NAME); + entryEditorTabList.remove(RelatedArticlesTab.NAME); + entryEditorTabList.remove(LatexCitationsTab.NAME); + entryEditorTabList.remove(FulltextSearchResultsTab.NAME); + entryEditorTabList.remove("Comments"); + // Then show the remaining configured + for (Map.Entry> tab : entryEditorTabList.entrySet()) { entryEditorTabs.add(new UserDefinedFieldsTab(tab.getKey(), tab.getValue(), databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), taskExecutor, journalAbbreviationRepository)); } - // Special tabs + // "Special" tabs entryEditorTabs.add(new MathSciNetTab()); entryEditorTabs.add(new FileAnnotationTab(libraryTab.getAnnotationCache())); entryEditorTabs.add(new RelatedArticlesTab(this, entryEditorPreferences, preferencesService, dialogService)); - // Source tab sourceTab = new SourceTab( databaseContext, undoManager, - preferencesService.getFieldWriterPreferences(), + preferencesService.getFieldPreferences(), preferencesService.getImportFormatPreferences(), fileMonitor, dialogService, stateManager, + bibEntryTypesManager, keyBindingRepository); entryEditorTabs.add(sourceTab); - // LaTeX citations tab entryEditorTabs.add(new LatexCitationsTab(databaseContext, preferencesService, taskExecutor, dialogService)); entryEditorTabs.add(new FulltextSearchResultsTab(stateManager, preferencesService, dialogService)); @@ -408,8 +421,8 @@ private void fetchAndMerge(EntryBasedFetcher fetcher) { public void setFocusToField(Field field) { DefaultTaskExecutor.runInJavaFXThread(() -> { for (Tab tab : tabbed.getTabs()) { - if ((tab instanceof FieldsEditorTab fieldsEditorTab) && fieldsEditorTab.getShownFields() - .contains(field)) { + if ((tab instanceof FieldsEditorTab fieldsEditorTab) + && fieldsEditorTab.getShownFields().contains(field)) { tabbed.getSelectionModel().select(tab); fieldsEditorTab.requestFocus(field); } diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java index 8e8a5b0a76d..73431acb78d 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java @@ -6,9 +6,11 @@ import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.MapProperty; +import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleMapProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableMap; @@ -16,42 +18,66 @@ public class EntryEditorPreferences { + /** + * Specifies the different possible enablement states for online services + */ + public enum JournalPopupEnabled { + FIRST_START, // The first time a user uses this service + ENABLED, + DISABLED; + + public static JournalPopupEnabled fromString(String status) { + for (JournalPopupEnabled value : JournalPopupEnabled.values()) { + if (value.toString().equalsIgnoreCase(status)) { + return value; + } + } + throw new IllegalArgumentException("No enum found with value: " + status); + } + } + private final MapProperty> entryEditorTabList; + private final MapProperty> defaultEntryEditorTabList; private final BooleanProperty shouldOpenOnNewEntry; private final BooleanProperty shouldShowRecommendationsTab; - private final BooleanProperty isMrdlibAccepted; private final BooleanProperty shouldShowLatexCitationsTab; private final BooleanProperty showSourceTabByDefault; private final BooleanProperty enableValidation; private final BooleanProperty allowIntegerEditionBibtex; private final DoubleProperty dividerPosition; + private final BooleanProperty autoLinkFiles; + private final ObjectProperty enablementStatus; public EntryEditorPreferences(Map> entryEditorTabList, + Map> defaultEntryEditorTabList, boolean shouldOpenOnNewEntry, boolean shouldShowRecommendationsTab, - boolean isMrdlibAccepted, boolean shouldShowLatexCitationsTab, boolean showSourceTabByDefault, boolean enableValidation, boolean allowIntegerEditionBibtex, - double dividerPosition) { + double dividerPosition, + boolean autolinkFilesEnabled, + JournalPopupEnabled journalPopupEnabled) { this.entryEditorTabList = new SimpleMapProperty<>(FXCollections.observableMap(entryEditorTabList)); + this.defaultEntryEditorTabList = new SimpleMapProperty<>(FXCollections.observableMap(defaultEntryEditorTabList)); this.shouldOpenOnNewEntry = new SimpleBooleanProperty(shouldOpenOnNewEntry); this.shouldShowRecommendationsTab = new SimpleBooleanProperty(shouldShowRecommendationsTab); - this.isMrdlibAccepted = new SimpleBooleanProperty(isMrdlibAccepted); this.shouldShowLatexCitationsTab = new SimpleBooleanProperty(shouldShowLatexCitationsTab); this.showSourceTabByDefault = new SimpleBooleanProperty(showSourceTabByDefault); this.enableValidation = new SimpleBooleanProperty(enableValidation); this.allowIntegerEditionBibtex = new SimpleBooleanProperty(allowIntegerEditionBibtex); this.dividerPosition = new SimpleDoubleProperty(dividerPosition); + this.autoLinkFiles = new SimpleBooleanProperty(autolinkFilesEnabled); + this.enablementStatus = new SimpleObjectProperty<>(journalPopupEnabled); } - public ObservableMap> getEntryEditorTabList() { + public ObservableMap> getEntryEditorTabs() { return entryEditorTabList.get(); } - public MapProperty> entryEditorTabListProperty() { + public MapProperty> entryEditorTabs() { return entryEditorTabList; } @@ -59,6 +85,10 @@ public void setEntryEditorTabList(Map> entryEditorTabList) { this.entryEditorTabList.set(FXCollections.observableMap(entryEditorTabList)); } + public ObservableMap> getDefaultEntryEditorTabs() { + return defaultEntryEditorTabList.get(); + } + public boolean shouldOpenOnNewEntry() { return shouldOpenOnNewEntry.get(); } @@ -83,18 +113,6 @@ public void setShouldShowRecommendationsTab(boolean shouldShowRecommendationsTab this.shouldShowRecommendationsTab.set(shouldShowRecommendationsTab); } - public boolean isMrdlibAccepted() { - return isMrdlibAccepted.get(); - } - - public BooleanProperty isMrdlibAcceptedProperty() { - return isMrdlibAccepted; - } - - public void setIsMrdlibAccepted(boolean isMrdlibAccepted) { - this.isMrdlibAccepted.set(isMrdlibAccepted); - } - public boolean shouldShowLatexCitationsTab() { return shouldShowLatexCitationsTab.get(); } @@ -154,4 +172,28 @@ public DoubleProperty dividerPositionProperty() { public void setDividerPosition(double dividerPosition) { this.dividerPosition.set(dividerPosition); } + + public boolean autoLinkFilesEnabled() { + return this.autoLinkFiles.getValue(); + } + + public BooleanProperty autoLinkEnabledProperty() { + return this.autoLinkFiles; + } + + public void setAutoLinkFilesEnabled(boolean enabled) { + this.autoLinkFiles.setValue(enabled); + } + + public JournalPopupEnabled shouldEnableJournalPopup() { + return enablementStatus.get(); + } + + public ObjectProperty enableJournalPopupProperty() { + return enablementStatus; + } + + public void setEnableJournalPopup(JournalPopupEnabled journalPopupEnabled) { + this.enablementStatus.set(journalPopupEnabled); + } } diff --git a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java index 49fc1d8715b..8ab2b955eae 100644 --- a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java @@ -46,7 +46,7 @@ */ abstract class FieldsEditorTab extends EntryEditorTab { protected final BibDatabaseContext databaseContext; - private final Map editors = new LinkedHashMap<>(); + protected final Map editors = new LinkedHashMap<>(); private final boolean isCompressed; private final SuggestionProviders suggestionProviders; private final DialogService dialogService; @@ -93,7 +93,7 @@ private static void addColumn(GridPane gridPane, int columnIndex, Stream gridPane.addColumn(columnIndex, nodes.toArray(Node[]::new)); } - private void setupPanel(BibEntry entry, boolean compressed) { + protected void setupPanel(BibEntry entry, boolean compressed) { // The preferences might be not initialized in tests -> return immediately // TODO: Replace this ugly workaround by proper injection propagation if (preferences == null) { @@ -184,9 +184,6 @@ private void setCompressedRowLayout(GridPane gridPane, int rows) { } } - /** - * Focuses the given field. - */ public void requestFocus(Field fieldName) { if (editors.containsKey(fieldName)) { editors.get(fieldName).focus(); @@ -202,7 +199,6 @@ public boolean shouldShow(BibEntry entry) { protected void bindToEntry(BibEntry entry) { initPanel(); setupPanel(entry, isCompressed); - if (previewPanel != null) { previewPanel.setEntry(entry); } diff --git a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java index 87658d4e46c..fd3b47a43f9 100644 --- a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java @@ -27,6 +27,7 @@ public class LatexCitationsTab extends EntryEditorTab { + public static final String NAME = "LaTeX Citations"; private final LatexCitationsTabViewModel viewModel; private final GridPane searchPane; private final ProgressIndicator progressIndicator; diff --git a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java index fbfbb5c37e3..fc2ce8361fe 100644 --- a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java +++ b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java @@ -68,7 +68,7 @@ public LatexCitationsTabViewModel(BibDatabaseContext databaseContext, this.preferencesService = preferencesService; this.taskExecutor = taskExecutor; this.dialogService = dialogService; - this.directory = new SimpleObjectProperty<>(databaseContext.getMetaData().getLatexFileDirectory(preferencesService.getFilePreferences().getUser()) + this.directory = new SimpleObjectProperty<>(databaseContext.getMetaData().getLatexFileDirectory(preferencesService.getFilePreferences().getUserAndHost()) .orElse(FileUtil.getInitialDirectory(databaseContext, preferencesService.getFilePreferences().getWorkingDirectory()))); this.citationList = FXCollections.observableArrayList(); this.status = new SimpleObjectProperty<>(Status.IN_PROGRESS); @@ -130,7 +130,7 @@ private void cancelSearch() { private Collection searchAndParse(String citeKey) throws IOException { // we need to check whether the user meanwhile set the LaTeX file directory or the database changed locations - Path newDirectory = databaseContext.getMetaData().getLatexFileDirectory(preferencesService.getFilePreferences().getUser()) + Path newDirectory = databaseContext.getMetaData().getLatexFileDirectory(preferencesService.getFilePreferences().getUserAndHost()) .orElse(FileUtil.getInitialDirectory(databaseContext, preferencesService.getFilePreferences().getWorkingDirectory())); if (latexParserResult == null || !newDirectory.equals(directory.get())) { @@ -172,7 +172,7 @@ public void setLatexDirectory() { .withInitialDirectory(directory.get()).build(); dialogService.showDirectorySelectionDialog(directoryDialogConfiguration).ifPresent(selectedDirectory -> - databaseContext.getMetaData().setLatexFileDirectory(preferencesService.getFilePreferences().getUser(), selectedDirectory.toAbsolutePath())); + databaseContext.getMetaData().setLatexFileDirectory(preferencesService.getFilePreferences().getUserAndHost(), selectedDirectory.toAbsolutePath())); init(currentEntry); } diff --git a/src/main/java/org/jabref/gui/entryeditor/MathSciNetTab.java b/src/main/java/org/jabref/gui/entryeditor/MathSciNetTab.java index 3113d016542..9f4ed909090 100644 --- a/src/main/java/org/jabref/gui/entryeditor/MathSciNetTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/MathSciNetTab.java @@ -14,6 +14,8 @@ public class MathSciNetTab extends EntryEditorTab { + public static final String NAME = "MathSciNet Review"; + public MathSciNetTab() { setText(Localization.lang("MathSciNet Review")); } diff --git a/src/main/java/org/jabref/gui/entryeditor/OptionalFields2Tab.java b/src/main/java/org/jabref/gui/entryeditor/OptionalFields2Tab.java index 97c38da58dd..da37540e513 100644 --- a/src/main/java/org/jabref/gui/entryeditor/OptionalFields2Tab.java +++ b/src/main/java/org/jabref/gui/entryeditor/OptionalFields2Tab.java @@ -15,6 +15,9 @@ import org.jabref.preferences.PreferencesService; public class OptionalFields2Tab extends OptionalFieldsTabBase { + + public static final String NAME = "Optional fields 2"; + public OptionalFields2Tab(BibDatabaseContext databaseContext, SuggestionProviders suggestionProviders, UndoManager undoManager, diff --git a/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTab.java index 93a6ff4b6f1..d511ef268cf 100644 --- a/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTab.java @@ -15,6 +15,9 @@ import org.jabref.preferences.PreferencesService; public class OptionalFieldsTab extends OptionalFieldsTabBase { + + public static final String NAME = "Optional fields"; + public OptionalFieldsTab(BibDatabaseContext databaseContext, SuggestionProviders suggestionProviders, UndoManager undoManager, diff --git a/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java index 180ea465c53..dae09c7a2a3 100644 --- a/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java @@ -1,5 +1,6 @@ package org.jabref.gui.entryeditor; +import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; @@ -28,10 +29,13 @@ import org.jabref.model.entry.field.BibField; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.InternalField; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.field.UserSpecificCommentField; import org.jabref.preferences.PreferencesService; public class OtherFieldsTab extends FieldsEditorTab { + public static final String NAME = "Other fields"; private final List customTabFieldNames; private final BibEntryTypesManager entryTypesManager; @@ -59,7 +63,8 @@ public OtherFieldsTab(BibDatabaseContext databaseContext, indexingTaskManager); this.entryTypesManager = entryTypesManager; - this.customTabFieldNames = preferences.getAllDefaultTabFieldNames(); + this.customTabFieldNames = new ArrayList<>(); + preferences.getEntryEditorPreferences().getDefaultEntryEditorTabs().values().forEach(customTabFieldNames::addAll); setText(Localization.lang("Other fields")); setTooltip(new Tooltip(Localization.lang("Show remaining fields"))); @@ -72,12 +77,14 @@ protected Set determineFieldsToShow(BibEntry entry) { Optional entryType = entryTypesManager.enrich(entry.getType(), mode); if (entryType.isPresent()) { Set allKnownFields = entryType.get().getAllFields(); - Set otherFields = entry.getFields().stream().filter(field -> !allKnownFields.contains(field)).collect(Collectors.toCollection(LinkedHashSet::new)); - + Set otherFields = entry.getFields().stream() + .filter(field -> !allKnownFields.contains(field) && + !(field.equals(StandardField.COMMENT) || field instanceof UserSpecificCommentField)) + .collect(Collectors.toCollection(LinkedHashSet::new)); otherFields.removeAll(entryType.get().getDeprecatedFields(mode)); otherFields.removeAll(entryType.get().getOptionalFields().stream().map(BibField::field).collect(Collectors.toSet())); otherFields.remove(InternalField.KEY_FIELD); - otherFields.removeAll(customTabFieldNames); + customTabFieldNames.forEach(otherFields::remove); return otherFields; } else { // Entry type unknown -> treat all fields as required diff --git a/src/main/java/org/jabref/gui/entryeditor/PreviewTab.java b/src/main/java/org/jabref/gui/entryeditor/PreviewTab.java index 4212506b8f6..908349bc0ad 100644 --- a/src/main/java/org/jabref/gui/entryeditor/PreviewTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/PreviewTab.java @@ -12,6 +12,7 @@ import org.jabref.preferences.PreferencesService; public class PreviewTab extends EntryEditorTab { + public static final String NAME = "Preview"; private final DialogService dialogService; private final BibDatabaseContext databaseContext; private final PreferencesService preferences; diff --git a/src/main/java/org/jabref/gui/entryeditor/RelatedArticlesTab.java b/src/main/java/org/jabref/gui/entryeditor/RelatedArticlesTab.java index 54f21252406..08047d1aae9 100644 --- a/src/main/java/org/jabref/gui/entryeditor/RelatedArticlesTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/RelatedArticlesTab.java @@ -40,6 +40,7 @@ */ public class RelatedArticlesTab extends EntryEditorTab { + public static final String NAME = "Related articles"; private static final Logger LOGGER = LoggerFactory.getLogger(RelatedArticlesTab.class); private final EntryEditorPreferences preferences; private final DialogService dialogService; @@ -65,7 +66,7 @@ private StackPane getRelatedArticlesPane(BibEntry entry) { ProgressIndicator progress = new ProgressIndicator(); progress.setMaxSize(100, 100); - MrDLibFetcher fetcher = new MrDLibFetcher(preferencesService.getGeneralPreferences().getLanguage().name(), + MrDLibFetcher fetcher = new MrDLibFetcher(preferencesService.getWorkspacePreferences().getLanguage().name(), Globals.BUILD_INFO.version, preferencesService.getMrDlibPreferences()); BackgroundTask .wrap(() -> fetcher.performSearch(entry)) @@ -127,7 +128,7 @@ private ScrollPane getRelatedArticleInfo(List list, MrDLibFetcher fetc titleLink.setOnAction(event -> { if (entry.getField(StandardField.URL).isPresent()) { try { - JabRefDesktop.openBrowser(entry.getField(StandardField.URL).get()); + JabRefDesktop.openBrowser(entry.getField(StandardField.URL).get(), preferencesService.getFilePreferences()); } catch (IOException e) { LOGGER.error("Error opening the browser to: " + entry.getField(StandardField.URL).get(), e); dialogService.showErrorDialogAndWait(e); @@ -191,7 +192,7 @@ private ScrollPane getPrivacyDialog(BibEntry entry) { Hyperlink mdlLink = new Hyperlink(Localization.lang("Further information about Mr. DLib for JabRef users.")); mdlLink.setOnAction(event -> { try { - JabRefDesktop.openBrowser("http://mr-dlib.org/information-for-users/information-about-mr-dlib-for-jabref-users/"); + JabRefDesktop.openBrowser("http://mr-dlib.org/information-for-users/information-about-mr-dlib-for-jabref-users/", preferencesService.getFilePreferences()); } catch (IOException e) { LOGGER.error("Error opening the browser to Mr. DLib information page.", e); dialogService.showErrorDialogAndWait(e); @@ -211,9 +212,8 @@ private ScrollPane getPrivacyDialog(BibEntry entry) { vb.setSpacing(10); button.setOnAction(event -> { - preferences.setIsMrdlibAccepted(true); - MrDlibPreferences mrDlibPreferences = preferencesService.getMrDlibPreferences(); + mrDlibPreferences.setAcceptRecommendations(true); mrDlibPreferences.setSendLanguage(cbLanguage.isSelected()); mrDlibPreferences.setSendOs(cbOS.isSelected()); mrDlibPreferences.setSendTimezone(cbTimezone.isSelected()); @@ -236,7 +236,7 @@ public boolean shouldShow(BibEntry entry) { @Override protected void bindToEntry(BibEntry entry) { // Ask for consent to send data to Mr. DLib on first time to tab - if (preferences.isMrdlibAccepted()) { + if (preferencesService.getMrDlibPreferences().shouldAcceptRecommendations()) { setContent(getRelatedArticlesPane(entry)); } else { setContent(getPrivacyDialog(entry)); diff --git a/src/main/java/org/jabref/gui/entryeditor/RequiredFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/RequiredFieldsTab.java index 3451f2c8f53..cfc530c9d08 100644 --- a/src/main/java/org/jabref/gui/entryeditor/RequiredFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/RequiredFieldsTab.java @@ -28,6 +28,7 @@ public class RequiredFieldsTab extends FieldsEditorTab { + public static final String NAME = "Required fields"; private final BibEntryTypesManager entryTypesManager; public RequiredFieldsTab(BibDatabaseContext databaseContext, @@ -44,7 +45,6 @@ public RequiredFieldsTab(BibDatabaseContext databaseContext, super(false, databaseContext, suggestionProviders, undoManager, dialogService, preferences, stateManager, themeManager, taskExecutor, journalAbbreviationRepository, indexingTaskManager); this.entryTypesManager = entryTypesManager; - setText(Localization.lang("Required fields")); setTooltip(new Tooltip(Localization.lang("Show required fields"))); setGraphic(IconTheme.JabRefIcons.REQUIRED.getGraphicNode()); @@ -56,7 +56,7 @@ protected Set determineFieldsToShow(BibEntry entry) { Set fields = new LinkedHashSet<>(); if (entryType.isPresent()) { for (OrFields orFields : entryType.get().getRequiredFields()) { - fields.addAll(orFields); + fields.addAll(orFields.getFields()); } // Add the edit field for Bibtex-key. fields.add(InternalField.KEY_FIELD); diff --git a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java index 1e3de019011..05c8f94a8a3 100644 --- a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java @@ -21,7 +21,6 @@ import javafx.scene.input.KeyEvent; import org.jabref.gui.DialogService; -import org.jabref.gui.Globals; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionFactory; import org.jabref.gui.actions.SimpleCommand; @@ -35,8 +34,8 @@ import org.jabref.gui.undo.UndoableFieldChange; import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.logic.bibtex.BibEntryWriter; +import org.jabref.logic.bibtex.FieldPreferences; import org.jabref.logic.bibtex.FieldWriter; -import org.jabref.logic.bibtex.FieldWriterPreferences; import org.jabref.logic.bibtex.InvalidFieldValueException; import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.importer.ImportFormatPreferences; @@ -49,6 +48,7 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.Field; import org.jabref.model.util.FileUpdateMonitor; @@ -63,7 +63,7 @@ public class SourceTab extends EntryEditorTab { private static final Logger LOGGER = LoggerFactory.getLogger(SourceTab.class); - private final FieldWriterPreferences fieldWriterPreferences; + private final FieldPreferences fieldPreferences; private final BibDatabaseMode mode; private final UndoManager undoManager; private final ObjectProperty sourceIsValid = new SimpleObjectProperty<>(); @@ -72,8 +72,9 @@ public class SourceTab extends EntryEditorTab { private final FileUpdateMonitor fileMonitor; private final DialogService dialogService; private final StateManager stateManager; - private Optional searchHighlightPattern = Optional.empty(); + private final BibEntryTypesManager entryTypesManager; private final KeyBindingRepository keyBindingRepository; + private Optional searchHighlightPattern = Optional.empty(); private CodeArea codeArea; private BibEntry previousEntry; @@ -98,17 +99,26 @@ public void execute() { } } - public SourceTab(BibDatabaseContext bibDatabaseContext, CountingUndoManager undoManager, FieldWriterPreferences fieldWriterPreferences, ImportFormatPreferences importFormatPreferences, FileUpdateMonitor fileMonitor, DialogService dialogService, StateManager stateManager, KeyBindingRepository keyBindingRepository) { + public SourceTab(BibDatabaseContext bibDatabaseContext, + CountingUndoManager undoManager, + FieldPreferences fieldPreferences, + ImportFormatPreferences importFormatPreferences, + FileUpdateMonitor fileMonitor, + DialogService dialogService, + StateManager stateManager, + BibEntryTypesManager entryTypesManager, + KeyBindingRepository keyBindingRepository) { this.mode = bibDatabaseContext.getMode(); this.setText(Localization.lang("%0 source", mode.getFormattedName())); this.setTooltip(new Tooltip(Localization.lang("Show/edit %0 source", mode.getFormattedName()))); this.setGraphic(IconTheme.JabRefIcons.SOURCE.getGraphicNode()); this.undoManager = undoManager; - this.fieldWriterPreferences = fieldWriterPreferences; + this.fieldPreferences = fieldPreferences; this.importFormatPreferences = importFormatPreferences; this.fileMonitor = fileMonitor; this.dialogService = dialogService; this.stateManager = stateManager; + this.entryTypesManager = entryTypesManager; this.keyBindingRepository = keyBindingRepository; stateManager.activeSearchQueryProperty().addListener((observable, oldValue, newValue) -> { @@ -129,11 +139,11 @@ private void highlightSearchPattern() { } } - private String getSourceString(BibEntry entry, BibDatabaseMode type, FieldWriterPreferences fieldWriterPreferences) throws IOException { + private String getSourceString(BibEntry entry, BibDatabaseMode type, FieldPreferences fieldPreferences) throws IOException { StringWriter writer = new StringWriter(); BibWriter bibWriter = new BibWriter(writer, OS.NEWLINE); - FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldWriterPreferences); - new BibEntryWriter(fieldWriter, Globals.entryTypesManager).write(entry, bibWriter, type); + FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldPreferences); + new BibEntryWriter(fieldWriter, entryTypesManager).write(entry, bibWriter, type); return writer.toString(); } @@ -225,7 +235,7 @@ private void updateCodeArea() { codeArea.clear(); try { - codeArea.appendText(getSourceString(currentEntry, mode, fieldWriterPreferences)); + codeArea.appendText(getSourceString(currentEntry, mode, fieldPreferences)); codeArea.setEditable(true); highlightSearchPattern(); } catch (IOException ex) { @@ -307,7 +317,7 @@ private void storeSource(BibEntry outOfFocusEntry, String text) { String newValue = field.getValue(); if (!Objects.equals(oldValue, newValue)) { // Test if the field is legally set. - new FieldWriter(fieldWriterPreferences).write(fieldName, newValue); + new FieldWriter(fieldPreferences).write(fieldName, newValue); compound.addEdit(new UndoableFieldChange(outOfFocusEntry, fieldName, oldValue, newValue)); outOfFocusEntry.setField(fieldName, newValue); diff --git a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.java b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.java index 9ba9c1b23e8..338cc91872b 100644 --- a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.java @@ -13,6 +13,7 @@ public class FileAnnotationTab extends EntryEditorTab { + public static final String NAME = "File annotations"; private final FileAnnotationCache fileAnnotationCache; public FileAnnotationTab(FileAnnotationCache cache) { diff --git a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationViewModel.java b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationViewModel.java index 62cd3d53491..d6cd4175103 100644 --- a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationViewModel.java +++ b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationViewModel.java @@ -31,7 +31,7 @@ private void setupContentProperties(FileAnnotation annotation) { this.content.set(annotation.getLinkedFileAnnotation().getContent()); String annotationContent = annotation.getContent(); String illegibleTextMessage = Localization.lang("The marked area does not contain any legible text!"); - String markingContent = (annotationContent.isEmpty() ? illegibleTextMessage : annotationContent); + String markingContent = annotationContent.isEmpty() ? illegibleTextMessage : annotationContent; this.marking.set(removePunctuationMark(markingContent)); } else { String content = annotation.getContent(); @@ -88,19 +88,19 @@ public StringProperty authorProperty() { @Override public String toString() { if (annotation.hasLinkedAnnotation() && this.getContent().isEmpty()) { - if (FileAnnotationType.UNDERLINE.equals(annotation.getAnnotationType())) { + if (FileAnnotationType.UNDERLINE == annotation.getAnnotationType()) { return Localization.lang("Empty Underline"); } - if (FileAnnotationType.HIGHLIGHT.equals(annotation.getAnnotationType())) { + if (FileAnnotationType.HIGHLIGHT == annotation.getAnnotationType()) { return Localization.lang("Empty Highlight"); } return Localization.lang("Empty Marking"); } - if (FileAnnotationType.UNDERLINE.equals(annotation.getAnnotationType())) { + if (FileAnnotationType.UNDERLINE == annotation.getAnnotationType()) { return Localization.lang("Underline") + ": " + this.getContent(); } - if (FileAnnotationType.HIGHLIGHT.equals(annotation.getAnnotationType())) { + if (FileAnnotationType.HIGHLIGHT == annotation.getAnnotationType()) { return Localization.lang("Highlight") + ": " + this.getContent(); } diff --git a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java index 8fe5c146b5b..9732e15b3a4 100644 --- a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java @@ -38,6 +38,7 @@ public class FulltextSearchResultsTab extends EntryEditorTab { + public static final String NAME = "Search results"; private static final Logger LOGGER = LoggerFactory.getLogger(FulltextSearchResultsTab.class); private final StateManager stateManager; @@ -98,7 +99,7 @@ protected void bindToEntry(BibEntry entry) { // Iterate through pages (within file) with search hits for (SearchResult searchResult : resultsForPath.getValue()) { for (String resultTextHtml : searchResult.getContentResultStringsHtml()) { - content.getChildren().addAll(TooltipTextUtil.createTextsFromHtml(resultTextHtml.replaceAll(" ", " "))); + content.getChildren().addAll(TooltipTextUtil.createTextsFromHtml(resultTextHtml.replace(" ", " "))); content.getChildren().addAll(new Text(System.lineSeparator()), lineSeparator(0.8), createPageLink(searchResult.getPageNumber())); } if (!searchResult.getAnnotationsResultStringsHtml().isEmpty()) { @@ -107,7 +108,7 @@ protected void bindToEntry(BibEntry entry) { content.getChildren().add(annotationsText); } for (String resultTextHtml : searchResult.getAnnotationsResultStringsHtml()) { - content.getChildren().addAll(TooltipTextUtil.createTextsFromHtml(resultTextHtml.replaceAll(" ", " "))); + content.getChildren().addAll(TooltipTextUtil.createTextsFromHtml(resultTextHtml.replace(" ", " "))); content.getChildren().addAll(new Text(System.lineSeparator()), lineSeparator(0.8), createPageLink(searchResult.getPageNumber())); } } @@ -126,7 +127,7 @@ private Text createFileLink(String pathToFile) { fileLinkText.setOnMouseClicked(event -> { if (MouseButton.PRIMARY.equals(event.getButton())) { try { - JabRefDesktop.openBrowser(resolvedPath.toUri()); + JabRefDesktop.openBrowser(resolvedPath.toUri(), preferencesService.getFilePreferences()); } catch (IOException e) { LOGGER.error("Cannot open {}.", resolvedPath.toString(), e); } diff --git a/src/main/java/org/jabref/gui/errorconsole/ErrorConsoleView.java b/src/main/java/org/jabref/gui/errorconsole/ErrorConsoleView.java index e29940c7717..daf9fb35fe9 100644 --- a/src/main/java/org/jabref/gui/errorconsole/ErrorConsoleView.java +++ b/src/main/java/org/jabref/gui/errorconsole/ErrorConsoleView.java @@ -26,6 +26,7 @@ import org.jabref.gui.util.ControlHelper; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.BuildInfo; +import org.jabref.preferences.PreferencesService; import com.airhacks.afterburner.views.ViewLoader; import jakarta.inject.Inject; @@ -41,6 +42,7 @@ public class ErrorConsoleView extends BaseDialog { @FXML private Label descriptionLabel; @Inject private DialogService dialogService; + @Inject private PreferencesService preferencesService; @Inject private ClipBoardManager clipBoardManager; @Inject private BuildInfo buildInfo; @Inject private KeyBindingRepository keyBindingRepository; @@ -63,7 +65,7 @@ public ErrorConsoleView() { @FXML private void initialize() { - viewModel = new ErrorConsoleViewModel(dialogService, clipBoardManager, buildInfo); + viewModel = new ErrorConsoleViewModel(dialogService, preferencesService, clipBoardManager, buildInfo); messagesListView.setCellFactory(createCellFactory()); messagesListView.itemsProperty().bind(viewModel.allMessagesDataProperty()); messagesListView.scrollTo(viewModel.allMessagesDataProperty().getSize() - 1); diff --git a/src/main/java/org/jabref/gui/errorconsole/ErrorConsoleViewModel.java b/src/main/java/org/jabref/gui/errorconsole/ErrorConsoleViewModel.java index 959224f3a5d..5f346cdcd4e 100644 --- a/src/main/java/org/jabref/gui/errorconsole/ErrorConsoleViewModel.java +++ b/src/main/java/org/jabref/gui/errorconsole/ErrorConsoleViewModel.java @@ -19,6 +19,7 @@ import org.jabref.logic.logging.LogMessages; import org.jabref.logic.util.BuildInfo; import org.jabref.logic.util.OS; +import org.jabref.preferences.PreferencesService; import com.tobiasdiez.easybind.EasyBind; import org.apache.http.client.utils.URIBuilder; @@ -30,12 +31,14 @@ public class ErrorConsoleViewModel extends AbstractViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(ErrorConsoleViewModel.class); private final DialogService dialogService; + private final PreferencesService preferencesService; private final ClipBoardManager clipBoardManager; private final BuildInfo buildInfo; private final ListProperty allMessagesData; - public ErrorConsoleViewModel(DialogService dialogService, ClipBoardManager clipBoardManager, BuildInfo buildInfo) { + public ErrorConsoleViewModel(DialogService dialogService, PreferencesService preferencesService, ClipBoardManager clipBoardManager, BuildInfo buildInfo) { this.dialogService = Objects.requireNonNull(dialogService); + this.preferencesService = Objects.requireNonNull(preferencesService); this.clipBoardManager = Objects.requireNonNull(clipBoardManager); this.buildInfo = Objects.requireNonNull(buildInfo); ObservableList eventViewModels = EasyBind.map(BindingsHelper.forUI(LogMessages.getInstance().getMessages()), LogEventViewModel::new); @@ -47,7 +50,7 @@ public ListProperty allMessagesDataProperty() { } /** - * Concatenates the formatted message of the given {@link LogEvent}s by using the a new line separator. + * Concatenates the formatted message of the given {@link LogEventViewModel}s by using a new line separator. * * @return all messages as String */ @@ -65,7 +68,7 @@ public void copyLog() { } /** - * Copies the given list of {@link LogEvent}s to the clipboard. + * Copies the given list of {@link LogEventViewModel}s to the clipboard. */ public void copyLog(List messages) { if (messages.isEmpty()) { @@ -110,7 +113,7 @@ public void reportIssue() { .setScheme("https").setHost("github.com") .setPath("/JabRef/jabref/issues/new") .setParameter("body", issueBody); - JabRefDesktop.openBrowser(uriBuilder.build().toString()); + JabRefDesktop.openBrowser(uriBuilder.build().toString(), preferencesService.getFilePreferences()); } catch (IOException | URISyntaxException e) { LOGGER.error("Problem opening url", e); } diff --git a/src/main/java/org/jabref/gui/errorconsole/LogEventViewModel.java b/src/main/java/org/jabref/gui/errorconsole/LogEventViewModel.java index 0694c080b14..24152e82f01 100644 --- a/src/main/java/org/jabref/gui/errorconsole/LogEventViewModel.java +++ b/src/main/java/org/jabref/gui/errorconsole/LogEventViewModel.java @@ -37,12 +37,12 @@ public String getStyleClass() { public JabRefIcon getIcon() { switch (logEvent.getLevel()) { case ERROR: - return (IconTheme.JabRefIcons.INTEGRITY_FAIL); + return IconTheme.JabRefIcons.INTEGRITY_FAIL; case WARN: - return (IconTheme.JabRefIcons.INTEGRITY_WARN); + return IconTheme.JabRefIcons.INTEGRITY_WARN; case INFO: default: - return (IconTheme.JabRefIcons.INTEGRITY_INFO); + return IconTheme.JabRefIcons.INTEGRITY_INFO; } } diff --git a/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogView.java b/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogView.java index d1370c42575..5ad22042ddc 100644 --- a/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogView.java +++ b/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogView.java @@ -7,7 +7,6 @@ import org.jabref.gui.DialogService; import org.jabref.gui.util.BaseDialog; -import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.l10n.Localization; import org.jabref.preferences.PreferencesService; @@ -16,7 +15,6 @@ public class CreateModifyExporterDialogView extends BaseDialog { - @Inject private JournalAbbreviationRepository repository; private final ExporterViewModel exporter; @FXML private TextField name; @FXML private TextField fileName; @@ -45,7 +43,7 @@ public CreateModifyExporterDialogView(ExporterViewModel exporter) { @FXML private void initialize() { - viewModel = new CreateModifyExporterDialogViewModel(exporter, dialogService, preferences, repository); + viewModel = new CreateModifyExporterDialogViewModel(exporter, dialogService, preferences); name.textProperty().bindBidirectional(viewModel.getName()); fileName.textProperty().bindBidirectional(viewModel.getLayoutFileName()); extension.textProperty().bindBidirectional(viewModel.getExtension()); diff --git a/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogViewModel.java b/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogViewModel.java index ae8ee9b7234..78c808dad3c 100644 --- a/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogViewModel.java +++ b/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogViewModel.java @@ -8,11 +8,8 @@ import org.jabref.gui.AbstractViewModel; import org.jabref.gui.DialogService; import org.jabref.gui.util.FileDialogConfiguration; -import org.jabref.logic.exporter.SavePreferences; import org.jabref.logic.exporter.TemplateExporter; -import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.layout.LayoutFormatterPreferences; import org.jabref.logic.util.StandardFileType; import org.jabref.preferences.PreferencesService; @@ -37,13 +34,11 @@ public class CreateModifyExporterDialogViewModel extends AbstractViewModel { private final StringProperty layoutFile = new SimpleStringProperty(""); private final StringProperty extension = new SimpleStringProperty(""); - private final JournalAbbreviationRepository repository; - - public CreateModifyExporterDialogViewModel(ExporterViewModel exporter, DialogService dialogService, PreferencesService preferences, - JournalAbbreviationRepository repository) { + public CreateModifyExporterDialogViewModel(ExporterViewModel exporter, + DialogService dialogService, + PreferencesService preferences) { this.dialogService = dialogService; this.preferences = preferences; - this.repository = repository; // Set text of each of the boxes if (exporter != null) { @@ -56,7 +51,7 @@ public CreateModifyExporterDialogViewModel(ExporterViewModel exporter, DialogSer public ExporterViewModel saveExporter() { Path layoutFileDir = Path.of(layoutFile.get()).getParent(); if (layoutFileDir != null) { - preferences.getImportExportPreferences().setExportWorkingDirectory(layoutFileDir); + preferences.getExportPreferences().setExportWorkingDirectory(layoutFileDir); } // Check that there are no empty strings. @@ -67,10 +62,12 @@ public ExporterViewModel saveExporter() { } // Create a new exporter to be returned to ExportCustomizationDialogViewModel, which requested it - LayoutFormatterPreferences layoutPreferences = preferences.getLayoutFormatterPreferences(repository); - SavePreferences savePreferences = preferences.getSavePreferencesForExport(); - TemplateExporter format = new TemplateExporter(name.get(), layoutFile.get(), extension.get(), - layoutPreferences, savePreferences); + TemplateExporter format = new TemplateExporter( + name.get(), + layoutFile.get(), + extension.get(), + preferences.getLayoutFormatterPreferences(), + preferences.getSelfContainedExportConfiguration().getSelfContainedSaveOrder()); format.setCustomExport(true); return new ExporterViewModel(format); } @@ -79,7 +76,7 @@ public void browse() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() .addExtensionFilter(Localization.lang("Custom layout file"), StandardFileType.LAYOUT) .withDefaultExtension(Localization.lang("Custom layout file"), StandardFileType.LAYOUT) - .withInitialDirectory(preferences.getImportExportPreferences().getExportWorkingDirectory()).build(); + .withInitialDirectory(preferences.getExportPreferences().getExportWorkingDirectory()).build(); dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(f -> layoutFile.set(f.toAbsolutePath().toString())); } diff --git a/src/main/java/org/jabref/gui/exporter/ExportCommand.java b/src/main/java/org/jabref/gui/exporter/ExportCommand.java index c5479d8c2cd..96eebc80160 100644 --- a/src/main/java/org/jabref/gui/exporter/ExportCommand.java +++ b/src/main/java/org/jabref/gui/exporter/ExportCommand.java @@ -70,16 +70,15 @@ public void execute() { // Get list of exporters and sort before adding to file dialog ExporterFactory exporterFactory = ExporterFactory.create( preferences, - Globals.entryTypesManager, - Globals.journalAbbreviationRepository); + Globals.entryTypesManager); List exporters = exporterFactory.getExporters().stream() .sorted(Comparator.comparing(Exporter::getName)) .collect(Collectors.toList()); FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() .addExtensionFilter(FileFilterConverter.exporterToExtensionFilter(exporters)) - .withDefaultExtension(preferences.getImportExportPreferences().getLastExportExtension()) - .withInitialDirectory(preferences.getImportExportPreferences().getExportWorkingDirectory()) + .withDefaultExtension(preferences.getExportPreferences().getLastExportExtension()) + .withInitialDirectory(preferences.getExportPreferences().getExportWorkingDirectory()) .build(); dialogService.showFileSaveDialog(fileDialogConfiguration) .ifPresent(path -> export(path, fileDialogConfiguration.getSelectedExtensionFilter(), exporters)); @@ -110,8 +109,8 @@ private void export(Path file, FileChooser.ExtensionFilter selectedExtensionFilt // Make sure we remember which filter was used, to set // the default for next time: - preferences.getImportExportPreferences().setLastExportExtension(format.getName()); - preferences.getImportExportPreferences().setExportWorkingDirectory(file.getParent()); + preferences.getExportPreferences().setLastExportExtension(format.getName()); + preferences.getExportPreferences().setExportWorkingDirectory(file.getParent()); final List finEntries = entries; @@ -120,7 +119,8 @@ private void export(Path file, FileChooser.ExtensionFilter selectedExtensionFilt format.export(stateManager.getActiveDatabase().get(), file, finEntries, - fileDirForDatabase); + fileDirForDatabase, + Globals.journalAbbreviationRepository); return null; // can not use BackgroundTask.wrap(Runnable) because Runnable.run() can't throw Exceptions }) .onSuccess(save -> { @@ -130,7 +130,7 @@ private void export(Path file, FileChooser.ExtensionFilter selectedExtensionFilt Localization.lang("Export operation finished successfully."), List.of(new Action(Localization.lang("Reveal in File Explorer"), event -> { try { - JabRefDesktop.openFolderAndSelectFile(file, preferences, dialogService); + JabRefDesktop.openFolderAndSelectFile(file, preferences.getExternalApplicationsPreferences(), dialogService); } catch (IOException e) { LOGGER.error("Could not open export folder.", e); } diff --git a/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java b/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java index 79a044aef28..c0d2e2c7135 100644 --- a/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java +++ b/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java @@ -75,8 +75,7 @@ public void execute() { ExporterFactory exporterFactory = ExporterFactory.create( preferences, - Globals.entryTypesManager, - Globals.journalAbbreviationRepository); + Globals.entryTypesManager); List exporters = exporterFactory.getExporters().stream() .sorted(Comparator.comparing(Exporter::getName)) .filter(exporter -> SUPPORTED_FILETYPES.contains(exporter.getFileType())) @@ -84,7 +83,7 @@ public void execute() { // Find default choice, if any Exporter defaultChoice = exporters.stream() - .filter(exporter -> exporter.getName().equals(preferences.getImportExportPreferences().getLastExportExtension())) + .filter(exporter -> exporter.getName().equals(preferences.getExportPreferences().getLastExportExtension())) .findAny() .orElse(null); @@ -107,7 +106,7 @@ private ExportResult exportToClipboard(Exporter exporter) throws Exception { .orElse(List.of(preferences.getFilePreferences().getWorkingDirectory())); // Add chosen export type to last used preference, to become default - preferences.getImportExportPreferences().setLastExportExtension(exporter.getName()); + preferences.getExportPreferences().setLastExportExtension(exporter.getName()); Path tmp = null; try { @@ -118,7 +117,7 @@ private ExportResult exportToClipboard(Exporter exporter) throws Exception { entries.addAll(stateManager.getSelectedEntries()); // Write to file: - exporter.export(stateManager.getActiveDatabase().get(), tmp, entries, fileDirForDatabase); + exporter.export(stateManager.getActiveDatabase().get(), tmp, entries, fileDirForDatabase, Globals.journalAbbreviationRepository); // Read the file and put the contents on the clipboard: return new ExportResult(Files.readString(tmp), exporter.getFileType()); diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 84bf9346e23..3d451540b2e 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -6,6 +6,7 @@ import java.nio.charset.UnsupportedCharsetException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -13,21 +14,25 @@ import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; import javafx.scene.control.DialogPane; +import javafx.scene.control.TableColumn; import javafx.scene.layout.VBox; import javafx.scene.text.Text; import org.jabref.gui.DialogService; import org.jabref.gui.JabRefFrame; import org.jabref.gui.LibraryTab; +import org.jabref.gui.autosaveandbackup.AutosaveManager; +import org.jabref.gui.autosaveandbackup.BackupManager; +import org.jabref.gui.maintable.BibEntryTableViewModel; +import org.jabref.gui.maintable.columns.MainTableColumn; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.FileDialogConfiguration; -import org.jabref.logic.autosaveandbackup.AutosaveManager; -import org.jabref.logic.autosaveandbackup.BackupManager; import org.jabref.logic.exporter.AtomicFileWriter; +import org.jabref.logic.exporter.BibDatabaseWriter; import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.exporter.BibtexDatabaseWriter; import org.jabref.logic.exporter.SaveException; -import org.jabref.logic.exporter.SavePreferences; +import org.jabref.logic.exporter.SelfContainedSaveConfiguration; import org.jabref.logic.l10n.Encodings; import org.jabref.logic.l10n.Localization; import org.jabref.logic.shared.DatabaseLocation; @@ -36,7 +41,8 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.event.ChangePropagation; import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.preferences.GeneralPreferences; +import org.jabref.model.metadata.SaveOrder; +import org.jabref.model.metadata.SelfContainedSaveOrder; import org.jabref.preferences.PreferencesService; import org.slf4j.Logger; @@ -79,7 +85,7 @@ public boolean save(SaveDatabaseMode mode) { } /** - * Asks the user for the path and saves afterwards + * Asks the user for the path and saves afterward */ public void saveAs() { askForSavePath().ifPresent(this::saveAs); @@ -89,10 +95,31 @@ public boolean saveAs(Path file) { return this.saveAs(file, SaveDatabaseMode.NORMAL); } + private SelfContainedSaveOrder getSaveOrder() { + return libraryTab.getBibDatabaseContext() + .getMetaData().getSaveOrder() + .map(so -> { + if (so.getOrderType() == SaveOrder.OrderType.TABLE) { + // We need to "flatten out" SaveOrder.OrderType.TABLE as BibWriter does not have access to preferences + List> sortOrder = libraryTab.getMainTable().getSortOrder(); + return new SelfContainedSaveOrder( + SaveOrder.OrderType.SPECIFIED, + sortOrder.stream() + .filter(col -> col instanceof MainTableColumn) + .map(column -> ((MainTableColumn) column).getModel()) + .flatMap(model -> model.getSortCriteria().stream()) + .toList()); + } else { + return SelfContainedSaveOrder.of(so); + } + }) + .orElse(SaveOrder.getDefaultSaveOrder()); + } + public void saveSelectedAsPlain() { askForSavePath().ifPresent(path -> { try { - saveDatabase(path, true, StandardCharsets.UTF_8, SavePreferences.DatabaseSaveType.PLAIN_BIBTEX); + saveDatabase(path, true, StandardCharsets.UTF_8, BibDatabaseWriter.SaveType.PLAIN_BIBTEX, getSaveOrder()); frame.getFileHistory().newFile(path); dialogService.notify(Localization.lang("Saved selected to '%0'.", path.toString())); } catch (SaveException ex) { @@ -116,7 +143,7 @@ boolean saveAs(Path file, SaveDatabaseMode mode) { final Path oldFile = databasePath.get(); context.setDatabasePath(oldFile); AutosaveManager.shutdown(context); - BackupManager.shutdown(context); + BackupManager.shutdown(context, this.preferences.getFilePreferences().getBackupDirectory(), preferences.getFilePreferences().shouldCreateBackup()); } // Set new location @@ -209,8 +236,7 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { // Make sure to remember which encoding we used libraryTab.getBibDatabaseContext().getMetaData().setEncoding(encoding, ChangePropagation.DO_NOT_POST_EVENT); - // Save the database - boolean success = saveDatabase(targetPath, false, encoding, SavePreferences.DatabaseSaveType.ALL); + boolean success = saveDatabase(targetPath, false, encoding, BibDatabaseWriter.SaveType.WITH_JABREF_META_DATA, getSaveOrder()); if (success) { libraryTab.getUndoManager().markUnchanged(); @@ -228,17 +254,20 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { } } - private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding, SavePreferences.DatabaseSaveType saveType) throws SaveException { + private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding, BibDatabaseWriter.SaveType saveType, SelfContainedSaveOrder saveOrder) throws SaveException { // if this code is adapted, please also adapt org.jabref.logic.autosaveandbackup.BackupManager.performBackup - - GeneralPreferences generalPreferences = this.preferences.getGeneralPreferences(); - SavePreferences savePreferences = this.preferences.getSavePreferences() - .withSaveType(saveType); + SelfContainedSaveConfiguration saveConfiguration + = new SelfContainedSaveConfiguration(saveOrder, false, saveType, preferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); BibDatabaseContext bibDatabaseContext = libraryTab.getBibDatabaseContext(); synchronized (bibDatabaseContext) { - try (AtomicFileWriter fileWriter = new AtomicFileWriter(file, encoding, savePreferences.shouldMakeBackup())) { + try (AtomicFileWriter fileWriter = new AtomicFileWriter(file, encoding, saveConfiguration.shouldMakeBackup())) { BibWriter bibWriter = new BibWriter(fileWriter, bibDatabaseContext.getDatabase().getNewLineSeparator()); - BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(bibWriter, generalPreferences, savePreferences, entryTypesManager); + BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter( + bibWriter, + saveConfiguration, + preferences.getFieldPreferences(), + preferences.getCitationKeyPatternPreferences(), + entryTypesManager); if (selectedOnly) { databaseWriter.savePartOfDatabase(bibDatabaseContext, libraryTab.getSelectedEntries()); @@ -249,7 +278,7 @@ private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding, libraryTab.registerUndoableChanges(databaseWriter.getSaveActionsFieldChanges()); if (fileWriter.hasEncodingProblems()) { - saveWithDifferentEncoding(file, selectedOnly, encoding, fileWriter.getEncodingProblems(), saveType); + saveWithDifferentEncoding(file, selectedOnly, encoding, fileWriter.getEncodingProblems(), saveType, saveOrder); } } catch (UnsupportedCharsetException ex) { throw new SaveException(Localization.lang("Character encoding '%0' is not supported.", encoding.displayName()), ex); @@ -260,7 +289,7 @@ private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding, } } - private void saveWithDifferentEncoding(Path file, boolean selectedOnly, Charset encoding, Set encodingProblems, SavePreferences.DatabaseSaveType saveType) throws SaveException { + private void saveWithDifferentEncoding(Path file, boolean selectedOnly, Charset encoding, Set encodingProblems, BibDatabaseWriter.SaveType saveType, SelfContainedSaveOrder saveOrder) throws SaveException { DialogPane pane = new DialogPane(); VBox vbox = new VBox(); vbox.getChildren().addAll( @@ -282,7 +311,7 @@ private void saveWithDifferentEncoding(Path file, boolean selectedOnly, Charset // Make sure to remember which encoding we used. libraryTab.getBibDatabaseContext().getMetaData().setEncoding(newEncoding.get(), ChangePropagation.DO_NOT_POST_EVENT); - saveDatabase(file, selectedOnly, newEncoding.get(), saveType); + saveDatabase(file, selectedOnly, newEncoding.get(), saveType, saveOrder); } } } diff --git a/src/main/java/org/jabref/gui/exporter/WriteMetadataToPdfAction.java b/src/main/java/org/jabref/gui/exporter/WriteMetadataToPdfAction.java index 34d39b2c8d4..6e579b4965a 100644 --- a/src/main/java/org/jabref/gui/exporter/WriteMetadataToPdfAction.java +++ b/src/main/java/org/jabref/gui/exporter/WriteMetadataToPdfAction.java @@ -25,8 +25,9 @@ import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.TaskExecutor; -import org.jabref.logic.bibtex.FieldWriterPreferences; +import org.jabref.logic.bibtex.FieldPreferences; import org.jabref.logic.exporter.EmbeddedBibFilePdfExporter; +import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.io.FileUtil; import org.jabref.logic.xmp.XmpPreferences; @@ -43,11 +44,12 @@ public class WriteMetadataToPdfAction extends SimpleCommand { private final StateManager stateManager; private final BibEntryTypesManager entryTypesManager; - private final FieldWriterPreferences fieldWriterPreferences; + private final FieldPreferences fieldPreferences; private final DialogService dialogService; private final TaskExecutor taskExecutor; private final FilePreferences filePreferences; private final XmpPreferences xmpPreferences; + private final JournalAbbreviationRepository abbreviationRepository; private OptionsDialog optionsDialog; @@ -59,14 +61,22 @@ public class WriteMetadataToPdfAction extends SimpleCommand { private int entriesChanged; private int errors; - public WriteMetadataToPdfAction(StateManager stateManager, BibEntryTypesManager entryTypesManager, FieldWriterPreferences fieldWriterPreferences, DialogService dialogService, TaskExecutor taskExecutor, FilePreferences filePreferences, XmpPreferences xmpPreferences) { + public WriteMetadataToPdfAction(StateManager stateManager, + BibEntryTypesManager entryTypesManager, + FieldPreferences fieldPreferences, + DialogService dialogService, + TaskExecutor taskExecutor, + FilePreferences filePreferences, + XmpPreferences xmpPreferences, + JournalAbbreviationRepository abbreviationRepository) { this.stateManager = stateManager; this.entryTypesManager = entryTypesManager; - this.fieldWriterPreferences = fieldWriterPreferences; + this.fieldPreferences = fieldPreferences; this.dialogService = dialogService; this.taskExecutor = taskExecutor; this.filePreferences = filePreferences; this.xmpPreferences = xmpPreferences; + this.abbreviationRepository = abbreviationRepository; this.executable.bind(needsDatabase(stateManager)); } @@ -195,8 +205,8 @@ private void writeMetadata() { synchronized private void writeMetadataToFile(Path file, BibEntry entry, BibDatabaseContext databaseContext, BibDatabase database) throws Exception { new XmpUtilWriter(xmpPreferences).writeXmp(file, entry, database); - EmbeddedBibFilePdfExporter embeddedBibExporter = new EmbeddedBibFilePdfExporter(databaseContext.getMode(), entryTypesManager, fieldWriterPreferences); - embeddedBibExporter.exportToFileByPath(databaseContext, database, filePreferences, file); + EmbeddedBibFilePdfExporter embeddedBibExporter = new EmbeddedBibFilePdfExporter(databaseContext.getMode(), entryTypesManager, fieldPreferences); + embeddedBibExporter.exportToFileByPath(databaseContext, filePreferences, file, abbreviationRepository); } class OptionsDialog extends FXDialog { diff --git a/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java b/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java index be8b74ede44..df950b7e893 100644 --- a/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java +++ b/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java @@ -113,7 +113,7 @@ private void downloadFullTexts(Map> downloads, BibDataba Optional dir = databaseContext.getFirstExistingFileDir(preferences.getFilePreferences()); if (dir.isEmpty()) { dialogService.showErrorDialogAndWait(Localization.lang("Directory not found"), - Localization.lang("Main file directory not set. Check the preferences (linked files) or the library properties.")); + Localization.lang("Main file directory not set. Check the preferences (\"Linked files\") or modify the library properties (\"Override default file directories\").")); return; } diff --git a/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java b/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java index 7f9591c60f4..57df1e395c8 100644 --- a/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java +++ b/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java @@ -1,18 +1,23 @@ package org.jabref.gui.externalfiles; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; +import org.jabref.gui.DialogService; import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.externalfiletype.UnknownExternalFileType; import org.jabref.logic.cleanup.MoveFilesCleanup; import org.jabref.logic.cleanup.RenamePdfCleanup; +import org.jabref.logic.l10n.Localization; import org.jabref.logic.pdf.search.indexing.IndexingTaskManager; import org.jabref.logic.pdf.search.indexing.PdfIndexer; +import org.jabref.logic.util.io.FileNameCleaner; import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; @@ -30,12 +35,14 @@ public class ExternalFilesEntryLinker { private final BibDatabaseContext bibDatabaseContext; private final MoveFilesCleanup moveFilesCleanup; private final RenamePdfCleanup renameFilesCleanup; + private final DialogService dialogService; - public ExternalFilesEntryLinker(FilePreferences filePreferences, BibDatabaseContext bibDatabaseContext) { + public ExternalFilesEntryLinker(FilePreferences filePreferences, BibDatabaseContext bibDatabaseContext, DialogService dialogService) { this.filePreferences = filePreferences; this.bibDatabaseContext = bibDatabaseContext; this.moveFilesCleanup = new MoveFilesCleanup(bibDatabaseContext, filePreferences); this.renameFilesCleanup = new RenamePdfCleanup(false, bibDatabaseContext, filePreferences); + this.dialogService = dialogService; } public Optional copyFileToFileDir(Path file) { @@ -58,7 +65,8 @@ public void moveLinkedFilesToFileDir(BibEntry entry) { } public void addFilesToEntry(BibEntry entry, List files) { - for (Path file : files) { + List validFiles = getValidFileNames(files); + for (Path file : validFiles) { FileUtil.getFileExtension(file).ifPresent(ext -> { ExternalFileType type = ExternalFileTypes.getExternalFileTypeByExt(ext, filePreferences) .orElse(new UnknownExternalFileType(ext)); @@ -69,7 +77,7 @@ public void addFilesToEntry(BibEntry entry, List files) { } } - public void moveFilesToFileDirAndAddToEntry(BibEntry entry, List files, IndexingTaskManager indexingTaskManager) { + public void moveFilesToFileDirRenameAndAddToEntry(BibEntry entry, List files, IndexingTaskManager indexingTaskManager) { try (AutoCloseable blocker = indexingTaskManager.blockNewTasks()) { addFilesToEntry(entry, files); moveLinkedFilesToFileDir(entry); @@ -102,4 +110,32 @@ public void copyFilesToFileDirAndAddToEntry(BibEntry entry, List files, In LOGGER.error("Could not access Fulltext-Index", e); } } + + private List getValidFileNames(List filesToAdd) { + List validFileNames = new ArrayList<>(); + + for (Path fileToAdd : filesToAdd) { + if (FileUtil.detectBadFileName(fileToAdd.toString())) { + String newFilename = FileNameCleaner.cleanFileName(fileToAdd.getFileName().toString()); + + boolean correctButtonPressed = dialogService.showConfirmationDialogAndWait(Localization.lang("File \"%0\" cannot be added!", fileToAdd.getFileName()), + Localization.lang("Illegal characters in the file name detected.\nFile will be renamed to \"%0\" and added.", newFilename), + Localization.lang("Rename and add")); + + if (correctButtonPressed) { + Path correctPath = fileToAdd.resolveSibling(newFilename); + try { + Files.move(fileToAdd, correctPath); + validFileNames.add(correctPath); + } catch (IOException ex) { + LOGGER.error("Error moving file", ex); + dialogService.showErrorDialogAndWait(ex); + } + } + } else { + validFileNames.add(fileToAdd); + } + } + return validFileNames; + } } diff --git a/src/main/java/org/jabref/gui/externalfiles/GitIgnoreFileFilter.java b/src/main/java/org/jabref/gui/externalfiles/GitIgnoreFileFilter.java index 64f01de977b..e396c4f883a 100644 --- a/src/main/java/org/jabref/gui/externalfiles/GitIgnoreFileFilter.java +++ b/src/main/java/org/jabref/gui/externalfiles/GitIgnoreFileFilter.java @@ -37,7 +37,7 @@ public GitIgnoreFileFilter(Path path) { Path gitIgnore = currentPath.resolve(".gitignore"); try { Set plainGitIgnorePatternsFromGitIgnoreFile = Files.readAllLines(gitIgnore).stream() - .map(line -> line.trim()) + .map(String::trim) .filter(not(String::isEmpty)) .filter(line -> !line.startsWith("#")) // convert to Java syntax for Glob patterns diff --git a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java index cb6eb471137..31edf671e97 100644 --- a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java +++ b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java @@ -33,6 +33,7 @@ import org.jabref.logic.importer.ParseException; import org.jabref.logic.importer.fetcher.ArXivFetcher; import org.jabref.logic.importer.fetcher.DoiFetcher; +import org.jabref.logic.importer.fetcher.isbntobibtex.IsbnFetcher; import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.UpdateField; @@ -44,6 +45,7 @@ import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.identifier.ArXivIdentifier; import org.jabref.model.entry.identifier.DOI; +import org.jabref.model.entry.identifier.ISBN; import org.jabref.model.groups.GroupEntryChanger; import org.jabref.model.groups.GroupTreeNode; import org.jabref.model.util.FileUpdateMonitor; @@ -64,7 +66,6 @@ public class ImportHandler { private final UndoManager undoManager; private final StateManager stateManager; private final DialogService dialogService; - private final ImportFormatReader importFormatReader; private final TaskExecutor taskExecutor; public ImportHandler(BibDatabaseContext database, @@ -73,7 +74,6 @@ public ImportHandler(BibDatabaseContext database, UndoManager undoManager, StateManager stateManager, DialogService dialogService, - ImportFormatReader importFormatReader, TaskExecutor taskExecutor) { this.bibDatabaseContext = database; @@ -81,10 +81,9 @@ public ImportHandler(BibDatabaseContext database, this.fileUpdateMonitor = fileupdateMonitor; this.stateManager = stateManager; this.dialogService = dialogService; - this.importFormatReader = importFormatReader; this.taskExecutor = taskExecutor; - this.linker = new ExternalFilesEntryLinker(preferencesService.getFilePreferences(), database); + this.linker = new ExternalFilesEntryLinker(preferencesService.getFilePreferences(), database, dialogService); this.contentImporter = new ExternalFilesContentImporter(preferencesService.getImportFormatPreferences()); this.undoManager = undoManager; } @@ -289,22 +288,32 @@ public List handleStringData(String data) throws FetcherException { return Collections.emptyList(); } - Optional doi = DOI.parse(data); + Optional doi = DOI.findInText(data); if (doi.isPresent()) { return fetchByDOI(doi.get()); } + Optional arXiv = ArXivIdentifier.parse(data); if (arXiv.isPresent()) { return fetchByArXiv(arXiv.get()); } + Optional isbn = ISBN.parse(data); + if (isbn.isPresent()) { + return fetchByISBN(isbn.get()); + } + return tryImportFormats(data); } private List tryImportFormats(String data) { try { + ImportFormatReader importFormatReader = new ImportFormatReader( + preferencesService.getImporterPreferences(), + preferencesService.getImportFormatPreferences(), + fileUpdateMonitor); UnknownFormatImport unknownFormatImport = importFormatReader.importUnknownFormat(data); - return unknownFormatImport.parserResult.getDatabase().getEntries(); + return unknownFormatImport.parserResult().getDatabase().getEntries(); } catch (ImportException ex) { // ex is already localized dialogService.showErrorDialogAndWait(Localization.lang("Import error"), ex); return Collections.emptyList(); @@ -312,7 +321,7 @@ private List tryImportFormats(String data) { } private List fetchByDOI(DOI doi) throws FetcherException { - LOGGER.info("Found DOI in clipboard"); + LOGGER.info("Found DOI identifer in clipboard"); Optional entry = new DoiFetcher(preferencesService.getImportFormatPreferences()).performSearchById(doi.getDOI()); return OptionalUtil.toList(entry); } @@ -322,4 +331,10 @@ private List fetchByArXiv(ArXivIdentifier arXivIdentifier) throws Fetc Optional entry = new ArXivFetcher(preferencesService.getImportFormatPreferences()).performSearchById(arXivIdentifier.getNormalizedWithoutVersion()); return OptionalUtil.toList(entry); } + + private List fetchByISBN(ISBN isbn) throws FetcherException { + LOGGER.info("Found ISBN identifier in clipboard"); + Optional entry = new IsbnFetcher(preferencesService.getImportFormatPreferences()).performSearchById(isbn.getNormalized()); + return OptionalUtil.toList(entry); + } } diff --git a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogView.java b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogView.java index 13ddbe4bd34..03d14de7df1 100644 --- a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogView.java +++ b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogView.java @@ -42,7 +42,6 @@ import org.jabref.gui.util.ValueTableCellFactory; import org.jabref.gui.util.ViewModelListCellFactory; import org.jabref.gui.util.ViewModelTreeCellFactory; -import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.util.FileUpdateMonitor; @@ -84,7 +83,6 @@ public class UnlinkedFilesDialogView extends BaseDialog { @Inject private TaskExecutor taskExecutor; @Inject private FileUpdateMonitor fileUpdateMonitor; @Inject private ThemeManager themeManager; - @Inject private ImportFormatReader importFormatReader; private final ControlsFxVisualizer validationVisualizer; private UnlinkedFilesDialogViewModel viewModel; @@ -112,7 +110,13 @@ public UnlinkedFilesDialogView() { @FXML private void initialize() { - viewModel = new UnlinkedFilesDialogViewModel(dialogService, undoManager, fileUpdateMonitor, preferencesService, stateManager, taskExecutor, importFormatReader); + viewModel = new UnlinkedFilesDialogViewModel( + dialogService, + undoManager, + fileUpdateMonitor, + preferencesService, + stateManager, + taskExecutor); this.bibDatabaseContext = stateManager.getActiveDatabase().orElseThrow(() -> new NullPointerException("No active library")); @@ -208,7 +212,7 @@ private void initResultTable() { colStatus.setCellValueFactory(cellData -> cellData.getValue().icon()); colStatus.setCellFactory(new ValueTableCellFactory().withGraphic(JabRefIcon::getGraphicNode)); - importResultTable.setColumnResizePolicy((param) -> true); + importResultTable.setColumnResizePolicy(param -> true); importResultTable.setItems(viewModel.resultTableItems()); } diff --git a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogViewModel.java b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogViewModel.java index 082a9390594..dffa9a50b51 100644 --- a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogViewModel.java +++ b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogViewModel.java @@ -35,7 +35,6 @@ import org.jabref.gui.util.FileDialogConfiguration; import org.jabref.gui.util.FileNodeViewModel; import org.jabref.gui.util.TaskExecutor; -import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.StandardFileType; import org.jabref.model.database.BibDatabaseContext; @@ -85,8 +84,7 @@ public UnlinkedFilesDialogViewModel(DialogService dialogService, FileUpdateMonitor fileUpdateMonitor, PreferencesService preferences, StateManager stateManager, - TaskExecutor taskExecutor, - ImportFormatReader importFormatReader) { + TaskExecutor taskExecutor) { this.preferences = preferences; this.dialogService = dialogService; this.taskExecutor = taskExecutor; @@ -98,7 +96,6 @@ public UnlinkedFilesDialogViewModel(DialogService dialogService, undoManager, stateManager, dialogService, - importFormatReader, taskExecutor); this.fileFilterList = FXCollections.observableArrayList( @@ -176,7 +173,7 @@ public void startExport() { List fileList = checkedFileListProperty.stream() .map(item -> item.getValue().getPath()) .filter(path -> path.toFile().isFile()) - .collect(Collectors.toList()); + .toList(); if (fileList.isEmpty()) { LOGGER.warn("There are no valid files checked"); return; diff --git a/src/main/java/org/jabref/gui/externalfiletype/CustomExternalFileType.java b/src/main/java/org/jabref/gui/externalfiletype/CustomExternalFileType.java index 5a4887c6fd3..9a922177f99 100644 --- a/src/main/java/org/jabref/gui/externalfiletype/CustomExternalFileType.java +++ b/src/main/java/org/jabref/gui/externalfiletype/CustomExternalFileType.java @@ -178,8 +178,7 @@ public boolean equals(Object object) { return true; } - if (object instanceof CustomExternalFileType) { - CustomExternalFileType other = (CustomExternalFileType) object; + if (object instanceof CustomExternalFileType other) { return Objects.equals(name, other.name) && Objects.equals(extension, other.extension) && Objects.equals(mimeType, other.mimeType) && Objects.equals(openWith, other.openWith) && Objects.equals(iconName, other.iconName); } diff --git a/src/main/java/org/jabref/gui/fieldeditors/EditorTextArea.java b/src/main/java/org/jabref/gui/fieldeditors/EditorTextArea.java index 92eb4e88817..d57148409be 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/EditorTextArea.java +++ b/src/main/java/org/jabref/gui/fieldeditors/EditorTextArea.java @@ -76,7 +76,6 @@ public void paste() { */ @FunctionalInterface public interface PasteActionHandler { - void handle(); } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java b/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java index 94eeb164af0..1ff28bdbb55 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java +++ b/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java @@ -53,7 +53,7 @@ public static FieldEditorFX getForField(final Field field, journalAbbreviationRepository, preferences.getEntryEditorPreferences().shouldAllowIntegerEditionBibtex()); - boolean isMultiLine = FieldFactory.isMultiLineField(field, preferences.getFieldContentParserPreferences().getNonWrappableFields()); + boolean isMultiLine = FieldFactory.isMultiLineField(field, preferences.getFieldPreferences().getNonWrappableFields()); if (preferences.getTimestampPreferences().getTimestampField().equals(field)) { return new DateEditor(field, DateTimeFormatter.ofPattern(preferences.getTimestampPreferences().getTimestampFormat()), suggestionProvider, fieldCheckers, preferences); @@ -62,13 +62,15 @@ public static FieldEditorFX getForField(final Field field, } else if (fieldProperties.contains(FieldProperty.EXTERNAL)) { return new UrlEditor(field, dialogService, suggestionProvider, fieldCheckers, preferences); } else if (fieldProperties.contains(FieldProperty.JOURNAL_NAME)) { - return new JournalEditor(field, journalAbbreviationRepository, preferences, suggestionProvider, fieldCheckers); + return new JournalEditor(field, taskExecutor, dialogService, journalAbbreviationRepository, preferences, suggestionProvider, fieldCheckers); } else if (fieldProperties.contains(FieldProperty.DOI) || fieldProperties.contains(FieldProperty.EPRINT) || fieldProperties.contains(FieldProperty.ISBN)) { return new IdentifierEditor(field, taskExecutor, dialogService, suggestionProvider, fieldCheckers, preferences); } else if (field == StandardField.OWNER) { return new OwnerEditor(field, preferences, suggestionProvider, fieldCheckers); + } else if (field == StandardField.GROUPS) { + return new GroupEditor(field, suggestionProvider, fieldCheckers, preferences, isMultiLine); } else if (fieldProperties.contains(FieldProperty.FILE_EDITOR)) { - return new LinkedFilesEditor(field, dialogService, databaseContext, taskExecutor, suggestionProvider, fieldCheckers, preferences); + return new LinkedFilesEditor(field, databaseContext, suggestionProvider, fieldCheckers); } else if (fieldProperties.contains(FieldProperty.YES_NO)) { return new OptionEditor<>(new YesNoEditorViewModel(field, suggestionProvider, fieldCheckers)); } else if (fieldProperties.contains(FieldProperty.MONTH)) { @@ -91,10 +93,12 @@ public static FieldEditorFX getForField(final Field field, return new LinkedEntriesEditor(field, databaseContext, (SuggestionProvider) suggestionProvider, fieldCheckers); } else if (fieldProperties.contains(FieldProperty.PERSON_NAMES)) { return new PersonsEditor(field, suggestionProvider, preferences, fieldCheckers, isMultiLine); - } else if (StandardField.KEYWORDS.equals(field)) { + } else if (StandardField.KEYWORDS == field) { return new KeywordsEditor(field, suggestionProvider, fieldCheckers, preferences); } else if (field == InternalField.KEY_FIELD) { return new CitationKeyEditor(field, preferences, suggestionProvider, fieldCheckers, databaseContext, undoManager, dialogService); + } else if (field == StandardField.ISSN) { + return new ISSNEditor(field, suggestionProvider, fieldCheckers, preferences, taskExecutor, dialogService); } else { // default return new SimpleEditor(field, suggestionProvider, fieldCheckers, preferences, isMultiLine); diff --git a/src/main/java/org/jabref/gui/fieldeditors/FieldNameLabel.java b/src/main/java/org/jabref/gui/fieldeditors/FieldNameLabel.java index ae3852c3aae..33c9018a829 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/FieldNameLabel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/FieldNameLabel.java @@ -223,14 +223,12 @@ public String getDescription(Field field) { case TIMESTAMP: return Localization.lang("Timestamp of this entry, when it has been created or last modified."); } - } else if (field instanceof InternalField) { - InternalField internalField = (InternalField) field; + } else if (field instanceof InternalField internalField) { switch (internalField) { case KEY_FIELD: return Localization.lang("Key by which the work may be cited."); } - } else if (field instanceof SpecialField) { - SpecialField specialField = (SpecialField) field; + } else if (field instanceof SpecialField specialField) { switch (specialField) { case PRINTED: return Localization.lang("User-specific printed flag, in case the entry has been printed."); diff --git a/src/main/java/org/jabref/gui/fieldeditors/GroupEditor.java b/src/main/java/org/jabref/gui/fieldeditors/GroupEditor.java new file mode 100644 index 00000000000..db516806b03 --- /dev/null +++ b/src/main/java/org/jabref/gui/fieldeditors/GroupEditor.java @@ -0,0 +1,57 @@ +package org.jabref.gui.fieldeditors; + +import java.util.List; +import java.util.Optional; + +import javafx.scene.input.TransferMode; + +import org.jabref.gui.DragAndDropDataFormats; +import org.jabref.gui.autocompleter.SuggestionProvider; +import org.jabref.logic.integrity.FieldCheckers; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.StandardField; +import org.jabref.preferences.PreferencesService; + +public class GroupEditor extends SimpleEditor { + + private Optional bibEntry; + + public GroupEditor(final Field field, + final SuggestionProvider suggestionProvider, + final FieldCheckers fieldCheckers, + final PreferencesService preferences, + final boolean isMultiLine) { + super(field, suggestionProvider, fieldCheckers, preferences, isMultiLine); + + this.setOnDragOver(event -> { + if (event.getDragboard().hasContent(DragAndDropDataFormats.GROUP)) { + event.acceptTransferModes(TransferMode.MOVE); + } + event.consume(); + }); + + this.setOnDragDropped(event -> { + boolean success = false; + if (event.getDragboard().hasContent(DragAndDropDataFormats.GROUP)) { + List draggedGroups = (List) event.getDragboard().getContent(DragAndDropDataFormats.GROUP); + if (bibEntry.isPresent() && draggedGroups.get(0) != null) { + String newGroup = bibEntry.map(entry -> entry.getField(StandardField.GROUPS) + .map(oldGroups -> oldGroups + (preferences.getBibEntryPreferences().getKeywordSeparator()) + (draggedGroups.get(0))) + .orElse(draggedGroups.get(0))) + .orElse(null); + bibEntry.map(entry -> entry.setField(StandardField.GROUPS, newGroup)); + success = true; + } + } + event.setDropCompleted(success); + event.consume(); + }); + } + + @Override + public void bindToEntry(BibEntry entry) { + super.bindToEntry(entry); + this.bibEntry = Optional.of(entry); + } +} diff --git a/src/main/java/org/jabref/gui/fieldeditors/ISSNEditor.fxml b/src/main/java/org/jabref/gui/fieldeditors/ISSNEditor.fxml new file mode 100644 index 00000000000..021a0f24f60 --- /dev/null +++ b/src/main/java/org/jabref/gui/fieldeditors/ISSNEditor.fxml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + diff --git a/src/main/java/org/jabref/gui/fieldeditors/ISSNEditor.java b/src/main/java/org/jabref/gui/fieldeditors/ISSNEditor.java new file mode 100644 index 00000000000..55dea620477 --- /dev/null +++ b/src/main/java/org/jabref/gui/fieldeditors/ISSNEditor.java @@ -0,0 +1,77 @@ +package org.jabref.gui.fieldeditors; + +import javafx.fxml.FXML; +import javafx.scene.Parent; +import javafx.scene.control.Button; +import javafx.scene.layout.HBox; + +import org.jabref.gui.DialogService; +import org.jabref.gui.autocompleter.SuggestionProvider; +import org.jabref.gui.fieldeditors.contextmenu.DefaultMenu; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.integrity.FieldCheckers; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.Field; +import org.jabref.preferences.PreferencesService; + +import com.airhacks.afterburner.views.ViewLoader; + +public class ISSNEditor extends HBox implements FieldEditorFX { + @FXML private ISSNEditorViewModel viewModel; + @FXML private EditorTextArea textArea; + @FXML private Button journalInfoButton; + private final DialogService dialogService; + private final PreferencesService preferencesService; + + public ISSNEditor(Field field, + SuggestionProvider suggestionProvider, + FieldCheckers fieldCheckers, + PreferencesService preferences, + TaskExecutor taskExecutor, + DialogService dialogService) { + this.preferencesService = preferences; + this.dialogService = dialogService; + + this.viewModel = new ISSNEditorViewModel( + field, + suggestionProvider, + fieldCheckers, + taskExecutor, + dialogService); + + ViewLoader.view(this) + .root(this) + .load(); + + textArea.textProperty().bindBidirectional(viewModel.textProperty()); + textArea.initContextMenu(new DefaultMenu(textArea)); + + new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textArea); + } + + public ISSNEditorViewModel getViewModel() { + return viewModel; + } + + @Override + public void bindToEntry(BibEntry entry) { + viewModel.bindToEntry(entry); + } + + @Override + public Parent getNode() { + return this; + } + + @Override + public void requestFocus() { + textArea.requestFocus(); + } + + @FXML + private void showJournalInfo() { + if (JournalInfoOptInDialogHelper.isJournalInfoEnabled(dialogService, preferencesService.getEntryEditorPreferences())) { + viewModel.showJournalInfo(journalInfoButton); + } + } +} diff --git a/src/main/java/org/jabref/gui/fieldeditors/ISSNEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/ISSNEditorViewModel.java new file mode 100644 index 00000000000..0ce1eb1862d --- /dev/null +++ b/src/main/java/org/jabref/gui/fieldeditors/ISSNEditorViewModel.java @@ -0,0 +1,29 @@ +package org.jabref.gui.fieldeditors; + +import javafx.scene.control.Button; + +import org.jabref.gui.DialogService; +import org.jabref.gui.autocompleter.SuggestionProvider; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.integrity.FieldCheckers; +import org.jabref.model.entry.field.Field; + +public class ISSNEditorViewModel extends AbstractEditorViewModel { + private final TaskExecutor taskExecutor; + private final DialogService dialogService; + + public ISSNEditorViewModel( + Field field, + SuggestionProvider suggestionProvider, + FieldCheckers fieldCheckers, + TaskExecutor taskExecutor, + DialogService dialogService) { + super(field, suggestionProvider, fieldCheckers); + this.taskExecutor = taskExecutor; + this.dialogService = dialogService; + } + + public void showJournalInfo(Button journalInfoButton) { + PopOverUtil.showJournalInfo(journalInfoButton, entry, dialogService, taskExecutor); + } +} diff --git a/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.fxml b/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.fxml index dafded3704e..e7e7636073c 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.fxml +++ b/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.fxml @@ -5,16 +5,29 @@ + - + + + + diff --git a/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java b/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java index 491818b4663..5c12abccd71 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java @@ -2,11 +2,14 @@ import javafx.fxml.FXML; import javafx.scene.Parent; +import javafx.scene.control.Button; import javafx.scene.layout.HBox; +import org.jabref.gui.DialogService; import org.jabref.gui.autocompleter.AutoCompletionTextInputBinding; import org.jabref.gui.autocompleter.SuggestionProvider; import org.jabref.gui.fieldeditors.contextmenu.DefaultMenu; +import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.integrity.FieldCheckers; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.model.entry.BibEntry; @@ -19,13 +22,27 @@ public class JournalEditor extends HBox implements FieldEditorFX { @FXML private JournalEditorViewModel viewModel; @FXML private EditorTextField textField; + @FXML private Button journalInfoButton; + private final DialogService dialogService; + private final PreferencesService preferencesService; public JournalEditor(Field field, + TaskExecutor taskExecutor, + DialogService dialogService, JournalAbbreviationRepository journalAbbreviationRepository, PreferencesService preferences, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers) { - this.viewModel = new JournalEditorViewModel(field, suggestionProvider, journalAbbreviationRepository, fieldCheckers); + this.dialogService = dialogService; + this.preferencesService = preferences; + + this.viewModel = new JournalEditorViewModel( + field, + suggestionProvider, + journalAbbreviationRepository, + fieldCheckers, + taskExecutor, + dialogService); ViewLoader.view(this) .root(this) @@ -57,4 +74,11 @@ public Parent getNode() { private void toggleAbbreviation() { viewModel.toggleAbbreviation(); } + + @FXML + private void showJournalInfo() { + if (JournalInfoOptInDialogHelper.isJournalInfoEnabled(dialogService, preferencesService.getEntryEditorPreferences())) { + viewModel.showJournalInfo(journalInfoButton); + } + } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/JournalEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/JournalEditorViewModel.java index 42effd401e4..3e6267e1eed 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/JournalEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/JournalEditorViewModel.java @@ -1,6 +1,10 @@ package org.jabref.gui.fieldeditors; +import javafx.scene.control.Button; + +import org.jabref.gui.DialogService; import org.jabref.gui.autocompleter.SuggestionProvider; +import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.integrity.FieldCheckers; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.model.entry.field.Field; @@ -8,10 +12,20 @@ public class JournalEditorViewModel extends AbstractEditorViewModel { private final JournalAbbreviationRepository journalAbbreviationRepository; + private final TaskExecutor taskExecutor; + private final DialogService dialogService; - public JournalEditorViewModel(Field field, SuggestionProvider suggestionProvider, JournalAbbreviationRepository journalAbbreviationRepository, FieldCheckers fieldCheckers) { + public JournalEditorViewModel( + Field field, + SuggestionProvider suggestionProvider, + JournalAbbreviationRepository journalAbbreviationRepository, + FieldCheckers fieldCheckers, + TaskExecutor taskExecutor, + DialogService dialogService) { super(field, suggestionProvider, fieldCheckers); this.journalAbbreviationRepository = journalAbbreviationRepository; + this.taskExecutor = taskExecutor; + this.dialogService = dialogService; } public void toggleAbbreviation() { @@ -28,4 +42,8 @@ public void toggleAbbreviation() { // panel.getUndoManager().addEdit(new UndoableFieldChange(entry, editor.getName(), text, nextAbbreviation)); }); } + + public void showJournalInfo(Button journalInfoButton) { + PopOverUtil.showJournalInfo(journalInfoButton, entry, dialogService, taskExecutor); + } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/JournalInfoOptInDialogHelper.java b/src/main/java/org/jabref/gui/fieldeditors/JournalInfoOptInDialogHelper.java new file mode 100644 index 00000000000..c811936a379 --- /dev/null +++ b/src/main/java/org/jabref/gui/fieldeditors/JournalInfoOptInDialogHelper.java @@ -0,0 +1,35 @@ +package org.jabref.gui.fieldeditors; + +import org.jabref.gui.DialogService; +import org.jabref.gui.entryeditor.EntryEditorPreferences; +import org.jabref.logic.l10n.Localization; + +public class JournalInfoOptInDialogHelper { + + /** + * Using the journal information data fetcher service needs to be opt-in for GDPR compliance. + */ + public static boolean isJournalInfoEnabled(DialogService dialogService, EntryEditorPreferences preferences) { + if (preferences.shouldEnableJournalPopup() == EntryEditorPreferences.JournalPopupEnabled.ENABLED) { + return true; + } + + if (preferences.shouldEnableJournalPopup() == EntryEditorPreferences.JournalPopupEnabled.DISABLED) { + dialogService.notify( + Localization.lang("Please enable journal information fetching in %0 > %1", + Localization.lang("Preferences"), + Localization.lang("Web search")) + ); + return false; + } + + boolean journalInfoEnabled = dialogService.showConfirmationDialogAndWait( + Localization.lang("Remote services"), + Localization.lang("Allow sending ISSN to a JabRef online service (SCimago) for fetching journal information")); + + preferences.setEnableJournalPopup(journalInfoEnabled + ? EntryEditorPreferences.JournalPopupEnabled.ENABLED + : EntryEditorPreferences.JournalPopupEnabled.DISABLED); + return journalInfoEnabled; + } +} diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.fxml b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.fxml index 4b75bbe6311..10d87fdc46e 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.fxml +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.fxml @@ -1,8 +1,8 @@ - + - + diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java index 3067ee36a11..8c54685b210 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java @@ -1,53 +1,101 @@ package org.jabref.gui.fieldeditors; +import java.util.Comparator; import java.util.stream.Collectors; import javafx.beans.binding.Bindings; import javafx.fxml.FXML; +import javafx.scene.Node; import javafx.scene.Parent; +import javafx.scene.control.ContentDisplay; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.Label; +import javafx.scene.input.MouseButton; import javafx.scene.layout.HBox; +import org.jabref.gui.ClipBoardManager; +import org.jabref.gui.DialogService; +import org.jabref.gui.JabRefDialogService; +import org.jabref.gui.actions.ActionFactory; +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.gui.actions.StandardActions; import org.jabref.gui.autocompleter.SuggestionProvider; +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.util.ViewModelListCellFactory; import org.jabref.logic.integrity.FieldCheckers; +import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.ParsedEntryLink; import org.jabref.model.entry.field.Field; import com.airhacks.afterburner.views.ViewLoader; -import com.jfoenix.controls.JFXChip; -import com.jfoenix.controls.JFXChipView; -import com.jfoenix.controls.JFXDefaultChip; +import com.dlsc.gemsfx.TagsField; +import jakarta.inject.Inject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class LinkedEntriesEditor extends HBox implements FieldEditorFX { - @FXML + private static final Logger LOGGER = LoggerFactory.getLogger(LinkedEntriesEditor.class); + + @FXML public TagsField entryLinkField; + + @Inject private DialogService dialogService; + @Inject private ClipBoardManager clipBoardManager; + @Inject private KeyBindingRepository keyBindingRepository; + private final LinkedEntriesEditorViewModel viewModel; - @FXML - private JFXChipView chipView; public LinkedEntriesEditor(Field field, BibDatabaseContext databaseContext, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers) { - this.viewModel = new LinkedEntriesEditorViewModel(field, suggestionProvider, databaseContext, fieldCheckers); - ViewLoader.view(this) .root(this) .load(); - chipView.setConverter(viewModel.getStringConverter()); - var autoCompletionItemFactory = new ViewModelListCellFactory() - .withText(ParsedEntryLink::getKey); - chipView.getAutoCompletePopup().setSuggestionsCellFactory(autoCompletionItemFactory); - chipView.getAutoCompletePopup().setCellLimit(5); - chipView.getSuggestions().addAll(suggestionProvider.getPossibleSuggestions().stream().map(ParsedEntryLink::new).collect(Collectors.toList())); - - chipView.setChipFactory((view, item) -> { - JFXChip chip = new JFXDefaultChip<>(view, item); - chip.setOnMouseClicked(event -> viewModel.jumpToEntry(item)); - return chip; + this.viewModel = new LinkedEntriesEditorViewModel(field, suggestionProvider, databaseContext, fieldCheckers); + + entryLinkField.setCellFactory(new ViewModelListCellFactory().withText(ParsedEntryLink::getKey)); + // Mind the .collect(Collectors.toList()) as the list needs to be mutable + entryLinkField.setSuggestionProvider(request -> + suggestionProvider.getPossibleSuggestions().stream() + .filter(suggestion -> suggestion.getCitationKey().orElse("").toLowerCase() + .contains(request.getUserText().toLowerCase())) + .map(ParsedEntryLink::new) + .collect(Collectors.toList())); + entryLinkField.setTagViewFactory(this::createTag); + entryLinkField.setConverter(viewModel.getStringConverter()); + entryLinkField.setNewItemProducer(searchText -> viewModel.getStringConverter().fromString(searchText)); + entryLinkField.setMatcher((entryLink, searchText) -> entryLink.getKey().toLowerCase().startsWith(searchText.toLowerCase())); + entryLinkField.setComparator(Comparator.comparing(ParsedEntryLink::getKey)); + entryLinkField.setShowSearchIcon(false); + entryLinkField.getEditor().getStyleClass().clear(); + entryLinkField.getEditor().getStyleClass().add("tags-field-editor"); + + Bindings.bindContentBidirectional(entryLinkField.getTags(), viewModel.linkedEntriesProperty()); + } + + private Node createTag(ParsedEntryLink entryLink) { + Label tagLabel = new Label(); + tagLabel.setText(entryLinkField.getConverter().toString(entryLink)); + tagLabel.setGraphic(IconTheme.JabRefIcons.REMOVE_TAGS.getGraphicNode()); + tagLabel.getGraphic().setOnMouseClicked(event -> entryLinkField.removeTags(entryLink)); + tagLabel.setContentDisplay(ContentDisplay.RIGHT); + tagLabel.setOnMouseClicked(event -> { + if (event.getClickCount() == 2 && event.getButton().equals(MouseButton.PRIMARY)) { + viewModel.jumpToEntry(entryLink); + } }); - Bindings.bindContentBidirectional(chipView.getChips(), viewModel.linkedEntriesProperty()); + ContextMenu contextMenu = new ContextMenu(); + ActionFactory factory = new ActionFactory(keyBindingRepository); + contextMenu.getItems().addAll( + factory.createMenuItem(StandardActions.COPY, new TagContextAction(StandardActions.COPY, entryLink)), + factory.createMenuItem(StandardActions.CUT, new TagContextAction(StandardActions.CUT, entryLink)), + factory.createMenuItem(StandardActions.DELETE, new TagContextAction(StandardActions.DELETE, entryLink)) + ); + tagLabel.setContextMenu(contextMenu); + return tagLabel; } public LinkedEntriesEditorViewModel getViewModel() { @@ -63,4 +111,35 @@ public void bindToEntry(BibEntry entry) { public Parent getNode() { return this; } + + private class TagContextAction extends SimpleCommand { + private final StandardActions command; + private final ParsedEntryLink entryLink; + + public TagContextAction(StandardActions command, ParsedEntryLink entryLink) { + this.command = command; + this.entryLink = entryLink; + } + + @Override + public void execute() { + switch (command) { + case COPY -> { + clipBoardManager.setContent(entryLink.getKey()); + dialogService.notify(Localization.lang("Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(entryLink.getKey()))); + } + case CUT -> { + clipBoardManager.setContent(entryLink.getKey()); + dialogService.notify(Localization.lang("Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(entryLink.getKey()))); + entryLinkField.removeTags(entryLink); + } + case DELETE -> + entryLinkField.removeTags(entryLink); + default -> + LOGGER.info("Action {} not defined", command.getText()); + } + } + } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java index 62ea9e1f973..d0f3c9a977e 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java @@ -13,6 +13,7 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSocketFactory; import javafx.beans.Observable; @@ -43,6 +44,8 @@ import org.jabref.gui.util.ControlHelper; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.externalfiles.LinkedFileHandler; +import org.jabref.logic.importer.FetcherClientException; +import org.jabref.logic.importer.FetcherServerException; import org.jabref.logic.importer.Importer; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.fileformat.PdfContentImporter; @@ -82,7 +85,7 @@ public class LinkedFileViewModel extends AbstractViewModel { private final DialogService dialogService; private final BibEntry entry; private final TaskExecutor taskExecutor; - private final PreferencesService preferences; + private final PreferencesService preferencesService; private final LinkedFileHandler linkedFileHandler; private ObjectBinding linkedFileIconBinding; @@ -94,11 +97,11 @@ public LinkedFileViewModel(LinkedFile linkedFile, BibDatabaseContext databaseContext, TaskExecutor taskExecutor, DialogService dialogService, - PreferencesService preferences) { + PreferencesService preferencesService) { this.linkedFile = linkedFile; - this.preferences = preferences; - this.linkedFileHandler = new LinkedFileHandler(linkedFile, entry, databaseContext, preferences.getFilePreferences()); + this.preferencesService = preferencesService; + this.linkedFileHandler = new LinkedFileHandler(linkedFile, entry, databaseContext, preferencesService.getFilePreferences()); this.databaseContext = databaseContext; this.entry = entry; this.dialogService = dialogService; @@ -110,14 +113,14 @@ public LinkedFileViewModel(LinkedFile linkedFile, if (linkedFile.isOnlineLink()) { return true; } else { - Optional path = FileUtil.find(databaseContext, link, preferences.getFilePreferences()); + Optional path = FileUtil.find(databaseContext, link, preferencesService.getFilePreferences()); return path.isPresent() && Files.exists(path.get()); } }, ValidationMessage.warning(Localization.lang("Could not find file '%0'.", linkedFile.getLink()))); downloadOngoing.bind(downloadProgress.greaterThanOrEqualTo(0).and(downloadProgress.lessThan(1))); - isOfflinePdf.setValue(!linkedFile.isOnlineLink() && linkedFile.getFileType().equalsIgnoreCase("pdf")); + isOfflinePdf.setValue(!linkedFile.isOnlineLink() && "pdf".equalsIgnoreCase(linkedFile.getFileType())); } public BooleanProperty isOfflinePdfProperty() { @@ -177,7 +180,7 @@ public Optional findIn(List directories) { } public JabRefIcon getTypeIcon() { - return ExternalFileTypes.getExternalFileTypeByLinkedFile(linkedFile, false, preferences.getFilePreferences()) + return ExternalFileTypes.getExternalFileTypeByLinkedFile(linkedFile, false, preferencesService.getFilePreferences()) .map(ExternalFileType::getIcon) .orElse(IconTheme.JabRefIcons.FILE); } @@ -208,8 +211,8 @@ public Observable[] getObservables() { public void open() { try { - Optional type = ExternalFileTypes.getExternalFileTypeByLinkedFile(linkedFile, true, preferences.getFilePreferences()); - boolean successful = JabRefDesktop.openExternalFileAnyFormat(databaseContext, preferences, linkedFile.getLink(), type); + Optional type = ExternalFileTypes.getExternalFileTypeByLinkedFile(linkedFile, true, preferencesService.getFilePreferences()); + boolean successful = JabRefDesktop.openExternalFileAnyFormat(databaseContext, preferencesService.getFilePreferences(), linkedFile.getLink(), type); if (!successful) { dialogService.showErrorDialogAndWait(Localization.lang("File not found"), Localization.lang("Could not find file '%0'.", linkedFile.getLink())); } @@ -224,10 +227,9 @@ public void openFolder() { Optional resolvedPath = FileUtil.find( databaseContext, linkedFile.getLink(), - preferences.getFilePreferences()); - + preferencesService.getFilePreferences()); if (resolvedPath.isPresent()) { - JabRefDesktop.openFolderAndSelectFile(resolvedPath.get(), preferences, dialogService); + JabRefDesktop.openFolderAndSelectFile(resolvedPath.get(), preferencesService.getExternalApplicationsPreferences(), dialogService); } else { dialogService.showErrorDialogAndWait(Localization.lang("File not found")); } @@ -259,7 +261,7 @@ public void renameFileToName(String targetFileName) { return; } - Optional file = linkedFile.findIn(databaseContext, preferences.getFilePreferences()); + Optional file = linkedFile.findIn(databaseContext, preferencesService.getFilePreferences()); if (file.isPresent()) { performRenameWithConflictCheck(targetFileName); } else { @@ -296,14 +298,14 @@ public void moveToDefaultDirectory() { } // Get target folder - Optional fileDir = databaseContext.getFirstExistingFileDir(preferences.getFilePreferences()); + Optional fileDir = databaseContext.getFirstExistingFileDir(preferencesService.getFilePreferences()); if (fileDir.isEmpty()) { dialogService.showErrorDialogAndWait(Localization.lang("Move file"), Localization.lang("File directory is not set or does not exist!")); return; } - Optional file = linkedFile.findIn(databaseContext, preferences.getFilePreferences()); - if ((file.isPresent())) { + Optional file = linkedFile.findIn(databaseContext, preferencesService.getFilePreferences()); + if (file.isPresent()) { // Found the linked file, so move it try { linkedFileHandler.moveToDefaultDirectory(); @@ -338,7 +340,7 @@ public boolean isGeneratedNameSameAsOriginal() { * @return true if suggested filepath is same as existing filepath. */ public boolean isGeneratedPathSameAsOriginal() { - FilePreferences filePreferences = preferences.getFilePreferences(); + FilePreferences filePreferences = preferencesService.getFilePreferences(); Optional baseDir = databaseContext.getFirstExistingFileDir(filePreferences); if (baseDir.isEmpty()) { // could not find default path @@ -353,7 +355,7 @@ public boolean isGeneratedPathSameAsOriginal() { Optional targetDir = baseDir.map(dir -> dir.resolve(targetDirectoryName)); - Optional currentDir = linkedFile.findIn(databaseContext, preferences.getFilePreferences()).map(Path::getParent); + Optional currentDir = linkedFile.findIn(databaseContext, preferencesService.getFilePreferences()).map(Path::getParent); if (currentDir.isEmpty()) { // Could not find file return false; @@ -381,7 +383,7 @@ public void moveToDefaultDirectoryAndRename() { * successfully, does not exist in the first place or the user choose to remove it) */ public boolean delete() { - Optional file = linkedFile.findIn(databaseContext, preferences.getFilePreferences()); + Optional file = linkedFile.findIn(databaseContext, preferencesService.getFilePreferences()); if (file.isEmpty()) { LOGGER.warn("Could not find file {}", linkedFile.getLink()); @@ -422,16 +424,12 @@ public void edit() { }); } - public WriteMetadataToPdfCommand createWriteMetadataToPdfCommand() { - return new WriteMetadataToPdfCommand(linkedFile, databaseContext, preferences, dialogService, entry, LOGGER, taskExecutor); - } - public void download() { if (!linkedFile.isOnlineLink()) { throw new UnsupportedOperationException("In order to download the file it has to be an online link"); } try { - Optional targetDirectory = databaseContext.getFirstExistingFileDir(preferences.getFilePreferences()); + Optional targetDirectory = databaseContext.getFirstExistingFileDir(preferencesService.getFilePreferences()); if (targetDirectory.isEmpty()) { dialogService.showErrorDialogAndWait(Localization.lang("Download file"), Localization.lang("File directory is not set or does not exist!")); return; @@ -456,8 +454,8 @@ public void download() { // we need to call LinkedFileViewModel#fromFile, because we need to make the path relative to the configured directories LinkedFile newLinkedFile = LinkedFilesEditorViewModel.fromFile( destination, - databaseContext.getFileDirectories(preferences.getFilePreferences()), - preferences.getFilePreferences()); + databaseContext.getFileDirectories(preferencesService.getFilePreferences()), + preferencesService.getFilePreferences()); entry.replaceDownloadedFile(linkedFile.getLink(), newLinkedFile); // Notify in bar when the file type is HTML. @@ -473,20 +471,37 @@ public void download() { Localization.lang("Fulltext for") + ": " + entry.getCitationKey().orElse(Localization.lang("New entry"))); downloadTask.showToUser(true); downloadTask.onFailure(ex -> { - LOGGER.error("Error downloading", ex); - dialogService.showErrorDialogAndWait(Localization.lang("Error downloading"), ex); - }); - taskExecutor.execute(downloadTask); - } catch (MalformedURLException exception) { - dialogService.showErrorDialogAndWait(Localization.lang("Invalid URL"), exception); + LOGGER.error("Error downloading from URL: " + urlDownload, ex); + String fetcherExceptionMessage = ex.getMessage(); + int statusCode; + if (ex instanceof FetcherClientException clientException) { + statusCode = clientException.getStatusCode(); + if (statusCode == 401) { + dialogService.showInformationDialogAndWait(Localization.lang("Failed to download from URL"), Localization.lang("401 Unauthorized: Access Denied. You are not authorized to access this resource. Please check your credentials and try again. If you believe you should have access, please contact the administrator for assistance.\nURL: %0 \n %1", urlDownload.getSource(), fetcherExceptionMessage)); + } else if (statusCode == 403) { + dialogService.showInformationDialogAndWait(Localization.lang("Failed to download from URL"), Localization.lang("403 Forbidden: Access Denied. You do not have permission to access this resource. Please contact the administrator for assistance or try a different action.\nURL: %0 \n %1", urlDownload.getSource(), fetcherExceptionMessage)); + } else if (statusCode == 404) { + dialogService.showInformationDialogAndWait(Localization.lang("Failed to download from URL"), Localization.lang("404 Not Found Error: The requested resource could not be found. It seems that the file you are trying to download is not available or has been moved. Please verify the URL and try again. If you believe this is an error, please contact the administrator for further assistance.\nURL: %0 \n %1", urlDownload.getSource(), fetcherExceptionMessage)); + } + } else if (ex instanceof FetcherServerException serverException) { + statusCode = serverException.getStatusCode(); + dialogService.showInformationDialogAndWait(Localization.lang("Failed to download from URL"), + Localization.lang("Error downloading form URL. Cause is likely the server side. HTTP Error %0 \n %1 \nURL: %2 \nPlease try again later or contact the server administrator.", statusCode, fetcherExceptionMessage, urlDownload.getSource())); + } else { + dialogService.showErrorDialogAndWait(Localization.lang("Failed to download from URL"), Localization.lang("Error message: %0 \nURL: %1 \nPlease check the URL and try again.", fetcherExceptionMessage, urlDownload.getSource())); + } + }); + taskExecutor.execute(downloadTask); + } catch (MalformedURLException exception) { + dialogService.showErrorDialogAndWait(Localization.lang("Invalid URL"), exception); + } } - } - public boolean checkSSLHandshake(URLDownload urlDownload) { - try { + public boolean checkSSLHandshake(URLDownload urlDownload) { + try { urlDownload.canBeReached(); } catch (kong.unirest.UnirestException ex) { - if (ex.getCause() instanceof javax.net.ssl.SSLHandshakeException) { + if (ex.getCause() instanceof SSLHandshakeException) { if (dialogService.showConfirmationDialogAndWait(Localization.lang("Download file"), Localization.lang("Unable to find valid certification path to requested target(%0), download anyway?", urlDownload.getSource().toString()))) { @@ -513,7 +528,7 @@ public BackgroundTask prepareDownloadTask(Path targetDirectory, URLDownloa ExternalFileType externalFileType = suggestedType.orElse(StandardExternalFileType.PDF); String suggestedName = linkedFileHandler.getSuggestedFileName(externalFileType.getExtension()); - String fulltextDir = FileUtil.createDirNameFromPattern(databaseContext.getDatabase(), entry, preferences.getFilePreferences().getFileDirectoryPattern()); + String fulltextDir = FileUtil.createDirNameFromPattern(databaseContext.getDatabase(), entry, preferencesService.getFilePreferences().getFileDirectoryPattern()); suggestedName = FileNameUniqueness.getNonOverWritingFileName(targetDirectory.resolve(fulltextDir), suggestedName); return targetDirectory.resolve(fulltextDir).resolve(suggestedName); }) @@ -537,15 +552,15 @@ private Optional inferFileTypeFromMimeType(URLDownload urlDown if (mimeType != null) { LOGGER.debug("MIME Type suggested: " + mimeType); - return ExternalFileTypes.getExternalFileTypeByMimeType(mimeType, preferences.getFilePreferences()); + return ExternalFileTypes.getExternalFileTypeByMimeType(mimeType, preferencesService.getFilePreferences()); } else { return Optional.empty(); } } private Optional inferFileTypeFromURL(String url) { - return URLUtil.getSuffix(url, preferences.getFilePreferences()) - .flatMap(extension -> ExternalFileTypes.getExternalFileTypeByExt(extension, preferences.getFilePreferences())); + return URLUtil.getSuffix(url, preferencesService.getFilePreferences()) + .flatMap(extension -> ExternalFileTypes.getExternalFileTypeByExt(extension, preferencesService.getFilePreferences())); } public LinkedFile getFile() { @@ -557,17 +572,17 @@ public ValidationStatus fileExistsValidationStatus() { } public void parsePdfMetadataAndShowMergeDialog() { - linkedFile.findIn(databaseContext, preferences.getFilePreferences()).ifPresent(filePath -> { - MultiMergeEntriesView dialog = new MultiMergeEntriesView(preferences, taskExecutor); + linkedFile.findIn(databaseContext, preferencesService.getFilePreferences()).ifPresent(filePath -> { + MultiMergeEntriesView dialog = new MultiMergeEntriesView(preferencesService, taskExecutor); dialog.setTitle(Localization.lang("Merge PDF metadata")); dialog.addSource(Localization.lang("Entry"), entry); - dialog.addSource(Localization.lang("Verbatim"), wrapImporterToSupplier(new PdfVerbatimBibTextImporter(preferences.getImportFormatPreferences()), filePath)); - dialog.addSource(Localization.lang("Embedded"), wrapImporterToSupplier(new PdfEmbeddedBibFileImporter(preferences.getImportFormatPreferences()), filePath)); - if (preferences.getGrobidPreferences().isGrobidEnabled()) { - dialog.addSource("Grobid", wrapImporterToSupplier(new PdfGrobidImporter(preferences.getImportFormatPreferences()), filePath)); + dialog.addSource(Localization.lang("Verbatim"), wrapImporterToSupplier(new PdfVerbatimBibTextImporter(preferencesService.getImportFormatPreferences()), filePath)); + dialog.addSource(Localization.lang("Embedded"), wrapImporterToSupplier(new PdfEmbeddedBibFileImporter(preferencesService.getImportFormatPreferences()), filePath)); + if (preferencesService.getGrobidPreferences().isGrobidEnabled()) { + dialog.addSource("Grobid", wrapImporterToSupplier(new PdfGrobidImporter(preferencesService.getImportFormatPreferences()), filePath)); } - dialog.addSource(Localization.lang("XMP metadata"), wrapImporterToSupplier(new PdfXmpImporter(preferences.getXmpPreferences()), filePath)); - dialog.addSource(Localization.lang("Content"), wrapImporterToSupplier(new PdfContentImporter(preferences.getImportFormatPreferences()), filePath)); + dialog.addSource(Localization.lang("XMP metadata"), wrapImporterToSupplier(new PdfXmpImporter(preferencesService.getXmpPreferences()), filePath)); + dialog.addSource(Localization.lang("Content"), wrapImporterToSupplier(new PdfContentImporter(preferencesService.getImportFormatPreferences()), filePath)); dialogService.showCustomDialogAndWait(dialog).ifPresent(newEntry -> { databaseContext.getDatabase().removeEntry(entry); databaseContext.getDatabase().insertEntry(newEntry); diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.fxml b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.fxml index 5f61cfd109c..f73bdc72290 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.fxml +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.fxml @@ -23,8 +23,7 @@