diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 226bf3a6f53..84c48007971 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,13 +10,16 @@ updates: ignore: - dependency-name: com.microsoft.azure:applicationinsights-core versions: - - ">= 2.5.a, <= 2.6.1" # Blocked by https://github.com/microsoft/ApplicationInsights-Java/issues/1155 + - ">= 2.5.a" # Blocked by https://github.com/microsoft/ApplicationInsights-Java/issues/1155 - dependency-name: com.microsoft.azure:applicationinsights-logging-log4j2 versions: - - ">= 2.5.a, <= 2.6.1" # Blocked by https://github.com/microsoft/ApplicationInsights-Java/issues/1155 + - ">= 2.5.a" # Blocked by https://github.com/microsoft/ApplicationInsights-Java/issues/1155 - dependency-name: de.jensd:fontawesomefx-materialdesignfont versions: - "> 1.7.22-4" # Strange versioning format + - dependency-name: org.fxmisc.richtext:richtextfx + versions: + - "< 0.10.6" # Blocked by https://github.com/FXMisc/RichTextFX/issues/967, wait for new release - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index da59d10a7b1..b8f22170b1b 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -11,12 +11,12 @@ jobs: id: waitforstatuschecks uses: "WyriHaximus/github-action-wait-for-status@v1.2" with: - ignoreActions: automerge + ignoreActions: Automerge Dependabot checkInterval: 13 env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - name: Merge pull requests - uses: pascalgn/automerge-action@v0.9.0 + uses: pascalgn/automerge-action@v0.12.0 if: steps.waitforstatuschecks.outputs.status == 'success' env: MERGE_METHOD: "squash" diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 6663ede2174..4ff79211d53 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -41,7 +41,7 @@ jobs: name: Create installer and portable version for ${{ matrix.displayName }} steps: - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.5.0 + uses: styfle/cancel-workflow-action@0.6.0 with: access_token: ${{ github.token }} - name: Fetch all history for all tags and branches @@ -49,24 +49,22 @@ jobs: with: fetch-depth: 0 - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v0.9.4 + uses: gittools/actions/gitversion/setup@v0.9.5 with: versionSpec: "5.3.7" - name: Run GitVersion id: gitversion - uses: gittools/actions/gitversion/execute@v0.9.4 - - name: Set up Adotptopen JDK mac - uses: joschi/setup-jdk@v2 + uses: gittools/actions/gitversion/execute@v0.9.5 + - name: Set up JDK 15 for linux and mac + uses: actions/setup-java@v1 with: - # reason: https://bugs.openjdk.java.net/browse/JDK-8238184 java-version: 15 - release_type: ea - if: matrix.os == 'macos-latest' - - name: Set up JDK for linux and windows + if: matrix.os != 'windows-latest' + - name: Set up JDK 14 for windows uses: actions/setup-java@v1 with: java-version: 14 - if: matrix.os != 'macos-latest' + if: matrix.os == 'windows-latest' - name: Restore gradle cache uses: actions/cache@master with: @@ -109,8 +107,8 @@ jobs: if: matrix.os == 'macos-latest' && github.ref == 'refs/heads/master' shell: bash run: | - REQUEST_UUID=$(xcrun altool --verbose --notarize-app --primary-bundle-id "org.jabref" --username ${{ secrets.OSX_NOTARIZATION_APP_USERNAME }} --password ${{ secrets.OSX_NOTARIZATION_APP_PWD }} --asc-provider "W2PU6LW5U5" --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" -u ${{ secrets.OSX_NOTARIZATION_APP_USERNAME }} -p ${{ secrets.OSX_NOTARIZATION_APP_PWD }} | grep "Status: in progress" > /dev/null; do + 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 "W2PU6LW5U5" --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 @@ -118,8 +116,8 @@ jobs: spctl -vvv --assess --type exec build/distribution/JabRef.app 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=$(xcrun altool --verbose --notarize-app --primary-bundle-id "org.jabref" --username ${{ secrets.OSX_NOTARIZATION_APP_USERNAME }} --password ${{ secrets.OSX_NOTARIZATION_APP_PWD }} --asc-provider "W2PU6LW5U5" --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" -u ${{ secrets.OSX_NOTARIZATION_APP_USERNAME }} -p ${{ secrets.OSX_NOTARIZATION_APP_PWD }} | grep "Status: in progress" > /dev/null; do + 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 "W2PU6LW5U5" --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 @@ -163,12 +161,12 @@ jobs: - name: Fetch all history for all tags and branches run: git fetch --prune --unshallow - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v0.9.4 + uses: gittools/actions/gitversion/setup@v0.9.5 with: versionSpec: '5.2.x' - name: Run GitVersion id: gitversion - uses: gittools/actions/gitversion/execute@v0.9.4 + uses: gittools/actions/gitversion/execute@v0.9.5 - name: Get linux binaries uses: actions/download-artifact@master with: diff --git a/.github/workflows/refresh-journal-lists.yml b/.github/workflows/refresh-journal-lists.yml index bb39672115b..414d195771e 100644 --- a/.github/workflows/refresh-journal-lists.yml +++ b/.github/workflows/refresh-journal-lists.yml @@ -1,9 +1,6 @@ name: Refresh Journal Lists on: - schedule: - # run on 1st and 15th of each month - - cron: '2 20 1,15 * *' workflow_dispatch: # Allow to run manually diff --git a/.github/workflows/snap.yml b/.github/workflows/snap.yml index dad9960111a..bba78f1338d 100644 --- a/.github/workflows/snap.yml +++ b/.github/workflows/snap.yml @@ -31,6 +31,8 @@ jobs: - name: Build snap (1) Run build uses: snapcore/action-build@v1 id: snapcraft + with: + snapcraft-args: "--debug" - name: Build snap (2) Upload snap if: ${{ steps.checksecrets.outputs.secretspresent }} uses: snapcore/action-publish@v1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index eee63f64018..fe57e60d3d0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.5.0 + uses: styfle/cancel-workflow-action@0.6.0 with: access_token: ${{ github.token }} - name: Checkout source @@ -41,7 +41,12 @@ jobs: with: path: ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} - - name: Run checkstyle + - name: Run check style reporter + uses: nikitasavinov/checkstyle-action@master + with: + reporter: github-pr-check + checkstyle_config: 'config/checkstyle/checkstyle_reviewdog.xml' + - name: Run checkstyle gradle run: ./gradlew checkstyleMain checkstyleTest checkstyleJmh - name: Run markdown-lint uses: avto-dev/markdown-lint@v1 @@ -86,7 +91,7 @@ jobs: runs-on: ubuntu-latest services: postgres: - image: postgres:10.8 + image: postgres:13-alpine env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -235,7 +240,7 @@ jobs: with: # full checkout needed for authors generation fetch-depth: 0 - - name: "Check AUTHORS" + - name: "All authors of this PR listed in AUTHORS" id: authors_check shell: bash run: | @@ -245,19 +250,17 @@ jobs: set +o pipefail added=$(git diff HEAD --no-ext-diff --unified=0 -a --no-prefix | egrep "^\+[^+]" | sed "s/^\+//") if [ -z "$added" ]; then - echo "::set-output name=newauthor::false" echo "No authors added" exit 0 fi - message="The JabRef maintainers will add the following name to the [AUTHORS file](https://github.com/JabRef/jabref/blob/master/CONTRIBUTING.md#author-credits). In case you want to use a different one, please comment here and [adjust your name in your git configuration](https://docs.github.com/en/github/using-git/setting-your-username-in-git) for future commits.%0A%0A\`\`\`%0A${added//$'\n'/'%0A'}%0A\`\`\`%0A" - echo "::set-output name=message::$message" - echo "::set-output name=newauthor::true" - echo "New authors found" - - name: Comment PR - uses: unsplash/comment-on-pr@master - if: steps.authors_check.outputs.newauthor == 'true' - env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN_COMMENT_ON_PR }} - with: - msg: "${{ steps.authors_check.outputs.message }}" - check_for_duplicate_msg: true + echo "Authors found in this PR not listed in the AUTHORS file." + echo + echo "The JabRef maintainers will add the following name to the AUTHORS file" + echo + echo -e "$added" + echo + echo "In case you want to use a different one, please comment here and adjust your name in your git configuration for future commits" + echo + echo "Just adding yourself into the AUHTORS file does not help as it is overwritten by our script ./scripts/generate-authors." + echo "Read more on the AUTHORS file at found at https://github.com/JabRef/jabref/blob/master/CONTRIBUTING.md#author-credits" + exit 1 diff --git a/.mailmap b/.mailmap index a755a5d1806..56af5eb0831 100644 --- a/.mailmap +++ b/.mailmap @@ -47,6 +47,7 @@ Frédéric Darboux Frédéric Darboux Frédéric Darboux Frédéric Darboux +Frédéric Darboux Florian Straßer Gregor Herrmann Hakan Duran hakova @@ -211,3 +212,5 @@ Tianjian Lei <185049857@qq.com> Muhammad Arsalan Badar ZhouSky <11711923@mail.sustech.edu.cn> Vincent Gagnon +Tom Warnke +Eric Lau <919023+skeric@users.noreply.github.com> diff --git a/.markdownlint.yml b/.markdownlint.yml index 3ed5b253fa6..0ada157fae9 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -13,6 +13,9 @@ MD013: false MD026: punctuation: ".,;:!" +# not supported by gitbook +MD031: false + MD033: # we have tags with ids and superscript allowed_elements: ['a', 'sup'] diff --git a/AUTHORS b/AUTHORS index c57cec7736e..fadd29f4ed8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -29,6 +29,7 @@ Andreas Amann Andreas Buhr Andreas Rudert Andrew Collins +Andrew Kuncevich Andrew Levit André Schlichting Andrés Sánchez @@ -46,6 +47,7 @@ Baruch Oltman Behrouz Javanmardi Benedikt Tutzer Benjamin Köhler +Benjamin Schroth Berk Gureken Bernd Kalbfuss Bernhard Tempel @@ -112,6 +114,7 @@ Eiswindyeti Ellen Reitmayr Erdem Derebasoglu Erdem Derebaşoğlu +Eric Lau Erik Putrycz Ervin Kolenovic Escoul @@ -125,6 +128,7 @@ Fancy Zhang Fedor Bezrukov Felix Berger Felix Langner +Felix Luthman Felix Wilke Fernando Santagata ffffatgoose @@ -161,12 +165,14 @@ Igor Chernyavsky Igor Steinmacher Illes Solt Ingvar Jackal +Isaac Roles Jackson Ryan Jan Frederik Maas Jan Kubovy Jan Schäfer Janosch Kutscherauer Jason Pickering +Jasper Vandemalle Javier Muñoz Ferrara jcampbell11245 Jeff Kerr @@ -177,11 +183,13 @@ Jens Döcke joeyzgraggen Johannes Hupe Johannes Manner +Johannes Theiner John David John Relph John Zedlewski Jonas Lähnemann Jonas Moosmann +Jonatan Asketorp Jonathan Powell Jong-Ho Shinn Jorge Tornero @@ -206,6 +214,7 @@ Kelly Click Koji Yokota KOLANICH Kolja Brix +Kristoffer Gunnarsson Krunoslav Zubrinic Krzysztof A. Kościuszkiewicz Kyle Johnson @@ -224,19 +233,20 @@ Lucas Beretti Luciana de Melo e Abud Lugduni Desrosiers Luis Romero -m-mauersberger Mairieli Wessel Malik Atalla Malte Deiseroth Manuel Siebeneicher Manuel Wtfjoke Marcel Luethi +Marco Aurélio Graciotto Silva Marco Konersmann Mariana Prudencio Marius Kleiner Mark Schenk Martin Kähmer Martin Stolle +Martin W. Kirst Martina Catizone Mathias Walter Matthias Geiger @@ -251,6 +261,7 @@ MhhhxX Michael Beckmann Michael Falkenthal Michael Lass +Michael Mauersberger Michael McCann Michael Schwarmi Michael Spiegel @@ -266,6 +277,7 @@ Morgan Lovato Moritz Ringler Morten Alver ms111ds +muachilin Muhammad Arsalan Badar Mélanie Tremblay Nadeem Mahmood @@ -275,10 +287,12 @@ Nick Mancuso Nick S. Weatherley Nico Schlömer Nicolas Pavillon +Niffler Nikita Borovikov Niklas Schmitt nikmilpv NikodemKch +Nikolaus Koopmann Nils Streijffert Niv Ierushalmi Nivedha Sunderraj @@ -371,10 +385,13 @@ Tianjian Lei Tim Kilian Tim van Rossum Tim Würtele +Timucin Merdin Tobias Boceck Tobias Bouschen Tobias Denkinger Tobias Diez +Tom Warnke +Tommy Samuelsson Tomás Morales de Luna Tony K Toralf Senger diff --git a/CHANGELOG.md b/CHANGELOG.md index 7444c09ad15..034d99986a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,24 +15,66 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We added a query parser and mapping layer to enable conversion of queries formulated in simplified lucene syntax by the user into api queries. [#6799](https://github.com/JabRef/jabref/pull/6799) - We added some basic functionality to customise the look of JabRef by importing a css theme file. [#5790](https://github.com/JabRef/jabref/issues/5790) - We added connection check function in network preference setting [#6560](https://github.com/JabRef/jabref/issues/6560) +- We added support for exporting to YAML. [#6974](https://github.com/JabRef/jabref/issues/6974) +- We added a DOI format and organization check to detect [American Physical Society](https://journals.aps.org/) journals to copy the article ID +to the page field for cases where the page numbers are missing. [#7019](https://github.com/JabRef/jabref/issues/7019) +- We added a new fetcher to enable users to search jstor.org [#6627](https://github.com/JabRef/jabref/issues/6627) +- We added an error message in the New Entry dialog that is shown in case the fetcher did not find anything . [#7000](https://github.com/JabRef/jabref/issues/7000) +- We added a new formatter to output shorthand month format. [#6579](https://github.com/JabRef/jabref/issues/6579) +- We added support for the new Microsoft Edge browser in all platforms. [#7056](https://github.com/JabRef/jabref/pull/7056) +- We reintroduced emacs/bash-like keybindings. [#6017](https://github.com/JabRef/jabref/issues/6017) ### Changed +- We changed the default preferences for OpenOffice/LibreOffice integration to automatically sync the bibliography when +inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://github.com/JabRef/jabref/issues/6957) - We restructured the 'File' tab and extracted some parts into the 'Linked files' tab [#6779](https://github.com/JabRef/jabref/pull/6779) - JabRef now offers journal lists from . JabRef the lists which use a dot inside the abbreviations. [#5749](https://github.com/JabRef/jabref/pull/5749) - We removed two useless preferences in the groups preferences dialog. [#6836](https://github.com/JabRef/jabref/pull/6836) - Synchronization of SpecialFields to keywords is now disabled by default. [#6621](https://github.com/JabRef/jabref/issues/6621) -- JabRef no longer opens the entry editor with the first entry on startup [6855](https://github.com/JabRef/jabref/issues/6855) +- JabRef no longer opens the entry editor with the first entry on startup [#6855](https://github.com/JabRef/jabref/issues/6855) +- We completed the rebranding of `bibtexkey` as `citationkey` which was started in JabRef 5.1. +- JabRef no longer opens the entry editor with the first entry on startup [#6855](https://github.com/JabRef/jabref/issues/6855) +- Fetch by ID: (long) "SAO/NASA Astrophysics Data System" replaced by (short) "SAO/NASA ADS" [#6876](https://github.com/JabRef/jabref/pull/6876) +- We changed the title of the window "Manage field names and content" to have the same title as the corresponding menu item [#6895](https://github.com/JabRef/jabref/pull/6895) +- We renamed the menus "View -> Previous citation style" and "View -> Next citation style" into "View -> Previous preview style" and "View -> Next preview style" and renamed the "Preview" style to "Customized preview style". [#6899](https://github.com/JabRef/jabref/pull/6899) +- We changed the default preference option "Search and store files relative to library file location" to on, as this seems to be a more intuitive behaviour. [#6863](https://github.com/JabRef/jabref/issues/6863) +- We changed the title of the window "Manage field names and content": to have the same title as the corresponding menu item [#6895](https://github.com/JabRef/jabref/pull/6895) +- We improved the detection of "short" DOIs [6880](https://github.com/JabRef/jabref/issues/6880) +- We improved the duplicate detection when identifiers like DOI or arxiv are semantiaclly the same, but just syntactically differ (e.g. with or without http(s):// prefix). [#6707](https://github.com/JabRef/jabref/issues/6707) +- We changed in the group interface "Generate groups from keywords in a BibTeX field" by "Generate groups from keywords in the following field". [#6983](https://github.com/JabRef/jabref/issues/6983) +- We changed the name of a group type from "Searching for keywords" to "Searching for a keyword". [6995](https://github.com/JabRef/jabref/pull/6995) +- We changed the way JabRef displays the title of a tab and of the window. [4161](https://github.com/JabRef/jabref/issues/4161) +- We changed connect timeouts for server requests to 30 seconds in general and 5 seconds for GROBID server (special) and improved user notifications on connection issues. [7026](https://github.com/JabRef/jabref/pull/7026) +- We changed the way linked files are opened on Linux to use the native openFile method, compatible with confined packages. [7037](https://github.com/JabRef/jabref/pull/7037) ### Fixed -- We fixed an issue changing the icon link_variation_off that is not meaningful. [#6834][https://github.com/JabRef/jabref/issues/6834] +- We fixed an issue changing the icon link_variation_off that is not meaningful. [#6834](https://github.com/JabRef/jabref/issues/6834) - We fixed an issue where the `.sav` file was not deleted upon exiting JabRef. [#6109](https://github.com/JabRef/jabref/issues/6109) - We fixed a linked identifier icon inconsistency. [#6705](https://github.com/JabRef/jabref/issues/6705) - We fixed the wrong behavior that font size changes are not reflected in dialogs. [#6039](https://github.com/JabRef/jabref/issues/6039) +- We fixed the failure to Copy citation key and link. [#5835](https://github.com/JabRef/jabref/issues/5835) +- We fixed an issue where the sort order of the entry table was reset after a restart of JabRef. [#6898](https://github.com/JabRef/jabref/pull/6898) +- We fixed an issue where no longer a warning was displayed when inserting references into LibreOffice with an invalid "ReferenceParagraphFormat". [#6907](https://github.com/JabRef/jabref/pull/60907). +- We fixed an issue where a selected field was not removed after the first click in the custom entry types dialog. [#6934](https://github.com/JabRef/jabref/issues/6934) +- We fixed an issue where a remove icon was shown for standard entry types in the custom entry types dialog. [#6906](https://github.com/JabRef/jabref/issues/6906) +- We fixed an issue where it was impossible to connect to OpenOffice/LibreOffice on Mac OSX. [#6970](https://github.com/JabRef/jabref/pull/6970) +- We fixed an issue with the python script used by browser plugins that failed to locate JabRef if not installed in its default location. [#6963](https://github.com/JabRef/jabref/pull/6963/files) +- We fixed an issue where spaces and newlines in an isbn would generate an exception. [#6456](https://github.com/JabRef/jabref/issues/6456) +- We fixed an issue where identity column header had incorrect foreground color in the Dark theme. [#6796](https://github.com/JabRef/jabref/issues/6796) +- We fixed an issue where the RIS exporter added extra blank lines.[#7007](https://github.com/JabRef/jabref/pull/7007/files) +- We fixed an issue where clicking on Collapse All button in the Search for Unlinked Local Files expanded the directory structure erroneously [#6848](https://github.com/JabRef/jabref/issues/6848) +- We fixed an issue, when pulling changes from shared database via shortcut caused creation of a new tech report [6867](https://github.com/JabRef/jabref/issues/6867) +- We fixed an issue where the JabRef GUI does not highlight the "All entries" group on start-up [#6691](https://github.com/JabRef/jabref/issues/6691) +- We fixed an issue where a custom dark theme was not applied to the entry preview tab [7068](https://github.com/JabRef/jabref/issues/7068) +- We fixed an issue where modifications to the Custom preview layout in the preferences were not saved [#6447](https://github.com/JabRef/jabref/issues/6447) +- We fixed an issue where errors from imports were not shown to the user [#7084](https://github.com/JabRef/jabref/pull/7084) +- We fixed an issue where the EndNote XML Import would fail on empty keywords tags [forum#2387](https://discourse.jabref.org/t/importing-in-unknown-format-fails-to-import-xml-library-from-bookends-export/2387) ### Removed +- We removed the menu entry "Manage external file types" because it's already in 'Preferences' dialog [#6991](https://github.com/JabRef/jabref/issues/6991) ## [5.1] – 2020-08-30 diff --git a/build.gradle b/build.gradle index 1595e669736..b9cd51a3c93 100644 --- a/build.gradle +++ b/build.gradle @@ -12,15 +12,15 @@ import org.jabref.build.xjc.XjcTask plugins { id 'application' id "com.simonharrer.modernizer" version '2.1.0-1' - id 'me.champeau.gradle.jmh' version '0.5.1' - id 'com.github.ben-manes.versions' version '0.29.0' + id 'me.champeau.gradle.jmh' version '0.5.2' + id 'com.github.ben-manes.versions' version '0.36.0' id 'org.javamodularity.moduleplugin' version '1.7.0' id 'org.openjfx.javafxplugin' version '0.0.9' - id 'org.beryx.jlink' version '2.21.3' + id 'org.beryx.jlink' version '2.22.1' // nicer test outputs during running and completion // Homepage: https://github.com/radarsh/gradle-test-logger-plugin - id 'com.adarshr.test-logger' version '2.1.0' + id 'com.adarshr.test-logger' version '2.1.1' } gradle.startParameter.showStacktrace = org.gradle.api.logging.configuration.ShowStacktrace.ALWAYS @@ -97,7 +97,7 @@ configurations { } javafx { - version = "14" + version = "15" modules = [ 'javafx.controls', 'javafx.fxml', 'javafx.web', 'javafx.swing' ] } @@ -105,9 +105,9 @@ dependencies { // Include all jar-files in the 'lib' folder as dependencies implementation fileTree(dir: 'lib', includes: ['*.jar']) - implementation 'org.apache.pdfbox:pdfbox:2.0.20' - implementation 'org.apache.pdfbox:fontbox:2.0.20' - implementation 'org.apache.pdfbox:xmpbox:2.0.20' + implementation 'org.apache.pdfbox:pdfbox:2.0.21' + implementation 'org.apache.pdfbox:fontbox:2.0.21' + implementation 'org.apache.pdfbox:xmpbox:2.0.21' implementation group: 'org.apache.commons', name: 'commons-csv', version: '1.8' implementation 'com.h2database:h2-mvstore:1.4.200' @@ -115,7 +115,7 @@ dependencies { implementation group: 'org.apache.tika', name: 'tika-core', version: '1.24.1' // required for reading write-protected PDFs - see https://github.com/JabRef/jabref/pull/942#issuecomment-209252635 - implementation 'org.bouncycastle:bcprov-jdk15on:1.66' + implementation 'org.bouncycastle:bcprov-jdk15on:1.67' implementation 'commons-cli:commons-cli:1.4' @@ -128,7 +128,7 @@ dependencies { libreoffice 'org.libreoffice:ridl:6.4.3' libreoffice 'org.libreoffice:unoil:6.4.3' - implementation 'io.github.java-diff-utils:java-diff-utils:4.7' + implementation 'io.github.java-diff-utils:java-diff-utils:4.9' implementation 'info.debatty:java-string-similarity:2.0.0' antlr3 'org.antlr:antlr:3.5.2' @@ -137,20 +137,20 @@ dependencies { antlr4 'org.antlr:antlr4:4.8-1' implementation 'org.antlr:antlr4-runtime:4.8-1' - implementation (group: 'org.apache.lucene', name: 'lucene-queryparser', version: '8.6.1') { + implementation (group: 'org.apache.lucene', name: 'lucene-queryparser', version: '8.7.0') { exclude group: 'org.apache.lucene', module: 'lucene-sandbox' } - implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.6.2' + implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.7.0' - implementation 'org.postgresql:postgresql:42.2.16' + implementation 'org.postgresql:postgresql:42.2.18' implementation ('com.oracle.ojdbc:ojdbc10:19.3.0.0') { // causing module issues exclude module: 'oraclepki' } - implementation ('com.google.guava:guava:29.0-jre') { + implementation ('com.google.guava:guava:30.0-jre') { // TODO: Remove this as soon as https://github.com/google/guava/issues/2960 is fixed exclude module: "jsr305" } @@ -163,14 +163,14 @@ dependencies { implementation 'de.saxsys:mvvmfx-validation:1.9.0-SNAPSHOT' implementation 'de.saxsys:mvvmfx:1.8.0' implementation 'com.tobiasdiez:easybind:2.1.0' - implementation 'org.fxmisc.flowless:flowless:0.6.1' - implementation 'org.fxmisc.richtext:richtextfx:0.10.5' + implementation 'org.fxmisc.flowless:flowless:0.6.2' + implementation 'org.fxmisc.richtext:richtextfx:0.10.4' implementation group: 'org.glassfish.hk2.external', name: 'jakarta.inject', version: '2.6.1' implementation 'com.jfoenix:jfoenix:9.0.10' - implementation 'org.controlsfx:controlsfx:11.0.2' + implementation 'org.controlsfx:controlsfx:11.0.3' implementation 'org.jsoup:jsoup:1.13.1' - implementation 'com.konghq:unirest-java:3.10.00' + implementation 'com.konghq:unirest-java:3.11.05' implementation 'org.slf4j:slf4j-api:2.0.0-alpha1' implementation group: 'org.apache.logging.log4j', name: 'log4j-jcl', version: '3.0.0-SNAPSHOT' @@ -207,24 +207,24 @@ dependencies { implementation 'com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:0.62.2' implementation 'com.vladsch.flexmark:flexmark-ext-gfm-tasklist:0.62.2' - testImplementation 'io.github.classgraph:classgraph:4.8.89' - testImplementation 'org.junit.jupiter:junit-jupiter:5.6.2' - testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.6.2' - testImplementation 'org.junit.platform:junit-platform-launcher:1.6.2' + testImplementation 'io.github.classgraph:classgraph:4.8.90' + testImplementation 'org.junit.jupiter:junit-jupiter:5.7.0' + testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.7.0' + testImplementation 'org.junit.platform:junit-platform-launcher:1.7.0' - testImplementation 'net.bytebuddy:byte-buddy-parent:1.10.14' + testImplementation 'net.bytebuddy:byte-buddy-parent:1.10.18' testRuntime group: 'org.apache.logging.log4j', name: 'log4j-core', version: '3.0.0-SNAPSHOT' testRuntime group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '3.0.0-SNAPSHOT' - testImplementation 'org.mockito:mockito-core:3.5.7' - testImplementation 'org.xmlunit:xmlunit-core:2.7.0' - testImplementation 'org.xmlunit:xmlunit-matchers:2.7.0' + testImplementation 'org.mockito:mockito-core:3.6.0' + testImplementation 'org.xmlunit:xmlunit-core:2.8.0' + testImplementation 'org.xmlunit:xmlunit-matchers:2.8.0' testRuntime 'com.tngtech.archunit:archunit-junit5-engine:0.14.1' testImplementation 'com.tngtech.archunit:archunit-junit5-api:0.14.1' testImplementation "org.testfx:testfx-core:4.0.17-alpha-SNAPSHOT" testImplementation "org.testfx:testfx-junit5:4.0.17-alpha-SNAPSHOT" testImplementation "org.hamcrest:hamcrest-library:2.2" - checkstyle 'com.puppycrawl.tools:checkstyle:8.36' + checkstyle 'com.puppycrawl.tools:checkstyle:8.37' xjc group: 'org.glassfish.jaxb', name: 'jaxb-xjc', version: '2.3.3' jython 'org.python:jython-standalone:2.7.2' } @@ -672,6 +672,7 @@ jlink { '--win-dir-chooser', '--win-shortcut', '--win-menu', + '--win-menu-group', "JabRef", '--temp', "$buildDir/installer", '--resource-dir', "${projectDir}/buildres/windows", '--file-associations', "${projectDir}/buildres/windows/bibtexAssociations.properties" @@ -681,6 +682,7 @@ jlink { if (OperatingSystem.current().isLinux()) { imageOptions = [ '--icon', "${projectDir}/src/main/resources/icons/JabRef-icon-64.png", + '--app-version', "${project.version}", ] installerOptions = [ '--verbose', @@ -720,7 +722,7 @@ if (OperatingSystem.current().isWindows()) { tasks.jpackageImage.doLast { copy { from("${projectDir}/buildres/windows") { - include "jabref.json", "jabref-chrome.json", "JabRefHost.bat", "JabRefHost.ps1" + include "jabref-firefox.json", "jabref-chrome.json", "JabRefHost.bat", "JabRefHost.ps1" } into "$buildDir/distribution/JabRef" } diff --git a/buildres/linux/jabrefHost.py b/buildres/linux/jabrefHost.py index e1343e23207..ef3fd603b86 100755 --- a/buildres/linux/jabrefHost.py +++ b/buildres/linux/jabrefHost.py @@ -6,6 +6,7 @@ import json import logging +import os import platform import shlex import shutil @@ -16,11 +17,16 @@ # We assume that this python script is located in "jabref/lib" while the executable is "jabref/bin/JabRef" script_dir = Path(__file__).resolve().parent.parent -JABREF_PATH = script_dir / "bin/JabRef" -if not JABREF_PATH.exists(): - JABREF_PATH = shutil.which("jabref") - -if not JABREF_PATH.exists(): +relpath_path = script_dir / "bin/JabRef" +lowercase_path = shutil.which("jabref") +uppercase_path = shutil.which("JabRef") +if relpath_path.exists(): + JABREF_PATH = relpath_path +elif lowercase_path is not None and os.path.exists(lowercase_path): + JABREF_PATH = Path(lowercase_path) +elif uppercase_path is not None and os.path.exists(uppercase_path): + JABREF_PATH = Path(uppercase_path) +else: logging.error("Could not determine JABREF_PATH") sys.exit(-1) diff --git a/buildres/linux/native-messaging-host/chromium/org.jabref.jabref.json b/buildres/linux/native-messaging-host/chromium/org.jabref.jabref.json index 6283016eafe..902c8d50d14 100644 --- a/buildres/linux/native-messaging-host/chromium/org.jabref.jabref.json +++ b/buildres/linux/native-messaging-host/chromium/org.jabref.jabref.json @@ -4,6 +4,7 @@ "path": "/opt/jabref/lib/jabrefHost.py", "type": "stdio", "allowed_origins": [ - "chrome-extension://bifehkofibaamoeaopjglfkddgkijdlh/" + "chrome-extension://bifehkofibaamoeaopjglfkddgkijdlh/", + "chrome-extension://pgkajmkfgbehiomipedjhoddkejohfna/" ] } diff --git a/buildres/linux/postinst b/buildres/linux/postinst index a26e18b43cc..b24ded8eeb1 100644 --- a/buildres/linux/postinst +++ b/buildres/linux/postinst @@ -23,6 +23,7 @@ case "$1" in install -D -m0755 /opt/jabref/lib/native-messaging-host/firefox/org.jabref.jabref.json /usr/lib/mozilla/native-messaging-hosts/org.jabref.jabref.json install -D -m0755 /opt/jabref/lib/native-messaging-host/chromium/org.jabref.jabref.json /etc/chromium/native-messaging-hosts/org.jabref.jabref.json install -D -m0755 /opt/jabref/lib/native-messaging-host/chromium/org.jabref.jabref.json /etc/opt/chrome/native-messaging-hosts/org.jabref.jabref.json + install -D -m0755 /opt/jabref/lib/native-messaging-host/chromium/org.jabref.jabref.json /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json # Trigger an auto-install of the browser addon for chrome/chromium browsers install -D -m0644 /opt/jabref/lib/native-messaging-host/chromium/bifehkofibaamoeaopjglfkddgkijdlh.json /opt/google/chrome/extensions/bifehkofibaamoeaopjglfkddgkijdlh.json install -D -m0644 /opt/jabref/lib/native-messaging-host/chromium/bifehkofibaamoeaopjglfkddgkijdlh.json /usr/share/google-chrome/extensions/bifehkofibaamoeaopjglfkddgkijdlh.json diff --git a/buildres/linux/postrm b/buildres/linux/postrm index 5708fd243d9..abcd727ac76 100644 --- a/buildres/linux/postrm +++ b/buildres/linux/postrm @@ -22,7 +22,8 @@ case "$1" in # Remove the native-messaging hosts script only if relative to the deb package for NATIVE_MESSAGING_JSON in "/usr/lib/mozilla/native-messaging-hosts/org.jabref.jabref.json"\ "/etc/chromium/native-messaging-hosts/org.jabref.jabref.json"\ - "/etc/opt/chrome/native-messaging-hosts/org.jabref.jabref.json"; do + "/etc/opt/chrome/native-messaging-hosts/org.jabref.jabref.json"\ + "/etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json"; do if [ -e $NATIVE_MESSAGING_JSON ] && grep --quiet '"path": "/opt' $NATIVE_MESSAGING_JSON; then rm $NATIVE_MESSAGING_JSON fi diff --git a/buildres/mac/native-messaging-host/chromium/org.jabref.jabref.json b/buildres/mac/native-messaging-host/chromium/org.jabref.jabref.json index 84ce13113d7..e07d6237487 100644 --- a/buildres/mac/native-messaging-host/chromium/org.jabref.jabref.json +++ b/buildres/mac/native-messaging-host/chromium/org.jabref.jabref.json @@ -4,6 +4,7 @@ "path": "/Applications/JabRef.app/Contents/Resources/jabrefHost.py", "type": "stdio", "allowed_origins": [ - "chrome-extension://bifehkofibaamoeaopjglfkddgkijdlh/" + "chrome-extension://bifehkofibaamoeaopjglfkddgkijdlh/", + "chrome-extension://pgkajmkfgbehiomipedjhoddkejohfna/" ] } diff --git a/buildres/mac/postinstall b/buildres/mac/postinstall index 235feb0ca5e..7fd28171458 100755 --- a/buildres/mac/postinstall +++ b/buildres/mac/postinstall @@ -14,5 +14,7 @@ install -d /Library/Application\ Support/Chromium/NativeMessagingHosts/ install -m0755 /Applications/JabRef.app/Contents/Resources/native-messaging-host/chromium/org.jabref.jabref.json /Library/Application\ Support/Chromium/NativeMessagingHosts/org.jabref.jabref.json install -d /Library/Google/Chrome/NativeMessagingHosts/ install -m0755 /Applications/JabRef.app/Contents/Resources/native-messaging-host/chromium/org.jabref.jabref.json /Library/Google/Chrome/NativeMessagingHosts/org.jabref.jabref.json +install -d /Library/Microsoft/Edge/NativeMessagingHosts/ +install -m0755 /Applications/JabRef.app/Contents/Resources/native-messaging-host/chromium/org.jabref.jabref.json /Library/Microsoft/Edge/NativeMessagingHosts/org.jabref.jabref.json exit 0 diff --git a/buildres/windows/JabRef-post-image.wsf b/buildres/windows/JabRef-post-image.wsf index a614c82460f..afdf92ef0ad 100644 --- a/buildres/windows/JabRef-post-image.wsf +++ b/buildres/windows/JabRef-post-image.wsf @@ -21,7 +21,7 @@ wxsFile.Close(); // Add registry values for JabRef Browser Extension - contents = contents.replace("", ""); + contents = contents.replace("", ""); // Specify banner contents = contents.replace("", ""); diff --git a/buildres/windows/jabref-chrome.json b/buildres/windows/jabref-chrome.json index 39dd8d35455..a8d3020cc2d 100644 --- a/buildres/windows/jabref-chrome.json +++ b/buildres/windows/jabref-chrome.json @@ -4,6 +4,7 @@ "path": "JabRefHost.bat", "type": "stdio", "allowed_origins": [ - "chrome-extension://bifehkofibaamoeaopjglfkddgkijdlh/" + "chrome-extension://bifehkofibaamoeaopjglfkddgkijdlh/", + "chrome-extension://pgkajmkfgbehiomipedjhoddkejohfna/" ] } diff --git a/buildres/windows/jabref.json b/buildres/windows/jabref-firefox.json similarity index 100% rename from buildres/windows/jabref.json rename to buildres/windows/jabref-firefox.json diff --git a/config/README.md b/config/README.md index 4088be4eff3..cb7d0275d0c 100644 --- a/config/README.md +++ b/config/README.md @@ -1,9 +1,9 @@ # IntelliJ IDEA Code Style Configuration IntelliJ IDEA comes with a powerful code formatter that helps you to keep the formatting consistent with the style JabRef uses. -Style-checks are done for each pull request and installing this cody style configuration helps you to ensure that this test passes. To install it, you need to do the following steps: +Style-checks are done for each pull request and installing this code style configuration helps you to ensure that this test passes. To install it, you need to do the following steps: -1. Goto *Preferences* or press Ctrl + Alt + S (Cmd + , on Mac OS X) +1. Go to *Preferences* or press Ctrl + Alt + S (Cmd + , on Mac OS X) 2. Go to "Editor > Code Style" 3. Click the gear (right of "Scheme: ...") 4. Click "Import Scheme >" diff --git a/config/checkstyle/checkstyle_reviewdog.xml b/config/checkstyle/checkstyle_reviewdog.xml new file mode 100644 index 00000000000..23d41f035e1 --- /dev/null +++ b/config/checkstyle/checkstyle_reviewdog.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/.gitbook/assets/intellij-choose-jdk-adoptopenjdk-on-windows-project-settings.png b/docs/.gitbook/assets/intellij-choose-jdk-adoptopenjdk-on-windows-project-settings.png new file mode 100644 index 00000000000..76207c5d3d6 Binary files /dev/null and b/docs/.gitbook/assets/intellij-choose-jdk-adoptopenjdk-on-windows-project-settings.png differ diff --git a/docs/.gitbook/assets/intellij-choose-jdk15-project-default.png b/docs/.gitbook/assets/intellij-choose-jdk15-project-default.png new file mode 100644 index 00000000000..ec031773c25 Binary files /dev/null and b/docs/.gitbook/assets/intellij-choose-jdk15-project-default.png differ diff --git a/docs/README.md b/docs/README.md index 096a6147596..5dc3b67c95e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,7 +25,7 @@ We are very happy that JabRef is part of [Software Engineering](https://en.wikip The package `org.jabref.cli` is responsible for handling the command line options. -During development, one can configure IntelliJ to pass command line paramters: +During development, one can configure IntelliJ to pass command line parameters: ![IntelliJ-run-configuration](.gitbook/assets/intellij-run-configuration-command-line%20%282%29.png) diff --git a/docs/adr.md b/docs/adr.md index b7d31167a4c..988a2ba7593 100644 --- a/docs/adr.md +++ b/docs/adr.md @@ -1,8 +1,27 @@ # Architectural Decisions -This directory contains the architectural decisions for JabRef. +Architectural decisions for JabRef: +* [ADR-0000](https://github.com/JabRef/jabref/tree/master/docs/adr/0000-use-markdown-architectural-decision-records.md) - Use Markdown Architectural Decision Records +* [ADR-0001](https://github.com/JabRef/jabref/tree/master/docs/adr/0001-use-crowdin-for-translations.md) - Use Crowdin for translations +* [ADR-0002](https://github.com/JabRef/jabref/tree/master/docs/adr/0002-use-slf4j-for-logging.md) - Use slf4j together with log4j2 for logging +* [ADR-0003](https://github.com/JabRef/jabref/tree/master/docs/adr/0003-use-gradle-as-build-tool.md) - Use Gradle as build tool +* [ADR-0004](https://github.com/JabRef/jabref/tree/master/docs/adr/0004-use-mariadb-connector.md) - Use MariaDB Connector +* [ADR-0005](https://github.com/JabRef/jabref/tree/master/docs/adr/0005-fully-support-utf8-only-for-latex-files.md) - Fully Support UTF-8 Only For LaTeX Files +* [ADR-0006](https://github.com/JabRef/jabref/tree/master/docs/adr/0006-only-translated-strings-in-language-file.md) - Only translated strings in language file +* [ADR-0007](https://github.com/JabRef/jabref/tree/master/docs/adr/0007-human-readable-changelog.md) - Provide a human-readable changelog +* [ADR-0008](https://github.com/JabRef/jabref/tree/master/docs/adr/0008-use-public-final-instead-of-getters.md) - Use public final instead of getters to offer access to immutable variables +* [ADR-0009](https://github.com/JabRef/jabref/tree/master/docs/adr/0009-use-plain-junit5-for-testing.md) - Use Plain JUnit5 for advanced test assertions +* [ADR-0010](https://github.com/JabRef/jabref/tree/master/docs/adr/0010-use-h2-as-internal-database.md) - Use H2 as Internal SQL Database +* [ADR-0011](https://github.com/JabRef/jabref/tree/master/docs/adr/0011-test-external-links-in-documentation.md) - Test external links in documentation +* [ADR-0012](https://github.com/JabRef/jabref/tree/master/docs/adr/0012-handle-different-bibEntry-formats-of-fetchers.md) - Handle different bibentry formats of fetchers by adding a layer +* [ADR-0013](https://github.com/JabRef/jabref/tree/master/docs/adr/0013-add-native-support-biblatex-software.md) - Add Native Support for BibLatex-Software +fix checkstyle in adr +* [ADR-0014](https://github.com/JabRef/jabref/tree/master/docs/adr/0014-separate-URL-creation-to-enable-proper-logging.md) - Separate URL creation to enable proper logging +* [ADR-0015](https://github.com/JabRef/jabref/tree/master/docs/adr/0015-support-an-abstract-query-syntax-for-query-conversion.md) - Query syntax design +* [ADR-0016](https://github.com/JabRef/jabref/tree/master/docs/adr/0016-mutable-preferences-objects.md) - Mutable preferences objects +* [ADR-0017](https://github.com/JabRef/jabref/tree/master/docs/adr/0017-allow-model-access-logic.md) - Allow org.jabref.model to access org.jabref.logic -For new ADRs, please use [template.md](https://github.com/JabRef/jabref/tree/98e9fc3ddc7b59ca35101c7f37b183ca2e69d8a0/docs/adr/template.md) as basis. More information on the used format is available at [https://adr.github.io/madr/](https://adr.github.io/madr/). General information about architectural decision records is available at [https://adr.github.io/](https://adr.github.io/). +For new ADRs, please use [template.md](https://github.com/JabRef/jabref/tree/master/docs/adr/template.md) as basis. More information on the used format is available at [https://adr.github.io/madr/](https://adr.github.io/madr/). General information about architectural decision records is available at [https://adr.github.io/](https://adr.github.io/). Then add them to the above list. diff --git a/docs/adr/0003-use-gradle-as-build-tool.md b/docs/adr/0003-use-gradle-as-build-tool.md index 5dac1018032..72dca30407c 100644 --- a/docs/adr/0003-use-gradle-as-build-tool.md +++ b/docs/adr/0003-use-gradle-as-build-tool.md @@ -19,9 +19,9 @@ Chosen option: "Gradle", because it is lean and fits our development style. ### Maven * Good, because [there is a plugin for almost everything](https://www.slant.co/versus/2107/11592/~apache-maven_vs_gradle) -* Good, because [it has good integration with third party tools](http://pages.zeroturnaround.com/rs/zeroturnaround/images/java-build-tools-part-2.pdf) -* Good, because [it has robust performance](http://pages.zeroturnaround.com/rs/zeroturnaround/images/java-build-tools-part-2.pdf) -* Good, because [it has a high popularity](http://pages.zeroturnaround.com/rs/zeroturnaround/images/java-build-tools-part-2.pdf) +* Good, because [it has good integration with third party tools](https://fdocuments.us/reader/full/java-build-tools-part-2) +* Good, because [it has robust performance](https://fdocuments.us/reader/full/java-build-tools-part-2) +* Good, because [it has a high popularity](https://fdocuments.us/reader/full/java-build-tools-part-2) * Good, [if one favors declarative over imperative](https://www.slant.co/versus/2107/11592/~apache-maven_vs_gradle) * Bad, because [getting a dependency list is not straight forward](https://stackoverflow.com/q/1677473/873282) * Bad, because [it based on a fixed and linear model of phases](https://dzone.com/articles/gradle-vs-maven) @@ -34,13 +34,13 @@ Chosen option: "Gradle", because it is lean and fits our development style. * Good, because [its build scripts are short](https://technologyconversations.com/2014/06/18/build-tools/) * Good, because [it follows the convention over configuration approach](https://www.safaribooksonline.com/library/view/building-and-testing/9781449306816/ch04.html) * Good, because [it offers a graph-based task dependencies](https://dzone.com/articles/gradle-vs-maven) -* Good, because [it is easy to customize](http://pages.zeroturnaround.com/rs/zeroturnaround/images/java-build-tools-part-2.pdf) +* Good, because [it is easy to customize](https://fdocuments.us/reader/full/java-build-tools-part-2) * Good, because [it offers custom dependency scopes](https://gradle.org/maven-vs-gradle/) * Good, because [it has good community support](https://linuxhint.com/ant-vs-maven-vs-gradle/) * Good, because [its performance can be 100 times more than maven's performance](https://gradle.org/gradle-vs-maven-performance/). * Bad, because [not that many plugins are available/maintained yet](https://phauer.com/2018/moving-back-from-gradle-to-maven/) -* Bad, because [it lacks a wide variety of application server integrations](http://pages.zeroturnaround.com/rs/zeroturnaround/images/java-build-tools-part-2.pdf) -* Bad, because [it has a medium popularity](http://pages.zeroturnaround.com/rs/zeroturnaround/images/java-build-tools-part-2.pdf) +* Bad, because [it lacks a wide variety of application server integrations](https://fdocuments.us/reader/full/java-build-tools-part-2) +* Bad, because [it has a medium popularity](https://fdocuments.us/reader/full/java-build-tools-part-2) * Bad, because [it allows custom build scripts which need to be debugged](https://www.softwareyoga.com/10-reasons-why-we-chose-maven-over-gradle/) ### Ant @@ -51,8 +51,8 @@ Chosen option: "Gradle", because it is lean and fits our development style. * Bad, because [build scripts can quickly become huge](https://technologyconversations.com/2014/06/18/build-tools/) * Bad, because [everything has to be written from scratch](http://www.baeldung.com/ant-maven-gradle) * Bad, because [no conventions are enforced which can make it hard to understand someone else's build script](http://www.baeldung.com/ant-maven-gradle) -* Bad, because [it has nearly no community support](http://pages.zeroturnaround.com/rs/zeroturnaround/images/java-build-tools-part-2.pdf) -* Bad, because [it has a low popularity](http://pages.zeroturnaround.com/rs/zeroturnaround/images/java-build-tools-part-2.pdf) +* Bad, because [it has nearly no community support](https://fdocuments.us/reader/full/java-build-tools-part-2) +* Bad, because [it has a low popularity](https://fdocuments.us/reader/full/java-build-tools-part-2) * Bad, because [it offers too much freedom](https://www.slant.co/versus/2106/2107/~apache-ant_vs_apache-maven) ## Links diff --git a/docs/adr/0004-use-mariadb-connector.md b/docs/adr/0004-use-mariadb-connector.md index 31b4b6604d6..7d02b15aa6a 100644 --- a/docs/adr/0004-use-mariadb-connector.md +++ b/docs/adr/0004-use-mariadb-connector.md @@ -21,7 +21,7 @@ Chosen option: "Use MariaDB Connector", because comes out best \(see below\). The [MariaDB Connector](https://mariadb.com/kb/en/library/about-mariadb-connector-j/) is a LGPL-licensed JDBC driver to connect to MySQL and MariaDB. -* Good, because can be used as drop-in replacement for MySQL connectopr +* Good, because can be used as drop-in replacement for MySQL connector ### Use MySQL Connector diff --git a/docs/adr/0006-only-translated-strings-in-language-file.md b/docs/adr/0006-only-translated-strings-in-language-file.md index 2fd3ccfbc16..d7db17c5fac 100644 --- a/docs/adr/0006-only-translated-strings-in-language-file.md +++ b/docs/adr/0006-only-translated-strings-in-language-file.md @@ -2,7 +2,7 @@ ## Context and Problem Statement -JabRef has translation files `JabRef_it.properties`, ... There are translated and unstranslated strings. Which ones should be in the translation file? +JabRef has translation files `JabRef_it.properties`, ... There are translated and untranslated strings. Which ones should be in the translation file? ## Decision Drivers @@ -31,8 +31,8 @@ Chosen option: "Only translated strings in language file", because comes out bes ### Translated and untranslated strings in language file, have value the untranslated string to indicate untranslated * Good, because no issues with FXML -* Good, because Crowin supports it -* Bad, because untranslated strings cannot be identified easily in latin languages +* Good, because Crowdin supports it +* Bad, because untranslated strings cannot be identified easily in Latin languages ### Translated and untranslated strings in language file, have empty to indicate untranslated diff --git a/docs/adr/0008-use-public-final-instead-of-getters.md b/docs/adr/0008-use-public-final-instead-of-getters.md index daf781194ab..498fdd8c52b 100644 --- a/docs/adr/0008-use-public-final-instead-of-getters.md +++ b/docs/adr/0008-use-public-final-instead-of-getters.md @@ -19,4 +19,4 @@ Chosen option: "Offer public static field", because getters used to be a convent ### Negative Consequences -* new comers could get confused, because getters/setters are still teached +* newcomers could get confused, because getters/setters are still taught diff --git a/docs/adr/0011-test-external-links-in-documentation.md b/docs/adr/0011-test-external-links-in-documentation.md index b1b9756e799..070efb81550 100644 --- a/docs/adr/0011-test-external-links-in-documentation.md +++ b/docs/adr/0011-test-external-links-in-documentation.md @@ -26,18 +26,18 @@ Chosen option: "\[option 1\]", because \[justification. e.g., only option, which ### Negative Consequences -* Some external sites need to [be disabled](https://github.com/JabRef/jabref/pull/6542/files). For isntance, GitHub.com always returns "forbidden". A [filter for status is future work of the used tool](https://github.com/tcort/markdown-link-check/issues/94#issuecomment-634947466). +* Some external sites need to [be disabled](https://github.com/JabRef/jabref/pull/6542/files). For instance, GitHub.com always returns "forbidden". A [filter for status is future work of the used tool](https://github.com/tcort/markdown-link-check/issues/94#issuecomment-634947466). ## Pros and Cons of the Options ### Check external links once a month -* Good, because does not interfer with the normal development workflow +* Good, because does not interfere with the normal development workflow * Bad, because an additional workflow is required ### Check external links in the "checkstyle" task -* Good, because no seperate workflow is required +* Good, because no separate workflow is required * Bad, because checks fail independent of the PR (because external web sites can go down and go up independent of a PR) ### Do not check external links diff --git a/docs/adr/0013-add-native-support-biblatex-software.md b/docs/adr/0013-add-native-support-biblatex-software.md index f2fba184731..55f1b16af9a 100644 --- a/docs/adr/0013-add-native-support-biblatex-software.md +++ b/docs/adr/0013-add-native-support-biblatex-software.md @@ -1,4 +1,4 @@ -# Add Native Support for BibLatex-Sotware +# Add Native Support for BibLatex-Software * Deciders: Oliver Kopp @@ -6,8 +6,8 @@ Technical Story: [6574-Adding support for biblatex-software](https://github.com/ ## Context and Problem Statement -JabRef does not right now have support for Biblatex-Software out of the box, users have to add custome entry type. -With citing software becoming fairly common , native support is helpful. +Right now, JabRef does not have support for Biblatex-Software out of the box, users have to add custom entry types. +With citing software becoming fairly common, native support is helpful. ## Decision Drivers diff --git a/docs/adr/0014-separate-URL-creation-to-enable-proper-logging.md b/docs/adr/0014-separate-URL-creation-to-enable-proper-logging.md index 1b980e3abd2..13ce0bde380 100644 --- a/docs/adr/0014-separate-URL-creation-to-enable-proper-logging.md +++ b/docs/adr/0014-separate-URL-creation-to-enable-proper-logging.md @@ -45,7 +45,7 @@ Chosen option: "Separate URL creation", because comes out best \(see below\). } ``` -* Good, because exceptions thrown at method are directly catched +* Good, because exceptions thrown at method are directly caught * Good, because exceptions in different statements belong to different catch blocks * Good, because code to determine URL is written once * OK, because "Java by Comparison" does not state anything about it diff --git a/docs/adr/0015-support-an-abstract-query-syntax-for-query-conversion.md b/docs/adr/0015-support-an-abstract-query-syntax-for-query-conversion.md index f25f161c2ff..69b9664be8f 100644 --- a/docs/adr/0015-support-an-abstract-query-syntax-for-query-conversion.md +++ b/docs/adr/0015-support-an-abstract-query-syntax-for-query-conversion.md @@ -14,9 +14,9 @@ Which features should the syntax support? ## Decision Outcome -Chosen option: "Use a syntax that is derived of the lucene query syntax", because only option that is already known, and easy to implemenent. +Chosen option: "Use a syntax that is derived of the lucene query syntax", because only option that is already known, and easy to implement. Furthermore parsers for lucene already exist and are tested. -For simplicitly, and lack of universal capabilities across fetchers, only basic query features and therefor syntax is supported: +For simplicity, and lack of universal capabilities across fetchers, only basic query features and therefor syntax is supported: * All terms in the query are whitespace separated and will be ANDed * Default and certain fielded terms are supported diff --git a/docs/adr/0016-mutable-preferences-objects.md b/docs/adr/0016-mutable-preferences-objects.md index 6abb70af9c1..96fadaf5e6f 100644 --- a/docs/adr/0016-mutable-preferences-objects.md +++ b/docs/adr/0016-mutable-preferences-objects.md @@ -12,5 +12,5 @@ To create an immutable preferences object every time seems to be a waste of time ## Decision Outcome Chosen option: "Alter the exiting object", because the preferences objects are just wrappers around the basic preferences framework of JDK. They -should be mutable on-the-fly similar to objects with a Builder inside and to be stored immediatly again in the +should be mutable on-the-fly similar to objects with a Builder inside and to be stored immediately again in the preferences. diff --git a/docs/adr/0015-allow-model-access-logic.md b/docs/adr/0017-allow-model-access-logic.md similarity index 79% rename from docs/adr/0015-allow-model-access-logic.md rename to docs/adr/0017-allow-model-access-logic.md index 5567fe198d4..bccb6650978 100644 --- a/docs/adr/0015-allow-model-access-logic.md +++ b/docs/adr/0017-allow-model-access-logic.md @@ -7,7 +7,7 @@ ## Decision Drivers -- New comers should find the architecture "split" natural +- Newcomers should find the architecture "split" natural - The architecture should be a help (and not a burden) ## Considered Options @@ -24,6 +24,10 @@ Chosen option: "`org.jabref.model` may use `org.jabref.logic` in defined cases", ### `org.jabref.model` uses `org.jabref.model` (and external libraries) only +The model package does not access logic or other packages of JabRef. +Access to classes of external libraries is allowed. +The logic package may use the model package. + - Good, because clear separation of model and logic - Bad, because this leads to an [Anemic Domain Model](https://martinfowler.com/bliki/AnemicDomainModel.html) @@ -31,7 +35,7 @@ Chosen option: "`org.jabref.model` may use `org.jabref.logic` in defined cases", - Good, because model and logic are still separated - Neutral, because each exception has to be discussed and agreed -- Bad, because new comers have to be informed that there are certain (agreed) exceptions for model to access logic +- Bad, because newcomers have to be informed that there are certain (agreed) exceptions for model to access logic ### `org.jabref.model` and `org.jabref.logic` may access each other freely diff --git a/docs/adr/0017-use-regular-expression-to-split-multiple-sentence-titles.md b/docs/adr/0017-use-regular-expression-to-split-multiple-sentence-titles.md new file mode 100644 index 00000000000..2b50f04b513 --- /dev/null +++ b/docs/adr/0017-use-regular-expression-to-split-multiple-sentence-titles.md @@ -0,0 +1,26 @@ +# Use regular expression to split multiple-sentence titles + +## Context and Problem Statement + +Some entry titles are composed of multiple sentences, for example: "Whose Music? A Sociology of Musical Language", therefore, it is necessary to first split the title into sentences and process them individually to ensure proper formatting using '[Sentence Case](https://en.wiktionary.org/wiki/sentence_case)' or '[Title Case](https://en.wiktionary.org/wiki/title_case#English)' + +## Considered Options + +* [Regular expression](https://docs.oracle.com/javase/tutorial/essential/regex/) +* [OpenNLP](https://opennlp.apache.org/) +* [ICU4J](http://site.icu-project.org/home) + +## Decision Outcome + +Chosen option: "Regular expression", because we can use Java internal classes (Pattern, Matcher) instead of adding additional dependencies + +### Positive Consequences + +* Less dependencies on third party libraries +* Smaller project size (ICU4J is very large) +* No need for model data (OpenNLP is a machine learning based toolkit and needs a trained model to work properly) + +### Negative Consequences + +* Regular expressions can never cover every case, therefore, splitting may not be accurate for every title + diff --git a/docs/adr/template.md b/docs/adr/template.md index b4974469854..255de8595c9 100644 --- a/docs/adr/template.md +++ b/docs/adr/template.md @@ -1,4 +1,4 @@ -# MADR Templae +# MADR Template The template of [MADR](https://adr.github.io/madr/) is available at . You can omit the `<-- optional -->` parts, which lead to a very minimal ADR: diff --git a/docs/advanced-reading/fetchers.md b/docs/advanced-reading/fetchers.md index b9310a4f0d5..e3256585127 100644 --- a/docs/advanced-reading/fetchers.md +++ b/docs/advanced-reading/fetchers.md @@ -1,6 +1,6 @@ # Working on fetchers -Fetchers are the implementation of the [search using online services](https://docs.jabref.org/collect/import-using-online-bibliographic-database). Some fetchers require API keys to get them working. To get the fetchers running in a JabRef development setup, the keys need to be placed in the respective enviornment variable. The following table lists the respective fetchers, where to get the key from and the environment variable where the key has to be placed. +Fetchers are the implementation of the [search using online services](https://docs.jabref.org/collect/import-using-online-bibliographic-database). Some fetchers require API keys to get them working. To get the fetchers running in a JabRef development setup, the keys need to be placed in the respective environment variable. The following table lists the respective fetchers, where to get the key from and the environment variable where the key has to be placed. | Service | Key Source | Environment Variable | Rate Limit | | :--- | :--- | :--- | :--- | diff --git a/docs/advanced-reading/remote-storage.md b/docs/advanced-reading/remote-storage.md new file mode 100644 index 00000000000..9fe71744a10 --- /dev/null +++ b/docs/advanced-reading/remote-storage.md @@ -0,0 +1,20 @@ +# Remote Storage + +## Using a shared PostgreSQL database + +... + +## Handling large shared databases + +Synchronization times may get long when working with a large database containing several thousand entries. Therefore, synchronization only happens if several conditions are fulfilled: + +* Edit to another field. +* Major changes have been made (pasting or deleting more than one character). + +Class `org.jabref.logic.util.CoarseChangeFilter.java` checks both conditions. + +Remaining changes that has not been synchronized yet are saved at closing the database rendering additional closing time. Saving is realized in `org.jabref.logic.shared.DBMSSynchronizer.java`. Following methods account for synchronization modes: + +* `pullChanges` synchronizes the database unconditionally. +* `pullLastEntryChanges` synchronizes only if there are remaining entry changes. It is invoked when closing the shared database (`closeSharedDatabase`). + diff --git a/docs/getting-into-the-code/code-howtos.md b/docs/getting-into-the-code/code-howtos.md index b1e0ca97980..8b5a452aaba 100644 --- a/docs/getting-into-the-code/code-howtos.md +++ b/docs/getting-into-the-code/code-howtos.md @@ -53,7 +53,7 @@ TODO: Usage of status bar and Swing Dialogs ### What the EventSystem is used for -Many times there is a need to provide an object on many locations simultaneously. This design pattern is quite similar to Java's Observer, but it is much simplier and readable while having the same functional sense. +Many times there is a need to provide an object on many locations simultaneously. This design pattern is quite similar to Java's Observer, but it is much simpler and readable while having the same functional sense. ### Main principle @@ -61,7 +61,7 @@ Many times there is a need to provide an object on many locations simultaneously ### Register to the `EventBus` -Any listening method has to be annotated with `@Subscribe` keyword and must have only one accepting parameter. Furthermore the object which contains such listening method\(s\) has to be registered using the `register(Object)` method provided by `EventBus`. The listening methods can be overloaded by using differnt parameter types. +Any listening method has to be annotated with `@Subscribe` keyword and must have only one accepting parameter. Furthermore the object which contains such listening method\(s\) has to be registered using the `register(Object)` method provided by `EventBus`. The listening methods can be overloaded by using different parameter types. ### Posting an object @@ -111,7 +111,7 @@ public class Main { ### Event handling in JabRef -The `event` package contains some specific events which occure in JabRef. +The `event` package contains some specific events which occur in JabRef. For example: Every time an entry was added to the database a new `EntryAddedEvent` is sent through the `eventBus` which is located in `BibDatabase`. @@ -175,7 +175,7 @@ The tests check whether translation strings appear correctly in the resource bun 1. Add new `Localization.lang("KEY")` to Java file. Run the `LocalizationConsistencyTest`under \(src/test/org.jabref.logic.l10n\) 2. Tests fail. In the test output a snippet is generated which must be added to the English translation file. There is also a snippet generated for the non-English files, but this is irrelevant. 3. Add snippet to English translation file located at `src/main/resources/l10n/JabRef_en.properties` -4. Please do not add tranlsations for other languages direclty in the properties. They will be overwritten by [Crowdin](https://crowdin.com/project/jabref) +4. Please do not add translations for other languages directly in the properties. They will be overwritten by [Crowdin](https://crowdin.com/project/jabref) ## Cleanup and Formatters @@ -300,7 +300,7 @@ public void getTypeReturnsBibLatexArticleInBibLatexMode() { } ``` -To test that a preferences migration works succesfully, use the mockito method `verify`. See `PreferencesMigrationsTest` for an example. +To test that a preferences migration works successfully, use the mockito method `verify`. See `PreferencesMigrationsTest` for an example. ## UI diff --git a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace.md b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace.md index 9bac648c0e7..4d90e5b9c0c 100644 --- a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace.md +++ b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace.md @@ -4,11 +4,11 @@ This guide explains how to set up your environment for development of JabRef. It ```text The most important step is to configure your IDE. -In case you know how to install JDK14 with JavaFX support and to fork JabRef's code, +In case you know how to install JDK15 with JavaFX support and to fork JabRef's code, please scroll down to the IDE setup. ``` -For a complete step-by-step guide for Linux using IntellJ IDEA as the IDE, have a look at the following video instructions: +For a complete step-by-step guide for Linux using IntelliJ IDEA as the IDE, have a look at the following video instructions: [![YouTube video showing the step-by-step guide](https://img.youtube.com/vi/JkFVJ6p0urw/mqdefault.jpg)](https://youtu.be/JkFVJ6p0urw) @@ -16,13 +16,14 @@ For a complete step-by-step guide for Linux using IntellJ IDEA as the IDE, have This section list the prerequisites you need to get started to develop JabRef. After this section, you are ready to get the code. -### Java Development Kit 14 +### Java Development Kit 15 -A working Java \(Develoment Kit\) 14 installation with Java FX support is required. In the command line \(terminal in Linux, cmd in Windows\) run `javac -version` and make sure that the reported version is Java 14 \(e.g `javac 14`\). 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. +A working Java \(Develoment Kit\) 15 installation with Java FX support is required. In the command line \(terminal in Linux, cmd in Windows\) run `javac -version` and make sure that the reported version is Java 15 \(e.g `javac 15`\). If `javac` is not found or a wrong version is reported, check your `PATH` environment variable, your `JAVA_HOME` environment variable or install the most recent JDK. -[JavaFX is not part of the default JDK any more](https://www.reddit.com/r/java/comments/82qm9x/javafx_will_be_removed_from_the_java_jdk_in_jdk_11/), it needs to be installed separately if not using a special JDK. We recommend to setup the full Liberica JDK including JavaFX. You can get it from [https://bell-sw.com/pages/downloads/?version=java-14&package=jdk-full](https://bell-sw.com/pages/downloads/?version=java-14&package=jdk-full). On Windows, you can execute `choco install libericajdkfull` \(requires [installation of chocolatey - a package manager for Windows](https://chocolatey.org/install)\). +[JavaFX is not part of the default JDK any more](https://www.reddit.com/r/java/comments/82qm9x/javafx_will_be_removed_from_the_java_jdk_in_jdk_11/), it needs to be installed separately if not using a special JDK. ~~We recommend to setup the full Liberica JDK including JavaFX. You can get it from [https://bell-sw.com/pages/downloads/?version=java-15&package=jdk-full](https://bell-sw.com/pages/downloads/?version=java-15&package=jdk-full). On Windows, you can execute `choco install libericajdkfull` \(requires [installation of chocolatey - a package manager for Windows](https://chocolatey.org/install)\).~~ (Liberica JDK 15 does not include the Java compiler properly any more) -Alternatively, you can download and install the JDK from [https://jdk.java.net/](https://jdk.java.net/). Afterwards, download download the "jmods" JavaFX 14 zip archive from [https://gluonhq.com/products/javafx/](https://gluonhq.com/products/javafx/) and put the `.jmod` files into `C:\Program Files\OpenJDK\jdk-14.0.1\jmods`. +Download and install the JDK from [https://jdk.java.net/](https://jdk.java.net/). +Afterwards, download the "jmods" JavaFX 15 zip archive from [https://gluonhq.com/products/javafx/](https://gluonhq.com/products/javafx/) and put the `.jmod` files into `C:\Program Files\OpenJDK\jdk-15\jmods`. ### GitHub Account @@ -34,9 +35,9 @@ Proposals for account names: * 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 adress 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/). +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 adress. They use one mich may get public. Mostly, they create a new email account for development only. That account then be used for develoment mailing lists, mail exchange with other developers, etc. +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: @@ -54,9 +55,18 @@ It is strongly recommend that you have git installed. ### IDE -We suggest [IntelliJ IDEA](https://www.jetbrains.com/idea/) or [Eclipse \(for advanced users\)](https://eclipse.org/) \(`2020-03` or newer\). +We suggest [IntelliJ IDEA](https://www.jetbrains.com/idea/). +For advanced users, [Eclipse](https://eclipse.org/) \(`2020-09` or newer\) is also possible. -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. Under Windows, download it from [www.eclipse.org](http://www.eclipse.org/downloads/) and run the installer. +#### IntelliJ + +We recommend to install IntelliJ IDEA using [JetBrains Toolbox App](https://www.jetbrains.com/toolbox-app/), because IDE updates are automatically installed. + +#### Ecipse + +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. + +Eclipse JEE 2020-09 or newer is required. For Eclipse 2020-09 you need to install [Java 15 Support](https://marketplace.eclipse.org/content/java-15-support-eclipse-2020-09-417). ### Other Tooling @@ -86,9 +96,7 @@ This section explains how you get the JabRef code onto your machine in a form al 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? -### Setup for IntelliJ IDEA - -We recommend to install IntelliJ IDEA using [JetBrains Toolbox App](https://www.jetbrains.com/toolbox-app/), because IDE updates are automatically installed. +### Configuration of IntelliJ IDEA 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 but once set up, IntelliJ IDEA's internal system can be used for sub-sequent builds. @@ -96,12 +104,15 @@ To configure IntelliJ IDEA for developing JabRef, you should first ensure that y * Navigate to **File \| Settings \| Plugins \| Installed** and check that you have the _Gradle_ and _Gradle Extension_ enabled. -After that, you can open `jabref/build.gradle` as a project. It is crucial that Java 14 is used consistently for the JabRef project which includes ensuring the right settings for your project structure, Gradle build, and run configurations. +After that, you can open `jabref/build.gradle` as a project. It is crucial that Java 15 is used consistently for the JabRef project which includes ensuring the right settings for your project structure, Gradle build, and run configurations. + +Ensure you have a Java 15 SDK configured by navigating to **File \| Project Structure \| Platform Settings \| SDKs**. If you don't have one, add a new Java JDK and point it to the location of a JDK 15. +![Project Settings](../.gitbook/assets/intellij-choose-jdk-adoptopenjdk-on-windows-project-settings.png) -Ensure you have a Java 14 SDK configured by navigating to **File \| Project Structure \| Platform Settings \| SDKs**. If you don't have one, add a new Java JDK and point it to the location of a JDK 14: +Navigate to **File \| Project Structure \| Project** and ensure that the projects' SDK is Java 15 +![Use JDK 15 as project SDK](../.gitbook/assets/intellij-choose-jdk15-project-default.png) -* Navigate to **File \| Project Structure \| Project** and ensure that the projects' SDK is Java 14 -* Navigate to **File \| Settings \| Build, Execution, Deployment \| Build Tools \| Gradle** and select the Java 14 SDK as the Gradle JVM at the bottom. +Navigate to **File \| Settings \| Build, Execution, Deployment \| Build Tools \| Gradle** and select the "Project SDK" as the Gradle JVM at the bottom. To prepare IntelliJ's build system two additional steps are required: @@ -148,7 +159,7 @@ To use IntelliJ IDEA's internal build system when you build JabRef through **Bui ![Ignore the Gradle project "buildSrc"](../.gitbook/assets/intellij-gradle-config-ignore-buildSrc%20%282%29.png) -* Delete `org.jabref.gui.logging.plugins.Log4jPlugins` \(location: `generated\org\jabref\gui\logging\plugins\Log4jPlugins.java`\). Otherwise, you will see folowing error: +* Delete `org.jabref.gui.logging.plugins.Log4jPlugins` \(location: `generated\org\jabref\gui\logging\plugins\Log4jPlugins.java`\). Otherwise, you will see following error: ```text Error:java: Unable to create Plugin Service Class org.jabref.gui.logging.plugins.Log4jPlugins @@ -162,7 +173,7 @@ To use IntelliJ IDEA's internal build system when you build JabRef through **Bui Essentially, you now have the best of both worlds: You can run Gradle tasks using the Gradle Tool Window and unless you haven't made changes to input files that generate sources, you can compile and run with IntelliJ's faster internal build system. -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 `generated\org\jabref\gui\logging\plugins\Log4jPlugins.java`. This is independet of having enabled or disabled Annotation Processing \(see above at "Enable Annotation Processing"\). +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 `generated\org\jabref\gui\logging\plugins\Log4jPlugins.java`. This is independent of having enabled or disabled Annotation Processing \(see above at "Enable Annotation Processing"\). #### Using JabRef's code style @@ -174,15 +185,24 @@ Contributions to JabRef's source code need to have a code formatting that is con * Go to **File \| Settings \| Editor \| Code Style** * Click on the settings wheel \(next to the scheme chooser\), then click "Import Scheme" * Select the IntelliJ configuration file `config/IntelliJ Code Style.xml` -* Go to **File \| Settings \| Tools \| Checkstyle \| Configuration File** 1. Import the CheckStyle configuration file by clicking the \[+\] button 2. For the description provide "JabRef" 3. Click "Browse" and choose `config/checkstyle/checkstyle.xml` 4. Click "Next" and "Finish" 5. Activate the CheckStyle configuration file by ticking it in the list 6. Ensure that the [latest CheckStyle version](https://checkstyle.org/releasenotes.html) is selected \(8.36 or higher\). 8.36 is required for Java 14. 7. Set the "Scan Scope" to "Only Java sources \(including tests\) 8. Save settings by clicking "OK" 9. Your configuration should now look like this: - ```text +Finally, ensure that the checkstyle configuration file is in place: + +1. Go to **File \| Settings \| Tools \| Checkstyle \| Configuration File** +2. Import the CheckStyle configuration file by clicking the \[+\] button +3. For the description provide "JabRef" +4. Click "Browse" and choose `config/checkstyle/checkstyle.xml` +5. Click "Next" and "Finish" +6. Activate the CheckStyle configuration file by ticking it in the list +7. Ensure that the [latest CheckStyle version](https://checkstyle.org/releasenotes.html) is selected \(8.36 or higher\). 8.36 is required for Java 15. +8. Set the "Scan Scope" to "Only Java sources \(including tests\) +9. Save settings by clicking "OK" +10. Your configuration should now look like this: ![checkstyle settings](../.gitbook/assets/intellij-checkstyle-settings.png) - ``` ### Setup for Eclipse -Make sure your Eclipse installation us up to date, Eclipse JEE 2020-03 or newer is required. For Eclipse 2020-03 you need to install [jdk14 support](https://marketplace.eclipse.org/content/java-14-support-eclipse-2020-03-415) +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. @@ -234,7 +254,7 @@ Got it running? GREAT! You are ready to lurk the code and contribute to JabRef. ### Java installation -An indication that `JAVA_HOME` is not correctly set or no JDK 14 is installed is following error message: +An indication that `JAVA_HOME` is not correctly set or no JDK 15 is installed is following error message: ```text compileJava FAILED @@ -256,3 +276,14 @@ java.lang.UnsupportedClassVersionError: org/javamodularity/moduleplugin/ModuleSy 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. +### Problems 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-15-mac.jar (org.openjfx:javafx-fxml:15). + Searched in the following locations: + file:/repository/org/openjfx/javafx-fxml/15/javafx-fxml-15-mac.jar +``` + +As a workaround, you can remove all local openjfx artifacts by deleting the whole openjfx folder from specified location. diff --git a/docs/getting-into-the-code/high-level-documentation.md b/docs/getting-into-the-code/high-level-documentation.md index eb4d80521db..84e5295ae70 100644 --- a/docs/getting-into-the-code/high-level-documentation.md +++ b/docs/getting-into-the-code/high-level-documentation.md @@ -35,5 +35,5 @@ All packages and classes which are currently not part of these packages \(we are ## Most Important Classes and their Relation -Both GUI and CLI are started via the `JabRefMain` which will in turn call `JabRef` which then decides whether the GUI \(`JabRefFrame`\) or the CLI \(`JabRefCLI` and a lot of code in `JabRef`\) will be started. The `JabRefFrame` represents the Window which contains a `SidePane` on the left used for the fetchers/groups Each tab is a `BasePanel` which has a `SearchBar` at the top, a `MainTable` at the center and a `PreviewPanel` or an `EntryEditor` at the bottom. Any right click on the `MainTable` is handled by the `RightClickMenu`. Each `BasePanel` holds a `BibDatabaseContext` consisting of a `BibDatabase` and the `MetaData`, which are the only relevant data of the currently shown database. A `BibDatabase` has a list of `BibEntries`. Each `BibEntry` has a key, a bibtex key and a key/value store for the fields with their values. Interpreted data \(such as the type or the file field\) is stored in the `TypedBibentry` type. The user can change the `JabRefPreferences` through the `PreferencesDialog`. +Both GUI and CLI are started via the `JabRefMain` which will in turn call `JabRef` which then decides whether the GUI \(`JabRefFrame`\) or the CLI \(`JabRefCLI` and a lot of code in `JabRef`\) will be started. The `JabRefFrame` represents the Window which contains a `SidePane` on the left used for the fetchers/groups Each tab is a `BasePanel` which has a `SearchBar` at the top, a `MainTable` at the center and a `PreviewPanel` or an `EntryEditor` at the bottom. Any right click on the `MainTable` is handled by the `RightClickMenu`. Each `BasePanel` holds a `BibDatabaseContext` consisting of a `BibDatabase` and the `MetaData`, which are the only relevant data of the currently shown database. A `BibDatabase` has a list of `BibEntries`. Each `BibEntry` has an ID, a citation key and a key/value store for the fields with their values. Interpreted data \(such as the type or the file field\) is stored in the `TypedBibentry` type. The user can change the `JabRefPreferences` through the `PreferencesDialog`. diff --git a/docs/readings-on-coding/tools.md b/docs/readings-on-coding/tools.md index a96fe324106..08d98cf1b99 100644 --- a/docs/readings-on-coding/tools.md +++ b/docs/readings-on-coding/tools.md @@ -4,7 +4,7 @@ This page lists some software we consider useful. ## Browser plugins -* [Refined GitHub](https://github.com/sindresorhus/refined-github) - GitHub on stereoids +* [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 diff --git a/docs/teaching.md b/docs/teaching.md index 41017b7a257..95f3327d493 100644 --- a/docs/teaching.md +++ b/docs/teaching.md @@ -2,7 +2,7 @@ With JabRef students can level-up their coding and GitHub skills. When taking part in JabRef development, one will learn modern Java coding practices, how code reviews work and how to properly address reviewing feedback. -JabRef tries to achieve high code quality. This ultimately leads to improved software engineering knowledge of contributors. After contributing for JabRef, both coding and general software engienering skills will have increased. Our [development strategy](getting-into-the-code/development-strategy.md) provides more details. +JabRef tries to achieve high code quality. This ultimately leads to improved software engineering knowledge of contributors. After contributing for JabRef, both coding and general software engineering skills will have increased. Our [development strategy](getting-into-the-code/development-strategy.md) provides more details. We recommend to start early and constantly, since students working earlier and more often produce projects that are more correct and completed earlier at the same overall invested time [1](teaching.md#Ayaankazerouni). @@ -17,8 +17,8 @@ We recommend to start early and constantly, since students working earlier and m * Feature Board: [https://github.com/JabRef/jabref/projects/7](https://github.com/JabRef/jabref/projects/7) * This is a general feature board. Recommended, if the board above is empty or you did not find something suitable * Bug Board: [https://github.com/JabRef/jabref/projects/5](https://github.com/JabRef/jabref/projects/5) - * This is an excellent board to find issues training the maintainance knowledge which is essential for industry work - * General "good first issues". The JabRef team tags issues as [good first issues](https://github.com/jabref/jabref/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) to indicate open tasks offering a good start into the JabRef code. These issues are more a list of random bugs and features. For a more structured comparison of the opened tasks, we recommend the projectd boards listed above. + * This is an excellent board to find issues training the maintenance knowledge which is essential for industry work + * General "good first issues". The JabRef team tags issues as [good first issues](https://github.com/jabref/jabref/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) to indicate open tasks offering a good start into the JabRef code. These issues are more a list of random bugs and features. For a more structured comparison of the opened tasks, we recommend the project boards listed above. 5. Be aware that the difficulty of bugs and feature vary. A task should be chosen with care. The JabRef team can help here: The majority of the team has experiences in teaching 6. Get in touch with the JabRef team to discuss details. We offer email, skype, [gitter.im](https://gitter.im/JabRef/jabref), discord. Get in touch with [@koppor](https://github.com/koppor/) to find the right channel and to start forming the success of your course. @@ -38,7 +38,7 @@ The process for accepting contributions is as below. The syntax is [BPMN](https: In short, the contribution is **reviewed by two JabRef developers**. Typically, they have constructive feedback on their contribution. This means, that the contributors get comments on their contribution enabling them to level-up their coding skill. Incorporating improvements takes time, too. The benefit is two-fold: a\) contributors improve their coding skills and b\) JabRef's code quality improves. All in all, we ask to respect the aims of the JabRef team and to reserve time to incorporate the reviewer's comments. -GitHub describes that in their page [Unterstanding the GitHub flow](https://guides.github.com/introduction/flow/): +GitHub describes that in their page [Understanding the GitHub flow](https://guides.github.com/introduction/flow/): [![GitHub flow](.gitbook/assets/github-flow%20%281%29.png)](https://github.com/JabRef/jabref/tree/ed275b62fe7dac57a086e43802e36deb93c63e31/docs/images/github-flow.png) diff --git a/eclipse.gradle b/eclipse.gradle index ea81b0738c7..25948d44d7b 100644 --- a/eclipse.gradle +++ b/eclipse.gradle @@ -26,7 +26,7 @@ eclipse { def javafxcontrols = entries.find { isJavafxControls(it) }; javafxcontrols.entryAttributes['add-exports'] = 'javafx.controls/com.sun.javafx.scene.control=org.jabref:javafx.controls/com.sun.javafx.scene.control.behavior=org.jabref:javafx.controls/javafx.scene.control=org.jabref'; - javafxcontrols.entryAttributes['add-opens'] = 'javafx.controls/com.sun.javafx.scene.control=org.jabref:javafx.controls/com.sun.javafx.scene.control.behavior=org.jabref:javafx.controls/javafx.scene.control=org.jabref'; + javafxcontrols.entryAttributes['add-opens'] = 'javafx.controls/com.sun.javafx.scene.control=org.jabref:javafx.controls/com.sun.javafx.scene.control.behavior=org.jabref:javafx.controls/javafx.scene.control=org.jabref:javafx.controls/javafx.scene.control.skin=org.controlsfx.controls'; def javafxgraphics = entries.find { isJavafxGraphics(it) }; javafxgraphics.entryAttributes['add-opens'] = 'javafx.graphics/javafx.scene=org.controlsfx.controls'; diff --git a/scripts/generate-authors.sh b/scripts/generate-authors.sh index 6f00f12bb62..d38d5c4ba5e 100755 --- a/scripts/generate-authors.sh +++ b/scripts/generate-authors.sh @@ -58,5 +58,5 @@ cd "$(dirname "$(readlink -f "$BASH_SOURCE")")/.." # co-authors coauthors=$(git log -i --grep=co-authored-by | grep -i "co-authored-by" | sed "s/.*co-authored-by: \(.*\)/\1/I" | sed "s/ <.*//") - echo -e "$authors\n$(git log --format='%aN')\n$coauthors" | grep -v "\[bot\]" | grep -v "JabRef" | grep -v "Siedlerchr" | grep -v "^Christoph$" | grep -v "^Mootez$" | grep -v "oscargus" | grep -v "dependabot" | grep -v "github actions" | grep -v "igorsteinmacher" | grep -v "halirutan" | grep -v "matthiasgeiger" | grep -v "Gitter Badger" | grep -v "gdstewart" | grep -v "chenyuheng" | LC_ALL=C.UTF-8 sort --unique --ignore-case + echo -e "$authors\n$(git log --format='%aN')\n$coauthors" | grep -v "\[bot\]" | grep -v "JabRef" | grep -v "Siedlerchr" | grep -v "^Christoph$" | grep -v "^Mootez$" | grep -v "oscargus" | grep -v "dependabot" | grep -v "github actions" | grep -v "igorsteinmacher" | grep -v "halirutan" | grep -v "matthiasgeiger" | grep -v "Gitter Badger" | grep -v "gdstewart" | grep -v "m-mauersberger" | grep -v "chenyuheng" | LC_ALL=C.UTF-8 sort --unique --ignore-case } > AUTHORS diff --git a/snap/hooks/connect-plug-etc-chromium-native-messaging-jabref b/snap/hooks/connect-plug-etc-chromium-native-messaging-jabref index 2644fc7026a..bcd982e7c1d 100755 --- a/snap/hooks/connect-plug-etc-chromium-native-messaging-jabref +++ b/snap/hooks/connect-plug-etc-chromium-native-messaging-jabref @@ -1,6 +1,6 @@ #!/bin/sh -if [ ! -d /var/lib/snapd/hostfs/usr/lib/mozilla/native-messaging-hosts ]; then +if [ ! -d /etc/chromium/native-messaging-hosts ]; then echo "Missing directory, create it manually then try again:" echo "sudo mkdir -p /etc/chromium/native-messaging-hosts" exit 1 diff --git a/snap/hooks/connect-plug-etc-opt-chrome-native-messaging-jabref b/snap/hooks/connect-plug-etc-opt-chrome-native-messaging-jabref index 025087770a7..bb5d8c7ec18 100755 --- a/snap/hooks/connect-plug-etc-opt-chrome-native-messaging-jabref +++ b/snap/hooks/connect-plug-etc-opt-chrome-native-messaging-jabref @@ -1,6 +1,6 @@ #!/bin/sh -if [ ! -d /var/lib/snapd/hostfs/usr/lib/mozilla/native-messaging-hosts ]; then +if [ ! -d /etc/opt/chrome/native-messaging-hosts ]; then echo "Missing directory, create it manually then try again:" echo "sudo mkdir -p /etc/opt/chrome/native-messaging-hosts" exit 1 diff --git a/snap/hooks/connect-plug-etc-opt-edge-native-messaging-jabref b/snap/hooks/connect-plug-etc-opt-edge-native-messaging-jabref new file mode 100755 index 00000000000..c3dca7ea72b --- /dev/null +++ b/snap/hooks/connect-plug-etc-opt-edge-native-messaging-jabref @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ ! -d /etc/opt/edge/native-messaging-hosts ]; then + echo "Missing directory, create it manually then try again:" + echo "sudo mkdir -p /etc/opt/edge/native-messaging-hosts" + exit 1 +fi + +cp "$SNAP/lib/native-messaging-host/chromium/org.jabref.jabref.json" /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json diff --git a/snap/hooks/disconnect-plug-etc-opt-edge-native-messaging-jabref b/snap/hooks/disconnect-plug-etc-opt-edge-native-messaging-jabref new file mode 100755 index 00000000000..cfb9af5905a --- /dev/null +++ b/snap/hooks/disconnect-plug-etc-opt-edge-native-messaging-jabref @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ ! -f /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json ]; then + exit 0 +elif grep --quiet '"path": "/snap' /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json; then + rm /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json +fi diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 5116e8437db..9e73b004280 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -15,11 +15,8 @@ architectures: - build-on: amd64 plugs: - desktop: - desktop-legacy: - wayland: - unity7: home: + unity7: opengl: network-bind: removable-media: @@ -31,6 +28,10 @@ plugs: interface: system-files write: - /etc/opt/chrome/native-messaging-hosts/org.jabref.jabref.json + etc-opt-edge-native-messaging-jabref: + interface: system-files + write: + - /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json etc-chromium-native-messaging-jabref: interface: system-files write: @@ -46,18 +47,17 @@ apps: environment: _JAVA_OPTIONS: "-Duser.home=$SNAP_USER_DATA" + GTK_USE_PORTAL: "1" parts: jabref: plugin: dump - # source: build/distribution/JabRef-5.1-portable_linux.tar.gz - # Use this source for debug purposes: - source: https://builds.jabref.org/master/JabRef-5.1-portable_linux.tar.gz + source: https://builds.jabref.org/master/JabRef-5.2-portable_linux.tar.gz stage-packages: - x11-utils override-build: | snapcraftctl build - snapcraftctl set-version "$(cat $SNAPCRAFT_PART_INSTALL/lib/app/JabRef.cfg | grep "app.version=" | cut -d'=' -f2)" + snapcraftctl set-version "$(cat $SNAPCRAFT_PART_INSTALL/lib/app/.jpackage.xml | grep "app-version" | cut -d">" -f2 | cut -d"<" -f1)" sed -i 's|/opt/jabref/lib/jabrefHost.py|/snap/bin/jabref.browser-proxy|g' $SNAPCRAFT_PART_INSTALL/lib/native-messaging-host/*/org.jabref.jabref.json rm $SNAPCRAFT_PART_INSTALL/bin/JabRef jabref-launcher: diff --git a/src/jmh/java/org/jabref/benchmarks/Benchmarks.java b/src/jmh/java/org/jabref/benchmarks/Benchmarks.java index b1826b20acb..31935438c71 100644 --- a/src/jmh/java/org/jabref/benchmarks/Benchmarks.java +++ b/src/jmh/java/org/jabref/benchmarks/Benchmarks.java @@ -55,7 +55,7 @@ public void init() throws Exception { Random randomizer = new Random(); for (int i = 0; i < 1000; i++) { BibEntry entry = new BibEntry(); - entry.setCiteKey("id" + i); + entry.setCitationKey("id" + i); entry.setField(StandardField.TITLE, "This is my title " + i); entry.setField(StandardField.AUTHOR, "Firstname Lastname and FirstnameA LastnameA and FirstnameB LastnameB" + i); entry.setField(StandardField.JOURNAL, "Journal Title " + i); diff --git a/src/main/java/org/jabref/gui/Base.css b/src/main/java/org/jabref/gui/Base.css index 2ad2009d218..e89c9a27680 100644 --- a/src/main/java/org/jabref/gui/Base.css +++ b/src/main/java/org/jabref/gui/Base.css @@ -1188,3 +1188,7 @@ TextFlow * { -fx-background-color: rgba(255, 159, 67, 0.5); } + +.mainTable-header{ + -fx-fill: -fx-mid-text-color; +} diff --git a/src/main/java/org/jabref/gui/BasePanelPreferences.java b/src/main/java/org/jabref/gui/BasePanelPreferences.java deleted file mode 100644 index bc95224afa6..00000000000 --- a/src/main/java/org/jabref/gui/BasePanelPreferences.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.jabref.gui; - -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.SimpleDoubleProperty; - -import org.jabref.gui.autocompleter.AutoCompletePreferences; -import org.jabref.gui.entryeditor.EntryEditorPreferences; -import org.jabref.gui.keyboard.KeyBindingRepository; -import org.jabref.gui.maintable.MainTablePreferences; -import org.jabref.preferences.JabRefPreferences; -import org.jabref.preferences.PreviewPreferences; - -import com.tobiasdiez.easybind.EasyBind; - -public class BasePanelPreferences { - private final MainTablePreferences tablePreferences; - private AutoCompletePreferences autoCompletePreferences; - private final EntryEditorPreferences entryEditorPreferences; - private final KeyBindingRepository keyBindings; - private final PreviewPreferences previewPreferences; - private final DoubleProperty entryEditorDividerPosition = new SimpleDoubleProperty(); - - public BasePanelPreferences(MainTablePreferences tablePreferences, AutoCompletePreferences autoCompletePreferences, EntryEditorPreferences entryEditorPreferences, KeyBindingRepository keyBindings, PreviewPreferences previewPreferences, Double entryEditorDividerPosition) { - this.tablePreferences = tablePreferences; - this.autoCompletePreferences = autoCompletePreferences; - this.entryEditorPreferences = entryEditorPreferences; - this.keyBindings = keyBindings; - this.previewPreferences = previewPreferences; - this.entryEditorDividerPosition.setValue(entryEditorDividerPosition); - } - - public static BasePanelPreferences from(JabRefPreferences preferences) { - BasePanelPreferences basePanelPreferences = new BasePanelPreferences( - preferences.getMainTablePreferences(), - preferences.getAutoCompletePreferences(), - preferences.getEntryEditorPreferences(), - Globals.getKeyPrefs(), - preferences.getPreviewPreferences(), - preferences.getDouble(JabRefPreferences.ENTRY_EDITOR_HEIGHT)); - EasyBind.subscribe(basePanelPreferences.entryEditorDividerPosition, value -> preferences.putDouble(JabRefPreferences.ENTRY_EDITOR_HEIGHT, value.doubleValue())); - return basePanelPreferences; - } - - public double getEntryEditorDividerPosition() { - return entryEditorDividerPosition.get(); - } - - public void setEntryEditorDividerPosition(double entryEditorDividerPosition) { - this.entryEditorDividerPosition.set(entryEditorDividerPosition); - } - - public DoubleProperty entryEditorDividerPositionProperty() { - return entryEditorDividerPosition; - } - - public MainTablePreferences getTablePreferences() { - return tablePreferences; - } - - public AutoCompletePreferences getAutoCompletePreferences() { - return autoCompletePreferences; - } - - public void setAutoCompletePreferences(AutoCompletePreferences autoCompletePreferences) { - this.autoCompletePreferences = autoCompletePreferences; - } - - public EntryEditorPreferences getEntryEditorPreferences() { - return entryEditorPreferences; - } - - public KeyBindingRepository getKeyBindings() { - return keyBindings; - } - - public PreviewPreferences getPreviewPreferences() { - return previewPreferences; - } -} diff --git a/src/main/java/org/jabref/gui/ClipBoardManager.java b/src/main/java/org/jabref/gui/ClipBoardManager.java index 78f9e7aa39a..d78183a3479 100644 --- a/src/main/java/org/jabref/gui/ClipBoardManager.java +++ b/src/main/java/org/jabref/gui/ClipBoardManager.java @@ -73,7 +73,7 @@ public static void addX11Support(TextInputControl input) { // using InvalidationListener because of https://bugs.openjdk.java.net/browse/JDK-8176270 observable -> Platform.runLater(() -> { String newValue = input.getSelectedText(); - if (!newValue.isEmpty() && primary != null) { + if (!newValue.isEmpty() && (primary != null)) { primary.setContents(new StringSelection(newValue), null); } })); @@ -105,7 +105,7 @@ public static String getContents() { public static String getContentsPrimary() { if (primary != null) { Transferable contents = primary.getContents(null); - if (contents != null && contents.isDataFlavorSupported(DataFlavor.stringFlavor)) { + if ((contents != null) && contents.isDataFlavorSupported(DataFlavor.stringFlavor)) { try { return (String) contents.getTransferData(DataFlavor.stringFlavor); } catch (UnsupportedFlavorException | IOException e) { @@ -137,9 +137,10 @@ public void setPrimaryClipboardContent(ClipboardContent content) { } } - public void setHtmlContent(String html) { + public void setHtmlContent(String html, String fallbackPlain) { final ClipboardContent content = new ClipboardContent(); content.putHtml(html); + content.putString(fallbackPlain); clipboard.setContent(content); setPrimaryClipboardContent(content); } @@ -181,7 +182,7 @@ private List handleBibTeXData(String entries) { } private List handleStringData(String data) { - if (data == null || data.isEmpty()) { + if ((data == null) || data.isEmpty()) { return Collections.emptyList(); } diff --git a/src/main/java/org/jabref/gui/DefaultInjector.java b/src/main/java/org/jabref/gui/DefaultInjector.java index d701324aa9b..7d590c43688 100644 --- a/src/main/java/org/jabref/gui/DefaultInjector.java +++ b/src/main/java/org/jabref/gui/DefaultInjector.java @@ -9,6 +9,7 @@ import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.logic.protectedterms.ProtectedTermsLoader; import org.jabref.logic.sharelatex.ShareLatexManager; +import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.PreferencesService; @@ -49,6 +50,8 @@ private static Object createDependency(Class clazz) { return Globals.clipboardManager; } else if (clazz == UndoManager.class) { return Globals.undoManager; + } else if (clazz == BibEntryTypesManager.class) { + return Globals.entryTypesManager; } else { try { return clazz.newInstance(); diff --git a/src/main/java/org/jabref/gui/EntryTypeView.java b/src/main/java/org/jabref/gui/EntryTypeView.java index f304238b688..83d7baa191c 100644 --- a/src/main/java/org/jabref/gui/EntryTypeView.java +++ b/src/main/java/org/jabref/gui/EntryTypeView.java @@ -23,6 +23,7 @@ import org.jabref.gui.util.IconValidationDecorator; import org.jabref.gui.util.ViewModelListCellFactory; import org.jabref.logic.importer.IdBasedFetcher; +import org.jabref.logic.importer.WebFetcher; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntryType; @@ -60,7 +61,7 @@ public class EntryTypeView extends BaseDialog { @FXML private TitledPane customTitlePane; @FXML private TitledPane biblatexSoftwareTitlePane; - private final BasePanel basePanel; + private final LibraryTab libraryTab; private final DialogService dialogService; private final JabRefPreferences prefs; @@ -68,8 +69,8 @@ public class EntryTypeView extends BaseDialog { private EntryTypeViewModel viewModel; private final ControlsFxVisualizer visualizer = new ControlsFxVisualizer(); - public EntryTypeView(BasePanel basePanel, DialogService dialogService, JabRefPreferences preferences) { - this.basePanel = basePanel; + public EntryTypeView(LibraryTab libraryTab, DialogService dialogService, JabRefPreferences preferences) { + this.libraryTab = libraryTab; this.dialogService = dialogService; this.prefs = preferences; @@ -120,7 +121,7 @@ private void addEntriesToPane(FlowPane pane, Collection @FXML public void initialize() { visualizer.setDecoration(new IconValidationDecorator()); - viewModel = new EntryTypeViewModel(prefs, basePanel, dialogService, stateManager); + viewModel = new EntryTypeViewModel(prefs, libraryTab, dialogService, stateManager); idBasedFetchers.itemsProperty().bind(viewModel.fetcherItemsProperty()); idTextField.textProperty().bindBidirectional(viewModel.idTextProperty()); @@ -133,7 +134,7 @@ public void initialize() { } }); - new ViewModelListCellFactory().withText(item -> item.getName()).install(idBasedFetchers); + new ViewModelListCellFactory().withText(WebFetcher::getName).install(idBasedFetchers); // we set the managed property so that they will only be rendered when they are visble so that the Nodes only take the space when visible // avoids removing and adding from the scence graph @@ -143,7 +144,7 @@ public void initialize() { customTitlePane.managedProperty().bind(customTitlePane.visibleProperty()); biblatexSoftwareTitlePane.managedProperty().bind(biblatexSoftwareTitlePane.visibleProperty()); - if (basePanel.getBibDatabaseContext().isBiblatexMode()) { + if (libraryTab.getBibDatabaseContext().isBiblatexMode()) { addEntriesToPane(biblatexPane, BiblatexEntryTypeDefinitions.ALL); addEntriesToPane(biblatexSoftwarePane, BiblatexSoftwareEntryTypeDefinitions.ALL); diff --git a/src/main/java/org/jabref/gui/EntryTypeViewModel.java b/src/main/java/org/jabref/gui/EntryTypeViewModel.java index 926ad996e08..21f60791931 100644 --- a/src/main/java/org/jabref/gui/EntryTypeViewModel.java +++ b/src/main/java/org/jabref/gui/EntryTypeViewModel.java @@ -46,13 +46,13 @@ public class EntryTypeViewModel { private final StringProperty idText = new SimpleStringProperty(); private final BooleanProperty focusAndSelectAllProperty = new SimpleBooleanProperty(); private Task> fetcherWorker = new FetcherWorker(); - private final BasePanel basePanel; + private final LibraryTab libraryTab; private final DialogService dialogService; private final Validator idFieldValidator; private final StateManager stateManager; - public EntryTypeViewModel(JabRefPreferences preferences, BasePanel basePanel, DialogService dialogService, StateManager stateManager) { - this.basePanel = basePanel; + public EntryTypeViewModel(JabRefPreferences preferences, LibraryTab libraryTab, DialogService dialogService, StateManager stateManager) { + this.libraryTab = libraryTab; this.prefs = preferences; this.dialogService = dialogService; this.stateManager = stateManager; @@ -147,22 +147,22 @@ public void runFetcherWorker() { Optional result = fetcherWorker.getValue(); if (result.isPresent()) { final BibEntry entry = result.get(); - ImportCleanup cleanup = new ImportCleanup(basePanel.getBibDatabaseContext().getMode()); + ImportCleanup cleanup = new ImportCleanup(libraryTab.getBibDatabaseContext().getMode()); cleanup.doPostCleanup(entry); - Optional duplicate = new DuplicateCheck(Globals.entryTypesManager).containsDuplicate(basePanel.getDatabase(), entry, basePanel.getBibDatabaseContext().getMode()); + Optional duplicate = new DuplicateCheck(Globals.entryTypesManager).containsDuplicate(libraryTab.getDatabase(), entry, libraryTab.getBibDatabaseContext().getMode()); if ((duplicate.isPresent())) { - DuplicateResolverDialog dialog = new DuplicateResolverDialog(entry, duplicate.get(), DuplicateResolverDialog.DuplicateResolverType.IMPORT_CHECK, basePanel.getBibDatabaseContext(), stateManager); + DuplicateResolverDialog dialog = new DuplicateResolverDialog(entry, duplicate.get(), DuplicateResolverDialog.DuplicateResolverType.IMPORT_CHECK, libraryTab.getBibDatabaseContext(), stateManager); switch (dialog.showAndWait().orElse(DuplicateResolverDialog.DuplicateResolverResult.BREAK)) { case KEEP_LEFT: - basePanel.getDatabase().removeEntry(duplicate.get()); - basePanel.getDatabase().insertEntry(entry); + libraryTab.getDatabase().removeEntry(duplicate.get()); + libraryTab.getDatabase().insertEntry(entry); break; case KEEP_BOTH: - basePanel.getDatabase().insertEntry(entry); + libraryTab.getDatabase().insertEntry(entry); break; case KEEP_MERGE: - basePanel.getDatabase().removeEntry(duplicate.get()); - basePanel.getDatabase().insertEntry(dialog.getMergedEntry()); + libraryTab.getDatabase().removeEntry(duplicate.get()); + libraryTab.getDatabase().insertEntry(dialog.getMergedEntry()); break; default: // Do nothing @@ -170,12 +170,16 @@ public void runFetcherWorker() { } } else { // Regenerate CiteKey of imported BibEntry - new CitationKeyGenerator(basePanel.getBibDatabaseContext(), prefs.getCitationKeyPatternPreferences()).generateAndSetKey(entry); - basePanel.insertEntry(entry); + new CitationKeyGenerator(libraryTab.getBibDatabaseContext(), prefs.getCitationKeyPatternPreferences()).generateAndSetKey(entry); + libraryTab.insertEntry(entry); } searchSuccesfulProperty.set(true); } else if (StringUtil.isBlank(idText.getValue())) { dialogService.showWarningDialogAndWait(Localization.lang("Empty search ID"), Localization.lang("The given search ID was empty.")); + } else if (result.isEmpty()) { + String fetcher = selectedItemProperty().getValue().getName(); + String searchId = idText.getValue(); + dialogService.showErrorDialogAndWait(Localization.lang("Fetcher '%0' did not find an entry for id '%1'.", fetcher, searchId)); } fetcherWorker = new FetcherWorker(); diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index d2fc140e5e2..726e15fdbd8 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -1,6 +1,5 @@ package org.jabref.gui; -import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; @@ -11,8 +10,11 @@ import java.util.Objects; import java.util.Optional; import java.util.TimerTask; +import java.util.stream.Collectors; import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.StringBinding; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.concurrent.Task; @@ -82,7 +84,6 @@ import org.jabref.gui.externalfiles.AutoLinkFilesAction; import org.jabref.gui.externalfiles.DownloadFullTextAction; import org.jabref.gui.externalfiles.FindUnlinkedFilesAction; -import org.jabref.gui.externalfiletype.EditExternalFileTypesAction; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.help.AboutAction; import org.jabref.gui.help.ErrorConsoleAction; @@ -133,19 +134,18 @@ import org.jabref.logic.undo.UndoChangeEvent; import org.jabref.logic.undo.UndoRedoEvent; import org.jabref.logic.util.OS; -import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.SpecialField; -import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.StandardEntryType; import org.jabref.preferences.JabRefPreferences; import org.jabref.preferences.LastFocusedTabPreferences; import com.google.common.eventbus.Subscribe; import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.EasyObservableList; import org.controlsfx.control.PopOver; import org.controlsfx.control.TaskProgressView; +import org.fxmisc.richtext.CodeArea; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -154,7 +154,6 @@ */ public class JabRefFrame extends BorderPane { - // Frame titles. public static final String FRAME_TITLE = "JabRef"; private static final Logger LOGGER = LoggerFactory.getLogger(JabRefFrame.class); @@ -165,12 +164,13 @@ public class JabRefFrame extends BorderPane { private final FileHistoryMenu fileHistory; + @SuppressWarnings({"FieldCanBeLocal"}) private EasyObservableList openDatabaseList; + private final Stage mainStage; private final StateManager stateManager; private final CountingUndoManager undoManager; private final PushToApplicationsManager pushToApplicationsManager; private final DialogService dialogService; - private final JabRefExecutorService executorService; private SidePaneManager sidePaneManager; private TabPane tabbedPane; private SidePane sidePane; @@ -183,7 +183,6 @@ public JabRefFrame(Stage mainStage) { this.pushToApplicationsManager = new PushToApplicationsManager(dialogService, stateManager, prefs); this.undoManager = Globals.undoManager; this.fileHistory = new FileHistoryMenu(prefs, dialogService, getOpenDatabaseAction()); - this.executorService = JabRefExecutorService.INSTANCE; this.setOnKeyTyped(key -> { if (this.fileHistory.isShowing()) { if (this.fileHistory.openFileByKey(key)) { @@ -193,10 +192,6 @@ public JabRefFrame(Stage mainStage) { }); } - private static BasePanel getBasePanel(Tab tab) { - return (BasePanel) tab.getContent(); - } - private void initDragAndDrop() { Tab dndIndicator = new Tab(Localization.lang("Open files..."), null); dndIndicator.getStyleClass().add("drop"); @@ -237,7 +232,7 @@ private void initKeyBindings() { if (keyBinding.isPresent()) { switch (keyBinding.get()) { case FOCUS_ENTRY_TABLE: - getCurrentBasePanel().getMainTable().requestFocus(); + getCurrentLibraryTab().getMainTable().requestFocus(); event.consume(); break; case NEXT_LIBRARY: @@ -275,9 +270,15 @@ private void initKeyBindings() { case NEW_UNPUBLISHED: new NewEntryAction(this, StandardEntryType.Unpublished, dialogService, prefs, stateManager).execute(); break; + case NEW_INPROCEEDINGS: + new NewEntryAction(this, StandardEntryType.InProceedings, dialogService, prefs, stateManager).execute(); + break; case PASTE: if (OS.OS_X) { // Workaround for a jdk issue that executes paste twice when using cmd+v in a TextField - event.consume(); + // Extra workaround for CodeArea, which does not inherit from TextInputControl + if (!(stateManager.getFocusOwner().isPresent() && (stateManager.getFocusOwner().get() instanceof CodeArea))) { + event.consume(); + } break; } default: @@ -313,43 +314,6 @@ private Void showTrackingNotification() { return null; } - public void refreshTitleAndTabs() { - DefaultTaskExecutor.runInJavaFXThread(() -> { - - setWindowTitle(); - updateAllTabTitles(); - }); - } - - /** - * Sets the title of the main window. - */ - public void setWindowTitle() { - BasePanel panel = getCurrentBasePanel(); - - // no database open - if (panel == null) { - // setTitle(FRAME_TITLE); - return; - } - - String mode = panel.getBibDatabaseContext().getMode().getFormattedName(); - String modeInfo = String.format(" (%s)", Localization.lang("%0 mode", mode)); - boolean isAutosaveEnabled = Globals.prefs.getBoolean(JabRefPreferences.LOCAL_AUTO_SAVE); - - if (panel.getBibDatabaseContext().getLocation() == DatabaseLocation.LOCAL) { - String changeFlag = panel.isModified() && !isAutosaveEnabled ? "*" : ""; - String databaseFile = panel.getBibDatabaseContext() - .getDatabasePath() - .map(Path::toString) - .orElse(Localization.lang("untitled")); - // setTitle(FRAME_TITLE + " - " + databaseFile + changeFlag + modeInfo); - } else if (panel.getBibDatabaseContext().getLocation() == DatabaseLocation.SHARED) { - // setTitle(FRAME_TITLE + " - " + panel.getBibDatabaseContext().getDBMSSynchronizer().getDBName() + " [" - // + Localization.lang("shared") + "]" + modeInfo); - } - } - /** * The MacAdapter calls this method when a "BIB" file has been double-clicked from the Finder. */ @@ -389,7 +353,7 @@ private void tearDownJabRef(List filenames) { prefs.remove(JabRefPreferences.LAST_EDITED); } else { prefs.putStringList(JabRefPreferences.LAST_EDITED, filenames); - Path focusedDatabase = getCurrentBasePanel().getBibDatabaseContext().getDatabasePath().orElse(null); + Path focusedDatabase = getCurrentLibraryTab().getBibDatabaseContext().getDatabasePath().orElse(null); new LastFocusedTabPreferences(prefs).setLastFocusedTab(focusedDatabase); } } @@ -429,12 +393,12 @@ public boolean quit() { // Then ask if the user really wants to close, if the library has not been saved since last save. List filenames = new ArrayList<>(); for (int i = 0; i < tabbedPane.getTabs().size(); i++) { - BasePanel panel = getBasePanelAt(i); - final BibDatabaseContext context = panel.getBibDatabaseContext(); + LibraryTab libraryTab = getLibraryTabAt(i); + final BibDatabaseContext context = libraryTab.getBibDatabaseContext(); - if (panel.isModified() && (context.getLocation() == DatabaseLocation.LOCAL)) { + if (libraryTab.isModified() && (context.getLocation() == DatabaseLocation.LOCAL)) { tabbedPane.getSelectionModel().select(i); - if (!confirmClose(panel)) { + if (!confirmClose(libraryTab)) { return false; } } else if (context.getLocation() == DatabaseLocation.SHARED) { @@ -448,7 +412,7 @@ public boolean quit() { } WaitForSaveFinishedDialog waitForSaveFinishedDialog = new WaitForSaveFinishedDialog(dialogService); - waitForSaveFinishedDialog.showAndWait(getBasePanelList()); + waitForSaveFinishedDialog.showAndWait(getLibraryTabs()); // Good bye! tearDownJabRef(filenames); @@ -577,31 +541,29 @@ private Node createToolbar() { } /** - * Returns the indexed BasePanel. + * Returns the indexed LibraryTab. * * @param i Index of base */ - public BasePanel getBasePanelAt(int i) { - return (BasePanel) tabbedPane.getTabs().get(i).getContent(); + public LibraryTab getLibraryTabAt(int i) { + return (LibraryTab) tabbedPane.getTabs().get(i); } /** - * Returns a list of BasePanel. + * Returns a list of all LibraryTabs in this frame. */ - public List getBasePanelList() { - List returnList = new ArrayList<>(); - for (int i = 0; i < getBasePanelCount(); i++) { - returnList.add(getBasePanelAt(i)); - } - return returnList; + public List getLibraryTabs() { + return tabbedPane.getTabs().stream() + .map(tab -> (LibraryTab) tab) + .collect(Collectors.toList()); } - public void showBasePanelAt(int i) { + public void showLibraryTabAt(int i) { tabbedPane.getSelectionModel().select(i); } - public void showBasePanel(BasePanel bp) { - tabbedPane.getSelectionModel().select(getTab(bp)); + public void showLibraryTab(LibraryTab libraryTab) { + tabbedPane.getSelectionModel().select(libraryTab); } public void init() { @@ -612,26 +574,26 @@ public void init() { tabbedPane.setTabDragPolicy(TabPane.TabDragPolicy.REORDER); initLayout(); - initKeyBindings(); - initDragAndDrop(); - // setBounds(GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds()); - // WindowLocation pw = new WindowLocation(this, JabRefPreferences.POS_X, JabRefPreferences.POS_Y, JabRefPreferences.SIZE_X, - // JabRefPreferences.SIZE_Y); - // pw.displayWindowAtStoredLocation(); - // Bind global state + + // This variable cannot be inlined, since otherwise the list created by EasyBind is being garbage collected + openDatabaseList = EasyBind.map(tabbedPane.getTabs(), tab -> ((LibraryTab) tab).getBibDatabaseContext()); + EasyBind.bindContent(stateManager.getOpenDatabases(), openDatabaseList); + stateManager.activeDatabaseProperty().bind( EasyBind.map(tabbedPane.getSelectionModel().selectedItemProperty(), - tab -> Optional.ofNullable(tab).map(JabRefFrame::getBasePanel).map(BasePanel::getBibDatabaseContext))); + selectedTab -> Optional.ofNullable(selectedTab) + .map(tab -> (LibraryTab) tab) + .map(LibraryTab::getBibDatabaseContext))); // Subscribe to the search EasyBind.subscribe(stateManager.activeSearchQueryProperty(), query -> { - if (getCurrentBasePanel() != null) { - getCurrentBasePanel().setCurrentSearchQuery(query); + if (getCurrentLibraryTab() != null) { + getCurrentLibraryTab().setCurrentSearchQuery(query); } }); @@ -646,29 +608,30 @@ public void init() { EasyBind.subscribe(tabbedPane.getSelectionModel().selectedItemProperty(), tab -> { if (tab == null) { stateManager.setSelectedEntries(Collections.emptyList()); + mainStage.titleProperty().unbind(); + mainStage.setTitle(FRAME_TITLE); return; } - BasePanel newBasePanel = getBasePanel(tab); - if (newBasePanel != null) { - // Poor-mans binding to global state - stateManager.setSelectedEntries(newBasePanel.getSelectedEntries()); + LibraryTab libraryTab = (LibraryTab) tab; - // Update active search query when switching between databases - stateManager.activeSearchQueryProperty().set(newBasePanel.getCurrentSearchQuery()); + // Poor-mans binding to global state + stateManager.setSelectedEntries(libraryTab.getSelectedEntries()); - // groupSidePane.getToggleCommand().setSelected(sidePaneManager.isComponentVisible(GroupSidePane.class)); - // previewToggle.setSelected(Globals.prefs.getPreviewPreferences().isPreviewPanelEnabled()); - // generalFetcher.getToggleCommand().setSelected(sidePaneManager.isComponentVisible(WebSearchPane.class)); - // openOfficePanel.getToggleCommand().setSelected(sidePaneManager.isComponentVisible(OpenOfficeSidePanel.class)); + // Update active search query when switching between databases + stateManager.activeSearchQueryProperty().set(libraryTab.getCurrentSearchQuery()); - setWindowTitle(); - // Update search autocompleter with information for the correct database: - newBasePanel.updateSearchManager(); + // Update search autocompleter with information for the correct database: + libraryTab.updateSearchManager(); - newBasePanel.getUndoManager().postUndoRedoEvent(); - newBasePanel.getMainTable().requestFocus(); - } + libraryTab.getUndoManager().postUndoRedoEvent(); + libraryTab.getMainTable().requestFocus(); + + // Set window title - copy tab title + StringBinding windowTitle = Bindings.createStringBinding( + () -> libraryTab.textProperty().getValue() + " \u2013 " + FRAME_TITLE, + libraryTab.textProperty()); + mainStage.titleProperty().bind(windowTitle); }); initShowTrackingNotification(); } @@ -676,11 +639,11 @@ public void init() { /** * Returns the currently viewed BasePanel. */ - public BasePanel getCurrentBasePanel() { + public LibraryTab getCurrentLibraryTab() { if ((tabbedPane == null) || (tabbedPane.getSelectionModel().getSelectedItem() == null)) { return null; } - return getBasePanel(tabbedPane.getSelectionModel().getSelectedItem()); + return (LibraryTab) tabbedPane.getSelectionModel().getSelectedItem(); } /** @@ -690,15 +653,6 @@ public int getBasePanelCount() { return tabbedPane.getTabs().size(); } - private Tab getTab(BasePanel comp) { - for (Tab tab : tabbedPane.getTabs()) { - if (tab.getContent() == comp) { - return tab; - } - } - return null; - } - /** * @deprecated do not operate on tabs but on BibDatabaseContexts */ @@ -707,14 +661,6 @@ public TabPane getTabbedPane() { return tabbedPane; } - public void setTabTitle(BasePanel comp, String title, String toolTip) { - DefaultTaskExecutor.runInJavaFXThread(() -> { - Tab tab = getTab(comp); - tab.setText(title); - tab.setTooltip(new Tooltip(toolTip)); - }); - } - private MenuBar createMenu() { ActionFactory factory = new ActionFactory(Globals.getKeyPrefs()); Menu file = new Menu(Localization.lang("File")); @@ -796,18 +742,18 @@ private MenuBar createMenu() { new SeparatorMenuItem(), // ToDo: SpecialField needs the active BasePanel to mark it as changed. // Refactor BasePanel, should mark the BibDatabaseContext or the UndoManager as dirty instead! - SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.RANKING, factory, this, dialogService, stateManager), - SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.RELEVANCE, factory, this, dialogService, stateManager), - SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.QUALITY, factory, this, dialogService, stateManager), - SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.PRINTED, factory, this, dialogService, stateManager), - SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.PRIORITY, factory, this, dialogService, stateManager), - SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.READ_STATUS, factory, this, dialogService, stateManager) + SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.RANKING, factory, this, dialogService, prefs, undoManager, stateManager), + SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.RELEVANCE, factory, this, dialogService, prefs, undoManager, stateManager), + SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.QUALITY, factory, this, dialogService, prefs, undoManager, stateManager), + SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.PRINTED, factory, this, dialogService, prefs, undoManager, stateManager), + SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.PRIORITY, factory, this, dialogService, prefs, undoManager, stateManager), + SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.READ_STATUS, factory, this, dialogService, prefs, undoManager, stateManager) ); } // @formatter:off library.getItems().addAll( - factory.createMenuItem(StandardActions.NEW_ENTRY, new NewEntryAction(this, dialogService, Globals.prefs, stateManager)), + factory.createMenuItem(StandardActions.NEW_ENTRY, new NewEntryAction(this, dialogService, prefs, stateManager)), factory.createMenuItem(StandardActions.NEW_ENTRY_FROM_PLAIN_TEXT, new ExtractBibtexAction(stateManager)), factory.createMenuItem(StandardActions.DELETE_ENTRY, new EditAction(StandardActions.DELETE_ENTRY, this, stateManager)), @@ -907,7 +853,6 @@ private MenuBar createMenu() { factory.createMenuItem(StandardActions.SETUP_GENERAL_FIELDS, new SetupGeneralFieldsAction()), factory.createMenuItem(StandardActions.MANAGE_CUSTOM_IMPORTS, new ManageCustomImportsAction()), factory.createMenuItem(StandardActions.MANAGE_CUSTOM_EXPORTS, new ManageCustomExportsAction()), - factory.createMenuItem(StandardActions.MANAGE_EXTERNAL_FILETYPES, new EditExternalFileTypesAction()), factory.createMenuItem(StandardActions.MANAGE_JOURNALS, new ManageJournalsAction()), factory.createMenuItem(StandardActions.CUSTOMIZE_KEYBINDING, new CustomizeKeyBindingAction()), factory.createMenuItem(StandardActions.MANAGE_PROTECTED_TERMS, new ManageProtectedTermsAction()), @@ -1022,21 +967,23 @@ hide it and clip it to a square of (width x width) each time width is updated. public void addParserResult(ParserResult parserResult, boolean focusPanel) { if (parserResult.toOpenTab()) { // Add the entries to the open tab. - BasePanel panel = getCurrentBasePanel(); - if (panel == null) { + LibraryTab libraryTab = getCurrentLibraryTab(); + if (libraryTab == null) { // There is no open tab to add to, so we create a new tab: addTab(parserResult.getDatabaseContext(), focusPanel); } else { - addImportedEntries(panel, parserResult); + addImportedEntries(libraryTab, parserResult); } } else { // only add tab if DB is not already open - Optional panel = getBasePanelList().stream() - .filter(p -> p.getBibDatabaseContext().getDatabasePath().equals(parserResult.getPath())) - .findFirst(); - - if (panel.isPresent()) { - tabbedPane.getSelectionModel().select(getTab(panel.get())); + Optional libraryTab = getLibraryTabs().stream() + .filter(p -> p.getBibDatabaseContext() + .getDatabasePath() + .equals(parserResult.getPath())) + .findFirst(); + + if (libraryTab.isPresent()) { + tabbedPane.getSelectionModel().select(libraryTab.get()); } else { addTab(parserResult.getDatabaseContext(), focusPanel); } @@ -1044,66 +991,17 @@ public void addParserResult(ParserResult parserResult, boolean focusPanel) { } /** - * This method causes all open BasePanels to set up their tables anew. When called from PrefsDialog3, this updates - * to the new settings. - */ + * This method causes all open LibraryTabs to set up their tables anew. When called from PreferencesDialogViewModel, + * this updates to the new settings. + * We need to notify all tabs about the changes to avoid problems when changing the column set. + * */ public void setupAllTables() { - // This action can be invoked without an open database, so - // we have to check if we have one before trying to invoke - // methods to execute changes in the preferences. - - // We want to notify all tabs about the changes to - // avoid problems when changing the column set. - for (int i = 0; i < tabbedPane.getTabs().size(); i++) { - BasePanel bf = getBasePanelAt(i); - - // Update tables: - if (bf.getDatabase() != null) { - DefaultTaskExecutor.runInJavaFXThread(bf::setupMainPanel); - } - } - } - - private List collectDatabaseFilePaths() { - List dbPaths = new ArrayList<>(getBasePanelCount()); - - for (BasePanel basePanel : getBasePanelList()) { - // db file exists - if (basePanel.getBibDatabaseContext().getDatabasePath().isPresent()) { - dbPaths.add(basePanel.getBibDatabaseContext().getDatabasePath().get().toAbsolutePath().toString()); - } else { - dbPaths.add(""); - } - } - return dbPaths; - } - - private List getUniquePathParts() { - List dbPaths = collectDatabaseFilePaths(); - - return FileUtil.uniquePathSubstrings(dbPaths); - } - - public void updateAllTabTitles() { - List paths = getUniquePathParts(); - for (int i = 0; i < getBasePanelCount(); i++) { - String uniqPath = paths.get(i); - Optional file = getBasePanelAt(i).getBibDatabaseContext().getDatabasePath(); - - if (file.isPresent()) { - if (!uniqPath.equals(file.get().getFileName().toString()) && uniqPath.contains(File.separator)) { - // remove filename - uniqPath = uniqPath.substring(0, uniqPath.lastIndexOf(File.separator)); - tabbedPane.getTabs().get(i).setText(getBasePanelAt(i).getTabTitle() + " \u2014 " + uniqPath); - } else { - // set original filename (again) - tabbedPane.getTabs().get(i).setText(getBasePanelAt(i).getTabTitle()); - } - } else { - tabbedPane.getTabs().get(i).setText(getBasePanelAt(i).getTabTitle()); + tabbedPane.getTabs().forEach(tab -> { + LibraryTab libraryTab = (LibraryTab) tab; + if (libraryTab.getDatabase() != null) { + DefaultTaskExecutor.runInJavaFXThread(libraryTab::setupMainPanel); } - tabbedPane.getTabs().get(i).setTooltip(new Tooltip(file.map(Path::toAbsolutePath).map(Path::toString).orElse(null))); - } + }); } private ContextMenu createTabContextMenu(KeyBindingRepository keyBindingRepository) { @@ -1122,55 +1020,48 @@ private ContextMenu createTabContextMenu(KeyBindingRepository keyBindingReposito return contextMenu; } - public void addTab(BasePanel basePanel, boolean raisePanel) { - // add tab - Tab newTab = new Tab(basePanel.getTabTitle(), basePanel); - tabbedPane.getTabs().add(newTab); - newTab.setOnCloseRequest(event -> { - closeTab((BasePanel) newTab.getContent()); + public void addTab(LibraryTab libraryTab, boolean raisePanel) { + tabbedPane.getTabs().add(libraryTab); + + libraryTab.setOnCloseRequest(event -> { + closeTab(libraryTab); event.consume(); }); - // add tab context menu - newTab.setContextMenu(createTabContextMenu(Globals.getKeyPrefs())); - - // update all tab titles - updateAllTabTitles(); + libraryTab.setContextMenu(createTabContextMenu(Globals.getKeyPrefs())); if (raisePanel) { - tabbedPane.getSelectionModel().select(newTab); + tabbedPane.getSelectionModel().select(libraryTab); } - // Register undo/redo listener - basePanel.getUndoManager().registerListener(new UndoRedoEventManager()); + libraryTab.getUndoManager().registerListener(new UndoRedoEventManager()); - BibDatabaseContext context = basePanel.getBibDatabaseContext(); + BibDatabaseContext context = libraryTab.getBibDatabaseContext(); if (readyForAutosave(context)) { AutosaveManager autosaver = AutosaveManager.start(context); - autosaver.registerListener(new AutosaveUiManager(basePanel)); + autosaver.registerListener(new AutosaveUiManager(libraryTab)); } BackupManager.start(context, Globals.entryTypesManager, prefs); - // Track opening - trackOpenNewDatabase(basePanel); + trackOpenNewDatabase(libraryTab); } - private void trackOpenNewDatabase(BasePanel basePanel) { + private void trackOpenNewDatabase(LibraryTab libraryTab) { Map properties = new HashMap<>(); Map measurements = new HashMap<>(); - measurements.put("NumberOfEntries", (double) basePanel.getBibDatabaseContext().getDatabase().getEntryCount()); + measurements.put("NumberOfEntries", (double) libraryTab.getBibDatabaseContext().getDatabase().getEntryCount()); Globals.getTelemetryClient().ifPresent(client -> client.trackEvent("OpenNewDatabase", properties, measurements)); } - public BasePanel addTab(BibDatabaseContext databaseContext, boolean raisePanel) { + public LibraryTab addTab(BibDatabaseContext databaseContext, boolean raisePanel) { Objects.requireNonNull(databaseContext); - BasePanel bp = new BasePanel(this, BasePanelPreferences.from(Globals.prefs), databaseContext, ExternalFileTypes.getInstance()); - addTab(bp, raisePanel); - return bp; + LibraryTab libraryTab = new LibraryTab(this, prefs, databaseContext, ExternalFileTypes.getInstance()); + addTab(libraryTab, raisePanel); + return libraryTab; } private boolean readyForAutosave(BibDatabaseContext context) { @@ -1186,7 +1077,7 @@ private boolean readyForAutosave(BibDatabaseContext context) { * @param panel The BasePanel to add to. * @param parserResult The entries to add. */ - private void addImportedEntries(final BasePanel panel, final ParserResult parserResult) { + private void addImportedEntries(final LibraryTab panel, final ParserResult parserResult) { BackgroundTask task = BackgroundTask.wrap(() -> parserResult); ImportCleanup cleanup = new ImportCleanup(panel.getBibDatabaseContext().getMode()); cleanup.doPostCleanup(parserResult.getDatabase().getEntries()); @@ -1199,47 +1090,17 @@ public FileHistoryMenu getFileHistory() { return fileHistory; } - /** - * Return a boolean, if the selected entry have file - * - * @param selectEntryList A selected entries list of the current base pane - * @return true, if the selected entry contains file. false, if multiple entries are selected or the selected entry - * doesn't contains file - */ - private boolean isExistFile(List selectEntryList) { - if (selectEntryList.size() == 1) { - BibEntry selectedEntry = selectEntryList.get(0); - return selectedEntry.getField(StandardField.FILE).isPresent(); - } - return false; - } - - /** - * Return a boolean, if the selected entry have url or doi - * - * @param selectEntryList A selected entries list of the current base pane - * @return true, if the selected entry contains url or doi. false, if multiple entries are selected or the selected - * entry doesn't contains url or doi - */ - private boolean isExistURLorDOI(List selectEntryList) { - if (selectEntryList.size() == 1) { - BibEntry selectedEntry = selectEntryList.get(0); - return (selectedEntry.getField(StandardField.URL).isPresent() || selectedEntry.getField(StandardField.DOI).isPresent()); - } - return false; - } - /** * Ask if the user really wants to close the given database * * @return true if the user choose to close the database */ - private boolean confirmClose(BasePanel panel) { - String filename = panel.getBibDatabaseContext() - .getDatabasePath() - .map(Path::toAbsolutePath) - .map(Path::toString) - .orElse(Localization.lang("untitled")); + private boolean confirmClose(LibraryTab libraryTab) { + String filename = libraryTab.getBibDatabaseContext() + .getDatabasePath() + .map(Path::toAbsolutePath) + .map(Path::toString) + .orElse(Localization.lang("untitled")); ButtonType saveChanges = new ButtonType(Localization.lang("Save changes"), ButtonBar.ButtonData.YES); ButtonType discardChanges = new ButtonType(Localization.lang("Discard changes"), ButtonBar.ButtonData.NO); @@ -1253,7 +1114,7 @@ private boolean confirmClose(BasePanel panel) { if (response.isPresent() && response.get().equals(saveChanges)) { // The user wants to save. try { - SaveDatabaseAction saveAction = new SaveDatabaseAction(panel, Globals.prefs, Globals.entryTypesManager); + SaveDatabaseAction saveAction = new SaveDatabaseAction(libraryTab, Globals.prefs, Globals.entryTypesManager); if (saveAction.save()) { return true; } @@ -1269,17 +1130,17 @@ private boolean confirmClose(BasePanel panel) { return response.isEmpty() || !response.get().equals(cancel); } - private void closeTab(BasePanel panel) { + private void closeTab(LibraryTab libraryTab) { // empty tab without database - if (panel == null) { + if (libraryTab == null) { return; } - final BibDatabaseContext context = panel.getBibDatabaseContext(); + final BibDatabaseContext context = libraryTab.getBibDatabaseContext(); - if (panel.isModified() && (context.getLocation() == DatabaseLocation.LOCAL)) { - if (confirmClose(panel)) { - removeTab(panel); + if (libraryTab.isModified() && (context.getLocation() == DatabaseLocation.LOCAL)) { + if (confirmClose(libraryTab)) { + removeTab(libraryTab); } else { return; } @@ -1287,26 +1148,23 @@ private void closeTab(BasePanel panel) { context.convertToLocalDatabase(); context.getDBMSSynchronizer().closeSharedDatabase(); context.clearDBMSSynchronizer(); - removeTab(panel); + removeTab(libraryTab); } else { - removeTab(panel); + removeTab(libraryTab); } AutosaveManager.shutdown(context); BackupManager.shutdown(context); } - private void removeTab(BasePanel panel) { + private void removeTab(LibraryTab libraryTab) { DefaultTaskExecutor.runInJavaFXThread(() -> { - panel.cleanUp(); - tabbedPane.getTabs().remove(getTab(panel)); - setWindowTitle(); - // update tab titles - updateAllTabTitles(); + libraryTab.cleanUp(); + tabbedPane.getTabs().remove(libraryTab); }); } public void closeCurrentTab() { - removeTab(getCurrentBasePanel()); + removeTab(getCurrentLibraryTab()); } public OpenDatabaseAction getOpenDatabaseAction() { @@ -1348,7 +1206,7 @@ private class CloseDatabaseAction extends SimpleCommand { @Override public void execute() { - closeTab(getCurrentBasePanel()); + closeTab(getCurrentLibraryTab()); } } @@ -1360,11 +1218,11 @@ public CloseOthersDatabaseAction() { @Override public void execute() { - BasePanel currentBasePanel = getCurrentBasePanel(); + LibraryTab currentLibraryTab = getCurrentLibraryTab(); for (Tab tab : tabbedPane.getTabs()) { - BasePanel basePanel = getBasePanel(tab); - if (basePanel != currentBasePanel) { - closeTab(basePanel); + LibraryTab libraryTab = (LibraryTab) tab; + if (libraryTab != currentLibraryTab) { + closeTab(libraryTab); } } } @@ -1375,8 +1233,7 @@ private class CloseAllDatabaseAction extends SimpleCommand { @Override public void execute() { for (Tab tab : tabbedPane.getTabs()) { - BasePanel basePanel = getBasePanel(tab); - closeTab(basePanel); + closeTab((LibraryTab) tab); } } } @@ -1400,7 +1257,7 @@ private class UndoRedoEventManager { @Subscribe public void listen(UndoRedoEvent event) { updateTexts(event); - JabRefFrame.this.getCurrentBasePanel().updateEntryEditorIfShowing(); + JabRefFrame.this.getCurrentLibraryTab().updateEntryEditorIfShowing(); } @Subscribe diff --git a/src/main/java/org/jabref/gui/JabRefGUI.java b/src/main/java/org/jabref/gui/JabRefGUI.java index 4cdbb7850fa..a4dc5d1b284 100644 --- a/src/main/java/org/jabref/gui/JabRefGUI.java +++ b/src/main/java/org/jabref/gui/JabRefGUI.java @@ -8,6 +8,7 @@ import javafx.application.Platform; import javafx.scene.Scene; +import javafx.scene.input.KeyEvent; import javafx.stage.Screen; import javafx.stage.Stage; @@ -16,6 +17,7 @@ import org.jabref.gui.icon.IconTheme; import org.jabref.gui.importer.ParserResultWarningDialog; import org.jabref.gui.importer.actions.OpenDatabaseAction; +import org.jabref.gui.keyboard.TextInputKeyBindings; import org.jabref.gui.shared.SharedDatabaseUIManager; import org.jabref.logic.autosaveandbackup.BackupManager; import org.jabref.logic.importer.OpenDatabase; @@ -86,6 +88,10 @@ private void openWindow(Stage mainStage) { Scene scene = new Scene(root, 800, 800); Globals.prefs.getTheme().installCss(scene); + + // Handle TextEditor key bindings + scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> TextInputKeyBindings.call(scene, event)); + mainStage.setTitle(JabRefFrame.FRAME_TITLE); mainStage.getIcons().addAll(IconTheme.getLogoSetFX()); mainStage.setScene(scene); @@ -145,7 +151,7 @@ private void openDatabases() { new SharedDatabaseUIManager(mainFrame).openSharedDatabaseFromParserResult(pr); } catch (SQLException | DatabaseNotSupportedException | InvalidDBMSConnectionPropertiesException | NotASharedDatabaseException e) { - pr.getDatabaseContext().clearDatabaseFile(); // do not open the original file + pr.getDatabaseContext().clearDatabasePath(); // do not open the original file pr.getDatabase().clearSharedDatabaseID(); LOGGER.error("Connection error", e); @@ -195,9 +201,9 @@ private void openDatabases() { for (int i = 0; (i < bibDatabases.size()) && (i < mainFrame.getBasePanelCount()); i++) { ParserResult pr = bibDatabases.get(i); - BasePanel panel = mainFrame.getBasePanelAt(i); + LibraryTab libraryTab = mainFrame.getLibraryTabAt(i); - OpenDatabaseAction.performPostOpenActions(panel, pr); + OpenDatabaseAction.performPostOpenActions(libraryTab, pr); } LOGGER.debug("Finished adding panels"); @@ -215,18 +221,17 @@ private void saveWindowState(Stage mainStage) { /** * outprints the Data from the Screen (only in debug mode) * - * @param mainStage + * @param mainStage JabRefs stage */ private void debugLogWindowState(Stage mainStage) { if (LOGGER.isDebugEnabled()) { - StringBuilder debugLogString = new StringBuilder(); - debugLogString.append("SCREEN DATA:"); - debugLogString.append("mainStage.WINDOW_MAXIMISED: " + mainStage.isMaximized() + "\n"); - debugLogString.append("mainStage.POS_X: " + mainStage.getX() + "\n"); - debugLogString.append("mainStage.POS_Y: " + mainStage.getY() + "\n"); - debugLogString.append("mainStage.SIZE_X: " + mainStage.getWidth() + "\n"); - debugLogString.append("mainStages.SIZE_Y: " + mainStage.getHeight() + "\n"); - LOGGER.debug(debugLogString.toString()); + String debugLogString = "SCREEN DATA:" + + "mainStage.WINDOW_MAXIMISED: " + mainStage.isMaximized() + "\n" + + "mainStage.POS_X: " + mainStage.getX() + "\n" + + "mainStage.POS_Y: " + mainStage.getY() + "\n" + + "mainStage.SIZE_X: " + mainStage.getWidth() + "\n" + + "mainStages.SIZE_Y: " + mainStage.getHeight() + "\n"; + LOGGER.debug(debugLogString); } } diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/LibraryTab.java similarity index 71% rename from src/main/java/org/jabref/gui/BasePanel.java rename to src/main/java/org/jabref/gui/LibraryTab.java index e98e641a73b..2cb7174d95c 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -1,16 +1,22 @@ package org.jabref.gui; +import java.io.File; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.collections.ListChangeListener; import javafx.geometry.Orientation; import javafx.scene.Node; import javafx.scene.control.SplitPane; -import javafx.scene.layout.StackPane; +import javafx.scene.control.Tab; +import javafx.scene.control.Tooltip; import org.jabref.gui.autocompleter.AutoCompletePreferences; import org.jabref.gui.autocompleter.PersonNameSuggestionProvider; @@ -34,6 +40,7 @@ import org.jabref.logic.search.SearchQuery; import org.jabref.logic.shared.DatabaseLocation; import org.jabref.logic.util.UpdateField; +import org.jabref.logic.util.io.FileUtil; import org.jabref.model.FieldChange; import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; @@ -45,7 +52,7 @@ import org.jabref.model.entry.event.EntryChangedEvent; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; -import org.jabref.preferences.JabRefPreferences; +import org.jabref.preferences.PreferencesService; import com.google.common.eventbus.Subscribe; import com.tobiasdiez.easybind.EasyBind; @@ -53,9 +60,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class BasePanel extends StackPane { +public class LibraryTab extends Tab { - private static final Logger LOGGER = LoggerFactory.getLogger(BasePanel.class); + private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); private final BibDatabaseContext bibDatabaseContext; private final MainTableDataModel tableModel; @@ -71,39 +78,45 @@ public class BasePanel extends StackPane { private final EntryEditor entryEditor; private final DialogService dialogService; + private final PreferencesService preferencesService; + private MainTable mainTable; - private BasePanelPreferences preferences; private BasePanelMode mode = BasePanelMode.SHOWING_NOTHING; private SplitPane splitPane; private DatabaseChangePane changePane; private boolean saving; private PersonNameSuggestionProvider searchAutoCompleter; - private boolean baseChanged; - private boolean nonUndoableChange; + + private final BooleanProperty changedProperty = new SimpleBooleanProperty(false); + private final BooleanProperty nonUndoableChangeProperty = new SimpleBooleanProperty(false); // Used to track whether the base has changed since last save. + private BibEntry showing; private SuggestionProviders suggestionProviders; - @SuppressWarnings({"FieldCanBeLocal", "unused"}) private Subscription dividerPositionSubscription; + @SuppressWarnings({"FieldCanBeLocal"}) private Subscription dividerPositionSubscription; // the query the user searches when this BasePanel is active private Optional currentSearchQuery = Optional.empty(); private Optional changeMonitor = Optional.empty(); - public BasePanel(JabRefFrame frame, BasePanelPreferences preferences, BibDatabaseContext bibDatabaseContext, ExternalFileTypes externalFileTypes) { - this.preferences = Objects.requireNonNull(preferences); + public LibraryTab(JabRefFrame frame, + PreferencesService preferencesService, + BibDatabaseContext bibDatabaseContext, + ExternalFileTypes externalFileTypes) { this.frame = Objects.requireNonNull(frame); this.bibDatabaseContext = Objects.requireNonNull(bibDatabaseContext); this.externalFileTypes = Objects.requireNonNull(externalFileTypes); this.undoManager = frame.getUndoManager(); this.dialogService = frame.getDialogService(); + this.preferencesService = Objects.requireNonNull(preferencesService); bibDatabaseContext.getDatabase().registerListener(this); bibDatabaseContext.getMetaData().registerListener(this); this.sidePaneManager = frame.getSidePaneManager(); - this.tableModel = new MainTableDataModel(getBibDatabaseContext(), Globals.prefs, Globals.stateManager); + this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferencesService, Globals.stateManager); citationStyleCache = new CitationStyleCache(bibDatabaseContext); - annotationCache = new FileAnnotationCache(bibDatabaseContext, Globals.prefs.getFilePreferences()); + annotationCache = new FileAnnotationCache(bibDatabaseContext, preferencesService.getFilePreferences()); setupMainPanel(); setupAutoCompletion(); @@ -116,54 +129,136 @@ public BasePanel(JabRefFrame frame, BasePanelPreferences preferences, BibDatabas // ensure that all entry changes mark the panel as changed this.bibDatabaseContext.getDatabase().registerListener(this); - this.getDatabase().registerListener(new UpdateTimestampListener(Globals.prefs)); + this.getDatabase().registerListener(new UpdateTimestampListener(preferencesService)); this.entryEditor = new EntryEditor(this, externalFileTypes); - } - @Subscribe - public void listen(BibDatabaseContextChangedEvent event) { - this.markBaseChanged(); + Platform.runLater(() -> { + EasyBind.subscribe(changedProperty, this::updateTabTitle); + Globals.stateManager.getOpenDatabases().addListener((ListChangeListener) c -> + updateTabTitle(changedProperty.getValue())); + }); } /** - * Returns a collection of suggestion providers, which are populated from the current library. + * Sets the title of the tab + * modification-asterisk filename – path-fragment + * + * The modification-asterisk (*) is shown if the file was modified since last save + * (path-fragment is only shown if filename is not (globally) unique) + * + * Example: + * *jabref-authors.bib – testbib */ - public SuggestionProviders getSuggestionProviders() { - return suggestionProviders; - } + public void updateTabTitle(boolean isChanged) { + boolean isAutosaveEnabled = preferencesService.getShouldAutosave(); + + DatabaseLocation databaseLocation = bibDatabaseContext.getLocation(); + Optional file = bibDatabaseContext.getDatabasePath(); - public String getTabTitle() { - StringBuilder title = new StringBuilder(); - DatabaseLocation databaseLocation = this.bibDatabaseContext.getLocation(); - boolean isAutosaveEnabled = Globals.prefs.getBoolean(JabRefPreferences.LOCAL_AUTO_SAVE); + StringBuilder tabTitle = new StringBuilder(); + StringBuilder toolTipText = new StringBuilder(); - if (databaseLocation == DatabaseLocation.LOCAL) { - if (this.bibDatabaseContext.getDatabasePath().isPresent()) { - title.append(this.bibDatabaseContext.getDatabasePath().get().getFileName()); - if (isModified() && !isAutosaveEnabled) { - title.append("*"); - } - } else { - title.append(Localization.lang("untitled")); + if (file.isPresent()) { + // Modification asterisk + if (isChanged && !isAutosaveEnabled) { + tabTitle.append('*'); + } + + // Filename + Path databasePath = file.get(); + String fileName = databasePath.getFileName().toString(); + tabTitle.append(fileName); + toolTipText.append(databasePath.toAbsolutePath().toString()); + + if (databaseLocation == DatabaseLocation.SHARED) { + tabTitle.append(" \u2013 "); + addSharedDbInformation(tabTitle, bibDatabaseContext); + toolTipText.append(' '); + addSharedDbInformation(toolTipText, bibDatabaseContext); + } + + // Database mode + addModeInfo(toolTipText, bibDatabaseContext); - if (getDatabase().hasEntries()) { + // Changed information (tooltip) + if (isChanged && !isAutosaveEnabled) { + addChangedInformation(toolTipText, fileName); + } + + // Unique path fragment + List uniquePathParts = FileUtil.uniquePathSubstrings(collectAllDatabasePaths()); + Optional uniquePathPart = uniquePathParts.stream() + .filter(part -> databasePath.toString().contains(part) + && !part.equals(fileName) && part.contains(File.separator)) + .findFirst(); + if (uniquePathPart.isPresent()) { + String uniquePath = uniquePathPart.get(); + // remove filename + uniquePath = uniquePath.substring(0, uniquePath.lastIndexOf(File.separator)); + tabTitle.append(" \u2013 ").append(uniquePath); + } + } else { + if (databaseLocation == DatabaseLocation.LOCAL) { + tabTitle.append(Localization.lang("untitled")); + if (bibDatabaseContext.getDatabase().hasEntries()) { // if the database is not empty and no file is assigned, // the database came from an import and has to be treated somehow // -> mark as changed - // This also happens internally at basepanel to ensure consistency line 224 - title.append('*'); + tabTitle.append('*'); } + } else { + addSharedDbInformation(tabTitle, bibDatabaseContext); + addSharedDbInformation(toolTipText, bibDatabaseContext); + } + addModeInfo(toolTipText, bibDatabaseContext); + if (databaseLocation == DatabaseLocation.LOCAL && bibDatabaseContext.getDatabase().hasEntries()) { + addChangedInformation(toolTipText, Localization.lang("untitled")); } - } else if (databaseLocation == DatabaseLocation.SHARED) { - title.append(this.bibDatabaseContext.getDBMSSynchronizer().getDBName() + " [" + Localization.lang("shared") + "]"); } - return title.toString(); + textProperty().setValue(tabTitle.toString()); + setTooltip(new Tooltip(toolTipText.toString())); } - public boolean isModified() { - return baseChanged; + private static void addChangedInformation(StringBuilder text, String fileName) { + text.append("\n"); + text.append(Localization.lang("Library '%0' has changed.", fileName)); + } + + private static void addModeInfo(StringBuilder text, BibDatabaseContext bibDatabaseContext) { + String mode = bibDatabaseContext.getMode().getFormattedName(); + String modeInfo = String.format("\n%s", Localization.lang("%0 mode", mode)); + text.append(modeInfo); + } + + private static void addSharedDbInformation(StringBuilder text, BibDatabaseContext bibDatabaseContext) { + text.append(bibDatabaseContext.getDBMSSynchronizer().getDBName()); + text.append(" ["); + text.append(Localization.lang("shared")); + text.append("]"); + } + + private List collectAllDatabasePaths() { + List list = new ArrayList<>(); + Globals.stateManager.getOpenDatabases().stream() + .map(BibDatabaseContext::getDatabasePath) + .forEachOrdered(pathOptional -> pathOptional.ifPresentOrElse( + path -> list.add(path.toAbsolutePath().toString()), + () -> list.add(""))); + return list; + } + + @Subscribe + public void listen(BibDatabaseContextChangedEvent event) { + this.changedProperty.setValue(true); + } + + /** + * Returns a collection of suggestion providers, which are populated from the current library. + */ + public SuggestionProviders getSuggestionProviders() { + return suggestionProviders; } public BasePanelMode getMode() { @@ -178,10 +273,6 @@ public JabRefFrame frame() { return frame; } - public void output(String s) { - dialogService.notify(s); - } - /** * Removes the selected entries from the database * @@ -210,8 +301,8 @@ private void delete(boolean cut, List entries) { bibDatabaseContext.getDatabase().removeEntries(entries); ensureNotShowingBottomPanel(entries); - markBaseChanged(); - this.output(formatOutputMessage(cut ? Localization.lang("Cut") : Localization.lang("Deleted"), entries.size())); + this.changedProperty.setValue(true); + dialogService.notify(formatOutputMessage(cut ? Localization.lang("Cut") : Localization.lang("Deleted"), entries.size())); // prevent the main table from loosing focus mainTable.requestFocus(); @@ -255,14 +346,14 @@ public void insertEntries(final List entries) { UpdateField.setAutomaticFields(entry, true, true, - Globals.prefs.getOwnerPreferences(), - Globals.prefs.getTimestampPreferences()); + preferencesService.getOwnerPreferences(), + preferencesService.getTimestampPreferences()); } // Create an UndoableInsertEntries object. getUndoManager().addEdit(new UndoableInsertEntries(bibDatabaseContext.getDatabase(), entries)); - markBaseChanged(); // The database just changed. - if (Globals.prefs.getBoolean(JabRefPreferences.AUTO_OPEN_FORM)) { + this.changedProperty.setValue(true); // The database just changed. + if (preferencesService.getEntryEditorPreferences().shouldOpenOnNewEntry()) { showAndEdit(entries.get(0)); } clearAndSelect(entries.get(0)); @@ -284,11 +375,11 @@ private void createMainTable() { mainTable = new MainTable(tableModel, this, bibDatabaseContext, - Globals.prefs, + preferencesService, dialogService, Globals.stateManager, externalFileTypes, - preferences.getKeyBindings()); + Globals.getKeyPrefs()); // Add the listener that binds selection to state manager (TODO: should be replaced by proper JavaFX binding as soon as table is implemented in JavaFX) mainTable.addSelectionListener(listEvent -> Globals.stateManager.setSelectedEntries(mainTable.getSelectedEntries())); @@ -301,11 +392,8 @@ private void createMainTable() { } public void setupMainPanel() { - preferences = BasePanelPreferences.from(Globals.prefs); - splitPane = new SplitPane(); splitPane.setOrientation(Orientation.VERTICAL); - adjustSplitter(); // restore last splitting state (before mainTable is created as creation affects the stored size of the entryEditors) createMainTable(); @@ -327,10 +415,10 @@ public void setupMainPanel() { // if the database is not empty and no file is assigned, // the database came from an import and has to be treated somehow // -> mark as changed - this.baseChanged = true; + this.changedProperty.setValue(true); } changePane = null; - getChildren().add(splitPane); + this.setContent(splitPane); } } @@ -338,7 +426,7 @@ public void setupMainPanel() { * Set up auto completion for this database */ private void setupAutoCompletion() { - AutoCompletePreferences autoCompletePreferences = preferences.getAutoCompletePreferences(); + AutoCompletePreferences autoCompletePreferences = preferencesService.getAutoCompletePreferences(); if (autoCompletePreferences.shouldAutoComplete()) { suggestionProviders = new SuggestionProviders(getDatabase(), Globals.journalAbbreviationRepository); } else { @@ -352,12 +440,6 @@ public void updateSearchManager() { frame.getGlobalSearchBar().setAutoCompleter(searchAutoCompleter); } - private void adjustSplitter() { - if (mode == BasePanelMode.SHOWING_EDITOR) { - splitPane.setDividerPositions(preferences.getEntryEditorDividerPosition()); - } - } - public EntryEditor getEntryEditor() { return entryEditor; } @@ -390,7 +472,8 @@ private void showBottomPane(BasePanelMode newMode) { splitPane.getItems().add(1, pane); } mode = newMode; - adjustSplitter(); + + splitPane.setDividerPositions(preferencesService.getEntryEditorPreferences().getDividerPosition()); } /** @@ -409,16 +492,6 @@ public void clearAndSelect(final BibEntry bibEntry) { mainTable.clearAndSelect(bibEntry); } - /** - * Select and open entry editor for first entry in main table. - */ - private void clearAndSelectFirst() { - mainTable.clearAndSelectFirst(); - if (!mainTable.getSelectedEntries().isEmpty()) { - showAndEdit(mainTable.getSelectedEntries().get(0)); - } - } - public void selectPreviousEntry() { mainTable.getSelectionModel().clearAndSelect(mainTable.getSelectionModel().getSelectedIndex() - 1); } @@ -455,32 +528,16 @@ public void updateEntryEditorIfShowing() { } } - public void markBaseChanged() { - baseChanged = true; - // Put an asterisk behind the filename to indicate the database has changed. - frame.setWindowTitle(); - DefaultTaskExecutor.runInJavaFXThread(frame::updateAllTabTitles); - } - - public void markNonUndoableBaseChanged() { - nonUndoableChange = true; - markBaseChanged(); - } + /** + * Put an asterisk behind the filename to indicate the database has changed. + */ public synchronized void markChangedOrUnChanged() { if (getUndoManager().hasChanged()) { - if (!baseChanged) { - markBaseChanged(); - } - } else if (baseChanged && !nonUndoableChange) { - baseChanged = false; - if (getBibDatabaseContext().getDatabasePath().isPresent()) { - frame.setTabTitle(this, getTabTitle(), getBibDatabaseContext().getDatabasePath().get().toAbsolutePath().toString()); - } else { - frame.setTabTitle(this, Localization.lang("untitled"), null); - } + this.changedProperty.setValue(true); + } else if (changedProperty.getValue() && !nonUndoableChangeProperty.getValue()) { + this.changedProperty.setValue(false); } - frame.setWindowTitle(); } public BibDatabase getDatabase() { @@ -488,7 +545,7 @@ public BibDatabase getDatabase() { } private boolean showDeleteConfirmationDialog(int numberOfEntries) { - if (Globals.prefs.getBoolean(JabRefPreferences.CONFIRM_DELETE)) { + if (preferencesService.getGeneralPreferences().shouldConfirmDelete()) { String title = Localization.lang("Delete entry"); String message = Localization.lang("Really delete the selected entry?"); String okButton = Localization.lang("Delete entry"); @@ -505,7 +562,8 @@ private boolean showDeleteConfirmationDialog(int numberOfEntries) { okButton, cancelButton, Localization.lang("Disable this confirmation dialog"), - optOut -> Globals.prefs.putBoolean(JabRefPreferences.CONFIRM_DELETE, !optOut)); + optOut -> preferencesService.storeGeneralPreferences( + preferencesService.getGeneralPreferences().withConfirmDelete(!optOut))); } else { return true; } @@ -517,7 +575,8 @@ private boolean showDeleteConfirmationDialog(int numberOfEntries) { */ private void saveDividerLocation(Number position) { if (mode == BasePanelMode.SHOWING_EDITOR) { - preferences.setEntryEditorDividerPosition(position.doubleValue()); + preferencesService.storeEntryEditorPreferences( + preferencesService.getEntryEditorPreferences().withDividerPosition(position.doubleValue())); } } @@ -546,14 +605,6 @@ public SidePaneManager getSidePaneManager() { return sidePaneManager; } - public void setNonUndoableChange(boolean nonUndoableChange) { - this.nonUndoableChange = nonUndoableChange; - } - - public void setBaseChanged(boolean baseChanged) { - this.baseChanged = baseChanged; - } - public boolean isSaving() { return saving; } @@ -603,7 +654,7 @@ public void resetChangeMonitorAndChangePane() { changePane = new DatabaseChangePane(splitPane, bibDatabaseContext, changeMonitor.get()); - this.getChildren().setAll(changePane); + this.setContent(changePane); } public void copy() { @@ -618,6 +669,32 @@ public void cut() { mainTable.cut(); } + public BooleanProperty changedProperty() { + return changedProperty; + } + + public boolean isModified() { + return changedProperty.getValue(); + } + + public void markBaseChanged() { + this.changedProperty.setValue(true); + } + + public BooleanProperty nonUndoableChangeProperty() { + return nonUndoableChangeProperty; + } + + public void markNonUndoableBaseChanged() { + this.nonUndoableChangeProperty.setValue(true); + this.changedProperty.setValue(true); + } + + public void resetChangedProperties() { + this.nonUndoableChangeProperty.setValue(false); + this.changedProperty.setValue(false); + } + private class GroupTreeListener { @Subscribe @@ -628,7 +705,7 @@ public void listen(EntriesAddedEvent addedEntriesEvent) { } // Automatically add new entries to the selected group (or set of groups) - if (Globals.prefs.getBoolean(JabRefPreferences.AUTO_ASSIGN_GROUP)) { + if (preferencesService.getGroupsPreferences().shouldAutoAssignGroup()) { Globals.stateManager.getSelectedGroup(bibDatabaseContext).forEach( selectedGroup -> selectedGroup.addEntriesToGroup(addedEntriesEvent.getBibEntries())); } diff --git a/src/main/java/org/jabref/gui/SendAsEMailAction.java b/src/main/java/org/jabref/gui/SendAsEMailAction.java index 2ba19e6798f..c54dc6fa60e 100644 --- a/src/main/java/org/jabref/gui/SendAsEMailAction.java +++ b/src/main/java/org/jabref/gui/SendAsEMailAction.java @@ -90,7 +90,7 @@ private String sendEmail() throws Exception { // the unofficial "mailto:attachment" property boolean openFolders = JabRefPreferences.getInstance().getBoolean(JabRefPreferences.OPEN_FOLDERS_OF_ATTACHED_FILES); - List fileList = FileUtil.getListOfLinkedFiles(entries, databaseContext.getFileDirectoriesAsPaths(Globals.prefs.getFilePreferences())); + List fileList = FileUtil.getListOfLinkedFiles(entries, databaseContext.getFileDirectories(Globals.prefs.getFilePreferences())); for (Path path : fileList) { attachments.add(path.toAbsolutePath().toString()); if (openFolders) { diff --git a/src/main/java/org/jabref/gui/StateManager.java b/src/main/java/org/jabref/gui/StateManager.java index 7554467b7ed..f1ffee17247 100644 --- a/src/main/java/org/jabref/gui/StateManager.java +++ b/src/main/java/org/jabref/gui/StateManager.java @@ -44,6 +44,7 @@ public class StateManager { private final CustomLocalDragboard localDragboard = new CustomLocalDragboard(); + private final ObservableList openDatabases = FXCollections.observableArrayList(); private final OptionalObjectProperty activeDatabase = OptionalObjectProperty.empty(); private final ReadOnlyListWrapper activeGroups = new ReadOnlyListWrapper<>(FXCollections.observableArrayList()); private final ObservableList selectedEntries = FXCollections.observableArrayList(); @@ -51,9 +52,7 @@ public class StateManager { private final OptionalObjectProperty activeSearchQuery = OptionalObjectProperty.empty(); private final ObservableMap searchResultMap = FXCollections.observableHashMap(); private final OptionalObjectProperty focusOwner = OptionalObjectProperty.empty(); - private final ObservableList> backgroundTasks = FXCollections.observableArrayList(task -> { - return new Observable[]{task.progressProperty(), task.runningProperty()}; - }); + private final ObservableList> backgroundTasks = FXCollections.observableArrayList(task -> new Observable[]{task.progressProperty(), task.runningProperty()}); private final EasyBinding anyTaskRunning = EasyBind.reduce(backgroundTasks, tasks -> tasks.anyMatch(Task::isRunning)); private final EasyBinding tasksProgress = EasyBind.reduce(backgroundTasks, tasks -> tasks.filter(Task::isRunning).mapToDouble(Task::getProgress).average().orElse(1)); private final ObservableMap dialogWindowStates = FXCollections.observableHashMap(); @@ -66,6 +65,10 @@ public CustomLocalDragboard getLocalDragboard() { return localDragboard; } + public ObservableList getOpenDatabases() { + return openDatabases; + } + public OptionalObjectProperty activeDatabaseProperty() { return activeDatabase; } diff --git a/src/main/java/org/jabref/gui/UpdateTimestampListener.java b/src/main/java/org/jabref/gui/UpdateTimestampListener.java index 4af4d661c29..aad998db035 100644 --- a/src/main/java/org/jabref/gui/UpdateTimestampListener.java +++ b/src/main/java/org/jabref/gui/UpdateTimestampListener.java @@ -1,7 +1,7 @@ package org.jabref.gui; import org.jabref.model.entry.event.EntryChangedEvent; -import org.jabref.preferences.JabRefPreferences; +import org.jabref.preferences.PreferencesService; import com.google.common.eventbus.Subscribe; @@ -9,17 +9,17 @@ * Updates the timestamp of changed entries if the feature is enabled */ class UpdateTimestampListener { - private final JabRefPreferences jabRefPreferences; + private final PreferencesService preferencesService; - UpdateTimestampListener(JabRefPreferences jabRefPreferences) { - this.jabRefPreferences = jabRefPreferences; + UpdateTimestampListener(PreferencesService preferencesService) { + this.preferencesService = preferencesService; } @Subscribe public void listen(EntryChangedEvent event) { - if (jabRefPreferences.getTimestampPreferences().includeTimestamps()) { - event.getBibEntry().setField(jabRefPreferences.getTimestampPreferences().getTimestampField(), - jabRefPreferences.getTimestampPreferences().now()); + if (preferencesService.getTimestampPreferences().includeTimestamps()) { + event.getBibEntry().setField(preferencesService.getTimestampPreferences().getTimestampField(), + preferencesService.getTimestampPreferences().now()); } } } diff --git a/src/main/java/org/jabref/gui/WaitForSaveFinishedDialog.java b/src/main/java/org/jabref/gui/WaitForSaveFinishedDialog.java index 954d3fe47b0..43f784669de 100644 --- a/src/main/java/org/jabref/gui/WaitForSaveFinishedDialog.java +++ b/src/main/java/org/jabref/gui/WaitForSaveFinishedDialog.java @@ -17,12 +17,12 @@ public WaitForSaveFinishedDialog(DialogService dialogService) { this.dialogService = dialogService; } - public void showAndWait(List basePanels) { - if (basePanels.stream().anyMatch(BasePanel::isSaving)) { - Task waitForSaveFinished = new Task() { + public void showAndWait(List LibraryTabs) { + if (LibraryTabs.stream().anyMatch(LibraryTab::isSaving)) { + Task waitForSaveFinished = new Task<>() { @Override protected Void call() throws Exception { - while (basePanels.stream().anyMatch(BasePanel::isSaving)) { + while (LibraryTabs.stream().anyMatch(LibraryTab::isSaving)) { if (isCancelled()) { return null; } else { diff --git a/src/main/java/org/jabref/gui/actions/JabRefAction.java b/src/main/java/org/jabref/gui/actions/JabRefAction.java index 5d1fbd10e28..b19c9ca110e 100644 --- a/src/main/java/org/jabref/gui/actions/JabRefAction.java +++ b/src/main/java/org/jabref/gui/actions/JabRefAction.java @@ -20,7 +20,7 @@ public JabRefAction(Action action, KeyBindingRepository keyBindingRepository) { action.getIcon() .ifPresent(icon -> setGraphic(icon.getGraphicNode())); action.getKeyBinding() - .ifPresent(keyBinding -> setAccelerator(keyBindingRepository.getKeyCombination(keyBinding))); + .ifPresent(keyBinding -> keyBindingRepository.getKeyCombination(keyBinding).ifPresent(combination -> setAccelerator(combination))); setLongText(action.getDescription()); } diff --git a/src/main/java/org/jabref/gui/actions/StandardActions.java b/src/main/java/org/jabref/gui/actions/StandardActions.java index 30aa8abc803..bdcb81767e0 100644 --- a/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -33,7 +33,7 @@ public enum StandardActions implements Action { 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), SEARCH_SHORTSCIENCE(Localization.lang("Search ShortScience")), - MERGE_WITH_FETCHED_ENTRY(Localization.lang("Get BibTeX data from %0", "DOI/ISBN/...")), + MERGE_WITH_FETCHED_ENTRY(Localization.lang("Get bibliographic data from %0", "DOI/ISBN/...")), ATTACH_FILE(Localization.lang("Attach file"), IconTheme.JabRefIcons.ATTACH_FILE), PRIORITY(Localization.lang("Priority"), IconTheme.JabRefIcons.PRIORITY), CLEAR_PRIORITY(Localization.lang("Clear priority")), @@ -105,7 +105,6 @@ public enum StandardActions implements Action { MANAGE_CUSTOM_IMPORTS(Localization.lang("Manage custom imports")), CUSTOMIZE_ENTRY_TYPES(Localization.lang("Customize entry types")), SETUP_GENERAL_FIELDS(Localization.lang("Set up general fields")), - MANAGE_EXTERNAL_FILETYPES(Localization.lang("Manage external file types")), MANAGE_PROTECTED_TERMS(Localization.lang("Manage protected terms")), CITATION_KEY_PATTERN(Localization.lang("Citation key patterns")), SHOW_PREFS(Localization.lang("Preferences")), @@ -116,8 +115,8 @@ public enum StandardActions implements Action { EDIT_ENTRY(Localization.lang("Open entry editor"), IconTheme.JabRefIcons.EDIT_ENTRY, KeyBinding.EDIT_ENTRY), SHOW_PDF_VIEWER(Localization.lang("Open document viewer"), IconTheme.JabRefIcons.PDF_FILE), - NEXT_PREVIEW_STYLE(Localization.lang("Next citation style"), KeyBinding.NEXT_PREVIEW_LAYOUT), - PREVIOUS_PREVIEW_STYLE(Localization.lang("Previous citation style"), KeyBinding.PREVIOUS_PREVIEW_LAYOUT), + NEXT_PREVIEW_STYLE(Localization.lang("Next preview style"), KeyBinding.NEXT_PREVIEW_LAYOUT), + PREVIOUS_PREVIEW_STYLE(Localization.lang("Previous preview style"), KeyBinding.PREVIOUS_PREVIEW_LAYOUT), SELECT_ALL(Localization.lang("Select all"), KeyBinding.SELECT_ALL), NEW_ENTRY(Localization.lang("New entry"), IconTheme.JabRefIcons.ADD_ENTRY, KeyBinding.NEW_ENTRY), diff --git a/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java b/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java index 3cf297c71c1..a69b8a3b1c1 100644 --- a/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java +++ b/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java @@ -13,7 +13,7 @@ import org.controlsfx.control.textfield.AutoCompletionBinding; /** - * Delivers possible completions as a list of {@link BibEntry} based on their cite key. + * Delivers possible completions as a list of {@link BibEntry} based on their citation key. */ public class BibEntrySuggestionProvider extends SuggestionProvider { @@ -25,7 +25,7 @@ public BibEntrySuggestionProvider(BibDatabase database) { @Override protected Equivalence getEquivalence() { - return Equivalence.equals().onResultOf(BibEntry::getCiteKeyOptional); + return Equivalence.equals().onResultOf(BibEntry::getCitationKey); } @Override @@ -36,7 +36,7 @@ protected Comparator getComparator() { @Override protected boolean isMatch(BibEntry entry, AutoCompletionBinding.ISuggestionRequest request) { String userText = request.getUserText(); - return entry.getCiteKeyOptional() + return entry.getCitationKey() .map(key -> StringUtil.containsIgnoreCase(key, userText)) .orElse(false); } diff --git a/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java b/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java index 518b09efc2b..5626bc8b60e 100644 --- a/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java +++ b/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java @@ -11,9 +11,9 @@ import javafx.scene.control.TextArea; import javafx.scene.control.TextField; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.FileDialogConfiguration; import org.jabref.logic.auxparser.AuxParser; @@ -32,7 +32,7 @@ */ public class FromAuxDialog extends BaseDialog { - private final BasePanel basePanel; + private final LibraryTab libraryTab; @FXML private ButtonType generateButtonType; private final Button generateButton; @FXML private TextField auxFileField; @@ -44,7 +44,7 @@ public class FromAuxDialog extends BaseDialog { @Inject private DialogService dialogService; public FromAuxDialog(JabRefFrame frame) { - basePanel = frame.getCurrentBasePanel(); + libraryTab = frame.getCurrentLibraryTab(); this.setTitle(Localization.lang("AUX file import")); ViewLoader.view(this) @@ -67,7 +67,7 @@ public FromAuxDialog(JabRefFrame frame) { private void parseActionPerformed() { notFoundList.getItems().clear(); statusInfos.setText(""); - BibDatabase refBase = basePanel.getDatabase(); + BibDatabase refBase = libraryTab.getDatabase(); String auxName = auxFileField.getText(); if ((auxName != null) && (refBase != null) && !auxName.isEmpty()) { diff --git a/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java b/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java index a7181ba3ae1..f1d99d74331 100644 --- a/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java +++ b/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java @@ -15,6 +15,7 @@ import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.fetcher.GrobidCitationFetcher; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; @@ -22,8 +23,13 @@ import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.JabRefPreferences; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class BibtexExtractorViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(BibtexExtractorViewModel.class); + private final StringProperty inputTextProperty = new SimpleStringProperty(""); private DialogService dialogService; private GrobidCitationFetcher currentCitationfetcher; @@ -58,6 +64,15 @@ public StringProperty inputTextProperty() { public void startParsing() { BackgroundTask.wrap(() -> currentCitationfetcher.performSearch(inputTextProperty.getValue())) .onRunning(() -> dialogService.notify(Localization.lang("Your text is being parsed..."))) + .onFailure((e) -> { + if (e instanceof FetcherException) { + String msg = Localization.lang("There are connection issues with a JabRef server. Detailed information: %0.", + e.getMessage()); + dialogService.notify(msg); + } else { + LOGGER.warn("Missing exception handling.", e); + } + }) .onSuccess(parsedEntries -> { dialogService.notify(Localization.lang("%0 entries were parsed from your query.", String.valueOf(parsedEntries.size()))); importHandler.importEntries(parsedEntries); diff --git a/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternAction.java b/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternAction.java index 74f20a3b7b4..983e9103e00 100644 --- a/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternAction.java +++ b/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternAction.java @@ -18,6 +18,6 @@ public CitationKeyPatternAction(JabRefFrame frame, StateManager stateManager) { @Override public void execute() { - new CitationKeyPatternDialog(frame.getCurrentBasePanel()).showAndWait(); + new CitationKeyPatternDialog(frame.getCurrentLibraryTab()).showAndWait(); } } diff --git a/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternDialog.java b/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternDialog.java index 4b33b9de97b..d92f35c7b35 100644 --- a/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternDialog.java +++ b/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternDialog.java @@ -2,8 +2,8 @@ import javafx.scene.control.ButtonType; -import org.jabref.gui.BasePanel; import org.jabref.gui.Globals; +import org.jabref.gui.LibraryTab; import org.jabref.gui.util.BaseDialog; import org.jabref.logic.citationkeypattern.AbstractCitationKeyPattern; import org.jabref.logic.l10n.Localization; @@ -12,13 +12,13 @@ public class CitationKeyPatternDialog extends BaseDialog { private final MetaData metaData; - private final BasePanel panel; + private final LibraryTab libraryTab; private final CitationKeyPatternPanel citationKeyPatternPanel; - public CitationKeyPatternDialog(BasePanel panel) { - this.citationKeyPatternPanel = new CitationKeyPatternPanel(panel); - this.panel = panel; - this.metaData = panel.getBibDatabaseContext().getMetaData(); + public CitationKeyPatternDialog(LibraryTab libraryTab) { + this.citationKeyPatternPanel = new CitationKeyPatternPanel(libraryTab.getBibDatabaseContext()); + this.libraryTab = libraryTab; + this.metaData = libraryTab.getBibDatabaseContext().getMetaData(); AbstractCitationKeyPattern keyPattern = metaData.getCiteKeyPattern(Globals.prefs.getGlobalCitationKeyPattern()); citationKeyPatternPanel.setValues(keyPattern); init(); @@ -34,7 +34,7 @@ private void init() { this.setResultConverter(button -> { if (button == ButtonType.APPLY) { metaData.setCiteKeyPattern(citationKeyPatternPanel.getKeyPatternAsDatabaseKeyPattern()); - panel.markNonUndoableBaseChanged(); + libraryTab.markNonUndoableBaseChanged(); } return null; diff --git a/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternPanel.java b/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternPanel.java index 56f98b88e26..9f1115d8c52 100644 --- a/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternPanel.java +++ b/src/main/java/org/jabref/gui/citationkeypattern/CitationKeyPatternPanel.java @@ -10,7 +10,6 @@ import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; -import org.jabref.gui.BasePanel; import org.jabref.gui.Globals; import org.jabref.gui.actions.ActionFactory; import org.jabref.gui.actions.StandardActions; @@ -19,6 +18,7 @@ import org.jabref.logic.citationkeypattern.DatabaseCitationKeyPattern; import org.jabref.logic.help.HelpFile; import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.types.EntryType; @@ -31,11 +31,11 @@ public class CitationKeyPatternPanel extends Pane { // one field for each type private final Map textFields = new HashMap<>(); - private final BasePanel panel; + private final BibDatabaseContext databaseContext; private final GridPane gridPane = new GridPane(); - public CitationKeyPatternPanel(BasePanel panel) { - this.panel = panel; + public CitationKeyPatternPanel(BibDatabaseContext databaseContext) { + this.databaseContext = databaseContext; gridPane.setHgap(10); gridPane.setVgap(5); buildGUI(); @@ -50,7 +50,12 @@ private static void setValue(TextField tf, EntryType fieldName, AbstractCitation } private void buildGUI() { - BibDatabaseMode mode; + BibDatabaseMode mode = databaseContext.getMode(); + + // The following got irrelevant - global settings for CitationKeyPattern are handled by + // commonfxcontrols/CitationKeyPatternPanel.java + // ToDo: this one should be abandoned + /* // check mode of currently used DB if (panel != null) { mode = panel.getBibDatabaseContext().getMode(); @@ -58,6 +63,7 @@ private void buildGUI() { // use preferences value if no DB is open mode = Globals.prefs.getDefaultBibDatabaseMode(); } + */ int rowIndex = 1; int columnIndex = 0; diff --git a/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java b/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java index e7dae493860..0cf54e3ada9 100644 --- a/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java +++ b/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java @@ -68,15 +68,14 @@ public static boolean confirmOverwriteKeys(DialogService dialogService) { private void checkOverwriteKeysChosen() { // We don't want to generate keys for entries which already have one thus remove the entries if (Globals.prefs.getBoolean(JabRefPreferences.AVOID_OVERWRITING_KEY)) { - entries.removeIf(BibEntry::hasCiteKey); - // if we're going to override some cite keys warn the user about it - } else if (entries.parallelStream().anyMatch(BibEntry::hasCiteKey)) { + entries.removeIf(BibEntry::hasCitationKey); + // if we're going to override some citation keys warn the user about it + } else if (entries.parallelStream().anyMatch(BibEntry::hasCitationKey)) { boolean overwriteKeys = confirmOverwriteKeys(dialogService); - // The user doesn't want to override cite keys + // The user doesn't want to override citation keys if (!overwriteKeys) { isCanceled = true; - return; } } } @@ -87,7 +86,7 @@ private void generateKeys() { } stateManager.getActiveDatabase().ifPresent(databaseContext -> { - // generate the new cite keys for each entry + // generate the new citation keys for each entry final NamedCompound compound = new NamedCompound(Localization.lang("Autogenerate citation keys")); CitationKeyGenerator keyGenerator = new CitationKeyGenerator(databaseContext, Globals.prefs.getCitationKeyPatternPreferences()); @@ -97,12 +96,12 @@ private void generateKeys() { } compound.end(); - // register the undo event only if new cite keys were generated + // register the undo event only if new citation keys were generated if (compound.hasEdits()) { frame.getUndoManager().addEdit(compound); } - frame.getCurrentBasePanel().markBaseChanged(); + frame.getCurrentLibraryTab().markBaseChanged(); dialogService.notify(formatOutputMessage(Localization.lang("Generated citation key for"), entries.size())); }); } diff --git a/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeySingleAction.java b/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeySingleAction.java index 43e2acbe1ef..b66beef36be 100644 --- a/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeySingleAction.java +++ b/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeySingleAction.java @@ -32,7 +32,7 @@ public GenerateCitationKeySingleAction(BibEntry entry, BibDatabaseContext databa @Override public void execute() { - if (!entry.hasCiteKey() || GenerateCitationKeyAction.confirmOverwriteKeys(dialogService)) { + if (!entry.hasCitationKey() || GenerateCitationKeyAction.confirmOverwriteKeys(dialogService)) { new CitationKeyGenerator(databaseContext, preferencesService.getCitationKeyPatternPreferences()) .generateAndSetKey(entry) .ifPresent(change -> undoManager.addEdit(new UndoableKeyChange(change))); diff --git a/src/main/java/org/jabref/gui/cleanup/CleanupAction.java b/src/main/java/org/jabref/gui/cleanup/CleanupAction.java index 1fa40af1f84..50e7e703c0f 100644 --- a/src/main/java/org/jabref/gui/cleanup/CleanupAction.java +++ b/src/main/java/org/jabref/gui/cleanup/CleanupAction.java @@ -108,8 +108,8 @@ private void showResults() { } if (modifiedEntriesCount > 0) { - frame.getCurrentBasePanel().updateEntryEditorIfShowing(); - frame.getCurrentBasePanel().markBaseChanged(); + frame.getCurrentLibraryTab().updateEntryEditorIfShowing(); + frame.getCurrentLibraryTab().markBaseChanged(); } if (modifiedEntriesCount == 0) { diff --git a/src/main/java/org/jabref/gui/collab/EntryAddChangeViewModel.java b/src/main/java/org/jabref/gui/collab/EntryAddChangeViewModel.java index ccd41b1b74d..6e5e0d8b456 100644 --- a/src/main/java/org/jabref/gui/collab/EntryAddChangeViewModel.java +++ b/src/main/java/org/jabref/gui/collab/EntryAddChangeViewModel.java @@ -17,7 +17,7 @@ class EntryAddChangeViewModel extends DatabaseChangeViewModel { public EntryAddChangeViewModel(BibEntry entry) { super(); - this.name = entry.getCiteKeyOptional() + this.name = entry.getCitationKey() .map(key -> Localization.lang("Added entry") + ": '" + key + '\'') .orElse(Localization.lang("Added entry")); this.entry = entry; diff --git a/src/main/java/org/jabref/gui/collab/EntryChangeViewModel.java b/src/main/java/org/jabref/gui/collab/EntryChangeViewModel.java index cc303b27178..bbb006da2cc 100644 --- a/src/main/java/org/jabref/gui/collab/EntryChangeViewModel.java +++ b/src/main/java/org/jabref/gui/collab/EntryChangeViewModel.java @@ -25,7 +25,7 @@ public EntryChangeViewModel(BibEntry entry, BibEntry newEntry) { this.oldEntry = entry; this.newEntry = newEntry; - name = entry.getCiteKeyOptional() + name = entry.getCitationKey() .map(key -> Localization.lang("Modified entry") + ": '" + key + '\'') .orElse(Localization.lang("Modified entry")); } diff --git a/src/main/java/org/jabref/gui/collab/EntryDeleteChangeViewModel.java b/src/main/java/org/jabref/gui/collab/EntryDeleteChangeViewModel.java index 5bc4f01fcf5..ff6adfd3fce 100644 --- a/src/main/java/org/jabref/gui/collab/EntryDeleteChangeViewModel.java +++ b/src/main/java/org/jabref/gui/collab/EntryDeleteChangeViewModel.java @@ -22,7 +22,7 @@ class EntryDeleteChangeViewModel extends DatabaseChangeViewModel { public EntryDeleteChangeViewModel(BibEntry entry) { super(Localization.lang("Deleted entry")); - this.name = entry.getCiteKeyOptional() + this.name = entry.getCitationKey() .map(key -> Localization.lang("Deleted entry") + ": '" + key + '\'') .orElse(Localization.lang("Deleted entry")); this.entry = entry; diff --git a/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialogView.java b/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialogView.java index 766258c0b60..62832cb51ad 100644 --- a/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialogView.java +++ b/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialogView.java @@ -12,8 +12,8 @@ import javafx.scene.control.ListView; import javafx.scene.control.SelectionModel; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; +import org.jabref.gui.LibraryTab; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.ControlHelper; import org.jabref.logic.l10n.Localization; @@ -41,14 +41,14 @@ public class ContentSelectorDialogView extends BaseDialog { @Inject private DialogService dialogService; - private final BasePanel basePanel; + private final LibraryTab libraryTab; private ContentSelectorDialogViewModel viewModel; - public ContentSelectorDialogView(BasePanel basePanel) { + public ContentSelectorDialogView(LibraryTab libraryTab) { this.setTitle(Localization.lang("Manage content selectors")); this.getDialogPane().setPrefSize(375, 475); - this.basePanel = basePanel; + this.libraryTab = libraryTab; ViewLoader.view(this) .load() @@ -59,7 +59,7 @@ public ContentSelectorDialogView(BasePanel basePanel) { @FXML public void initialize() { - viewModel = new ContentSelectorDialogViewModel(basePanel, dialogService); + viewModel = new ContentSelectorDialogViewModel(libraryTab, dialogService); initFieldNameComponents(); initKeywordsComponents(); diff --git a/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialogViewModel.java b/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialogViewModel.java index 7d10d5841e2..d83a4de4469 100644 --- a/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialogViewModel.java +++ b/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialogViewModel.java @@ -20,8 +20,8 @@ import javafx.collections.FXCollections; import org.jabref.gui.AbstractViewModel; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; +import org.jabref.gui.LibraryTab; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; @@ -33,7 +33,7 @@ class ContentSelectorDialogViewModel extends AbstractViewModel { private static final List DEFAULT_FIELD_NAMES = Arrays.asList(StandardField.AUTHOR, StandardField.JOURNAL, StandardField.KEYWORDS, StandardField.PUBLISHER); - private final BasePanel basePanel; + private final LibraryTab libraryTab; private final MetaData metaData; private final DialogService dialogService; private final Map> fieldKeywordsMap = new HashMap<>(); @@ -43,9 +43,9 @@ class ContentSelectorDialogViewModel extends AbstractViewModel { private ObjectProperty selectedField = new SimpleObjectProperty<>(); private StringProperty selectedKeyword = new SimpleStringProperty(); - ContentSelectorDialogViewModel(BasePanel basePanel, DialogService dialogService) { - this.basePanel = basePanel; - this.metaData = basePanel.getBibDatabaseContext().getMetaData(); + ContentSelectorDialogViewModel(LibraryTab libraryTab, DialogService dialogService) { + this.libraryTab = libraryTab; + this.metaData = libraryTab.getBibDatabaseContext().getMetaData(); this.dialogService = dialogService; populateFieldNameKeywordsMapWithExistingValues(); populateFieldNamesListWithValues(); @@ -177,8 +177,8 @@ void saveChanges() { List fieldNamesToRemove = filterFieldsToRemove(); fieldNamesToRemove.forEach(metaData::clearContentSelectors); - basePanel.setupMainPanel(); - basePanel.markNonUndoableBaseChanged(); + libraryTab.setupMainPanel(); + libraryTab.markNonUndoableBaseChanged(); } private List filterFieldsToRemove() { diff --git a/src/main/java/org/jabref/gui/contentselector/ManageContentSelectorAction.java b/src/main/java/org/jabref/gui/contentselector/ManageContentSelectorAction.java index 555afe1dc20..e91c22b6e19 100644 --- a/src/main/java/org/jabref/gui/contentselector/ManageContentSelectorAction.java +++ b/src/main/java/org/jabref/gui/contentselector/ManageContentSelectorAction.java @@ -1,7 +1,7 @@ package org.jabref.gui.contentselector; -import org.jabref.gui.BasePanel; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; @@ -19,7 +19,7 @@ public ManageContentSelectorAction(JabRefFrame jabRefFrame, StateManager stateMa @Override public void execute() { - BasePanel basePanel = jabRefFrame.getCurrentBasePanel(); - new ContentSelectorDialogView(basePanel).showAndWait(); + LibraryTab libraryTab = jabRefFrame.getCurrentLibraryTab(); + new ContentSelectorDialogView(libraryTab).showAndWait(); } } diff --git a/src/main/java/org/jabref/gui/customentrytypes/CustomEntryTypeDialogViewModel.java b/src/main/java/org/jabref/gui/customentrytypes/CustomEntryTypeDialogViewModel.java index 2e91b67a4b7..d7a034c3225 100644 --- a/src/main/java/org/jabref/gui/customentrytypes/CustomEntryTypeDialogViewModel.java +++ b/src/main/java/org/jabref/gui/customentrytypes/CustomEntryTypeDialogViewModel.java @@ -50,13 +50,13 @@ public Field fromString(String string) { } }; - private final ObservableList fieldsForAdding = FXCollections.observableArrayList(FieldFactory.getStandardFielsdsWithBibTexKey()); - private final ObjectProperty selectedEntryType = new SimpleObjectProperty<>(); + private final ObservableList fieldsForAdding = FXCollections.observableArrayList(FieldFactory.getStandardFieldsWithCitationKey()); + private final ObjectProperty selectedEntryType = new SimpleObjectProperty<>(); private final ObjectProperty selectedFieldToAdd = new SimpleObjectProperty<>(); private final StringProperty entryTypeToAdd = new SimpleStringProperty(""); private final ObjectProperty newFieldToAdd = new SimpleObjectProperty<>(); private final BibDatabaseMode mode; - private final ObservableList entryTypesWithFields = FXCollections.observableArrayList(extractor -> new Observable[] {extractor.entryType(), extractor.fields()}); + private final ObservableList entryTypesWithFields = FXCollections.observableArrayList(extractor -> new Observable[] {extractor.entryType(), extractor.fields()}); private final List entryTypesToDelete = new ArrayList<>(); private final PreferencesService preferencesService; @@ -86,12 +86,17 @@ public void addAllTypes() { Collection allTypes = entryTypesManager.getAllTypes(mode); for (BibEntryType entryType : allTypes) { - CustomEntryTypeViewModel viewModel = new CustomEntryTypeViewModel(entryType); + EntryTypeViewModel viewModel; + if (entryTypesManager.isCustomType(entryType.getType(), mode)) { + viewModel = new CustomEntryTypeViewModel(entryType); + } else { + viewModel = new EntryTypeViewModel(entryType); + } this.entryTypesWithFields.add(viewModel); } } - public ObservableList entryTypes() { + public ObservableList entryTypes() { return this.entryTypesWithFields; } @@ -127,17 +132,17 @@ public void addNewField() { newFieldToAddProperty().setValue(null); } - public CustomEntryTypeViewModel addNewCustomEntryType() { + public EntryTypeViewModel addNewCustomEntryType() { EntryType newentryType = new UnknownEntryType(entryTypeToAdd.getValue()); BibEntryType type = new BibEntryType(newentryType, new ArrayList<>(), Collections.emptyList()); - CustomEntryTypeViewModel viewModel = new CustomEntryTypeViewModel(type); + EntryTypeViewModel viewModel = new CustomEntryTypeViewModel(type); this.entryTypesWithFields.add(viewModel); this.entryTypeToAdd.setValue(""); return viewModel; } - public ObjectProperty selectedEntryTypeProperty() { + public ObjectProperty selectedEntryTypeProperty() { return this.selectedEntryType; } @@ -161,7 +166,7 @@ public ValidationStatus fieldValidationStatus() { return fieldValidator.getValidationStatus(); } - public void removeEntryType(CustomEntryTypeViewModel focusedItem) { + public void removeEntryType(EntryTypeViewModel focusedItem) { entryTypesWithFields.remove(focusedItem); entryTypesToDelete.add(focusedItem.entryType().getValue()); } diff --git a/src/main/java/org/jabref/gui/customentrytypes/CustomEntryTypeViewModel.java b/src/main/java/org/jabref/gui/customentrytypes/CustomEntryTypeViewModel.java index 4c9de9f9416..e915318e471 100644 --- a/src/main/java/org/jabref/gui/customentrytypes/CustomEntryTypeViewModel.java +++ b/src/main/java/org/jabref/gui/customentrytypes/CustomEntryTypeViewModel.java @@ -1,64 +1,9 @@ package org.jabref.gui.customentrytypes; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; - import org.jabref.model.entry.BibEntryType; -public class CustomEntryTypeViewModel { - - private final ObjectProperty entryType = new SimpleObjectProperty<>(); - private final ObservableList fields; - +public class CustomEntryTypeViewModel extends EntryTypeViewModel { public CustomEntryTypeViewModel(BibEntryType entryType) { - this.entryType.set(entryType); - - List allFieldsForType = entryType.getAllBibFields().stream().map(bibField -> new FieldViewModel(bibField.getField(), entryType.isRequired(bibField.getField()), bibField.getPriority())).collect(Collectors.toList()); - fields = FXCollections.observableArrayList((allFieldsForType)); - } - - @Override - public int hashCode() { - return Objects.hash(entryType, fields); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof CustomEntryTypeViewModel)) { - return false; - } - CustomEntryTypeViewModel other = (CustomEntryTypeViewModel) obj; - return Objects.equals(entryType, other.entryType) && Objects.equals(fields, other.fields); - } - - public void addField(FieldViewModel field) { - this.fields.add(field); - } - - public ObservableList fields() { - return this.fields; - } - - public ObjectProperty entryType() { - return this.entryType; + super(entryType); } - - public void removeField(FieldViewModel focusedItem) { - this.fields.remove(focusedItem); - } - - @Override - public String toString() { - return "CustomEntryTypeViewModel [entryType=" + entryType + ", fields=" + fields + "]"; - } - } diff --git a/src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryTypeDialogView.java b/src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryTypeDialogView.java index 38ecf48af78..59b61392554 100644 --- a/src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryTypeDialogView.java +++ b/src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryTypeDialogView.java @@ -49,9 +49,9 @@ public class CustomizeEntryTypeDialogView extends BaseDialog { private final BibDatabaseMode mode; private final BibEntryTypesManager entryTypesManager; - @FXML private TableView entryTypes; - @FXML private TableColumn entryTypColumn; - @FXML private TableColumn entryTypeActionsColumn; + @FXML private TableView entryTypes; + @FXML private TableColumn entryTypColumn; + @FXML private TableColumn entryTypeActionsColumn; @FXML private TextField addNewEntryType; @FXML private TableView fields; @FXML private TableColumn fieldNameColumn; @@ -117,10 +117,29 @@ private void setupTable() { entryTypeActionsColumn.setSortable(false); entryTypeActionsColumn.setReorderable(false); entryTypeActionsColumn.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue().entryType().get().getType().getDisplayName())); - new ValueTableCellFactory() - .withGraphic(item -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) - .withTooltip(name -> Localization.lang("Remove entry type") + " " + name) - .withOnMouseClickedEvent(item -> evt -> viewModel.removeEntryType(entryTypes.getSelectionModel().getSelectedItem())) + new ValueTableCellFactory() + .withGraphic((type, name) -> { + if (type instanceof CustomEntryTypeViewModel) { + return IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode(); + } else { + return null; + } + }) + .withTooltip((type, name) -> { + if (type instanceof CustomEntryTypeViewModel) { + return (Localization.lang("Remove entry type") + " " + name); + } else { + return null; + } + }) + .withOnMouseClickedEvent((type, name) -> { + if (type instanceof CustomEntryTypeViewModel) { + return evt -> viewModel.removeEntryType(entryTypes.getSelectionModel().getSelectedItem()); + } else { + return evt -> { + }; + } + }) .install(entryTypeActionsColumn); fieldTypeColumn.setCellFactory(cellData -> new RadioButtonCell<>(EnumSet.allOf(FieldType.class))); @@ -141,12 +160,15 @@ private void setupTable() { fieldTypeActionColumn.setSortable(false); fieldTypeActionColumn.setReorderable(false); + fieldTypeActionColumn.setEditable(false); fieldTypeActionColumn.setCellValueFactory(cellData -> cellData.getValue().fieldName()); new ValueTableCellFactory() .withGraphic(item -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) .withTooltip(name -> Localization.lang("Remove field %0 from currently selected entry type", name)) - .withOnMouseClickedEvent(item -> evt -> viewModel.removeField(fields.getSelectionModel().getSelectedItem())) + .withOnMouseClickedEvent(item -> evt -> { + viewModel.removeField(fields.getSelectionModel().getSelectedItem()); + }) .install(fieldTypeActionColumn); viewModel.newFieldToAddProperty().bindBidirectional(addNewField.valueProperty()); @@ -210,7 +232,7 @@ private void handleOnDragDropped(TableRow row, FieldViewModel or @FXML void addEntryType() { - CustomEntryTypeViewModel newlyAdded = viewModel.addNewCustomEntryType(); + EntryTypeViewModel newlyAdded = viewModel.addNewCustomEntryType(); this.entryTypes.getSelectionModel().select(newlyAdded); this.entryTypes.scrollTo(newlyAdded); } diff --git a/src/main/java/org/jabref/gui/customentrytypes/EntryTypeViewModel.java b/src/main/java/org/jabref/gui/customentrytypes/EntryTypeViewModel.java new file mode 100644 index 00000000000..9b8e4fb7fd6 --- /dev/null +++ b/src/main/java/org/jabref/gui/customentrytypes/EntryTypeViewModel.java @@ -0,0 +1,64 @@ +package org.jabref.gui.customentrytypes; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import org.jabref.model.entry.BibEntryType; + +public class EntryTypeViewModel { + + private final ObjectProperty entryType = new SimpleObjectProperty<>(); + private final ObservableList fields; + + public EntryTypeViewModel(BibEntryType entryType) { + this.entryType.set(entryType); + + List allFieldsForType = entryType.getAllBibFields().stream().map(bibField -> new FieldViewModel(bibField.getField(), entryType.isRequired(bibField.getField()), bibField.getPriority())).collect(Collectors.toList()); + fields = FXCollections.observableArrayList((allFieldsForType)); + } + + @Override + public int hashCode() { + return Objects.hash(entryType, fields); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof EntryTypeViewModel)) { + return false; + } + EntryTypeViewModel other = (EntryTypeViewModel) obj; + return Objects.equals(entryType, other.entryType) && Objects.equals(fields, other.fields); + } + + public void addField(FieldViewModel field) { + this.fields.add(field); + } + + public ObservableList fields() { + return this.fields; + } + + public ObjectProperty entryType() { + return this.entryType; + } + + public void removeField(FieldViewModel focusedItem) { + this.fields.remove(focusedItem); + } + + @Override + public String toString() { + return "CustomEntryTypeViewModel [entryType=" + entryType + ", fields=" + fields + "]"; + } + +} diff --git a/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java b/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java index 3ae0a346f02..eb12f33a2f2 100644 --- a/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java +++ b/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java @@ -55,7 +55,7 @@ public static void openExternalViewer(BibDatabaseContext databaseContext, String Field field = initialField; if (StandardField.PS.equals(field) || StandardField.PDF.equals(field)) { // Find the default directory for this field type: - List directories = databaseContext.getFileDirectoriesAsPaths(Globals.prefs.getFilePreferences()); + List directories = databaseContext.getFileDirectories(Globals.prefs.getFilePreferences()); Optional file = FileHelper.find(link, directories); diff --git a/src/main/java/org/jabref/gui/desktop/os/Linux.java b/src/main/java/org/jabref/gui/desktop/os/Linux.java index e501d3ba620..e4a73faa177 100644 --- a/src/main/java/org/jabref/gui/desktop/os/Linux.java +++ b/src/main/java/org/jabref/gui/desktop/os/Linux.java @@ -1,5 +1,6 @@ package org.jabref.gui.desktop.os; +import java.awt.Desktop; import java.io.BufferedReader; import java.io.File; import java.io.IOException; @@ -8,6 +9,7 @@ import java.util.Locale; import java.util.Optional; +import org.jabref.architecture.AllowedToUseAwt; import org.jabref.gui.JabRefExecutorService; import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; @@ -16,10 +18,31 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@AllowedToUseAwt("Requires AWT to open a file with the native method") public class Linux implements NativeDesktop { private static final Logger LOGGER = LoggerFactory.getLogger(Linux.class); + private void nativeOpenFile(String filePath) { + JabRefExecutorService.INSTANCE.execute(() -> { + try { + File file = new File(filePath); + Desktop.getDesktop().open(file); + System.out.println("Open file in default application with Desktop integration"); + } catch (IllegalArgumentException e) { + System.out.println("Fail back to xdg-open"); + try { + String[] cmd = {"xdg-open", filePath}; + Runtime.getRuntime().exec(cmd); + } catch (Exception e2) { + System.out.println("Open operation not successful: " + e2); + } + } catch (IOException e) { + System.out.println("Native open operation not successful: " + e); + } + }); + } + @Override public void openFile(String filePath, String fileType) throws IOException { Optional type = ExternalFileTypes.getInstance().getExternalFileTypeByExt(fileType); @@ -27,16 +50,16 @@ public void openFile(String filePath, String fileType) throws IOException { if (type.isPresent() && !type.get().getOpenWithApplication().isEmpty()) { viewer = type.get().getOpenWithApplication(); + ProcessBuilder processBuilder = new ProcessBuilder(viewer, filePath); + Process process = processBuilder.start(); + StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), LOGGER::debug); + StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), LOGGER::debug); + + JabRefExecutorService.INSTANCE.execute(streamGobblerInput); + JabRefExecutorService.INSTANCE.execute(streamGobblerError); } else { - viewer = "xdg-open"; + nativeOpenFile(filePath); } - ProcessBuilder processBuilder = new ProcessBuilder(viewer, filePath); - Process process = processBuilder.start(); - StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), LOGGER::debug); - StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), LOGGER::debug); - - JabRefExecutorService.INSTANCE.execute(streamGobblerInput); - JabRefExecutorService.INSTANCE.execute(streamGobblerError); } @Override @@ -45,21 +68,21 @@ public void openFileWithApplication(String filePath, String application) throws String[] openWith; if ((application != null) && !application.isEmpty()) { openWith = application.split(" "); - } else { - openWith = new String[] {"xdg-open"}; - } - String[] cmdArray = new String[openWith.length + 1]; - System.arraycopy(openWith, 0, cmdArray, 0, openWith.length); - cmdArray[cmdArray.length - 1] = filePath; + String[] cmdArray = new String[openWith.length + 1]; + System.arraycopy(openWith, 0, cmdArray, 0, openWith.length); + cmdArray[cmdArray.length - 1] = filePath; - ProcessBuilder processBuilder = new ProcessBuilder(cmdArray); - Process process = processBuilder.start(); + ProcessBuilder processBuilder = new ProcessBuilder(cmdArray); + Process process = processBuilder.start(); - StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), LOGGER::debug); - StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), LOGGER::debug); + StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), LOGGER::debug); + StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), LOGGER::debug); - JabRefExecutorService.INSTANCE.execute(streamGobblerInput); - JabRefExecutorService.INSTANCE.execute(streamGobblerError); + JabRefExecutorService.INSTANCE.execute(streamGobblerInput); + JabRefExecutorService.INSTANCE.execute(streamGobblerError); + } else { + nativeOpenFile(filePath); + } } @Override diff --git a/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java b/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java index 660995a0795..7aee99be695 100644 --- a/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java +++ b/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java @@ -1,7 +1,7 @@ package org.jabref.gui.dialogs; -import org.jabref.gui.BasePanel; import org.jabref.gui.Globals; +import org.jabref.gui.LibraryTab; import org.jabref.gui.exporter.SaveDatabaseAction; import org.jabref.model.database.event.AutosaveEvent; @@ -11,21 +11,21 @@ /** * This class has an abstract UI role as it listens for an {@link AutosaveEvent} and saves the bib file associated with - * the given {@link BasePanel}. + * the given {@link LibraryTab}. */ public class AutosaveUiManager { private static final Logger LOGGER = LoggerFactory.getLogger(AutosaveUiManager.class); - private final BasePanel panel; + private final LibraryTab libraryTab; - public AutosaveUiManager(BasePanel panel) { - this.panel = panel; + public AutosaveUiManager(LibraryTab libraryTab) { + this.libraryTab = libraryTab; } @Subscribe - public void listen(@SuppressWarnings("unused") AutosaveEvent event) { + public void listen(AutosaveEvent event) { try { - new SaveDatabaseAction(panel, Globals.prefs, Globals.entryTypesManager).save(SaveDatabaseAction.SaveDatabaseMode.SILENT); + new SaveDatabaseAction(libraryTab, Globals.prefs, Globals.entryTypesManager).save(SaveDatabaseAction.SaveDatabaseMode.SILENT); } catch (Throwable e) { LOGGER.error("Problem occurred while saving.", e); } diff --git a/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java b/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java index b0bdb122ea1..230c3165e4d 100644 --- a/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java +++ b/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java @@ -11,11 +11,11 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; import org.jabref.gui.JabRefExecutorService; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.duplicationFinder.DuplicateResolverDialog.DuplicateResolverResult; @@ -131,7 +131,7 @@ private DuplicateSearchResult verifyDuplicates() { } private void askResolveStrategy(DuplicateSearchResult result, BibEntry first, BibEntry second, DuplicateResolverType resolverType) { - DuplicateResolverDialog dialog = new DuplicateResolverDialog(first, second, resolverType, frame.getCurrentBasePanel().getBibDatabaseContext(), stateManager); + DuplicateResolverDialog dialog = new DuplicateResolverDialog(first, second, resolverType, frame.getCurrentLibraryTab().getBibDatabaseContext(), stateManager); DuplicateResolverResult resolverResult = dialog.showAndWait().orElse(DuplicateResolverResult.BREAK); @@ -156,25 +156,25 @@ private void handleDuplicates(DuplicateSearchResult result) { return; } - BasePanel panel = frame.getCurrentBasePanel(); + LibraryTab libraryTab = frame.getCurrentLibraryTab(); final NamedCompound compoundEdit = new NamedCompound(Localization.lang("duplicate removal")); // Now, do the actual removal: if (!result.getToRemove().isEmpty()) { - compoundEdit.addEdit(new UndoableRemoveEntries(panel.getDatabase(), result.getToRemove())); - panel.getDatabase().removeEntries(result.getToRemove()); - panel.markBaseChanged(); + compoundEdit.addEdit(new UndoableRemoveEntries(libraryTab.getDatabase(), result.getToRemove())); + libraryTab.getDatabase().removeEntries(result.getToRemove()); + libraryTab.markBaseChanged(); } // and adding merged entries: if (!result.getToAdd().isEmpty()) { - compoundEdit.addEdit(new UndoableInsertEntries(panel.getDatabase(), result.getToAdd())); - panel.getDatabase().insertEntries(result.getToAdd()); - panel.markBaseChanged(); + compoundEdit.addEdit(new UndoableInsertEntries(libraryTab.getDatabase(), result.getToAdd())); + libraryTab.getDatabase().insertEntries(result.getToAdd()); + libraryTab.markBaseChanged(); } dialogService.notify(Localization.lang("Duplicates found") + ": " + duplicateCount.get() + ' ' + Localization.lang("pairs processed") + ": " + result.getDuplicateCount()); compoundEdit.end(); - panel.getUndoManager().addEdit(compoundEdit); + libraryTab.getUndoManager().addEdit(compoundEdit); } /** diff --git a/src/main/java/org/jabref/gui/edit/CopyMoreAction.java b/src/main/java/org/jabref/gui/edit/CopyMoreAction.java index 0d87538efb9..9ef0f292594 100644 --- a/src/main/java/org/jabref/gui/edit/CopyMoreAction.java +++ b/src/main/java/org/jabref/gui/edit/CopyMoreAction.java @@ -104,8 +104,8 @@ private void copyKey() { // Collect all non-null keys. List keys = entries.stream() - .filter(entry -> entry.getCiteKeyOptional().isPresent()) - .map(entry -> entry.getCiteKeyOptional().get()) + .filter(entry -> entry.getCitationKey().isPresent()) + .map(entry -> entry.getCitationKey().get()) .collect(Collectors.toList()); if (keys.isEmpty()) { @@ -131,8 +131,8 @@ private void copyCiteKey() { // Collect all non-null keys. List keys = entries.stream() - .filter(entry -> entry.getCiteKeyOptional().isPresent()) - .map(entry -> entry.getCiteKeyOptional().get()) + .filter(entry -> entry.getCitationKey().isPresent()) + .map(entry -> entry.getCitationKey().get()) .collect(Collectors.toList()); if (keys.isEmpty()) { @@ -161,7 +161,7 @@ private void copyKeyAndTitle() { List entries = stateManager.getSelectedEntries(); // ToDo: this string should be configurable to allow arbitrary exports - StringReader layoutString = new StringReader("\\bibtexkey - \\begin{title}\\format[RemoveBrackets]{\\title}\\end{title}\n"); + 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(); @@ -175,7 +175,7 @@ private void copyKeyAndTitle() { int entriesWithKeys = 0; // Collect all non-null keys. for (BibEntry entry : entries) { - if (entry.hasCiteKey()) { + if (entry.hasCitationKey()) { entriesWithKeys++; keyAndTitle.append(layout.doLayout(entry, stateManager.getActiveDatabase().get().getDatabase())); } @@ -207,9 +207,10 @@ private void copyKeyAndLink() { List entries = stateManager.getSelectedEntries(); StringBuilder keyAndLink = new StringBuilder(); + StringBuilder fallbackString = new StringBuilder(); List entriesWithKey = entries.stream() - .filter(BibEntry::hasCiteKey) + .filter(BibEntry::hasCitationKey) .collect(Collectors.toList()); if (entriesWithKey.isEmpty()) { @@ -218,13 +219,15 @@ private void copyKeyAndLink() { } for (BibEntry entry : entriesWithKey) { - String key = entry.getCiteKeyOptional().get(); + String key = entry.getCitationKey().get(); String url = entry.getField(StandardField.URL).orElse(""); keyAndLink.append(url.isEmpty() ? key : String.format("%s", url, key)); keyAndLink.append(OS.NEWLINE); + fallbackString.append(url.isEmpty() ? key : String.format("%s - %s", key, url)); + fallbackString.append(OS.NEWLINE); } - clipBoardManager.setHtmlContent(keyAndLink.toString()); + clipBoardManager.setHtmlContent(keyAndLink.toString(), fallbackString.toString()); if (entriesWithKey.size() == entries.size()) { // All entries had keys. diff --git a/src/main/java/org/jabref/gui/edit/EditAction.java b/src/main/java/org/jabref/gui/edit/EditAction.java index 4e983c27726..5cdebd07b14 100644 --- a/src/main/java/org/jabref/gui/edit/EditAction.java +++ b/src/main/java/org/jabref/gui/edit/EditAction.java @@ -8,6 +8,7 @@ import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.actions.StandardActions; +import org.fxmisc.richtext.CodeArea; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,49 +44,31 @@ public String toString() { @Override public void execute() { stateManager.getFocusOwner().ifPresent(focusOwner -> { - LOGGER.debug("EditAction - focusOwner: {}; Action: {}", focusOwner.toString(), action.getText()); + LOGGER.debug("focusOwner: {}; Action: {}", focusOwner.toString(), action.getText()); if (focusOwner instanceof TextInputControl) { // Focus is on text field -> copy/paste/cut selected text TextInputControl textInput = (TextInputControl) focusOwner; + // DELETE_ENTRY in text field should do forward delete switch (action) { - case COPY: - textInput.copy(); - break; - case CUT: - textInput.cut(); - break; - case PASTE: - textInput.paste(); - break; - case DELETE_ENTRY: - // DELETE_ENTRY in text field should do forward delete - textInput.deleteNextChar(); - break; - default: - throw new IllegalStateException("Only cut/copy/paste supported in TextInputControl but got " + action); + case COPY -> textInput.copy(); + case CUT -> textInput.cut(); + case PASTE -> textInput.paste(); + case DELETE_ENTRY -> textInput.deleteNextChar(); + default -> throw new IllegalStateException("Only cut/copy/paste supported in TextInputControl but got " + action); } - } else { + } else if (!(focusOwner instanceof CodeArea)) { - LOGGER.debug("EditAction - Else: {}", frame.getCurrentBasePanel().getTabTitle()); + LOGGER.debug("Else: {}", focusOwner.getClass().getSimpleName()); // Not sure what is selected -> copy/paste/cut selected entries - // ToDo: Should be handled by BibDatabaseContext instead of BasePanel + // ToDo: Should be handled by BibDatabaseContext instead of LibraryTab switch (action) { - case COPY: - frame.getCurrentBasePanel().copy(); - break; - case CUT: - frame.getCurrentBasePanel().cut(); - break; - case PASTE: - frame.getCurrentBasePanel().paste(); - break; - case DELETE_ENTRY: - frame.getCurrentBasePanel().delete(false); - break; - default: - throw new IllegalStateException("Only cut/copy/paste supported but got " + action); + case COPY -> frame.getCurrentLibraryTab().copy(); + case CUT -> frame.getCurrentLibraryTab().cut(); + case PASTE -> frame.getCurrentLibraryTab().paste(); + case DELETE_ENTRY -> frame.getCurrentLibraryTab().delete(false); + default -> throw new IllegalStateException("Only cut/copy/paste supported but got " + action); } } }); diff --git a/src/main/java/org/jabref/gui/edit/MassSetFieldsDialog.java b/src/main/java/org/jabref/gui/edit/MassSetFieldsDialog.java index 496c9f648c6..785b571ceb3 100644 --- a/src/main/java/org/jabref/gui/edit/MassSetFieldsDialog.java +++ b/src/main/java/org/jabref/gui/edit/MassSetFieldsDialog.java @@ -62,7 +62,7 @@ public class MassSetFieldsDialog extends BaseDialog { this.undoManager = undoManager; init(); - this.setTitle("Set/clear/append/rename fields"); + this.setTitle(Localization.lang("Manage field names & content")); this.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); this.setResultConverter(button -> { if (button == ButtonType.OK) { diff --git a/src/main/java/org/jabref/gui/edit/ReplaceStringAction.java b/src/main/java/org/jabref/gui/edit/ReplaceStringAction.java index 33b016ce9e7..7b8b9d4a288 100644 --- a/src/main/java/org/jabref/gui/edit/ReplaceStringAction.java +++ b/src/main/java/org/jabref/gui/edit/ReplaceStringAction.java @@ -16,7 +16,7 @@ public ReplaceStringAction(JabRefFrame frame, StateManager stateManager) { @Override public void execute() { - ReplaceStringView dialog = new ReplaceStringView(frame.getCurrentBasePanel()); + ReplaceStringView dialog = new ReplaceStringView(frame.getCurrentLibraryTab()); dialog.showAndWait(); } } diff --git a/src/main/java/org/jabref/gui/edit/ReplaceStringView.java b/src/main/java/org/jabref/gui/edit/ReplaceStringView.java index deb4c15c3d9..a931406c99c 100644 --- a/src/main/java/org/jabref/gui/edit/ReplaceStringView.java +++ b/src/main/java/org/jabref/gui/edit/ReplaceStringView.java @@ -6,7 +6,7 @@ import javafx.scene.control.RadioButton; import javafx.scene.control.TextField; -import org.jabref.gui.BasePanel; +import org.jabref.gui.LibraryTab; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.ControlHelper; import org.jabref.gui.util.IconValidationDecorator; @@ -28,10 +28,10 @@ public class ReplaceStringView extends BaseDialog { private final ControlsFxVisualizer visualizer = new ControlsFxVisualizer(); - public ReplaceStringView(BasePanel basePanel) { + public ReplaceStringView(LibraryTab libraryTab) { this.setTitle(Localization.lang("Replace String")); - viewModel = new ReplaceStringViewModel(basePanel); + viewModel = new ReplaceStringViewModel(libraryTab); ViewLoader.view(this) .load() diff --git a/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java b/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java index a7a534c9603..04db4bf9147 100644 --- a/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java +++ b/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java @@ -9,7 +9,7 @@ import javafx.beans.property.StringProperty; import org.jabref.gui.AbstractViewModel; -import org.jabref.gui.BasePanel; +import org.jabref.gui.LibraryTab; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableFieldChange; import org.jabref.logic.l10n.Localization; @@ -22,7 +22,7 @@ public class ReplaceStringViewModel extends AbstractViewModel { private String findString; private String replaceString; private Set fields; - private BasePanel panel; + private LibraryTab panel; private StringProperty findStringProperty = new SimpleStringProperty(); private StringProperty replaceStringProperty = new SimpleStringProperty(); @@ -30,9 +30,9 @@ public class ReplaceStringViewModel extends AbstractViewModel { private BooleanProperty allFieldReplaceProperty = new SimpleBooleanProperty(); private BooleanProperty selectOnlyProperty = new SimpleBooleanProperty(); - public ReplaceStringViewModel(BasePanel basePanel) { - Objects.requireNonNull(basePanel); - this.panel = basePanel; + public ReplaceStringViewModel(LibraryTab libraryTab) { + Objects.requireNonNull(libraryTab); + this.panel = libraryTab; } public int replace() { diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index 068e9ecd41d..7f05300ff00 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -25,9 +25,9 @@ import javafx.scene.input.TransferMode; import javafx.scene.layout.BorderPane; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; +import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.citationkeypattern.GenerateCitationKeySingleAction; import org.jabref.gui.entryeditor.fileannotationtab.FileAnnotationTab; @@ -69,7 +69,7 @@ public class EntryEditor extends BorderPane { private static final Logger LOGGER = LoggerFactory.getLogger(EntryEditor.class); - private final BasePanel panel; + private final LibraryTab libraryTab; private final BibDatabaseContext databaseContext; private final EntryEditorPreferences entryEditorPreferences; private final ExternalFilesEntryLinker fileLinker; @@ -100,9 +100,9 @@ public class EntryEditor extends BorderPane { @Inject private CountingUndoManager undoManager; private final List entryEditorTabs = new LinkedList<>(); - public EntryEditor(BasePanel panel, ExternalFileTypes externalFileTypes) { - this.panel = panel; - this.databaseContext = panel.getBibDatabaseContext(); + public EntryEditor(LibraryTab libraryTab, ExternalFileTypes externalFileTypes) { + this.libraryTab = libraryTab; + this.databaseContext = libraryTab.getBibDatabaseContext(); ViewLoader.view(this) .root(this) @@ -137,18 +137,18 @@ public EntryEditor(BasePanel panel, ExternalFileTypes externalFileTypes) { if (event.getDragboard().hasContent(DataFormat.FILES)) { List files = event.getDragboard().getFiles().stream().map(File::toPath).collect(Collectors.toList()); switch (event.getTransferMode()) { - case COPY: + case COPY -> { LOGGER.debug("Mode COPY"); fileLinker.copyFilesToFileDirAndAddToEntry(entry, files); - break; - case MOVE: + } + case MOVE -> { LOGGER.debug("Mode MOVE"); fileLinker.moveFilesToFileDirAndAddToEntry(entry, files); - break; - case LINK: + } + case LINK -> { LOGGER.debug("Mode LINK"); fileLinker.addFilesToEntry(entry, files); - break; + } } success = true; } @@ -177,11 +177,11 @@ private void setupKeyBindings() { event.consume(); break; case ENTRY_EDITOR_NEXT_ENTRY: - panel.selectNextEntry(); + libraryTab.selectNextEntry(); event.consume(); break; case ENTRY_EDITOR_PREVIOUS_ENTRY: - panel.selectPreviousEntry(); + libraryTab.selectPreviousEntry(); event.consume(); break; case HELP: @@ -202,12 +202,12 @@ private void setupKeyBindings() { @FXML public void close() { - panel.entryEditorClosing(); + libraryTab.entryEditorClosing(); } @FXML private void deleteEntry() { - panel.delete(entry); + libraryTab.delete(entry); } @FXML @@ -219,12 +219,12 @@ void generateCiteKeyButton() { @FXML private void navigateToPreviousEntry() { - panel.selectPreviousEntry(); + libraryTab.selectPreviousEntry(); } @FXML private void navigateToNextEntry() { - panel.selectNextEntry(); + libraryTab.selectNextEntry(); } private List createTabs() { @@ -232,24 +232,24 @@ private List createTabs() { entryEditorTabs.add(new PreviewTab(databaseContext, dialogService, Globals.prefs, ExternalFileTypes.getInstance())); // Required fields - entryEditorTabs.add(new RequiredFieldsTab(databaseContext, panel.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); + entryEditorTabs.add(new RequiredFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); // Optional fields - entryEditorTabs.add(new OptionalFieldsTab(databaseContext, panel.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); - entryEditorTabs.add(new OptionalFields2Tab(databaseContext, panel.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); - entryEditorTabs.add(new DeprecatedFieldsTab(databaseContext, panel.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); + entryEditorTabs.add(new OptionalFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); + entryEditorTabs.add(new OptionalFields2Tab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); + entryEditorTabs.add(new DeprecatedFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); // Other fields - entryEditorTabs.add(new OtherFieldsTab(databaseContext, panel.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); + entryEditorTabs.add(new OtherFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); // General fields from preferences for (Map.Entry> tab : entryEditorPreferences.getEntryEditorTabList().entrySet()) { - entryEditorTabs.add(new UserDefinedFieldsTab(tab.getKey(), tab.getValue(), databaseContext, panel.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); + entryEditorTabs.add(new UserDefinedFieldsTab(tab.getKey(), tab.getValue(), databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, Globals.prefs, Globals.entryTypesManager, ExternalFileTypes.getInstance(), Globals.TASK_EXECUTOR, Globals.journalAbbreviationRepository)); } // Special tabs entryEditorTabs.add(new MathSciNetTab()); - entryEditorTabs.add(new FileAnnotationTab(panel.getAnnotationCache())); + entryEditorTabs.add(new FileAnnotationTab(libraryTab.getAnnotationCache())); entryEditorTabs.add(new RelatedArticlesTab(this, entryEditorPreferences, dialogService)); // Source tab @@ -361,7 +361,7 @@ private void setupToolBar() { } private void fetchAndMerge(EntryBasedFetcher fetcher) { - new FetchAndMergeEntry(panel, taskExecutor).fetchAndMerge(entry, fetcher); + new FetchAndMergeEntry(libraryTab, taskExecutor).fetchAndMerge(entry, fetcher); } public void setFocusToField(Field field) { diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java index 82d2fea3b06..278c9a85427 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java @@ -14,6 +14,7 @@ public class EntryEditorPreferences { private final boolean shouldShowLatexCitationsTab; private boolean showSourceTabByDefault; private boolean enableValidation; + private double dividerPosition; public EntryEditorPreferences(Map> entryEditorTabList, boolean shouldOpenOnNewEntry, @@ -21,7 +22,8 @@ public EntryEditorPreferences(Map> entryEditorTabList, boolean isMrdlibAccepted, boolean shouldShowLatexCitationsTab, boolean showSourceTabByDefault, - boolean enableValidation) { + boolean enableValidation, + double dividerPosition) { this.entryEditorTabList = entryEditorTabList; this.shouldOpenOnNewEntry = shouldOpenOnNewEntry; @@ -30,6 +32,7 @@ public EntryEditorPreferences(Map> entryEditorTabList, this.shouldShowLatexCitationsTab = shouldShowLatexCitationsTab; this.showSourceTabByDefault = showSourceTabByDefault; this.enableValidation = enableValidation; + this.dividerPosition = dividerPosition; } public Map> getEntryEditorTabList() { @@ -59,4 +62,13 @@ public boolean shouldShowLatexCitationsTab() { public boolean isEnableValidation() { return enableValidation; } + + public double getDividerPosition() { + return dividerPosition; + } + + public EntryEditorPreferences withDividerPosition(double dividerPosition) { + this.dividerPosition = dividerPosition; + return this; + } } diff --git a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java index 760ad6edf80..03dc9fa06ab 100644 --- a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java +++ b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java @@ -76,7 +76,7 @@ public void init(BibEntry entry) { cancelSearch(); currentEntry = entry; - Optional citeKey = entry.getCiteKeyOptional(); + Optional citeKey = entry.getCitationKey(); if (citeKey.isPresent()) { startSearch(citeKey.get()); diff --git a/src/main/java/org/jabref/gui/entryeditor/OpenEntryEditorAction.java b/src/main/java/org/jabref/gui/entryeditor/OpenEntryEditorAction.java index e6e759880e7..4fe0a8101b4 100644 --- a/src/main/java/org/jabref/gui/entryeditor/OpenEntryEditorAction.java +++ b/src/main/java/org/jabref/gui/entryeditor/OpenEntryEditorAction.java @@ -19,7 +19,7 @@ public OpenEntryEditorAction(JabRefFrame frame, StateManager stateManager) { public void execute() { if (!stateManager.getSelectedEntries().isEmpty()) { - frame.getCurrentBasePanel().showAndEdit(stateManager.getSelectedEntries().get(0)); + frame.getCurrentLibraryTab().showAndEdit(stateManager.getSelectedEntries().get(0)); } } } diff --git a/src/main/java/org/jabref/gui/entryeditor/PreviewSwitchAction.java b/src/main/java/org/jabref/gui/entryeditor/PreviewSwitchAction.java index 03247c4fdd0..366adcc70f8 100644 --- a/src/main/java/org/jabref/gui/entryeditor/PreviewSwitchAction.java +++ b/src/main/java/org/jabref/gui/entryeditor/PreviewSwitchAction.java @@ -23,9 +23,9 @@ public PreviewSwitchAction(Direction direction, JabRefFrame frame, StateManager @Override public void execute() { if (direction == Direction.NEXT) { - frame.getCurrentBasePanel().getEntryEditor().nextPreviewStyle(); + frame.getCurrentLibraryTab().getEntryEditor().nextPreviewStyle(); } else { - frame.getCurrentBasePanel().getEntryEditor().previousPreviewStyle(); + frame.getCurrentLibraryTab().getEntryEditor().previousPreviewStyle(); } } } diff --git a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java index d413eb07bfc..9161758a58d 100644 --- a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java @@ -18,6 +18,7 @@ import javafx.scene.control.ContextMenu; import javafx.scene.control.Tooltip; import javafx.scene.input.InputMethodRequests; +import javafx.scene.input.KeyEvent; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; @@ -26,6 +27,7 @@ import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.actions.StandardActions; import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.keyboard.CodeAreaKeyBindings; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.undo.CountingUndoManager; import org.jabref.gui.undo.NamedCompound; @@ -85,18 +87,10 @@ public EditAction(StandardActions command) { @Override public void execute() { switch (command) { - case COPY: - codeArea.copy(); - break; - case CUT: - codeArea.cut(); - break; - case PASTE: - codeArea.paste(); - break; - case SELECT_ALL: - codeArea.selectAll(); - break; + case COPY -> codeArea.copy(); + case CUT -> codeArea.cut(); + case PASTE -> codeArea.paste(); + case SELECT_ALL -> codeArea.selectAll(); } codeArea.requestFocus(); } @@ -133,7 +127,7 @@ private void highlightSearchPattern() { } } - private static String getSourceString(BibEntry entry, BibDatabaseMode type, FieldWriterPreferences fieldWriterPreferences) throws IOException { + private String getSourceString(BibEntry entry, BibDatabaseMode type, FieldWriterPreferences fieldWriterPreferences) throws IOException { StringWriter stringWriter = new StringWriter(200); FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldWriterPreferences); new BibEntryWriter(fieldWriter, Globals.entryTypesManager).writeWithoutPrependedNewlines(entry, stringWriter, type); @@ -178,6 +172,7 @@ private void setupSourceEditor() { } }); codeArea.setId("bibtexSourceCodeArea"); + codeArea.addEventFilter(KeyEvent.KEY_PRESSED, event -> CodeAreaKeyBindings.call(codeArea, event)); ActionFactory factory = new ActionFactory(keyBindingRepository); ContextMenu contextMenu = new ContextMenu(); @@ -224,6 +219,7 @@ private void updateCodeArea() { codeArea.clear(); try { codeArea.appendText(getSourceString(currentEntry, mode, fieldWriterPreferences)); + codeArea.setEditable(true); highlightSearchPattern(); } catch (IOException ex) { codeArea.setEditable(false); @@ -278,10 +274,10 @@ private void storeSource(BibEntry outOfFocusEntry, String text) { NamedCompound compound = new NamedCompound(Localization.lang("source edit")); BibEntry newEntry = database.getEntries().get(0); - String newKey = newEntry.getCiteKeyOptional().orElse(null); + String newKey = newEntry.getCitationKey().orElse(null); if (newKey != null) { - outOfFocusEntry.setCiteKey(newKey); + outOfFocusEntry.setCitationKey(newKey); } else { outOfFocusEntry.clearCiteKey(); } diff --git a/src/main/java/org/jabref/gui/exporter/ExportCommand.java b/src/main/java/org/jabref/gui/exporter/ExportCommand.java index 70f4e5adca0..ef726e448fb 100644 --- a/src/main/java/org/jabref/gui/exporter/ExportCommand.java +++ b/src/main/java/org/jabref/gui/exporter/ExportCommand.java @@ -82,16 +82,16 @@ private void export(Path file, FileChooser.ExtensionFilter selectedExtensionFilt List entries; if (selectedOnly) { // Selected entries - entries = frame.getCurrentBasePanel().getSelectedEntries(); + entries = frame.getCurrentLibraryTab().getSelectedEntries(); } else { // All entries - entries = frame.getCurrentBasePanel().getDatabase().getEntries(); + entries = frame.getCurrentLibraryTab().getDatabase().getEntries(); } // Set the global variable for this database's file directory before exporting, // so formatters can resolve linked files correctly. // (This is an ugly hack!) - Globals.prefs.fileDirForDatabase = frame.getCurrentBasePanel() + Globals.prefs.fileDirForDatabase = frame.getCurrentLibraryTab() .getBibDatabaseContext() .getFileDirectories(Globals.prefs.getFilePreferences()); @@ -103,9 +103,9 @@ private void export(Path file, FileChooser.ExtensionFilter selectedExtensionFilt final List finEntries = entries; BackgroundTask .wrap(() -> { - format.export(frame.getCurrentBasePanel().getBibDatabaseContext(), + format.export(frame.getCurrentLibraryTab().getBibDatabaseContext(), file, - frame.getCurrentBasePanel() + frame.getCurrentLibraryTab() .getBibDatabaseContext() .getMetaData() .getEncoding() diff --git a/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java b/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java index b40ff5cb952..b5750b815e5 100644 --- a/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java +++ b/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java @@ -13,11 +13,11 @@ import javafx.scene.input.ClipboardContent; -import org.jabref.gui.BasePanel; import org.jabref.gui.ClipBoardManager; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.TaskExecutor; @@ -42,7 +42,7 @@ public class ExportToClipboardAction extends SimpleCommand { private JabRefFrame frame; private final DialogService dialogService; - private BasePanel panel; + private LibraryTab panel; private final List entries = new ArrayList<>(); private final ExporterFactory exporterFactory; private final ClipBoardManager clipBoardManager; @@ -56,7 +56,7 @@ public ExportToClipboardAction(JabRefFrame frame, DialogService dialogService, E this.taskExecutor = taskExecutor; } - public ExportToClipboardAction(BasePanel panel, DialogService dialogService, ExporterFactory exporterFactory, ClipBoardManager clipBoardManager, TaskExecutor taskExecutor) { + public ExportToClipboardAction(LibraryTab panel, DialogService dialogService, ExporterFactory exporterFactory, ClipBoardManager clipBoardManager, TaskExecutor taskExecutor) { this.panel = panel; this.dialogService = dialogService; this.exporterFactory = exporterFactory; @@ -67,7 +67,7 @@ public ExportToClipboardAction(BasePanel panel, DialogService dialogService, Exp @Override public void execute() { if (panel == null) { - panel = frame.getCurrentBasePanel(); + panel = frame.getCurrentLibraryTab(); } if (panel.getSelectedEntries().isEmpty()) { @@ -103,7 +103,8 @@ private ExportResult exportToClipboard(Exporter exporter) throws Exception { // Set the global variable for this database's file directory before exporting, // so formatters can resolve linked files correctly. // (This is an ugly hack!) - Globals.prefs.fileDirForDatabase = panel.getBibDatabaseContext().getFileDirectoriesAsPaths(Globals.prefs.getFilePreferences()).stream().map(Path::toString).collect(Collectors.toList()); + Globals.prefs.fileDirForDatabase = panel.getBibDatabaseContext() + .getFileDirectories(Globals.prefs.getFilePreferences()); // Add chosen export type to last used pref, to become default Globals.prefs.put(JabRefPreferences.LAST_USED_EXPORT, exporter.getName()); diff --git a/src/main/java/org/jabref/gui/exporter/SaveAction.java b/src/main/java/org/jabref/gui/exporter/SaveAction.java index 2a80a25148d..80061fa220c 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveAction.java @@ -30,22 +30,14 @@ public SaveAction(SaveMethod saveMethod, JabRefFrame frame, StateManager stateMa @Override public void execute() { SaveDatabaseAction saveDatabaseAction = new SaveDatabaseAction( - frame.getCurrentBasePanel(), + frame.getCurrentLibraryTab(), Globals.prefs, Globals.entryTypesManager); switch (saveMethod) { - case SAVE: - saveDatabaseAction.save(); - break; - case SAVE_AS: - saveDatabaseAction.saveAs(); - break; - case SAVE_SELECTED: - saveDatabaseAction.saveSelectedAsPlain(); - break; - default: - // Never happens + case SAVE -> saveDatabaseAction.save(); + case SAVE_AS -> saveDatabaseAction.saveAs(); + case SAVE_SELECTED -> saveDatabaseAction.saveSelectedAsPlain(); } } } diff --git a/src/main/java/org/jabref/gui/exporter/SaveAllAction.java b/src/main/java/org/jabref/gui/exporter/SaveAllAction.java index 4c2c1dd69a3..dd115d79ec8 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveAllAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveAllAction.java @@ -1,9 +1,9 @@ package org.jabref.gui.exporter; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.Globals; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.actions.SimpleCommand; import org.jabref.logic.l10n.Localization; @@ -21,8 +21,8 @@ public SaveAllAction(JabRefFrame frame) { public void execute() { dialogService.notify(Localization.lang("Saving all libraries...")); - for (BasePanel panel : frame.getBasePanelList()) { - SaveDatabaseAction saveDatabaseAction = new SaveDatabaseAction(panel, Globals.prefs, Globals.entryTypesManager); + for (LibraryTab libraryTab : frame.getLibraryTabs()) { + SaveDatabaseAction saveDatabaseAction = new SaveDatabaseAction(libraryTab, Globals.prefs, Globals.entryTypesManager); boolean saveResult = saveDatabaseAction.save(); if (!saveResult) { dialogService.notify(Localization.lang("Could not save file.")); diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 42c5a572c48..7e98241ad7d 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -14,9 +14,9 @@ import javafx.scene.layout.VBox; import javafx.scene.text.Text; -import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; import org.jabref.gui.dialogs.AutosaveUiManager; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.FileDialogConfiguration; @@ -49,7 +49,7 @@ public class SaveDatabaseAction { private static final Logger LOGGER = LoggerFactory.getLogger(SaveDatabaseAction.class); - private final BasePanel panel; + private final LibraryTab libraryTab; private final JabRefFrame frame; private final DialogService dialogService; private final JabRefPreferences preferences; @@ -59,20 +59,20 @@ public enum SaveDatabaseMode { SILENT, NORMAL } - public SaveDatabaseAction(BasePanel panel, JabRefPreferences preferences, BibEntryTypesManager entryTypesManager) { - this.panel = panel; - this.frame = panel.frame(); + public SaveDatabaseAction(LibraryTab libraryTab, JabRefPreferences preferences, BibEntryTypesManager entryTypesManager) { + this.libraryTab = libraryTab; + this.frame = libraryTab.frame(); this.dialogService = frame.getDialogService(); this.preferences = preferences; this.entryTypesManager = entryTypesManager; } public boolean save() { - return save(panel.getBibDatabaseContext(), SaveDatabaseMode.NORMAL); + return save(libraryTab.getBibDatabaseContext(), SaveDatabaseMode.NORMAL); } public boolean save(SaveDatabaseMode mode) { - return save(panel.getBibDatabaseContext(), mode); + return save(libraryTab.getBibDatabaseContext(), mode); } /** @@ -105,7 +105,7 @@ public void saveSelectedAsPlain() { * @return true on successful save */ boolean saveAs(Path file, SaveDatabaseMode mode) { - BibDatabaseContext context = panel.getBibDatabaseContext(); + BibDatabaseContext context = libraryTab.getBibDatabaseContext(); // Close AutosaveManager and BackupManager for original library Optional databasePath = context.getDatabasePath(); @@ -129,13 +129,13 @@ boolean saveAs(Path file, SaveDatabaseMode mode) { // we managed to successfully save the file // thus, we can store the store the path into the context context.setDatabasePath(file); - frame.refreshTitleAndTabs(); + // FIXME: We need to refresh the tab titles // Reinstall AutosaveManager and BackupManager for the new file name - panel.resetChangeMonitorAndChangePane(); + libraryTab.resetChangeMonitorAndChangePane(); if (readyForAutosave(context)) { AutosaveManager autosaver = AutosaveManager.start(context); - autosaver.registerListener(new AutosaveUiManager(panel)); + autosaver.registerListener(new AutosaveUiManager(libraryTab)); } if (readyForBackup(context)) { BackupManager.start(context, entryTypesManager, preferences); @@ -168,7 +168,7 @@ private boolean save(BibDatabaseContext bibDatabaseContext, SaveDatabaseMode mod Optional databasePath = bibDatabaseContext.getDatabasePath(); if (databasePath.isEmpty()) { Optional savePath = askForSavePath(); - if (!savePath.isPresent()) { + if (savePath.isEmpty()) { return false; } return saveAs(savePath.get(), mode); @@ -182,27 +182,21 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { dialogService.notify(String.format("%s...", Localization.lang("Saving library"))); } - panel.setSaving(true); + libraryTab.setSaving(true); try { - Charset encoding = panel.getBibDatabaseContext() - .getMetaData() - .getEncoding() - .orElse(preferences.getDefaultEncoding()); + Charset encoding = libraryTab.getBibDatabaseContext() + .getMetaData() + .getEncoding() + .orElse(preferences.getDefaultEncoding()); // Make sure to remember which encoding we used. - panel.getBibDatabaseContext().getMetaData().setEncoding(encoding, ChangePropagation.DO_NOT_POST_EVENT); + libraryTab.getBibDatabaseContext().getMetaData().setEncoding(encoding, ChangePropagation.DO_NOT_POST_EVENT); // Save the database boolean success = saveDatabase(targetPath, false, encoding, SavePreferences.DatabaseSaveType.ALL); if (success) { - panel.getUndoManager().markUnchanged(); - // After a successful save the following statement marks that the base is unchanged since last save - panel.setNonUndoableChange(false); - panel.setBaseChanged(false); - - frame.setTabTitle(panel, panel.getTabTitle(), targetPath.toAbsolutePath().toString()); - frame.setWindowTitle(); - frame.updateAllTabTitles(); + libraryTab.getUndoManager().markUnchanged(); + libraryTab.resetChangedProperties(); } return success; } catch (SaveException ex) { @@ -211,7 +205,7 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { return false; } finally { // release panel from save status - panel.setSaving(false); + libraryTab.setSaving(false); } } @@ -223,12 +217,12 @@ private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding, BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(fileWriter, preferences, entryTypesManager); if (selectedOnly) { - databaseWriter.savePartOfDatabase(panel.getBibDatabaseContext(), panel.getSelectedEntries()); + databaseWriter.savePartOfDatabase(libraryTab.getBibDatabaseContext(), libraryTab.getSelectedEntries()); } else { - databaseWriter.saveDatabase(panel.getBibDatabaseContext()); + databaseWriter.saveDatabase(libraryTab.getBibDatabaseContext()); } - panel.registerUndoableChanges(databaseWriter.getSaveActionsFieldChanges()); + libraryTab.registerUndoableChanges(databaseWriter.getSaveActionsFieldChanges()); if (fileWriter.hasEncodingProblems()) { saveWithDifferentEncoding(file, selectedOnly, preferences.getEncoding(), fileWriter.getEncodingProblems(), saveType); @@ -262,7 +256,7 @@ private void saveWithDifferentEncoding(Path file, boolean selectedOnly, Charset Optional newEncoding = dialogService.showChoiceDialogAndWait(Localization.lang("Save library"), Localization.lang("Select new encoding"), Localization.lang("Save library"), encoding, Encodings.getCharsets()); if (newEncoding.isPresent()) { // Make sure to remember which encoding we used. - panel.getBibDatabaseContext().getMetaData().setEncoding(newEncoding.get(), ChangePropagation.DO_NOT_POST_EVENT); + libraryTab.getBibDatabaseContext().getMetaData().setEncoding(newEncoding.get(), ChangePropagation.DO_NOT_POST_EVENT); saveDatabase(file, selectedOnly, newEncoding.get(), saveType); } diff --git a/src/main/java/org/jabref/gui/exporter/WriteXMPAction.java b/src/main/java/org/jabref/gui/exporter/WriteXMPAction.java index 0ac0918fb09..51570c8fdfa 100644 --- a/src/main/java/org/jabref/gui/exporter/WriteXMPAction.java +++ b/src/main/java/org/jabref/gui/exporter/WriteXMPAction.java @@ -117,7 +117,7 @@ private void writeXMP() { .collect(Collectors.toList()); Platform.runLater(() -> optionsDialog.getProgressArea() - .appendText(entry.getCiteKeyOptional().orElse(Localization.lang("undefined")) + "\n")); + .appendText(entry.getCitationKey().orElse(Localization.lang("undefined")) + "\n")); if (files.isEmpty()) { skipped++; diff --git a/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java b/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java index 70927daa0a6..2bc5a42b410 100644 --- a/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java +++ b/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java @@ -37,7 +37,7 @@ public class AutoSetFileLinksUtil { private ExternalFileTypes externalFileTypes; public AutoSetFileLinksUtil(BibDatabaseContext databaseContext, FilePreferences filePreferences, AutoLinkPreferences autoLinkPreferences, ExternalFileTypes externalFileTypes) { - this(databaseContext.getFileDirectoriesAsPaths(filePreferences), autoLinkPreferences, externalFileTypes); + this(databaseContext.getFileDirectories(filePreferences), autoLinkPreferences, externalFileTypes); } private AutoSetFileLinksUtil(List directories, AutoLinkPreferences autoLinkPreferences, ExternalFileTypes externalFileTypes) { diff --git a/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java b/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java index 1ec8ca2ee1c..d63f3ff7eda 100644 --- a/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java +++ b/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java @@ -126,7 +126,7 @@ private void downloadFullTexts(Map> downloads, BibDataba addLinkedFileFromURL(databaseContext, result.get(), entry, dir.get()); } else { dialogService.notify(Localization.lang("No full text document found for entry %0.", - entry.getCiteKeyOptional().orElse(Localization.lang("undefined")))); + entry.getCitationKey().orElse(Localization.lang("undefined")))); } } } @@ -160,15 +160,15 @@ private void addLinkedFileFromURL(BibDatabaseContext databaseContext, URL url, B downloadTask.onSuccess(destination -> { LinkedFile downloadedFile = LinkedFilesEditorViewModel.fromFile( destination, - databaseContext.getFileDirectoriesAsPaths(preferences.getFilePreferences()), + databaseContext.getFileDirectories(preferences.getFilePreferences()), ExternalFileTypes.getInstance()); entry.addFile(downloadedFile); dialogService.notify(Localization.lang("Finished downloading full text document for entry %0.", - entry.getCiteKeyOptional().orElse(Localization.lang("undefined")))); + entry.getCitationKey().orElse(Localization.lang("undefined")))); }); downloadTask.titleProperty().set(Localization.lang("Downloading")); downloadTask.messageProperty().set( - Localization.lang("Fulltext for") + ": " + entry.getCiteKeyOptional().orElse(Localization.lang("New entry"))); + Localization.lang("Fulltext for") + ": " + entry.getCitationKey().orElse(Localization.lang("New entry"))); downloadTask.showToUser(true); Globals.TASK_EXECUTOR.execute(downloadTask); } catch (MalformedURLException exception) { @@ -176,7 +176,7 @@ private void addLinkedFileFromURL(BibDatabaseContext databaseContext, URL url, B } } else { dialogService.notify(Localization.lang("Full text document for entry %0 already linked.", - entry.getCiteKeyOptional().orElse(Localization.lang("undefined")))); + entry.getCitationKey().orElse(Localization.lang("undefined")))); } } } diff --git a/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java b/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java index 2c5dfa66571..dafdf500cf4 100644 --- a/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java +++ b/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java @@ -56,8 +56,8 @@ public void addFilesToEntry(BibEntry entry, List files) { FileUtil.getFileExtension(file).ifPresent(ext -> { ExternalFileType type = externalFileTypes.getExternalFileTypeByExt(ext) .orElse(new UnknownExternalFileType(ext)); - Path relativePath = FileUtil.relativize(file, bibDatabaseContext.getFileDirectoriesAsPaths(filePreferences)); - LinkedFile linkedfile = new LinkedFile("", relativePath.toString(), type.getName()); + Path relativePath = FileUtil.relativize(file, bibDatabaseContext.getFileDirectories(filePreferences)); + LinkedFile linkedfile = new LinkedFile("", relativePath, type.getName()); entry.addFile(linkedfile); }); } diff --git a/src/main/java/org/jabref/gui/externalfiles/FindUnlinkedFilesDialog.java b/src/main/java/org/jabref/gui/externalfiles/FindUnlinkedFilesDialog.java index cbef3f235f6..66c97e29ef1 100644 --- a/src/main/java/org/jabref/gui/externalfiles/FindUnlinkedFilesDialog.java +++ b/src/main/java/org/jabref/gui/externalfiles/FindUnlinkedFilesDialog.java @@ -165,7 +165,7 @@ private void initialize() { buttonOptionCollapseAll.setOnAction(event -> { CheckBoxTreeItem root = (CheckBoxTreeItem) tree.getRoot(); expandTree(root, false); - root.setExpanded(true); + root.setExpanded(false); }); textfieldDirectoryPath = new TextField(); diff --git a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java index ccb21e3b936..dcb570a82cd 100644 --- a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java +++ b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java @@ -117,7 +117,7 @@ public void importEntries(List entries) { preferencesService.getOwnerPreferences(), preferencesService.getTimestampPreferences()); - // Generate bibtex keys + // Generate citation keys generateKeys(entries); // Add to group diff --git a/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditor.fxml b/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditor.fxml index 5e38d8c98ab..fcb4b34414c 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditor.fxml +++ b/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditor.fxml @@ -6,5 +6,5 @@ - -