diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000000..f140a5511af --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,85 @@ +version: 2 + +jobs: + downloadinstall4j: + docker: + - image: circleci/openjdk:8-jdk + steps: + - checkout + - run: git submodule sync + - run: git submodule update --init + - restore_cache: + keys: + - install4j-{{ checksum "scripts/extract-install4j.sh" }} + - run: scripts/download-install4j-and-jres.sh + - save_cache: + key: install4j-{{ checksum "scripts/extract-install4j.sh" }} + paths: + - "~/downloads" + - "~/.install4j7" + filters: + tags: + only: /.*/ + + buildDev: + docker: + - image: circleci/openjdk:8-jdk + steps: + - restore_cache: + key: dependency-cache + - checkout + - run: git submodule sync + - run: git submodule update --init + - restore_cache: + key: install4j-{{ checksum "scripts/extract-install4j.sh" }} + - run: scripts/extract-install4j.sh + - run: install4j7/bin/install4jc --verbose --license=$INSTALL4J_KEY + - run: ./gradlew -Pdev=true -Pinstall4jDir="install4j7" release --stacktrace + - save_cache: + key: dependency-cache + paths: + - "~/.gradle" + - store_artifacts: + path: build/releases + destination: build + - run: scripts/upload-to-builds.jabref.org.sh + + buildRelease: + docker: + - image: circleci/openjdk:8-jdk + steps: + - restore_cache: + key: dependency-cache + - checkout + - run: git submodule sync + - run: git submodule update --init + - restore_cache: + key: install4j-{{ checksum "scripts/extract-install4j.sh" }} + - run: scripts/extract-install4j.sh + - run: install4j7/bin/install4jc --verbose --license=$INSTALL4J_KEY + - run: ./gradlew -Pinstall4jDir="install4j7" release --stacktrace + - store_artifacts: + path: build/releases + destination: release + - run: scripts/upload-to-builds.jabref.org.sh + filters: + tags: + only: /.*/ + +workflows: + version: 2 + build: + jobs: + - downloadinstall4j + - buildDev: + requires: + - downloadinstall4j + - buildRelease: + requires: + - downloadinstall4j + filters: + branches: + ignore: /.*/ + tags: + only: /.*/ + diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 1426417c5bf..00000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,26 +0,0 @@ - - -- [ ] I have tested the latest master version from http://builds.jabref.org/master/ and the problem persists - - -JabRef version on - - - -Steps to reproduce: - -1. ... -2. ... -3. ... - - - -
- Log File - - ``` - Paste an excerpt of your log file here - ``` -
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..03dff7e01a6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + + + +JabRef version on + + +- [ ] I have tested the latest development version from http://builds.jabref.org/master/ and the problem persists + + + +Steps to reproduce the behavior: +1. ... +2. ... +3. ... + + + +
+ Log File + + ``` + Paste an excerpt of your log file here + ``` +
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000000..d2ae96c562d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,9 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +Please use the GitHub issue tracker only for bug reports and suggestions for improvements. +Feature requests, questions and general feedback is now handled at http://discourse.jabref.org. +Thanks! diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 00000000000..7e9c25fe4b4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,9 @@ +--- +name: Question +about: Ask a question about JabRef + +--- + +Please use the GitHub issue tracker only for bug reports and suggestions for improvements. +Feature requests, questions and general feedback is now handled at http://discourse.jabref.org. +Thanks! diff --git a/.github/ISSUE_TEMPLATE/suggestion-for-improvement.md b/.github/ISSUE_TEMPLATE/suggestion-for-improvement.md new file mode 100644 index 00000000000..ec6d48cc1dc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/suggestion-for-improvement.md @@ -0,0 +1,20 @@ +--- +name: Suggestion for improvement +about: Suggest an enhancement + +--- + + + +**Is your suggestion for improvement related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.gitignore b/.gitignore index eef074f6e45..39f4c1f4ac2 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,14 @@ status.md # Install4J install4j6/ +# Snap +parts/ +stage/ +prime/ +*.snap +jabref_source.tar.bz2 +snap/.snapcraft/ + # Gradle # generated when `gradlew --gui` is called ui/ @@ -23,18 +31,6 @@ ui/ jabref.xml *.sonargraph -# Snapcraft - JabRef places the files into buildres/snapcraft -snap/ - - - - - - - - - - # Created by https://www.gitignore.io/api/gradle,java,jabref,intellij,eclipse,netbeans,windows,linux,macos,node,snapcraft ### Eclipse ### diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000000..b37b24cf6d9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "src/main/resources/csl-styles"] + path = src/main/resources/csl-styles + url = https://github.com/citation-style-language/styles.git +[submodule "src/main/resources/csl-locales"] + path = src/main/resources/csl-locales + url = https://github.com/citation-style-language/locales.git diff --git a/.mailmap b/.mailmap index da5b9592d0d..b8698d75865 100644 --- a/.mailmap +++ b/.mailmap @@ -140,3 +140,6 @@ Mattia Bunel Waida Fan <31742543+weidafan@users.noreply.github.com> Johannes Manner Dominik Traczyk +Cerrianne Santos +Stefan Scheffel +Stefan Gerzmann \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index e981c01a6db..489044d8ec4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,8 +17,8 @@ env: global: - GRADLE_OPTS=-Dorg.gradle.daemon=false matrix: - - TEST_SUITE=checkstyle - TEST_SUITE=check OPTIONS=modernizer + - TEST_SUITE=checkstyle - TEST_SUITE=fetcherTest - TEST_SUITE=databaseTest - TEST_SUITE=guiTest @@ -47,10 +47,10 @@ before_script: script: # --scan enables the Gradle build scan, which can be used to investigate the time each action consumes # For more information see https://gradle.com/scans/get-started - - if [ "$TEST_SUITE" != "guiTest" ] && [ "$TEST_SUITE" != "checkstyle" ] && [ "$TEST_SUITE" != "codecov" ]; then ./gradlew $TEST_SUITE $OPTIONS --scan; fi + - if [ "$TEST_SUITE" != "guiTest" ] && [ "$TEST_SUITE" != "checkstyle" ] && [ "$TEST_SUITE" != "codecov" ]; then ./gradlew $TEST_SUITE $OPTIONS -x checkstyleJmh -x checkstyleMain -x checkstyleTest --scan; fi - if [ "$TEST_SUITE" == "checkstyle" ]; then ./gradlew checkstyleMain checkstyleTest checkstyleJmh; fi - if [ "$TEST_SUITE" == "guiTest" ]; then ./buildres/gui-tests.sh; fi - - if [ "$TEST_SUITE" == "codecov" ]; then ./gradlew jacocoJunit5TestReport; bash <(curl -s https://codecov.io/bash); fi + - if [ "$TEST_SUITE" == "codecov" ]; then ./gradlew jacocoTestReport; bash <(curl -s https://codecov.io/bash); fi - if [ "$DEPENDENCY_UPDATES" == "check" ]; then ./gradlew -q checkOutdatedDependencies; fi after_failure: @@ -60,6 +60,7 @@ after_failure: branches: only: - master + - maintable-beta # cache gradle dependencies # https://docs.travis-ci.com/user/languages/java#Caching @@ -70,3 +71,4 @@ cache: directories: - $HOME/.gradle/caches/ - $HOME/.gradle/wrapper/ + diff --git a/AUTHORS b/AUTHORS index 9fd46a04888..05f6f4111da 100644 --- a/AUTHORS +++ b/AUTHORS @@ -11,7 +11,9 @@ Alex Montgomery Alexis Gallagher Alexsandro Lauber Ali Ayan +Alick Zhao Ambrogio Oliva +Amish Shah Andreas Amann Andreas Buhr Andreas Rudert @@ -29,8 +31,8 @@ Bernd Kalbfuss Bernhard Tempel Brian Quistorff Brian Van Essen -captain123 Carlos Silla +Cerrianne Santos Christian Bartsch Christian Kopf Christoph Braun @@ -67,11 +69,11 @@ Felix Berger Felix Langner Felix Wilke Fernando Santagata +Florian Beetz Florian Straßer Foivos Christoulakis Francois Charette Frank Steimle -Fred Frédéric Darboux Gert Renckens Gregor Herrmann @@ -128,9 +130,7 @@ Mathias Walter Matthias Geiger Mattia Bunel Mattias Ulbrich -mcmoody Meltem Demirköprü -Michael Michael Beckmann Michael Falkenthal Michael Lass @@ -145,10 +145,8 @@ Nadeem Mahmood Nathan Dunn Nathan Sheffield Nicolas Pavillon -nikmilpv Niv Ierushalmi Nizar N. Batada -noravanq Olaf Lenz Oliver Beckmann Oliver Kopp @@ -156,7 +154,6 @@ Oscar Gustafsson Owen Huang Patrick Scheibe Paul Martin -payload Peter Ansell Philip Johnson Predrag Milanovic @@ -181,8 +178,8 @@ Seb Wills Shitikanth Simon Harrer Simon Rutishauser -speed9 Stefan Feyer +Stefan Gerzmann Stefan Kolb Stefan Robert Stefano Gariazzo @@ -190,7 +187,6 @@ Stephan Lau Stephan Rave Stéphane Curet Sven Jäger -The Gitter Badger Thiago Toledo Thomas Arildsen Thomas Ilsche @@ -201,10 +197,7 @@ Tobias Boceck Tobias Bouschen Tobias Denkinger Tobias Diez -tokkot -Tony K Toralf Senger -uid112001 Ulrich Stärk Ulrik Stervbo Uwe Kuehn @@ -213,9 +206,7 @@ Waida Fan Waluyo Adi Siswanto Ward Poelmans Wenbo Yang -wuw Yang Zongze Yara Grassi Gouffon Yifan Peng -zacmks Zhang Liang diff --git a/CHANGELOG.md b/CHANGELOG.md index c275c5f058d..9e14888432a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,3 @@ - # Changelog All notable changes to this project will be documented in this file. This project **does not** adhere to [Semantic Versioning](http://semver.org/). @@ -12,90 +11,112 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# ## [Unreleased] ### Changed -- Added "*.*" (any file type) to the Import File Filter Dialog. [#3757](https://github.com/JabRef/jabref/issues/3757) -- Abbreviate journal names functionality is now running parallel, increasing performance significantly. [#2831](https://github.com/JabRef/jabref/issues/2831) -- Changed order of items in context menu [#298](https://github.com/koppor/jabref/issues/298) -- Changed ID-based entry generator to store the last used fetcher. [#2796](https://github.com/JabRef/jabref/issues/2796) -- Reorganised annotation information on the right side of the "File annotations" tab. [#3109](https://github.com/JabRef/jabref/issues/3109) -- We now show a small notification icon in the entry editor when we detect data inconsistency or other problems. [#3145](https://github.com/JabRef/jabref/issues/3145) -- We added [oaDOI](https://oadoi.org/) as a fulltext provider, so that JabRef is now able to provide fulltexts for more than 90 million open-access articles. -- We changed one default of [Cleanup entries dialog](http://help.jabref.org/en/CleanupEntries): Per default, the PDF are not moved to the default file directory anymore. [#3619](https://github.com/JabRef/jabref/issues/3619) -- We added a new type of group that shows all items referenced in a given LaTeX file (actually the generated AUX file). [#1664](https://github.com/JabRef/jabref/issues/1664) -- We added an importer for the EndNote XML format. [Feature request in the forum](http://discourse.jabref.org/t/import-from-bookends-or-endnote/1048) -- We added the export of the `translator` field to the according MS-Office XML field. [#1750, comment](https://github.com/JabRef/jabref/issues/1750#issuecomment-357350986) -- We changed the import of the MS-Office XML fields `bookauthor` and `translator`. Both are now imported to their corresponding bibtex/biblatex fields. -- We improved the export of the `address` and `location` field to the MS-Office XML fields. If the address field does not contain a comma, it is treated as single value and exported to the field `city`. [#1750, comment](https://github.com/JabRef/jabref/issues/1750#issuecomment-357539167) -For more details refer to the [field mapping help page](http://help.jabref.org/en/MsOfficeBibFieldMapping) -- We added Facebook and Twitter icons in the toolbar to link to our [Facebook](https://www.facebook.com/JabRef/) and [Twitter](https://twitter.com/jabref_org) pages. -- Renamed the _Review_ Tab into _Comments_ Tab -- We no longer print empty lines when exporting an entry in RIS format [#3634](https://github.com/JabRef/jabref/issues/3634) -- We added the option to download linked URLs in the context menu in the entry editor. -- We improved file saving so that hard links are now preserved when a save is performed [#2633](https://github.com/JabRef/jabref/issues/2633) -- We changed the default dialog option when removing a [file link](http://help.jabref.org/en/FileLinks#adding-external-links-to-an-entry) from an entry. -The new default removes the linked file from the entry instead of deleting the file from disk. [#3679](https://github.com/JabRef/jabref/issues/3679) -- The magnifier icon at the search shows the [search mode](https://help.jabref.org/en/Search#search-modes) again. [#3535](https://github.com/JabRef/jabref/issues/3535) -- We added a new cleanup operation that replaces ligatures with their expanded form. [#3613](https://github.com/JabRef/jabref/issues/3613) -- We added the function to parse German month names. [#3536](https://github.com/JabRef/jabref/pull/3536) -- Pressing ESC while searching will clear the search field and select the first entry, if available, in the table. [koppor#293](https://github.com/koppor/jabref/issues/293) -- We changed the metadata reading and writing. DublinCore is now the only metadata format, JabRef supports. (https://github.com/JabRef/jabref/pull/3710) -- We added another CLI functionality for reading and writing metadata to pdfs. (see https://github.com/JabRef/jabref/pull/3756 and see http://help.jabref.org/en/CommandLine) -- We no longer print errors in field values during autosave into the log [#3811](https://github.com/JabRef/jabref/issues/3811) +- We changed the location of some fields in the entry editor (you might need to reset your preferences for these changes to come into effect) + - Journal/Year/Month in biblatex mode -> Deprecated (if filled) + - DOI/URL: General -> Optional + - Internal fields like ranking, read status and priority: Other -> General + - Moreover, empty deprecated fields are no longer shown +- Added server timezone parameter when connecting to a shared database. +- We updated the dialog for setting up general fields. +- URL field formatting is updated. All whitespace chars, located at the beginning/ending of the url, are trimmed automatically +- We changed the behavior of the field formatting dialog such that the `bibtexkey` is not changed when formatting all fields or all text fields. +- We added a "Move file to file directory and rename file" option for simultaneously moving and renaming of document file. [#4166](https://github.com/JabRef/jabref/issues/4166) +- Use integrated graphics card instead of discrete on macOS [#4070](https://github.com/JabRef/jabref/issues/4070) +- We added a cleanup operation that detects an arXiv identifier in the note, journal or url field and moves it to the `eprint` field. + Because of this change, the last-used cleanup operations were reset. +- We changed the minimum required version of Java to 1.8.0_171, as this is the latest release for which the automatic Java update works. [4093](https://github.com/JabRef/jabref/issues/4093) +- The special fields like `Printed` and `Read status` now show gray icons when the row is hovered. +- We added a button in the tab header which allows you to close the database with one click. https://github.com/JabRef/jabref/issues/494 +- Sorting in the main table now takes information from cross-referenced entries into account. https://github.com/JabRef/jabref/issues/2808 +- If a group has a color specified, then entries matched by this group have a small colored bar in front of them in the main table. +- Change default icon for groups to a circle because a colored version of the old icon was hard to distinguish from its black counterpart. +- In the main table, the context menu appears now when you press the "context menu" button on the keyboard. [feature request in the forum](http://discourse.jabref.org/t/how-to-enable-keyboard-context-key-windows) +- We added icons to the group side panel to quickly switch between `union` and `intersection` group view mode https://github.com/JabRef/jabref/issues/3269. +- We use `https` for [fetching from most online bibliographic database](https://help.jabref.org/en/#-using-online-bibliographic-database). +- We changed the default keyboard shortcuts for moving between entries when the entry editor is active to ̀alt + up/down. +- Opening a new file now prompts the directory of the currently selected file, instead of the directory of the last opened file. +- Window state is saved on close and restored on start. +- We made the MathSciNet fetcher more reliable. +- We added the ISBN fetcher to the list of fetcher available under "Update with bibliographic information from the web" in the entry editor toolbar. +- Files without a defined external file type are now directly opened with the default application of the operating system +- We streamlined the process to rename and move files by removing the confirmation dialogs. +- We removed the redundant new lines of markings and wrapped the summary in the File annotation tab. [#3823](https://github.com/JabRef/jabref/issues/3823) +- We add auto url formatting when user paste link to URL field in entry editor. [koppor#254](https://github.com/koppor/jabref/issues/254) +- We added a minimal height for the entry editor so that it can no longer be hidden by accident. [#4279](https://github.com/JabRef/jabref/issues/4279) +- We added a new keyboard shortcut so that the entry editor could be closed by Ctrl + E. [#4222] (https://github.com/JabRef/jabref/issues/4222) +- We added an option in the preference dialog box, that allows user to pick the dark or light theme option. [#4130] (https://github.com/JabRef/jabref/issues/4130) +- We updated updated the Related Articles tab to accept JSON from the new version of the Mr. DLib service +- We added an option in the preference dialog box that allows user to choose behavior after dragging and dropping files in Entry Editor. [#4356](https://github.com/JabRef/jabref/issues/4356) +- We added the ability to have an export preference where previously "File"-->"Export"/"Export selected entries" would not save the user's preference[#4495](https://github.com/JabRef/jabref/issues/4495) +- We optimized the code responsible for connecting to an external database, which should lead to huge improvements in performance. +- For automatically created groups, added ability to filter groups by entry type. [#4539](https://github.com/JabRef/jabref/issues/4539) +- We added the ability to add field names from the Preferences Dialog [#4546](https://github.com/JabRef/jabref/issues/4546) +- We added the ability change the column widths directly in the main table. [#4546](https://github.com/JabRef/jabref/issues/4546) +- We added the ability to execute default action in dialog by using with Ctrl + Enter combination [#4496](https://github.com/JabRef/jabref/issues/4496) +- We grouped and reordered the Main Menu (File, Edit, Library, Quality, Tools, and View tabs & icons). [#4666](https://github.com/JabRef/jabref/issues/4666) [#4667](https://github.com/JabRef/jabref/issues/4667) [#4668](https://github.com/JabRef/jabref/issues/4668) [#4669](https://github.com/JabRef/jabref/issues/4669) [#4670](https://github.com/JabRef/jabref/issues/4670) [#4671](https://github.com/JabRef/jabref/issues/4671) [#4672](https://github.com/JabRef/jabref/issues/4672) [#4673](https://github.com/JabRef/jabref/issues/4673) +- We added additional modifiers (capitalize, titlecase and sentencecase) to the Bibtex key generator. [#1506](https://github.com/JabRef/jabref/issues/1506) +- We grouped the toolbar icons and changed the Open Library and Copy icons. [#4584](https://github.com/JabRef/jabref/issues/4584) +- We added a browse button next to the path text field for aux-based groups. [#4586](https://github.com/JabRef/jabref/issues/4586) +- We changed the title of Group Dialog to "Add subgroup" from "Edit group" when we select Add subgroup option. +- We enable import button only if entries are selected. [#4755](https://github.com/JabRef/jabref/issues/4755) +- We made modifications to improve contrast of UI elements. [#4583](https://github.com/JabRef/jabref/issues/4583) ### Fixed -- We fixed several performance problems with the management of journal abbreviations [#3323](https://github.com/JabRef/jabref/issues/3323) -- We fixed an issue where changing the type of an entry did not update the label in the tool bar of the entry editor and the contents of the currently visible entry editor tab -- We fixed an issue where pressing space caused the cursor to jump to the start of the text field. [#3471](https://github.com/JabRef/jabref/issues/3471) -- We fixed the missing dot in the name of an exported file. [#3576](https://github.com/JabRef/jabref/issues/3576) -- Autocompletion in the search bar can now be disabled via the preferences. [#3598](https://github.com/JabRef/jabref/issues/3598) -- We fixed an issue where the progress of an ongoing file download was not shown correctly. [#3614](https://github.com/JabRef/jabref/issues/3614) -- We fixed an issue where odd linked files could not be selected in the entry editor. [#3639](https://github.com/JabRef/jabref/issues/3639) -- We fixed and extended the RIS import functionality to cover more fields. [#3634](https://github.com/JabRef/jabref/issues/3634)[#2607](https://github.com/JabRef/jabref/issues/2607) -- Chaining modifiers in BibTeX key pattern now works as described in the documentation. [#3648](https://github.com/JabRef/jabref/issues/3648) -- We fixed an issue where not all bibtex/biblatex fields would be exported as latex-free to MS-Office XML [koppor#284](https://github.com/koppor/jabref/issues/284) -- We fixed an issue where linked files would be deleted from bibliography entries despite choosing the "Cancel" option in the dialog menu. -- We fixed the name of the group editing window to "Add group" instead of "Edit Group" when adding a new group. [koppor#277](https://github.com/koppor/jabref/issues/277) -- We fixed the `pureauth` [BibTeX key generator pattern](https://help.jabref.org/en/BibtexKeyPatterns) to just return the author if no author, but an editor is present. -- We fixed an issue where the "Copy linked files" dialog produced an error when the entry had no file [#3818](https://github.com/JabRef/jabref/issues/3818) - -### Removed -- We removed the [Look and Feels from JGoodies](http://www.jgoodies.com/freeware/libraries/looks/), because the open source version is not compatible with Java 9. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +- We fixed an issue where corresponding groups are sometimes not highlighted when clicking on entries [#3112](https://github.com/JabRef/jabref/issues/3112) +- We fixed an issue where custom exports could not be selected in the 'Export (selected) entries' dialog [#4013](https://github.com/JabRef/jabref/issues/4013) +- Italic text is now rendered correctly. https://github.com/JabRef/jabref/issues/3356 +- The entry editor no longer gets corrupted after using the source tab. https://github.com/JabRef/jabref/issues/3532 https://github.com/JabRef/jabref/issues/3608 https://github.com/JabRef/jabref/issues/3616 +- We fixed multiple issues where entries did not show up after import if a search was active. https://github.com/JabRef/jabref/issues/1513 https://github.com/JabRef/jabref/issues/3219 +- We fixed an issue where the group tree was not updated correctly after an entry was changed. https://github.com/JabRef/jabref/issues/3618 +- We fixed an issue where a right-click in the main table selected a wrong entry. https://github.com/JabRef/jabref/issues/3267 +- We fixed an issue where in rare cases entries where overlayed in the main table. https://github.com/JabRef/jabref/issues/3281 +- We fixed an issue where selecting a group messed up the focus of the main table / entry editor. https://github.com/JabRef/jabref/issues/3367 +- We fixed an issue where composite author names were sorted incorrectly. https://github.com/JabRef/jabref/issues/2828 +- We fixed an issue where commands followed by `-` didn't work. [#3805](https://github.com/JabRef/jabref/issues/3805) +- We fixed an issue where some journal names were wrongly marked as abbreviated. [#4115](https://github.com/JabRef/jabref/issues/4115) +- We fixed an issue where the custom file column were sorted incorrectly. https://github.com/JabRef/jabref/issues/3119 +- We fixed an issues where the entry losses focus when a field is edited and at the same time used for sorting. https://github.com/JabRef/jabref/issues/3373 +- We fixed an issue where the menu on Mac OS was not displayed in the usual Mac-specific way. https://github.com/JabRef/jabref/issues/3146 +- We improved the integrity check for page numbers. [#4113](https://github.com/JabRef/jabref/issues/4113) and [feature request in the forum](http://discourse.jabref.org/t/pages-field-allow-use-of-en-dash/1199) +- We fixed an issue where the order of fields in customized entry types was not saved correctly. [#4033](http://github.com/JabRef/jabref/issues/4033) +- We fixed an issue where renaming a group did not change the group name in the interface. [#3189](https://github.com/JabRef/jabref/issues/3189) +- We fixed an issue where the groups tree of the last database was still shown even after the database was already closed. +- We fixed an issue where the "Open file dialog" may disappear behind other windows. https://github.com/JabRef/jabref/issues/3410 +- We fixed an issue where the number of entries matched was not updated correctly upon adding or removing an entry. [#3537](https://github.com/JabRef/jabref/issues/3537) +- We fixed an issue where the default icon of a group was not colored correctly. +- We fixed an issue where the first field in entry editor was not focused when adding a new entry. [#4024](https://github.com/JabRef/jabref/issues/4024) +- We reworked the "Edit file" dialog to make it resizeable and improved the workflow for adding and editing files https://github.com/JabRef/jabref/issues/2970 +- We fixed an issue where the month was not shown in the preview https://github.com/JabRef/jabref/issues/3239. +- Rewritten logic to detect a second jabref instance. [#4023](https://github.com/JabRef/jabref/issues/4023) +- We fixed an issue where the "Convert to BibTeX-Cleanup" moved the content of the `file` field to the `pdf` field [#4120](https://github.com/JabRef/jabref/issues/4120) +- We fixed an issue where the preview pane in entry preview in preferences wasn't showing the citation style selected [#3849](https://github.com/JabRef/jabref/issues/3849) +- We fixed an issue where the default entry preview style still contained the field `review`. The field `review` in the style is now replaced with comment to be consistent with the entry editor [#4098](https://github.com/JabRef/jabref/issues/4098) +- We fixed an issue where users were vulnerable to XXE attacks during parsing [#4229](https://github.com/JabRef/jabref/issues/4229) +- We fixed an issue where files added via the "Attach file" contextmenu of an entry were not made relative. [#4201](https://github.com/JabRef/jabref/issues/4201) and [#4241](https://github.com/JabRef/jabref/issues/4241) +- We fixed an issue where author list parser can't generate bibtex for Chinese author. [#4169](https://github.com/JabRef/jabref/issues/4169) +- We fixed an issue where the list of XMP Exclusion fields in the preferences was not be saved [#4072](https://github.com/JabRef/jabref/issues/4072) +- We fixed an issue where the ArXiv Fetcher did not support HTTP URLs [koppor#328](https://github.com/koppor/jabref/issues/328) +- We fixed an issue where only one PDF file could be imported [#4422](https://github.com/JabRef/jabref/issues/4422) +- We fixed an issue where "Move to group" would always move the first entry in the library and not the selected [#4414](https://github.com/JabRef/jabref/issues/4414) +- We fixed an issue where an older dialog appears when downloading full texts from the quality menu. [#4489](https://github.com/JabRef/jabref/issues/4489) +### Removed +- The feature to "mark entries" was removed and merged with the groups functionality. For migration, a group is created for every value of the `__markedentry` field and the entry is added to this group. +- The number column was removed. +- We removed the coloring of cells in the maintable according to whether the field is optional/required. +- We removed the feature to find and resolve duplicate BibTeX keys (as this use case is already covered by the integrity check). +- We removed a few commands from the right-click menu that are not needed often and thus don't need to be placed that prominently: + - Print entry preview: available through entry preview + - All commands related to marking: marking is not yet reimplemented + - Set/clear/append/rename fields: available through Edit menu + - Manage keywords: available through Edit menu + - Copy linked files to folder: available through File menu + - Add/move/remove from group: removed completely (functionality still available through group interface) +- We removed the option to change the column widths in the preferences dialog. [#4546](https://github.com/JabRef/jabref/issues/4546) @@ -108,284 +129,33 @@ The new default removes the linked file from the entry instead of deleting the f -## [4.1] - 2017-12-23 -### Changed -- We added bracketed expression support for file search patterns, import file name patterns and file directory patters, in addition to bibtexkey patterns. -- We added support for `[entrytype]` bracketed expression. -- Updated French translation -- We improved the handling of abstracts in the "Astrophysics Data System" fetcher. [#2471](https://github.com/JabRef/jabref/issues/2471) -- We added support for pasting entries in different formats [#3143](https://github.com/JabRef/jabref/issues/3143) -- In the annotation tab, PDF files are now monitored for new or changed annotation. A manual reload is no longer necessary. [#3292](https://github.com/JabRef/jabref/issues/3292) -- We increased the relative size of the "abstract" field in the entry editor. [Feature request in the forum](http://discourse.jabref.org/t/entry-preview-in-version-4/827) -- Crossreferenced entries are now used when a BibTex key is generated for an entry with empty fields. [#2811](https://github.com/JabRef/jabref/issues/2811) -- We now set the `WM_CLASS` of the UI to org-jabref-JabRefMain to allow certain Un*x window managers to properly identify its windows -- We added an option to convert entries in the biblatex format to BibTeX so that you can again collaborate with these nostalgic diehards. [Feature request in the forum](http://discourse.jabref.org/t/convert-back-from-biblatex-to-bib/) -- We changed the default paths for the OpenOffice/LibreOffice binaries to the default path for LibreOffice -- File annotation tab now removes newlines and hyphens before newlines from content and displays an empty String instead of N/A if no contents are found. [#3280](https://github.com/JabRef/jabref/issues/3280) -- We moved the groups field from the "Other fields" tab to "General" (you may have to reset your editor preferences under Options > Set up general fields) -- We no longer create a new entry editor when selecting a new entry to increase performance. [#3187](https://github.com/JabRef/jabref/pull/3187) -- We added the possibility to copy linked files from entries to a single output folder. [#2539](https://github.com/JabRef/jabref/pull/2593) -- We increased performance and decreased the memory footprint of the entry editor drastically. [#3331](https://github.com/JabRef/jabref/pull/3331) -- Late initialization of the context menus in the entry editor. This improves performance and memory footprint further [#3340](https://github.com/JabRef/jabref/pull/3340) -- Integrity check "Abbreviation Detection" detects abbreviated names for journals and booktitles based on the internal list instead of only looking for `.` signs. Fixes [#3144](https://github.com/JabRef/jabref/issues/3144). -- We added a dialog to show that JabRef is working on checking integrity. [#3358](https://github.com/JabRef/jabref/issues/3358) -- When you click the PDF icon in the file list of the entry editor, then the file is opened. [#3491](https://github.com/JabRef/jabref/issues/3491) -- We added an option to mass append to fields via the Quality -> [set/clear/append/rename fields dialog](http://help.jabref.org/en/SetClearRenameFields). [#2721](https://github.com/JabRef/jabref/issues/2721) -- We added a check on startup to ensure JabRef is run with an adequate Java version. [3310](https://github.com/JabRef/jabref/issues/3310) -- In the preference, all installed java Look and Feels are now listed and selectable -- We added an ID fetcher for [IACR eprints](https://eprint.iacr.org/). [#3473](https://github.com/JabRef/jabref/pull/3473) -- We added a clear option to the right-click menu of the text field in the entry editor. [koppor#198](https://github.com/koppor/jabref/issues/198) -- We improved the performance and memory footprint of the citation preview when [CSL styles](http://citationstyles.org/) are used. [#2533](https://github.com/JabRef/jabref/issues/2533) -- We disabled the auto completion as default, because it still causes issues. [#3522](https://github.com/JabRef/jabref/issues/3522) -### Fixed - - We fixed the translation of `\textendash` and `\textquotesingle` in the entry preview. [#3307](https://github.com/JabRef/jabref/issues/3307) - - We fixed an issue where JabRef would not terminated after asking to collect anonymous statistics. [#2955 comment](https://github.com/JabRef/jabref/issues/2955#issuecomment-334591123) - - We fixed an issue where JabRef would not shut down when started with the `-n` (No GUI) option. [#3247](https://github.com/JabRef/jabref/issues/3247) - - We improved the way metadata is updated in remote databases. [#3235](https://github.com/JabRef/jabref/issues/3235) - - We improved font rendering of the Entry Editor for Linux based systems. [#3295](https://github.com/JabRef/jabref/issues/3295) - - We fixed an issue where JabRef would freeze when trying to replace the original entry after a merge with new information from identifiers like DOI/ISBN etc. [#3294](https://github.com/JabRef/jabref/issues/3294) - - We no longer allow to add a field multiple times in customized entry types and thereby fix an issue in the entry editor that resulted from having a field multiple times. [#3046](https://github.com/JabRef/jabref/issues/3046) - - We fixed an issue where JabRef would not show the translated content at some points, although there existed a translation - - We fixed an issue where editing in the source tab would override content of other entries. [#3352](https://github.com/JabRef/jabref/issues/3352#issue-268580818) - - We fixed an issue where file links created under Windows could not be opened on Linux/OSX. [#3311](https://github.com/JabRef/jabref/issues/3311) - - We fixed several issues with the automatic linking of files in the entry editor where files were not found or not correctly saved in the bibtex source. [#3346](https://github.com/JabRef/jabref/issues/3346) - - We fixed an issue where fetching entries from crossref that had no titles caused an error. [#3376](https://github.com/JabRef/jabref/issues/3376) - - We fixed an issue where the same Java Look and Feel would be listed more than once in the Preferences. [#3391](https://github.com/JabRef/jabref/issues/3391) - - We fixed an issue where errors in citation styles triggered an exception when opening the preferences dialog. [#3389](https://github.com/JabRef/jabref/issues/3389) - - We fixed an issue where entries were displayed twice after insertion into a shared database. [#3164](https://github.com/JabRef/jabref/issues/3164) - - We improved the auto link algorithm so that files starting with a similar key are no longer found (e.g, `Einstein1902` vs `Einstein1902a`). [#3472](https://github.com/JabRef/jabref/issues/3472) - - We fixed an issue where special fields (such as `printed`) could not be cleared when syncing special fields via the keywords. [#3432](https://github.com/JabRef/jabref/issues/3432) - - We fixed an issue where the tooltip of the global search bar showed html tags instead of formatting the text. [#3381](https://github.com/JabRef/jabref/issues/3381) - - We fixed an issue where timestamps were not updated for changed entries. [#2810](https://github.com/JabRef/jabref/issues/2810) - - We fixed an issue where trying to fetch data from Medline/PubMed resulted in an error. [#3463](https://github.com/JabRef/jabref/issues/3463) - - We fixed an issue where double clicking on an entry in the integrity check dialog resulted in an exception. [#3485](https://github.com/JabRef/jabref/issues/3485) - - We fixed an issue where the entry type could sometimes not be changed when the entry editor was open [#3435](https://github.com/JabRef/jabref/issues/3435) - - We fixed an issue where dropping a pdf on the entry table and renaming it triggered an exception. [#3490](https://github.com/JabRef/jabref/issues/3490) - - We fixed an issue where no longer existing files could not be removed from the entry by pressing the del key. [#3493](https://github.com/JabRef/jabref/issues/3493) - - We fixed an issue where integrating external changes to a bib file caused instability. [#3498](https://github.com/JabRef/jabref/issues/3498) - - We fixed an issue where fetched entries from the ACM fetcher could not be imported. [#3500](https://github.com/JabRef/jabref/issues/3500) - - We fixed an issue where custom data in combobox fields in the entry editor was not saved. [#3538](https://github.com/JabRef/jabref/issues/3538) - - We fixed an issue where automatically found files were not added with a relative paths when the bib file is in the same directory as the files. [#3476](https://github.com/JabRef/jabref/issues/3476) - - We improved the key generator to remove certain illegal characters such as colons or apostrophes. [#3359](https://github.com/JabRef/jabref/issues/3359) - - -## [4.0] - 2017-10-04 -### Changed -- We added a textArea to see versionInfo in the About JabRef Dialog. [#2942](https://github.com/JabRef/jabref/issues/2942) -- We turned the validation feature in the entry editor off by default, because of a bug in the library we have been using [#3145](https://github.com/JabRef/jabref/issues/3145) -- Added 'Filter All' and 'Filter None' buttons with corresponding functionality to Quality Check tool. -- We increased the size of the keywords and file text areas in the entry editor -- When the entry that is currently shown in the entry editor is deleted externally, the editor is now closed automatically [#2946](https://github.com/JabRef/jabref/issues/2946) -- We added reordering of file and link entries in the `General`-Tab [3165, comment](https://github.com/JabRef/jabref/issues/3165#issuecomment-326269715) -- We added autcompletion for the `crossref` field on basis of the BibTeX-key. To accept such an autcompleted key as new entry-link, you have to press Enter two times, otherwise the field data is not stored in the library file.[koppor#257](https://github.com/koppor/jabref/issues/257) -- We added drag and drop support for adding files directly in the `General`-Tab. The dragged files are currently only linked from their existing directory. For more advanced features use the `Add files` dialog. [#koppor#244](https://github.com/koppor/jabref/issues/244) -- We added the file description filed back to the list of files in the `General`-Tab [#2930, comment](https://github.com/JabRef/jabref/issues/2930#issuecomment-328328172) -- Added an error dialog if the file is open in another process and cannot be renamed. [#3229] -- On Windows, the `JabRef.exe` executable can now be used to start JabRef from the command line. By default, no output is shown unless the new "-console" option is specified. -### Fixed -- We re-added the "Normalize to BibTeX name format" context menu item [#3136](https://github.com/JabRef/jabref/issues/3136) -- We fixed a memory leak in the source tab of the entry editor [#3113](https://github.com/JabRef/jabref/issues/3113) -- We fixed a [java bug](https://bugs.openjdk.java.net/browse/JDK-8185792) where linux users could not enter accented characters in the entry editor and the search bar [#3028](https://github.com/JabRef/jabref/issues/3028) -- We fixed a regression introduced in v4.0-beta2: A file can be dropped to the entry preview to attach it to the entry [koppor#245](https://github.com/koppor/jabref/issues/245) -- We fixed an issue in the "Replace String" dialog (Ctrl+R where search and replace did not work for the `bibtexkey` field. [#3132](https://github.com/JabRef/jabref/issues/3132) -- We fixed an issue in the entry editor where adding a term to a new protected terms list freezed JabRef completely. [#3157](https://github.com/JabRef/jabref/issues/3157) -- We fixed an issue in the "Manage protected terms" dialog where an 'Open file' dialog instead of a 'Save file' dialog was shown when creating a new list. [#3157](https://github.com/JabRef/jabref/issues/3157) -- We fixed an issue where unparseable dates of the FileAnnotations caused the FileAnnotationsTab to crash. -- We fixed an issue where a new protected terms list was not available immediately after its addition. [#3161](https://github.com/JabRef/jabref/issues/3161) -- We fixed an issue where an online file link could not be removed from an entry [#3165](https://github.com/JabRef/jabref/issues/3165) -- We fixed an issue where an online file link did not open the browser and created an error [#3165](https://github.com/JabRef/jabref/issues/3165) -- We fixed an issue where the arrow keys in the search bar did not work as expected [#3081](https://github.com/JabRef/jabref/issues/3081) -- We fixed wrong hotkey being displayed at "automatically file links" in the entry editor -- We fixed an issue where metadata syncing with local and shared database were unstable. It will also fix syncing groups and sub-groups in database. [#2284](https://github.com/JabRef/jabref/issues/2284) -- We fixed an issue where renaming a linked file would fail silently if a file with the same name existed. Added support for overriding existing file at user discretion. [#3172](https://github.com/JabRef/jabref/issues/3172) -- We fixed an issue where the "Remove group and subgroups" operation did not remove group information from entries in the group [#3190](https://github.com/JabRef/jabref/issues/3190) -- We fixed an issue where it was possible to leave the entry editor with an imbalance of braces. [#3167](https://github.com/JabRef/jabref/issues/3167) -- Renaming files now truncates the filename to not exceed the limit of 255 chars [#2622](https://github.com/JabRef/jabref/issues/2622) -- We improved the handling of hyphens in names. [#2775](https://github.com/JabRef/jabref/issues/2775) -- We fixed an issue where an entered file description was not written to the bib-file [#3208](https://github.com/JabRef/jabref/issues/3208) -- We improved the auto completion in the search bar. [koppor#253](https://github.com/koppor/jabref/issues/253) -- We fixed renaming files which are not in the main directory. [#3230](https://github.com/JabRef/jabref/issues/3230) -### Removed -- We removed support for LatexEditor, as it is not under active development. [#3199](https://github.com/JabRef/jabref/issues/3199) -## [4.0-beta3] – 2017-08-16 -### Changed -- We made the font size in the entry editor and group panel customizable by "Menu and label font size". [#3034](https://github.com/JabRef/jabref/issues/3034) -- If fetched article is already in database, then the entry merge dialog is shown. -- An error message is now displayed if you try to create a group containing the keyword separator or if there is already a group with the same name. [#3075](https://github.com/JabRef/jabref/issues/3075) and [#1495](https://github.com/JabRef/jabref/issues/1495) -- The FileAnnotationsTab was re-implemented in JavaFx. [#3082](https://github.com/JabRef/jabref/pull/3082) -- Integrity warnings are now directly displayed in the entry editor. -- We added the functionality to have `regex` as modifier. [#457](https://github.com/JabRef/jabref/issues/457) -### Fixed -- We fixed an issue where the fetcher for the Astrophysics Data System (ADS) added some non-bibtex data to the entry returned from the search [#3035](https://github.com/JabRef/jabref/issues/3035) -- We improved the auto completion so that minor changes are not added as suggestions. [#2998](https://github.com/JabRef/jabref/issues/2998) -- We readded the undo mechanism for changes in the entry editor [#2973](https://github.com/JabRef/jabref/issues/2973) -- We fixed an issue where assigning an entry via drag and drop to a group caused JabRef to stop/freeze completely [#3036](https://github.com/JabRef/jabref/issues/3036) -- We fixed the shortcut Ctrl+F for the search field. -- We fixed an issue where `title_case` and `capitalize` modifiers did not work with shorttitle. -- We fixed an issue where the preferences could not be imported without a restart of JabRef [#3064](https://github.com/JabRef/jabref/issues/3064) -- We fixed an issue where DEL, Ctrl+C, Ctrl+V and Ctrl+A in the search field triggered corresponding actions in the main table [#3067](https://github.com/JabRef/jabref/issues/3067) -- We fixed an issue where JabRef freezed when editing an assigned file in the `General`-Tab [#2930, comment](https://github.com/JabRef/jabref/issues/2930#issuecomment-311050976) -- We fixed an issue where a file could not be assigned to an existing entry via the entry context menu action `Attach file` [#3080](https://github.com/JabRef/jabref/issues/3080) -- We fixed an issue where entry editor was not focused after opening up. [#3052](https://github.com/JabRef/jabref/issues/3052) -- We fixed an issue where changes in the source tab were not stored when selecting a new entry. [#3086](https://github.com/JabRef/jabref/issues/3086) -- We fixed an issue where the other tab was not updated when fields where changed in the source tab. [#3063](https://github.com/JabRef/jabref/issues/3063) -- We fixed an issue where the source tab was not updated after fetching data by DOI. [#3103](https://github.com/JabRef/jabref/issues/3103) -- We fixed an issue where the move to group operation did not remove the entry from other groups [#3101](https://github.com/JabRef/jabref/issues/3101) -- We fixed an issue where the main table was not updated when grouping changes [#1903](https://github.com/JabRef/jabref/issues/1903) - -## [4.0-beta2] – 2017-07-18 -### Changed -- We moved the `adsurl` field to `url` field when fetching with the ADS fetcher. -- We continued to improve the new groups interface: - - You can now again select multiple groups (and a few related settings were added to the preferences) [#2786](https://github.com/JabRef/jabref/issues/2786). - - We further improved performance of group operations, especially of the new filter feature [#2852](https://github.com/JabRef/jabref/issues/2852). - - It is now possible to resort groups using drag & drop [#2785](https://github.com/JabRef/jabref/issues/2785). -- The entry editor got a fresh coat of paint: - - Homogenize the size of text fields. - - The buttons were changed to icons. - - Completely new interface to add or modify linked files. - - Removed the hidden feature that a double click in the editor inserted the current date. - - Complete new implementation of the the auto complete feature. -- All authors and editors are separated using semicolons when exporting to csv. [#2762](https://github.com/JabRef/jabref/issues/2762) -- Improved wording of "Show recommendations: into "Show 'Related Articles' tab" in the preferences -- We added integration of the Library of Congress catalog as a fetcher based on the [LCCN identifier](https://en.wikipedia.org/wiki/Library_of_Congress_Control_Number). [Feature request 636 in the forum](http://discourse.jabref.org/t/loc-marc-mods-connection/636) -- The integrity check for person names now also tests that the names are specified in one of the standard BibTeX formats. -- Links in the Recommended Articles tab (Mr.DLib), when clicked, are now opened in the system's default browser. [2931](https://github.com/JabRef/jabref/issues/2931) -- We improved the duplicate checker such that different editions of the same publication are not marked as duplicates. [2960](https://github.com/JabRef/jabref/issues/2960) -### Fixed -- We fixed a bug that leaves .sav file after SaveAs [#2947](https://github.com/JabRef/jabref/issues/2947) -- We fixed the function "Edit - Copy BibTeX key and link" to pass a hyperlink rather than an HTML statement. -- We fixed the adding of a new entry from DOI which led to a connection error. The DOI resolution now uses HTTPS to protect the user's privacy.[#2879](https://github.com/JabRef/jabref/issues/2897) -- We fixed the IEEE Xplore web search functionality [#2789](https://github.com/JabRef/jabref/issues/2789) -- We fixed an error in the CrossRef fetcher that occurred if one of the fetched entries had no title -- We fixed an issue that prevented new entries to be automatically assigned to the currently selected group [#2783](https://github.com/JabRef/jabref/issues/2783). -- We fixed a bug that only allowed parsing positive timezones from a FileAnnotation [#2839](https://github.com/JabRef/jabref/issues/2839) -- We fixed a bug that did not allow the correct re-export of the MS-Office XML field `msbib-accessed` with a different date format [#2859](https://github.com/JabRef/jabref/issues/2859). -- We fixed some bugs that prevented the display of FileAnnotations that were created using the Foxit Reader. [#2839, comment](https://github.com/JabRef/jabref/issues/2839#issuecomment-302058227). -- We fixed an error that prevented the FileAnnotation tab to load when the entry had no bibtexkey [#2903](https://github.com/JabRef/jabref/issues/2903). -- We fixed a bug which which could result in an exception when opening/saving files from/to a nonexistent directory [#2917](https://github.com/JabRef/jabref/issues/2917). -- We fixed a bug where recursive RegExpBased search found a file in a subdirectory multiple times and non-recursive RegExpBased search erroneously found files in subdirectories. -- We fixed a bug where new groups information was not stored on save [#2932](https://github.com/JabRef/jabref/issues/2932) -- We fixed a bug where the language files for Brazilian Portugese could not be loaded and the GUI localization remained in English [#1128](https://github.com/JabRef/jabref/issues/1182) -- We fixed a bug where the database was not marked as dirty when entries or groups were changed [#2787](https://github.com/JabRef/jabref/issues/2787) -- We fixed a bug where editors in the DocBook export were not exported [#3020](https://github.com/JabRef/jabref/issues/3020) -- We fixed a bug where the source tab was not updated when one the fields was changed [#2888](https://github.com/JabRef/jabref/issues/2888) -- We restored the original functionality that when browsing through the MainTable, the Entry Editor remembers which tab was opened before [#2896](https://github.com/JabRef/jabref/issues/2896) - -## [4.0-beta] – 2017-04-17 -### Changed -- JabRef has a new logo! The logo was designed by "[AikTheOne](https://99designs.de/profiles/theonestudio)" - who was the winner of a design contest at 99designs.com -- Partly switched to a new UI technology ([JavaFX]). - - Redesigned group panel. - - Number of matched entries is always shown. - - The background color of the hit counter signals whether the group contains all/any of the entries selected in the main table. - - Added a possibility to filter the groups panel [#1904](https://github.com/JabRef/jabref/issues/1904) - - Removed edit mode. - - Removed the following commands in the right-click menu: - - Expand/collapse subtree - - Move up/down/left/right - - Remove option to "highlight overlapping groups" - - Moved the option to "Gray out non-hits" / "Hide non-hits" to the preferences - - Removed the following options from the group preferences: - - Show icons (icons can now be customized) - - Show dynamic groups in italics (dynamic groups are not treated specially now) - - Initially show groups tree expanded (always true now) - - Expansion status of groups are saved across sessions. [#1428](https://github.com/JabRef/jabref/issues/1428) - - Redesigned about dialog. - - Redesigned key bindings dialog. - - Redesigned journal abbreviations dialog. - - New error console. - - All file dialogs now use the native file selector of the OS. [#1711](https://github.com/JabRef/jabref/issues/1711) -- We added a few properties to a group: - - Icon (with customizable color) that is shown in the groups panel (implements a [feature request in the forum](http://discourse.jabref.org/t/assign-colors-to-groups/321)). - - Description text that is shown on mouse hover (implements old feature requests [489](https://sourceforge.net/p/jabref/feature-requests/489/) and [818](https://sourceforge.net/p/jabref/feature-requests/818/)) -- We introduced "automatic groups" that automatically create subgroups based on a certain criteria (e.g., a subgroup for every author or keyword) and supports hierarchies. Implements [91](https://sourceforge.net/p/jabref/feature-requests/91/), [398](https://sourceforge.net/p/jabref/feature-requests/398/), [#1173](https://github.com/JabRef/jabref/issues/1173) and [#628](https://github.com/JabRef/jabref/issues/628). -- We added a document viewer which allows you to have a glance at your PDF documents directly from within JabRef. -- Using "Look up document identifier" in the quality menu, it is possible to look up DOIs, ArXiv ids and other identifiers for multiple entries. -- Comments in PDF files can now be displayed inside JabRef in a separate tab -- We separated the `Move file` and `Rename Pdfs` logic and context menu entries in the `General`-Tab for the Field `file` to improve the semantics -- We integrated support for the [paper recommender system Mr.DLib](http://help.jabref.org/en/EntryEditor#related-articles-tab) in a new tab in the entry editor. -- We renamed "database" to "library" to have a real distinction to SQL databases ("shared database") and `bib` files ("library"). [#2095](https://github.com/JabRef/jabref/issues/2095) -- We improved the UI customization possibilities: - - It is now possible to customize the colors and the size of the icons (implements a [feature request in the forum](http://discourse.jabref.org/t/menu-and-buttons-with-a-dark-theme/405)). - - Resizing the menu and label sizes has been improved. - - Font sizes can now be increased Ctrl + Plus, decreased Ctrl + Minus, and reset to default CTRL + 0. -- F4 opens selected file in current JTable context not just from selected entry inside the main table [#2355](https://github.com/JabRef/jabref/issues/2355) -- We are happy to welcome [CrossRef](https://www.crossref.org/) as a new member of our fetcher family. [#2455](https://github.com/JabRef/jabref/issues/2455) -- We added MathSciNet as a ID-based fetcher in the `BibTeX -> New entry` dialog (implements a [feature request in the forum](http://discourse.jabref.org/t/allow-to-search-by-mr-number-mathscinet)) -- Add tab which shows the MathSciNet review website if the `MRNumber` field is present. -- A scrollbar was added to the cleanup panel, as a result of issue [#2501](https://github.com/JabRef/jabref/issues/2501) -- Several scrollbars were added to the preference dialog which show up when content is too large [#2559](https://github.com/JabRef/jabref/issues/2559) -- We fixed and improved the auto detection of the [OpenOffice and LibreOffice connection](http://help.jabref.org/en/OpenOfficeIntegration) -- We added an option to copy the title of BibTeX entries to the clipboard through `Edit -> Copy title` (implements [#210](https://github.com/koppor/jabref/issues/210)) -- The `Move linked files to default file directory`-Cleanup operation respects the `File directory pattern` setting -- We removed the ordinals-to-superscript formatter from the recommendations for biblatex save actions [#2596](https://github.com/JabRef/jabref/issues/2596) -- Improved MS-Office Import/Export - - Improved author handling - - The `day` part of the biblatex `date` field is now exported to the corresponding `day` field. [#2691](https://github.com/JabRef/jabref/issues/2691) - - Entries with a single corporate author are now correctly exported to the corresponding `corporate` author field. [#1497](https://github.com/JabRef/jabref/issues/1497) - - Now exports the field `volumes` and `pubstate`. -- The integrity checker reports now if a journal is not found in the abbreviation list -- JabRef will now no longer delete meta data it does not know, but keeps such entries and tries to keep their formatting as far as possible. -- Switch to the [latex2unicode library](https://github.com/tomtung/latex2unicode) for converting LaTeX to unicode -- Single underscores are not converted during the LaTeX to unicode conversion, which does not follow the rules of LaTeX, but is what users require. [#2664](https://github.com/JabRef/jabref/issues/2664) -- The bibtexkey field is not converted to unicode -### Fixed - - ArXiV fetcher now checks similarity of entry when using DOI retrieval to avoid false positives [#2575](https://github.com/JabRef/jabref/issues/2575) - - We fixed an issue of duplicate keys after using a fetcher, e.g., DOI or ISBN [#2867](https://github.com/JabRef/jabref/issues/2687) - - We fixed an issue that prevented multiple parallel JabRef instances from terminating gracefully. [#2698](https://github.com/JabRef/jabref/issues/2698) - - We fixed an issue where authors with multiple surnames were not presented correctly in the main table. [#2534](https://github.com/JabRef/jabref/issues/2534) - - Repairs the handling of apostrophes in the LaTeX to unicode conversion. [#2500](https://github.com/JabRef/jabref/issues/2500) - - Fix import of journal title in RIS format. [#2506](https://github.com/JabRef/jabref/issues/2506) - - We fixed the export of the `number` field in MS-Office XML export. [#2509](https://github.com/JabRef/jabref/issues/2509) - - The field `issue` is now always exported to the corresponding `issue` field in MS-Office XML. - - We fixed the import of MS-Office XML files, when the `month` field contained an invalid value. - - We fixed an issue with repeated escaping of the %-sign when running the LaTeXCleanup more than once. [#2451](https://github.com/JabRef/jabref/issues/2451) - - Sciencedirect/Elsevier fetcher is now able to scrape new HTML structure [#2576](https://github.com/JabRef/jabref/issues/2576) - - Fixed the synchronization logic of keywords and special fields and vice versa [#2580](https://github.com/JabRef/jabref/issues/2580) - - We fixed an exception that prevented JabRef from starting in rare cases [bug report in the forum](http://discourse.jabref.org/t/jabref-not-opening/476). - - We fixed an unhandled exception when saving an entry containing unbalanced braces [#2571](https://github.com/JabRef/jabref/issues/2571) - - Fixed a display issue when removing a group with a long name [#1407](https://github.com/JabRef/jabref/issues/1407) - - We fixed an issue where the "find unlinked files" functionality threw an error when only one PDF was imported but not assigned to an entry [#2577](https://github.com/JabRef/jabref/issues/2577) - - We fixed issue where escaped braces were incorrectly counted when calculating brace balance in a field [#2561](https://github.com/JabRef/jabref/issues/2561) - - We fixed an issue introduced with Version 3.8.2 where executing the `Rename PDFs`-cleanup operation moved the files to the file directory. [#2526](https://github.com/JabRef/jabref/issues/2526) - - We improved the performance when opening a big library that still used the old groups format. Fixes an [issue raised in the forum](http://discourse.jabref.org/t/v3-8-2-x64-windows-problem-saving-large-bib-libraries/456). - - We fixed an issue where the `Move linked files to default file directory`- cleanup operation did not move the files to the location of the bib-file. [#2454](https://github.com/JabRef/jabref/issues/2454) - - We fixed an issue where executing `Move file` on a selected file in the `general`-tab could overwrite an existing file. [#2385](https://github.com/JabRef/jabref/issues/2358) - - We fixed an issue with importing groups and subgroups [#2600](https://github.com/JabRef/jabref/issues/2600) - - Fixed an issue where title-related key patterns did not correspond to the documentation. [#2604](https://github.com/JabRef/jabref/issues/2604) [#2589](https://github.com/JabRef/jabref/issues/2589) - - We fixed an issue which prohibited the citation export to external programs on MacOS. [#2613](https://github.com/JabRef/jabref/issues/2613) - - We fixed an issue where the file folder could not be changed when running `Get fulltext` in the `general`-tab. [#2572](https://github.com/JabRef/jabref/issues/2572) - - Newly created libraries no longer have the executable bit set under POSIX/Linux systems. The file permissions are now set to `664 (-rw-rw-r--)`. [#2635](https://github.com/JabRef/jabref/issues/#2635) - - Fixed an issue where names were split inconsistently with the BibTeX conventions [#2652](https://github.com/JabRef/jabref/issues/2652) - - Ctrl + A now correctly selects all entries again. [#2615](https://github.com/JabRef/jabref/issues/#2615) - - We fixed an issue where the dialog for selecting the main file directory in the preferences opened the wrong folder - - OpenOffice text formatting now handles nested tags properly [#2483](https://github.com/JabRef/jabref/issues/#2483) - - The group selection is no longer lost when switching tabs [#1104](https://github.com/JabRef/jabref/issues/1104) ## Older versions -The changelog of versions 3.x is available at the [v3.8.2 tag](https://github.com/JabRef/jabref/blob/v3.8.2/CHANGELOG.md). -The changelog of 2.11 and versions before is available as [text file in the v2.11.1 tag](https://github.com/JabRef/jabref/blob/v2.11.1/CHANGELOG). +The changelog of JabRef 4.x is available at the [v4.x branch](https://github.com/JabRef/jabref/blob/v4.x/CHANGELOG.md). +The changelog of JabRef 3.x is available at the [v3.8.2 tag](https://github.com/JabRef/jabref/blob/v3.8.2/CHANGELOG.md). +The changelog of JabRef 2.11 and all previous versions is available as [text file in the v2.11.1 tag](https://github.com/JabRef/jabref/blob/v2.11.1/CHANGELOG). -[Unreleased]: https://github.com/JabRef/jabref/compare/v4.1...HEAD +[Unreleased]: https://github.com/JabRef/jabref/compare/v4.3...HEAD +[4.3]: https://github.com/JabRef/jabref/compare/v4.2...v4.3 +[4.2]: https://github.com/JabRef/jabref/compare/v4.1...v4.2 [4.1]: https://github.com/JabRef/jabref/compare/v4.0...v4.1 [4.0]: https://github.com/JabRef/jabref/compare/v4.0-beta3...v4.0 [4.0-beta3]: https://github.com/JabRef/jabref/compare/v4.0-beta2...v4.0-beta3 @@ -393,3 +163,4 @@ The changelog of 2.11 and versions before is available as [text file in the v2.1 [4.0-beta]: https://github.com/JabRef/jabref/compare/v3.8.2...v4.0-beta [2.11.1]: https://github.com/JabRef/jabref/compare/v2.11...v2.11.1 [JavaFX]: https://en.wikipedia.org/wiki/JavaFX + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 80c012f83b1..2ccc618e536 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ ## Understanding the basics We welcome contributions to JabRef and encourage to create a fork, clone, **create a new branch** (such as `fix-for-issue-121`), **work on the new branch — not master**, and create a pull request. Be sure to create a **separate branch** for each improvement you implement. -Take a look at GitHub's excellent [help documentation] for a detailed explanation and the explanation of [Feature Branch Workflow](https://de.atlassian.com/git/tutorials/comparing-workflows#feature-branch-workflow) for the idea behind this kind of development. +Take a look at GitHub's excellent overview on the [GitHub flow](https://guides.github.com/introduction/flow/index.html) and their [pull request help documentation](https://help.github.com/articles/about-pull-requests/) for a detailed explanation and the explanation of [Feature Branch Workflow](https://de.atlassian.com/git/tutorials/comparing-workflows#feature-branch-workflow) for the idea behind this kind of development. We also have [code howtos](https://github.com/JabRef/jabref/wiki/Code-Howtos) and [guidelines for setting up a local workspace](https://github.com/JabRef/jabref/wiki/Guidelines-for-setting-up-a-local-workspace). @@ -118,5 +118,4 @@ You can add the prefix `[WIP]` to indicate that the pull request is not yet comp [commit guidelines section of Pro Git]: http://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines -[good commit message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html -[help documentation]: https://help.github.com/articles/about-pull-requests/ +[good commit message]: https://github.com/joelparkerhenderson/git_commit_message diff --git a/LICENSE.md b/LICENSE.md index 8ae99528256..150d62db659 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright © 2003-2016 [JabRef Authors](https://github.com/JabRef/jabref/blob/master/AUTHORS) +Copyright © 2003-2018 [JabRef Authors](https://github.com/JabRef/jabref/blob/master/AUTHORS) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 5cd2607dbc4..cec54b33f74 100644 --- a/README.md +++ b/README.md @@ -101,9 +101,7 @@ When you want to develop, it is necessary to generate additional sources using ` and then generate the Eclipse `gradlew eclipse`. For IntelliJ IDEA, just import the project via a Gradle Import by pointing at the `build.gradle`. -`gradlew test` executes the normal unit tests. -If you want to test the UI, execute `gradlew integrationTest`. -Sources for the integration test are kept in `src/integrationTest`. +`gradlew test` executes all tests. We use [Travis CI](https://travis-ci.org/) for executing the tests after each commit. For developing, it is sufficient to locally only run the associated test for the classes you changed. Travis will report any other failure. ## Acknowledgements diff --git a/build.gradle b/build.gradle index 80d6ff6b83e..0a45aab3885 100644 --- a/build.gradle +++ b/build.gradle @@ -12,20 +12,16 @@ buildscript { url 'https://oss.sonatype.org/content/groups/public' } } - dependencies { - classpath 'org.junit.platform:junit-platform-gradle-plugin:1.1.0' - } } plugins { - id 'com.gradle.build-scan' version '1.11' - id 'com.install4j.gradle' version '7.0.3' - id 'com.github.johnrengelman.shadow' version '2.0.2' - id "de.sebastianboegl.shadow.transformer.log4j" version "2.1.1" - id "com.simonharrer.modernizer" version '1.5.0-1' - id 'me.champeau.gradle.jmh' version '0.4.3' - id 'net.ltgt.errorprone' version '0.0.13' - id 'com.github.ben-manes.versions' version '0.17.0' + id 'com.gradle.build-scan' version '1.16' + id 'com.install4j.gradle' version '7.0.9' + id 'com.github.johnrengelman.shadow' version '4.0.2' + id "com.simonharrer.modernizer" version '1.6.0-1' + id 'me.champeau.gradle.jmh' version '0.4.8' + id 'net.ltgt.errorprone' version '0.6.1' + id 'com.github.ben-manes.versions' version '0.20.0' } // use the gradle build scan feature: https://scans.gradle.com/get-started @@ -41,22 +37,21 @@ apply plugin: 'jacoco' apply plugin: 'install4j' apply plugin: 'me.champeau.gradle.jmh' apply plugin: 'checkstyle' -apply plugin: 'org.junit.platform.gradle.plugin' apply from: 'eclipse.gradle' apply from: 'localization.gradle' apply from: 'xjc.gradle' group = "org.jabref" -version = "4.2-dev" -project.ext.threeDotVersion = "4.1.0.1" -project.ext.install4jDir = hasProperty("install4jDir") ? getProperty("install4jDir") : (OperatingSystem.current().isWindows() ? 'C:/Program Files/install4j6' : 'install4j6') +version = "5.0-dev" +project.ext.threeDotVersion = "5.0.0.0" +project.ext.install4jDir = hasProperty("install4jDir") ? getProperty("install4jDir") : (OperatingSystem.current().isWindows() ? 'C:/Program Files/install4j7' : 'install4j7') sourceCompatibility = 1.8 targetCompatibility = 1.8 mainClassName = "org.jabref.JabRefMain" // These are the Java version requirements we will check on each start of JabRef -ext.minRequiredJavaVersion = "1.8.0_144" +ext.minRequiredJavaVersion = "1.8.0_171" ext.allowJava9 = false sourceSets { @@ -69,6 +64,14 @@ sourceSets { srcDirs = ["src/main/java", "src/main/resources"] } } + test { + java { + srcDirs = ["src/test/java"] + } + resources { + srcDirs = ["src/test/resources"] + } + } } repositories { @@ -82,20 +85,24 @@ repositories { configurations { antlr3 antlr4 + errorprone } dependencies { + // Include all jar-files in the 'lib' folder as dependencies compile fileTree(dir: 'lib', includes: ['*.jar']) compile 'com.jgoodies:jgoodies-common:1.8.1' compile 'com.jgoodies:jgoodies-forms:1.9.0' - compile 'org.apache.pdfbox:pdfbox:2.0.9' - compile 'org.apache.pdfbox:fontbox:2.0.9' - compile 'org.apache.pdfbox:xmpbox:2.0.9' + compile 'org.apache.pdfbox:pdfbox:2.0.14' + compile 'org.apache.pdfbox:fontbox:2.0.14' + compile 'org.apache.pdfbox:xmpbox:2.0.14' + + compile group: 'org.apache.tika', name: 'tika-core', version: '1.20' // required for reading write-protected PDFs - see https://github.com/JabRef/jabref/pull/942#issuecomment-209252635 - compile 'org.bouncycastle:bcprov-jdk15on:1.59' + compile 'org.bouncycastle:bcprov-jdk15on:1.61' compile 'commons-cli:commons-cli:1.4' @@ -104,78 +111,77 @@ dependencies { compile "org.libreoffice:ridl:5.4.2" compile "org.libreoffice:unoil:5.4.2" - compile 'com.github.bkromhout:java-diff-utils:2.1.1' + compile 'io.github.java-diff-utils:java-diff-utils:4.0' compile 'info.debatty:java-string-similarity:1.1.0' antlr3 'org.antlr:antlr:3.5.2' compile 'org.antlr:antlr-runtime:3.5.2' - antlr4 'org.antlr:antlr4:4.7.1' - compile 'org.antlr:antlr4-runtime:4.7.1' + antlr4 'org.antlr:antlr4:4.7.2' + compile 'org.antlr:antlr4-runtime:4.7.2' - // VersionEye states that 6.0.5 is the most recent version, but http://dev.mysql.com/downloads/connector/j/ shows that as "Development Release" - compile 'mysql:mysql-connector-java:5.1.46' + compile 'mysql:mysql-connector-java:8.0.15' - compile 'com.impossibl.pgjdbc-ng:pgjdbc-ng:0.7.1' + compile 'org.postgresql:postgresql:42.2.5' compile 'net.java.dev.glazedlists:glazedlists_java15:1.9.1' - compile 'com.google.guava:guava:24.1-jre' + compile 'com.google.guava:guava:27.1-jre' // JavaFX stuff - compile 'com.airhacks:afterburner.fx:1.7.0' - compile 'de.codecentric.centerdevice:javafxsvg:1.3.0' compile 'de.jensd:fontawesomefx-materialdesignfont:1.7.22-4' - compile 'de.saxsys:mvvmfx-validation:1.7.0' + compile 'de.saxsys:mvvmfx-validation:1.8.0' + compile 'de.saxsys:mvvmfx:1.7.0' compile 'org.fxmisc.easybind:easybind:1.0.3' - compile 'org.fxmisc.flowless:flowless:0.6' - compile 'org.fxmisc.richtext:richtextfx:0.8.2' + compile 'org.fxmisc.flowless:flowless:0.6.1' + compile 'org.fxmisc.richtext:richtextfx:0.9.3' + compile 'com.sibvisions.external.jvxfx:dndtabpane:0.1' + compile 'javax.inject:javax.inject:1' + compile 'com.jfoenix:jfoenix:8.0.8' // Cannot be updated to 9.*.* until Jabref works with Java 9 compile 'org.controlsfx:controlsfx:8.40.15-SNAPSHOT' - compile 'org.jsoup:jsoup:1.11.2' + compile 'org.jsoup:jsoup:1.11.3' compile 'com.mashape.unirest:unirest-java:1.4.9' // >1.8.0-beta is required for java 9 compatibility - compile 'org.slf4j:slf4j-api:1.8.0-beta2' - compile 'org.apache.logging.log4j:log4j-slf4j-impl:2.11.0' - compile 'org.apache.logging.log4j:log4j-jcl:2.11.0' - compile 'org.apache.logging.log4j:log4j-api:2.11.0' - compile 'org.apache.logging.log4j:log4j-core:2.11.0' - - // need to use snapshots as the stable version is from 2013 and doesn't support v1.0.1 CitationStyles - compile 'org.citationstyles:styles:1.0.1-SNAPSHOT' - compile 'org.citationstyles:locales:1.0.1-SNAPSHOT' + compile 'org.slf4j:slf4j-api:1.8.0-beta4' + compile 'org.apache.logging.log4j:log4j-slf4j18-impl:2.11.2' + compile 'org.apache.logging.log4j:log4j-jcl:2.11.2' + compile 'org.apache.logging.log4j:log4j-api:2.11.2' + compile 'org.apache.logging.log4j:log4j-core:2.11.2' + compile 'de.undercouch:citeproc-java:1.0.1' compile 'com.github.tomtung:latex2unicode_2.12:0.2.2' - compile group: 'com.microsoft.azure', name: 'applicationinsights-core', version: '2.0.2' - compile group: 'com.microsoft.azure', name: 'applicationinsights-logging-log4j2', version: '2.0.2' - - testCompile 'org.junit.jupiter:junit-jupiter-api:5.1.0' - testCompile 'org.junit.jupiter:junit-jupiter-params:5.1.0' - testRuntime 'org.junit.jupiter:junit-jupiter-engine:5.1.0' - testRuntime 'org.junit.vintage:junit-vintage-engine:5.1.0' - testCompile 'org.junit.platform:junit-platform-launcher:1.1.0' - testRuntime 'org.apache.logging.log4j:log4j-core:2.11.0' - testRuntime 'org.apache.logging.log4j:log4j-jul:2.11.0' - testCompile 'org.mockito:mockito-core:2.17.0' - testCompile 'com.github.tomakehurst:wiremock:2.16.0' - testCompile 'org.assertj:assertj-swing-junit:3.8.0' + errorproneJavac 'com.google.errorprone:javac:1.8.0-u20' + + compile group: 'com.microsoft.azure', name: 'applicationinsights-core', version: '2.3.1' + compile group: 'com.microsoft.azure', name: 'applicationinsights-logging-log4j2', version: '2.3.1' + + testImplementation 'org.junit.jupiter:junit-jupiter:5.4.0' + testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.4.0' + testCompile 'org.junit.platform:junit-platform-launcher:1.4.0' + + testRuntime 'org.apache.logging.log4j:log4j-core:2.11.1' + testRuntime 'org.apache.logging.log4j:log4j-jul:2.11.2' + testCompile 'org.mockito:mockito-core:2.25.0' + testCompile 'com.github.tomakehurst:wiremock:2.21.0' testCompile 'org.reflections:reflections:0.9.11' - testCompile 'org.xmlunit:xmlunit-core:2.5.1' - testCompile 'org.xmlunit:xmlunit-matchers:2.5.1' - testCompile 'com.tngtech.archunit:archunit-junit:0.5.0' + testCompile 'org.xmlunit:xmlunit-core:2.6.2' + testCompile 'org.xmlunit:xmlunit-matchers:2.6.2' + testRuntime 'com.tngtech.archunit:archunit-junit5-engine:0.9.3' + testCompile 'com.tngtech.archunit:archunit-junit5-api:0.9.3' testCompile "org.testfx:testfx-core:4.0.+" - testCompile "org.testfx:testfx-junit:4.0.+" + testCompile "org.testfx:testfx-junit5:4.0.+" - checkstyle 'com.puppycrawl.tools:checkstyle:8.8' + checkstyle 'com.puppycrawl.tools:checkstyle:8.18' } jacoco { - toolVersion = '0.8.0' + toolVersion = '0.8.1' } dependencyUpdates { @@ -184,30 +190,51 @@ dependencyUpdates { // We have some dependencies which cannot be updated due to various reasons. dependencyUpdates.resolutionStrategy = { - componentSelection { - withModule("org.controlsfx:controlsfx") { ComponentSelection selection -> + componentSelection { rules -> + rules.all { ComponentSelection selection -> + if (selection.candidate.version ==~ /[0-9].*SNAPSHOT/) { + selection.reject("Ignore SNAPSHOT releases") + } + } + rules.withModule("com.gradle.build-scan:com.gradle.build-scan.gradle.plugin") { ComponentSelection selection -> + if (selection.candidate.version ==~ /2.*/) { + selection.reject("Cannot be upgraded to version 2 until we upgrade to gradle 5") + } + } + rules.withModule("org.controlsfx:controlsfx") { ComponentSelection selection -> if (selection.candidate.version ==~ /9.*/) { // Reject version 9 or higher selection.reject("Cannot be updated to 9.*.* until Jabref works with Java 9") } } - withModule("de.jensd:fontawesomefx-materialdesignfont") { ComponentSelection selection -> + rules.withModule("com.github.tomtung:latex2unicode_2.12") { ComponentSelection selection -> + if (selection.candidate.version ==~ /0.2.[3,4]/) { + // Reject version higher than 2.0.2. see https://github.com/JabRef/jabref/pull/3781 + selection.reject("Cannot be updated to 0.2.4 until JabRef is prepared for it") + } + } + rules.withModule("com.github.johnrengelman.shadow:com.github.johnrengelman.shadow.gradle.plugin") { ComponentSelection selection -> + if (selection.candidate.version ==~ /4.*/) { + selection.reject("Version 4.X breaks the release process.") + } + } + rules.withModule("de.jensd:fontawesomefx-materialdesignfont") { ComponentSelection selection -> if (selection.candidate.version ==~ /2.*/) { selection.reject("Cannot be upgraded to version 2") } } - withModule("mysql:mysql-connector-java") { ComponentSelection selection -> - if (selection.candidate.version ==~ /[6-9].*/) { - selection.reject("http://dev.mysql.com/downloads/connector/j/ lists the version 5.* as last stable version.") + rules.withModule("com.jfoenix:jfoenix") { ComponentSelection selection -> + if (selection.candidate.version ==~ /9.*/) { // Reject version 9 or higher + selection.reject("Cannot be updated to 9.*.* until Jabref works with Java 9") } } - withModule("org.jacoco:org.jacoco.agent") { ComponentSelection selection -> - if (selection.candidate.version.equals("0.8.0")) { - selection.reject("As a native plugin we cannot control the actual version of jacoco. This dependency should be hidden.") + rules.withModule("com.google.errorprone:javac") { ComponentSelection selection -> + if (selection.candidate.version ==~ /1.9.*/ || selection.candidate.version ==~ /9.*/) { + selection.reject("Cannot be updated to 9.*.* until Jabref works with Java 9") } } - withModule("org.jacoco:org.jacoco.ant") { ComponentSelection selection -> - if (selection.candidate.version.equals("0.8.0")) { - selection.reject("As a native plugin we cannot control the actual version of jacoco. This dependency should be hidden.") + rules.withModule("com.sun.xml.bind:jaxb-xjc") { ComponentSelection selection -> + if (!(selection.candidate.version ==~ /2.2.4.*/) || selection.candidate.version ==~ /2.[3-9].*/) { + selection.reject("Cannot be updated to 2.2.5 or higher.") } } } @@ -311,14 +338,16 @@ javadoc { } // Test tasks -junitPlatform { - filters { - tags { - exclude 'DatabaseTest', 'FetcherTest', 'GUITest', 'org.jabref.testutils.category.FetcherTest', 'org.jabref.testutils.category.GUITest' - } +test { + useJUnitPlatform { + excludeTags 'DatabaseTest', 'FetcherTest', 'GUITest', 'org.jabref.testutils.category.FetcherTest', 'org.jabref.testutils.category.GUITest' } - logManager 'org.apache.logging.log4j.jul.LogManager' + testLogging { + // set options for log level LIFECYCLE + events "failed" + exceptionFormat "full" + } } task databaseTest(type: Test) { @@ -355,8 +384,8 @@ tasks.withType(Test) { } task jacocoMerge(type: JacocoMerge) { - executionData file("$buildDir/jacoco/junitPlatformTest.exec"), file("$buildDir/jacoco/databaseTest.exec"), file("$buildDir/jacoco/fetcherTest.exec") - dependsOn junitPlatformTest, databaseTest, fetcherTest + executionData file("$buildDir/jacoco/test.exec"), file("$buildDir/jacoco/databaseTest.exec"), file("$buildDir/jacoco/fetcherTest.exec") + dependsOn test, databaseTest, fetcherTest } jacocoTestReport { @@ -369,36 +398,10 @@ jacocoTestReport { } } -afterEvaluate { - def junitPlatformTest = tasks.junitPlatformTest - - jacoco { - applyTo(junitPlatformTest) - } - - task jacocoJunit5TestReport(type: JacocoReport) { - executionData jacocoMerge.destinationFile - dependsOn jacocoMerge - sourceSets sourceSets.main - sourceDirectories = files(sourceSets.main.allSource.srcDirs) - classDirectories = files(sourceSets.main.output) - - reports { - xml.enabled true - html.enabled true - } - } -} - // Code quality tasks checkstyle { - // do not use other packages for checkstyle, excluding gen(erated) sources - checkstyleMain.source = "src/main/java" - toolVersion = '8.5' -} -checkstyleMain.shouldRunAfter test -checkstyleTest.shouldRunAfter test +} modernizer { // We have more than 20 issues, which are not fixed yet. Nevertheless, we produce the modernizer output. @@ -408,6 +411,7 @@ modernizer { // Release tasks shadowJar { + transform(com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer) classifier 'fat' } @@ -448,8 +452,16 @@ install4j { installDir = file(project.ext.install4jDir) } +task generateFinalJabRefPS1File(type: Copy) { + from('buildres') { + include 'JabRef.ps1' + } + into 'build' + filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: [jabRefJarFileName: jar.archiveName]) +} + // has to be defined AFTER 'dev' things to have the correct project.version -task media(type: com.install4j.gradle.Install4jTask, dependsOn: "releaseJar") { +task media(type: com.install4j.gradle.Install4jTask, dependsOn: ["releaseJar", "generateFinalJabRefPS1File"]) { projectFile = file('jabref.install4j') release = project.version winKeystorePassword = System.getenv('CERTIFICATE_PW') @@ -499,3 +511,19 @@ jmh { iterations = 10 fork = 2 } + +// Source: https://stackoverflow.com/a/44168582/873282 +task downloadDependencies { + description "Pre-downloads *most* dependencies" + doLast { + configurations.getAsMap().each { name, config -> + println "Retrieving dependencies for $name" + try { + config.files + } catch (e) { + // some cannot be resolved, just log them + project.logger.info e.message + } + } + } +} diff --git a/buildres/JabRef.bat b/buildres/JabRef.bat new file mode 100644 index 00000000000..7a2cee363b0 --- /dev/null +++ b/buildres/JabRef.bat @@ -0,0 +1,3 @@ +@echo off +pushd %~dp0 +@powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -WindowStyle Hidden -File ".\JabRef.ps1" diff --git a/buildres/JabRef.ps1 b/buildres/JabRef.ps1 new file mode 100644 index 00000000000..fe814e6013e --- /dev/null +++ b/buildres/JabRef.ps1 @@ -0,0 +1,48 @@ +function Respond($response) { + $jsonResponse = $response | ConvertTo-Json + + try { + $writer = New-Object System.IO.BinaryWriter([System.Console]::OpenStandardOutput()) + $writer.Write([int]$jsonResponse.Length) + $writer.Write([System.Text.Encoding]::UTF8.GetBytes($jsonResponse)) + $writer.Close() + } finally { + $writer.Dispose() + } +} + +$jabRefJarFileName = "@jabRefJarFileName@" +$jabRefJar = [System.IO.Path]::Combine($PSScriptRoot, $jabRefJarFileName) + +try { + $reader = New-Object System.IO.BinaryReader([System.Console]::OpenStandardInput()) + $length = $reader.ReadInt32() + $messageRaw = [System.Text.Encoding]::UTF8.GetString($reader.ReadBytes($length)) + $message = $messageRaw | ConvertFrom-Json + + if ($message.Status -eq "validate") { + if (-not (Test-Path $jabRefJar)) { + return Respond @{message="jarNotFound";path=$jabRefJar} + } else { + return Respond @{message="jarFound"} + } + } + + if (-not (Test-Path $jabRefJar)) { + $wshell = New-Object -ComObject Wscript.Shell + $popup = "Unable to locate '$jabRefJarFileName' in '$([System.IO.Path]::GetDirectoryName($jabRefJar))'." + $wshell.Popup($popup,0,"JabRef", 0x0 + 0x30) + return + } + + #$wshell = New-Object -ComObject Wscript.Shell + #$wshell.Popup($message.Text,0,"JabRef", 0x0 + 0x30) + + $messageText = $message.Text + $output = & java -jar $jabRefJar -importBibtex "$messageText" 2>&1 + #$output = & echoargs -importBibtex $messageText 2>&1 + #$wshell.Popup($output,0,"JabRef", 0x0 + 0x30) + return Respond @{message="ok";output="$output"} +} finally { + $reader.Dispose() +} diff --git a/buildres/jabref.json b/buildres/jabref.json new file mode 100644 index 00000000000..dee50f008fe --- /dev/null +++ b/buildres/jabref.json @@ -0,0 +1,9 @@ +{ + "name": "org.jabref.jabref", + "description": "JabRef", + "path": "JabRef.bat", + "type": "stdio", + "allowed_extensions": [ + "@jabfox" + ] +} diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 9ee9a6fa11d..00000000000 --- a/circle.yml +++ /dev/null @@ -1,51 +0,0 @@ -machine: - environment: - TERM: dumb - java: - version: oraclejdk8 - -dependencies: - pre: - # update locally with: - # openssl aes-256-cbc -e -in ./buildres/jabref-cert-2016.p12 -out jabref-cert-2016.p12.enc -k {PASSWORD} - #- openssl aes-256-cbc -d -in ./buildres/jabref-cert-2016.p12.enc -out ./buildres/jabref-cert-2016.p12 -k $CERTIFICATE - - scripts/prepare-install4j.sh - - install4j7/bin/install4jc --verbose --license=$INSTALL4J_KEY - #--win-keystore-password $CERTIFICATE_PW --mac-keystore-password $CERTIFICATE_PW - override: - # We do this to decrease build time by using CircleCI's cache. See https://discuss.circleci.com/t/effective-caching-for-gradle/540 for a longer motivation. - - ./gradlew compileJava - cache_directories: - - "~/.install4j7" - - "~/downloads" - -test: - override: - - ./gradlew -Pdev=true -Pinstall4jDir="install4j7" release --stacktrace - post: - # save test reports as build artifacts - - mkdir -p $CIRCLE_TEST_REPORTS/junit/ - - find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \; - -deployment: - release: - # we only do a simple check for SemVer and do not use a sophisticated check as presented at https://github.com/sindresorhus/semver-regex/blob/master/index.js - tag: /v[0-9]+(\.[0-9]+)*(-.+)?/ - commands: - # we have to do a clean build as changing gradle's "project.version" does not lead to a rebuild of resources (mirroring project.version) - - ./gradlew -Pinstall4jDir="install4j7" clean release --stacktrace - # upload at all circumstances - - scripts/upload-to-builds.jabref.org.sh - - ./gradlew dependencyUpdates -Drevision=release -DoutputFormatter=json -DoutputDir=build/releases - development: - # match all branches; this is executed, if "release" is not matched - see https://circleci.com/docs/configuration/ - branch: /.*/ - commands: - # if upload fails, it is accepted; CircleCI provides deep links to the binaries as fallback - - timeout 580 scripts/upload-to-builds.jabref.org.sh || exit 0 - - - -general: - artifacts: - - "build/releases" diff --git a/config/Eclipse Code Style.epf b/config/Eclipse Code Style.epf new file mode 100644 index 00000000000..f0a04dd9eb8 --- /dev/null +++ b/config/Eclipse Code Style.epf @@ -0,0 +1,328 @@ +#Fri Mar 09 22:05:27 CET 2018 +\!/= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.argumentPrefixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.argumentSuffixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.fieldPrefixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.fieldSuffixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.localPrefixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.localSuffixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFieldPrefixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFieldSuffixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_enum_constants=48 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_module_statements=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=88 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_field=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_block_comments=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_header=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_html=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_line_comments=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_source_code=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.line_length=120 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.compact_else_if=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.continuation_indentation=2 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_empty_lines=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indentation.size=4 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.join_lines_in_comments=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.join_wrapped_lines=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.lineSplit=9999 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.tabulation.char=space +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.tabulation.size=4 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.use_on_off_tags=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true +/instance/org.eclipse.jdt.ui/formatter_profile=_eclipse-cs JabRef +/instance/org.eclipse.jdt.ui/formatter_settings_version=13 +/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.cleanupprofiles=\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n +/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.exception.name=e +/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.formatterprofiles=\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n +/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.formatterprofiles.version=13 +/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.gettersetter.use.is=true +/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.overrideannotation=true +/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.text.custom_code_templates= +@org.eclipse.jdt.core=3.13.100.v20171123-1049 +@org.eclipse.jdt.ui=3.13.51.v20171122-0652 +file_export_version=3.0 diff --git a/config/README.md b/config/README.md index 76ec69f0770..4088be4eff3 100644 --- a/config/README.md +++ b/config/README.md @@ -15,3 +15,16 @@ Style-checks are done for each pull request and installing this cody style confi 10. Press "OK" * Please let `.editorconfig` override the settings of IntelliJ + + +# Eclipse: + +The Eclipse code formatter style is stored in the `eclipse.gradle` file and gets imported automatically. +In case the formatter style needs to be adapted, configure it and export in in eclipse. + +1. Right click on the eclipse project "JabRef" +2. Select "Export > General > Preferences" +3. Select "Java Code Style preferences" +4. Choose output file +5. Compare the formatter settings in the epf file with the ones in the eclipse.gradle file (`org.eclipse.jdt.core.formatter.`) +6. Replace the Eclipse Code Style.epf with the exported epf file diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 20cbcee3f60..d0d037d677b 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -49,6 +49,8 @@ + + diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml index 7e853704d9d..71943814250 100644 --- a/config/checkstyle/suppressions.xml +++ b/config/checkstyle/suppressions.xml @@ -6,5 +6,4 @@ - diff --git a/config/eclipseJabRef.xml b/config/eclipseJabRef.xml deleted file mode 100644 index 55862811cd5..00000000000 --- a/config/eclipseJabRef.xml +++ /dev/null @@ -1,314 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/crowdin.yml b/crowdin.yml index d1185f9784c..54d5e1039ad 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,10 +1,4 @@ files: - - source: /src/main/resources/l10n/Menu_en.properties - translation: /src/main/resources/l10n/Menu_%two_letters_code%.properties - languages_mapping: - two_letters_code: - pt-BR: pt_BR - id: in - source: /src/main/resources/l10n/JabRef_en.properties translation: /src/main/resources/l10n/JabRef_%two_letters_code%.properties languages_mapping: diff --git a/docs/adr/0003-use-gradle-as-build-tool.md b/docs/adr/0003-use-gradle-as-build-tool.md new file mode 100644 index 00000000000..6484e752df3 --- /dev/null +++ b/docs/adr/0003-use-gradle-as-build-tool.md @@ -0,0 +1,60 @@ +# Use Gradle as build tool + +## Context and Problem Statement + +Which build tool should be used? + +## Considered Options + +* [Maven](https://maven.apache.org/) +* [Gradle](https://gradle.org/) +* [Ant](https://ant.apache.org/) + +## Decision Outcome + +Chosen option: "Gradle", because it is lean and fits our development style. + +## Pros and Cons of the Options + +### 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, [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) +* Bad, because [it is hard to customize](https://www.slant.co/versus/2107/11592/~apache-maven_vs_gradle) +* Bad, because [it needs plugins for everything](https://www.slant.co/versus/2107/11592/~apache-maven_vs_gradle) +* Bad, because [it is verbose leading to huge build files](https://technologyconversations.com/2014/06/18/build-tools/) + +### Gradle + +* 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 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://blog.philipphauer.de/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 allows custom build scripts which need to be debugged](https://www.softwareyoga.com/10-reasons-why-we-chose-maven-over-gradle/) + +### Ant + +* Good, because [it offers a lot of control over the build process](https://technologyconversations.com/2014/06/18/build-tools/) +* Good, because [it has an agile dependency manager](https://blog.alejandrocelaya.com/2014/02/22/dependency-management-in-java-projects-with-ant-and-ivy/) +* Good, because [it has a low learning curve](https://technologyconversations.com/2014/06/18/build-tools/) +* 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 offers too much freedom](https://www.slant.co/versus/2106/2107/~apache-ant_vs_apache-maven) + +## Links + +* GADR: diff --git a/docs/adr/index.md b/docs/adr/index.md index 98c4e216f4d..c44730123e6 100644 --- a/docs/adr/index.md +++ b/docs/adr/index.md @@ -7,6 +7,7 @@ This log lists the architectural decisions for JabRef. - [ADR-0000](0000-use-markdown-architectural-decision-records.md) - Use Markdown Architectural Decision Records - [ADR-0001](0001-use-crowdin-for-translations.md) - Use Crowdin for translations - [ADR-0002](0002-use-slf4j-for-logging.md) - Use slf4j together with log4j2 for logging +- [ADR-0003](0003-use-gradle-as-build-tool.md) - Use Gradle as build tool diff --git a/eclipse.gradle b/eclipse.gradle index e4ab6fbc1ae..8efd725e98e 100644 --- a/eclipse.gradle +++ b/eclipse.gradle @@ -136,295 +136,310 @@ tasks.eclipse.doFirst { org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=warning org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning - org.eclipse.jdt.core.formatter.align_type_members_on_columns=false - org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 - org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 - org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=0 - org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 - org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 - org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 - org.eclipse.jdt.core.formatter.alignment_for_assignment=0 - org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 - org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 - org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=0 - org.eclipse.jdt.core.formatter.alignment_for_enum_constants=48 - org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 - org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 - org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 - org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 - org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 - org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=88 - org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=80 - org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 - org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=0 - org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 - org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 - org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 - org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 + org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert + org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert + org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines + org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert + org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert + org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert + org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines + org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 - org.eclipse.jdt.core.formatter.blank_lines_after_package=1 - org.eclipse.jdt.core.formatter.blank_lines_before_field=0 - org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=1 + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert + org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert + org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert + org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines + org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=false + org.eclipse.jdt.core.formatter.indentation.size=4 + org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert + org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert + org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert + org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert + org.eclipse.jdt.core.formatter.disabling_tag=@formatter\\:off + org.eclipse.jdt.core.formatter.continuation_indentation=1 + org.eclipse.jdt.core.formatter.alignment_for_enum_constants=48 org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 - org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 - org.eclipse.jdt.core.formatter.blank_lines_before_method=1 - org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 - org.eclipse.jdt.core.formatter.blank_lines_before_package=0 - org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 + org.eclipse.jdt.core.formatter.blank_lines_after_package=1 + org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert + org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines + org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=0 + org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert + org.eclipse.jdt.core.formatter.comment.indent_root_tags=true + org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=false + org.eclipse.jdt.core.formatter.enabling_tag=@formatter\\:on + org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert + org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false + org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert + org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 + org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert + org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false + org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert + org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert + org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 - org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line + org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert + org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert + org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0 + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert + org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert + org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false + org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert + org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line - org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line + org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert + org.eclipse.jdt.core.formatter.comment.line_length=9999 + org.eclipse.jdt.core.formatter.use_on_off_tags=true + org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert + org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert + org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false + org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert + org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert + org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=18 + org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert + org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 + org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert + org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false + org.eclipse.jdt.core.formatter.alignment_for_binary_expression=18 + org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert + org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=18 + org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert + org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line - org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line - org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line - org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line - org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line - org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line - org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line + org.eclipse.jdt.core.formatter.compact_else_if=true + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert + org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true + org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=18 + org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0 + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert + org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=18 + org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=18 org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false + org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert + org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false - org.eclipse.jdt.core.formatter.comment.format_block_comments=false - org.eclipse.jdt.core.formatter.comment.format_header=false - org.eclipse.jdt.core.formatter.comment.format_html=true - org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=false - org.eclipse.jdt.core.formatter.comment.format_line_comments=false - org.eclipse.jdt.core.formatter.comment.format_source_code=true - org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true - org.eclipse.jdt.core.formatter.comment.indent_root_tags=true - org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert - org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert - org.eclipse.jdt.core.formatter.comment.line_length=9999 - org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true - org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true - org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false - org.eclipse.jdt.core.formatter.compact_else_if=true - org.eclipse.jdt.core.formatter.continuation_indentation=2 - org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 - org.eclipse.jdt.core.formatter.disabling_tag=@formatter\\:off - org.eclipse.jdt.core.formatter.enabling_tag=@formatter\\:on - org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert + org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert + org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert + org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=18 org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true - org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true - org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true - org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true - org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true - org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true - org.eclipse.jdt.core.formatter.indent_empty_lines=false - org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true - org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true - org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true - org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true - org.eclipse.jdt.core.formatter.indentation.size=4 - org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=do not insert - org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert - org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert - org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert - org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert - org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert - org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert - org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert - org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert - org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert - org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert - org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert - org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert - org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert - org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert - org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert - org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert - org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert - org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert - org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert - org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert - org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert - org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert - org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert - org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert - org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert - org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert - org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert - org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert - org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert + org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 + org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert - org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert - org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert + org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert + org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert + org.eclipse.jdt.core.formatter.comment.format_line_comments=false org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert - org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert - org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert - org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert - org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert + org.eclipse.jdt.core.formatter.align_type_members_on_columns=false + org.eclipse.jdt.core.formatter.alignment_for_assignment=2 + org.eclipse.jdt.core.formatter.alignment_for_module_statements=16 + org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert + org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true + org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert + org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 + org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=1 + org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=2 + org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert + org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert + org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert + org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line + org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert + org.eclipse.jdt.core.formatter.comment.format_header=false + org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=18 org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert + org.eclipse.jdt.core.formatter.alignment_for_method_declaration=16 + org.eclipse.jdt.core.formatter.join_wrapped_lines=false + org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert + org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true + org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true + org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert + org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 + org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true + org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line + org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert + org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=26 + org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false + org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines + org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=18 + org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert - org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert - org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert + org.eclipse.jdt.core.formatter.tabulation.size=4 + org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert + org.eclipse.jdt.core.formatter.comment.format_source_code=true + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert + org.eclipse.jdt.core.formatter.blank_lines_before_field=0 + org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert + org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=1 + org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert + org.eclipse.jdt.core.formatter.blank_lines_before_method=1 + org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 + org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=0 + org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert + org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false + org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert + org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert + org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert + org.eclipse.jdt.core.formatter.comment.format_html=true + org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert + org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines + org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 + org.eclipse.jdt.core.formatter.indent_empty_lines=false + org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0 + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert + org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert + org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false + org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true + org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert - org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert - org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert + org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 + org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert + org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true + org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert - org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert - org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert - org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert + org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 + org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert + org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=18 + org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert + org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert + org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert + org.eclipse.jdt.core.formatter.comment.format_block_comments=false + org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert + org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert + org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=18 + org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert + org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true + org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert + org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert + org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line + org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert + org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines + org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert + org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true + org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert + org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line + org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line + org.eclipse.jdt.core.formatter.blank_lines_before_package=0 + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert - org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert - org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert - org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert - org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert - org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert - org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert - org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert - org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert - org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert - org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert - org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert - org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert - org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert - org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert + org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0 + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert + org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true + org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert org.eclipse.jdt.core.formatter.join_lines_in_comments=false - org.eclipse.jdt.core.formatter.join_wrapped_lines=false - org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false - org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false - org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false - org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false - org.eclipse.jdt.core.formatter.lineSplit=9999 - org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false - org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false - org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 - org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 - org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true - org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines - org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocationc=common_lines - org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines - org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines - org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines + org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert + org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true + org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert org.eclipse.jdt.core.formatter.tabulation.char=space - org.eclipse.jdt.core.formatter.tabulation.size=4 - org.eclipse.jdt.core.formatter.use_on_off_tags=true - org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false - org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true - org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=false - org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert + org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 + org.eclipse.jdt.core.formatter.lineSplit=9999 + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter '''. stripIndent()) diff --git a/external-libraries.txt b/external-libraries.txt index 80992ae8e21..7688c032dc6 100644 --- a/external-libraries.txt +++ b/external-libraries.txt @@ -40,11 +40,6 @@ Project: AppleJavaExtensions URL: https://developer.apple.com/legacy/library/samplecode/AppleJavaExtensions/Introduction/Intro.html License: Apple License -Id: com.github.bkromhout:java-diff-utils -Project: java-diff-utils -URL: https://github.com/bkromhout/java-diff-utils -License: Apache-2.0 - Id: com.github.tomtung Project: latex2unicode URL: https://github.com/tomtung/latex2unicode @@ -80,6 +75,11 @@ Project: Application Insights SDK for Java URL: https://github.com/Microsoft/ApplicationInsights-Java License: MIT +Id: com.sibvisions.external.jvxfx:DnDTabPane +Project: Drag'n'Drop TabPane +URL: https://github.com/sibvisions/javafx.DndTabPane +License: EPL-1.0 + Id: commons-cli:commons-cli Project: Apache Commons CLI URL: http://commons.apache.org/cli/ @@ -115,6 +115,11 @@ Project: Java String Similarity URL: https://github.com/tdebatty/java-string-similarity License: MIT +Id: io.github.java-diff-utils:java-diff-utils +Project: java-diff-utils +URL: https://github.com/java-diff-utils/java-diff-utils +License: Apache-1.1 + Id: mysql:mysql-connector-java Project: MySQL Connector/J URL: http://www.mysql.de/downloads/connector/j/ @@ -230,10 +235,4 @@ Project: XMLUnit URL: http://www.xmlunit.org/ License: Apache-2.0 -Id: spin -Path: lib/spin.jar -Project: Spin -URL: http://spin.sourceforge.net/ -License: LGPL-2.1+ - The last entry has to end with an empty line. Otherwise the entry is not present in About.html. diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 27768f1bbac..13536770052 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 568c50bf3a4..e0b3fb8d70b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-bin.zip diff --git a/jabref.install4j b/jabref.install4j index 28d3fad052e..08da26fd0a2 100644 --- a/jabref.install4j +++ b/jabref.install4j @@ -1,6 +1,6 @@ - - + + @@ -42,7 +42,10 @@ - + + + + @@ -51,10 +54,13 @@ - + + + + @@ -89,7 +95,7 @@ <key>NSSupportsAutomaticGraphicsSwitching</key> -<string>YES</string> +<true/> @@ -102,7 +108,7 @@ - + @@ -113,6 +119,9 @@ + + + @@ -141,7 +150,7 @@ - + @@ -183,7 +192,7 @@ context.setInstallationDirectory(new File(System.getenv("LOCALAPPDATA"), "JabRef")); } else { context.setInstallationDirectory(new File(System.getProperty("user.home"), "JabRef")); - } + } } return true; @@ -561,6 +570,27 @@ return console.askOkCancel(message, true); !(Util.hasFullAdminRights() || Util.isAdminGroup()) + + + + + + SOFTWARE\Mozilla\NativeMessagingHosts\org.jabref.jabref + + + + com.install4j.api.windows.RegistryRoot + HKEY_LOCAL_MACHINE + + + + ${installer:sys.installationDir}\jabref.json + + + + + + @@ -742,6 +772,9 @@ return console.askOkCancel(message, true); + + + @@ -896,6 +929,27 @@ return console.askYesNo(message, true); !(Util.hasFullAdminRights() || Util.isAdminGroup()) + + + + + + SOFTWARE\Mozilla\NativeMessagingHosts\org.jabref.jabref + + + false + + + + com.install4j.api.windows.RegistryRoot + HKEY_LOCAL_MACHINE + + + + + + + @@ -1009,7 +1063,7 @@ return console.askYesNo(message, true); false - install4j + JabRef @@ -1217,7 +1271,7 @@ return console.askYesNo(message, true); - icon:${installer:sys.installerApplicationMode}_header.png + ./src/main/resources/icons/JabRef-icon-64.png @@ -1410,7 +1464,7 @@ return console.askYesNo(message, true); - + @@ -1423,7 +1477,7 @@ return console.askYesNo(message, true); - + @@ -1436,7 +1490,7 @@ return console.askYesNo(message, true); - + diff --git a/javafx/scene/control/annotations.xml b/javafx/scene/control/annotations.xml new file mode 100644 index 00000000000..240687d556c --- /dev/null +++ b/javafx/scene/control/annotations.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/afterburner.fx.jar b/lib/afterburner.fx.jar new file mode 100644 index 00000000000..816b3e27f5d Binary files /dev/null and b/lib/afterburner.fx.jar differ diff --git a/lib/customjfx-1.0.0.jar b/lib/customjfx-1.0.0.jar deleted file mode 100644 index 4636cdd8975..00000000000 Binary files a/lib/customjfx-1.0.0.jar and /dev/null differ diff --git a/lib/spin.jar b/lib/spin.jar deleted file mode 100644 index 9d7f0e4b6ec..00000000000 Binary files a/lib/spin.jar and /dev/null differ diff --git a/scripts/download-install4j-and-jres.sh b/scripts/download-install4j-and-jres.sh new file mode 100755 index 00000000000..cdd5aee3b20 --- /dev/null +++ b/scripts/download-install4j-and-jres.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# fetch intall4j binary +if [ ! -d ~/downloads ]; then + mkdir ~/downloads +fi +cd ~/downloads +wget --quiet -nc --show-progress http://download-keycdn.ej-technologies.com/install4j/install4j_unix_7_0_8.tar.gz + +# fetch JREs +if [ ! -d ~/.install4j7/jres ]; then + mkdir -p ~/.install4j7/jres +fi +cd ~/.install4j7/jres +wget --quiet -nc https://files.jabref.org/jres/windows-x86-1.8.0_172.tar.gz +wget --quiet -nc https://files.jabref.org/jres/windows-amd64-1.8.0_172.tar.gz +wget --quiet -nc https://files.jabref.org/jres/macosx-amd64-1.8.0_172_unpacked.tar.gz diff --git a/scripts/extract-install4j.sh b/scripts/extract-install4j.sh new file mode 100755 index 00000000000..9e960280b81 --- /dev/null +++ b/scripts/extract-install4j.sh @@ -0,0 +1,4 @@ +#!/bin/bash +tar -xf ~/downloads/install4j_unix_7_0_8.tar.gz +# fix directory name (until install4j 6.1.5 it was install4j6 +mv install4j7.0.8 install4j7 diff --git a/scripts/prepare-install4j.sh b/scripts/prepare-install4j.sh deleted file mode 100755 index 3718b432a98..00000000000 --- a/scripts/prepare-install4j.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# ensure that downloads directory exists -if [ ! -d ~/downloads ]; then - mkdir ~/downloads -fi - -# ensure that tar archive of install4j exists -cd ~/downloads -wget --quiet -nc http://download-keycdn.ej-technologies.com/install4j/install4j_unix_7_0_3.tar.gz - -# extract tar archive of install4j into the source directory of JabRef -cd ~/jabref -# version 7.0.1 is NOT zipped any more - old command line: "-xzf" -tar -xf ~/downloads/install4j_unix_7_0_3.tar.gz -# fix directory name (until install4j 6.1.5 it was install4j6 -mv install4j7.0.3 install4j7 - -# fetch JREs -if [ ! -d ~/.install4j7/jres/ ]; then - mkdir -p ~/.install4j7/jres/ -fi -cd ~/.install4j7/jres/ -wget --quiet -nc https://files.jabref.org/jres/windows-x86-1.8.0_152.tar.gz -wget --quiet -nc https://files.jabref.org/jres/windows-amd64-1.8.0_152.tar.gz -wget --quiet -nc https://files.jabref.org/jres/macosx-amd64-1.8.0_152_unpacked.tar.gz diff --git a/scripts/upload-to-builds.jabref.org.sh b/scripts/upload-to-builds.jabref.org.sh index a7297a169f8..5e49c2c577c 100755 --- a/scripts/upload-to-builds.jabref.org.sh +++ b/scripts/upload-to-builds.jabref.org.sh @@ -42,4 +42,10 @@ command="${command}exit\n" # now $command is complete +# add host key of build-upload.jabref.org to SSH known hosts +cat <> ~/.ssh/known_hosts +|1|/E0gFRKMKG83OQVcwqFPIy3mnE4=|tLYRVZQ/3nCkBTZ9NtBVxx3si+Y= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNjYLP9C+PhQrpKfYsdgr8dDB/50S3BnaXAYQOVC5o3H0SqKisWw8iTkij/u8H20Rmsf/ABduOLPOBubfPFlE34= +|1|dEeue80RCldo/x5XyhbGIkS72d8=|09t8muprLf6YoXsc3r3kxicBykI= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNjYLP9C+PhQrpKfYsdgr8dDB/50S3BnaXAYQOVC5o3H0SqKisWw8iTkij/u8H20Rmsf/ABduOLPOBubfPFlE34= +EOF + echo -e "$command" | sftp -P 9922 builds_jabref_org@build-upload.jabref.org diff --git a/buildres/snapcraft/jabref.desktop b/snap/gui/jabref.desktop similarity index 90% rename from buildres/snapcraft/jabref.desktop rename to snap/gui/jabref.desktop index ef63a896d2d..3444b73fc8a 100644 --- a/buildres/snapcraft/jabref.desktop +++ b/snap/gui/jabref.desktop @@ -4,7 +4,7 @@ GenericName=BibTeX Editor Comment=JabRef is an open source bibliography reference manager. The native file format used by JabRef is BibTeX, the standard LaTeX bibliography format. Type=Application Terminal=false -Icon=${SNAP}/meta/gui/JabRef-icon-256.png +Icon=${SNAP}/meta/gui/jabref.png Exec=jabref %U Keywords=bibtex;biblatex;latex;bibliography Categories=Office; diff --git a/buildres/snapcraft/JabRef-icon-256.png b/snap/gui/jabref.png similarity index 100% rename from buildres/snapcraft/JabRef-icon-256.png rename to snap/gui/jabref.png diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml new file mode 100644 index 00000000000..b26f84fef0d --- /dev/null +++ b/snap/snapcraft.yaml @@ -0,0 +1,41 @@ +name: jabref +version: "git" +version-script: cat build.gradle | grep "^version =" | cut -d'"' -f2 +#icon: snap/gui/jabref.png +summary: Bibliography manager +description: JabRef is an open source bibliography reference manager. The native file format used by JabRef is BibTeX, the standard LaTeX bibliography format. + +grade: devel +confinement: strict + +architectures: + - build-on: amd64 + - build-on: i386 + +apps: + jabref: + command: desktop-launch java -jar $SNAP/jar/JabRef-$SNAP_VERSION.jar + environment: + _JAVA_OPTIONS: "-Duser.home=$SNAP_USER_DATA" + plugs: + - desktop + - desktop-legacy + - wayland + - unity7 + - home + - opengl + - network-bind + - removable-media + +parts: + jabref: + plugin: gradle + source: . + source-type: git + stage-packages: + - openjdk-8-jre + - openjfx + - x11-utils + gradle-options: [snapJar, -xtest] + gradle-output-dir: 'build/releases' + after: [desktop-gtk2] diff --git a/snapcraft.yaml b/snapcraft.yaml deleted file mode 100644 index 0daa48c83c4..00000000000 --- a/snapcraft.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# based on https://github.com/snapcore/snapcraft/blob/master/demos/gradle/snap/snapcraft.yaml - -name: jabref - -# the version string 4.2-dev is replaced by scripts/run-snapcraft.sh with the current version provided in build.gradle -version: '4.2-dev' - -summary: Bibliography manager -icon: buildres/snapcraft/JabRef-icon-256.png -description: JabRef is an open source bibliography reference manager. The native file format used by JabRef is BibTeX, the standard LaTeX bibliography format. - -# only with the following set to stable + strict, we can do a full release - -# see https://snapcraft.io/docs/reference/channels -# stable | devel -grade: devel - -# see https://snapcraft.io/docs/reference/confinement -# strict | devmode -confinement: strict - -apps: - jabref: - command: desktop-launch java -jar $SNAP/jar/JabRef-4.2-dev.jar - plugs: [desktop, desktop-legacy, wayland, unity7, home, network-bind] - desktop: ../buildres/snapcraft/jabref.desktop - -parts: - jabref: - plugin: gradle - source: . - stage-packages: [default-jre, openjfx, x11-utils] - gradle-options: [snapJar] - gradle-output-dir: 'build/releases' - after: [desktop-gtk3] diff --git a/src/jmh/java/org/jabref/benchmarks/Benchmarks.java b/src/jmh/java/org/jabref/benchmarks/Benchmarks.java index c261a64589c..a7316584eee 100644 --- a/src/jmh/java/org/jabref/benchmarks/Benchmarks.java +++ b/src/jmh/java/org/jabref/benchmarks/Benchmarks.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.StringReader; +import java.io.StringWriter; import java.util.List; import java.util.Random; import java.util.stream.Collectors; @@ -9,9 +10,7 @@ import org.jabref.Globals; import org.jabref.logic.exporter.BibtexDatabaseWriter; import org.jabref.logic.exporter.SavePreferences; -import org.jabref.logic.exporter.StringSaveSession; import org.jabref.logic.formatter.bibtexfields.HtmlToLatexFormatter; -import org.jabref.logic.importer.ParseException; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.logic.layout.format.HTMLChars; @@ -37,6 +36,8 @@ import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.runner.RunnerException; +import static org.mockito.Mockito.mock; + @State(Scope.Thread) public class Benchmarks { @@ -61,17 +62,22 @@ public void init() throws Exception { entry.setField("rnd", "2" + randomizer.nextInt()); database.insertEntry(entry); } - BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter<>(StringSaveSession::new); - StringSaveSession saveSession = databaseWriter.savePartOfDatabase( - new BibDatabaseContext(database, new MetaData(), new Defaults()), database.getEntries(), - new SavePreferences()); - bibtexString = saveSession.getStringValue(); + + bibtexString = getOutputWriter().toString(); latexConversionString = "{A} \\textbf{bold} approach {\\it to} ${{\\Sigma}}{\\Delta}$ modulator \\textsuperscript{2} \\$"; htmlConversionString = "Österreich – & characters ⪢ italic"; } + private StringWriter getOutputWriter() throws IOException { + StringWriter outputWriter = new StringWriter(); + BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(outputWriter, mock(SavePreferences.class)); + databaseWriter.savePartOfDatabase( + new BibDatabaseContext(database, new MetaData(), new Defaults()), database.getEntries()); + return outputWriter; + } + @Benchmark public ParserResult parse() throws IOException { BibtexParser parser = new BibtexParser(Globals.prefs.getImportFormatPreferences(), new DummyFileUpdateMonitor()); @@ -80,11 +86,7 @@ public ParserResult parse() throws IOException { @Benchmark public String write() throws Exception { - BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter<>(StringSaveSession::new); - StringSaveSession saveSession = databaseWriter.savePartOfDatabase( - new BibDatabaseContext(database, new MetaData(), new Defaults()), database.getEntries(), - new SavePreferences()); - return saveSession.getStringValue(); + return getOutputWriter().toString(); } @Benchmark @@ -125,7 +127,7 @@ public String htmlToLatexConversion() { } @Benchmark - public boolean keywordGroupContains() throws ParseException { + public boolean keywordGroupContains() { KeywordGroup group = new WordKeywordGroup("testGroup", GroupHierarchyType.INDEPENDENT, "keyword", "testkeyword", false, ',', false); return group.containsAll(database.getEntries()); } diff --git a/src/main/java/org/jabref/FallbackExceptionHandler.java b/src/main/java/org/jabref/FallbackExceptionHandler.java index ef65037ee54..0c0a4cb4391 100644 --- a/src/main/java/org/jabref/FallbackExceptionHandler.java +++ b/src/main/java/org/jabref/FallbackExceptionHandler.java @@ -1,5 +1,7 @@ package org.jabref; +import org.jabref.gui.util.DefaultTaskExecutor; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,5 +19,11 @@ public static void installExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable exception) { LOGGER.error("Uncaught exception occurred in " + thread, exception); + + DefaultTaskExecutor.runInJavaFXThread(() -> + JabRefGUI.getMainFrame() + .getDialogService() + .showErrorDialogAndWait("Uncaught exception occurred in " + thread, exception) + ); } } diff --git a/src/main/java/org/jabref/Globals.java b/src/main/java/org/jabref/Globals.java index 86bda457fa5..1a5cc0ee385 100644 --- a/src/main/java/org/jabref/Globals.java +++ b/src/main/java/org/jabref/Globals.java @@ -1,16 +1,19 @@ package org.jabref; import java.awt.GraphicsEnvironment; -import java.awt.Toolkit; import java.util.Optional; import java.util.UUID; -import org.jabref.gui.GlobalFocusListener; +import javafx.stage.Screen; + +import org.jabref.gui.ClipBoardManager; import org.jabref.gui.StateManager; import org.jabref.gui.keyboard.KeyBindingRepository; +import org.jabref.gui.undo.CountingUndoManager; import org.jabref.gui.util.DefaultFileUpdateMonitor; import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.gui.util.TaskExecutor; +import org.jabref.gui.util.ThemeLoader; import org.jabref.logic.exporter.ExporterFactory; import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.journals.JournalAbbreviationLoader; @@ -23,6 +26,7 @@ import com.google.common.base.StandardSystemProperty; import com.microsoft.applicationinsights.TelemetryClient; import com.microsoft.applicationinsights.TelemetryConfiguration; +import com.microsoft.applicationinsights.internal.shutdown.SDKShutdownActivity; import com.microsoft.applicationinsights.telemetry.SessionState; public class Globals { @@ -31,7 +35,6 @@ public class Globals { public static final BuildInfo BUILD_INFO = new BuildInfo(); // Remote listener public static final RemoteListenerServerLifecycle REMOTE_LISTENER = new RemoteListenerServerLifecycle(); - public static final ImportFormatReader IMPORT_FORMAT_READER = new ImportFormatReader(); public static final TaskExecutor TASK_EXECUTOR = new DefaultTaskExecutor(); // In the main program, this field is initialized in JabRef.java @@ -50,13 +53,16 @@ public class Globals { /** * Manager for the state of the GUI. */ + + public static ClipBoardManager clipboardManager = new ClipBoardManager(); + public static StateManager stateManager = new StateManager(); public static ExporterFactory exportFactory; + public static CountingUndoManager undoManager = new CountingUndoManager(); // Key binding preferences private static KeyBindingRepository keyBindingRepository; - // Background tasks - private static GlobalFocusListener focusListener; private static DefaultFileUpdateMonitor fileUpdateMonitor; + private static ThemeLoader themeLoader; private static TelemetryClient telemetryClient; private Globals() { @@ -70,24 +76,26 @@ public static synchronized KeyBindingRepository getKeyPrefs() { return keyBindingRepository; } - // Background tasks - public static void startBackgroundTasks() { - Globals.focusListener = new GlobalFocusListener(); - + public static void startBackgroundTasks() throws JabRefException { Globals.fileUpdateMonitor = new DefaultFileUpdateMonitor(); JabRefExecutorService.INSTANCE.executeInterruptableTask(Globals.fileUpdateMonitor, "FileUpdateMonitor"); + themeLoader = new ThemeLoader(fileUpdateMonitor, prefs); + if (Globals.prefs.shouldCollectTelemetry() && !GraphicsEnvironment.isHeadless()) { startTelemetryClient(); } } private static void stopTelemetryClient() { - if (Globals.prefs.shouldCollectTelemetry()) { - getTelemetryClient().ifPresent(client -> client.trackSessionState(SessionState.End)); - getTelemetryClient().ifPresent(client -> client.flush()); - } + getTelemetryClient().ifPresent(client -> { + client.trackSessionState(SessionState.End); + client.flush(); + + //FIXME: Workaround for bug https://github.com/Microsoft/ApplicationInsights-Java/issues/662 + SDKShutdownActivity.INSTANCE.stopAll(); + }); } private static void startTelemetryClient() { @@ -101,16 +109,11 @@ private static void startTelemetryClient() { telemetryClient.getContext().getSession().setId(UUID.randomUUID().toString()); telemetryClient.getContext().getDevice().setOperatingSystem(StandardSystemProperty.OS_NAME.value()); telemetryClient.getContext().getDevice().setOperatingSystemVersion(StandardSystemProperty.OS_VERSION.value()); - telemetryClient.getContext().getDevice().setScreenResolution( - Toolkit.getDefaultToolkit().getScreenSize().toString()); + telemetryClient.getContext().getDevice().setScreenResolution(Screen.getPrimary().getVisualBounds().toString()); telemetryClient.trackSessionState(SessionState.Start); } - public static GlobalFocusListener getFocusListener() { - return focusListener; - } - public static FileUpdateMonitor getFileUpdateMonitor() { return fileUpdateMonitor; } @@ -127,4 +130,8 @@ public static void stopBackgroundTasks() { public static Optional getTelemetryClient() { return Optional.ofNullable(telemetryClient); } + + public static ThemeLoader getThemeLoader() { + return themeLoader; + } } diff --git a/src/main/java/org/jabref/JabRefExecutorService.java b/src/main/java/org/jabref/JabRefExecutorService.java index 3f5aa22badc..df127df89f6 100644 --- a/src/main/java/org/jabref/JabRefExecutorService.java +++ b/src/main/java/org/jabref/JabRefExecutorService.java @@ -140,7 +140,7 @@ public void shutdownEverything() { this.lowPriorityExecutorService.shutdownNow(); // kill the remote thread stopRemoteThread(); - // timer doesn't need to be canceled as it is run in daemon mode, which ensures that it is stopped if the application is shut down + timer.cancel(); } private class NamedRunnable implements Runnable { diff --git a/src/main/java/org/jabref/JabRefGUI.java b/src/main/java/org/jabref/JabRefGUI.java index e51014e4114..57214d9ef9f 100644 --- a/src/main/java/org/jabref/JabRefGUI.java +++ b/src/main/java/org/jabref/JabRefGUI.java @@ -1,79 +1,70 @@ package org.jabref; -import java.awt.Frame; import java.io.File; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; import java.util.Iterator; import java.util.List; -import javax.swing.JOptionPane; -import javax.swing.UIDefaults; -import javax.swing.UIManager; -import javax.swing.UnsupportedLookAndFeelException; -import javax.swing.plaf.FontUIResource; +import javafx.scene.Scene; +import javafx.stage.Stage; import org.jabref.gui.BasePanel; +import org.jabref.gui.DialogService; +import org.jabref.gui.FXDialogService; import org.jabref.gui.GUIGlobals; import org.jabref.gui.JabRefFrame; import org.jabref.gui.dialogs.BackupUIManager; +import org.jabref.gui.help.VersionWorker; +import org.jabref.gui.icon.IconTheme; import org.jabref.gui.importer.ParserResultWarningDialog; import org.jabref.gui.importer.actions.OpenDatabaseAction; import org.jabref.gui.shared.SharedDatabaseUIManager; -import org.jabref.gui.worker.VersionWorker; import org.jabref.logic.autosaveandbackup.BackupManager; import org.jabref.logic.importer.OpenDatabase; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.l10n.Localization; import org.jabref.logic.shared.exception.InvalidDBMSConnectionPropertiesException; import org.jabref.logic.shared.exception.NotASharedDatabaseException; -import org.jabref.logic.util.OS; -import org.jabref.logic.util.Version; import org.jabref.model.database.shared.DatabaseNotSupportedException; import org.jabref.preferences.JabRefPreferences; +import impl.org.controlsfx.skin.DecorationPane; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class JabRefGUI { private static final Logger LOGGER = LoggerFactory.getLogger(JabRefGUI.class); + private static JabRefFrame mainFrame; private final List bibDatabases; private final boolean isBlank; private final List failed = new ArrayList<>(); private final List toOpenTab = new ArrayList<>(); + private final DialogService dialogService; private final String focusedFile; - public JabRefGUI(List argsDatabases, boolean isBlank) { + public JabRefGUI(Stage mainStage, List argsDatabases, boolean isBlank) { this.bibDatabases = argsDatabases; this.isBlank = isBlank; + this.dialogService = new FXDialogService(mainStage); // passed file (we take the first one) should be focused - focusedFile = argsDatabases.stream().findFirst().flatMap(ParserResult::getFile).map(File::getAbsolutePath) - .orElse(Globals.prefs.get(JabRefPreferences.LAST_FOCUSED)); - - openWindow(); - JabRefGUI.checkForNewVersion(false); - } - - public static void checkForNewVersion(boolean manualExecution) { - Version toBeIgnored = Globals.prefs.getVersionPreferences().getIgnoredVersion(); - Version currentVersion = Globals.BUILD_INFO.getVersion(); - new VersionWorker(JabRefGUI.getMainFrame(), manualExecution, currentVersion, toBeIgnored).execute(); + focusedFile = argsDatabases.stream() + .findFirst() + .flatMap(ParserResult::getFile) + .map(File::getAbsolutePath) + .orElse(Globals.prefs.get(JabRefPreferences.LAST_FOCUSED)); + + openWindow(mainStage); + new VersionWorker(Globals.BUILD_INFO.getVersion(), Globals.prefs.getVersionPreferences().getIgnoredVersion(), JabRefGUI.getMainFrame().getDialogService(), Globals.TASK_EXECUTOR) + .checkForNewVersionAsync(false); } - private void openWindow() { - - // This property is set to make the Mac OSX Java VM move the menu bar to the top of the screen - if (OS.OS_X) { - System.setProperty("apple.laf.useScreenMenuBar", "true"); - } - + private void openWindow(Stage mainStage) { // Set antialiasing on everywhere. This only works in JRE >= 1.5. // Or... it doesn't work, period. // TODO test and maybe remove this! I found this commented out with no additional info ( payload@lavabit.com ) @@ -94,7 +85,7 @@ private void openWindow() { GUIGlobals.init(); LOGGER.debug("Initializing frame"); - JabRefGUI.mainFrame = new JabRefFrame(); + JabRefGUI.mainFrame = new JabRefFrame(mainStage); // Add all bibDatabases databases to the frame: boolean first = false; @@ -102,7 +93,7 @@ private void openWindow() { for (Iterator parserResultIterator = bibDatabases.iterator(); parserResultIterator.hasNext();) { ParserResult pr = parserResultIterator.next(); // Define focused tab - if (pr.getFile().get().getAbsolutePath().equals(focusedFile)) { + if (pr.getFile().filter(path -> path.getAbsolutePath().equals(focusedFile)).isPresent()) { first = true; } @@ -118,9 +109,10 @@ private void openWindow() { pr.getDatabase().clearSharedDatabaseID(); LOGGER.error("Connection error", e); - JOptionPane.showMessageDialog(mainFrame, - e.getMessage() + "\n\n" + Localization.lang("A local copy will be opened."), - Localization.lang("Connection error"), JOptionPane.WARNING_MESSAGE); + dialogService.showErrorDialogAndWait( + Localization.lang("Connection error"), + Localization.lang("A local copy will be opened."), + e); } toOpenTab.add(pr); } else if (pr.toOpenTab()) { @@ -144,18 +136,40 @@ private void openWindow() { // state. This needs to be set after the window has been made visible, so we // do it here: if (Globals.prefs.getBoolean(JabRefPreferences.WINDOW_MAXIMISED)) { - JabRefGUI.getMainFrame().setExtendedState(Frame.MAXIMIZED_BOTH); + mainStage.setMaximized(true); + } else { + mainStage.setX(Globals.prefs.getDouble(JabRefPreferences.POS_X)); + mainStage.setY(Globals.prefs.getDouble(JabRefPreferences.POS_Y)); + mainStage.setWidth(Globals.prefs.getDouble(JabRefPreferences.SIZE_X)); + mainStage.setHeight(Globals.prefs.getDouble(JabRefPreferences.SIZE_Y)); } - JabRefGUI.getMainFrame().setVisible(true); + // We create a decoration pane ourselves for performance reasons + // (otherwise it has to be injected later, leading to a complete redraw/relayout of the complete scene) + DecorationPane root = new DecorationPane(); + root.getChildren().add(JabRefGUI.mainFrame); + + Scene scene = new Scene(root, 800, 800); + Globals.getThemeLoader().installCss(scene, Globals.prefs); + mainStage.setTitle(JabRefFrame.FRAME_TITLE); + mainStage.getIcons().addAll(IconTheme.getLogoSetFX()); + mainStage.setScene(scene); + mainStage.show(); + + mainStage.setOnCloseRequest(event -> { + saveWindowState(mainStage); + boolean reallyQuit = mainFrame.quit(); + if (!reallyQuit) { + event.consume(); + } + }); for (ParserResult pr : failed) { - String message = "" + Localization.lang("Error opening file '%0'.", pr.getFile().get().getName()) - + "

" - + pr.getErrorMessage() + ""; + String message = Localization.lang("Error opening file '%0'.", pr.getFile().get().getName()) + "\n" + + pr.getErrorMessage(); + + dialogService.showErrorDialogAndWait(Localization.lang("Error opening file"), message); - JOptionPane.showMessageDialog(JabRefGUI.getMainFrame(), message, Localization.lang("Error opening file"), - JOptionPane.ERROR_MESSAGE); } // Display warnings, if any @@ -180,10 +194,14 @@ private void openWindow() { } LOGGER.debug("Finished adding panels"); + } - if (!bibDatabases.isEmpty()) { - JabRefGUI.getMainFrame().getCurrentBasePanel().getMainTable().requestFocus(); - } + private void saveWindowState(Stage mainStage) { + Globals.prefs.putBoolean(JabRefPreferences.WINDOW_MAXIMISED, mainStage.isMaximized()); + Globals.prefs.putDouble(JabRefPreferences.POS_X, mainStage.getX()); + Globals.prefs.putDouble(JabRefPreferences.POS_Y, mainStage.getY()); + Globals.prefs.putDouble(JabRefPreferences.SIZE_X, mainStage.getWidth()); + Globals.prefs.putDouble(JabRefPreferences.SIZE_Y, mainStage.getHeight()); } private void openLastEditedDatabases() { @@ -201,7 +219,7 @@ private void openLastEditedDatabases() { } if (BackupManager.checkForBackupFile(dbFile.toPath())) { - BackupUIManager.showRestoreBackupDialog(mainFrame, dbFile.toPath()); + BackupUIManager.showRestoreBackupDialog(dialogService, dbFile.toPath()); } ParserResult parsedDatabase = OpenDatabase.loadDatabase(fileName, @@ -225,71 +243,11 @@ private boolean isLoaded(File fileToOpen) { } private void setLookAndFeel() { - try { - String lookFeel; - String systemLookFeel = UIManager.getSystemLookAndFeelClassName(); - - if (Globals.prefs.getBoolean(JabRefPreferences.USE_DEFAULT_LOOK_AND_FEEL)) { - // FIXME: Problems with OpenJDK and GTK L&F - // See https://github.com/JabRef/jabref/issues/393, https://github.com/JabRef/jabref/issues/638 - if (System.getProperty("java.runtime.name").contains("OpenJDK")) { - // Metal L&F - lookFeel = UIManager.getCrossPlatformLookAndFeelClassName(); - LOGGER.warn( - "There seem to be problems with OpenJDK and the default GTK Look&Feel. Using Metal L&F instead. Change to another L&F with caution."); - } else { - lookFeel = systemLookFeel; - } - } else { - lookFeel = Globals.prefs.get(JabRefPreferences.WIN_LOOK_AND_FEEL); - } - - // FIXME: Open JDK problem - if (UIManager.getCrossPlatformLookAndFeelClassName().equals(lookFeel) - && !System.getProperty("java.runtime.name").contains("OpenJDK")) { - // try to avoid ending up with the ugly Metal L&F - UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel"); - } else { - try { - UIManager.setLookAndFeel(lookFeel); - } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | - UnsupportedLookAndFeelException e) { - // specified look and feel does not exist on the classpath, so use system l&f - UIManager.setLookAndFeel(systemLookFeel); - // also set system l&f as default - Globals.prefs.put(JabRefPreferences.WIN_LOOK_AND_FEEL, systemLookFeel); - // notify the user - JOptionPane.showMessageDialog(JabRefGUI.getMainFrame(), - Localization - .lang("Unable to find the requested look and feel and thus the default one is used."), - Localization.lang("Warning"), JOptionPane.WARNING_MESSAGE); - LOGGER.warn("Unable to find requested look and feel", e); - } - } - - // On Linux, Java FX fonts look blurry per default. This can be improved by using a non-default rendering - // setting. See https://github.com/woky/javafx-hates-linux - if (Globals.prefs.getBoolean(JabRefPreferences.FX_FONT_RENDERING_TWEAK)) { - System.setProperty("prism.text", "t2k"); - System.setProperty("prism.lcdtext", "true"); - } - } catch (Exception e) { - LOGGER.warn("Look and feel could not be set", e); - } - - // In JabRef v2.8, we did it only on NON-Mac. Now, we try on all platforms - boolean overrideDefaultFonts = Globals.prefs.getBoolean(JabRefPreferences.OVERRIDE_DEFAULT_FONTS); - if (overrideDefaultFonts) { - int fontSize = Globals.prefs.getInt(JabRefPreferences.MENU_FONT_SIZE); - UIDefaults defaults = UIManager.getDefaults(); - Enumeration keys = defaults.keys(); - for (Object key : Collections.list(keys)) { - if ((key instanceof String) && ((String) key).endsWith(".font")) { - FontUIResource font = (FontUIResource) UIManager.get(key); - font = new FontUIResource(font.getName(), font.getStyle(), fontSize); - defaults.put(key, font); - } - } + // On Linux, Java FX fonts look blurry per default. This can be improved by using a non-default rendering + // setting. See https://github.com/woky/javafx-hates-linux + if (Globals.prefs.getBoolean(JabRefPreferences.FX_FONT_RENDERING_TWEAK)) { + System.setProperty("prism.text", "t2k"); + System.setProperty("prism.lcdtext", "true"); } } diff --git a/src/main/java/org/jabref/JabRefMain.java b/src/main/java/org/jabref/JabRefMain.java index 8ad2341544c..66e624a002d 100644 --- a/src/main/java/org/jabref/JabRefMain.java +++ b/src/main/java/org/jabref/JabRefMain.java @@ -4,7 +4,6 @@ import javax.swing.JFrame; import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; import javafx.application.Application; import javafx.application.Platform; @@ -12,7 +11,6 @@ import org.jabref.cli.ArgumentProcessor; import org.jabref.gui.remote.JabRefMessageHandler; -import org.jabref.logic.exporter.ExporterFactory; import org.jabref.logic.journals.JournalAbbreviationLoader; import org.jabref.logic.l10n.Localization; import org.jabref.logic.net.ProxyAuthenticator; @@ -20,7 +18,7 @@ import org.jabref.logic.net.ProxyRegisterer; import org.jabref.logic.protectedterms.ProtectedTermsLoader; import org.jabref.logic.remote.RemotePreferences; -import org.jabref.logic.remote.client.RemoteListenerClient; +import org.jabref.logic.remote.client.RemoteClient; import org.jabref.logic.util.BuildInfo; import org.jabref.logic.util.JavaVersion; import org.jabref.logic.util.OS; @@ -39,6 +37,7 @@ public class JabRefMain extends Application { private static final Logger LOGGER = LoggerFactory.getLogger(JabRefMain.class); + private static String[] arguments; public static void main(String[] args) { @@ -48,10 +47,45 @@ public static void main(String[] args) { @Override public void start(Stage mainStage) throws Exception { - Platform.setImplicitExit(false); - SwingUtilities.invokeLater(() -> start(arguments)); - } + try { + // Fail on unsupported Java versions + ensureCorrectJavaVersion(); + FallbackExceptionHandler.installExceptionHandler(); + + // Init preferences + final JabRefPreferences preferences = JabRefPreferences.getInstance(); + Globals.prefs = preferences; + // Perform migrations + PreferencesMigrations.runMigrations(); + + configureProxy(preferences.getProxyPreferences()); + + Globals.startBackgroundTasks(); + + applyPreferences(preferences); + // Process arguments + ArgumentProcessor argumentProcessor = new ArgumentProcessor(arguments, ArgumentProcessor.Mode.INITIAL_START); + + // Check for running JabRef + if (!handleMultipleAppInstances(arguments) || argumentProcessor.shouldShutDown()) { + Platform.exit(); + return; + } + + // If not, start GUI + new JabRefGUI(mainStage, argumentProcessor.getParserResults(), argumentProcessor.isBlank()); + } catch (Exception ex) { + LOGGER.error("Unexpected exception", ex); + } + } + + @Override + public void stop() { + Globals.stopBackgroundTasks(); + Globals.shutdownThreadPools(); + } + /** * Tests if we are running an acceptable Java and terminates JabRef when we are sure the version is not supported. * This test uses the requirements for the Java version as specified in gradle.build. It is possible to @@ -74,11 +108,9 @@ private static void ensureCorrectJavaVersion() { if (java9Fail || versionFail) { StringBuilder versionError = new StringBuilder( - Localization.lang("Your current Java version (%0) is not supported. Please install version %1 or higher.", - checker.getJavaVersion(), - buildInfo.getMinRequiredJavaVersion() - ) - ); + Localization.lang("Your current Java version (%0) is not supported. Please install version %1 or higher.", + checker.getJavaVersion(), + buildInfo.getMinRequiredJavaVersion())); versionError.append("\n"); versionError.append(Localization.lang("Your Java Runtime Environment is located at %0.", checker.getJavaInstallationDirectory())); @@ -88,7 +120,7 @@ private static void ensureCorrectJavaVersion() { versionError.append(Localization.lang("Note that currently, JabRef does not run with Java 9.")); } final JFrame frame = new JFrame(); - JOptionPane.showMessageDialog(frame, versionError, Localization.lang("Error"), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, versionError, Localization.lang("Error"), JOptionPane.ERROR_MESSAGE); frame.dispose(); // We exit on Java 9 error since this will definitely not work @@ -98,41 +130,31 @@ private static void ensureCorrectJavaVersion() { } } - private static void start(String[] args) { - FallbackExceptionHandler.installExceptionHandler(); - - JabRefPreferences preferences = JabRefPreferences.getInstance(); - - ensureCorrectJavaVersion(); - - ProxyPreferences proxyPreferences = preferences.getProxyPreferences(); - ProxyRegisterer.register(proxyPreferences); - if (proxyPreferences.isUseProxy() && proxyPreferences.isUseAuthentication()) { - Authenticator.setDefault(new ProxyAuthenticator()); + private static boolean handleMultipleAppInstances(String[] args) { + RemotePreferences remotePreferences = Globals.prefs.getRemotePreferences(); + if (remotePreferences.useRemoteServer()) { + // Try to contact already running JabRef + RemoteClient remoteClient = new RemoteClient(remotePreferences.getPort()); + if (remoteClient.ping()) { + // We are not alone, there is already a server out there, send command line arguments to other instance + if (remoteClient.sendCommandLineArguments(args)) { + // So we assume it's all taken care of, and quit. + LOGGER.info(Localization.lang("Arguments passed on to running JabRef instance. Shutting down.")); + return false; + } else { + LOGGER.warn("Could not communicate with other running JabRef instance."); + } + } else { + // We are alone, so we start the server + Globals.REMOTE_LISTENER.openAndStart(new JabRefMessageHandler(), remotePreferences.getPort()); + } } + return true; + } - Globals.prefs = preferences; - Globals.startBackgroundTasks(); - - // Note that the language was already set during the initialization of the preferences and it is safe to - // call the next function. - Globals.prefs.setLanguageDependentDefaultValues(); - - // Perform Migrations - // Perform checks and changes for users with a preference set from an older JabRef version. - PreferencesMigrations.upgradePrefsToOrgJabRef(); - PreferencesMigrations.upgradeSortOrder(); - PreferencesMigrations.upgradeFaultyEncodingStrings(); - PreferencesMigrations.upgradeLabelPatternToBibtexKeyPattern(); - PreferencesMigrations.upgradeImportFileAndDirePatterns(); - PreferencesMigrations.upgradeStoredCustomEntryTypes(); - PreferencesMigrations.upgradeKeyBindingsToJavaFX(); - PreferencesMigrations.addCrossRefRelatedFieldsForAutoComplete(); - PreferencesMigrations.upgradeObsoleteLookAndFeels(); - + private static void applyPreferences(JabRefPreferences preferences) { // Update handling of special fields based on preferences - InternalBibtexFields - .updateSpecialFields(Globals.prefs.getBoolean(JabRefPreferences.SERIALIZESPECIALFIELDS)); + InternalBibtexFields.updateSpecialFields(Globals.prefs.getBoolean(JabRefPreferences.SERIALIZESPECIALFIELDS)); // Update name of the time stamp field based on preferences InternalBibtexFields.updateTimeStampField(Globals.prefs.getTimestampPreferences().getTimestampField()); // Update which fields should be treated as numeric, based on preferences: @@ -143,52 +165,23 @@ private static void start(String[] args) { /* Build list of Import and Export formats */ Globals.IMPORT_FORMAT_READER.resetImportFormats(Globals.prefs.getImportFormatPreferences(), - Globals.prefs.getXMPPreferences(), Globals.getFileUpdateMonitor()); + Globals.prefs.getXMPPreferences(), Globals.getFileUpdateMonitor()); EntryTypes.loadCustomEntryTypes(preferences.loadCustomEntryTypes(BibDatabaseMode.BIBTEX), - preferences.loadCustomEntryTypes(BibDatabaseMode.BIBLATEX)); - Globals.exportFactory = ExporterFactory.create(Globals.prefs, Globals.journalAbbreviationLoader); + preferences.loadCustomEntryTypes(BibDatabaseMode.BIBLATEX)); + Globals.exportFactory = Globals.prefs.getExporterFactory(Globals.journalAbbreviationLoader); // Initialize protected terms loader Globals.protectedTermsLoader = new ProtectedTermsLoader(Globals.prefs.getProtectedTermsPreferences()); - // Check for running JabRef - RemotePreferences remotePreferences = Globals.prefs.getRemotePreferences(); - if (remotePreferences.useRemoteServer()) { - Globals.REMOTE_LISTENER.open(new JabRefMessageHandler(), remotePreferences.getPort()); - - if (!Globals.REMOTE_LISTENER.isOpen()) { - // we are not alone, there is already a server out there, try to contact already running JabRef: - if (RemoteListenerClient.sendToActiveJabRefInstance(args, remotePreferences.getPort())) { - // We have successfully sent our command line options through the socket to another JabRef instance. - // So we assume it's all taken care of, and quit. - LOGGER.info(Localization.lang("Arguments passed on to running JabRef instance. Shutting down.")); - Globals.shutdownThreadPools(); - // needed to tell JavaFx to stop - Platform.exit(); - return; - } - } - // we are alone, we start the server - Globals.REMOTE_LISTENER.start(); - } - // override used newline character with the one stored in the preferences // The preferences return the system newline character sequence as default OS.NEWLINE = Globals.prefs.get(JabRefPreferences.NEWLINE); + } - // Process arguments - ArgumentProcessor argumentProcessor = new ArgumentProcessor(args, ArgumentProcessor.Mode.INITIAL_START); - - // See if we should shut down now - if (argumentProcessor.shouldShutDown()) { - Globals.shutdownThreadPools(); - Platform.exit(); - return; + private static void configureProxy(ProxyPreferences proxyPreferences) { + ProxyRegisterer.register(proxyPreferences); + if (proxyPreferences.isUseProxy() && proxyPreferences.isUseAuthentication()) { + Authenticator.setDefault(new ProxyAuthenticator()); } - - // If not, start GUI - SwingUtilities - .invokeLater(() -> new JabRefGUI(argumentProcessor.getParserResults(), - argumentProcessor.isBlank())); } } diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index cd4e96ce523..f97a582daab 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -5,34 +5,33 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Optional; import java.util.prefs.BackingStoreException; import org.jabref.Globals; import org.jabref.JabRefException; import org.jabref.gui.externalfiles.AutoSetLinks; -import org.jabref.gui.importer.fetcher.EntryFetcher; -import org.jabref.gui.importer.fetcher.EntryFetchers; import org.jabref.logic.bibtexkeypattern.BibtexKeyGenerator; +import org.jabref.logic.exporter.AtomicFileWriter; import org.jabref.logic.exporter.BibDatabaseWriter; import org.jabref.logic.exporter.BibtexDatabaseWriter; import org.jabref.logic.exporter.Exporter; import org.jabref.logic.exporter.ExporterFactory; -import org.jabref.logic.exporter.FileSaveSession; -import org.jabref.logic.exporter.SaveException; import org.jabref.logic.exporter.SavePreferences; -import org.jabref.logic.exporter.SaveSession; import org.jabref.logic.exporter.TemplateExporter; +import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.ImportException; import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.importer.OpenDatabase; import org.jabref.logic.importer.OutputPrinter; +import org.jabref.logic.importer.ParseException; import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.importer.SearchBasedFetcher; +import org.jabref.logic.importer.WebFetchers; +import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.logic.l10n.Localization; import org.jabref.logic.layout.LayoutFormatterPreferences; import org.jabref.logic.logging.JabRefLogger; @@ -41,6 +40,7 @@ import org.jabref.logic.search.SearchQuery; import org.jabref.logic.shared.prefs.SharedDatabasePreferences; import org.jabref.logic.util.OS; +import org.jabref.logic.xmp.XmpPreferences; import org.jabref.model.Defaults; import org.jabref.model.EntryTypes; import org.jabref.model.database.BibDatabase; @@ -82,6 +82,19 @@ private static Optional importToOpenBase(String argument) { return result; } + private static Optional importBibtexToOpenBase(String argument) { + BibtexParser parser = new BibtexParser(Globals.prefs.getImportFormatPreferences(), Globals.getFileUpdateMonitor()); + try { + List entries = parser.parseEntries(argument); + ParserResult result = new ParserResult(entries); + result.setToOpenTab(); + return Optional.of(result); + } catch (ParseException e) { + System.err.println(Localization.lang("Error occurred when parsing entry") + ": " + e.getLocalizedMessage()); + return Optional.empty(); + } + } + private static Optional importFile(String argument) { String[] data = argument.split(","); @@ -204,13 +217,12 @@ private List processArguments() { if (cli.isFileExport()) { if (!loaded.isEmpty()) { exportFile(loaded, cli.getFileExport().split(",")); + LOGGER.debug("Finished export"); } else { System.err.println(Localization.lang("The output option depends on a valid import option.")); } } - LOGGER.debug("Finished export"); - if (cli.isPreferencesExport()) { try { Globals.prefs.exportPreferences(cli.getPreferencesExport()); @@ -223,11 +235,6 @@ private List processArguments() { doAuxImport(loaded); } - if (cli.isXmpFacilities()) { - XmpUtilMain.executeXmpConsoleApplicaton(); - System.exit(0); - } - return loaded; } @@ -250,18 +257,18 @@ private boolean exportMatches(List loaded) { //read in the export format, take default format if no format entered switch (data.length) { - case 3: - formatName = data[2]; - break; - case 2: - //default exporter: HTML table (with Abstract & BibTeX) - formatName = "tablerefsabsbib"; - break; - default: - System.err.println(Localization.lang("Output file missing").concat(". \n \t ") - .concat(Localization.lang("Usage")).concat(": ") + JabRefCLI.getExportMatchesSyntax()); - noGUINeeded = true; - return false; + case 3: + formatName = data[2]; + break; + case 2: + //default exporter: HTML table (with Abstract & BibTeX) + formatName = "tablerefsabsbib"; + break; + default: + System.err.println(Localization.lang("Output file missing").concat(". \n \t ") + .concat(Localization.lang("Usage")).concat(": ") + JabRefCLI.getExportMatchesSyntax()); + noGUINeeded = true; + return false; } //export new database @@ -346,6 +353,10 @@ private List importAndOpenFiles() { importToOpenBase(cli.getImportToOpenBase()).ifPresent(loaded::add); } + if (!cli.isBlank() && cli.isBibtexImport()) { + importBibtexToOpenBase(cli.getBibtexImport()).ifPresent(loaded::add); + } + return loaded; } @@ -360,27 +371,7 @@ private boolean generateAux(List loaded, String[] data) { // write an output, if something could be resolved if ((newBase != null) && newBase.hasEntries()) { String subName = StringUtil.getCorrectFileName(data[1], "bib"); - - try { - System.out.println(Localization.lang("Saving") + ": " + subName); - SavePreferences prefs = SavePreferences.loadForSaveFromPreferences(Globals.prefs); - BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter<>(FileSaveSession::new); - Defaults defaults = new Defaults(Globals.prefs.getDefaultBibDatabaseMode()); - SaveSession session = databaseWriter.saveDatabase(new BibDatabaseContext(newBase, defaults), prefs); - - // Show just a warning message if encoding did not work for all characters: - if (!session.getWriter().couldEncodeAll()) { - System.err.println(Localization.lang("Warning") + ": " - + Localization.lang( - "The chosen encoding '%0' could not encode the following characters:", - session.getEncoding().displayName()) - + " " + session.getWriter().getProblemCharacters()); - } - session.commit(subName); - } catch (SaveException ex) { - System.err.println(Localization.lang("Could not save file.") + "\n" + ex.getLocalizedMessage()); - } - + saveDatabase(newBase, subName); notSavedMsg = true; } @@ -393,6 +384,28 @@ private boolean generateAux(List loaded, String[] data) { } } + private void saveDatabase(BibDatabase newBase, String subName) { + try { + System.out.println(Localization.lang("Saving") + ": " + subName); + SavePreferences prefs = Globals.prefs.loadForSaveFromPreferences(); + AtomicFileWriter fileWriter = new AtomicFileWriter(Paths.get(subName), prefs.getEncoding()); + BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter(fileWriter, prefs); + Defaults defaults = new Defaults(Globals.prefs.getDefaultBibDatabaseMode()); + databaseWriter.saveDatabase(new BibDatabaseContext(newBase, defaults)); + + // Show just a warning message if encoding did not work for all characters: + if (fileWriter.hasEncodingProblems()) { + System.err.println(Localization.lang("Warning") + ": " + + Localization.lang( + "The chosen encoding '%0' could not encode the following characters:", + prefs.getEncoding().displayName()) + + " " + fileWriter.getEncodingProblems()); + } + } catch (IOException ex) { + System.err.println(Localization.lang("Could not save file.") + "\n" + ex.getLocalizedMessage()); + } + } + private void exportFile(List loaded, String[] data) { if (data.length == 1) { // This signals that the latest import should be stored in BibTeX @@ -400,27 +413,7 @@ private void exportFile(List loaded, String[] data) { if (!loaded.isEmpty()) { ParserResult pr = loaded.get(loaded.size() - 1); if (!pr.isInvalid()) { - try { - System.out.println(Localization.lang("Saving") + ": " + data[0]); - SavePreferences prefs = SavePreferences.loadForSaveFromPreferences(Globals.prefs); - Defaults defaults = new Defaults(Globals.prefs.getDefaultBibDatabaseMode()); - BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter<>( - FileSaveSession::new); - SaveSession session = databaseWriter.saveDatabase( - new BibDatabaseContext(pr.getDatabase(), pr.getMetaData(), defaults), prefs); - - // Show just a warning message if encoding did not work for all characters: - if (!session.getWriter().couldEncodeAll()) { - System.err.println(Localization.lang("Warning") + ": " - + Localization.lang( - "The chosen encoding '%0' could not encode the following characters:", - session.getEncoding().displayName()) - + " " + session.getWriter().getProblemCharacters()); - } - session.commit(data[0]); - } catch (SaveException ex) { - System.err.println(Localization.lang("Could not save file.") + "\n" + ex.getLocalizedMessage()); - } + saveDatabase(pr.getDatabase(), data[0]); } } else { System.err.println(Localization.lang("The output option depends on a valid import option.")); @@ -440,7 +433,7 @@ private void exportFile(List loaded, String[] data) { BibDatabaseContext databaseContext = pr.getDatabaseContext(); databaseContext.setDatabaseFile(theFile); Globals.prefs.fileDirForDatabase = databaseContext - .getFileDirectories(Globals.prefs.getFileDirectoryPreferences()); + .getFileDirectories(Globals.prefs.getFilePreferences()); System.out.println(Localization.lang("Exporting") + ": " + data[0]); Optional exporter = Globals.exportFactory.getExporterByName(data[1]); if (!exporter.isPresent()) { @@ -457,7 +450,6 @@ private void exportFile(List loaded, String[] data) { + Throwables.getStackTraceAsString(ex)); } } - } } @@ -466,12 +458,12 @@ private void importPreferences() { Globals.prefs.importPreferences(cli.getPreferencesImport()); EntryTypes.loadCustomEntryTypes(Globals.prefs.loadCustomEntryTypes(BibDatabaseMode.BIBTEX), Globals.prefs.loadCustomEntryTypes(BibDatabaseMode.BIBLATEX)); - Map customExporters = Globals.prefs.customExports.getCustomExportFormats(Globals.prefs, - Globals.journalAbbreviationLoader); + List customExporters = Globals.prefs.getCustomExportFormats(Globals.journalAbbreviationLoader); LayoutFormatterPreferences layoutPreferences = Globals.prefs .getLayoutFormatterPreferences(Globals.journalAbbreviationLoader); - SavePreferences savePreferences = SavePreferences.loadForExportFromPreferences(Globals.prefs); - Globals.exportFactory = ExporterFactory.create(customExporters, layoutPreferences, savePreferences); + SavePreferences savePreferences = Globals.prefs.loadForExportFromPreferences(); + XmpPreferences xmpPreferences = Globals.prefs.getXMPPreferences(); + Globals.exportFactory = ExporterFactory.create(customExporters, layoutPreferences, savePreferences, xmpPreferences); } catch (JabRefException ex) { LOGGER.error("Cannot import preferences", ex); } @@ -523,15 +515,12 @@ private void regenerateBibtexKeys(List loaded) { /** * Run an entry fetcher from the command line. - *

- * Note that this only works headlessly if the EntryFetcher does not show any GUI. * - * @param fetchCommand A string containing both the fetcher to use (id of EntryFetcherExtension minus Fetcher) and + * @param fetchCommand A string containing both the name of the fetcher to use and * the search query, separated by a : * @return A parser result containing the entries fetched or null if an error occurred. */ private Optional fetch(String fetchCommand) { - if ((fetchCommand == null) || !fetchCommand.contains(":") || (fetchCommand.split(":").length != 2)) { System.out.println(Localization.lang("Expected syntax for --fetch=':'")); System.out.println(Localization.lang("The following fetchers are available:")); @@ -540,38 +529,36 @@ private Optional fetch(String fetchCommand) { String[] split = fetchCommand.split(":"); String engine = split[0]; + String query = split[1]; - EntryFetchers fetchers = new EntryFetchers(Globals.journalAbbreviationLoader); - EntryFetcher fetcher = null; - for (EntryFetcher e : fetchers.getEntryFetchers()) { - if (engine.equalsIgnoreCase(e.getClass().getSimpleName().replace("Fetcher", ""))) { - fetcher = e; - } - } - - if (fetcher == null) { + List fetchers = WebFetchers.getSearchBasedFetchers(Globals.prefs.getImportFormatPreferences()); + Optional selectedFetcher = fetchers.stream() + .filter(fetcher -> fetcher.getName().equalsIgnoreCase(engine)) + .findFirst(); + if (!selectedFetcher.isPresent()) { System.out.println(Localization.lang("Could not find fetcher '%0'", engine)); - System.out.println(Localization.lang("The following fetchers are available:")); - - for (EntryFetcher e : fetchers.getEntryFetchers()) { - System.out.println( - " " + e.getClass().getSimpleName().replace("Fetcher", "").toLowerCase(Locale.ENGLISH)); - } - return Optional.empty(); - } - String query = split[1]; - System.out.println(Localization.lang("Running query '%0' with fetcher '%1'.", query, engine) + " " - + Localization.lang("Please wait...")); - Collection result = new ImportInspectionCommandLine().query(query, fetcher); + System.out.println(Localization.lang("The following fetchers are available:")); + fetchers.forEach(fetcher -> System.out.println(" " + fetcher.getName())); - if (result.isEmpty()) { - System.out.println( - Localization.lang("Query '%0' with fetcher '%1' did not return any results.", query, engine)); return Optional.empty(); + } else { + System.out.println(Localization.lang("Running query '%0' with fetcher '%1'.", query, engine)); + System.out.print(Localization.lang("Please wait...")); + try { + List matches = selectedFetcher.get().performSearch(query); + if (matches.isEmpty()) { + System.out.println("\r" + Localization.lang("No results found.")); + return Optional.empty(); + } else { + System.out.println("\r" + Localization.lang("Found %0 results.", String.valueOf(matches.size()))); + return Optional.of(new ParserResult(matches)); + } + } catch (FetcherException e) { + LOGGER.error("Error while fetching", e); + return Optional.empty(); + } } - - return Optional.of(new ParserResult(result)); } public boolean isBlank() { @@ -585,5 +572,4 @@ public boolean shouldShutDown() { public enum Mode { INITIAL_START, REMOTE_START } - } diff --git a/src/main/java/org/jabref/cli/ImportInspectionCommandLine.java b/src/main/java/org/jabref/cli/ImportInspectionCommandLine.java deleted file mode 100644 index bf3d66ba544..00000000000 --- a/src/main/java/org/jabref/cli/ImportInspectionCommandLine.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.jabref.cli; - -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -import org.jabref.gui.importer.fetcher.EntryFetcher; -import org.jabref.logic.importer.ImportInspector; -import org.jabref.logic.importer.OutputPrinter; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.entry.BibEntry; - -public class ImportInspectionCommandLine implements ImportInspector { - - private final List entries = new LinkedList<>(); - - private final OutputPrinter status = new SystemOutputPrinter(); - - @Override - public void addEntry(BibEntry entry) { - entries.add(entry); - } - - @Override - public void setProgress(int current, int max) { - status.setStatus(Localization.lang("Progress: %0 of %1", String.valueOf(current), String - .valueOf(max))); - } - - public Collection query(String query, EntryFetcher fetcher) { - entries.clear(); - if (fetcher.processQuery(query, ImportInspectionCommandLine.this, status)) { - return entries; - } - return Collections.emptyList(); - } -} diff --git a/src/main/java/org/jabref/cli/JabRefCLI.java b/src/main/java/org/jabref/cli/JabRefCLI.java index e6585516889..83459c97e01 100644 --- a/src/main/java/org/jabref/cli/JabRefCLI.java +++ b/src/main/java/org/jabref/cli/JabRefCLI.java @@ -1,10 +1,10 @@ package org.jabref.cli; -import java.util.Arrays; import java.util.List; import org.jabref.Globals; import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseMode; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; @@ -21,14 +21,13 @@ public class JabRefCLI { private final CommandLine cl; private List leftOver; - public JabRefCLI(String[] args) { Options options = getOptions(); try { this.cl = new DefaultParser().parse(options, args); - this.leftOver = Arrays.asList(cl.getArgs()); + this.leftOver = cl.getArgList(); } catch (ParseException e) { LOGGER.warn("Problem parsing arguments", e); @@ -96,6 +95,14 @@ public String getFileExport() { return cl.getOptionValue("output"); } + public boolean isBibtexImport() { + return cl.hasOption("importBibtex"); + } + + public String getBibtexImport() { + return cl.getOptionValue("importBibtex"); + } + public boolean isFileImport() { return cl.hasOption("import"); } @@ -140,10 +147,6 @@ public String getExportMatches() { return cl.getOptionValue("exportMatches"); } - public boolean isXmpFacilities() { - return cl.hasOption("readAndWriteXmpMetadata"); - } - public boolean isGenerateBibtexKeys() { return cl.hasOption("generateBibtexKeys"); } public boolean isAutomaticallySetFileLinks() { return cl.hasOption("automaticallySetFileLinks"); } @@ -168,6 +171,14 @@ private Options getOptions() { hasArg(). argName("FILE").build()); + options.addOption( + Option.builder("ib") + .longOpt("importBibtex") + .desc(String.format("%s: %s[,importBibtex bibtexString]", Localization.lang("Import") + " " + BibDatabaseMode.BIBTEX.getFormattedName(), Localization.lang("filename"))) + .hasArg() + .argName("FILE") + .build()); + options.addOption(Option.builder("o"). longOpt("output"). desc(String.format("%s: %s[,export format]", Localization.lang("Output or export file"), @@ -236,11 +247,6 @@ private Options getOptions() { desc(Localization.lang("Automatically set file links")). build()); - options.addOption(Option.builder("xmp"). - longOpt("readAndWriteXmpMetadata"). - desc("Read and write xmp metadata from/to pdf files"). - build()); - return options; } diff --git a/src/main/java/org/jabref/cli/XmpUtilMain.java b/src/main/java/org/jabref/cli/XmpUtilMain.java deleted file mode 100644 index 5ff5ffc6a0d..00000000000 --- a/src/main/java/org/jabref/cli/XmpUtilMain.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.jabref.cli; - -import java.io.BufferedReader; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.StringWriter; -import java.nio.file.Paths; -import java.util.List; - -import javax.xml.transform.TransformerException; - -import org.jabref.Globals; -import org.jabref.logic.bibtex.BibEntryWriter; -import org.jabref.logic.bibtex.LatexFieldFormatter; -import org.jabref.logic.importer.ImportFormatPreferences; -import org.jabref.logic.importer.ParserResult; -import org.jabref.logic.importer.fileformat.BibtexParser; -import org.jabref.logic.xmp.XmpPreferences; -import org.jabref.logic.xmp.XmpUtilReader; -import org.jabref.logic.xmp.XmpUtilWriter; -import org.jabref.model.database.BibDatabaseMode; -import org.jabref.model.entry.BibEntry; -import org.jabref.preferences.JabRefPreferences; - -public class XmpUtilMain { - - private static XmpPreferences xmpPreferences; - private static ImportFormatPreferences importFormatPreferences; - - private XmpUtilMain() { - } - - /** - * Reads metadata from pdf and print all included bib entries to the console. - * - * @param filename Filename of the pdf file (.pdf) - */ - private static void readPdfAndPrintBib(String filename) throws IOException { - if (filename.endsWith(".pdf")) { - List entryList = XmpUtilReader.readXmp(filename, xmpPreferences); - - BibEntryWriter bibtexEntryWriter = new BibEntryWriter( - new LatexFieldFormatter(Globals.prefs.getLatexFieldFormatterPreferences()), false); - - for (BibEntry entry : entryList) { - StringWriter writer = new StringWriter(); - bibtexEntryWriter.write(entry, writer, BibDatabaseMode.BIBTEX); - System.out.println(writer.getBuffer()); - } - } else { - System.err.println("Insert a file path (.pdf)"); - } - } - - /** - * Writes all entries included in the bib file to the metadata section of the pdf file. - * - * @param bibFile Filename of the bib file (.bib) - * @param pdfFile Filename of the pdf file (.pdf) - */ - private static void writeBibFileToPdfMetadata(String bibFile, String pdfFile) throws FileNotFoundException, IOException, TransformerException { - if (bibFile.endsWith(".bib") && pdfFile.endsWith(".pdf")) { - try (FileReader reader = new FileReader(bibFile)) { - ParserResult result = new BibtexParser(importFormatPreferences, Globals.getFileUpdateMonitor()).parse(reader); - XmpUtilWriter.writeXmp(Paths.get(pdfFile), result.getDatabase().getEntries(), result.getDatabase(), xmpPreferences); - System.out.println("Metadata sucessfully written to Pdf."); - } - } else { - System.err.println("Insert correct file paths (.bib and .pdf)"); - } - } - - /** - * Print usage information for the console tool xmpUtil. - */ - private static void printMenu() { - System.out.println("---------------------Menu-----------------------"); - System.out.println("(0) Exit"); - System.out.println("(1) Read metadata from PDF and print as bibtex"); - System.out.println("(2) Write entries in bib file to Pdf metadata"); - System.out.println(" To report bugs visit https://issues.jabref.org"); - System.out.println("-------------------------------------------------"); - System.out.print("Choose an option: "); - } - - /** - * The tool is implemented as a console application with a read-evaluate-print cycle. - */ - public static void executeXmpConsoleApplicaton() { - if (Globals.prefs == null) { - Globals.prefs = JabRefPreferences.getInstance(); - } - - xmpPreferences = Globals.prefs.getXMPPreferences(); - importFormatPreferences = Globals.prefs.getImportFormatPreferences(); - - BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in)); - - int option = -1; - while (option != 0) { - try { - XmpUtilMain.printMenu(); - option = Integer.parseInt(consoleReader.readLine()); - - if (option == 0) { - break; - } else if (option == 1) { - System.out.print("Insert your filename (.pdf): "); - String filename = consoleReader.readLine().trim(); - XmpUtilMain.readPdfAndPrintBib(filename); - } else if (option == 2) { - System.out.print("Insert your filename (.bib): "); - String bibFile = consoleReader.readLine().trim(); - System.out.print("Insert your filename (.pdf): "); - String pdfFile = consoleReader.readLine().trim(); - XmpUtilMain.writeBibFileToPdfMetadata(bibFile, pdfFile); - } - } catch (IOException | TransformerException e) { - System.err.println(e.getMessage()); - } - } - } - - public static void main(String[] args) { - XmpUtilMain.executeXmpConsoleApplicaton(); - } -} diff --git a/src/main/java/org/jabref/gui/AbstractController.java b/src/main/java/org/jabref/gui/AbstractController.java deleted file mode 100644 index 53ad25c113b..00000000000 --- a/src/main/java/org/jabref/gui/AbstractController.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.jabref.gui; - -import java.util.Objects; - -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.stage.Stage; - -public class AbstractController { - - @FXML protected T viewModel; - private Stage stage; - - /** - * Gets the associated view model. - * - * Without this method the {@link FXMLLoader} is not able to resolve references in the fxml file of the form - * text="${controller.viewModel.someProperty}" - */ - public T getViewModel() { - return viewModel; - } - - /** - * Returns the stage where this controller is displayed. - * The stage can be used to e.g. close the dialog. - */ - public Stage getStage() { - return stage; - } - - public void setStage(Stage stage) { - this.stage = Objects.requireNonNull(stage); - } -} diff --git a/src/main/java/org/jabref/gui/AbstractDialogView.java b/src/main/java/org/jabref/gui/AbstractDialogView.java deleted file mode 100644 index d7880e3cd11..00000000000 --- a/src/main/java/org/jabref/gui/AbstractDialogView.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.jabref.gui; - -import java.util.function.Function; - -public abstract class AbstractDialogView extends AbstractView { - - public AbstractDialogView() { - super(); - } - - public AbstractDialogView(Function injectionContext) { - super(injectionContext); - } - - public abstract void show(); -} diff --git a/src/main/java/org/jabref/gui/AbstractView.java b/src/main/java/org/jabref/gui/AbstractView.java deleted file mode 100644 index 055d5a69d0e..00000000000 --- a/src/main/java/org/jabref/gui/AbstractView.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.jabref.gui; - -import java.util.Optional; -import java.util.function.Function; - -import javafx.scene.Parent; -import javafx.stage.Stage; - -import org.jabref.logic.l10n.Localization; - -import com.airhacks.afterburner.views.FXMLView; - -public class AbstractView extends FXMLView { - - public AbstractView() { - this(f -> null); - } - - public AbstractView(Function injectionContext) { - super(injectionContext); - - // Set resource bundle to internal localizations - bundle = Localization.getMessages(); - } - - @Override - public Parent getView() { - Parent view = super.getView(); - - // Add our base css file - view.getStylesheets().add(0, AbstractDialogView.class.getResource("Main.css").toExternalForm()); - - // Notify controller about the stage, where it is displayed - view.sceneProperty().addListener((observable, oldValue, newValue) -> { - if (newValue != null && newValue.getWindow() instanceof Stage) { - Stage stage = (Stage) newValue.getWindow(); - if (stage != null) { - getController().ifPresent(controller -> controller.setStage(stage)); - } - } - }); - return view; - } - - private Optional getController() { - return Optional.ofNullable(presenterProperty.get()).map( - presenter -> (AbstractController) presenter); - } -} diff --git a/src/main/java/org/jabref/gui/AbstractViewModel.java b/src/main/java/org/jabref/gui/AbstractViewModel.java index f04b77cd7a8..ff9f1fca633 100644 --- a/src/main/java/org/jabref/gui/AbstractViewModel.java +++ b/src/main/java/org/jabref/gui/AbstractViewModel.java @@ -1,5 +1,5 @@ package org.jabref.gui; public class AbstractViewModel { - + //empty } diff --git a/src/main/java/org/jabref/gui/Base.css b/src/main/java/org/jabref/gui/Base.css new file mode 100644 index 00000000000..642fe8dcf88 --- /dev/null +++ b/src/main/java/org/jabref/gui/Base.css @@ -0,0 +1,1034 @@ +.root { + + /* + The theme color and some derived colors from it are used for icons, tab-headers, marking of selected inputs and + hover colors for the main menu. It generally defines the look of JabRef. The highlighting colors below should + work nicely with this base color + */ + + /* This theme is the original JabRef dark blue color */ + -jr-theme: #50618F; + -jr-accent: #a3b7e6; + -jr-selected: -jr-accent; + -jr-checked: -jr-theme; + -jr-hover: #0002; + + /* The base gray. Most gray-tones in the application are derived from this color. */ + -jr-base: #ebebeb; + + -jr-white: #ffffff; + -jr-gray-0: #f2f2f2; + -jr-gray-1: #dddddd; + -jr-gray-2: #808080; + -jr-gray-3: #404040; + -jr-black: #000; + + /* Highlights */ + -jr-blue: #0abde3; + -jr-light-blue: #48dbfb; + -jr-purple: #f368e0; + -jr-light-purple: #ff9ff3; + -jr-green: #10ac84; + -jr-light-green: #1dd1a1; + -jr-red: #ee5253; + -jr-light-red: #ff6b6b; + -jr-yellow: #feca57; + -jr-orange: #ff9f43; + + /* Some blueish greys */ + -jr-blue-gray-1: #c8d6e5; + -jr-blue-gray-2: #8395a7; + -jr-blue-gray-3: #576574; + -jr-blue-gray-4: #222f3e; + + /* Background specs */ + -jr-background-alt: -fx-background; + -jr-text-area-background: derive(-jr-base, 80%); + -jr-search-background: -jr-text-area-background; + -jr-toolbar: derive(-jr-base, 46.4%); + -jr-menu-background: derive(-jr-base, 46.4%); + -jr-menu-background-active: -jr-hover; + + -jr-menu-foreground: -fx-dark-text-color; + -jr-menu-item-foreground: -fx-dark-text-color; + -jr-menu-forground-active: -fx-dark-text-color; + + -jr-drag-target: -jr-accent; + + -jr-head-fg: -fx-text-inner-color; + + /* All icons/text on toolbars */ + -jr-theme-text: -jr-theme; + + -jr-icon-background: transparent; + -jr-icon-background-active: #0001; + -jr-icon-background-armed: #0002; + + /* Colors for messages and errors */ + -jr-info: -jr-light-green; + -jr-warn: -jr-orange; + -jr-error: -jr-light-red; + + /* Color for the small group view indicator for the number of hits */ + -jr-group-hits-bg: derive(-jr-sidepane-background, -50%); + -jr-group-hits-fg: ladder( + -jr-group-hits-bg, + -fx-light-text-color 45%, + -fx-dark-text-color 46%, + -fx-dark-text-color 59%, + -fx-mid-text-color 60% + ); + + /* Specific color for general tooltips */ + -jr-tooltip-bg: -jr-accent; + -jr-tooltip-fg: -jr-black; + + /* Finally, some specific jr styles that depend on -fx definitions in *this* style */ + -jr-sidepane-background: -jr-gray-1; + -jr-sidepane-header-background: -jr-gray-1; + -jr-sidepane-header-color: -jr-theme-text; + + /* Specs for the scrollbars */ + -jr-scrollbar-thumb: -fx-outer-border; + -jr-scrollbar-track: -fx-control-inner-background; + + -jr-separator: derive(-fx-color, -5%); + + -jr-search-text: -fx-text-base-color; + + /* + Here are redefinitions of the default properties of modena. They should in principle all be derived from the + above colors. Goal should be to make as few as possible direct color-changes to elements and only do this for + very specific purposes. + */ + -fx-base: -jr-base; + + /* A very light grey used for the background of windows. See also + * -fx-text-background-color, which should be used as the -fx-text-fill + * value for text painted on top of backgrounds colored with -fx-background. + */ + -fx-background: derive(-fx-base, 26.4%); + + /* Used for the inside of text boxes, password boxes, lists, trees, and + * tables. See also -fx-text-inner-color, which should be used as the + * -fx-text-fill value for text painted on top of backgrounds colored + * with -fx-control-inner-background. + */ + -fx-control-inner-background: derive(-fx-base, 95%); + /* Version of -fx-control-inner-background for alternative rows */ + -fx-control-inner-background-alt: derive(-fx-control-inner-background, -6%); + + /* One of these colors will be chosen based upon a ladder calculation + * that uses the brightness of a background color. Instead of using these + * colors directly as -fx-text-fill values, the sections in this file should + * use a derived color to match the background in use. See also: + * + * -fx-text-base-color for text on top of -fx-base, -fx-color, and -fx-body-color + * -fx-text-background-color for text on top of -fx-background + * -fx-text-inner-color for text on top of -fx-control-inner-color + * -fx-selection-bar-text for text on top of -fx-selection-bar + */ + -fx-dark-text-color: -jr-black; + -fx-mid-text-color: -jr-gray-3; + -fx-light-text-color: -jr-white; + + /* We overwrite accents -> make old stick out */ + -fx-accent: red; + + /* Default buttons color, this is similar to accent but more subtle */ + -fx-default-button: derive(-jr-accent, 50%); + + /* A bright blue for the focus indicator of objects. Typically used as the + * first color in -fx-background-color for the "focused" pseudo-class. Also + * typically used with insets of -1.4 to provide a glowing effect. + */ + -fx-focus-color: -jr-accent; + -fx-faint-focus-color: derive(-jr-accent, 50%); + + /* The color that is used in styling controls. The default value is based + * on -fx-base, but is changed by pseudoclasses to change the base color. + * For example, the "hover" pseudoclass will typically set -fx-color to + * -fx-hover-base (see below) and the "armed" pseudoclass will typically + * set -fx-color to -fx-pressed-base. + */ + -fx-color: -fx-base; + + -fx-hover-base: derive(-fx-base,30%); + + /* A little darker than -fx-base and used as the -fx-color for the + * "armed" pseudoclass state. + * + * TODO: should this be renamed to -fx-armed-base? + */ + -fx-pressed-base: derive(-fx-base,-6%); + + /* A little darker than -fx-color and used to draw boxes around objects such + * as progress bars, scroll bars, scroll panes, trees, tables, and lists. + */ + -fx-box-border: derive(-fx-color,-5%); + + /* Darker than -fx-background and used to draw boxes around text boxes and + * password boxes. + */ + -fx-text-box-border: derive(-fx-background, -15%); + + /* Lighter than -fx-background and used to provide a small highlight when + * needed on top of -fx-background. This is never a shadow in Modena but + * keep -fx-shadow-highlight-color name to be compatible with Caspian. + */ + -fx-shadow-highlight-color: rgba(255,255,255,0.07) 70%; + + /* A gradient that goes from a little darker than -fx-color on the top to + * even more darker than -fx-color on the bottom. Typically is the second + * color in the -fx-background-color list as the small thin border around + * a control. It is typically the same size as the control (i.e., insets + * are 0). + */ + -fx-outer-border: derive(-fx-color,-5%); + + /* A gradient that goes from a bit lighter than -fx-color on the top to + * a little darker at the bottom. Typically is the third color in the + * -fx-background-color list as a thin highlight inside the outer border. + * Insets are typically 1. + */ + -fx-inner-border: derive(-fx-color,65%); + -fx-inner-border-horizontal: derive(-fx-color,65%); + -fx-inner-border-bottomup: derive(-fx-color,65%); + + /*-fx-inner-border: red;*/ + /*-fx-inner-border-horizontal: green;*/ + /*-fx-inner-border-bottomup: blue;*/ + + /* A gradient that goes from a little lighter than -fx-color at the top to + * a little darker than -fx-color at the bottom and is used to fill the + * body of many controls such as buttons. + */ + -fx-body-color: derive(-fx-color,20%); + -fx-body-color-bottomup: derive(-fx-color,20%); + -fx-body-color-to-right: derive(-fx-color,20%); + + /* The small thin light "shadow" for mark-like objects. Typically used in + * conjunction with -fx-mark-color with an insets of 1 0 -1 0. */ + -fx-mark-highlight-color: transparent; + /*-fx-mark-highlight-color: derive(-fx-color,80%);*/ + + /* Background for items in list like things such as menus, lists, trees, + * and tables. */ + -fx-selection-bar: -jr-accent; + + /* Background color to use for selection of list cells etc. This is when + * the control doesn't have focus or the row of a previously selected item. */ + -fx-selection-bar-non-focused: lightgrey; + + /* The color to use as -fx-text-fill when painting text on top of + * backgrounds filled with -fx-selection-bar. */ + -fx-selection-bar-text: -fx-text-background-color; + + /* These are needed for Popup */ + -fx-background-color: inherit; + -fx-background-radius: inherit; + -fx-background-insets: inherit; + -fx-padding: inherit; + + /** Focus line for keyboard focus traversal on cell based controls */ + -fx-cell-focus-inner-border: derive(-fx-selection-bar,30%); + + -fx-focused-mark-color : -fx-focused-text-base-color; + + /* Consistent size for headers of tab-pane and side-panels*/ + -jr-header-height: 3em; +} + +/* + * The base css file defining the style that is valid for every pane and dialog. + */ + +.hyperlink { + -fx-padding: 0; + -fx-underline: false; + -fx-border-style: null; + -fx-border-color: null; + -fx-text-fill: -jr-theme; +} + +.hyperlink:visited { + -fx-text-fill: -jr-accent; +} + +.glyph-icon { + /* This adjusts text alignment within the bounds of text nodes so that + the text is always vertically centered within the bounds. Based on + the cap height of the text. */ + -fx-bounds-type: logical_vertical_center; +} + +.tooltip { + -fx-background-color: -jr-tooltip-bg; + -fx-opacity: 95%; + -fx-text-fill: -jr-tooltip-fg; + -fx-font-size: 1em; +} + +.tooltip > TextFlow > Text { + -fx-font-size: 1em; +} + +.tooltip > TextFlow > .tooltip-text-bold { + -fx-font-weight: bold; +} + +.tooltip > TextFlow > .tooltip-text-italic { + -fx-font-style: italic; +} + +.tooltip > TextFlow > .tooltip-text-monospaced { + -fx-font-family: monospace; +} + +.button, +.toggle-button, +.radio-button > .radio, +.check-box > .box, +.menu-button, +.choice-box, +.combo-box-base, +.combo-box-base:editable > .arrow-button { + -fx-background-color: transparent; + -fx-background-insets: 0; + -fx-background-radius: 0; + -fx-text-fill: -fx-text-base-color; +} + +.button { + -fx-background-color: transparent; + -fx-border-color: rgba(0, 0, 0, 0.23); + -fx-border-width: 1px; + -fx-border-radius: 4px; + -fx-padding: 0.5em 1em 0.5em 1em; +} + +.button:hover { + -fx-background-color: rgba(0, 0, 0, 0.12); +} + +.button:focused, +.button:pressed { + -fx-background-color: rgba(0, 0, 0, 0.3); +} + +.button:default { + -fx-background-color: -fx-default-button; +} + +.button:default:hover { + -fx-background-color: derive(-fx-default-button, -10%); +} + +.button:default:focused, +.button:default:pressed { + -fx-background-color: derive(-fx-default-button, -20%); +} + +.text-button { + -fx-border-width: 0px; +} + +.contained-button { + -fx-background-color: -jr-accent; + -fx-border-color: -jr-accent; +} + +.icon-buttonNoSpaceBottom, +.icon-buttonNoSpaceTop, +.icon-button { + -fx-border-width: 0px; + -fx-background-color: -jr-icon-background; + -fx-padding: 0.5em; +} + +.icon-buttonNoSpaceBottom:hover, +.icon-buttonNoSpaceTop:hover, +.icon-button:hover { + -fx-background-color: -jr-icon-background-active; +} + +.icon-buttonNoSpaceBottom:armed, +.icon-buttonNoSpaceTop:armed, +.icon-button:armed { + -fx-background-color: -jr-icon-background-armed; +} + +.icon-button:selected { + -fx-background-color: -jr-icon-background-active; + -fx-text-fill: white; + -fx-fill: white; +} + +.toggle-button:selected.icon-button:selected { + -fx-background-color: transparent; + -fx-fill: -jr-selected; +} + +.icon-buttonNoSpaceBottom { + -fx-padding: 0.5em 0.5em -0.1em 0.5em; +} + +.icon-buttonNoSpaceTop { + -fx-padding: -0.1em 0.5em 0.5em 0.5em; +} + +.check-box { + -fx-label-padding: 0.0em 0.0em 0.0em 0.75em; + -fx-text-fill: -fx-text-background-color; +} + +.check-box > .box { + -fx-border-color: rgba(0, 0, 0, 0.54); + -fx-border-width: 2px; + -fx-border-radius: 1px; + -fx-padding: 0.1em 0.1em 0.2em 0.2em; +} + +.check-box:selected > .box { + -fx-background-insets: 2px; + -fx-border-color: -jr-checked; + -fx-background-color: -jr-checked; +} + +.check-box > .box > .mark { + -fx-background-color: -fx-control-inner-background; + -fx-padding: 0.2em 0.2em 0.2em 0.2em; + -fx-shape: "M6.61 11.89L3.5 8.78 2.44 9.84 6.61 14l8.95-8.95L14.5 4z"; + -fx-stroke-width: 5; +} + +.radio-button > .radio { + -fx-background-radius: 1.0em; /* large value to make sure this remains circular */ + -fx-padding: 0.35em; /* padding from outside edge to the inner dot */ + -fx-background-color: rgba(0, 0, 0, 0.54), -fx-control-inner-background; + -fx-background-insets: 0, 2px; +} + +.radio-button:selected > .radio { + -fx-background-color: -jr-checked, -fx-background; +} + +.radio-button > .radio > .dot { + -fx-padding: 0.25em; /* radius of the inner dot when selected */ + -fx-background-insets: 0; +} + +.radio-button:selected > .radio > .dot { + -fx-background-color: -jr-checked; +} + +.menu-bar { + -fx-background-color: -jr-menu-background; + -fx-background-insets: 0; + -fx-background-radius: 0; +} + +.menu-bar > .container > .menu-button > .label { + -fx-padding: 0.41777em 0.41777em 0.41777em 0.41777em; +} + +.menu-bar > .menu { + -fx-padding: 0.0em 0.666667em 0.0em 0.666667em; +} + +.menu-item { + -fx-padding: 0.5em 0.41777em 0.5em 0.41777em; +} + +.tab-pane { + -fx-open-tab-animation: NONE; + -fx-close-tab-animation: NONE; +} + +.tab-pane > .tab-header-area > .headers-region > .tab { + -fx-background-insets: 0; + -fx-background-radius: 0; + -fx-background-color: -jr-background-alt; + -fx-border-color: -fx-outer-border; + -fx-border-width: 0.5 0.5 0.5 0.5; + -fx-padding: 0.3em 0.9em 0.3em 0.9em; + -fx-pref-height: -jr-header-height; +} + +.tab-pane > .tab-header-area > .headers-region > .tab .tab-label { + -fx-text-fill: -fx-mid-text-color; +} + +.tab-pane > .tab-header-area > .headers-region > .control-buttons-tab { + -fx-border-color: -jr-theme; + -fx-fill: -jr-theme-text; + -fx-text-fill: -jr-theme-text; +} + +.tab-pane > .tab-header-area > .headers-region > .tab:selected { + -fx-background-color: -fx-control-inner-background; + -fx-border-color: -jr-theme; + -fx-border-width: 3 0 0 0; +} + +.tab-pane > .tab-header-area > .headers-region > .tab:selected .tab-label{ + -fx-fill: -jr-theme-text; + -fx-text-fill: -jr-theme-text; +} + +.tab-pane > .tab-header-area > .headers-region > .tab:selected .tab-close-button { + -fx-background-color: -jr-theme-text; +} + +.tab-pane > .tab-header-area > .headers-region > .tab:selected .glyph-icon { + -fx-text-fill: -fx-mid-text-color; + -fx-fill: -fx-mid-text-color; +} + +.tab-pane:focused > .tab-header-area > .headers-region > .tab:selected .focus-indicator { + -fx-border-width: 0; + -fx-border-insets: 0; + -fx-border-radius: 0; +} + +.tab-pane > .tab-header-area > .tab-header-background { + -fx-background-color: -jr-background-alt; +} + +.tab-pane > .tab-header-area > .headers-region > .tab .glyph-icon { + -glyph-size: 13px; + -fx-text-fill: -fx-mid-text-color; + -fx-fill: -fx-mid-text-color; +} + +.tab-pane > .tab-header-area > .headers-region > .tab:selected .glyph-icon { + -fx-text-fill: -jr-theme-text; + -fx-fill: -jr-theme-text; +} + +.tab-pane > .tab-header-area { + -fx-padding: 0 0 0 0; +} + +.table-view { + -fx-background-insets: 0; + -fx-padding: 0; +} + +.table-view:focused { + -fx-background-insets: 0; +} + +.split-pane > .split-pane-divider { + -fx-background-color: -jr-sidepane-background; + -fx-padding: 0 .5 0 .5; +} + +.table-row-cell:hover, +.tree-table-row-cell:hover { + -fx-background-color: -jr-hover; + -fx-text-fill: -fx-focused-text-base-color; + -fx-fill: -fx-focused-text-base-color; +} + +.table-row-cell:hover, +.tree-table-row-cell:selected > .tree-table-cell > .glyph-icon { + -fx-fill: white; + -fx-text-fill: white; +} + +.table-view > .virtual-flow > .clipped-container > .sheet > .table-row-cell .table-cell:selected, +.tree-table-view > .virtual-flow > .clipped-container > .sheet > .tree-table-row-cell .tree-table-cell:selected { + -fx-border-color: transparent; + -fx-background-insets: 0; +} + +.scroll-pane:focused, +.split-pane:focused, +.list-view:focused, +.tree-view:focused, +.table-view:focused, +.tree-table-view:focused, +.html-editor:contains-focus { + -fx-background-color: -fx-control-inner-background; + -fx-background-insets: 0; + -fx-background-radius: 0; +} + +/* Selected rows */ +.list-view:focused > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected, +.tree-view:focused > .virtual-flow > .clipped-container > .sheet > .tree-cell:filled:selected, +.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected, +.tree-table-view:focused > .virtual-flow > .clipped-container > .sheet > .tree-table-row-cell:filled:selected, +.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell .table-cell:selected, +.tree-table-view:focused > .virtual-flow > .clipped-container > .sheet > .tree-table-row-cell .tree-table-cell:selected { + -fx-background: -jr-selected; + -fx-background-color: -jr-selected; + -fx-table-cell-border-color: transparent; +} + +/* Selected when control is not focused */ +.list-cell:filled:selected, +.tree-cell:filled:selected, +.table-row-cell:filled:selected, +.tree-table-row-cell:filled:selected, +.table-row-cell:filled > .table-cell:selected, +.tree-table-row-cell:filled > .tree-table-cell:selected { + -fx-background: -jr-selected; + -fx-background-color: -jr-selected; + -fx-table-cell-border-color: transparent; + -fx-text-fill: -jr-black; +} + +.combo-box-base { + -fx-background-color: -fx-outer-border, -fx-control-inner-background; + -fx-background-insets: 0, 1; + -fx-background-radius: 0, 0; +} + +.combo-box > .list-cell { + -fx-background-color: -fx-outer-border, -fx-control-inner-background; + -fx-background-insets: 0, 1 0 1 1; + -fx-text-fill: -fx-text-base-color; +} + +.combo-box-popup > .list-view { + -fx-background-color: -fx-control-inner-background; + -fx-background-insets: 0; + -fx-effect: null; +} + +.combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell { + -fx-padding: 4 0 4 5; + /* No alternate highlighting */ + -fx-background: -fx-control-inner-background; +} + +.combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:hover { + -fx-background: -fx-control-inner-background; + -fx-background-color: -jr-hover; +} + +.combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected, +.combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected:hover { + -fx-background: -fx-control-inner-background; + -fx-background-insets: 0; +} + +.combo-box-popup > .list-view > .placeholder > .label { + -fx-text-fill: -fx-text-base-color; +} + +.stack-pane, +.scroll-pane, +.scroll-pane > .viewport, +.split-pane { + -fx-background-color: transparent; + -fx-background-insets: 0, 0; + -fx-padding: 0; +} + +.text-input { + -fx-background-color: -fx-outer-border, -fx-control-inner-background; + -fx-background-insets: 0, 1; + -fx-prompt-text-fill: -fx-mid-text-color; +} + +.text-input:focused { + -fx-highlight-fill: derive(-jr-accent, 20%); + -fx-background-color: -jr-accent, -fx-control-inner-background; + -fx-background-insets: 0, 2; + -fx-highlight-text-fill: -fx-text-inner-color; +} + +.text-area { + -fx-background-color: -fx-control-inner-background; +} + +.text-area .content { + -fx-background-color: -fx-outer-border, -fx-control-inner-background; + -fx-background-insets: 0, 1; + -fx-padding: 0.333333em 0.583em 0.333333em 0.583em; + -fx-background-radius: 0; +} + +.text-area:focused .content { + -fx-background-color: -jr-accent, -fx-control-inner-background; + -fx-background-insets: 0, 2; + -fx-background-radius: 0; +} + +.text-area > .scroll-pane > .corner { + -fx-background-radius: 0 0 0 0; + -fx-background-color: -fx-background; +} + +.combo-box-base:editable > .text-field, +.date-picker > .text-field { + -fx-background-color: -fx-outer-border, -fx-control-inner-background; + -fx-background-insets: 0, 1; + -fx-background-radius: 0; +} + +.combo-box-base:editable:focused > .text-field, +.combo-box-base:editable > .text-field:focused, +.date-picker > .text-field:focused { + -fx-background-color: -jr-accent, -fx-control-inner-background; + -fx-background-insets: 0, 2; + -fx-background-radius: 0; +} + +.date-picker:focused > .text-field { + -fx-background-color: -fx-control-inner-background; + -fx-background-insets: 0; +} + +.scroll-bar { + -fx-background-color: transparent; + -fx-opacity: 0; +} + +.scroll-bar:horizontal .track, +.scroll-bar:vertical .track { + -fx-background-color: -jr-scrollbar-track; + -fx-opacity: 0.2; + -fx-background-radius: 0em; +} + + +.scroll-bar:horizontal .thumb, +.scroll-bar:vertical .thumb { + -fx-background-color: -jr-scrollbar-thumb; + -fx-background-insets: 0, 0, 0; + -fx-background-radius: 0em; +} + +.scroll-bar .thumb:hover, +.scroll-bar .thumb:pressed { + -fx-background-color: derive(-jr-scrollbar-thumb, -20%); +} + +/* Hide increment and decrement buttons */ +.scroll-bar > .increment-button, +.scroll-bar > .decrement-button { + -fx-background-color: null; + -fx-background-radius: 0; + -fx-background-insets: 0; + -fx-padding: 0; +} + +/* Hide increment and decrement arrows */ +.scroll-bar:horizontal > .decrement-button > .decrement-arrow, +.scroll-bar:horizontal > .increment-button > .increment-arrow, +.scroll-bar:vertical > .decrement-button > .decrement-arrow, +.scroll-bar:vertical > .increment-button > .increment-arrow { + -fx-background-color: null; + -fx-background-radius: 0; + -fx-background-insets: 0; + -fx-shape: null; + -fx-padding: 0; +} + +/* Need some padding since otherwise no scroll-bar is displayed at all */ +.scroll-bar:horizontal > .decrement-button > .decrement-arrow { + -fx-padding: 0.333em 0.167em 0.333em 0.167em; /* 4 2 4 2 */ +} + +.scroll-bar:vertical > .decrement-button > .decrement-arrow { + -fx-padding: 0em 0.333em 0em 0.333em; /* 2 4 2 4 */ +} + +/* Only show scrollbars for hovered elements */ +.list-view:hover .scroll-bar, +.tree-view:hover .scroll-bar, +.table-view:hover .scroll-bar, +.tree-table-view:hover .scroll-bar, +.text-input:hover .scroll-bar, +.scroll-pane:hover .scroll-bar { + -fx-opacity: 1; +} + +.sidePaneComponentHeader { + -fx-background-color: -jr-sidepane-header-background; + -fx-padding: 0.3em 0.9em 0.3em 0.9em; + -fx-pref-height: -jr-header-height; +} + +.sidePaneComponentHeader > .label { + -fx-text-fill: -jr-sidepane-header-color; + -fx-font-weight: bold; + -fx-padding: 0.3em 0.9em 0.3em 0.9em; +} + +.sidePaneComponentHeader .glyph-icon{ + -fx-fill: -jr-sidepane-header-color; + -fx-text-fill: -jr-sidepane-header-color; + -fx-font-size: 16px; +} + +.mainMenu { + -fx-background-color: -jr-menu-background; + -fx-background-insets: 0; +} + +.menu-bar > .container { + -fx-border-width: 0; +} + +.menu-bar > .container > .menu-button:hover, +.menu-bar > .container > .menu-button:focused, +.menu-bar > .container > .menu-button:showing { + -fx-background-color: -jr-menu-background-active; + -fx-background: -jr-menu-background-active; +} + +.menu-bar > .container > .menu-button:hover > .label, +.menu-bar > .container > .menu-button:focused > .label, +.menu-bar > .container > .menu-button:showing > .label { + -fx-text-fill: -jr-menu-forground-active; +} + +.mainMenu > .container > .menu-button > .label { + -fx-text-fill: -jr-menu-foreground; +} + +.menu-item > .label { + -fx-text-fill: -jr-menu-item-foreground; +} + +.menu-item:focused { + -fx-text-fill: -jr-menu-background-active; + -fx-background: -jr-menu-background-active; + -fx-background-color: -jr-menu-background-active; +} + +.menu-item:focused > .label { + -fx-text-fill: -jr-menu-forground-active; +} + +.menu-item .glyph-icon { + -fx-fill: -jr-menu-item-foreground; + -fx-text-fill: -jr-menu-item-foreground; +} + +.menu-item:focused .glyph-icon { + -fx-fill: -jr-menu-forground-active; + -fx-text-fill: -jr-menu-forground-active; +} + +.context-menu { + -fx-border-color: -fx-outer-border; + -fx-border-width: 1; +} + +.separator:horizontal .line { + -fx-border-color: -jr-separator; + -fx-border-width: 0.3; + -fx-border-insets: 1 15 0 20; +} + +.separator:vertical .line { + -fx-border-color: -jr-separator; + -fx-border-width: 1; + -fx-border-insets: 5 15 5 15; +} + +.mainToolbar { + -fx-background-color: -jr-toolbar; + -fx-border-color: derive(-jr-toolbar, 50%); + -fx-border-width: 0; +} + +.mainToolbar .glyph-icon { + -fx-font-size: 1.7em; + -fx-fill: -jr-theme-text; + -fx-text-fill: -jr-theme-text; +} + +.mainToolbar .search-field { + -fx-background-color: -jr-search-background; + -fx-border-width: 1; + -fx-border-color: -jr-separator; + -fx-border-radius: 2; + -fx-fill: -jr-search-text; +} + +.mainToolbar .search-field .glyph-icon { + -fx-fill: -jr-search-text; + -fx-text-fill: -jr-search-text; +} + +/* The little arrow that shows up when not all tool-bar icons fit into the tool-bar. +We want to have a look that matches our icons in the tool-bar */ +.mainToolbar .tool-bar-overflow-button > .arrow { + -fx-background-color: -jr-theme-text; +} + +.mainToolbar .tool-bar-overflow-button:hover > .arrow { + -fx-background-color: -fx-mark-highlight-color, derive(-jr-theme-text, -30%); +} + +.table-view { + -fx-border-width: 0; + -fx-padding: 0; + -fx-border-insets: 0; + -fx-table-cell-border-color: transparent; +} + +.table-view .column-header-background { + -fx-background-color: -fx-control-inner-background; + -fx-border-width: 0; +} + +.table-view .column-header-background:hover { + -fx-background-color: -fx-outer-border; +} + +.table-view .column-header, +.table-view .filler { + -fx-background-color: transparent, -fx-control-inner-background; + -fx-background-insets: 0, 0 0.02em 0 0.02em; + -fx-font-weight: bold; + -fx-size: 3em; + -fx-border-width: 0 0 1 0; + -fx-border-color: -fx-outer-border; +} + +.table-view .column-header > .label { + -fx-padding: 0 1em 0 1em; + -fx-alignment: center-left; + -fx-text-fill: -jr-head-fg; +} + +.table-view .column-header .glyph-icon { + -fx-alignment: baseline-center; + -fx-text-fill: -jr-head-fg; + -fx-fill: -jr-head-fg; +} + +.table-cell, .table-cell .glyph-icon{ + -fx-padding: 0.5em 1em 0.5em 1em; + -fx-cell-size: 4.0em; + -fx-text-fill: -fx-text-background-color; + -fx-fill: -fx-text-background-color; +} + +/* Improve the context menu of the main toolbar, when icons don't fit and you have to press the little arrow to see them */ +.mainToolbar .context-menu .glyph-icon { + -fx-fill: -jr-theme-text; +} + +.mainToolbar .context-menu .glyph-icon:hover { + -fx-fill: -jr-theme-active; + -fx-text-fill: -jr-theme-active; + -fx-background-color: -jr-icon-background-active; /* TODO: This has no effect */ +} + + +/* This is awful, but I don't know a better way*/ +.mainToolbar .context-menu * { + -fx-background-color: -fx-control-inner-background; +} + +#sidePane { + -fx-background-color: -jr-sidepane-background; +} + +.sidePaneComponent { + -fx-background-color: -jr-sidepane-background; +} + +.jfx-snackbar-content { + -fx-background-color: -jr-gray-3; + -fx-background-radius: 4px; + -fx-padding: 0ex 0ex 0ex 0ex; +} + +.jfx-snackbar-toast { + -fx-text-fill: -fx-light-text-color; + -fx-padding: -1ex -0.5ex -1ex -0.5ex; +} + +.progress-bar > .bar { + -fx-background-color: -jr-theme; +} + +.progress-bar:indeterminate > .bar { + -fx-background-color: -jr-theme; +} + +.progress-bar > .track { + -fx-background-color: -jr-accent; +} + +.jfx-color-picker:armed, +.jfx-color-picker:hover, +.jfx-color-picker:focused, +.jfx-color-picker { + -fx-background-color: transparent, transparent, transparent, transparent; + -fx-background-radius: 0px; + -fx-background-insets: 0px; + -fx-effect: null; +} + +.color-palette { + -fx-background-color: -fx-background; + -fx-background-radius: 0px; + -fx-background-insets: 0px; +} + +.color-palette-region .button { + -fx-border-width: 0px; +} + +.bibEntry { + +} + +.bibEntry .type { + -fx-font-size: 110%; +} + +.bibEntry .title { + -fx-font-size: 110%; + -fx-font-weight: bold; +} + +.bibEntry .year { + -fx-font-size: 101%; + -fx-font-weight: bold; +} + +.bibEntry .journal { + -fx-font-size: 101%; +} + +.bibEntry .authors { + -fx-font-size: 101%; +} + +.bibEntry .summary { + -fx-padding: 1ex 0ex 0ex 0ex; +} + +.warning-icon { + -fx-fill: -jr-warn; +} + +.error-icon { + -fx-text-fill: -jr-error; + -fx-fill: -jr-error; +} + +.tooltip-warning { + -fx-background-color: -jr-warn; +} + +.tooltip-error { + -fx-background-color: -jr-error; +} + +.sectionHeader { + -fx-font-size: 1.5em; + -fx-padding: 1em 0em 1em 0em; +} diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/BasePanel.java index b7162fe2dae..ea88315f304 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/BasePanel.java @@ -1,47 +1,32 @@ package org.jabref.gui; -import java.awt.BorderLayout; -import java.awt.Toolkit; -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.ClipboardOwner; -import java.awt.datatransfer.StringSelection; -import java.awt.datatransfer.Transferable; -import java.awt.event.ActionEvent; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.lang.reflect.InvocationTargetException; -import java.nio.charset.Charset; -import java.nio.charset.UnsupportedCharsetException; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import javax.swing.AbstractAction; -import javax.swing.BorderFactory; -import javax.swing.JComponent; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JSplitPane; -import javax.swing.JTextArea; import javax.swing.SwingUtilities; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javafx.application.Platform; -import javafx.embed.swing.JFXPanel; -import javafx.scene.Scene; +import javafx.beans.binding.Bindings; +import javafx.geometry.Orientation; +import javafx.scene.Node; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.SplitPane; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.StackPane; import org.jabref.Globals; import org.jabref.JabRefExecutorService; @@ -49,41 +34,32 @@ import org.jabref.gui.actions.BaseAction; import org.jabref.gui.actions.CleanupAction; import org.jabref.gui.actions.CopyBibTeXKeyAndLinkAction; +import org.jabref.gui.actions.GenerateBibtexKeyAction; +import org.jabref.gui.actions.WriteXMPAction; import org.jabref.gui.autocompleter.AutoCompletePreferences; import org.jabref.gui.autocompleter.AutoCompleteUpdater; import org.jabref.gui.autocompleter.PersonNameSuggestionProvider; import org.jabref.gui.autocompleter.SuggestionProviders; -import org.jabref.gui.bibtexkeypattern.SearchFixDuplicateLabels; import org.jabref.gui.collab.DatabaseChangeMonitor; -import org.jabref.gui.collab.FileUpdatePanel; -import org.jabref.gui.contentselector.ContentSelectorDialog; -import org.jabref.gui.customjfx.CustomJFXPanel; +import org.jabref.gui.collab.DatabaseChangePane; import org.jabref.gui.desktop.JabRefDesktop; +import org.jabref.gui.edit.ReplaceStringAction; import org.jabref.gui.entryeditor.EntryEditor; -import org.jabref.gui.exporter.ExportToClipboardAction; import org.jabref.gui.exporter.SaveDatabaseAction; import org.jabref.gui.externalfiles.FindFullTextAction; -import org.jabref.gui.externalfiles.SynchronizeFileField; -import org.jabref.gui.externalfiles.WriteXMPAction; import org.jabref.gui.externalfiletype.ExternalFileMenuItem; import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; -import org.jabref.gui.fieldeditors.FieldEditor; -import org.jabref.gui.filelist.AttachFileAction; import org.jabref.gui.filelist.FileListEntry; import org.jabref.gui.filelist.FileListTableModel; -import org.jabref.gui.groups.GroupAddRemoveDialog; +import org.jabref.gui.icon.JabRefIcon; import org.jabref.gui.importer.actions.AppendDatabaseAction; import org.jabref.gui.journals.AbbreviateAction; import org.jabref.gui.journals.UnabbreviateAction; -import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.maintable.MainTable; import org.jabref.gui.maintable.MainTableDataModel; -import org.jabref.gui.maintable.MainTableFormat; -import org.jabref.gui.maintable.MainTableSelectionListener; -import org.jabref.gui.mergeentries.MergeEntriesDialog; +import org.jabref.gui.mergeentries.MergeEntriesAction; import org.jabref.gui.mergeentries.MergeWithFetchedEntryAction; -import org.jabref.gui.plaintextimport.TextInputDialog; import org.jabref.gui.specialfields.SpecialFieldDatabaseChangeListener; import org.jabref.gui.specialfields.SpecialFieldValueViewModel; import org.jabref.gui.specialfields.SpecialFieldViewModel; @@ -92,31 +68,17 @@ import org.jabref.gui.undo.UndoableChangeType; import org.jabref.gui.undo.UndoableFieldChange; import org.jabref.gui.undo.UndoableInsertEntry; -import org.jabref.gui.undo.UndoableKeyChange; import org.jabref.gui.undo.UndoableRemoveEntry; import org.jabref.gui.util.DefaultTaskExecutor; -import org.jabref.gui.util.FileDialogConfiguration; -import org.jabref.gui.util.component.CheckBoxMessage; -import org.jabref.gui.worker.AbstractWorker; -import org.jabref.gui.worker.CallBack; import org.jabref.gui.worker.CitationStyleToClipboardWorker; -import org.jabref.gui.worker.MarkEntriesAction; import org.jabref.gui.worker.SendAsEMailAction; -import org.jabref.logic.bibtexkeypattern.BibtexKeyGenerator; import org.jabref.logic.citationstyle.CitationStyleCache; import org.jabref.logic.citationstyle.CitationStyleOutputFormat; -import org.jabref.logic.exporter.BibtexDatabaseWriter; -import org.jabref.logic.exporter.FileSaveSession; -import org.jabref.logic.exporter.SaveException; -import org.jabref.logic.exporter.SavePreferences; -import org.jabref.logic.exporter.SaveSession; -import org.jabref.logic.l10n.Encodings; import org.jabref.logic.l10n.Localization; import org.jabref.logic.layout.Layout; import org.jabref.logic.layout.LayoutHelper; import org.jabref.logic.pdf.FileAnnotationCache; import org.jabref.logic.search.SearchQuery; -import org.jabref.logic.util.FileType; import org.jabref.logic.util.UpdateField; import org.jabref.logic.util.io.FileFinder; import org.jabref.logic.util.io.FileFinders; @@ -132,32 +94,29 @@ import org.jabref.model.database.shared.DatabaseLocation; import org.jabref.model.database.shared.DatabaseSynchronizer; import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.EntryType; import org.jabref.model.entry.FieldName; import org.jabref.model.entry.InternalBibtexFields; +import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.event.EntryChangedEvent; import org.jabref.model.entry.event.EntryEventSource; import org.jabref.model.entry.specialfields.SpecialField; import org.jabref.model.entry.specialfields.SpecialFieldValue; -import org.jabref.model.strings.StringUtil; import org.jabref.preferences.JabRefPreferences; import org.jabref.preferences.PreviewPreferences; import com.google.common.eventbus.Subscribe; -import com.jgoodies.forms.builder.FormBuilder; -import com.jgoodies.forms.layout.FormLayout; +import org.fxmisc.easybind.EasyBind; +import org.fxmisc.easybind.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class BasePanel extends JPanel implements ClipboardOwner { +public class BasePanel extends StackPane { private static final Logger LOGGER = LoggerFactory.getLogger(BasePanel.class); - // Divider size for BaseFrame split pane. 0 means non-resizable. - private static final int SPLIT_PANE_DIVIDER_SIZE = 4; - private final BibDatabaseContext bibDatabaseContext; private final MainTableDataModel tableModel; + private final DatabaseChangePane changePane; private final CitationStyleCache citationStyleCache; private final FileAnnotationCache annotationCache; @@ -166,22 +125,20 @@ public class BasePanel extends JPanel implements ClipboardOwner { // The undo manager. private final UndoAction undoAction = new UndoAction(); private final RedoAction redoAction = new RedoAction(); - private final CountingUndoManager undoManager = new CountingUndoManager(); - private final List previousEntries = new ArrayList<>(); - private final List nextEntries = new ArrayList<>(); + private final CountingUndoManager undoManager; // Keeps track of the string dialog if it is open. - private final Map actions = new HashMap<>(); + private final Map actions = new HashMap<>(); private final SidePaneManager sidePaneManager; private final PreviewPanel preview; - private final JFXPanel previewContainer; + private final BasePanelPreferences preferences; + private final ExternalFileTypes externalFileTypes; + private final EntryEditor entryEditor; + private MainTable mainTable; // To contain instantiated entry editors. This is to save time // As most enums, this must not be null private BasePanelMode mode = BasePanelMode.SHOWING_NOTHING; - private final EntryEditor entryEditor; - private final JFXPanel entryEditorContainer; - private MainTableSelectionListener selectionListener; - private JSplitPane splitPane; + private SplitPane splitPane; private boolean saving; // AutoCompleter used in the search bar @@ -189,41 +146,34 @@ public class BasePanel extends JPanel implements ClipboardOwner { private boolean baseChanged; private boolean nonUndoableChange; // Used to track whether the base has changed since last save. - private MainTable mainTable; - private MainTableFormat tableFormat; private BibEntry showing; - // Variable to prevent erroneous update of back/forward histories at the time - // when a Back or Forward operation is being processed: - private boolean backOrForwardInProgress; - // in switching between entries. - private PreambleEditor preambleEditor; - // Keeps track of the preamble dialog if it is open. - private StringDialog stringDialog; + private SuggestionProviders suggestionProviders; + @SuppressWarnings({"FieldCanBeLocal", "unused"}) private Subscription dividerPositionSubscription; + // the query the user searches when this BasePanel is active private Optional currentSearchQuery = Optional.empty(); private Optional changeMonitor = Optional.empty(); + private final DialogService dialogService; - public BasePanel(JabRefFrame frame, BibDatabaseContext bibDatabaseContext) { - Objects.requireNonNull(frame); - Objects.requireNonNull(bibDatabaseContext); + public BasePanel(JabRefFrame frame, BasePanelPreferences preferences, BibDatabaseContext bibDatabaseContext, ExternalFileTypes externalFileTypes) { + this.preferences = Objects.requireNonNull(preferences); + this.frame = Objects.requireNonNull(frame); + this.bibDatabaseContext = Objects.requireNonNull(bibDatabaseContext); + this.externalFileTypes = Objects.requireNonNull(externalFileTypes); + this.undoManager = frame.getUndoManager(); + this.dialogService = frame.getDialogService(); - this.bibDatabaseContext = bibDatabaseContext; bibDatabaseContext.getDatabase().registerListener(this); bibDatabaseContext.getMetaData().registerListener(this); this.sidePaneManager = frame.getSidePaneManager(); - this.frame = frame; this.tableModel = new MainTableDataModel(getBibDatabaseContext()); citationStyleCache = new CitationStyleCache(bibDatabaseContext); - annotationCache = new FileAnnotationCache(bibDatabaseContext); - - this.preview = new PreviewPanel(this, getBibDatabaseContext()); - DefaultTaskExecutor.runInJavaFXThread(() -> frame().getGlobalSearchBar().getSearchQueryHighlightObservable().addSearchListener(preview)); - this.previewContainer = CustomJFXPanel.wrap(new Scene(preview)); + annotationCache = new FileAnnotationCache(bibDatabaseContext, Globals.prefs.getFilePreferences()); setupMainPanel(); @@ -234,11 +184,15 @@ public BasePanel(JabRefFrame frame, BibDatabaseContext bibDatabaseContext) { // ensure that at each addition of a new entry, the entry is added to the groups interface this.bibDatabaseContext.getDatabase().registerListener(new GroupTreeListener()); + // ensure that all entry changes mark the panel as changed + this.bibDatabaseContext.getDatabase().registerListener(this); Optional file = bibDatabaseContext.getDatabaseFile(); if (file.isPresent()) { // Register so we get notifications about outside changes to the file. - changeMonitor = Optional.of(new DatabaseChangeMonitor(bibDatabaseContext, Globals.getFileUpdateMonitor(), this)); + changeMonitor = Optional.of(new DatabaseChangeMonitor(bibDatabaseContext, Globals.getFileUpdateMonitor(), Globals.TASK_EXECUTOR)); + changePane = new DatabaseChangePane(mainTable.getPane(), bibDatabaseContext, changeMonitor.get()); + getChildren().add(changePane); } else { if (bibDatabaseContext.getDatabase().hasEntries()) { // if the database is not empty and no file is assigned, @@ -246,66 +200,21 @@ public BasePanel(JabRefFrame frame, BibDatabaseContext bibDatabaseContext) { // -> mark as changed this.baseChanged = true; } + changePane = null; } this.getDatabase().registerListener(new UpdateTimestampListener(Globals.prefs)); - entryEditor = new EntryEditor(this); - entryEditorContainer = setupEntryEditor(entryEditor); - - } - - private static JFXPanel setupEntryEditor(EntryEditor entryEditor) { - JFXPanel container = CustomJFXPanel.wrap(new Scene(entryEditor)); - container.addKeyListener(new KeyAdapter() { - - @Override - public void keyPressed(KeyEvent e) { - - //We need to consume this event here to prevent the propgation of keybinding events back to the JFrame - Optional keyBinding = Globals.getKeyPrefs().mapToKeyBinding(e); - if (keyBinding.isPresent()) { - switch (keyBinding.get()) { - case CUT: - case COPY: - case PASTE: - case DELETE_ENTRY: - case SELECT_ALL: - e.consume(); - break; - default: - //do nothing - } - } - } - }); - return container; - } - - public static void runWorker(AbstractWorker worker) throws Exception { - // This part uses Spin's features: - Runnable wrk = worker.getWorker(); - // The Worker returned by getWorker() has been wrapped - // by Spin.off(), which makes its methods be run in - // a different thread from the EDT. - CallBack clb = worker.getCallBack(); + this.entryEditor = new EntryEditor(this, preferences.getEntryEditorPreferences(), Globals.getFileUpdateMonitor(), dialogService, externalFileTypes, Globals.TASK_EXECUTOR); - worker.init(); // This method runs in this same thread, the EDT. - // Useful for initial GUI actions, like printing a message. + this.preview = new PreviewPanel(this, getBibDatabaseContext(), preferences.getKeyBindings(), preferences.getPreviewPreferences(), dialogService, externalFileTypes); + frame().getGlobalSearchBar().getSearchQueryHighlightObservable().addSearchListener(preview); - // The CallBack returned by getCallBack() has been wrapped - // by Spin.over(), which makes its methods be run on - // the EDT. - wrk.run(); // Runs the potentially time-consuming action - // without freezing the GUI. The magic is that THIS line - // of execution will not continue until run() is finished. - clb.update(); // Runs the update() method on the EDT. } @Subscribe public void listen(BibDatabaseContextChangedEvent event) { - SwingUtilities.invokeLater(() -> this.markBaseChanged()); - + this.markBaseChanged(); } /** @@ -337,9 +246,7 @@ public String getTabTitle() { } } } else if (databaseLocation == DatabaseLocation.SHARED) { - title.append( - this.bibDatabaseContext.getDBMSSynchronizer().getDBName() + " [" + Localization.lang("shared") - + "]"); + title.append(this.bibDatabaseContext.getDBMSSynchronizer().getDBName() + " [" + Localization.lang("shared") + "]"); } return title.toString(); @@ -366,39 +273,30 @@ public void output(String s) { } private void setupActions() { - SaveDatabaseAction saveAction = new SaveDatabaseAction(this); + SaveDatabaseAction saveAction = new SaveDatabaseAction(this, Globals.prefs); CleanupAction cleanUpAction = new CleanupAction(this, Globals.prefs); actions.put(Actions.UNDO, undoAction); actions.put(Actions.REDO, redoAction); - actions.put(Actions.FOCUS_TABLE, (BaseAction) () -> { - mainTable.requestFocus(); - }); - // The action for opening an entry editor. - actions.put(Actions.EDIT, (BaseAction) selectionListener::editSignalled); + actions.put(Actions.EDIT, this::showAndEdit); // The action for saving a database. - actions.put(Actions.SAVE, saveAction); - - actions.put(Actions.SAVE_AS, (BaseAction) saveAction::saveAs); + actions.put(Actions.SAVE, saveAction::save); - actions.put(Actions.SAVE_SELECTED_AS, new SaveSelectedAction(SavePreferences.DatabaseSaveType.ALL)); + actions.put(Actions.SAVE_AS, saveAction::saveAs); - actions.put(Actions.SAVE_SELECTED_AS_PLAIN, - new SaveSelectedAction(SavePreferences.DatabaseSaveType.PLAIN_BIBTEX)); + actions.put(Actions.SAVE_SELECTED_AS_PLAIN, saveAction::saveSelectedAsPlain); // The action for copying selected entries. - actions.put(Actions.COPY, (BaseAction) () -> copy()); + actions.put(Actions.COPY, this::copy); actions.put(Actions.PRINT_PREVIEW, new PrintPreviewAction()); - actions.put(Actions.CUT, (BaseAction) this::cut); + actions.put(Actions.CUT, this::cut); - //when you modify this action be sure to adjust Actions.CUT, - //they are the same except of the Localization, delete confirmation and Actions.COPY call - actions.put(Actions.DELETE, (BaseAction) () -> delete(false)); + actions.put(Actions.DELETE, () -> delete(false)); // The action for pasting entries or cell contents. // - more robust detection of available content flavors (doesn't only look at first one offered) @@ -406,172 +304,45 @@ private void setupActions() { // This allows you to (a) paste entire bibtex entries from a text editor, web browser, etc // (b) copy and paste entries between multiple instances of JabRef (since // only the text representation seems to get as far as the X clipboard, at least on my system) - actions.put(Actions.PASTE, (BaseAction) () -> paste()); + actions.put(Actions.PASTE, this::paste); - actions.put(Actions.SELECT_ALL, (BaseAction) mainTable::selectAll); - - // The action for opening the preamble editor - actions.put(Actions.EDIT_PREAMBLE, (BaseAction) () -> { - if (preambleEditor == null) { - PreambleEditor form = new PreambleEditor(frame, BasePanel.this, bibDatabaseContext.getDatabase()); - form.setLocationRelativeTo(frame); - form.setVisible(true); - preambleEditor = form; - } else { - preambleEditor.setVisible(true); - } - }); - - // The action for opening the string editor - actions.put(Actions.EDIT_STRINGS, (BaseAction) () -> { - if (stringDialog == null) { - StringDialog form = new StringDialog(frame, BasePanel.this, bibDatabaseContext.getDatabase()); - form.setVisible(true); - stringDialog = form; - } else { - stringDialog.setVisible(true); - } - }); - - actions.put(FindUnlinkedFilesDialog.ACTION_COMMAND, (BaseAction) () -> { - final FindUnlinkedFilesDialog dialog = new FindUnlinkedFilesDialog(frame, frame, BasePanel.this); - dialog.setLocationRelativeTo(frame); - dialog.setVisible(true); - }); + actions.put(Actions.SELECT_ALL, mainTable.getSelectionModel()::selectAll); // The action for auto-generating keys. - actions.put(Actions.MAKE_KEY, new AbstractWorker() { - - List entries; - int numSelected; - boolean canceled; - - // Run first, in EDT: - @Override - public void init() { - entries = getSelectedEntries(); - numSelected = entries.size(); - - if (entries.isEmpty()) { // None selected. Inform the user to select entries first. - JOptionPane.showMessageDialog(frame, - Localization.lang("First select the entries you want keys to be generated for."), - Localization.lang("Autogenerate BibTeX keys"), JOptionPane.INFORMATION_MESSAGE); - return; - } - frame.block(); - output(formatOutputMessage(Localization.lang("Generating BibTeX key for"), numSelected)); - } - - // Run second, on a different thread: - @Override - public void run() { - // 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 (Globals.prefs.getBoolean(JabRefPreferences.WARN_BEFORE_OVERWRITING_KEY)) { - if (entries.parallelStream().anyMatch(BibEntry::hasCiteKey)) { - CheckBoxMessage cbm = new CheckBoxMessage( - Localization.lang("One or more keys will be overwritten. Continue?"), - Localization.lang("Disable this confirmation dialog"), false); - final int answer = JOptionPane.showConfirmDialog(frame, cbm, - Localization.lang("Overwrite keys"), JOptionPane.YES_NO_OPTION); - Globals.prefs.putBoolean(JabRefPreferences.WARN_BEFORE_OVERWRITING_KEY, !cbm.isSelected()); - - // The user doesn't want to overide cite keys - if (answer == JOptionPane.NO_OPTION) { - canceled = true; - return; - } - } - } - - // generate the new cite keys for each entry - final NamedCompound ce = new NamedCompound(Localization.lang("Autogenerate BibTeX keys")); - BibtexKeyGenerator keyGenerator = new BibtexKeyGenerator(bibDatabaseContext, Globals.prefs.getBibtexKeyPatternPreferences()); - for (BibEntry entry : entries) { - Optional change = keyGenerator.generateAndSetKey(entry); - change.ifPresent(fieldChange -> ce.addEdit(new UndoableKeyChange(fieldChange))); - } - ce.end(); - - // register the undo event only if new cite keys were generated - if (ce.hasEdits()) { - getUndoManager().addEdit(ce); - } - } - - // Run third, on EDT: - @Override - public void update() { - if (canceled) { - frame.unblock(); - return; - } - markBaseChanged(); - numSelected = entries.size(); - - //////////////////////////////////////////////////////////////////////////////// - // Prevent selection loss for autogenerated BibTeX-Keys - //////////////////////////////////////////////////////////////////////////////// - for (final BibEntry bibEntry : entries) { - SwingUtilities.invokeLater(() -> { - final int row = mainTable.findEntry(bibEntry); - if ((row >= 0) && (mainTable.getSelectedRowCount() < entries.size())) { - mainTable.addRowSelectionInterval(row, row); - } - }); - } - //////////////////////////////////////////////////////////////////////////////// - output(formatOutputMessage(Localization.lang("Generated BibTeX key for"), numSelected)); - frame.unblock(); - } - }); + actions.put(Actions.MAKE_KEY, new GenerateBibtexKeyAction(this, frame.getDialogService())); // The action for cleaning up entry. actions.put(Actions.CLEANUP, cleanUpAction); - actions.put(Actions.MERGE_ENTRIES, (BaseAction) () -> new MergeEntriesDialog(BasePanel.this)); - - actions.put(Actions.SEARCH, (BaseAction) frame.getGlobalSearchBar()::focus); - actions.put(Actions.GLOBAL_SEARCH, (BaseAction) frame.getGlobalSearchBar()::performGlobalSearch); + actions.put(Actions.MERGE_ENTRIES, () -> new MergeEntriesAction(frame).execute()); // The action for copying the selected entry's key. - actions.put(Actions.COPY_KEY, (BaseAction) () -> copyKey()); + actions.put(Actions.COPY_KEY, this::copyKey); // The action for copying the selected entry's title. - actions.put(Actions.COPY_TITLE, (BaseAction) () -> copyTitle()); + actions.put(Actions.COPY_TITLE, this::copyTitle); // The action for copying a cite for the selected entry. - actions.put(Actions.COPY_CITE_KEY, (BaseAction) () -> copyCiteKey()); + actions.put(Actions.COPY_CITE_KEY, this::copyCiteKey); // The action for copying the BibTeX key and the title for the first selected entry - actions.put(Actions.COPY_KEY_AND_TITLE, (BaseAction) () -> copyKeyAndTitle()); - - actions.put(Actions.COPY_CITATION_ASCII_DOC, - (BaseAction) () -> copyCitationToClipboard(CitationStyleOutputFormat.ASCII_DOC)); - actions.put(Actions.COPY_CITATION_XSLFO, - (BaseAction) () -> copyCitationToClipboard(CitationStyleOutputFormat.XSL_FO)); - actions.put(Actions.COPY_CITATION_HTML, - (BaseAction) () -> copyCitationToClipboard(CitationStyleOutputFormat.HTML)); - actions.put(Actions.COPY_CITATION_RTF, - (BaseAction) () -> copyCitationToClipboard(CitationStyleOutputFormat.RTF)); - actions.put(Actions.COPY_CITATION_TEXT, - (BaseAction) () -> copyCitationToClipboard(CitationStyleOutputFormat.TEXT)); + actions.put(Actions.COPY_KEY_AND_TITLE, this::copyKeyAndTitle); + + actions.put(Actions.COPY_CITATION_ASCII_DOC, () -> copyCitationToClipboard(CitationStyleOutputFormat.ASCII_DOC)); + actions.put(Actions.COPY_CITATION_XSLFO, () -> copyCitationToClipboard(CitationStyleOutputFormat.XSL_FO)); + actions.put(Actions.COPY_CITATION_HTML, () -> copyCitationToClipboard(CitationStyleOutputFormat.HTML)); + actions.put(Actions.COPY_CITATION_RTF, () -> copyCitationToClipboard(CitationStyleOutputFormat.RTF)); + actions.put(Actions.COPY_CITATION_TEXT, () -> copyCitationToClipboard(CitationStyleOutputFormat.TEXT)); // The action for copying the BibTeX keys as hyperlinks to the urls of the selected entries - actions.put(Actions.COPY_KEY_AND_LINK, new CopyBibTeXKeyAndLinkAction(mainTable)); + actions.put(Actions.COPY_KEY_AND_LINK, new CopyBibTeXKeyAndLinkAction(mainTable, Globals.clipboardManager)); actions.put(Actions.MERGE_DATABASE, new AppendDatabaseAction(frame, this)); - actions.put(Actions.ADD_FILE_LINK, new AttachFileAction(this)); + actions.put(Actions.OPEN_EXTERNAL_FILE, this::openExternalFile); - actions.put(Actions.OPEN_EXTERNAL_FILE, (BaseAction) () -> openExternalFile()); - - actions.put(Actions.OPEN_FOLDER, (BaseAction) () -> JabRefExecutorService.INSTANCE.execute(() -> { - final List files = FileUtil.getListOfLinkedFiles(mainTable.getSelectedEntries(), - bibDatabaseContext.getFileDirectoriesAsPaths(Globals.prefs.getFileDirectoryPreferences())); + actions.put(Actions.OPEN_FOLDER, () -> JabRefExecutorService.INSTANCE.execute(() -> { + final List files = FileUtil.getListOfLinkedFiles(mainTable.getSelectedEntries(), bibDatabaseContext.getFileDirectoriesAsPaths(Globals.prefs.getFilePreferences())); for (final Path f : files) { try { JabRefDesktop.openFolderAndSelectFile(f.toAbsolutePath()); @@ -581,175 +352,63 @@ public void update() { } })); - actions.put(Actions.OPEN_CONSOLE, (BaseAction) () -> JabRefDesktop - .openConsole(frame.getCurrentBasePanel().getBibDatabaseContext().getDatabaseFile().orElse(null))); + actions.put(Actions.OPEN_CONSOLE, () -> JabRefDesktop.openConsole(frame.getCurrentBasePanel().getBibDatabaseContext().getDatabaseFile().orElse(null))); - actions.put(Actions.PULL_CHANGES_FROM_SHARED_DATABASE, (BaseAction) () -> { + actions.put(Actions.PULL_CHANGES_FROM_SHARED_DATABASE, () -> { DatabaseSynchronizer dbmsSynchronizer = frame.getCurrentBasePanel().getBibDatabaseContext().getDBMSSynchronizer(); dbmsSynchronizer.pullChanges(); }); actions.put(Actions.OPEN_URL, new OpenURLAction()); - actions.put(Actions.MERGE_WITH_FETCHED_ENTRY, new MergeWithFetchedEntryAction(this)); + actions.put(Actions.MERGE_WITH_FETCHED_ENTRY, new MergeWithFetchedEntryAction(this, frame.getDialogService())); - actions.put(Actions.REPLACE_ALL, (BaseAction) () -> { - final ReplaceStringDialog rsd = new ReplaceStringDialog(frame); - rsd.setVisible(true); - if (!rsd.okPressed()) { - return; - } - int counter = 0; - final NamedCompound ce = new NamedCompound(Localization.lang("Replace string")); - if (rsd.selOnly()) { - for (BibEntry be : mainTable.getSelectedEntries()) { - counter += rsd.replace(be, ce); - } - } else { - for (BibEntry entry : bibDatabaseContext.getDatabase().getEntries()) { - counter += rsd.replace(entry, ce); - } - } + actions.put(Actions.REPLACE_ALL, () -> (new ReplaceStringAction(this)).execute()); - output(Localization.lang("Replaced") + ' ' + counter + ' ' - + (counter == 1 ? Localization.lang("occurrence") : Localization.lang("occurrences")) + '.'); - if (counter > 0) { - ce.end(); - getUndoManager().addEdit(ce); - markBaseChanged(); - } - }); + actions.put(new SpecialFieldValueViewModel(SpecialField.RELEVANCE.getValues().get(0)).getCommand(), + new SpecialFieldViewModel(SpecialField.RELEVANCE, undoManager).getSpecialFieldAction(SpecialField.RELEVANCE.getValues().get(0), frame)); - actions.put(Actions.DUPLI_CHECK, - (BaseAction) () -> JabRefExecutorService.INSTANCE.execute(new DuplicateSearch(BasePanel.this))); + actions.put(new SpecialFieldValueViewModel(SpecialField.QUALITY.getValues().get(0)).getCommand(), + new SpecialFieldViewModel(SpecialField.QUALITY, undoManager).getSpecialFieldAction(SpecialField.QUALITY.getValues().get(0), frame)); - actions.put(Actions.PLAIN_TEXT_IMPORT, (BaseAction) () -> { - // get Type of new entry - EntryTypeDialog etd = new EntryTypeDialog(frame); - etd.setLocationRelativeTo(BasePanel.this); - etd.setVisible(true); - EntryType tp = etd.getChoice(); - if (tp == null) { - return; - } - - BibEntry bibEntry = new BibEntry(tp.getName()); - TextInputDialog tidialog = new TextInputDialog(frame, bibEntry); - tidialog.setLocationRelativeTo(BasePanel.this); - tidialog.setVisible(true); - - if (tidialog.okPressed()) { - UpdateField.setAutomaticFields(Collections.singletonList(bibEntry), false, false, - Globals.prefs.getUpdateFieldPreferences()); - insertEntry(bibEntry); - } - }); - - actions.put(Actions.MARK_ENTRIES, new MarkEntriesAction(frame, 0)); - - actions.put(Actions.UNMARK_ENTRIES, (BaseAction) () -> { - try { - List bes = mainTable.getSelectedEntries(); - if (bes.isEmpty()) { - output(Localization.lang("This operation requires one or more entries to be selected.")); - return; - } - NamedCompound ce = new NamedCompound(Localization.lang("Unmark entries")); - for (BibEntry be : bes) { - EntryMarker.unmarkEntry(be, false, bibDatabaseContext.getDatabase(), ce); - } - ce.end(); - getUndoManager().addEdit(ce); - markBaseChanged(); - String outputStr; - if (bes.size() == 1) { - outputStr = Localization.lang("Unmarked selected entry"); - } else { - outputStr = Localization.lang("Unmarked all %0 selected entries", Integer.toString(bes.size())); - } - output(outputStr); - } catch (Throwable ex) { - LOGGER.warn("Could not unmark", ex); - } - }); - - actions.put(Actions.UNMARK_ALL, (BaseAction) () -> { - NamedCompound ce = new NamedCompound(Localization.lang("Unmark all")); - - for (BibEntry be : bibDatabaseContext.getDatabase().getEntries()) { - EntryMarker.unmarkEntry(be, false, bibDatabaseContext.getDatabase(), ce); - } - ce.end(); - getUndoManager().addEdit(ce); - markBaseChanged(); - output(Localization.lang("Unmarked all entries")); - }); - - // Note that we can't put the number of entries that have been reverted into the undoText as the concrete number cannot be injected - actions.put(new SpecialFieldValueViewModel(SpecialField.RELEVANCE.getValues().get(0)).getActionName(), - new SpecialFieldViewModel(SpecialField.RELEVANCE).getSpecialFieldAction( - SpecialField.RELEVANCE.getValues().get(0), frame)); - actions.put(new SpecialFieldValueViewModel(SpecialField.QUALITY.getValues().get(0)).getActionName(), - new SpecialFieldViewModel(SpecialField.QUALITY) - .getSpecialFieldAction(SpecialField.QUALITY.getValues().get(0), frame)); - actions.put(new SpecialFieldValueViewModel(SpecialField.PRINTED.getValues().get(0)).getActionName(), - new SpecialFieldViewModel(SpecialField.PRINTED).getSpecialFieldAction( - SpecialField.PRINTED.getValues().get(0), frame)); + actions.put(new SpecialFieldValueViewModel(SpecialField.PRINTED.getValues().get(0)).getCommand(), + new SpecialFieldViewModel(SpecialField.PRINTED, undoManager).getSpecialFieldAction(SpecialField.PRINTED.getValues().get(0), frame)); for (SpecialFieldValue prio : SpecialField.PRIORITY.getValues()) { - actions.put(new SpecialFieldValueViewModel(prio).getActionName(), - new SpecialFieldViewModel(SpecialField.PRIORITY).getSpecialFieldAction(prio, this.frame)); + actions.put(new SpecialFieldValueViewModel(prio).getCommand(), + new SpecialFieldViewModel(SpecialField.PRIORITY, undoManager).getSpecialFieldAction(prio, this.frame)); } for (SpecialFieldValue rank : SpecialField.RANKING.getValues()) { - actions.put(new SpecialFieldValueViewModel(rank).getActionName(), - new SpecialFieldViewModel(SpecialField.RANKING).getSpecialFieldAction(rank, this.frame)); + actions.put(new SpecialFieldValueViewModel(rank).getCommand(), + new SpecialFieldViewModel(SpecialField.RANKING, undoManager).getSpecialFieldAction(rank, this.frame)); } for (SpecialFieldValue status : SpecialField.READ_STATUS.getValues()) { - actions.put(new SpecialFieldValueViewModel(status).getActionName(), - new SpecialFieldViewModel(SpecialField.READ_STATUS).getSpecialFieldAction(status, this.frame)); + actions.put(new SpecialFieldValueViewModel(status).getCommand(), + new SpecialFieldViewModel(SpecialField.READ_STATUS, undoManager).getSpecialFieldAction(status, this.frame)); } - actions.put(Actions.TOGGLE_PREVIEW, (BaseAction) () -> { + actions.put(Actions.TOGGLE_PREVIEW, () -> { PreviewPreferences previewPreferences = Globals.prefs.getPreviewPreferences(); boolean enabled = !previewPreferences.isPreviewPanelEnabled(); - PreviewPreferences newPreviewPreferences = previewPreferences - .getBuilder() - .withPreviewPanelEnabled(enabled) - .build(); + PreviewPreferences newPreviewPreferences = previewPreferences.getBuilder() + .withPreviewPanelEnabled(enabled) + .build(); Globals.prefs.storePreviewPreferences(newPreviewPreferences); - setPreviewActiveBasePanels(enabled); - frame.setPreviewToggle(enabled); + DefaultTaskExecutor.runInJavaFXThread(() -> setPreviewActiveBasePanels(enabled)); }); - actions.put(Actions.NEXT_PREVIEW_STYLE, (BaseAction) this::nextPreviewStyle); - actions.put(Actions.PREVIOUS_PREVIEW_STYLE, (BaseAction) this::previousPreviewStyle); - - actions.put(Actions.MANAGE_SELECTORS, (BaseAction) () -> { - ContentSelectorDialog csd = new ContentSelectorDialog(frame, frame, BasePanel.this, false, null); - csd.setLocationRelativeTo(frame); - csd.setVisible(true); - }); + actions.put(Actions.NEXT_PREVIEW_STYLE, this::nextPreviewStyle); + actions.put(Actions.PREVIOUS_PREVIEW_STYLE, this::previousPreviewStyle); - actions.put(Actions.EXPORT_TO_CLIPBOARD, new ExportToClipboardAction(frame)); actions.put(Actions.SEND_AS_EMAIL, new SendAsEMailAction(frame)); - actions.put(Actions.WRITE_XMP, new WriteXMPAction(this)); + actions.put(Actions.WRITE_XMP, new WriteXMPAction(this)::execute); actions.put(Actions.ABBREVIATE_ISO, new AbbreviateAction(this, true)); actions.put(Actions.ABBREVIATE_MEDLINE, new AbbreviateAction(this, false)); actions.put(Actions.UNABBREVIATE, new UnabbreviateAction(this)); - actions.put(Actions.AUTO_SET_FILE, new SynchronizeFileField(this)); - actions.put(Actions.BACK, (BaseAction) BasePanel.this::back); - actions.put(Actions.FORWARD, (BaseAction) BasePanel.this::forward); - - actions.put(Actions.RESOLVE_DUPLICATE_KEYS, new SearchFixDuplicateLabels(this)); - - actions.put(Actions.ADD_TO_GROUP, new GroupAddRemoveDialog(this, true, false)); - actions.put(Actions.REMOVE_FROM_GROUP, new GroupAddRemoveDialog(this, false, false)); - actions.put(Actions.MOVE_TO_GROUP, new GroupAddRemoveDialog(this, true, true)); - - actions.put(Actions.DOWNLOAD_FULL_TEXT, new FindFullTextAction(this)); + actions.put(Actions.DOWNLOAD_FULL_TEXT, new FindFullTextAction(this)::execute); } /** @@ -758,39 +417,8 @@ public void update() { * @param outputFormat the desired {@link CitationStyleOutputFormat} */ private void copyCitationToClipboard(CitationStyleOutputFormat outputFormat) { - new CitationStyleToClipboardWorker(this, outputFormat).execute(); - } - - private void copy() { - List bes = mainTable.getSelectedEntries(); - - if (bes.isEmpty()) { - // The user maybe selected a single cell. - // TODO: Check if this can actually happen - int[] rows = mainTable.getSelectedRows(); - int[] cols = mainTable.getSelectedColumns(); - if ((cols.length == 1) && (rows.length == 1)) { - // Copy single value. - Object o = mainTable.getValueAt(rows[0], cols[0]); - if (o != null) { - StringSelection ss = new StringSelection(o.toString()); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, BasePanel.this); - - output(Localization.lang("Copied cell contents") + '.'); - } - } - } else { - TransferableBibtexEntry trbe = new TransferableBibtexEntry(bes); - // ! look at ClipBoardManager - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(trbe, BasePanel.this); - output(formatOutputMessage(Localization.lang("Copied"), bes.size())); - } - } - - private void cut() { - runCommand(Actions.COPY); - // cannot call runCommand(Actions.DELETE), b/c it will call delete(false) with the wrong parameter - delete(true); + CitationStyleToClipboardWorker worker = new CitationStyleToClipboardWorker(this, outputFormat, dialogService, Globals.clipboardManager, Globals.prefs.getPreviewPreferences()); + worker.copyCitationStyleToClipboard(Globals.TASK_EXECUTOR); } /** @@ -799,7 +427,7 @@ private void cut() { * @param cut If false the user will get asked if he really wants to delete the entries, and it will be localized as * "deleted". If true the action will be localized as "cut" */ - private void delete(boolean cut) { + public void delete(boolean cut) { delete(cut, mainTable.getSelectedEntries()); } @@ -817,20 +445,11 @@ private void delete(boolean cut, List entries) { return; } - // select the next entry to stay at the same place as before (or the previous if we're already at the end) - if (mainTable.getSelectedRow() != (mainTable.getRowCount() - 1)) { - selectNextEntry(); - } else { - selectPreviousEntry(); - } - NamedCompound compound; if (cut) { - compound = new NamedCompound( - (entries.size() > 1 ? Localization.lang("cut entries") : Localization.lang("cut entry"))); + compound = new NamedCompound((entries.size() > 1 ? Localization.lang("cut entries") : Localization.lang("cut entry"))); } else { - compound = new NamedCompound( - (entries.size() > 1 ? Localization.lang("delete entries") : Localization.lang("delete entry"))); + compound = new NamedCompound((entries.size() > 1 ? Localization.lang("delete entries") : Localization.lang("delete entry"))); } for (BibEntry entry : entries) { compound.addEdit(new UndoableRemoveEntry(bibDatabaseContext.getDatabase(), entry, BasePanel.this)); @@ -841,8 +460,7 @@ private void delete(boolean cut, List entries) { getUndoManager().addEdit(compound); markBaseChanged(); - frame.output( - formatOutputMessage(cut ? Localization.lang("Cut") : Localization.lang("Deleted"), entries.size())); + frame.output(formatOutputMessage(cut ? Localization.lang("Cut") : Localization.lang("Deleted"), entries.size())); // prevent the main table from loosing focus mainTable.requestFocus(); @@ -852,78 +470,26 @@ public void delete(BibEntry entry) { delete(false, Collections.singletonList(entry)); } - private void paste() { - Collection bes = new ClipBoardManager().extractBibEntriesFromClipboard(); - - // finally we paste in the entries (if any), which either came from TransferableBibtexEntries - // or were parsed from a string - if (!bes.isEmpty()) { - - NamedCompound ce = new NamedCompound( - (bes.size() > 1 ? Localization.lang("paste entries") : Localization.lang("paste entry"))); - - // Store the first inserted bibtexentry. - // bes[0] does not work as bes[0] is first clonded, - // then inserted. - // This entry is used to open up an entry editor - // for the first inserted entry. - BibEntry firstBE = null; - - for (BibEntry be1 : bes) { - - BibEntry be = (BibEntry) be1.clone(); - if (firstBE == null) { - firstBE = be; - } - UpdateField.setAutomaticFields(be, Globals.prefs.getUpdateFieldPreferences()); - - // We have to clone the - // entries, since the pasted - // entries must exist - // independently of the copied - // ones. - bibDatabaseContext.getDatabase().insertEntry(be); - - ce.addEdit(new UndoableInsertEntry(bibDatabaseContext.getDatabase(), be, BasePanel.this)); - } - ce.end(); - getUndoManager().addEdit(ce); - output(formatOutputMessage(Localization.lang("Pasted"), bes.size())); - markBaseChanged(); - - highlightEntry(firstBE); - mainTable.requestFocus(); - - if (Globals.prefs.getBoolean(JabRefPreferences.AUTO_OPEN_FORM)) { - selectionListener.editSignalled(firstBE); - } - } - } - private void copyTitle() { List selectedBibEntries = mainTable.getSelectedEntries(); if (!selectedBibEntries.isEmpty()) { // Collect all non-null titles. List titles = selectedBibEntries.stream() - .filter(bibEntry -> bibEntry.getTitle().isPresent()) - .map(bibEntry -> bibEntry.getTitle().get()) - .collect(Collectors.toList()); + .filter(bibEntry -> bibEntry.getTitle().isPresent()) + .map(bibEntry -> bibEntry.getTitle().get()) + .collect(Collectors.toList()); if (titles.isEmpty()) { output(Localization.lang("None of the selected entries have titles.")); return; } - StringSelection ss = new StringSelection(String.join("\n", titles)); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, BasePanel.this); + Globals.clipboardManager.setContent(String.join("\n", titles)); if (titles.size() == selectedBibEntries.size()) { // All entries had titles. - output((selectedBibEntries.size() > 1 ? Localization.lang("Copied titles") : Localization - .lang("Copied title")) + '.'); + output((selectedBibEntries.size() > 1 ? Localization.lang("Copied titles") : Localization.lang("Copied title")) + '.'); } else { - output(Localization.lang("Warning: %0 out of %1 entries have undefined title.", - Integer.toString(selectedBibEntries.size() - titles.size()), - Integer.toString(selectedBibEntries.size()))); + output(Localization.lang("Warning: %0 out of %1 entries have undefined title.", Integer.toString(selectedBibEntries.size() - titles.size()), Integer.toString(selectedBibEntries.size()))); } } } @@ -943,17 +509,15 @@ private void copyCiteKey() { String sb = String.join(",", keys); String citeCommand = Optional.ofNullable(Globals.prefs.get(JabRefPreferences.CITE_COMMAND)) - .filter(cite -> cite.contains("\\")) // must contain \ - .orElse("\\cite"); - StringSelection ss = new StringSelection(citeCommand + "{" + sb + '}'); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, BasePanel.this); + .filter(cite -> cite.contains("\\")) // must contain \ + .orElse("\\cite"); + Globals.clipboardManager.setContent(citeCommand + "{" + sb + '}'); if (keys.size() == bes.size()) { // All entries had keys. output(bes.size() > 1 ? Localization.lang("Copied keys") : Localization.lang("Copied key") + '.'); } else { - output(Localization.lang("Warning: %0 out of %1 entries have undefined BibTeX key.", - Integer.toString(bes.size() - keys.size()), Integer.toString(bes.size()))); + output(Localization.lang("Warning: %0 out of %1 entries have undefined BibTeX key.", Integer.toString(bes.size() - keys.size()), Integer.toString(bes.size()))); } } } @@ -971,15 +535,13 @@ private void copyKey() { return; } - StringSelection ss = new StringSelection(String.join(",", keys)); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, BasePanel.this); + Globals.clipboardManager.setContent(String.join(",", keys)); if (keys.size() == bes.size()) { // All entries had keys. output((bes.size() > 1 ? Localization.lang("Copied keys") : Localization.lang("Copied key")) + '.'); } else { - output(Localization.lang("Warning: %0 out of %1 entries have undefined BibTeX key.", - Integer.toString(bes.size() - keys.size()), Integer.toString(bes.size()))); + output(Localization.lang("Warning: %0 out of %1 entries have undefined BibTeX key.", Integer.toString(bes.size() - keys.size()), Integer.toString(bes.size()))); } } } @@ -988,13 +550,11 @@ private void copyKeyAndTitle() { List bes = mainTable.getSelectedEntries(); if (!bes.isEmpty()) { // OK: in a future version, this string should be configurable to allow arbitrary exports - StringReader sr = new StringReader( - "\\bibtexkey - \\begin{title}\\format[RemoveBrackets]{\\title}\\end{title}\n"); + StringReader sr = new StringReader("\\bibtexkey - \\begin{title}\\format[RemoveBrackets]{\\title}\\end{title}\n"); Layout layout; try { - layout = new LayoutHelper(sr, - Globals.prefs.getLayoutFormatterPreferences(Globals.journalAbbreviationLoader)) - .getLayoutFromText(); + layout = new LayoutHelper(sr, Globals.prefs.getLayoutFormatterPreferences(Globals.journalAbbreviationLoader)) + .getLayoutFromText(); } catch (IOException e) { LOGGER.info("Could not get layout", e); return; @@ -1016,15 +576,13 @@ private void copyKeyAndTitle() { return; } - final StringSelection ss = new StringSelection(sb.toString()); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, BasePanel.this); + Globals.clipboardManager.setContent(sb.toString()); if (copied == bes.size()) { // All entries had keys. output((bes.size() > 1 ? Localization.lang("Copied keys") : Localization.lang("Copied key")) + '.'); } else { - output(Localization.lang("Warning: %0 out of %1 entries have undefined BibTeX key.", - Integer.toString(bes.size() - copied), Integer.toString(bes.size()))); + output(Localization.lang("Warning: %0 out of %1 entries have undefined BibTeX key.", Integer.toString(bes.size() - copied), Integer.toString(bes.size()))); } } } @@ -1051,8 +609,7 @@ private void openExternalFile() { return; } FileListEntry flEntry = fileListTableModel.getEntry(0); - ExternalFileMenuItem item = new ExternalFileMenuItem(frame(), entry, "", flEntry.getLink(), - flEntry.getType().get().getIcon(), bibDatabaseContext, flEntry.getType()); + ExternalFileMenuItem item = new ExternalFileMenuItem(frame(), "", flEntry.getLink(), flEntry.getType().map(ExternalFileType::getIcon).map(JabRefIcon::getSmallIcon).orElse(null), bibDatabaseContext, flEntry.getType()); item.doClick(); }); } @@ -1061,116 +618,25 @@ private void openExternalFile() { * This method is called from JabRefFrame if a database specific action is requested by the user. Runs the command * if it is defined, or prints an error message to the standard error stream. * - * @param _command The name of the command to run. + * @param command The name of the command to run. */ - public void runCommand(final String _command) { - if (!actions.containsKey(_command)) { - LOGGER.info("No action defined for '" + _command + '\''); + public void runCommand(final Actions command) { + if (!actions.containsKey(command)) { + LOGGER.info("No action defined for '" + command + '\''); return; } - Object o = actions.get(_command); + BaseAction action = actions.get(command); try { - if (o instanceof BaseAction) { - ((BaseAction) o).action(); - } else { - runWorker((AbstractWorker) o); - } + action.action(); } catch (Throwable ex) { - // If the action has blocked the JabRefFrame before crashing, we need to unblock it. - // The call to unblock will simply hide the glasspane, so there is no harm in calling - // it even if the frame hasn't been blocked. - frame.unblock(); LOGGER.error("runCommand error: " + ex.getMessage(), ex); } } - private boolean saveDatabase(File file, boolean selectedOnly, Charset enc, - SavePreferences.DatabaseSaveType saveType) - throws SaveException { - SaveSession session; - frame.block(); - final String SAVE_DATABASE = Localization.lang("Save library"); - try { - SavePreferences prefs = SavePreferences.loadForSaveFromPreferences(Globals.prefs) - .withEncoding(enc) - .withSaveType(saveType); - BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter<>( - FileSaveSession::new); - if (selectedOnly) { - session = databaseWriter.savePartOfDatabase(bibDatabaseContext, mainTable.getSelectedEntries(), prefs); - } else { - session = databaseWriter.saveDatabase(bibDatabaseContext, prefs); - } - - registerUndoableChanges(session); - } - // FIXME: not sure if this is really thrown anywhere - catch (UnsupportedCharsetException ex) { - JOptionPane.showMessageDialog(frame, - Localization.lang("Could not save file.") + ' ' - + Localization.lang("Character encoding '%0' is not supported.", enc.displayName()), - SAVE_DATABASE, JOptionPane.ERROR_MESSAGE); - throw new SaveException("rt"); - } catch (SaveException ex) { - if (ex.specificEntry()) { - // Error occurred during processing of the entry. Highlight it: - highlightEntry(ex.getEntry()); - showAndEdit(ex.getEntry()); - } else { - LOGGER.warn("Could not save", ex); - } - - JOptionPane.showMessageDialog(frame, Localization.lang("Could not save file.") + "\n" + ex.getMessage(), - SAVE_DATABASE, JOptionPane.ERROR_MESSAGE); - throw new SaveException("rt"); - } finally { - frame.unblock(); - } - - boolean commit = true; - if (!session.getWriter().couldEncodeAll()) { - FormBuilder builder = FormBuilder.create() - .layout(new FormLayout("left:pref, 4dlu, fill:pref", "pref, 4dlu, pref")); - JTextArea ta = new JTextArea(session.getWriter().getProblemCharacters()); - ta.setEditable(false); - builder.add(Localization.lang("The chosen encoding '%0' could not encode the following characters:", - session.getEncoding().displayName())).xy(1, 1); - builder.add(ta).xy(3, 1); - builder.add(Localization.lang("What do you want to do?")).xy(1, 3); - String tryDiff = Localization.lang("Try different encoding"); - int answer = JOptionPane.showOptionDialog(frame, builder.getPanel(), SAVE_DATABASE, - JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, - new String[] {Localization.lang("Save"), tryDiff, Localization.lang("Cancel")}, tryDiff); - - if (answer == JOptionPane.NO_OPTION) { - // The user wants to use another encoding. - Object choice = JOptionPane.showInputDialog(frame, Localization.lang("Select encoding"), SAVE_DATABASE, - JOptionPane.QUESTION_MESSAGE, null, Encodings.ENCODINGS_DISPLAYNAMES, enc); - if (choice == null) { - commit = false; - } else { - Charset newEncoding = Charset.forName((String) choice); - return saveDatabase(file, selectedOnly, newEncoding, saveType); - } - } else if (answer == JOptionPane.CANCEL_OPTION) { - commit = false; - } - } - - if (commit) { - session.commit(file.toPath()); - this.bibDatabaseContext.getMetaData().setEncoding(enc); // Make sure to remember which encoding we used. - } else { - session.cancel(); - } - - return commit; - } - - public void registerUndoableChanges(SaveSession session) { + public void registerUndoableChanges(List changes) { NamedCompound ce = new NamedCompound(Localization.lang("Save actions")); - for (FieldChange change : session.getFieldChanges()) { + for (FieldChange change : changes) { ce.addEdit(new UndoableFieldChange(change)); } ce.end(); @@ -1179,59 +645,6 @@ public void registerUndoableChanges(SaveSession session) { } } - /** - * This method is called from JabRefFrame when the user wants to create a new entry. If the argument is null, the - * user is prompted for an entry type. - * - * @param type The type of the entry to create. - * @return The newly created BibEntry or null the operation was canceled by the user. - */ - public BibEntry newEntry(EntryType type) { - EntryType actualType = type; - if (actualType == null) { - // Find out what type is wanted. - final EntryTypeDialog etd = new EntryTypeDialog(frame); - // We want to center the dialog, to make it look nicer. - etd.setLocationRelativeTo(frame); - etd.setVisible(true); - actualType = etd.getChoice(); - } - if (actualType != null) { // Only if the dialog was not canceled. - final BibEntry be = new BibEntry(actualType.getName()); - try { - bibDatabaseContext.getDatabase().insertEntry(be); - // Set owner/timestamp if options are enabled: - List list = new ArrayList<>(); - list.add(be); - UpdateField.setAutomaticFields(list, true, true, Globals.prefs.getUpdateFieldPreferences()); - - // Create an UndoableInsertEntry object. - getUndoManager().addEdit(new UndoableInsertEntry(bibDatabaseContext.getDatabase(), be, BasePanel.this)); - output(Localization.lang("Added new '%0' entry.", actualType.getName().toLowerCase(Locale.ROOT))); - - // We are going to select the new entry. Before that, make sure that we are in - // show-entry mode. If we aren't already in that mode, enter the WILL_SHOW_EDITOR - // mode which makes sure the selection will trigger display of the entry editor - // and adjustment of the splitter. - if (mode != BasePanelMode.SHOWING_EDITOR) { - mode = BasePanelMode.WILL_SHOW_EDITOR; - } - - highlightEntry(be); - - // The database just changed. - markBaseChanged(); - - this.showAndEdit(be); - - return be; - } catch (KeyCollisionException ex) { - LOGGER.info(ex.getMessage(), ex); - } - } - return null; - } - /** * This method is called from JabRefFrame when the user wants to create a new entry. * @@ -1241,31 +654,31 @@ public void insertEntry(final BibEntry bibEntry) { if (bibEntry != null) { try { bibDatabaseContext.getDatabase().insertEntry(bibEntry); - if (Globals.prefs.getBoolean(JabRefPreferences.USE_OWNER)) { - // Set owner field to default value - UpdateField.setAutomaticFields(bibEntry, true, true, Globals.prefs.getUpdateFieldPreferences()); - } + + // Set owner and timestamp + UpdateField.setAutomaticFields(bibEntry, true, true, Globals.prefs.getUpdateFieldPreferences()); + // Create an UndoableInsertEntry object. - getUndoManager().addEdit(new UndoableInsertEntry(bibDatabaseContext.getDatabase(), bibEntry, BasePanel.this)); + getUndoManager().addEdit(new UndoableInsertEntry(bibDatabaseContext.getDatabase(), bibEntry)); output(Localization.lang("Added new '%0' entry.", bibEntry.getType())); markBaseChanged(); // The database just changed. if (Globals.prefs.getBoolean(JabRefPreferences.AUTO_OPEN_FORM)) { - selectionListener.editSignalled(bibEntry); + showAndEdit(bibEntry); } - highlightEntry(bibEntry); + clearAndSelect(bibEntry); } catch (KeyCollisionException ex) { LOGGER.info("Collision for bibtex key" + bibEntry.getId(), ex); } } } - public void editEntryByIdAndFocusField(final String entryId, final String fieldName) { - bibDatabaseContext.getDatabase().getEntryById(entryId).ifPresent(entry -> { - mainTable.setSelected(mainTable.findEntry(entry)); - selectionListener.editSignalled(); - showAndEdit(entry); + public void editEntryAndFocusField(BibEntry entry, String fieldName) { + showAndEdit(entry); + Platform.runLater(() -> { + // Focus field and entry in main table (async to give entry editor time to load) entryEditor.setFocusToField(fieldName); + clearAndSelect(entry); }); } @@ -1274,24 +687,26 @@ public void updateTableFont() { } private void createMainTable() { - bibDatabaseContext.getDatabase().registerListener(tableModel.getListSynchronizer()); - bibDatabaseContext.getDatabase().registerListener(SpecialFieldDatabaseChangeListener.getInstance()); + bibDatabaseContext.getDatabase().registerListener(SpecialFieldDatabaseChangeListener.INSTANCE); - tableFormat = new MainTableFormat(bibDatabaseContext.getDatabase()); - tableFormat.updateTableFormat(); - mainTable = new MainTable(tableFormat, tableModel, frame, this); + mainTable = new MainTable(tableModel, frame, this, bibDatabaseContext, preferences.getTablePreferences(), externalFileTypes, preferences.getKeyBindings()); - selectionListener = new MainTableSelectionListener(this, mainTable); mainTable.updateFont(); - mainTable.addSelectionListener(selectionListener); - mainTable.addMouseListener(selectionListener); - mainTable.addKeyListener(selectionListener); - mainTable.addFocusListener(selectionListener); // 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 -> Platform - .runLater(() -> Globals.stateManager.setSelectedEntries(mainTable.getSelectedEntries()))); - + mainTable.addSelectionListener(listEvent -> Globals.stateManager.setSelectedEntries(mainTable.getSelectedEntries())); + + // Update entry editor and preview according to selected entries + mainTable.addSelectionListener(event -> mainTable.getSelectedEntries() + .stream() + .findFirst() + .ifPresent(entry -> { + preview.setEntry(entry); + entryEditor.setEntry(entry); + })); + + // TODO: Register these actions globally + /* String clearSearch = "clearSearch"; mainTable.getInputMap().put(Globals.getKeyPrefs().getKey(KeyBinding.CLEAR_SEARCH), clearSearch); mainTable.getActionMap().put(clearSearch, new AbstractAction() { @@ -1350,68 +765,24 @@ public void actionPerformed(ActionEvent e) { } } }); - - mainTable.addKeyListener(new KeyAdapter() { - - @Override - public void keyPressed(KeyEvent e) { - final int keyCode = e.getKeyCode(); - - if (e.isControlDown()) { - switch (keyCode) { - case KeyEvent.VK_PAGE_DOWN: - frame.nextTab.actionPerformed(null); - e.consume(); - break; - case KeyEvent.VK_PAGE_UP: - frame.prevTab.actionPerformed(null); - e.consume(); - break; - default: - break; - } - } else if (keyCode == KeyEvent.VK_ENTER) { - e.consume(); - try { - runCommand(Actions.EDIT); - } catch (Throwable ex) { - LOGGER.warn("Could not run action based on key press", ex); - } - } - } - }); + */ } public void setupMainPanel() { - splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); - splitPane.setDividerSize(SPLIT_PANE_DIVIDER_SIZE); + 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) - // check whether a mainTable already existed and a floatSearch was active - boolean floatSearchActive = (mainTable != null) && (this.tableModel.getSearchState() == MainTableDataModel.DisplayOption.FLOAT); - createMainTable(); - splitPane.setTopComponent(mainTable.getPane()); - - // Remove borders - splitPane.setBorder(BorderFactory.createEmptyBorder()); - setBorder(BorderFactory.createEmptyBorder()); - - // If an entry is currently being shown, make sure it stays shown, - // otherwise set the bottom component to null. - if (mode == BasePanelMode.SHOWING_PREVIEW) { - mode = BasePanelMode.SHOWING_NOTHING; - highlightEntry(selectionListener.getPreview().getEntry()); - } else if (mode == BasePanelMode.SHOWING_EDITOR) { - mode = BasePanelMode.SHOWING_NOTHING; - } else { - splitPane.setBottomComponent(null); - } - - setLayout(new BorderLayout()); - removeAll(); - add(splitPane, BorderLayout.CENTER); + ScrollPane pane = mainTable.getPane(); + AnchorPane anchorPane = new AnchorPane(pane); + AnchorPane.setBottomAnchor(pane, 0.0); + AnchorPane.setTopAnchor(pane, 0.0); + AnchorPane.setLeftAnchor(pane, 0.0); + AnchorPane.setRightAnchor(pane, 0.0); + splitPane.getItems().add(anchorPane); + this.getChildren().setAll(splitPane); // Set up name autocompleter for search: instantiateSearchAutoCompleter(); @@ -1419,25 +790,18 @@ public void setupMainPanel() { setupAutoCompletion(); - // restore floating search result - // (needed if preferences have been changed which causes a recreation of the main table) - if (floatSearchActive) { - mainTable.showFloatSearch(); - } - - splitPane.revalidate(); - revalidate(); - repaint(); - - // saves the divider position as soon as it changes - splitPane.addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, event -> saveDividerLocation()); + // Saves the divider position as soon as it changes + // We need to keep a reference to the subscription, otherwise the binding gets garbage collected + dividerPositionSubscription = EasyBind.monadic(Bindings.valueAt(splitPane.getDividers(), 0)) + .flatMap(SplitPane.Divider::positionProperty) + .subscribe((observable, oldValue, newValue) -> saveDividerLocation(newValue)); } /** * Set up auto completion for this database */ private void setupAutoCompletion() { - AutoCompletePreferences autoCompletePreferences = Globals.prefs.getAutoCompletePreferences(); + AutoCompletePreferences autoCompletePreferences = preferences.getAutoCompletePreferences(); if (autoCompletePreferences.shouldAutoComplete()) { suggestionProviders = new SuggestionProviders(autoCompletePreferences, Globals.journalAbbreviationLoader); suggestionProviders.indexDatabase(getDatabase()); @@ -1461,31 +825,11 @@ private void instantiateSearchAutoCompleter() { } } - public void updatePreamble() { - if (preambleEditor != null) { - preambleEditor.updatePreamble(); - } - } - - public void assureStringDialogNotEditing() { - if (stringDialog != null) { - stringDialog.assureNotEditing(); - } - } - - public void updateStringDialog() { - if (stringDialog != null) { - stringDialog.refreshTable(); - } - } - - public void adjustSplitter() { + private void adjustSplitter() { if (mode == BasePanelMode.SHOWING_PREVIEW) { - splitPane.setDividerLocation( - splitPane.getHeight() - Globals.prefs.getPreviewPreferences().getPreviewPanelHeight()); - } else { - splitPane.setDividerLocation( - splitPane.getHeight() - Globals.prefs.getInt(JabRefPreferences.ENTRY_EDITOR_HEIGHT)); + splitPane.setDividerPositions(Globals.prefs.getPreviewPreferences().getPreviewPanelDividerPosition().doubleValue()); + } else if (mode == BasePanelMode.SHOWING_EDITOR) { + splitPane.setDividerPositions(preferences.getEntryEditorDividerPosition()); } } @@ -1501,38 +845,60 @@ public EntryEditor getEntryEditor() { * @param entry The entry to edit. */ public void showAndEdit(BibEntry entry) { - - if (mode == BasePanelMode.SHOWING_EDITOR) { - Globals.prefs.putInt(JabRefPreferences.ENTRY_EDITOR_HEIGHT, splitPane.getHeight() - splitPane.getDividerLocation()); - } - mode = BasePanelMode.SHOWING_EDITOR; - splitPane.setBottomComponent(entryEditorContainer); DefaultTaskExecutor.runInJavaFXThread(() -> { + + showBottomPane(BasePanelMode.SHOWING_EDITOR); + if (entry != getShowing()) { entryEditor.setEntry(entry); - newEntryShowing(entry); + showing = entry; } entryEditor.requestFocus(); }); + } + + private void showBottomPane(BasePanelMode newMode) { + Node pane; + switch (newMode) { + case SHOWING_PREVIEW: + pane = preview; + break; + case SHOWING_EDITOR: + pane = entryEditor; + break; + default: + throw new UnsupportedOperationException("new mode not recognized: " + newMode.name()); + } + if (splitPane.getItems().size() == 2) { + splitPane.getItems().set(1, pane); + } else { + splitPane.getItems().add(1, pane); + } + mode = newMode; adjustSplitter(); } + private void showAndEdit() { + if (!mainTable.getSelectedEntries().isEmpty()) { + showAndEdit(mainTable.getSelectedEntries().get(0)); + } + } + /** * Sets the given preview panel as the bottom component in the split panel. Updates the mode to SHOWING_PREVIEW. * * @param entry The entry to show in the preview. */ - public void showPreview(BibEntry entry) { + private void showPreview(BibEntry entry) { + showBottomPane(BasePanelMode.SHOWING_PREVIEW); + preview.setEntry(entry); - mode = BasePanelMode.SHOWING_PREVIEW; - splitPane.setBottomComponent(previewContainer); - adjustSplitter(); } private void showPreview() { - if (!mainTable.getSelected().isEmpty()) { - showPreview(mainTable.getSelected().get(0)); + if (!mainTable.getSelectedEntries().isEmpty()) { + showPreview(mainTable.getSelectedEntries().get(0)); } } @@ -1546,9 +912,9 @@ public void previousPreviewStyle() { private void cyclePreview(int newPosition) { PreviewPreferences previewPreferences = Globals.prefs.getPreviewPreferences() - .getBuilder() - .withPreviewCyclePosition(newPosition) - .build(); + .getBuilder() + .withPreviewCyclePosition(newPosition) + .build(); Globals.prefs.storePreviewPreferences(previewPreferences); preview.updateLayout(previewPreferences); @@ -1557,44 +923,25 @@ private void cyclePreview(int newPosition) { /** * Removes the bottom component. */ - public void hideBottomComponent() { + public void closeBottomPane() { mode = BasePanelMode.SHOWING_NOTHING; - splitPane.setBottomComponent(null); + splitPane.getItems().removeAll(entryEditor, preview); } /** * This method selects the given entry, and scrolls it into view in the table. If an entryEditor is shown, it is * given focus afterwards. */ - public void highlightEntry(final BibEntry bibEntry) { - highlightEntry(mainTable.findEntry(bibEntry)); - } - - /** - * This method selects the entry on the given position, and scrolls it into view in the table. - * If an entryEditor is shown, it is given focus afterwards. - */ - public void highlightEntry(int pos) { - if ((pos >= 0) && (pos < mainTable.getRowCount())) { - mainTable.setRowSelectionInterval(pos, pos); - mainTable.ensureVisible(pos); - } + public void clearAndSelect(final BibEntry bibEntry) { + mainTable.clearAndSelect(bibEntry); } public void selectPreviousEntry() { - highlightEntry(((mainTable.getSelectedRow() - 1) + mainTable.getRowCount()) % mainTable.getRowCount()); + mainTable.getSelectionModel().clearAndSelect(mainTable.getSelectionModel().getSelectedIndex() - 1); } public void selectNextEntry() { - highlightEntry((mainTable.getSelectedRow() + 1) % mainTable.getRowCount()); - } - - public void selectFirstEntry() { - highlightEntry(0); - } - - public void selectLastEntry() { - highlightEntry(mainTable.getRowCount() - 1); + mainTable.getSelectionModel().clearAndSelect(mainTable.getSelectionModel().getSelectedIndex() + 1); } /** @@ -1604,10 +951,12 @@ public void selectLastEntry() { * @param editor The entry editor to close. */ public void entryEditorClosing(EntryEditor editor) { - // Store divider location for next time: - Globals.prefs.putInt(JabRefPreferences.ENTRY_EDITOR_HEIGHT, - splitPane.getHeight() - splitPane.getDividerLocation()); - selectionListener.entryEditorClosing(editor); + if (Globals.prefs.getPreviewPreferences().isPreviewPanelEnabled()) { + showPreview(editor.getEntry()); + } else { + closeBottomPane(); + } + mainTable.requestFocus(); } /** @@ -1615,8 +964,8 @@ public void entryEditorClosing(EntryEditor editor) { */ public void ensureNotShowingBottomPanel(BibEntry entry) { if (((mode == BasePanelMode.SHOWING_EDITOR) && (entryEditor.getEntry() == entry)) - || ((mode == BasePanelMode.SHOWING_PREVIEW) && (selectionListener.getPreview().getEntry() == entry))) { - hideBottomComponent(); + || ((mode == BasePanelMode.SHOWING_PREVIEW) && (preview.getEntry() == entry))) { + closeBottomPane(); } } @@ -1644,13 +993,7 @@ public void markBaseChanged() { private void markBasedChangedInternal() { // Put an asterisk behind the filename to indicate the database has changed. frame.setWindowTitle(); - frame.updateAllTabTitles(); - // If the status line states that the base has been saved, we - // remove this message, since it is no longer relevant. If a - // different message is shown, we leave it. - if (frame.getStatusLineText().startsWith(Localization.lang("Saved library"))) { - frame.output(" "); - } + DefaultTaskExecutor.runInJavaFXThread(frame::updateAllTabTitles); } public void markNonUndoableBaseChanged() { @@ -1678,14 +1021,6 @@ public BibDatabase getDatabase() { return bibDatabaseContext.getDatabase(); } - public void preambleEditorClosing() { - preambleEditor = null; - } - - public void stringsClosing() { - stringDialog = null; - } - public void changeTypeOfSelectedEntries(String newType) { List bes = mainTable.getSelectedEntries(); changeType(bes, newType); @@ -1698,10 +1033,8 @@ private void changeType(List entries, String newType) { } if (entries.size() > 1) { - int choice = JOptionPane.showConfirmDialog(this, - Localization.lang("Multiple entries selected. Do you want to change the type of all these to '%0'?", newType), - Localization.lang("Change entry type"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); - if (choice == JOptionPane.NO_OPTION) { + boolean proceed = dialogService.showConfirmationDialogAndWait(Localization.lang("Change entry type"), Localization.lang("Multiple entries selected. Do you want to change the type of all these to '%0'?")); + if (!proceed) { return; } } @@ -1723,89 +1056,52 @@ private void changeType(List entries, String newType) { public boolean showDeleteConfirmationDialog(int numberOfEntries) { if (Globals.prefs.getBoolean(JabRefPreferences.CONFIRM_DELETE)) { - String msg; - msg = Localization.lang("Really delete the selected entry?"); String title = Localization.lang("Delete entry"); + String message = Localization.lang("Really delete the selected entry?"); + String okButton = Localization.lang("Delete entry"); + String cancelButton = Localization.lang("Keep entry"); if (numberOfEntries > 1) { - msg = Localization.lang("Really delete the %0 selected entries?", Integer.toString(numberOfEntries)); title = Localization.lang("Delete multiple entries"); + message = Localization.lang("Really delete the %0 selected entries?", Integer.toString(numberOfEntries)); + okButton = Localization.lang("Delete entries"); + cancelButton = Localization.lang("Keep entries"); } - CheckBoxMessage cb = new CheckBoxMessage(msg, Localization.lang("Disable this confirmation dialog"), false); - - int answer = JOptionPane.showConfirmDialog(frame, cb, title, JOptionPane.YES_NO_OPTION, - JOptionPane.QUESTION_MESSAGE); - if (cb.isSelected()) { - Globals.prefs.putBoolean(JabRefPreferences.CONFIRM_DELETE, false); - } - return answer == JOptionPane.YES_OPTION; + return dialogService.showConfirmationDialogWithOptOutAndWait(title, + message, + okButton, + cancelButton, + Localization.lang("Disable this confirmation dialog"), + optOut -> Globals.prefs.putBoolean(JabRefPreferences.CONFIRM_DELETE, !optOut)); } else { return true; } } /** - * If the relevant option is set, autogenerate keys for all entries that are lacking keys. + * Depending on whether a preview or an entry editor is showing, save the current divider location in the correct preference setting. */ - public void autoGenerateKeysBeforeSaving() { - if (Globals.prefs.getBoolean(JabRefPreferences.GENERATE_KEYS_BEFORE_SAVING)) { - NamedCompound ce = new NamedCompound(Localization.lang("Autogenerate BibTeX keys")); - - BibtexKeyGenerator keyGenerator = new BibtexKeyGenerator(bibDatabaseContext, Globals.prefs.getBibtexKeyPatternPreferences()); - for (BibEntry bes : bibDatabaseContext.getDatabase().getEntries()) { - Optional oldKey = bes.getCiteKeyOptional(); - if (StringUtil.isBlank(oldKey)) { - Optional change = keyGenerator.generateAndSetKey(bes); - change.ifPresent(fieldChange -> ce.addEdit(new UndoableKeyChange(fieldChange))); - } - } - - // Store undo information, if any: - if (ce.hasEdits()) { - ce.end(); - getUndoManager().addEdit(ce); - } + private void saveDividerLocation(Number position) { + if (position == null) { + return; } - } - /** - * Depending on whether a preview or an entry editor is showing, save the current divider location in the correct - * preference setting. - */ - public void saveDividerLocation() { if (mode == BasePanelMode.SHOWING_PREVIEW) { - int previewPanelHeight = splitPane.getHeight() - splitPane.getDividerLocation(); PreviewPreferences previewPreferences = Globals.prefs.getPreviewPreferences() - .getBuilder() - .withPreviewPanelHeight(previewPanelHeight) - .build(); + .getBuilder() + .withPreviewPanelDividerPosition(position) + .build(); Globals.prefs.storePreviewPreferences(previewPreferences); } else if (mode == BasePanelMode.SHOWING_EDITOR) { - Globals.prefs.putInt(JabRefPreferences.ENTRY_EDITOR_HEIGHT, - splitPane.getHeight() - splitPane.getDividerLocation()); + preferences.setEntryEditorDividerPosition(position.doubleValue()); } } - // Method pertaining to the ClipboardOwner interface. - @Override - public void lostOwnership(Clipboard clipboard, Transferable contents) { - // Nothing - } - /** * Perform necessary cleanup when this BasePanel is closed. */ public void cleanUp() { changeMonitor.ifPresent(DatabaseChangeMonitor::unregister); - - // Check if there is a FileUpdatePanel for this BasePanel being shown. If so, - // remove it: - if (sidePaneManager.hasComponent(FileUpdatePanel.class)) { - FileUpdatePanel fup = (FileUpdatePanel) sidePaneManager.getComponent(FileUpdatePanel.class); - if (fup.getPanel() == this) { - sidePaneManager.hideComponent(FileUpdatePanel.class); - } - } } /** @@ -1822,10 +1118,6 @@ public BibDatabaseContext getBibDatabaseContext() { return this.bibDatabaseContext; } - public boolean isUpdatedExternally() { - return changeMonitor.map(DatabaseChangeMonitor::hasBeenModifiedExternally).orElse(false); - } - public void markExternalChangesAsResolved() { changeMonitor.ifPresent(DatabaseChangeMonitor::markExternalChangesAsResolved); } @@ -1854,81 +1146,15 @@ private BibEntry getShowing() { return showing; } - /** - * Update the pointer to the currently shown entry in all cases where the user has moved to a new entry, except when - * using Back and Forward commands. Also updates history for Back command, and clears history for Forward command. - * - * @param entry The entry that is now to be shown. - */ - private void newEntryShowing(BibEntry entry) { - - // If this call is the result of a Back or Forward operation, we must take - // care not to make any history changes, since the necessary changes will - // already have been done in the back() or forward() method: - if (backOrForwardInProgress) { - showing = entry; - backOrForwardInProgress = false; - setBackAndForwardEnabledState(); - return; - } - nextEntries.clear(); - if (!Objects.equals(entry, showing)) { - // Add the entry we are leaving to the history: - if (showing != null) { - previousEntries.add(showing); - if (previousEntries.size() > GUIGlobals.MAX_BACK_HISTORY_SIZE) { - previousEntries.remove(0); - } - } - showing = entry; - setBackAndForwardEnabledState(); - } - } - - /** - * Go back (if there is any recorded history) and update the histories for the Back and Forward commands. - */ - private void back() { - if (!previousEntries.isEmpty()) { - BibEntry toShow = previousEntries.get(previousEntries.size() - 1); - previousEntries.remove(previousEntries.size() - 1); - // Add the entry we are going back from to the Forward history: - if (showing != null) { - nextEntries.add(showing); - } - backOrForwardInProgress = true; // to avoid the history getting updated erroneously - highlightEntry(toShow); - } - } - - private void forward() { - if (!nextEntries.isEmpty()) { - BibEntry toShow = nextEntries.get(nextEntries.size() - 1); - nextEntries.remove(nextEntries.size() - 1); - // Add the entry we are going forward from to the Back history: - if (showing != null) { - previousEntries.add(showing); - } - backOrForwardInProgress = true; // to avoid the history getting updated erroneously - highlightEntry(toShow); - } - } - - public void setBackAndForwardEnabledState() { - frame.getBackAction().setEnabled(!previousEntries.isEmpty()); - frame.getForwardAction().setEnabled(!nextEntries.isEmpty()); - } - - private String formatOutputMessage(String start, int count) { - return String.format("%s %d %s.", start, count, - (count > 1 ? Localization.lang("entries") : Localization.lang("entry"))); + public String formatOutputMessage(String start, int count) { + return String.format("%s %d %s.", start, count, (count > 1 ? Localization.lang("entries") : Localization.lang("entry"))); } /** * Set the preview active state for all BasePanel instances. */ private void setPreviewActiveBasePanels(boolean enabled) { - for (int i = 0; i < frame.getTabbedPane().getTabCount(); i++) { + for (int i = 0; i < frame.getTabbedPane().getTabs().size(); i++) { frame.getBasePanelAt(i).setPreviewActive(enabled); } } @@ -1976,15 +1202,23 @@ public FileAnnotationCache getAnnotationCache() { public void resetChangeMonitor() { changeMonitor.ifPresent(DatabaseChangeMonitor::unregister); - changeMonitor = Optional.of(new DatabaseChangeMonitor(bibDatabaseContext, Globals.getFileUpdateMonitor(), this)); + changeMonitor = Optional.of(new DatabaseChangeMonitor(bibDatabaseContext, Globals.getFileUpdateMonitor(), Globals.TASK_EXECUTOR)); } public void updateTimeStamp() { changeMonitor.ifPresent(DatabaseChangeMonitor::markAsSaved); } - public Path getTempFile() { - return changeMonitor.map(DatabaseChangeMonitor::getTempFile).orElse(null); + public void copy() { + mainTable.copy(); + } + + public void paste() { + mainTable.paste(); + } + + public void cut() { + mainTable.cut(); } private static class SearchAndOpenFile { @@ -2006,7 +1240,7 @@ public void searchAndOpen() { } final Set types = ExternalFileTypes.getInstance().getExternalFileTypeSelection(); - final List dirs = basePanel.getBibDatabaseContext().getFileDirectoriesAsPaths(Globals.prefs.getFileDirectoryPreferences()); + final List dirs = basePanel.getBibDatabaseContext().getFileDirectoriesAsPaths(Globals.prefs.getFilePreferences()); final List extensions = types.stream().map(ExternalFileType::getExtension).collect(Collectors.toList()); // Run the search operation: @@ -2041,7 +1275,7 @@ public void listen(EntryAddedEvent addedEntryEvent) { if (Globals.prefs.getBoolean(JabRefPreferences.AUTO_ASSIGN_GROUP)) { final List entries = Collections.singletonList(addedEntryEvent.getBibEntry()); Globals.stateManager.getSelectedGroup(bibDatabaseContext).forEach( - selectedGroup -> selectedGroup.addEntriesToGroup(entries)); + selectedGroup -> selectedGroup.addEntriesToGroup(entries)); } } } @@ -2050,73 +1284,61 @@ private class EntryRemovedListener { @Subscribe public void listen(EntryRemovedEvent entryRemovedEvent) { - // if the entry that is displayed in the current entry editor is removed, close the entry editor - if ((mode == BasePanelMode.SHOWING_EDITOR) && entryEditor.getEntry().equals(entryRemovedEvent.getBibEntry())) { - entryEditor.close(); - } - - BibEntry previewEntry = selectionListener.getPreview().getEntry(); - if ((previewEntry != null) && previewEntry.equals(entryRemovedEvent.getBibEntry())) { - preview.close(); - } + ensureNotShowingBottomPanel(entryRemovedEvent.getBibEntry()); } } /** * Ensures that the search auto completer is up to date when entries are changed AKA Let the auto completer, if any, * harvest words from the entry + * Actual methods for autocomplete indexing must run in javafx thread */ private class SearchAutoCompleteListener { @Subscribe public void listen(EntryAddedEvent addedEntryEvent) { - searchAutoCompleter.indexEntry(addedEntryEvent.getBibEntry()); + DefaultTaskExecutor.runInJavaFXThread(() -> searchAutoCompleter.indexEntry(addedEntryEvent.getBibEntry())); } @Subscribe public void listen(EntryChangedEvent entryChangedEvent) { - searchAutoCompleter.indexEntry(entryChangedEvent.getBibEntry()); + DefaultTaskExecutor.runInJavaFXThread(() -> searchAutoCompleter.indexEntry(entryChangedEvent.getBibEntry())); } } /** * Ensures that the results of the current search are updated when a new entry is inserted into the database + * Actual methods for performing search must run in javafx thread */ private class SearchListener { @Subscribe public void listen(EntryAddedEvent addedEntryEvent) { - frame.getGlobalSearchBar().performSearch(); + DefaultTaskExecutor.runInJavaFXThread(() -> frame.getGlobalSearchBar().performSearch()); } @Subscribe public void listen(EntryChangedEvent entryChangedEvent) { - frame.getGlobalSearchBar().setDontSelectSearchBar(); - frame.getGlobalSearchBar().performSearch(); + DefaultTaskExecutor.runInJavaFXThread(() -> frame.getGlobalSearchBar().performSearch()); } @Subscribe public void listen(EntryRemovedEvent removedEntryEvent) { // IMO only used to update the status (found X entries) - frame.getGlobalSearchBar().performSearch(); + DefaultTaskExecutor.runInJavaFXThread(() -> frame.getGlobalSearchBar().performSearch()); } } + @Subscribe + public void listen(EntryChangedEvent entryChangedEvent) { + this.markBaseChanged(); + } + private class UndoAction implements BaseAction { @Override public void action() { try { - JComponent focused = Globals.getFocusListener().getFocused(); - if ((focused != null) && (focused instanceof FieldEditor) && focused.hasFocus()) { - // User is currently editing a field: - // Check if it is the preamble: - - FieldEditor fieldEditor = (FieldEditor) focused; - if ((preambleEditor != null) && (fieldEditor.equals(preambleEditor.getFieldEditor()))) { - preambleEditor.storeCurrentEdit(); - } - } getUndoManager().undo(); markBaseChanged(); frame.output(Localization.lang("Undo")); @@ -2151,29 +1373,30 @@ public void action() { } else { // No URL or DOI found in the "url" and "doi" fields. // Look for web links in the "file" field as a fallback: - FileListEntry entry = null; - FileListTableModel tm = new FileListTableModel(); - bes.get(0).getField(FieldName.FILE).ifPresent(tm::setContent); - for (int i = 0; i < tm.getRowCount(); i++) { - FileListEntry flEntry = tm.getEntry(i); - if (FieldName.URL.equalsIgnoreCase(flEntry.getType().get().getName()) - || FieldName.PS.equalsIgnoreCase(flEntry.getType().get().getName()) - || FieldName.PDF.equalsIgnoreCase(flEntry.getType().get().getName())) { - entry = flEntry; - break; - } - } - if (entry == null) { - output(Localization.lang("No URL defined") + '.'); - } else { + + List files = bes.get(0).getFiles(); + + Optional linkedFile = files.stream() + .filter(file -> (FieldName.URL.equalsIgnoreCase(file.getFileType()) + || FieldName.PS.equalsIgnoreCase(file.getFileType()) + || FieldName.PDF.equalsIgnoreCase(file.getFileType()))) + .findFirst(); + + if (linkedFile.isPresent()) { + try { - JabRefDesktop.openExternalFileAnyFormat(bibDatabaseContext, entry.getLink(), - entry.getType()); + + JabRefDesktop.openExternalFileAnyFormat(bibDatabaseContext, + linkedFile.get().getLink(), + ExternalFileTypes.getInstance().fromLinkedFile(linkedFile.get(), true)); + output(Localization.lang("External viewer called") + '.'); } catch (IOException e) { output(Localization.lang("Could not open link")); LOGGER.info("Could not open link", e); } + } else { + output(Localization.lang("No URL defined") + '.'); } } } else { @@ -2187,8 +1410,6 @@ private class RedoAction implements BaseAction { @Override public void action() { try { - - JComponent focused = Globals.getFocusListener().getFocused(); getUndoManager().redo(); markBaseChanged(); frame.output(Localization.lang("Redo")); @@ -2203,39 +1424,9 @@ public void action() { private class PrintPreviewAction implements BaseAction { @Override - public void action() throws Exception { + public void action() { showPreview(); preview.print(); } } - - private class SaveSelectedAction implements BaseAction { - - private final SavePreferences.DatabaseSaveType saveType; - - public SaveSelectedAction(SavePreferences.DatabaseSaveType saveType) { - this.saveType = saveType; - } - - @Override - public void action() throws SaveException { - FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .withDefaultExtension(FileType.BIBTEX_DB) - .addExtensionFilter(FileType.BIBTEX_DB) - .withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)) - .build(); - - DialogService ds = new FXDialogService(); - - Optional chosenFile = DefaultTaskExecutor - .runInJavaFXThread(() -> ds.showFileSaveDialog(fileDialogConfiguration)); - - if (chosenFile.isPresent()) { - Path path = chosenFile.get(); - saveDatabase(path.toFile(), true, Globals.prefs.getDefaultEncoding(), saveType); - frame.getFileHistory().newFile(path.toString()); - frame.output(Localization.lang("Saved selected to '%0'.", path.toString())); - } - } - } } diff --git a/src/main/java/org/jabref/gui/BasePanelPreferences.java b/src/main/java/org/jabref/gui/BasePanelPreferences.java new file mode 100644 index 00000000000..56f8e356272 --- /dev/null +++ b/src/main/java/org/jabref/gui/BasePanelPreferences.java @@ -0,0 +1,80 @@ +package org.jabref.gui; + +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; + +import org.jabref.Globals; +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 org.fxmisc.easybind.EasyBind; + +public class BasePanelPreferences { + private MainTablePreferences tablePreferences; + private AutoCompletePreferences autoCompletePreferences; + private EntryEditorPreferences entryEditorPreferences; + private KeyBindingRepository keyBindings; + private PreviewPreferences previewPreferences; + private 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 a79eb860213..c7c6ce2b9d4 100644 --- a/src/main/java/org/jabref/gui/ClipBoardManager.java +++ b/src/main/java/org/jabref/gui/ClipBoardManager.java @@ -1,31 +1,36 @@ package org.jabref.gui; -import java.awt.Toolkit; -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.ClipboardOwner; -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.StringSelection; -import java.awt.datatransfer.Transferable; -import java.awt.datatransfer.UnsupportedFlavorException; import java.io.IOException; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; +import javafx.scene.input.DataFormat; + import org.jabref.Globals; +import org.jabref.logic.bibtex.BibEntryWriter; +import org.jabref.logic.bibtex.LatexFieldFormatter; import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.ImportException; import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.importer.ImportFormatReader.UnknownFormatImport; +import org.jabref.logic.importer.ParseException; import org.jabref.logic.importer.fetcher.DoiFetcher; +import org.jabref.logic.importer.fileformat.BibtexParser; +import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.identifier.DOI; +import org.jabref.model.util.OptionalUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ClipBoardManager implements ClipboardOwner { +public class ClipBoardManager { + public static final DataFormat XML = new DataFormat("application/xml"); + private static final Logger LOGGER = LoggerFactory.getLogger(ClipBoardManager.class); private final Clipboard clipboard; @@ -33,7 +38,7 @@ public class ClipBoardManager implements ClipboardOwner { private final ImportFormatReader importFormatReader; public ClipBoardManager() { - this(Toolkit.getDefaultToolkit().getSystemClipboard(), Globals.IMPORT_FORMAT_READER); + this(Clipboard.getSystemClipboard(), Globals.IMPORT_FORMAT_READER); } public ClipBoardManager(Clipboard clipboard, ImportFormatReader importFormatReader) { @@ -42,18 +47,10 @@ public ClipBoardManager(Clipboard clipboard, ImportFormatReader importFormatRead } /** - * Empty implementation of the ClipboardOwner interface. - */ - @Override - public void lostOwnership(Clipboard aClipboard, Transferable aContents) { - //do nothing - } - - /** - * Places the string into the clipboard using a {@link Transferable}. + * Puts content onto the clipboard. */ - public void setTransferableClipboardContents(Transferable transferable) { - clipboard.setContents(transferable, this); + public void setContent(ClipboardContent content) { + clipboard.setContent(content); } /** @@ -62,71 +59,70 @@ public void setTransferableClipboardContents(Transferable transferable) { * @return any text found on the Clipboard; if none found, return an * empty String. */ - public String getClipboardContents() { - String result = ""; - //odd: the Object param of getContents is not currently used - Transferable contents = clipboard.getContents(null); - if ((contents != null) && contents.isDataFlavorSupported(DataFlavor.stringFlavor)) { - try { - result = (String) contents.getTransferData(DataFlavor.stringFlavor); - } catch (UnsupportedFlavorException | IOException e) { - //highly unlikely since we are using a standard DataFlavor - LOGGER.info("problem with getting clipboard contents", e); - } + public String getContents() { + String result = clipboard.getString(); + if (result == null) { + return ""; + } else { + return result; } - return result; } - /** - * Place a String on the clipboard, and make this class the - * owner of the Clipboard's contents. - */ - public void setClipboardContents(String aString) { - StringSelection stringSelection = new StringSelection(aString); - clipboard.setContents(stringSelection, this); + public void setHtmlContent(String html) { + final ClipboardContent content = new ClipboardContent(); + content.putHtml(html); + clipboard.setContent(content); + } + + public void setContent(String string) { + final ClipboardContent content = new ClipboardContent(); + content.putString(string); + clipboard.setContent(content); } - public List extractBibEntriesFromClipboard() { - // Get clipboard contents, and see if TransferableBibtexEntry is among the content flavors offered - Transferable content = clipboard.getContents(null); - List result = new ArrayList<>(); + public void setContent(List entries) throws IOException { + final ClipboardContent content = new ClipboardContent(); + BibEntryWriter writer = new BibEntryWriter(new LatexFieldFormatter(Globals.prefs.getLatexFieldFormatterPreferences()), false); + String serializedEntries = writer.serializeAll(entries, BibDatabaseMode.BIBTEX); + content.put(DragAndDropDataFormats.ENTRIES, serializedEntries); + content.putString(serializedEntries); + clipboard.setContent(content); + } - if (content.isDataFlavorSupported(TransferableBibtexEntry.ENTRY_FLAVOR)) { - // We have determined that the clipboard data is a set of entries. + public List extractEntries() { + Object entries = clipboard.getContent(DragAndDropDataFormats.ENTRIES); + + BibtexParser parser = new BibtexParser(Globals.prefs.getImportFormatPreferences(), Globals.getFileUpdateMonitor()); + if (entries != null) { + // We have determined that the clipboard data is a set of entries (serialized as a string). try { - @SuppressWarnings("unchecked") - List contents = (List) content.getTransferData(TransferableBibtexEntry.ENTRY_FLAVOR); - result = contents; - } catch (UnsupportedFlavorException | ClassCastException ex) { - LOGGER.warn("Could not paste this type", ex); - } catch (IOException ex) { - LOGGER.warn("Could not paste", ex); + return parser.parseEntries((String) entries); + } catch (ParseException ex) { + LOGGER.error("Could not paste", ex); } - } else if (content.isDataFlavorSupported(DataFlavor.stringFlavor)) { - try { - String data = (String) content.getTransferData(DataFlavor.stringFlavor); - // fetch from doi - if (DOI.parse(data).isPresent()) { - LOGGER.info("Found DOI in clipboard"); - Optional entry = new DoiFetcher(Globals.prefs.getImportFormatPreferences()).performSearchById(new DOI(data).getDOI()); - entry.ifPresent(result::add); - } else { - try { - UnknownFormatImport unknownFormatImport = importFormatReader.importUnknownFormat(data); - result = unknownFormatImport.parserResult.getDatabase().getEntries(); - } catch (ImportException e) { - // import failed and result will be empty + } else { + String data = clipboard.getString(); + if (data != null) { + try { + // fetch from doi + Optional doi = DOI.parse(data); + if (doi.isPresent()) { + LOGGER.info("Found DOI in clipboard"); + Optional entry = new DoiFetcher(Globals.prefs.getImportFormatPreferences()).performSearchById(doi.get().getDOI()); + return OptionalUtil.toList(entry); + } else { + try { + UnknownFormatImport unknownFormatImport = importFormatReader.importUnknownFormat(data); + return unknownFormatImport.parserResult.getDatabase().getEntries(); + } catch (ImportException e) { + // import failed and result will be empty + } } + } catch (FetcherException ex) { + LOGGER.error("Error while fetching", ex); } - } catch (UnsupportedFlavorException ex) { - LOGGER.warn("Could not parse this type", ex); - } catch (IOException ex) { - LOGGER.warn("Data is no longer available in the requested flavor", ex); - } catch (FetcherException ex) { - LOGGER.error("Error while fetching", ex); } - } - return result; + return Collections.emptyList(); } } diff --git a/src/main/java/org/jabref/gui/Dark.css b/src/main/java/org/jabref/gui/Dark.css new file mode 100644 index 00000000000..8df9935c460 --- /dev/null +++ b/src/main/java/org/jabref/gui/Dark.css @@ -0,0 +1,52 @@ +.root { + -jr-theme: #2c9490; + -jr-accent: #255652; + -jr-selected: -jr-accent; + -jr-hover: #fff1; + + + -jr-red: #b71c1f; + -jr-light-red: #db1d2b; + -jr-green: #1cb631; + -jr-light-green: #28d93c; + -jr-blue: #2c2cb7; + -jr-light-blue: #3a3ad9; + -jr-purple: #b72486; + -jr-light-purple: #d927a8; + -jr-yellow: #b5b021; + -jr-orange: #b77620; + + -jr-base: #141824; + + -jr-background-alt: #151924; + -jr-menu-background: #141824; + -jr-toolbar: -jr-menu-background; + -jr-sidepane-background: #212330; + -jr-search-background: #2c2e3b; + + -jr-sidepane-header-background: -jr-background-alt; + -jr-group-hits-bg: -jr-background-alt; + -jr-group-hits-fg: -fx-light-text-color; + -fx-control-inner-background: #272b38; + + -fx-control-inner-background-alt: -fx-control-inner-background; + + -fx-dark-text-color: black; + -fx-mid-text-color: #7d8591; + -fx-light-text-color: #9aa3af; + -jr-separator: #333744; + -fx-outer-border: #424758; + + -jr-icon: -fx-light-text-color; + -jr-icon-active: derive(-fx-light-text-color, 50%); + -jr-icon-background-active: -jr-hover; + -jr-icon-background-armed: #fff2; + + -jr-menu-foreground: -fx-light-text-color; + -jr-menu-item-foreground: -fx-light-text-color; + -jr-menu-forground-active: derive(-fx-light-text-color, 50%); + + -fx-focused-text-base-color: -fx-dark-text-color; + + -jr-tooltip-fg: derive(-fx-light-text-color, 50%); +} diff --git a/src/main/java/org/jabref/gui/DefaultInjector.java b/src/main/java/org/jabref/gui/DefaultInjector.java index ee81dd7072d..ee7c8c05839 100644 --- a/src/main/java/org/jabref/gui/DefaultInjector.java +++ b/src/main/java/org/jabref/gui/DefaultInjector.java @@ -2,10 +2,13 @@ import java.util.function.Function; +import javax.swing.undo.UndoManager; + import org.jabref.Globals; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.journals.JournalAbbreviationLoader; +import org.jabref.logic.protectedterms.ProtectedTermsLoader; import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.PreferencesService; @@ -38,6 +41,12 @@ private static Object createDependency(Class clazz) { return Globals.stateManager; } else if (clazz == FileUpdateMonitor.class) { return Globals.getFileUpdateMonitor(); + } else if (clazz == ProtectedTermsLoader.class) { + return Globals.protectedTermsLoader; + } else if (clazz == ClipBoardManager.class) { + return Globals.clipboardManager; + } else if (clazz == UndoManager.class) { + return Globals.undoManager; } else { try { return clazz.newInstance(); @@ -57,4 +66,14 @@ public T instantiatePresenter(Class clazz, Function injec return Injector.instantiatePresenter(clazz, injectionContext); } + + @Override + public void injectMembers(Object instance, Function injectionContext) { + LOGGER.debug("Inject into " + instance.getClass().getName()); + + // Use our own method to construct dependencies + Injector.setInstanceSupplier(DefaultInjector::createDependency); + + Injector.injectMembers(instance, injectionContext); + } } diff --git a/src/main/java/org/jabref/gui/DialogService.java b/src/main/java/org/jabref/gui/DialogService.java index 1ddbe50a90c..2e105481d1d 100644 --- a/src/main/java/org/jabref/gui/DialogService.java +++ b/src/main/java/org/jabref/gui/DialogService.java @@ -1,14 +1,19 @@ package org.jabref.gui; +import java.io.IOException; import java.nio.file.Path; +import java.util.Collection; import java.util.List; import java.util.Optional; +import java.util.function.Consumer; import javafx.concurrent.Task; import javafx.print.PrinterJob; import javafx.scene.control.Alert; import javafx.scene.control.ButtonType; +import javafx.scene.control.ChoiceDialog; import javafx.scene.control.DialogPane; +import javafx.scene.control.TextInputDialog; import org.jabref.gui.util.DirectoryDialogConfiguration; import org.jabref.gui.util.FileDialogConfiguration; @@ -21,6 +26,23 @@ */ public interface DialogService { + /** + * This will create and display new {@link ChoiceDialog} of type T with a default choice and a collection of possible choices + * + * @implNote The implementation should accept {@code null} for {@code defaultChoice}, but callers should use {@link #showChoiceDialogAndWait(String, String, String, Collection)}. + */ + Optional showChoiceDialogAndWait(String title, String content, String okButtonLabel, T defaultChoice, Collection choices); + + /** + * This will create and display new {@link ChoiceDialog} of type T with a collection of possible choices + */ + default Optional showChoiceDialogAndWait(String title, String content, String okButtonLabel, Collection choices) { + return showChoiceDialogAndWait(title, content, okButtonLabel, null, choices); + } + + /** + * This will create and display new {@link TextInputDialog} with a text fields to enter data + */ Optional showInputDialogAndWait(String title, String content); /** @@ -64,6 +86,13 @@ default void showErrorDialogAndWait(Exception exception) { showErrorDialogAndWait(Localization.lang("Unhandled exception occurred."), exception); } + /** + * Create and display error dialog displaying the given exception. + * + * @param exception the exception causing the error + */ + void showErrorDialogAndWait(String title, String content, Throwable exception); + /** * Create and display error dialog displaying the given message. * @@ -85,7 +114,7 @@ default void showErrorDialogAndWait(Exception exception) { * Create and display a new confirmation dialog. * It will include a blue question icon on the left and * a OK (with given label) and Cancel button. To create a confirmation dialog with custom - * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)} + * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)}. * * @return true if the use clicked "OK" otherwise false */ @@ -95,12 +124,37 @@ default void showErrorDialogAndWait(Exception exception) { * Create and display a new confirmation dialog. * It will include a blue question icon on the left and * a OK (with given label) and Cancel (also with given label) button. To create a confirmation dialog with custom - * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)} + * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)}. * * @return true if the use clicked "OK" otherwise false */ boolean showConfirmationDialogAndWait(String title, String content, String okButtonLabel, String cancelButtonLabel); + /** + * Create and display a new confirmation dialog. + * It will include a blue question icon on the left and + * a YES (with given label) and Cancel (also with given label) button. To create a confirmation dialog with custom + * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)}. + * Moreover, the dialog contains a opt-out checkbox with the given text to support "Do not ask again"-behaviour. + * + * @return true if the use clicked "YES" otherwise false + */ + boolean showConfirmationDialogWithOptOutAndWait(String title, String content, + String optOutMessage, Consumer optOutAction); + + /** + * Create and display a new confirmation dialog. + * It will include a blue question icon on the left and + * a YES (with given label) and Cancel (also with given label) button. To create a confirmation dialog with custom + * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)}. + * Moreover, the dialog contains a opt-out checkbox with the given text to support "Do not ask again"-behaviour. + * + * @return true if the use clicked "YES" otherwise false + */ + boolean showConfirmationDialogWithOptOutAndWait(String title, String content, + String okButtonLabel, String cancelButtonLabel, + String optOutMessage, Consumer optOutAction); + /** * This will create and display a new dialog of the specified * {@link Alert.AlertType} but with user defined buttons as optional @@ -130,12 +184,14 @@ Optional showCustomButtonDialogAndWait(Alert.AlertType type, String /** * Constructs and shows a canceable {@link ProgressDialog}. Clicking cancel will cancel the underlying service and close the dialog * + * @param title title of the dialog + * @param content message to show above the progress bar * @param task The {@link Task} which executes the work and for which to show the dialog */ - void showCanceableProgressDialogAndWait(Task task); + void showProgressDialogAndWait(String title, String content, Task task); /** - * Notify the user in an non-blocking way (i.e., update status message instead of showing a dialog). + * Notify the user in an non-blocking way (i.e., in form of toast in a snackbar). * * @param message the message to show. */ @@ -191,4 +247,13 @@ Optional showCustomButtonDialogAndWait(Alert.AlertType type, String * @return false if the user opts to cancel printing */ boolean showPrintDialog(PrinterJob job); + + /** + * Shows a new dialog that list all files contained in the given archive and which lets the user select one of these + * files. The method doesn't return until the displayed open dialog is dismissed. The return value specifies the + * file chosen by the user or an empty {@link Optional} if no selection has been made. + * + * @return the selected file or an empty {@link Optional} if no file has been selected + */ + Optional showFileOpenFromArchiveDialog(Path archivePath) throws IOException; } diff --git a/src/main/java/org/jabref/gui/DragAndDropDataFormats.java b/src/main/java/org/jabref/gui/DragAndDropDataFormats.java index 9ffe3125c10..8776ecaca3f 100644 --- a/src/main/java/org/jabref/gui/DragAndDropDataFormats.java +++ b/src/main/java/org/jabref/gui/DragAndDropDataFormats.java @@ -1,7 +1,11 @@ package org.jabref.gui; +import java.util.List; + import javafx.scene.input.DataFormat; +import org.jabref.model.entry.BibEntry; + /** * Contains all the different {@link DataFormat}s that may occur in JabRef. */ @@ -9,5 +13,7 @@ public class DragAndDropDataFormats { public static final DataFormat GROUP = new DataFormat("dnd/org.jabref.model.groups.GroupTreeNode"); public static final DataFormat LINKED_FILE = new DataFormat("dnd/org.jabref.model.entry.LinkedFile"); - public static final DataFormat ENTRIES = new DataFormat("application/x-java-jvm-local-objectref"); + public static final DataFormat ENTRIES = new DataFormat("dnd/org.jabref.model.entry.BibEntries"); + @SuppressWarnings("unchecked") public static final Class> BIBENTRY_LIST_CLASS = (Class>) (Class) List.class; + } diff --git a/src/main/java/org/jabref/gui/DragDropPane.java b/src/main/java/org/jabref/gui/DragDropPane.java deleted file mode 100644 index 4382b1cfea5..00000000000 --- a/src/main/java/org/jabref/gui/DragDropPane.java +++ /dev/null @@ -1,159 +0,0 @@ -package org.jabref.gui; - -import java.awt.AlphaComposite; -import java.awt.Component; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionAdapter; - -import javax.swing.JPanel; -import javax.swing.JTabbedPane; -import javax.swing.SwingUtilities; - -/** - * Extends the JTabbedPane class to support Drag&Drop of Tabs. - * - * @author kleinms, strassfn - */ -class DragDropPane extends JTabbedPane { - - private boolean draggingState; // State var if we are at dragging or not - private int indexDraggedTab; // The index of the tab we drag at the moment - private final MarkerPane markerPane; // The glass panel for painting the position marker - - - DragDropPane() { - super(); - indexDraggedTab = -1; - markerPane = new MarkerPane(); - markerPane.setVisible(false); - - // ------------------------------------------- - // Adding listeners for Drag&Drop Actions - // ------------------------------------------- - addMouseMotionListener(new MouseMotionAdapter() { - - @Override - public void mouseDragged(MouseEvent e) { // Mouse is dragging - // Calculates the tab index based on the mouse position - int indexActTab = getUI().tabForCoordinate(DragDropPane.this, - e.getX(), e.getY()); - if (draggingState) { // We are at tab dragging - if ((indexDraggedTab >= 0) && (indexActTab >= 0)) { //Is it a valid scenario? - boolean toTheLeft = e.getX() <= getUI().getTabBounds(DragDropPane.this, indexActTab).getCenterX(); //Go to the left or to the right of the actual Tab - DragDropPane.this.getRootPane().setGlassPane(markerPane); //Set the MarkerPane as glass Pane - Rectangle actTabRect = SwingUtilities.convertRectangle(DragDropPane.this, getBoundsAt(indexActTab), - DragDropPane.this.markerPane); //Rectangle with the same dimensions as the tab at the mouse position - if (toTheLeft) { - markerPane.setPicLocation(new Point(actTabRect.x, actTabRect.y - + actTabRect.height)); //Set pic to the left of the tab at the mouse position - } - else { - markerPane.setPicLocation(new Point(actTabRect.x + actTabRect.width, actTabRect.y - + actTabRect.height)); //Set pic to the right of the tab at the mouse position - } - - markerPane.setVisible(true); - markerPane.repaint(); - repaint(); - } else { //We have no valid tab tragging scenario - markerPane.setVisible(false); - markerPane.repaint(); - } - - } else { //We are not at tab dragging - if (indexActTab >= 0) { // Mouse is above a tab, otherwise tabNumber would be -1 - // -->Now we are at tab tragging - draggingState = true; // Mark now we are at dragging - indexDraggedTab = indexActTab; // Set draggedTabIndex to the tabNumber where we are now - repaint(); - } - } - super.mouseDragged(e); - } - }); - - addMouseListener(new MouseAdapter() { - - @Override - public void mouseReleased(MouseEvent e) { - DragDropPane.this.markerPane.setVisible(false); //Set MarkerPane invisible - int indexActTab = getUI().tabForCoordinate(DragDropPane.this, - e.getX(), e.getY()); - if ((indexDraggedTab >= 0) && (indexActTab >= 0) && (indexDraggedTab != indexActTab)) { //Is it a valid scenario? - if (draggingState) { //We are at tab dragging - boolean toTheLeft = e.getX() <= getUI().getTabBounds(DragDropPane.this, indexActTab).getCenterX(); //Go to the left or to the right of the actual Tab - DragDropPane.this.markerPane.setVisible(false); - - Component actTab = getComponentAt(indexDraggedTab); //Save dragged tab - String actTabTitle = getTitleAt(indexDraggedTab); //Save Title of the dragged tab - removeTabAt(indexDraggedTab); //Remove dragged tab - int newTabPos; - if (indexActTab < indexDraggedTab) { //We are dragging the tab to the left of its the position - if (toTheLeft && (indexActTab < (DragDropPane.this.getTabCount()))) { - newTabPos = indexActTab; - } else { - newTabPos = indexActTab + 1; - } - } else { //We are dragging the tab to the right of the old position - if (toTheLeft && (indexActTab > 0)) { - newTabPos = indexActTab - 1; - } else { - newTabPos = indexActTab; - } - } - insertTab(actTabTitle, null, actTab, null, newTabPos); //Insert dragged tab at new position - DragDropPane.this.setSelectedIndex(newTabPos); //Set selection back to the tab (at the new tab position - } - } - draggingState = false; - } - }); - } - - - /** - * A glass panel which sets the marker for Dragging of Tabs. - * - */ - static class MarkerPane extends JPanel { - - private Point locationP; - private final IconTheme.JabRefIcon moveTabArrow; - - - public MarkerPane() { - setOpaque(false); - - // Sets the marker fontIcon - moveTabArrow = IconTheme.JabRefIcon.MOVE_TAB_ARROW; - } - - @Override - public void paintComponent(Graphics g) { - super.paintComponent(g); - Graphics2D g2 = (Graphics2D) g; - g2.setComposite(AlphaComposite.getInstance( - AlphaComposite.SRC_OVER, 0.9f)); // Set transparency - g.setFont(IconTheme.FONT.deriveFont(Font.BOLD, 24f)); - g.drawString(moveTabArrow.getCode(), locationP.x - (moveTabArrow.getIcon().getIconWidth() / 2), - locationP.y + (moveTabArrow.getIcon().getIconHeight() / 2)); - - } - - /** - * Sets the new location, where the marker should be placed. - * - * @param pt the point for the marker - */ - public void setPicLocation(Point pt) { - this.locationP = pt; - } - - } -} diff --git a/src/main/java/org/jabref/gui/DragDropPopupPane.java b/src/main/java/org/jabref/gui/DragDropPopupPane.java deleted file mode 100644 index 42a49da2cfc..00000000000 --- a/src/main/java/org/jabref/gui/DragDropPopupPane.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.jabref.gui; - -import java.awt.event.MouseEvent; - -import javax.swing.JPopupMenu; - -/** - * Adds popup functionality to DragDropPane - */ -public class DragDropPopupPane extends DragDropPane { - - private final JPopupMenu popupMenu; - - public DragDropPopupPane(JPopupMenu menu) { - this.popupMenu = menu; - - addMouseListener(new java.awt.event.MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - tabRightClick(e); - } - }); - } - - private void tabRightClick(MouseEvent e) { - if ((e.getButton() != MouseEvent.BUTTON1) && (e.getClickCount() == 1)) { - // display popup near location of mouse click - popupMenu.show(e.getComponent(), e.getX(), e.getY() - 10); - } - } -} diff --git a/src/main/java/org/jabref/gui/DuplicateResolverDialog.java b/src/main/java/org/jabref/gui/DuplicateResolverDialog.java index baae8dac19a..858a7c7e7f2 100644 --- a/src/main/java/org/jabref/gui/DuplicateResolverDialog.java +++ b/src/main/java/org/jabref/gui/DuplicateResolverDialog.java @@ -1,23 +1,23 @@ package org.jabref.gui; -import java.awt.BorderLayout; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; - -import javax.swing.Box; -import javax.swing.JButton; -import javax.swing.JPanel; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonBar.ButtonData; +import javafx.scene.control.ButtonType; +import javafx.scene.layout.BorderPane; +import org.jabref.gui.DuplicateResolverDialog.DuplicateResolverResult; import org.jabref.gui.help.HelpAction; -import org.jabref.gui.importer.ImportInspectionDialog; import org.jabref.gui.mergeentries.MergeEntries; -import org.jabref.gui.util.WindowLocation; +import org.jabref.gui.util.BaseDialog; import org.jabref.logic.help.HelpFile; import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; -import org.jabref.preferences.JabRefPreferences; -public class DuplicateResolverDialog extends JabRefDialog { +public class DuplicateResolverDialog extends BaseDialog { + + private final BibDatabaseContext database; public enum DuplicateResolverType { DUPLICATE_SEARCH, @@ -27,7 +27,6 @@ public enum DuplicateResolverType { } public enum DuplicateResolverResult { - NOT_CHOSEN, KEEP_BOTH, KEEP_LEFT, KEEP_RIGHT, @@ -36,108 +35,88 @@ public enum DuplicateResolverResult { BREAK } - JButton helpButton = new HelpAction(Localization.lang("Help"), HelpFile.FIND_DUPLICATES).getHelpButton(); - private final JButton cancel = new JButton(Localization.lang("Cancel")); - private final JButton merge = new JButton(Localization.lang("Keep merged entry only")); - private final JabRefFrame frame; - private final JPanel options = new JPanel(); - private DuplicateResolverResult status = DuplicateResolverResult.NOT_CHOSEN; private MergeEntries me; - public DuplicateResolverDialog(JabRefFrame frame, BibEntry one, BibEntry two, DuplicateResolverType type) { - super(frame, Localization.lang("Possible duplicate entries"), true, DuplicateResolverDialog.class); - this.frame = frame; - init(one, two, type); - } - - public DuplicateResolverDialog(ImportInspectionDialog dialog, BibEntry one, BibEntry two, - DuplicateResolverType type) { - super(dialog, Localization.lang("Possible duplicate entries"), true, DuplicateResolverDialog.class); - this.frame = dialog.getFrame(); + public DuplicateResolverDialog(BibEntry one, BibEntry two, DuplicateResolverType type, BibDatabaseContext database) { + this.setTitle(Localization.lang("Possible duplicate entries")); init(one, two, type); + this.database = database; } private void init(BibEntry one, BibEntry two, DuplicateResolverType type) { - JButton both; - JButton second; - JButton first; - JButton removeExact = null; - switch (type) { - case DUPLICATE_SEARCH: - first = new JButton(Localization.lang("Keep left")); - second = new JButton(Localization.lang("Keep right")); - both = new JButton(Localization.lang("Keep both")); - me = new MergeEntries(one, two, frame.getCurrentBasePanel().getBibDatabaseContext().getMode()); - break; - case INSPECTION: - first = new JButton(Localization.lang("Remove old entry")); - second = new JButton(Localization.lang("Remove entry from import")); - both = new JButton(Localization.lang("Keep both")); - me = new MergeEntries(one, two, Localization.lang("Old entry"), - Localization.lang("From import"), frame.getCurrentBasePanel().getBibDatabaseContext().getMode()); - break; - case DUPLICATE_SEARCH_WITH_EXACT: - first = new JButton(Localization.lang("Keep left")); - second = new JButton(Localization.lang("Keep right")); - both = new JButton(Localization.lang("Keep both")); - removeExact = new JButton(Localization.lang("Automatically remove exact duplicates")); - me = new MergeEntries(one, two, frame.getCurrentBasePanel().getBibDatabaseContext().getMode()); - break; - default: - first = new JButton(Localization.lang("Import and remove old entry")); - second = new JButton(Localization.lang("Do not import entry")); - both = new JButton(Localization.lang("Import and keep old entry")); - me = new MergeEntries(one, two, Localization.lang("Old entry"), - Localization.lang("From import"), frame.getCurrentBasePanel().getBibDatabaseContext().getMode()); - break; - } - if (removeExact != null) { - options.add(removeExact); + HelpAction helpCommand = new HelpAction(HelpFile.FIND_DUPLICATES); + ButtonType help = new ButtonType(Localization.lang("Help"), ButtonData.HELP); + + ButtonType cancel = ButtonType.CANCEL; + ButtonType merge = new ButtonType(Localization.lang("Keep merged entry only"), ButtonData.APPLY); + + ButtonBar options = new ButtonBar(); + ButtonType both; + ButtonType second; + ButtonType first; + ButtonType removeExact = new ButtonType(Localization.lang("Automatically remove exact duplicates"), ButtonData.APPLY); + boolean removeExactVisible = false; + + switch (type) { + case DUPLICATE_SEARCH: + first = new ButtonType(Localization.lang("Keep left"), ButtonData.APPLY); + second = new ButtonType(Localization.lang("Keep right"), ButtonData.APPLY); + both = new ButtonType(Localization.lang("Keep both"), ButtonData.APPLY); + me = new MergeEntries(one, two, database.getMode()); + break; + case INSPECTION: + first = new ButtonType(Localization.lang("Remove old entry"), ButtonData.APPLY); + second = new ButtonType(Localization.lang("Remove entry from import"), ButtonData.APPLY); + both = new ButtonType(Localization.lang("Keep both"), ButtonData.APPLY); + me = new MergeEntries(one, two, Localization.lang("Old entry"), + Localization.lang("From import"), database.getMode()); + break; + case DUPLICATE_SEARCH_WITH_EXACT: + first = new ButtonType(Localization.lang("Keep left"), ButtonData.APPLY); + second = new ButtonType(Localization.lang("Keep right"), ButtonData.APPLY); + both = new ButtonType(Localization.lang("Keep both"), ButtonData.APPLY); + + removeExactVisible = true; + + me = new MergeEntries(one, two, database.getMode()); + break; + default: + first = new ButtonType(Localization.lang("Import and remove old entry"), ButtonData.APPLY); + second = new ButtonType(Localization.lang("Do not import entry"), ButtonData.APPLY); + both = new ButtonType(Localization.lang("Import and keep old entry"), ButtonData.APPLY); + me = new MergeEntries(one, two, Localization.lang("Old entry"), + Localization.lang("From import"), database.getMode()); + break; } - options.add(first); - options.add(second); - options.add(both); - options.add(merge); - options.add(Box.createHorizontalStrut(5)); - options.add(cancel); - options.add(helpButton); - - first.addActionListener(e -> buttonPressed(DuplicateResolverResult.KEEP_LEFT)); - second.addActionListener(e -> buttonPressed(DuplicateResolverResult.KEEP_RIGHT)); - both.addActionListener(e -> buttonPressed(DuplicateResolverResult.KEEP_BOTH)); - merge.addActionListener(e -> buttonPressed(DuplicateResolverResult.KEEP_MERGE)); - if (removeExact != null) { - removeExact.addActionListener(e -> buttonPressed(DuplicateResolverResult.AUTOREMOVE_EXACT)); + if (removeExactVisible) { + this.getDialogPane().getButtonTypes().add(removeExact); } - cancel.addActionListener(e -> buttonPressed(DuplicateResolverResult.BREAK)); - addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - buttonPressed(DuplicateResolverResult.BREAK); - } - }); + this.getDialogPane().getButtonTypes().addAll(first, second, both, merge, cancel, help); - getContentPane().add(me.getMergeEntryPanel()); - getContentPane().add(options, BorderLayout.SOUTH); - pack(); + BorderPane borderPane = new BorderPane(me); + borderPane.setBottom(options); - WindowLocation pw = new WindowLocation(this, JabRefPreferences.DUPLICATES_POS_X, - JabRefPreferences.DUPLICATES_POS_Y, JabRefPreferences.DUPLICATES_SIZE_X, - JabRefPreferences.DUPLICATES_SIZE_Y); - pw.displayWindowAtStoredLocation(); + this.setResultConverter(button -> { - both.requestFocus(); - } - - private void buttonPressed(DuplicateResolverResult result) { - status = result; - dispose(); - } + if (button.equals(first)) { + return DuplicateResolverResult.KEEP_LEFT; + } else if (button.equals(second)) { + return DuplicateResolverResult.KEEP_RIGHT; + } else if (button.equals(both)) { + return DuplicateResolverResult.KEEP_BOTH; + } else if (button.equals(merge)) { + return DuplicateResolverResult.KEEP_MERGE; + } else if (button.equals(removeExact)) { + return DuplicateResolverResult.AUTOREMOVE_EXACT; + } + return null; + }); - public DuplicateResolverResult getSelected() { - return status; + getDialogPane().setContent(borderPane); + Button helpButton = (Button) this.getDialogPane().lookupButton(help); + helpButton.setOnAction(evt -> helpCommand.getCommand().execute()); } public BibEntry getMergedEntry() { diff --git a/src/main/java/org/jabref/gui/DuplicateSearch.java b/src/main/java/org/jabref/gui/DuplicateSearch.java index fd6537ea3c5..0db4c5df1c2 100644 --- a/src/main/java/org/jabref/gui/DuplicateSearch.java +++ b/src/main/java/org/jabref/gui/DuplicateSearch.java @@ -2,219 +2,215 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; import java.util.List; - -import javax.swing.SwingUtilities; - +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.jabref.Globals; import org.jabref.JabRefExecutorService; -import org.jabref.JabRefGUI; import org.jabref.gui.DuplicateResolverDialog.DuplicateResolverResult; import org.jabref.gui.DuplicateResolverDialog.DuplicateResolverType; +import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableInsertEntry; import org.jabref.gui.undo.UndoableRemoveEntry; -import org.jabref.gui.worker.CallBack; +import org.jabref.gui.util.BackgroundTask; +import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.logic.bibtex.DuplicateCheck; import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; -import spin.Spin; - -public class DuplicateSearch implements Runnable { +public class DuplicateSearch extends SimpleCommand { - private final BasePanel panel; - private List bes; - private final List> duplicates = new ArrayList<>(); + private final JabRefFrame frame; + private final BlockingQueue> duplicates = new LinkedBlockingQueue<>(); + private final AtomicBoolean libraryAnalyzed = new AtomicBoolean(); + private final AtomicBoolean autoRemoveExactDuplicates = new AtomicBoolean(); + private final AtomicInteger duplicateCount = new AtomicInteger(); + private final DialogService dialogService; - public DuplicateSearch(BasePanel bp) { - panel = bp; + public DuplicateSearch(JabRefFrame frame, DialogService dialogService) { + this.frame = frame; + this.dialogService = dialogService; } @Override - public void run() { + public void execute() { + BasePanel panel = frame.getCurrentBasePanel(); + dialogService.notify(Localization.lang("Searching for duplicates...")); - panel.output(Localization.lang("Searching for duplicates...")); + List entries = panel.getDatabase().getEntries(); + duplicates.clear(); + libraryAnalyzed.set(false); + autoRemoveExactDuplicates.set(false); + duplicateCount.set(0); - bes = panel.getDatabase().getEntries(); - if (bes.size() < 2) { + if (entries.size() < 2) { return; } - SearcherRunnable st = new SearcherRunnable(); - JabRefExecutorService.INSTANCE.executeInterruptableTask(st, "DuplicateSearcher"); - int current = 0; - - final List toRemove = new ArrayList<>(); - final List toAdd = new ArrayList<>(); - - int duplicateCounter = 0; - boolean autoRemoveExactDuplicates = false; + JabRefExecutorService.INSTANCE.executeInterruptableTask(() -> searchPossibleDuplicates(entries, panel.getBibDatabaseContext().getMode()), "DuplicateSearcher"); + BackgroundTask.wrap(this::verifyDuplicates) + .onSuccess(this::handleDuplicates) + .executeWith(Globals.TASK_EXECUTOR); - synchronized (duplicates) { - while (!st.finished() || (current < duplicates.size())) { + } - if (current >= duplicates.size()) { - // wait until the search thread puts something into duplicates vector - // or finish its work + private void searchPossibleDuplicates(List entries, BibDatabaseMode databaseMode) { + for (int i = 0; (i < (entries.size() - 1)); i++) { + for (int j = i + 1; (j < entries.size()); j++) { + if (Thread.interrupted()) { + return; + } - try { - duplicates.wait(); - } catch (InterruptedException ignored) { - // Ignore - } + BibEntry first = entries.get(i); + BibEntry second = entries.get(j); - } else { // duplicates found - List be = duplicates.get(current); - current++; - if (!toRemove.contains(be.get(0)) && !toRemove.contains(be.get(1))) { - // Check if they are exact duplicates: - boolean askAboutExact = false; - if (DuplicateCheck.compareEntriesStrictly(be.get(0), be.get(1)) > 1) { - if (autoRemoveExactDuplicates) { - toRemove.add(be.get(1)); - duplicateCounter++; - continue; - } - askAboutExact = true; - } - - DuplicateCallBack cb = new DuplicateCallBack(JabRefGUI.getMainFrame(), be.get(0), be.get(1), - askAboutExact ? DuplicateResolverType.DUPLICATE_SEARCH_WITH_EXACT : DuplicateResolverType.DUPLICATE_SEARCH); - ((CallBack) Spin.over(cb)).update(); - - duplicateCounter++; - DuplicateResolverResult answer = cb.getSelected(); - if ((answer == DuplicateResolverResult.KEEP_LEFT) - || (answer == DuplicateResolverResult.AUTOREMOVE_EXACT)) { - toRemove.add(be.get(1)); - if (answer == DuplicateResolverResult.AUTOREMOVE_EXACT) { - autoRemoveExactDuplicates = true; // Remember choice - } - } else if (answer == DuplicateResolverResult.KEEP_RIGHT) { - toRemove.add(be.get(0)); - } else if (answer == DuplicateResolverResult.BREAK) { - st.setFinished(); // thread killing - current = Integer.MAX_VALUE; - duplicateCounter--; // correct counter - } else if (answer == DuplicateResolverResult.KEEP_MERGE) { - toRemove.addAll(be); - toAdd.add(cb.getMergedEntry()); - } - } + if (DuplicateCheck.isDuplicate(first, second, databaseMode)) { + duplicates.add(Arrays.asList(first, second)); + duplicateCount.getAndIncrement(); } } } + libraryAnalyzed.set(true); + } - final NamedCompound ce = new NamedCompound(Localization.lang("duplicate removal")); - - final int dupliC = duplicateCounter; - SwingUtilities.invokeLater(new Runnable() { + private DuplicateSearchResult verifyDuplicates() { + DuplicateSearchResult result = new DuplicateSearchResult(); - @Override - public void run() { - // Now, do the actual removal: - if (!toRemove.isEmpty()) { - for (BibEntry entry : toRemove) { - panel.getDatabase().removeEntry(entry); - ce.addEdit(new UndoableRemoveEntry(panel.getDatabase(), entry, panel)); - } - panel.markBaseChanged(); + while (!libraryAnalyzed.get() || !duplicates.isEmpty()) { + List dups; + try { + // poll with timeout in case the library is not analyzed completely, but contains no more duplicates + dups = this.duplicates.poll(100, TimeUnit.MILLISECONDS); + if (dups == null) { + continue; } - // and adding merged entries: - if (!toAdd.isEmpty()) { - for (BibEntry entry : toAdd) { - panel.getDatabase().insertEntry(entry); - ce.addEdit(new UndoableInsertEntry(panel.getDatabase(), entry, panel)); + } catch (InterruptedException e) { + return null; + } + + BibEntry first = dups.get(0); + BibEntry second = dups.get(1); + + if (!result.isToRemove(first) && !result.isToRemove(second)) { + // Check if they are exact duplicates: + boolean askAboutExact = false; + if (DuplicateCheck.compareEntriesStrictly(first, second) > 1) { + if (autoRemoveExactDuplicates.get()) { + result.remove(second); + continue; } - panel.markBaseChanged(); + askAboutExact = true; } - synchronized (duplicates) { - panel.output(Localization.lang("Duplicates found") + ": " + duplicates.size() + ' ' - + Localization.lang("pairs processed") + ": " + dupliC); - } - ce.end(); - panel.getUndoManager().addEdit(ce); + DuplicateResolverType resolverType = askAboutExact ? DuplicateResolverType.DUPLICATE_SEARCH_WITH_EXACT : DuplicateResolverType.DUPLICATE_SEARCH; + DefaultTaskExecutor.runAndWaitInJavaFXThread(() -> askResolveStrategy(result, first, second, resolverType)); } + } - }); - + return result; } - class SearcherRunnable implements Runnable { - - private volatile boolean finished; + private void askResolveStrategy(DuplicateSearchResult result, BibEntry first, BibEntry second, DuplicateResolverType resolverType) { + DuplicateResolverDialog dialog = new DuplicateResolverDialog(first, second, resolverType, frame.getCurrentBasePanel().getBibDatabaseContext()); - @Override - public void run() { - for (int i = 0; (i < (bes.size() - 1)) && !finished; i++) { - for (int j = i + 1; (j < bes.size()) && !finished; j++) { - BibEntry first = bes.get(i); - BibEntry second = bes.get(j); - boolean eq = DuplicateCheck.isDuplicate(first, second, panel.getBibDatabaseContext().getMode()); + DuplicateResolverResult resolverResult = dialog.showAndWait().orElse(DuplicateResolverResult.BREAK); - // If (suspected) duplicates, add them to the duplicates vector. - if (eq) { - synchronized (duplicates) { - duplicates.add(Arrays.asList(first, second)); - duplicates.notifyAll(); // send wake up all - } - } - } - } - finished = true; - // if no duplicates found, the graphical thread will never wake up - synchronized (duplicates) { - duplicates.notifyAll(); + if ((resolverResult == DuplicateResolverResult.KEEP_LEFT) + || (resolverResult == DuplicateResolverResult.AUTOREMOVE_EXACT)) { + result.remove(second); + if (resolverResult == DuplicateResolverResult.AUTOREMOVE_EXACT) { + autoRemoveExactDuplicates.set(true); // Remember choice } + } else if (resolverResult == DuplicateResolverResult.KEEP_RIGHT) { + result.remove(first); + } else if (resolverResult == DuplicateResolverResult.BREAK) { + libraryAnalyzed.set(true); + duplicates.clear(); + } else if (resolverResult == DuplicateResolverResult.KEEP_MERGE) { + result.replace(first, second, dialog.getMergedEntry()); } + } - public boolean finished() { - return finished; + private void handleDuplicates(DuplicateSearchResult result) { + if (result == null) { + return; } - // Thread cancel option - // no synchronized used because no "really" critical situations expected - public void setFinished() { - finished = true; + BasePanel panel = frame.getCurrentBasePanel(); + final NamedCompound compoundEdit = new NamedCompound(Localization.lang("duplicate removal")); + // Now, do the actual removal: + if (!result.getToRemove().isEmpty()) { + for (BibEntry entry : result.getToRemove()) { + panel.getDatabase().removeEntry(entry); + compoundEdit.addEdit(new UndoableRemoveEntry(panel.getDatabase(), entry, panel)); + } + panel.markBaseChanged(); } + // and adding merged entries: + if (!result.getToAdd().isEmpty()) { + for (BibEntry entry : result.getToAdd()) { + panel.getDatabase().insertEntry(entry); + compoundEdit.addEdit(new UndoableInsertEntry(panel.getDatabase(), entry)); + } + panel.markBaseChanged(); + } + + dialogService.notify(Localization.lang("Duplicates found") + ": " + duplicateCount.get() + ' ' + + Localization.lang("pairs processed") + ": " + result.getDuplicateCount()); + compoundEdit.end(); + panel.getUndoManager().addEdit(compoundEdit); + } - static class DuplicateCallBack implements CallBack { + /** + * Result of a duplicate search. + * Uses {@link System#identityHashCode(Object)} for identifying objects for removal, as completely identical + * {@link BibEntry BibEntries} are equal to each other. + */ + class DuplicateSearchResult { - private DuplicateResolverResult reply = DuplicateResolverResult.NOT_CHOSEN; - private final JabRefFrame frame; - private final BibEntry one; - private final BibEntry two; - private final DuplicateResolverType dialogType; - private BibEntry merged; + private final Map toRemove = new HashMap<>(); + private final List toAdd = new ArrayList<>(); + private int duplicates = 0; - public DuplicateCallBack(JabRefFrame frame, BibEntry one, BibEntry two, DuplicateResolverType dialogType) { - this.frame = frame; - this.one = one; - this.two = two; - this.dialogType = dialogType; + public synchronized Collection getToRemove() { + return toRemove.values(); } - public DuplicateResolverResult getSelected() { - return reply; + public synchronized List getToAdd() { + return toAdd; } - public BibEntry getMergedEntry() { - return merged; + public synchronized void remove(BibEntry entry) { + toRemove.put(System.identityHashCode(entry), entry); + duplicates++; } - @Override - public void update() { - DuplicateResolverDialog diag = new DuplicateResolverDialog(frame, one, two, dialogType); - diag.setVisible(true); - diag.dispose(); - reply = diag.getSelected(); - merged = diag.getMergedEntry(); + public synchronized void replace(BibEntry first, BibEntry second, BibEntry replacement) { + remove(first); + remove(second); + toAdd.add(replacement); + duplicates++; } - } + public synchronized boolean isToRemove(BibEntry entry) { + return toRemove.containsKey(System.identityHashCode(entry)); + } + + public synchronized int getDuplicateCount() { + return duplicates; + } + } } diff --git a/src/main/java/org/jabref/gui/EntryMarker.java b/src/main/java/org/jabref/gui/EntryMarker.java deleted file mode 100644 index 8237304aced..00000000000 --- a/src/main/java/org/jabref/gui/EntryMarker.java +++ /dev/null @@ -1,199 +0,0 @@ -package org.jabref.gui; - -import java.util.Set; -import java.util.TreeSet; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.jabref.Globals; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableFieldChange; -import org.jabref.model.database.BibDatabase; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.FieldName; -import org.jabref.preferences.JabRefPreferences; - -public class EntryMarker { - - public static final int MARK_COLOR_LEVELS = 6; - public static final int MAX_MARKING_LEVEL = MARK_COLOR_LEVELS - 1; - public static final int IMPORT_MARK_LEVEL = MARK_COLOR_LEVELS; - - private static final Pattern MARK_NUMBER_PATTERN = Pattern.compile(JabRefPreferences.getInstance().MARKING_WITH_NUMBER_PATTERN); - - private EntryMarker() { - } - - /** - * @param increment whether the given increment should be added to the current one. Currently never used in JabRef. Could be used to increase marking color ("Mark in specific color"). - */ - public static void markEntry(BibEntry be, int markIncrement, boolean increment, NamedCompound ce) { - int prevMarkLevel; - String newValue = null; - if (be.hasField(FieldName.MARKED_INTERNAL)) { - String markerString = be.getField(FieldName.MARKED_INTERNAL).get(); - int index = markerString.indexOf(Globals.prefs.getWrappedUsername()); - if (index >= 0) { - // Already marked 1 for this user. - prevMarkLevel = 1; - newValue = markerString.substring(0, index) - + markerString.substring(index + Globals.prefs.getWrappedUsername().length()) - + Globals.prefs.getWrappedUsername().substring(0, - Globals.prefs.getWrappedUsername().length() - 1) - + ":" + (increment ? Math.min(MAX_MARKING_LEVEL, prevMarkLevel + markIncrement) : markIncrement) - + "]"; - } else { - Matcher m = MARK_NUMBER_PATTERN.matcher(markerString); - if (m.find()) { - try { - prevMarkLevel = Integer.parseInt(m.group(1)); - newValue = markerString.substring(0, m.start(1)) + (increment ? Math.min(MAX_MARKING_LEVEL, prevMarkLevel + markIncrement) : markIncrement) + markerString.substring(m.end(1)); - } catch (NumberFormatException ex) { - // Do nothing. - } - } - } - } - if (newValue == null) { - newValue = Globals.prefs.getWrappedUsername().substring(0, Globals.prefs.getWrappedUsername().length() - 1) + ":" + markIncrement + "]"; - } - - ce.addEdit(new UndoableFieldChange(be, FieldName.MARKED_INTERNAL, - be.getField(FieldName.MARKED_INTERNAL).orElse(null), newValue)); - be.setField(FieldName.MARKED_INTERNAL, newValue); - } - - /** - * SIDE EFFECT: Unselects given entry - */ - public static void unmarkEntry(BibEntry be, boolean onlyMaxLevel, BibDatabase database, NamedCompound ce) { - if (be.hasField(FieldName.MARKED_INTERNAL)) { - String markerString = be.getField(FieldName.MARKED_INTERNAL).get(); - if ("0".equals(markerString)) { - if (!onlyMaxLevel) { - unmarkOldStyle(be, database, ce); - } - return; - } - String newValue = null; - int index = markerString.indexOf(Globals.prefs.getWrappedUsername()); - if (index >= 0) { - // Marked 1 for this user. - if (onlyMaxLevel) { - return; - } else { - newValue = markerString.substring(0, index) - + markerString.substring(index + Globals.prefs.getWrappedUsername().length()); - } - } else { - Matcher m = MARK_NUMBER_PATTERN.matcher(markerString); - if (m.find()) { - try { - int prevMarkLevel = Integer.parseInt(m.group(1)); - if (!onlyMaxLevel || (prevMarkLevel == MARK_COLOR_LEVELS)) { - if (prevMarkLevel > 1) { - newValue = markerString.substring(0, m.start(1)) + markerString.substring(m.end(1)); - } else { - String toRemove = Globals.prefs.getWrappedUsername().substring(0, - Globals.prefs.getWrappedUsername().length() - 1) + ":1]"; - index = markerString.indexOf(toRemove); - if (index >= 0) { - newValue = markerString.substring(0, index) + markerString.substring(index + toRemove.length()); - } - } - } else { - return; - } - } catch (NumberFormatException ex) { - // Do nothing. - } - } - } - - /*int piv = 0, hit; - StringBuffer sb = new StringBuffer(); - while ((hit = s.indexOf(G047749118118 - 1110lobals.prefs.WRAPPED_USERNAME, piv)) >= 0) { - if (hit > 0) - sb.append(s.substring(piv, hit)); - piv = hit + Globals.prefs.WRAPPED_USERNAME.length(); - } - if (piv < s.length() - 1) { - sb.append(s.substring(piv)); - } - String newVal = sb.length() > 0 ? sb.toString() : null;*/ - ce.addEdit(new UndoableFieldChange(be, FieldName.MARKED_INTERNAL, - be.getField(FieldName.MARKED_INTERNAL).get(), newValue)); - if (newValue == null) { - be.clearField(FieldName.MARKED_INTERNAL); - } else { - be.setField(FieldName.MARKED_INTERNAL, newValue); - } - } - } - - /** - * An entry is marked with a "0", not in the new style with user names. We - * want to unmark it as transparently as possible. Since this shouldn't - * happen too often, we do it by scanning the "owner" fields of the entire - * database, collecting all user names. We then mark the entry for all users - * except the current one. Thus only the user who unmarks will see that it - * is unmarked, and we get rid of the old-style marking. - * - * @param be - * @param ce - */ - private static void unmarkOldStyle(BibEntry be, BibDatabase database, NamedCompound ce) { - Set owners = new TreeSet<>(); - for (BibEntry entry : database.getEntries()) { - entry.getField(FieldName.OWNER).ifPresent(owners::add); - } - owners.remove(Globals.prefs.get(JabRefPreferences.DEFAULT_OWNER)); - StringBuilder sb = new StringBuilder(); - for (Object owner : owners) { - sb.append('['); - sb.append(owner); - sb.append(']'); - } - String newVal = sb.toString(); - if (newVal.isEmpty()) { - ce.addEdit(new UndoableFieldChange(be, FieldName.MARKED_INTERNAL, - be.getField(FieldName.MARKED_INTERNAL).orElse(null), null)); - be.clearField(FieldName.MARKED_INTERNAL); - } else { - ce.addEdit(new UndoableFieldChange(be, FieldName.MARKED_INTERNAL, - be.getField(FieldName.MARKED_INTERNAL).orElse(null), newVal)); - be.setField(FieldName.MARKED_INTERNAL, newVal); - } - } - - public static int isMarked(BibEntry be) { - if (!be.hasField(FieldName.MARKED_INTERNAL)) { - return 0; - } - String s = be.getField(FieldName.MARKED_INTERNAL).get(); - if ("0".equals(s)) { - return 1; - } - int index = s.indexOf(Globals.prefs.getWrappedUsername()); - if (index >= 0) { - return 1; - } - - Matcher m = MARK_NUMBER_PATTERN.matcher(s); - if (m.find()) { - try { - return Integer.parseInt(m.group(1)); - } catch (NumberFormatException ex) { - return 1; - } - } else { - return 0; - } - - } - - public static boolean shouldMarkEntries() { - return Globals.prefs.getBoolean(JabRefPreferences.MARK_IMPORTED_ENTRIES); - } -} diff --git a/src/main/java/org/jabref/gui/EntryType.fxml b/src/main/java/org/jabref/gui/EntryType.fxml new file mode 100644 index 00000000000..0ed5f37b502 --- /dev/null +++ b/src/main/java/org/jabref/gui/EntryType.fxml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/jabref/gui/EntryTypeDialog.java b/src/main/java/org/jabref/gui/EntryTypeDialog.java deleted file mode 100644 index 84e2a78d4d1..00000000000 --- a/src/main/java/org/jabref/gui/EntryTypeDialog.java +++ /dev/null @@ -1,355 +0,0 @@ -package org.jabref.gui; - -import java.awt.BorderLayout; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutionException; - -import javax.swing.AbstractAction; -import javax.swing.BorderFactory; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JTextField; -import javax.swing.SwingUtilities; -import javax.swing.SwingWorker; - -import org.jabref.Globals; -import org.jabref.gui.importer.ImportInspectionDialog; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.logic.bibtex.DuplicateCheck; -import org.jabref.logic.bibtexkeypattern.BibtexKeyGenerator; -import org.jabref.logic.importer.FetcherException; -import org.jabref.logic.importer.IdBasedFetcher; -import org.jabref.logic.importer.WebFetchers; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.EntryTypes; -import org.jabref.model.database.BibDatabaseMode; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.BiblatexEntryTypes; -import org.jabref.model.entry.BibtexEntryTypes; -import org.jabref.model.entry.EntryType; -import org.jabref.model.entry.IEEETranEntryTypes; -import org.jabref.preferences.JabRefPreferences; - -import com.jgoodies.forms.builder.ButtonBarBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Dialog that prompts the user to choose a type for an entry. - * Returns null if canceled. - */ -public class EntryTypeDialog extends JabRefDialog implements ActionListener { - - private static final Logger LOGGER = LoggerFactory.getLogger(EntryTypeDialog.class); - private static final int COLUMN = 3; - private final JabRefFrame frame; - private final CancelAction cancelAction = new CancelAction(); - private EntryType type; - private SwingWorker, Void> fetcherWorker = new FetcherWorker(); - private JButton generateButton; - private JTextField idTextField; - private JComboBox comboBox; - - public EntryTypeDialog(JabRefFrame frame) { - // modal dialog - super(frame, true, EntryTypeDialog.class); - - this.frame = frame; - - setTitle(Localization.lang("Select entry type")); - - addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - cancelAction.actionPerformed(null); - } - }); - - getContentPane().setLayout(new BorderLayout()); - getContentPane().add(createCancelButtonBarPanel(), BorderLayout.SOUTH); - getContentPane().add(createEntryGroupsPanel(), BorderLayout.CENTER); - - pack(); - setResizable(false); - } - - private JPanel createEntryGroupsPanel() { - JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); - - if (frame.getCurrentBasePanel().getBibDatabaseContext().isBiblatexMode()) { - panel.add(createEntryGroupPanel("biblatex", BiblatexEntryTypes.ALL)); - - List customTypes = EntryTypes.getAllCustomTypes(BibDatabaseMode.BIBLATEX); - if (!customTypes.isEmpty()) { - panel.add(createEntryGroupPanel(Localization.lang("Custom"), customTypes)); - } - } else { - panel.add(createEntryGroupPanel("BibTeX", BibtexEntryTypes.ALL)); - panel.add(createEntryGroupPanel("IEEETran", IEEETranEntryTypes.ALL)); - - List customTypes = EntryTypes.getAllCustomTypes(BibDatabaseMode.BIBTEX); - if (!customTypes.isEmpty()) { - panel.add(createEntryGroupPanel(Localization.lang("Custom"), customTypes)); - } - } - panel.add(createIdFetcherPanel()); - - return panel; - } - - private JPanel createCancelButtonBarPanel() { - JButton cancel = new JButton(Localization.lang("Cancel")); - cancel.addActionListener(this); - - // Make ESC close dialog, equivalent to clicking Cancel. - cancel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DIALOG), "close"); - cancel.getActionMap().put("close", cancelAction); - - JPanel buttons = new JPanel(); - ButtonBarBuilder bb = new ButtonBarBuilder(buttons); - bb.addGlue(); - bb.addButton(cancel); - bb.addGlue(); - return buttons; - } - - private JPanel createEntryGroupPanel(String groupTitle, Collection entries) { - JPanel panel = new JPanel(); - GridBagLayout bagLayout = new GridBagLayout(); - panel.setLayout(bagLayout); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.anchor = GridBagConstraints.WEST; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.insets = new Insets(4, 4, 4, 4); - // column count - int col = 0; - - for (EntryType entryType : entries) { - TypeButton entryButton = new TypeButton(entryType.getName(), entryType); - entryButton.addActionListener(this); - // Check if we should finish the row. - col++; - if (col == EntryTypeDialog.COLUMN) { - col = 0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - } else { - constraints.gridwidth = 1; - } - bagLayout.setConstraints(entryButton, constraints); - panel.add(entryButton); - } - panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), groupTitle)); - - return panel; - } - - private JPanel createIdFetcherPanel() { - JLabel fetcherLabel = new JLabel(Localization.lang("ID type")); - JLabel idLabel = new JLabel(Localization.lang("ID")); - generateButton = new JButton(Localization.lang("Generate")); - idTextField = new JTextField(""); - comboBox = new JComboBox<>(); - - WebFetchers.getIdBasedFetchers(Globals.prefs.getImportFormatPreferences()).forEach(fetcher -> comboBox.addItem(fetcher.getName())); - - comboBox.setSelectedItem(Globals.prefs.get(JabRefPreferences.ID_ENTRY_GENERATOR)); - - generateButton.addActionListener(action -> { - fetcherWorker.execute(); - }); - - comboBox.addActionListener(e -> { - idTextField.requestFocus(); - idTextField.selectAll(); - }); - - idTextField.addActionListener(event -> fetcherWorker.execute()); - - JPanel jPanel = new JPanel(); - - GridBagConstraints constraints = new GridBagConstraints(); - constraints.insets = new Insets(4, 4, 4, 4); - - GridBagLayout layout = new GridBagLayout(); - jPanel.setLayout(layout); - - constraints.fill = GridBagConstraints.HORIZONTAL; - - constraints.gridx = 0; - constraints.gridy = 0; - constraints.weightx = 1; - jPanel.add(fetcherLabel, constraints); - - constraints.gridx = 1; - constraints.gridy = 0; - constraints.weightx = 2; - jPanel.add(comboBox, constraints); - - constraints.gridx = 0; - constraints.gridy = 1; - constraints.weightx = 1; - jPanel.add(idLabel, constraints); - - constraints.gridx = 1; - constraints.gridy = 1; - constraints.weightx = 2; - jPanel.add(idTextField, constraints); - - constraints.gridy = 2; - constraints.gridx = 0; - constraints.gridwidth = 2; - constraints.fill = GridBagConstraints.NONE; - jPanel.add(generateButton, constraints); - - jPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), Localization.lang("ID-based entry generator"))); - - SwingUtilities.invokeLater(() -> idTextField.requestFocus()); - - return jPanel; - } - - private void stopFetching() { - if (fetcherWorker.getState() == SwingWorker.StateValue.STARTED) { - fetcherWorker.cancel(true); - } - } - - @Override - public void actionPerformed(ActionEvent e) { - if (e.getSource() instanceof TypeButton) { - type = ((TypeButton) e.getSource()).getType(); - } - stopFetching(); - dispose(); - } - - public EntryType getChoice() { - return type; - } - - static class TypeButton extends JButton implements Comparable { - - private final EntryType type; - - - TypeButton(String label, EntryType type) { - super(label); - this.type = type; - } - - @Override - public int compareTo(TypeButton o) { - return type.getName().compareTo(o.type.getName()); - } - - public EntryType getType() { - return type; - } - } - - class CancelAction extends AbstractAction { - public CancelAction() { - super("Cancel"); - } - - @Override - public void actionPerformed(ActionEvent e) { - stopFetching(); - dispose(); - } - } - - private class FetcherWorker extends SwingWorker, Void> { - private boolean fetcherException = false; - private String fetcherExceptionMessage = ""; - private IdBasedFetcher fetcher = null; - private String searchID = ""; - - @Override - protected Optional doInBackground() throws Exception { - Optional bibEntry = Optional.empty(); - SwingUtilities.invokeLater(() -> { - generateButton.setEnabled(false); - generateButton.setText(Localization.lang("Searching...")); - }); - - Globals.prefs.put(JabRefPreferences.ID_ENTRY_GENERATOR,String.valueOf(comboBox.getSelectedItem())); - searchID = idTextField.getText().trim(); - searchID = searchID.replaceAll(" ", ""); - fetcher = WebFetchers.getIdBasedFetchers(Globals.prefs.getImportFormatPreferences()).get(comboBox.getSelectedIndex()); - if (!searchID.isEmpty()) { - try { - bibEntry = fetcher.performSearchById(searchID); - } catch (FetcherException e) { - LOGGER.error(e.getMessage(), e); - fetcherException = true; - fetcherExceptionMessage = e.getMessage(); - } - } - return bibEntry; - } - - @Override - protected void done() { - try { - Optional result = get(); - if (result.isPresent()) { - final BibEntry bibEntry = result.get(); - if ((DuplicateCheck.containsDuplicate(frame.getCurrentBasePanel().getDatabase(), bibEntry, frame.getCurrentBasePanel().getBibDatabaseContext().getMode()).isPresent())) { - //If there are duplicates starts ImportInspectionDialog - final BasePanel panel = (BasePanel) frame.getTabbedPane().getSelectedComponent(); - - ImportInspectionDialog diag = new ImportInspectionDialog(frame, panel, Localization.lang("Import"), false); - diag.addEntry(bibEntry); - diag.entryListComplete(); - diag.setLocationRelativeTo(frame); - diag.setVisible(true); - diag.toFront(); - } else { - // Regenerate CiteKey of imported BibEntry - new BibtexKeyGenerator(frame.getCurrentBasePanel().getBibDatabaseContext(), Globals.prefs.getBibtexKeyPatternPreferences()).generateAndSetKey(bibEntry); - // Update Timestamps - if (Globals.prefs.getTimestampPreferences().includeCreatedTimestamp()) { - bibEntry.setField(Globals.prefs.getTimestampPreferences().getTimestampField(), Globals.prefs.getTimestampPreferences().now()); - } - frame.getCurrentBasePanel().insertEntry(bibEntry); - } - - dispose(); - } else if (searchID.trim().isEmpty()) { - JOptionPane.showMessageDialog(frame, Localization.lang("The given search ID was empty."), Localization.lang("Empty search ID"), JOptionPane.WARNING_MESSAGE); - } else if (!fetcherException) { - JOptionPane.showMessageDialog(frame, Localization.lang("Fetcher '%0' did not find an entry for id '%1'.", fetcher.getName(), searchID) + "\n" + fetcherExceptionMessage, Localization.lang("No files found."), JOptionPane.WARNING_MESSAGE); - } else { - JOptionPane.showMessageDialog(frame, - Localization.lang("Error while fetching from %0", fetcher.getName()) + "." + "\n" + fetcherExceptionMessage, - Localization.lang("Error"), JOptionPane.ERROR_MESSAGE); - } - fetcherWorker = new FetcherWorker(); - SwingUtilities.invokeLater(() -> { - idTextField.requestFocus(); - idTextField.selectAll(); - generateButton.setText(Localization.lang("Generate")); - generateButton.setEnabled(true); - }); - } catch (ExecutionException | InterruptedException e) { - LOGGER.error(String.format("Exception during fetching when using fetcher '%s' with entry id '%s'.", searchID, fetcher.getName()), e); - } - } - } -} diff --git a/src/main/java/org/jabref/gui/EntryTypeView.java b/src/main/java/org/jabref/gui/EntryTypeView.java new file mode 100644 index 00000000000..326c1aa855a --- /dev/null +++ b/src/main/java/org/jabref/gui/EntryTypeView.java @@ -0,0 +1,170 @@ +package org.jabref.gui; + +import java.util.Collection; +import java.util.List; + +import javafx.application.Platform; +import javafx.event.Event; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.ComboBox; +import javafx.scene.control.TextField; +import javafx.scene.control.TitledPane; +import javafx.scene.layout.FlowPane; + +import org.jabref.gui.util.BaseDialog; +import org.jabref.gui.util.ControlHelper; +import org.jabref.gui.util.ViewModelListCellFactory; +import org.jabref.logic.importer.IdBasedFetcher; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.EntryTypes; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BiblatexEntryTypes; +import org.jabref.model.entry.BibtexEntryTypes; +import org.jabref.model.entry.EntryType; +import org.jabref.model.entry.IEEETranEntryTypes; +import org.jabref.preferences.JabRefPreferences; + +import com.airhacks.afterburner.views.ViewLoader; +import org.fxmisc.easybind.EasyBind; + +/** + * Dialog that prompts the user to choose a type for an entry. + * Returns null if canceled. + */ +public class EntryTypeView extends BaseDialog { + + @FXML private ButtonType generateButton; + @FXML private TextField idTextField; + @FXML private ComboBox idBasedFetchers; + @FXML private FlowPane biblatexPane; + @FXML private FlowPane bibTexPane; + @FXML private FlowPane ieeetranPane; + @FXML private FlowPane customPane; + @FXML private TitledPane biblatexTitlePane; + @FXML private TitledPane bibTexTitlePane; + @FXML private TitledPane ieeeTranTitlePane; + @FXML private TitledPane customTitlePane; + + private final BasePanel basePanel; + private final DialogService dialogService; + private final JabRefPreferences prefs; + + private EntryType type; + private EntryTypeViewModel viewModel; + + public EntryTypeView(BasePanel basePanel, DialogService dialogService, JabRefPreferences preferences) { + this.basePanel = basePanel; + this.dialogService = dialogService; + this.prefs = preferences; + + this.setTitle(Localization.lang("Select entry type")); + ViewLoader.view(this) + .load() + .setAsDialogPane(this); + + ControlHelper.setAction(generateButton, this.getDialogPane(), event -> viewModel.runFetcherWorker()); + + setResultConverter(button -> { + //The buttonType will always be cancel, even if we pressed one of the entry type buttons + return type; + }); + + Button btnGenerate = (Button) this.getDialogPane().lookupButton(generateButton); + + btnGenerate.textProperty().bind(EasyBind.map(viewModel.searchingProperty(), searching -> (searching) ? Localization.lang("Searching...") : Localization.lang("Generate"))); + btnGenerate.disableProperty().bind(viewModel.searchingProperty()); + + EasyBind.subscribe(viewModel.searchSuccesfulProperty(), value -> { + if (value) { + setEntryTypeForReturnAndClose(null); + } + }); + + } + + private void addEntriesToPane(FlowPane pane, Collection entries) { + + for (EntryType entryType : entries) { + Button entryButton = new Button(entryType.getName()); + entryButton.setUserData(entryType); + entryButton.setOnAction(event -> setEntryTypeForReturnAndClose(entryType)); + pane.getChildren().add(entryButton); + } + } + + @FXML + public void initialize() { + viewModel = new EntryTypeViewModel(prefs, basePanel, dialogService); + + idBasedFetchers.itemsProperty().bind(viewModel.fetcherItemsProperty()); + idTextField.textProperty().bindBidirectional(viewModel.idTextProperty()); + idBasedFetchers.valueProperty().bindBidirectional(viewModel.selectedItemProperty()); + + EasyBind.subscribe(viewModel.getFocusAndSelectAllProperty(), evt -> { + if (evt) { + idTextField.requestFocus(); + idTextField.selectAll(); + } + }); + + new ViewModelListCellFactory().withText(item -> item.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 + bibTexTitlePane.managedProperty().bind(bibTexTitlePane.visibleProperty()); + ieeeTranTitlePane.managedProperty().bind(ieeeTranTitlePane.visibleProperty()); + biblatexTitlePane.managedProperty().bind(biblatexTitlePane.visibleProperty()); + customTitlePane.managedProperty().bind(customTitlePane.visibleProperty()); + + if (basePanel.getBibDatabaseContext().isBiblatexMode()) { + addEntriesToPane(biblatexPane, BiblatexEntryTypes.ALL); + + bibTexTitlePane.setVisible(false); + ieeeTranTitlePane.setVisible(false); + + List customTypes = EntryTypes.getAllCustomTypes(BibDatabaseMode.BIBLATEX); + if (customTypes.isEmpty()) { + customTitlePane.setVisible(false); + } else { + addEntriesToPane(customPane, customTypes); + } + + } else { + biblatexTitlePane.setVisible(false); + addEntriesToPane(bibTexPane, BibtexEntryTypes.ALL); + addEntriesToPane(ieeetranPane, IEEETranEntryTypes.ALL); + + List customTypes = EntryTypes.getAllCustomTypes(BibDatabaseMode.BIBTEX); + if (customTypes.isEmpty()) { + customTitlePane.setVisible(false); + } else { + addEntriesToPane(customPane, customTypes); + } + } + + Platform.runLater(() -> idTextField.requestFocus()); + } + + public EntryType getChoice() { + return type; + } + + @FXML + private void runFetcherWorker(Event event) { + viewModel.runFetcherWorker(); + } + + @FXML + private void focusTextField(Event event) { + idTextField.requestFocus(); + idTextField.selectAll(); + } + + private void setEntryTypeForReturnAndClose(EntryType entryType) { + type = entryType; + viewModel.stopFetching(); + this.close(); + } +} diff --git a/src/main/java/org/jabref/gui/EntryTypeViewModel.java b/src/main/java/org/jabref/gui/EntryTypeViewModel.java new file mode 100644 index 00000000000..766d449f6c4 --- /dev/null +++ b/src/main/java/org/jabref/gui/EntryTypeViewModel.java @@ -0,0 +1,176 @@ +package org.jabref.gui; + +import java.util.Optional; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ListProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; +import javafx.concurrent.Task; +import javafx.concurrent.Worker; + +import org.jabref.logic.bibtex.DuplicateCheck; +import org.jabref.logic.bibtexkeypattern.BibtexKeyGenerator; +import org.jabref.logic.importer.FetcherException; +import org.jabref.logic.importer.IdBasedFetcher; +import org.jabref.logic.importer.WebFetchers; +import org.jabref.logic.importer.fetcher.DoiFetcher; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.strings.StringUtil; +import org.jabref.preferences.JabRefPreferences; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EntryTypeViewModel { + + private static final Logger LOGGER = LoggerFactory.getLogger(EntryTypeViewModel.class); + + private final JabRefPreferences prefs; + private final BooleanProperty searchingProperty = new SimpleBooleanProperty(); + private final BooleanProperty searchSuccesfulProperty = new SimpleBooleanProperty(); + private final ObjectProperty selectedItemProperty = new SimpleObjectProperty<>(); + private final ListProperty fetchers = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final StringProperty idText = new SimpleStringProperty(); + private final BooleanProperty focusAndSelectAllProperty = new SimpleBooleanProperty(); + private Task> fetcherWorker = new FetcherWorker(); + private final BasePanel basePanel; + private final DialogService dialogService; + + public EntryTypeViewModel(JabRefPreferences preferences, BasePanel basePanel, DialogService dialogService) { + this.basePanel = basePanel; + this.prefs = preferences; + this.dialogService = dialogService; + fetchers.addAll(WebFetchers.getIdBasedFetchers(preferences.getImportFormatPreferences())); + selectedItemProperty.setValue(getLastSelectedFetcher()); + + } + + public BooleanProperty searchSuccesfulProperty() { + return searchSuccesfulProperty; + } + + public BooleanProperty searchingProperty() { + return searchingProperty; + } + + public ObjectProperty selectedItemProperty() { + return selectedItemProperty; + } + + public StringProperty idTextProperty() { + return idText; + } + + public BooleanProperty getFocusAndSelectAllProperty() { + return focusAndSelectAllProperty; + } + + public void storeSelectedFetcher() { + prefs.setIdBasedFetcherForEntryGenerator(selectedItemProperty.getValue().getName()); + } + + private IdBasedFetcher getLastSelectedFetcher() { + return fetchers.stream().filter(fetcher -> fetcher.getName().equals(prefs.getIdBasedFetcherForEntryGenerator())) + .findFirst().orElse(new DoiFetcher(prefs.getImportFormatPreferences())); + } + + public ListProperty fetcherItemsProperty() { + return fetchers; + } + + public void stopFetching() { + if (fetcherWorker.getState() == Worker.State.RUNNING) { + fetcherWorker.cancel(true); + } + } + + private class FetcherWorker extends Task> { + + private IdBasedFetcher fetcher = null; + private String searchID = ""; + + @Override + protected Optional call() throws InterruptedException, FetcherException { + Optional bibEntry = Optional.empty(); + + searchingProperty().setValue(true); + storeSelectedFetcher(); + fetcher = selectedItemProperty().getValue(); + searchID = idText.getValue(); + if (!searchID.isEmpty()) { + bibEntry = fetcher.performSearchById(searchID); + } + return bibEntry; + } + + } + + public void runFetcherWorker() { + searchSuccesfulProperty.set(false); + fetcherWorker.run(); + fetcherWorker.setOnFailed(event -> { + Throwable exception = fetcherWorker.getException(); + String fetcherExceptionMessage = exception.getMessage(); + String fetcher = selectedItemProperty().getValue().getName(); + String searchId = idText.getValue(); + if (exception instanceof FetcherException) { + dialogService.showErrorDialogAndWait(Localization.lang("Error"), Localization.lang("Error while fetching from %0", fetcher + "." + "\n" + fetcherExceptionMessage)); + } else { + dialogService.showErrorDialogAndWait(Localization.lang("No files found.", Localization.lang("Fetcher '%0' did not find an entry for id '%1'.", fetcher, searchId) + "\n" + fetcherExceptionMessage)); + } + LOGGER.error(String.format("Exception during fetching when using fetcher '%s' with entry id '%s'.", searchId, fetcher), exception); + + searchingProperty.set(false); + + fetcherWorker = new FetcherWorker(); + + }); + + fetcherWorker.setOnSucceeded(evt -> { + Optional result = fetcherWorker.getValue(); + if (result.isPresent()) { + final BibEntry entry = result.get(); + Optional duplicate = DuplicateCheck.containsDuplicate(basePanel.getDatabase(), entry, basePanel.getBibDatabaseContext().getMode()); + if ((duplicate.isPresent())) { + DuplicateResolverDialog dialog = new DuplicateResolverDialog(entry, duplicate.get(), DuplicateResolverDialog.DuplicateResolverType.IMPORT_CHECK, basePanel.getBibDatabaseContext()); + switch (dialog.showAndWait().orElse(DuplicateResolverDialog.DuplicateResolverResult.BREAK)) { + case KEEP_LEFT: + basePanel.getDatabase().removeEntry(duplicate.get()); + basePanel.getDatabase().insertEntry(entry); + break; + case KEEP_BOTH: + basePanel.getDatabase().insertEntry(entry); + break; + case KEEP_MERGE: + basePanel.getDatabase().removeEntry(duplicate.get()); + basePanel.getDatabase().insertEntry(dialog.getMergedEntry()); + break; + default: + // Do nothing + break; + } + } else { + // Regenerate CiteKey of imported BibEntry + new BibtexKeyGenerator(basePanel.getBibDatabaseContext(), prefs.getBibtexKeyPatternPreferences()).generateAndSetKey(entry); + basePanel.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.")); + } + fetcherWorker = new FetcherWorker(); + + focusAndSelectAllProperty.set(true); + searchingProperty().setValue(false); + + }); + } +} diff --git a/src/main/java/org/jabref/gui/FXDialog.java b/src/main/java/org/jabref/gui/FXDialog.java index d59ccaecda4..e577568267a 100644 --- a/src/main/java/org/jabref/gui/FXDialog.java +++ b/src/main/java/org/jabref/gui/FXDialog.java @@ -1,10 +1,5 @@ package org.jabref.gui; -import java.awt.Window; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; - -import javafx.application.Platform; import javafx.fxml.FXMLLoader; import javafx.scene.control.Alert; import javafx.scene.control.Dialog; @@ -14,17 +9,13 @@ import javafx.stage.Stage; import org.jabref.Globals; -import org.jabref.JabRefGUI; +import org.jabref.gui.icon.IconTheme; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.keyboard.KeyBindingRepository; /** * This class provides a super class for all dialogs implemented in JavaFX. - * It mimics the behavior of a Swing JDialog which means once a object of this class - * is shown all Swing windows will be blocked and stay in the background. Since this - * class extends from a JavaFX {@link Alert} it behaves as a normal dialog towards all - * windows in the JavaFX thread. - *

+ * * To create a custom JavaFX dialog one should create an instance of this class and set a dialog * pane through the inherited {@link Dialog#setDialogPane(DialogPane)} method. * The dialog can be shown via {@link Dialog#show()} or {@link Dialog#showAndWait()}. @@ -34,33 +25,6 @@ */ public class FXDialog extends Alert { - /** - * The WindowAdapter will be added to all Swing windows once an instance - * of this class is shown and redirects the focus towards this instance. - * The WindowAdapter will be removed once the instance of this class gets hidden. - * - */ - private final WindowAdapter fxOverSwingHelper = new WindowAdapter() { - - @Override - public void windowActivated(WindowEvent e) { - Platform.runLater(() -> { - Stage fxDialogWindow = getDialogWindow(); - fxDialogWindow.toFront(); - fxDialogWindow.requestFocus(); - }); - } - - @Override - public void windowGainedFocus(WindowEvent e) { - Platform.runLater(() -> { - Stage fxDialogWindow = getDialogWindow(); - fxDialogWindow.toFront(); - fxDialogWindow.requestFocus(); - }); - } - }; - public FXDialog(AlertType type, String title, Image image, boolean isModal) { this(type, title, isModal); setDialogIcon(image); @@ -93,17 +57,10 @@ public FXDialog(AlertType type, boolean isModal) { } else { initModality(Modality.NONE); } - dialogWindow.setOnShown(evt -> { - setSwingWindowsEnabledAndFocusable(!isModal); - setLocationRelativeToMainWindow(); - }); - dialogWindow.setOnHiding(evt -> setSwingWindowsEnabledAndFocusable(true)); - - dialogWindow.setOnCloseRequest(evt -> this.close()); dialogWindow.getScene().setOnKeyPressed(event -> { KeyBindingRepository keyBindingRepository = Globals.getKeyPrefs(); - if (keyBindingRepository.checkKeyCombinationEquality(KeyBinding.CLOSE_DIALOG, event)) { + if (keyBindingRepository.checkKeyCombinationEquality(KeyBinding.CLOSE, event)) { dialogWindow.close(); } }); @@ -113,7 +70,7 @@ public FXDialog(AlertType type) { this(type, true); } - public void setDialogIcon(Image image) { + private void setDialogIcon(Image image) { Stage fxDialogWindow = getDialogWindow(); fxDialogWindow.getIcons().add(image); } @@ -122,25 +79,4 @@ private Stage getDialogWindow() { return (Stage) getDialogPane().getScene().getWindow(); } - private void setSwingWindowsEnabledAndFocusable(boolean enabled) { - for (Window swingWindow : Window.getWindows()) { - swingWindow.setEnabled(enabled); - if (!enabled) { - swingWindow.addWindowListener(fxOverSwingHelper); - } else { - swingWindow.removeWindowListener(fxOverSwingHelper); - } - } - } - - private void setLocationRelativeToMainWindow() { - double mainWindowX = JabRefGUI.getMainFrame().getLocationOnScreen().getX(); - double mainWindowY = JabRefGUI.getMainFrame().getLocationOnScreen().getY(); - double mainWindowWidth = JabRefGUI.getMainFrame().getSize().getWidth(); - double mainWindowHeight = JabRefGUI.getMainFrame().getSize().getHeight(); - - setX((mainWindowX + (mainWindowWidth / 2)) - (getWidth() / 2)); - setY((mainWindowY + (mainWindowHeight / 2)) - (getHeight() / 2)); - } - } diff --git a/src/main/java/org/jabref/gui/FXDialogService.java b/src/main/java/org/jabref/gui/FXDialogService.java index a42523f17bf..f3edaba275a 100644 --- a/src/main/java/org/jabref/gui/FXDialogService.java +++ b/src/main/java/org/jabref/gui/FXDialogService.java @@ -1,27 +1,40 @@ package org.jabref.gui; import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.Path; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.function.Consumer; import java.util.stream.Collectors; import javafx.concurrent.Task; import javafx.print.PrinterJob; +import javafx.scene.Group; +import javafx.scene.Node; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ChoiceDialog; import javafx.scene.control.DialogPane; import javafx.scene.control.TextInputDialog; import javafx.scene.layout.Region; import javafx.stage.DirectoryChooser; import javafx.stage.FileChooser; +import javafx.stage.Stage; +import javafx.stage.Window; import org.jabref.JabRefGUI; +import org.jabref.gui.icon.IconTheme; import org.jabref.gui.util.DirectoryDialogConfiguration; import org.jabref.gui.util.FileDialogConfiguration; +import org.jabref.gui.util.ZipFileChooser; import org.jabref.logic.l10n.Localization; import org.controlsfx.dialog.ExceptionDialog; @@ -38,6 +51,20 @@ */ public class FXDialogService implements DialogService { + private final Window mainWindow; + + /** + * @deprecated try not to initialize a new dialog service but reuse the one constructed in {@link org.jabref.gui.JabRefFrame}. + */ + @Deprecated + public FXDialogService() { + this(null); + } + + public FXDialogService(Window mainWindow) { + this.mainWindow = mainWindow; + } + private static FXDialog createDialog(AlertType type, String title, String content) { FXDialog alert = new FXDialog(type, title, true); alert.setHeaderText(null); @@ -46,6 +73,51 @@ private static FXDialog createDialog(AlertType type, String title, String conten return alert; } + private static FXDialog createDialogWithOptOut(AlertType type, String title, String content, + String optOutMessage, Consumer optOutAction) { + FXDialog alert = new FXDialog(type, title, true); + // Need to force the alert to layout in order to grab the graphic as we are replacing the dialog pane with a custom pane + alert.getDialogPane().applyCss(); + Node graphic = alert.getDialogPane().getGraphic(); + + // Create a new dialog pane that has a checkbox instead of the hide/show details button + // Use the supplied callback for the action of the checkbox + alert.setDialogPane(new DialogPane() { + + @Override + protected Node createDetailsButton() { + CheckBox optOut = new CheckBox(); + optOut.setText(optOutMessage); + optOut.setOnAction(e -> optOutAction.accept(optOut.isSelected())); + return optOut; + } + }); + + // Fool the dialog into thinking there is some expandable content; a group won't take up any space if it has no children + alert.getDialogPane().setExpandableContent(new Group()); + alert.getDialogPane().setExpanded(true); + + // Reset the dialog graphic using the default style + alert.getDialogPane().setGraphic(graphic); + + alert.setHeaderText(null); + alert.setContentText(content); + alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + return alert; + } + + @Override + public Optional showChoiceDialogAndWait(String title, String content, String okButtonLabel, T defaultChoice, Collection choices) { + ChoiceDialog choiceDialog = new ChoiceDialog<>(defaultChoice, choices); + ButtonType okButtonType = new ButtonType(okButtonLabel, ButtonBar.ButtonData.OK_DONE); + choiceDialog.getDialogPane().getButtonTypes().setAll(ButtonType.CANCEL, okButtonType); + choiceDialog.setHeaderText(title); + choiceDialog.setTitle(title); + choiceDialog.setContentText(content); + return choiceDialog.showAndWait(); + + } + @Override public Optional showInputDialogAndWait(String title, String content) { TextInputDialog inputDialog = new TextInputDialog(); @@ -79,6 +151,14 @@ public void showErrorDialogAndWait(String message, Throwable exception) { exceptionDialog.showAndWait(); } + @Override + public void showErrorDialogAndWait(String title, String content, Throwable exception) { + ExceptionDialog exceptionDialog = new ExceptionDialog(exception); + exceptionDialog.setHeaderText(title); + exceptionDialog.setContentText(content); + exceptionDialog.showAndWait(); + } + @Override public void showErrorDialogAndWait(String message) { FXDialog alert = createDialog(AlertType.ERROR, Localization.lang("Error Occurred"), message); @@ -100,8 +180,8 @@ public boolean showConfirmationDialogAndWait(String title, String content, Strin } @Override - public boolean showConfirmationDialogAndWait(String title, String content, String okButtonLabel, - String cancelButtonLabel) { + public boolean showConfirmationDialogAndWait(String title, String content, + String okButtonLabel, String cancelButtonLabel) { FXDialog alert = createDialog(AlertType.CONFIRMATION, title, content); ButtonType okButtonType = new ButtonType(okButtonLabel, ButtonBar.ButtonData.OK_DONE); ButtonType cancelButtonType = new ButtonType(cancelButtonLabel, ButtonBar.ButtonData.NO); @@ -109,9 +189,28 @@ public boolean showConfirmationDialogAndWait(String title, String content, Strin return alert.showAndWait().filter(buttonType -> buttonType == okButtonType).isPresent(); } + @Override + public boolean showConfirmationDialogWithOptOutAndWait(String title, String content, + String optOutMessage, Consumer optOutAction) { + FXDialog alert = createDialogWithOptOut(AlertType.CONFIRMATION, title, content, optOutMessage, optOutAction); + alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO); + return alert.showAndWait().filter(buttonType -> buttonType == ButtonType.YES).isPresent(); + } + + @Override + public boolean showConfirmationDialogWithOptOutAndWait(String title, String content, + String okButtonLabel, String cancelButtonLabel, + String optOutMessage, Consumer optOutAction) { + FXDialog alert = createDialogWithOptOut(AlertType.CONFIRMATION, title, content, optOutMessage, optOutAction); + ButtonType okButtonType = new ButtonType(okButtonLabel, ButtonBar.ButtonData.YES); + ButtonType cancelButtonType = new ButtonType(cancelButtonLabel, ButtonBar.ButtonData.NO); + alert.getButtonTypes().setAll(okButtonType, cancelButtonType); + return alert.showAndWait().filter(buttonType -> buttonType == okButtonType).isPresent(); + } + @Override public Optional showCustomButtonDialogAndWait(AlertType type, String title, String content, - ButtonType... buttonTypes) { + ButtonType... buttonTypes) { FXDialog alert = createDialog(type, title, content); alert.getButtonTypes().setAll(buttonTypes); return alert.showAndWait(); @@ -119,10 +218,12 @@ public Optional showCustomButtonDialogAndWait(AlertType type, String @Override public Optional showCustomDialogAndWait(String title, DialogPane contentPane, - ButtonType... buttonTypes) { + ButtonType... buttonTypes) { FXDialog alert = new FXDialog(AlertType.NONE, title); alert.setDialogPane(contentPane); alert.getButtonTypes().setAll(buttonTypes); + alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + alert.setResizable(true); return alert.showAndWait(); } @@ -132,8 +233,13 @@ public Optional showCustomDialogAndWait(Dialog dialog) { } @Override - public void showCanceableProgressDialogAndWait(Task task) { + public void showProgressDialogAndWait(String title, String content, Task task) { ProgressDialog progressDialog = new ProgressDialog(task); + progressDialog.setHeaderText(null); + progressDialog.setTitle(title); + progressDialog.setContentText(content); + progressDialog.setGraphic(null); + ((Stage) progressDialog.getDialogPane().getScene().getWindow()).getIcons().add(IconTheme.getJabRefImageFX()); progressDialog.setOnCloseRequest(evt -> task.cancel()); DialogPane dialogPane = progressDialog.getDialogPane(); dialogPane.getButtonTypes().add(ButtonType.CANCEL); @@ -153,7 +259,7 @@ public void notify(String message) { @Override public Optional showFileSaveDialog(FileDialogConfiguration fileDialogConfiguration) { FileChooser chooser = getConfiguredFileChooser(fileDialogConfiguration); - File file = chooser.showSaveDialog(null); + File file = chooser.showSaveDialog(mainWindow); Optional.ofNullable(chooser.getSelectedExtensionFilter()).ifPresent(fileDialogConfiguration::setSelectedExtensionFilter); return Optional.ofNullable(file).map(File::toPath); } @@ -161,7 +267,7 @@ public Optional showFileSaveDialog(FileDialogConfiguration fileDialogConfi @Override public Optional showFileOpenDialog(FileDialogConfiguration fileDialogConfiguration) { FileChooser chooser = getConfiguredFileChooser(fileDialogConfiguration); - File file = chooser.showOpenDialog(null); + File file = chooser.showOpenDialog(mainWindow); Optional.ofNullable(chooser.getSelectedExtensionFilter()).ifPresent(fileDialogConfiguration::setSelectedExtensionFilter); return Optional.ofNullable(file).map(File::toPath); } @@ -169,14 +275,14 @@ public Optional showFileOpenDialog(FileDialogConfiguration fileDialogConfi @Override public Optional showDirectorySelectionDialog(DirectoryDialogConfiguration directoryDialogConfiguration) { DirectoryChooser chooser = getConfiguredDirectoryChooser(directoryDialogConfiguration); - File file = chooser.showDialog(null); + File file = chooser.showDialog(mainWindow); return Optional.ofNullable(file).map(File::toPath); } @Override public List showFileOpenDialogAndGetMultipleFiles(FileDialogConfiguration fileDialogConfiguration) { FileChooser chooser = getConfiguredFileChooser(fileDialogConfiguration); - List files = chooser.showOpenMultipleDialog(null); + List files = chooser.showOpenMultipleDialog(mainWindow); return files != null ? files.stream().map(File::toPath).collect(Collectors.toList()) : Collections.emptyList(); } @@ -197,6 +303,15 @@ private FileChooser getConfiguredFileChooser(FileDialogConfiguration fileDialogC @Override public boolean showPrintDialog(PrinterJob job) { - return job.showPrintDialog(null); + return job.showPrintDialog(mainWindow); + } + + @Override + public Optional showFileOpenFromArchiveDialog(Path archivePath) throws IOException { + try (FileSystem zipFile = FileSystems.newFileSystem(archivePath, null)) { + return new ZipFileChooser(zipFile).showAndWait(); + } catch (NoClassDefFoundError exc) { + throw new IOException("Could not instantiate ZIP-archive reader.", exc); + } } } diff --git a/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java b/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java deleted file mode 100644 index cdab1ab29c8..00000000000 --- a/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java +++ /dev/null @@ -1,1154 +0,0 @@ -package org.jabref.gui; - -import java.awt.Component; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.Frame; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.awt.event.ComponentListener; -import java.awt.event.KeyEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.swing.AbstractAction; -import javax.swing.Action; -import javax.swing.BorderFactory; -import javax.swing.DefaultListCellRenderer; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JProgressBar; -import javax.swing.JRootPane; -import javax.swing.JScrollPane; -import javax.swing.JTextField; -import javax.swing.JTree; -import javax.swing.KeyStroke; -import javax.swing.SwingConstants; -import javax.swing.WindowConstants; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.filechooser.FileSystemView; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeCellRenderer; -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreeModel; -import javax.swing.tree.TreeNode; -import javax.swing.tree.TreePath; - -import org.jabref.Globals; -import org.jabref.JabRefExecutorService; -import org.jabref.JabRefGUI; -import org.jabref.gui.desktop.JabRefDesktop; -import org.jabref.gui.externalfiletype.ExternalFileTypes; -import org.jabref.gui.importer.EntryFromFileCreator; -import org.jabref.gui.importer.EntryFromFileCreatorManager; -import org.jabref.gui.importer.UnlinkedFilesCrawler; -import org.jabref.gui.importer.UnlinkedPDFFileFilter; -import org.jabref.gui.util.DefaultTaskExecutor; -import org.jabref.gui.util.DirectoryDialogConfiguration; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.EntryTypes; -import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibtexEntryType; -import org.jabref.model.entry.EntryType; -import org.jabref.model.entry.FieldName; -import org.jabref.preferences.JabRefPreferences; - -import com.jgoodies.forms.builder.ButtonBarBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * GUI Dialog for the feature "Find unlinked files". - */ -public class FindUnlinkedFilesDialog extends JabRefDialog { - - /** - * Keys to be used for referencing this Action. - */ - public static final String ACTION_COMMAND = "findUnlinkedFiles"; - public static final String ACTION_MENU_TITLE = Localization.menuTitle("Find unlinked files..."); - - public static final String ACTION_SHORT_DESCRIPTION = Localization - .lang("Searches for unlinked PDF files on the file system"); - - private static final Logger LOGGER = LoggerFactory.getLogger(FindUnlinkedFilesDialog.class); - private static final String GLOBAL_PREFS_WORKING_DIRECTORY_KEY = "findUnlinkedFilesWD"; - - private static final String GLOBAL_PREFS_DIALOG_SIZE_KEY = "findUnlinkedFilesDialogSize"; - private final JabRefFrame frame; - private final BibDatabaseContext databaseContext; - private final EntryFromFileCreatorManager creatorManager; - - private final UnlinkedFilesCrawler crawler; - private Path lastSelectedDirectory; - - private TreeModel treeModel; - /* PANELS */ - private JPanel panelDirectory; - private JPanel panelSearchArea; - private JPanel panelFiles; - private JPanel panelOptions; - private JPanel panelButtons; - private JPanel panelEntryTypesSelection; - - private JPanel panelImportArea; - private JButton buttonBrowse; - private JButton buttonScan; - private JButton buttonApply; - - private JButton buttonClose; - /* Options for the TreeView */ - private JButton buttonOptionSelectAll; - private JButton buttonOptionDeselectAll; - private JButton buttonOptionExpandAll; - private JButton buttonOptionCollapseAll; - - private JCheckBox checkboxCreateKeywords; - private JTextField textfieldDirectoryPath; - private JLabel labelDirectoryDescription; - private JLabel labelFileTypesDescription; - private JLabel labelFilesDescription; - private JLabel labelEntryTypeDescription; - private JLabel labelSearchingDirectoryInfo; - - private JLabel labelImportingInfo; - private JTree tree; - private JScrollPane scrollpaneTree; - private JComboBox comboBoxFileTypeSelection; - - private JComboBox comboBoxEntryTypeSelection; - private JProgressBar progressBarSearching; - private JProgressBar progressBarImporting; - - private MouseListener treeMouseListener; - private Action actionSelectAll; - private Action actionUnselectAll; - private Action actionExpandTree; - - private Action actionCollapseTree; - - private ComponentListener dialogPositionListener; - private final AtomicBoolean threadState = new AtomicBoolean(); - - private boolean checkBoxWhyIsThereNoGetSelectedStupidSwing; - - public FindUnlinkedFilesDialog(Frame owner, JabRefFrame frame, BasePanel panel) { - super(owner, Localization.lang("Find unlinked files"), true, FindUnlinkedFilesDialog.class); - this.frame = frame; - - restoreSizeOfDialog(); - - databaseContext = panel.getBibDatabaseContext(); - creatorManager = new EntryFromFileCreatorManager(ExternalFileTypes.getInstance()); - crawler = new UnlinkedFilesCrawler(databaseContext); - - lastSelectedDirectory = loadLastSelectedDirectory(); - - initialize(); - buttonApply.setEnabled(false); - } - - /** - * Close dialog when pressing escape - */ - @Override - protected JRootPane createRootPane() { - ActionListener actionListener = actionEvent -> setVisible(false); - JRootPane rPane = new JRootPane(); - KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); - rPane.registerKeyboardAction(actionListener, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW); - - return rPane; - } - - /** - * Stores the current size of this dialog persistently. - */ - private void storeSizeOfDialog() { - Dimension dim = getSize(); - String store = dim.width + ";" + dim.height; - Globals.prefs.put(FindUnlinkedFilesDialog.GLOBAL_PREFS_DIALOG_SIZE_KEY, store); - } - - /** - * Restores the location and size of this dialog from the persistent storage. - */ - private void restoreSizeOfDialog() { - - String store = Globals.prefs.get(FindUnlinkedFilesDialog.GLOBAL_PREFS_DIALOG_SIZE_KEY); - - Dimension dimension = null; - - if (store != null) { - try { - String[] dim = store.split(";"); - dimension = new Dimension(Integer.valueOf(dim[0]), Integer.valueOf(dim[1])); - } catch (NumberFormatException ignoredEx) { - LOGGER.debug("RestoreSizeDialog Exception ", ignoredEx); - } - } - if (dimension != null) { - setPreferredSize(dimension); - } - } - - /** - * Initializes the components, the layout, the data structure and the - * actions in this dialog. - */ - private void initialize() { - - initializeActions(); - initComponents(); - createTree(); - createFileTypesCombobox(); - createEntryTypesCombobox(); - initLayout(); - setupActions(); - pack(); - } - - /** - * Initializes action objects.
- * Does not assign actions to components yet! - */ - private void initializeActions() { - - actionSelectAll = new AbstractAction(Localization.lang("Select all")) { - - @Override - public void actionPerformed(ActionEvent e) { - CheckableTreeNode rootNode = (CheckableTreeNode) tree.getModel().getRoot(); - rootNode.setSelected(true); - tree.invalidate(); - tree.repaint(); - } - }; - - actionUnselectAll = new AbstractAction(Localization.lang("Unselect all")) { - - @Override - public void actionPerformed(ActionEvent e) { - CheckableTreeNode rootNode = (CheckableTreeNode) tree.getModel().getRoot(); - rootNode.setSelected(false); - tree.invalidate(); - tree.repaint(); - } - }; - - actionExpandTree = new AbstractAction(Localization.lang("Expand all")) { - - @Override - public void actionPerformed(ActionEvent e) { - CheckableTreeNode rootNode = (CheckableTreeNode) tree.getModel().getRoot(); - expandTree(tree, new TreePath(rootNode), true); - } - }; - - actionCollapseTree = new AbstractAction(Localization.lang("Collapse all")) { - - @Override - public void actionPerformed(ActionEvent e) { - CheckableTreeNode rootNode = (CheckableTreeNode) tree.getModel().getRoot(); - expandTree(tree, new TreePath(rootNode), false); - } - }; - - dialogPositionListener = new ComponentAdapter() { - - /* (non-Javadoc) - * @see java.awt.event.ComponentAdapter#componentResized(java.awt.event.ComponentEvent) - */ - @Override - public void componentResized(ComponentEvent e) { - storeSizeOfDialog(); - } - - /* (non-Javadoc) - * @see java.awt.event.ComponentAdapter#componentMoved(java.awt.event.ComponentEvent) - */ - @Override - public void componentMoved(ComponentEvent e) { - storeSizeOfDialog(); - } - }; - - } - - /** - * Stores the working directory path for this view in the global - * preferences. - * - * @param lastSelectedDir - * directory that is used as the working directory in this view. - */ - private void storeLastSelectedDirectory(Path lastSelectedDir) { - lastSelectedDirectory = lastSelectedDir; - if (lastSelectedDirectory != null) { - Globals.prefs.put(FindUnlinkedFilesDialog.GLOBAL_PREFS_WORKING_DIRECTORY_KEY, - lastSelectedDirectory.toAbsolutePath().toString()); - } - } - - /** - * Loads the working directory path which is persistantly stored for this - * view and returns it as a {@link File}-Object.
- *
- * If there is no working directory path stored, the general working - * directory will be consulted. - * - * @return The persistently stored working directory path for this view. - */ - private Path loadLastSelectedDirectory() { - String workingDirectory = Globals.prefs.get(FindUnlinkedFilesDialog.GLOBAL_PREFS_WORKING_DIRECTORY_KEY); - if (workingDirectory == null) { - workingDirectory = Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY); - } - lastSelectedDirectory = Paths.get(workingDirectory); - - return lastSelectedDirectory; - } - - /** - * Disables or enables all visible Elements in this Dialog.
- *
- * This also removes the {@link MouseListener} from the Tree-View to prevent - * it from receiving mouse events when in disabled-state. - * - * @param enable - * true when the elements shall get enabled, - * false when they shall get disabled. - */ - private void disOrEnableDialog(boolean enable) { - - if (enable) { - tree.addMouseListener(treeMouseListener); - } else { - tree.removeMouseListener(treeMouseListener); - } - disOrEnableAllElements(FindUnlinkedFilesDialog.this, enable); - } - - /** - * Recursively disables or enables all swing and awt components in this - * dialog, starting with but not including the container - * startContainer. - * - * @param startContainer - * The GUI Element to start with. - * @param enable - * true, if all elements will get enabled, - * false if all elements will get disabled. - */ - private void disOrEnableAllElements(Container startContainer, boolean enable) { - Component[] children = startContainer.getComponents(); - for (Component child : children) { - if (child instanceof Container) { - disOrEnableAllElements((Container) child, enable); - } - child.setEnabled(enable); - } - } - - /** - * Expands or collapses the specified tree according to the - * expand-parameter. - */ - private void expandTree(JTree currentTree, TreePath parent, boolean expand) { - TreeNode node = (TreeNode) parent.getLastPathComponent(); - if (node.getChildCount() >= 0) { - for (Enumeration e = node.children(); e.hasMoreElements();) { - TreePath path = parent.pathByAddingChild(e.nextElement()); - expandTree(currentTree, path, expand); - } - } - if (expand) { - currentTree.expandPath(parent); - } else { - currentTree.collapsePath(parent); - } - } - - /** - * Starts the search of unlinked files according to the current dialog - * state.
- *
- * This state is made of:
- *

  • The value of the "directory"-input-textfield and
  • The file type - * selection.
    - * The search will process in a seperate thread and the progress bar behind - * the "search" button will be displayed.
    - *
    - * When the search has completed, the - * {@link #searchFinishedHandler(CheckableTreeNode)} handler method is - * invoked. - */ - private void startSearch() { - - Path directory = Paths.get(textfieldDirectoryPath.getText()); - if (Files.notExists(directory)) { - directory = Paths.get(System.getProperty("user.dir")); - } - if (!Files.isDirectory(directory)) { - directory = directory.getParent(); - } - - //this addtional statement is needed because for the lamdba the variable must be effetively final - Path dir = directory; - - storeLastSelectedDirectory(directory); - - progressBarSearching.setMinimumSize( - new Dimension(buttonScan.getSize().width, progressBarSearching.getMinimumSize().height)); - progressBarSearching.setVisible(true); - progressBarSearching.setString(""); - - labelSearchingDirectoryInfo.setVisible(true); - buttonScan.setVisible(false); - - disOrEnableDialog(false); - labelSearchingDirectoryInfo.setEnabled(true); - - final FileFilter selectedFileFilter = (FileFilter) comboBoxFileTypeSelection.getSelectedItem(); - - threadState.set(true); - JabRefExecutorService.INSTANCE.execute(() -> { - UnlinkedPDFFileFilter unlinkedPDFFileFilter = new UnlinkedPDFFileFilter(selectedFileFilter, - databaseContext); - CheckableTreeNode rootNode = crawler.searchDirectory(dir.toFile(), unlinkedPDFFileFilter, threadState, - new ChangeListener() { - - int counter; - - @Override - public void stateChanged(ChangeEvent e) { - counter++; - String message; - if (counter == 1) { - message = Localization.lang("One file found"); - } else { - message = Localization.lang("%0 files found", Integer.toString(counter)); - } - progressBarSearching.setString(message); - } - }); - searchFinishedHandler(rootNode); - }); - - } - - /** - * This will start the import of all file of all selected nodes in this - * dialogs tree view.
    - *
    - * The import itself will run in a seperate thread, whilst this dialog will - * be showing a progress bar, until the thread has finished its work.
    - *
    - * When the import has finished, the {@link #importFinishedHandler(java.util.List)} is - * invoked. - */ - private void startImport() { - - if (treeModel == null) { - return; - } - setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - - CheckableTreeNode root = (CheckableTreeNode) treeModel.getRoot(); - - final List fileList = getFileListFromNode(root); - - if ((fileList == null) || fileList.isEmpty()) { - return; - } - - progressBarImporting.setVisible(true); - labelImportingInfo.setVisible(true); - buttonApply.setVisible(false); - buttonClose.setVisible(false); - disOrEnableDialog(false); - - labelImportingInfo.setEnabled(true); - - progressBarImporting.setMinimum(0); - progressBarImporting.setMaximum(fileList.size()); - progressBarImporting.setValue(0); - progressBarImporting.setString(""); - - final EntryType entryType = ((BibtexEntryTypeWrapper) comboBoxEntryTypeSelection.getSelectedItem()) - .getEntryType(); - - threadState.set(true); - JabRefExecutorService.INSTANCE.execute(() -> { - List errors = new LinkedList<>(); - creatorManager.addEntriesFromFiles(fileList, databaseContext.getDatabase(), frame.getCurrentBasePanel(), - entryType, checkBoxWhyIsThereNoGetSelectedStupidSwing, new ChangeListener() { - - int counter; - - @Override - public void stateChanged(ChangeEvent e) { - counter++; - progressBarImporting.setValue(counter); - progressBarImporting.setString(Localization.lang("%0 of %1", Integer.toString(counter), - Integer.toString(progressBarImporting.getMaximum()))); - } - }, errors); - importFinishedHandler(errors); - }); - } - - /** - * - * @param errors - */ - private void importFinishedHandler(List errors) { - - if ((errors != null) && !errors.isEmpty()) { - String message; - if (errors.size() == 1) { - message = Localization.lang("There was one file that could not be imported."); - } else { - message = Localization.lang("There were %0 files which could not be imported.", - Integer.toString(errors.size())); - } - JOptionPane.showMessageDialog(this, - Localization.lang("The import finished with warnings:") + "\n" + message, - Localization.lang("Warning"), JOptionPane.WARNING_MESSAGE); - } - - progressBarImporting.setVisible(false); - labelImportingInfo.setVisible(false); - buttonApply.setVisible(true); - buttonClose.setVisible(true); - disOrEnableDialog(true); - setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - frame.getCurrentBasePanel().markBaseChanged(); - } - - /** - * Will be called from the Thread in which the "unlinked files search" is - * processed. As the result of the search, the root node of the determined - * file structure is passed. - * - * @param rootNode - * The root of the file structure as the result of the search. - */ - private void searchFinishedHandler(CheckableTreeNode rootNode) { - treeModel = new DefaultTreeModel(rootNode); - tree.setModel(treeModel); - tree.setRootVisible(rootNode.getChildCount() > 0); - - tree.invalidate(); - tree.repaint(); - - progressBarSearching.setVisible(false); - labelSearchingDirectoryInfo.setVisible(false); - buttonScan.setVisible(true); - actionSelectAll.actionPerformed(null); - - disOrEnableDialog(true); - buttonApply.setEnabled(true); - } - - /** - * Sets up the actions for the components. - */ - private void setupActions() { - - DirectoryDialogConfiguration directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)).build(); - DialogService ds = new FXDialogService(); - /** - * Stores the selected directory. - */ - buttonBrowse.addActionListener(e -> { - Optional selectedDirectory = DefaultTaskExecutor - .runInJavaFXThread(() -> ds.showDirectorySelectionDialog(directoryDialogConfiguration)); - selectedDirectory.ifPresent(d -> { - textfieldDirectoryPath.setText(d.toAbsolutePath().toString()); - storeLastSelectedDirectory(d); - }); - }); - - buttonScan.addActionListener(e -> startSearch()); - - /** - * Action for the button "Import...".
    - *
    - * Actions on this button will start the import of all file of all - * selected nodes in this dialogs tree view.
    - */ - ActionListener actionListenerImportEntrys = e -> startImport(); - buttonApply.addActionListener(actionListenerImportEntrys); - buttonClose.addActionListener(e -> dispose()); - } - - /** - * Creates a list of {@link File}s for all leaf nodes in the tree structure - * node, which have been marked as selected.
    - *
    - * Selected nodes correspond to those entries in the tree, - * whose checkbox is checked. - * - * SIDE EFFECT: The checked nodes are removed from the tree. - * - * @param node - * The root node representing a tree structure. - * @return A list of files of all checked leaf nodes. - */ - private List getFileListFromNode(CheckableTreeNode node) { - List filesList = new ArrayList<>(); - Enumeration children = node.depthFirstEnumeration(); - List nodesToRemove = new ArrayList<>(); - for (CheckableTreeNode child : Collections.list(children)) { - if (child.isLeaf() && child.isSelected()) { - File nodeFile = ((FileNodeWrapper) child.getUserObject()).file; - if ((nodeFile != null) && nodeFile.isFile()) { - filesList.add(nodeFile); - nodesToRemove.add(child); - } - } - } - - // remove imported files from tree - DefaultTreeModel model = (DefaultTreeModel) tree.getModel(); - for (CheckableTreeNode nodeToRemove : nodesToRemove) { - DefaultMutableTreeNode parent = (DefaultMutableTreeNode) nodeToRemove.getParent(); - model.removeNodeFromParent(nodeToRemove); - - // remove empty parent node - while ((parent != null) && parent.isLeaf()) { - DefaultMutableTreeNode pp = (DefaultMutableTreeNode) parent.getParent(); - if (pp != null) { - model.removeNodeFromParent(parent); - } - parent = pp; - } - // TODO: update counter / see: getTreeCellRendererComponent for label generation - } - tree.invalidate(); - tree.repaint(); - - return filesList; - } - - /** - * Initializes the visible components in this dialog. - */ - private void initComponents() { - - this.addComponentListener(dialogPositionListener); - /* Interrupts the searchThread by setting the State-Array to 0 */ - this.addWindowListener(new WindowAdapter() { - - @Override - public void windowClosing(WindowEvent e) { - threadState.set(false); - } - }); - - panelDirectory = new JPanel(); - panelSearchArea = new JPanel(); - panelFiles = new JPanel(); - panelOptions = new JPanel(); - panelEntryTypesSelection = new JPanel(); - panelButtons = new JPanel(); - panelImportArea = new JPanel(); - - buttonBrowse = new JButton(Localization.lang("Browse")); - buttonBrowse.setMnemonic('B'); - buttonBrowse.setToolTipText(Localization.lang("Opens the file browser.")); - buttonScan = new JButton(Localization.lang("Scan directory")); - buttonScan.setMnemonic('S'); - buttonScan.setToolTipText(Localization.lang("Searches the selected directory for unlinked files.")); - buttonApply = new JButton(Localization.lang("Apply")); - buttonApply.setMnemonic('I'); - buttonApply.setToolTipText(Localization.lang("Starts the import of BibTeX entries.")); - buttonClose = new JButton(Localization.lang("Close")); - buttonClose.setToolTipText(Localization.lang("Leave this dialog.")); - buttonClose.setMnemonic('C'); - - /* Options for the TreeView */ - buttonOptionSelectAll = new JButton(); - buttonOptionSelectAll.setMnemonic('A'); - buttonOptionSelectAll.setAction(actionSelectAll); - buttonOptionDeselectAll = new JButton(); - buttonOptionDeselectAll.setMnemonic('U'); - buttonOptionDeselectAll.setAction(actionUnselectAll); - buttonOptionExpandAll = new JButton(); - buttonOptionExpandAll.setMnemonic('E'); - buttonOptionExpandAll.setAction(actionExpandTree); - buttonOptionCollapseAll = new JButton(); - buttonOptionCollapseAll.setMnemonic('L'); - buttonOptionCollapseAll.setAction(actionCollapseTree); - - checkboxCreateKeywords = new JCheckBox(Localization.lang("Create directory based keywords")); - checkboxCreateKeywords - .setToolTipText(Localization.lang("Creates keywords in created entrys with directory pathnames")); - checkboxCreateKeywords.setSelected(checkBoxWhyIsThereNoGetSelectedStupidSwing); - checkboxCreateKeywords.addItemListener( - e -> checkBoxWhyIsThereNoGetSelectedStupidSwing = !checkBoxWhyIsThereNoGetSelectedStupidSwing); - - textfieldDirectoryPath = new JTextField(); - textfieldDirectoryPath - .setText(lastSelectedDirectory == null ? "" : lastSelectedDirectory.toAbsolutePath().toString()); - - labelDirectoryDescription = new JLabel(Localization.lang("Select a directory where the search shall start.")); - labelFileTypesDescription = new JLabel(Localization.lang("Select file type:")); - labelFilesDescription = new JLabel(Localization.lang("These files are not linked in the active library.")); - labelEntryTypeDescription = new JLabel(Localization.lang("Entry type to be created:")); - labelSearchingDirectoryInfo = new JLabel(Localization.lang("Searching file system...")); - labelSearchingDirectoryInfo.setHorizontalAlignment(SwingConstants.CENTER); - labelSearchingDirectoryInfo.setVisible(false); - labelImportingInfo = new JLabel(Localization.lang("Importing into Library...")); - labelImportingInfo.setHorizontalAlignment(SwingConstants.CENTER); - labelImportingInfo.setVisible(false); - - tree = new JTree(); - - scrollpaneTree = new JScrollPane(tree); - scrollpaneTree.setWheelScrollingEnabled(true); - - progressBarSearching = new JProgressBar(); - progressBarSearching.setIndeterminate(true); - progressBarSearching.setVisible(false); - progressBarSearching.setStringPainted(true); - - progressBarImporting = new JProgressBar(); - progressBarImporting.setIndeterminate(false); - progressBarImporting.setVisible(false); - progressBarImporting.setStringPainted(true); - - } - - /** - * Initializes the layout for the visible components in this menu. A - * {@link GridBagLayout} is used. - */ - private void initLayout() { - - GridBagLayout gbl = new GridBagLayout(); - - panelDirectory.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), - Localization.lang("Select directory"))); - panelFiles.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), - Localization.lang("Select files"))); - panelEntryTypesSelection.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), - Localization.lang("BibTeX entry creation"))); - - Insets basicInsets = new Insets(6, 6, 6, 6); - Insets smallInsets = new Insets(3, 2, 3, 1); - Insets noInsets = new Insets(0, 0, 0, 0); - - // x, y, w, h, wx,wy,ix,iy - FindUnlinkedFilesDialog.addComponent(gbl, panelSearchArea, buttonScan, GridBagConstraints.HORIZONTAL, - GridBagConstraints.EAST, noInsets, 0, 1, 1, 1, 1, 1, 40, 10); - FindUnlinkedFilesDialog.addComponent(gbl, panelSearchArea, labelSearchingDirectoryInfo, - GridBagConstraints.HORIZONTAL, GridBagConstraints.EAST, noInsets, 0, 2, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelSearchArea, progressBarSearching, GridBagConstraints.HORIZONTAL, - GridBagConstraints.EAST, noInsets, 0, 3, 1, 1, 0, 0, 0, 0); - - FindUnlinkedFilesDialog.addComponent(gbl, panelDirectory, labelDirectoryDescription, null, - GridBagConstraints.WEST, new Insets(6, 6, 0, 6), 0, 0, 3, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelDirectory, textfieldDirectoryPath, GridBagConstraints.HORIZONTAL, - null, basicInsets, 0, 1, 2, 1, 1, 1, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelDirectory, buttonBrowse, GridBagConstraints.HORIZONTAL, - GridBagConstraints.EAST, basicInsets, 2, 1, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelDirectory, labelFileTypesDescription, GridBagConstraints.NONE, - GridBagConstraints.WEST, new Insets(18, 6, 18, 3), 0, 3, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelDirectory, comboBoxFileTypeSelection, - GridBagConstraints.HORIZONTAL, GridBagConstraints.WEST, new Insets(18, 3, 18, 6), 1, 3, 1, 1, 1, 0, 0, - 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelDirectory, panelSearchArea, GridBagConstraints.HORIZONTAL, - GridBagConstraints.EAST, new Insets(18, 6, 18, 6), 2, 3, 1, 1, 0, 0, 0, 0); - - FindUnlinkedFilesDialog.addComponent(gbl, panelFiles, labelFilesDescription, GridBagConstraints.HORIZONTAL, - GridBagConstraints.WEST, new Insets(6, 6, 0, 6), 0, 0, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelFiles, scrollpaneTree, GridBagConstraints.BOTH, - GridBagConstraints.CENTER, basicInsets, 0, 1, 1, 1, 1, 1, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelFiles, panelOptions, GridBagConstraints.NONE, - GridBagConstraints.NORTHEAST, basicInsets, 1, 1, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelOptions, buttonOptionSelectAll, GridBagConstraints.HORIZONTAL, - GridBagConstraints.NORTH, noInsets, 0, 0, 1, 1, 1, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelOptions, buttonOptionDeselectAll, GridBagConstraints.HORIZONTAL, - GridBagConstraints.NORTH, noInsets, 0, 1, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelOptions, buttonOptionExpandAll, GridBagConstraints.HORIZONTAL, - GridBagConstraints.NORTH, new Insets(6, 0, 0, 0), 0, 2, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelOptions, buttonOptionCollapseAll, GridBagConstraints.HORIZONTAL, - GridBagConstraints.NORTH, noInsets, 0, 3, 1, 1, 0, 0, 0, 0); - - FindUnlinkedFilesDialog.addComponent(gbl, panelEntryTypesSelection, labelEntryTypeDescription, - GridBagConstraints.NONE, GridBagConstraints.WEST, basicInsets, 0, 0, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelEntryTypesSelection, comboBoxEntryTypeSelection, - GridBagConstraints.NONE, GridBagConstraints.WEST, basicInsets, 1, 0, 1, 1, 1, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelEntryTypesSelection, checkboxCreateKeywords, - GridBagConstraints.HORIZONTAL, GridBagConstraints.WEST, basicInsets, 0, 1, 2, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelImportArea, labelImportingInfo, GridBagConstraints.HORIZONTAL, - GridBagConstraints.CENTER, new Insets(6, 6, 0, 6), 0, 1, 1, 1, 1, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelImportArea, progressBarImporting, GridBagConstraints.HORIZONTAL, - GridBagConstraints.CENTER, new Insets(0, 6, 6, 6), 0, 2, 1, 1, 1, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelButtons, panelImportArea, GridBagConstraints.NONE, - GridBagConstraints.EAST, smallInsets, 1, 0, 1, 1, 0, 0, 0, 0); - - FindUnlinkedFilesDialog.addComponent(gbl, getContentPane(), panelDirectory, GridBagConstraints.HORIZONTAL, - GridBagConstraints.CENTER, basicInsets, 0, 0, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, getContentPane(), panelFiles, GridBagConstraints.BOTH, - GridBagConstraints.NORTHWEST, new Insets(12, 6, 2, 2), 0, 1, 1, 1, 1, 1, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, getContentPane(), panelEntryTypesSelection, - GridBagConstraints.HORIZONTAL, GridBagConstraints.SOUTHWEST, new Insets(12, 6, 2, 2), 0, 2, 1, 1, 0, 0, - 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, getContentPane(), panelButtons, GridBagConstraints.HORIZONTAL, - GridBagConstraints.CENTER, new Insets(10, 6, 10, 6), 0, 3, 1, 1, 0, 0, 0, 0); - - ButtonBarBuilder bb = new ButtonBarBuilder(); - bb.addGlue(); - bb.addButton(buttonApply); - bb.addButton(buttonClose); - bb.addGlue(); - - bb.getPanel().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - panelImportArea.add(bb.getPanel(), GridBagConstraints.NONE); - pack(); - - } - - /** - * Adds a component to a container, using the specified gridbag-layout and - * the supplied parameters.
    - *
    - * This method is simply used to ged rid of thousands of lines of code, - * which inevitably rise when layouts such as the gridbag-layout is being - * used. - * - * @param layout - * The layout to be used. - * @param container - * The {@link Container}, to which the component will be added. - * @param component - * An AWT {@link Component}, that will be added to the container. - * @param fill - * A constant describing the fill behaviour (see - * {@link GridBagConstraints}). Can be null, if no - * filling wants to be specified. - * @param anchor - * A constant describing the anchor of the element in its parent - * container (see {@link GridBagConstraints}). Can be - * null, if no specification is needed. - * @param gridX - * The relative grid-X coordinate. - * @param gridY - * The relative grid-Y coordinate. - * @param width - * The relative width of the component. - * @param height - * The relative height of the component. - * @param weightX - * A value for the horizontal weight. - * @param weightY - * A value for the vertical weight. - * @param insets - * Insets of the component. Can be null. - */ - private static void addComponent(GridBagLayout layout, Container container, Component component, Integer fill, - Integer anchor, Insets insets, int gridX, int gridY, int width, int height, double weightX, double weightY, - int ipadX, int ipadY) { - container.setLayout(layout); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.gridx = gridX; - constraints.gridy = gridY; - constraints.gridwidth = width; - constraints.gridheight = height; - constraints.weightx = weightX; - constraints.weighty = weightY; - constraints.ipadx = ipadX; - constraints.ipady = ipadY; - if (fill != null) { - constraints.fill = fill; - } - if (insets != null) { - constraints.insets = insets; - } - if (anchor != null) { - constraints.anchor = anchor; - } - layout.setConstraints(component, constraints); - container.add(component); - } - - /** - * Creates the tree view, that holds the data structure.
    - *
    - * Initially, the root node is not visible, so that the tree appears empty at the beginning. - */ - private void createTree() { - - /** - * Mouse listener to listen for mouse events on the tree.
    - * This will mark the selected tree entry as "selected" or "unselected", - * which will cause this nodes checkbox to appear as either "checked" or - * "unchecked". - */ - treeMouseListener = new MouseAdapter() { - - @Override - public void mousePressed(MouseEvent e) { - int x = e.getX(); - int y = e.getY(); - - int row = tree.getRowForLocation(x, y); - - TreePath path = tree.getPathForRow(row); - if (path != null) { - CheckableTreeNode node = (CheckableTreeNode) path.getLastPathComponent(); - if (e.getClickCount() == 2) { - Object userObject = node.getUserObject(); - if ((userObject instanceof FileNodeWrapper) && node.isLeaf()) { - FileNodeWrapper fnw = (FileNodeWrapper) userObject; - try { - JabRefDesktop.openExternalViewer( - JabRefGUI.getMainFrame().getCurrentBasePanel().getBibDatabaseContext(), - fnw.file.getAbsolutePath(), FieldName.PDF); - } catch (IOException e1) { - LOGGER.info("Error opening file", e1); - } - } - } else { - node.check(); - tree.invalidate(); - tree.repaint(); - } - } - } - - }; - - CheckableTreeNode startNode = new CheckableTreeNode("ROOT"); - DefaultTreeModel model = new DefaultTreeModel(startNode); - - tree.setModel(model); - tree.setRootVisible(false); - - DefaultTreeCellRenderer renderer = new CheckboxTreeCellRenderer(); - tree.setCellRenderer(renderer); - - tree.addMouseListener(treeMouseListener); - - } - - /** - * Initialises the combobox that contains the available file types which - * bibtex entries can be created of. - */ - private void createFileTypesCombobox() { - - List fileFilterList = creatorManager.getFileFilterList(); - - comboBoxFileTypeSelection = new JComboBox<>(fileFilterList.toArray(new FileFilter[fileFilterList.size()])); - - comboBoxFileTypeSelection.setRenderer(new DefaultListCellRenderer() { - - /* (non-Javadoc) - * @see javax.swing.DefaultListCellRenderer#getListCellRendererComponent(javax.swing.JList, java.lang.Object, int, boolean, boolean) - */ - @Override - public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, - boolean cellHasFocus) { - JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, - cellHasFocus); - if (value instanceof EntryFromFileCreator) { - EntryFromFileCreator creator = (EntryFromFileCreator) value; - if (creator.getExternalFileType() != null) { - label.setIcon(creator.getExternalFileType().getIcon()); - } - } - return label; - } - }); - - } - - /** - * Creates the ComboBox-View for the Listbox that holds the Bibtex entry - * types. - */ - private void createEntryTypesCombobox() { - - Iterator iterator = EntryTypes - .getAllValues(frame.getCurrentBasePanel().getBibDatabaseContext().getMode()).iterator(); - List list = new ArrayList<>(); - list.add( - new BibtexEntryTypeWrapper(null)); - while (iterator.hasNext()) { - list.add(new BibtexEntryTypeWrapper(iterator.next())); - } - comboBoxEntryTypeSelection = new JComboBox<>(list.toArray(new BibtexEntryTypeWrapper[list.size()])); - } - - /** - * Wrapper for displaying the Type {@link BibtexEntryType} in a Combobox. - * - * @author Nosh&Dan - * @version 12.11.2008 | 01:02:30 - * - */ - private static class BibtexEntryTypeWrapper { - - private final EntryType entryType; - - BibtexEntryTypeWrapper(EntryType bibtexType) { - this.entryType = bibtexType; - } - - @Override - public String toString() { - if (entryType == null) { - return Localization.lang(""); - } - return entryType.getName(); - } - - public EntryType getEntryType() { - return entryType; - } - } - - public static class CheckableTreeNode extends DefaultMutableTreeNode { - - private boolean isSelected; - private final JCheckBox checkbox; - - public CheckableTreeNode(Object userObject) { - super(userObject); - checkbox = new JCheckBox(); - } - - /** - * @return the checkbox - */ - public JCheckBox getCheckbox() { - return checkbox; - } - - public void check() { - setSelected(!isSelected); - } - - public void setSelected(boolean bSelected) { - isSelected = bSelected; - Enumeration tmpChildren = this.children(); - for (CheckableTreeNode child : Collections.list(tmpChildren)) { - child.setSelected(bSelected); - } - - } - - public boolean isSelected() { - return isSelected; - } - - } - - private static class CheckboxTreeCellRenderer extends DefaultTreeCellRenderer { - - private final FileSystemView fsv = FileSystemView.getFileSystemView(); - - @Override - public Component getTreeCellRendererComponent(final JTree tree, Object value, boolean sel, boolean expanded, - boolean leaf, int row, boolean hasFocus) { - - Component nodeComponent = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, - hasFocus); - CheckableTreeNode node = (CheckableTreeNode) value; - - FileNodeWrapper userObject = (FileNodeWrapper) node.getUserObject(); - - JPanel newPanel = new JPanel(); - - JCheckBox checkbox = node.getCheckbox(); - checkbox.setSelected(node.isSelected()); - - try { - setIcon(fsv.getSystemIcon(userObject.file)); - } catch (Exception ignored) { - // Ignored - } - - newPanel.setBackground(nodeComponent.getBackground()); - checkbox.setBackground(nodeComponent.getBackground()); - - GridBagLayout gbl = new GridBagLayout(); - FindUnlinkedFilesDialog.addComponent(gbl, newPanel, checkbox, null, null, null, 0, 0, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, newPanel, nodeComponent, GridBagConstraints.HORIZONTAL, null, - new Insets(1, 2, 0, 0), 1, 0, 1, 1, 1, 0, 0, 0); - - if (userObject.fileCount > 0) { - JLabel label = new JLabel( - "(" + userObject.fileCount + " file" + (userObject.fileCount > 1 ? "s" : "") + ")"); - FindUnlinkedFilesDialog.addComponent(gbl, newPanel, label, null, null, new Insets(1, 2, 0, 0), 2, 0, 1, - 1, 0, 0, 0, 0); - } - return newPanel; - } - - } - - public static class FileNodeWrapper { - - public final File file; - public final int fileCount; - - public FileNodeWrapper(File aFile) { - this(aFile, 0); - } - - /** - * @param aDirectory - * @param fileCount - */ - public FileNodeWrapper(File aDirectory, int fileCount) { - this.file = aDirectory; - this.fileCount = fileCount; - } - - /* - * (non-Javadoc) - * - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return file.getName(); - } - } - -} diff --git a/src/main/java/org/jabref/gui/GUIGlobals.java b/src/main/java/org/jabref/gui/GUIGlobals.java index 2b3ba25b364..e1f145054ff 100644 --- a/src/main/java/org/jabref/gui/GUIGlobals.java +++ b/src/main/java/org/jabref/gui/GUIGlobals.java @@ -2,21 +2,12 @@ import java.awt.Color; import java.awt.Font; -import java.awt.Toolkit; -import java.util.HashMap; -import java.util.Map; - -import javax.swing.JLabel; import org.jabref.Globals; -import org.jabref.gui.externalfiletype.ExternalFileType; -import org.jabref.gui.externalfiletype.ExternalFileTypes; +import org.jabref.gui.icon.IconTheme; import org.jabref.gui.keyboard.EmacsKeyBindings; -import org.jabref.gui.specialfields.SpecialFieldViewModel; +import org.jabref.gui.util.CustomLocalDragboard; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.util.OS; -import org.jabref.model.entry.FieldName; -import org.jabref.model.entry.specialfields.SpecialField; import org.jabref.preferences.JabRefPreferences; import org.slf4j.Logger; @@ -26,42 +17,29 @@ * Static variables for graphics files and keyboard shortcuts. */ public class GUIGlobals { + public static Color editorTextColor; public static Color validFieldBackgroundColor; public static Color activeBackgroundColor; public static Color invalidFieldBackgroundColor; public static Font currentFont; - public static final Color NULL_FIELD_COLOR = new Color(75, 130, 95); // Valid field, green. - public static final Color ACTIVE_EDITOR_COLOR = new Color(230, 230, 255); - public static final int WIDTH_ICON_COL = JabRefPreferences.getInstance().getInt(JabRefPreferences.ICON_SIZE_SMALL) + 12; // add some additional space to improve appearance + public static CustomLocalDragboard localDragboard = new CustomLocalDragboard(); + + public static final int WIDTH_ICON_COL = 16 + 12; // add some additional space to improve appearance - public static final int WIDTH_ICON_COL_RANKING = 5 * JabRefPreferences.getInstance().getInt(JabRefPreferences.ICON_SIZE_SMALL); // Width of Ranking Icon Column + public static final int WIDTH_ICON_COL_RANKING = 5 * 16; // Width of Ranking Icon Column public static final String UNTITLED_TITLE = Localization.lang("untitled"); - public static final int MAX_BACK_HISTORY_SIZE = 10; // The maximum number of "Back" operations stored. // Colors. public static final Color ENTRY_EDITOR_LABEL_COLOR = new Color(100, 100, 150); // Empty field, blue. - static final Color INACTIVE_TABBED_COLOR = Color.black; // inactive Database private static final Logger LOGGER = LoggerFactory.getLogger(GUIGlobals.class); - private static final Map TABLE_ICONS = new HashMap<>(); // Contains table icon mappings. Set up - static final Color ACTIVE_TABBED_COLOR = ENTRY_EDITOR_LABEL_COLOR.darker(); // active Database (JTabbedPane) private GUIGlobals() { } - public static JLabel getTableIcon(String fieldType) { - JLabel label = GUIGlobals.TABLE_ICONS.get(fieldType); - if (label == null) { - LOGGER.info("Error: no table icon defined for type '" + fieldType + "'."); - return null; - } else { - return label; - } - } - public static void updateEntryEditorColors() { GUIGlobals.activeBackgroundColor = JabRefPreferences.getInstance().getColor(JabRefPreferences.ACTIVE_FIELD_EDITOR_BACKGROUND_COLOR); GUIGlobals.validFieldBackgroundColor = JabRefPreferences.getInstance().getColor(JabRefPreferences.VALID_FIELD_BACKGROUND_COLOR); @@ -75,79 +53,6 @@ public static void updateEntryEditorColors() { * on Un*x is unavailable. */ public static void init() { - JLabel label; - label = new JLabel(IconTheme.JabRefIcon.PDF_FILE.getSmallIcon()); - label.setToolTipText(Localization.lang("Open") + " PDF"); - GUIGlobals.TABLE_ICONS.put(FieldName.PDF, label); - - label = new JLabel(IconTheme.JabRefIcon.WWW.getSmallIcon()); - label.setToolTipText(Localization.lang("Open") + " URL"); - GUIGlobals.TABLE_ICONS.put(FieldName.URL, label); - - label = new JLabel(IconTheme.JabRefIcon.WWW.getSmallIcon()); - label.setToolTipText(Localization.lang("Open") + " CiteSeer URL"); - GUIGlobals.TABLE_ICONS.put("citeseerurl", label); - - label = new JLabel(IconTheme.JabRefIcon.WWW.getSmallIcon()); - label.setToolTipText(Localization.lang("Open") + " ArXiv URL"); - GUIGlobals.TABLE_ICONS.put(FieldName.EPRINT, label); - - label = new JLabel(IconTheme.JabRefIcon.DOI.getSmallIcon()); - label.setToolTipText(Localization.lang("Open") + " DOI " + Localization.lang("web link")); - GUIGlobals.TABLE_ICONS.put(FieldName.DOI, label); - - label = new JLabel(IconTheme.JabRefIcon.FILE.getSmallIcon()); - label.setToolTipText(Localization.lang("Open") + " PS"); - GUIGlobals.TABLE_ICONS.put(FieldName.PS, label); - - label = new JLabel(IconTheme.JabRefIcon.FOLDER.getSmallIcon()); - label.setToolTipText(Localization.lang("Open folder")); - GUIGlobals.TABLE_ICONS.put(FieldName.FOLDER, label); - - label = new JLabel(IconTheme.JabRefIcon.FILE.getSmallIcon()); - label.setToolTipText(Localization.lang("Open file")); - GUIGlobals.TABLE_ICONS.put(FieldName.FILE, label); - - for (ExternalFileType fileType : ExternalFileTypes.getInstance().getExternalFileTypeSelection()) { - label = new JLabel(fileType.getIcon()); - label.setToolTipText(Localization.lang("Open %0 file", fileType.getName())); - GUIGlobals.TABLE_ICONS.put(fileType.getName(), label); - } - - SpecialFieldViewModel relevanceViewModel = new SpecialFieldViewModel(SpecialField.RELEVANCE); - label = new JLabel(relevanceViewModel.getRepresentingIcon()); - label.setToolTipText(relevanceViewModel.getLocalization()); - GUIGlobals.TABLE_ICONS.put(SpecialField.RELEVANCE.getFieldName(), label); - - SpecialFieldViewModel qualityViewModel = new SpecialFieldViewModel(SpecialField.QUALITY); - label = new JLabel(qualityViewModel.getRepresentingIcon()); - label.setToolTipText(qualityViewModel.getLocalization()); - GUIGlobals.TABLE_ICONS.put(SpecialField.QUALITY.getFieldName(), label); - - // Ranking item in the menu uses one star - SpecialFieldViewModel rankViewModel = new SpecialFieldViewModel(SpecialField.RANKING); - label = new JLabel(rankViewModel.getRepresentingIcon()); - label.setToolTipText(rankViewModel.getLocalization()); - GUIGlobals.TABLE_ICONS.put(SpecialField.RANKING.getFieldName(), label); - - // Priority icon used for the menu - SpecialFieldViewModel priorityViewModel = new SpecialFieldViewModel(SpecialField.PRIORITY); - label = new JLabel(priorityViewModel.getRepresentingIcon()); - label.setToolTipText(priorityViewModel.getLocalization()); - GUIGlobals.TABLE_ICONS.put(SpecialField.PRIORITY.getFieldName(), label); - - // Read icon used for menu - SpecialFieldViewModel readViewModel = new SpecialFieldViewModel(SpecialField.READ_STATUS); - label = new JLabel(readViewModel.getRepresentingIcon()); - label.setToolTipText(readViewModel.getLocalization()); - GUIGlobals.TABLE_ICONS.put(SpecialField.READ_STATUS.getFieldName(), label); - - // Print icon used for menu - SpecialFieldViewModel printedViewModel = new SpecialFieldViewModel(SpecialField.PRINTED); - label = new JLabel(printedViewModel.getRepresentingIcon()); - label.setToolTipText(printedViewModel.getLocalization()); - GUIGlobals.TABLE_ICONS.put(SpecialField.PRINTED.getFieldName(), label); - if (Globals.prefs.getBoolean(JabRefPreferences.EDITOR_EMACS_KEYBINDINGS)) { EmacsKeyBindings.load(); } @@ -155,21 +60,9 @@ public static void init() { // Set up entry editor colors, first time: GUIGlobals.updateEntryEditorColors(); + IconTheme.loadFonts(); GUIGlobals.currentFont = new Font(Globals.prefs.get(JabRefPreferences.FONT_FAMILY), - Globals.prefs.getInt(JabRefPreferences.FONT_STYLE), Globals.prefs.getInt(JabRefPreferences.FONT_SIZE)); - - // Set WM_CLASS using reflection for certain Un*x window managers - if (!OS.WINDOWS && !OS.OS_X) { - try { - Toolkit xToolkit = Toolkit.getDefaultToolkit(); - java.lang.reflect.Field awtAppClassNameField = xToolkit.getClass().getDeclaredField("awtAppClassName"); - awtAppClassNameField.setAccessible(true); - awtAppClassNameField.set(xToolkit, "org-jabref-JabRefMain"); - } catch (Exception e) { - // ignore any error since this code only works for certain toolkits - } - } - + Globals.prefs.getInt(JabRefPreferences.FONT_STYLE), Globals.prefs.getInt(JabRefPreferences.FONT_SIZE)); } public static void setFont(int size) { @@ -177,5 +70,4 @@ public static void setFont(int size) { // update preferences Globals.prefs.putInt(JabRefPreferences.FONT_SIZE, size); } - } diff --git a/src/main/java/org/jabref/gui/GenFieldsCustomizer.java b/src/main/java/org/jabref/gui/GenFieldsCustomizer.java deleted file mode 100644 index b8b3a800b76..00000000000 --- a/src/main/java/org/jabref/gui/GenFieldsCustomizer.java +++ /dev/null @@ -1,175 +0,0 @@ -package org.jabref.gui; - -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.util.Locale; - -import javax.swing.AbstractAction; -import javax.swing.ActionMap; -import javax.swing.BorderFactory; -import javax.swing.InputMap; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; - -import org.jabref.Globals; -import org.jabref.gui.entryeditor.EntryEditorTabList; -import org.jabref.gui.help.HelpAction; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.logic.bibtexkeypattern.BibtexKeyGenerator; -import org.jabref.logic.help.HelpFile; -import org.jabref.logic.l10n.Localization; -import org.jabref.preferences.JabRefPreferences; - -import com.jgoodies.forms.builder.ButtonBarBuilder; -import com.jgoodies.forms.layout.Sizes; - -public class GenFieldsCustomizer extends JabRefDialog { - - private final JPanel buttons = new JPanel(); - private final JButton ok = new JButton(); - private final JButton cancel = new JButton(); - private final JButton helpBut; - private final JLabel jLabel1 = new JLabel(); - private final JPanel jPanel3 = new JPanel(); - private final JPanel jPanel4 = new JPanel(); - private final GridBagLayout gridBagLayout1 = new GridBagLayout(); - private final JScrollPane jScrollPane1 = new JScrollPane(); - private final JLabel jLabel2 = new JLabel(); - private final JTextArea fieldsArea = new JTextArea(); - private final GridBagLayout gridBagLayout2 = new GridBagLayout(); - private final JButton revert = new JButton(); - - public GenFieldsCustomizer(JabRefFrame frame) { - super(frame, Localization.lang("Set general fields"), false, GenFieldsCustomizer.class); - helpBut = new HelpAction(HelpFile.GENERAL_FIELDS).getHelpButton(); - jbInit(); - setSize(new Dimension(650, 300)); - } - - private void jbInit() { - ok.setText(Localization.lang("OK")); - ok.addActionListener(e -> okActionPerformed()); - cancel.setText(Localization.lang("Cancel")); - cancel.addActionListener(e -> dispose()); - jLabel1.setText(Localization.lang("Delimit fields with semicolon, ex.") + ": url;pdf;note"); - jPanel3.setLayout(gridBagLayout2); - jPanel4.setBorder(BorderFactory.createEtchedBorder()); - jPanel4.setLayout(gridBagLayout1); - jLabel2.setText(Localization.lang("General fields")); - - setFieldsText(); - - revert.setText(Localization.lang("Default")); - revert.addActionListener(e -> revertActionPerformed()); - this.getContentPane().add(buttons, BorderLayout.SOUTH); - ButtonBarBuilder bb = new ButtonBarBuilder(buttons); - buttons.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); - bb.addGlue(); - bb.addButton(ok); - bb.addButton(revert); - bb.addButton(cancel); - bb.addStrut(Sizes.DLUX5); - bb.addButton(helpBut); - bb.addGlue(); - - this.getContentPane().add(jPanel3, BorderLayout.CENTER); - jPanel3.add(jLabel1, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0 - , GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); - jPanel3.add(jPanel4, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0 - , GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 318, 193)); - jPanel4.add(jScrollPane1, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0 - , GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); - jScrollPane1.getViewport().add(fieldsArea, null); - jPanel4.add(jLabel2, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0 - , GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); - - // Key bindings: - ActionMap am = buttons.getActionMap(); - InputMap im = buttons.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DIALOG), "close"); - am.put("close", new AbstractAction() { - - @Override - public void actionPerformed(ActionEvent e) { - dispose(); - } - }); - - } - - private void okActionPerformed() { - String[] lines = fieldsArea.getText().split("\n"); - int i = 0; - for (; i < lines.length; i++) { - String[] parts = lines[i].split(":"); - if (parts.length != 2) { - // Report error and exit. - String field = Localization.lang("field"); - JOptionPane.showMessageDialog(this, Localization.lang("Each line must be on the following form") + " '" + - Localization.lang("Tabname") + ':' + field + "1;" + field + "2;...;" + field + "N'", - Localization.lang("Error"), JOptionPane.ERROR_MESSAGE); - return; - } - String testString = BibtexKeyGenerator.cleanKey(parts[1], - Globals.prefs.getBoolean(JabRefPreferences.ENFORCE_LEGAL_BIBTEX_KEY)); - if (!testString.equals(parts[1]) || (parts[1].indexOf('&') >= 0)) { - // Report error and exit. - JOptionPane.showMessageDialog(this, Localization.lang("Field names are not allowed to contain white space or the following " - + "characters") + ": # { } ~ , ^ &", - Localization.lang("Error"), JOptionPane.ERROR_MESSAGE); - - return; - } - - Globals.prefs.put((JabRefPreferences.CUSTOM_TAB_NAME + i), parts[0]); - Globals.prefs.put((JabRefPreferences.CUSTOM_TAB_FIELDS + i), parts[1].toLowerCase(Locale.ROOT)); - } - Globals.prefs.purgeSeries(JabRefPreferences.CUSTOM_TAB_NAME, i); - Globals.prefs.purgeSeries(JabRefPreferences.CUSTOM_TAB_FIELDS, i); - Globals.prefs.updateEntryEditorTabList(); - - dispose(); - } - - private void setFieldsText() { - StringBuilder sb = new StringBuilder(); - - EntryEditorTabList tabList = Globals.prefs.getEntryEditorTabList(); - for (int i = 0; i < tabList.getTabCount(); i++) { - sb.append(tabList.getTabName(i)); - sb.append(':'); - sb.append(String.join(";", tabList.getTabFields(i))); - sb.append('\n'); - } - - fieldsArea.setText(sb.toString()); - } - - private void revertActionPerformed() { - StringBuilder sb = new StringBuilder(); - String name; - String fields; - int i = 0; - while ((name = (String) Globals.prefs.defaults.get - (JabRefPreferences.CUSTOM_TAB_NAME + "_def" + i)) != null) { - sb.append(name); - fields = (String) Globals.prefs.defaults.get - (JabRefPreferences.CUSTOM_TAB_FIELDS + "_def" + i); - sb.append(':'); - sb.append(fields); - sb.append('\n'); - i++; - } - fieldsArea.setText(sb.toString()); - - } -} diff --git a/src/main/java/org/jabref/gui/GlobalFocusListener.java b/src/main/java/org/jabref/gui/GlobalFocusListener.java deleted file mode 100644 index 865de194b33..00000000000 --- a/src/main/java/org/jabref/gui/GlobalFocusListener.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.jabref.gui; - -import java.awt.Component; -import java.awt.event.FocusEvent; -import java.awt.event.FocusListener; - -import javax.swing.JComponent; - -public class GlobalFocusListener implements FocusListener { - private Component focused; - - @Override - public void focusGained(FocusEvent e) { - if (!e.isTemporary()) { - focused = (Component) e.getSource(); - } - } - - @Override - public void focusLost(FocusEvent e) { - // Do nothing - } - - public JComponent getFocused() { - return (JComponent) focused; - } - - public void setFocused(Component c) { - focused = c; - } -} diff --git a/src/main/java/org/jabref/gui/JabRefDialog.java b/src/main/java/org/jabref/gui/JabRefDialog.java index 3a70de1a368..3221956a393 100644 --- a/src/main/java/org/jabref/gui/JabRefDialog.java +++ b/src/main/java/org/jabref/gui/JabRefDialog.java @@ -1,7 +1,6 @@ package org.jabref.gui; import java.awt.Frame; -import java.awt.Window; import javax.swing.JDialog; @@ -9,30 +8,20 @@ public class JabRefDialog extends JDialog { - public JabRefDialog(Frame owner, boolean modal, Class clazz) { - super(owner, modal); - - trackDialogOpening(clazz); + public JabRefDialog(boolean modal, Class clazz) { + this("JabRef", modal, clazz); } public JabRefDialog(Class clazz) { - super(); - - trackDialogOpening(clazz); + this(true, clazz); } - public JabRefDialog(Frame owner, Class clazz) { - super(owner); - - trackDialogOpening(clazz); + public JabRefDialog(String title, Class clazz) { + this(title, true, clazz); } - public JabRefDialog(Frame owner, String title, Class clazz) { - this(owner, title, true, clazz); - } - - public JabRefDialog(Frame owner, String title, boolean modal, Class clazz) { - super(owner, title, modal); + public JabRefDialog(String title, boolean modal, Class clazz) { + super((Frame) null, title, modal); trackDialogOpening(clazz); } @@ -47,13 +36,19 @@ public JabRefDialog(java.awt.Dialog owner, String titl trackDialogOpening(clazz); } - public JabRefDialog(Window owner, String title, Class clazz) { - super(owner, title); - - trackDialogOpening(clazz); - } - private void trackDialogOpening(Class clazz) { Globals.getTelemetryClient().ifPresent(client -> client.trackPageView(clazz.getName())); } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + + if (visible) { + // FIXME: Ugly hack to ensure that new dialogs are not hidden behind the main window + setAlwaysOnTop(true); + requestFocus(); + setAlwaysOnTop(false); + } + } } diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 0efa554e479..ff496a25e09 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -1,29 +1,12 @@ package org.jabref.gui; -import java.awt.BorderLayout; import java.awt.Component; -import java.awt.Container; -import java.awt.Cursor; -import java.awt.FlowLayout; -import java.awt.Frame; -import java.awt.GraphicsEnvironment; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; import java.awt.Window; -import java.awt.event.ActionEvent; -import java.awt.event.KeyAdapter; -import java.awt.event.MouseAdapter; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -31,95 +14,105 @@ import java.util.Objects; import java.util.Optional; import java.util.TimerTask; +import java.util.stream.Collectors; -import javax.swing.AbstractAction; import javax.swing.Action; -import javax.swing.Icon; -import javax.swing.JButton; -import javax.swing.JCheckBoxMenuItem; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JProgressBar; -import javax.swing.JSplitPane; -import javax.swing.JTabbedPane; -import javax.swing.JToggleButton; -import javax.swing.KeyStroke; -import javax.swing.MenuElement; -import javax.swing.SwingConstants; import javax.swing.SwingUtilities; -import javax.swing.TransferHandler; -import javax.swing.UIManager; -import javax.swing.WindowConstants; import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener; +import javafx.geometry.Orientation; +import javafx.scene.Node; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuBar; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.Separator; +import javafx.scene.control.SeparatorMenuItem; +import javafx.scene.control.SplitPane; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.control.TextInputControl; +import javafx.scene.control.ToolBar; +import javafx.scene.control.Tooltip; +import javafx.scene.input.DataFormat; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.TransferMode; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; +import javafx.stage.Stage; +import javafx.util.Duration; import org.jabref.Globals; import org.jabref.JabRefExecutorService; +import org.jabref.gui.actions.ActionFactory; import org.jabref.gui.actions.Actions; import org.jabref.gui.actions.AutoLinkFilesAction; -import org.jabref.gui.actions.ConnectToSharedDatabaseAction; +import org.jabref.gui.actions.BibtexKeyPatternAction; +import org.jabref.gui.actions.ConnectToSharedDatabaseCommand; +import org.jabref.gui.actions.CopyFilesAction; +import org.jabref.gui.actions.CustomizeKeyBindingAction; +import org.jabref.gui.actions.EditExternalFileTypesAction; import org.jabref.gui.actions.ErrorConsoleAction; -import org.jabref.gui.actions.IntegrityCheckAction; +import org.jabref.gui.actions.LibraryPropertiesAction; import org.jabref.gui.actions.LookupIdentifierAction; -import org.jabref.gui.actions.ManageKeywordsAction; -import org.jabref.gui.actions.MassSetFieldAction; -import org.jabref.gui.actions.MnemonicAwareAction; +import org.jabref.gui.actions.ManageContentSelectorAction; +import org.jabref.gui.actions.ManageCustomExportsAction; +import org.jabref.gui.actions.ManageCustomImportsAction; +import org.jabref.gui.actions.ManageJournalsAction; import org.jabref.gui.actions.NewDatabaseAction; import org.jabref.gui.actions.NewEntryAction; -import org.jabref.gui.actions.NewSubDatabaseAction; +import org.jabref.gui.actions.NewEntryFromPlainTextAction; +import org.jabref.gui.actions.NewSubLibraryAction; +import org.jabref.gui.actions.OldDatabaseCommandWrapper; import org.jabref.gui.actions.OpenBrowserAction; import org.jabref.gui.actions.SearchForUpdateAction; -import org.jabref.gui.actions.SortTabsAction; -import org.jabref.gui.bibtexkeypattern.BibtexKeyPatternDialog; -import org.jabref.gui.copyfiles.CopyFilesAction; -import org.jabref.gui.customentrytypes.EntryCustomizationDialog; -import org.jabref.gui.dbproperties.DatabasePropertiesDialog; +import org.jabref.gui.actions.SetupGeneralFieldsAction; +import org.jabref.gui.actions.ShowDocumentViewerAction; +import org.jabref.gui.actions.ShowPreferencesAction; +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.gui.actions.StandardActions; import org.jabref.gui.dialogs.AutosaveUIManager; -import org.jabref.gui.documentviewer.ShowDocumentViewerAction; -import org.jabref.gui.exporter.ExportAction; -import org.jabref.gui.exporter.ExportCustomizationDialog; +import org.jabref.gui.edit.ManageKeywordsAction; +import org.jabref.gui.edit.MassSetFieldsAction; +import org.jabref.gui.exporter.ExportCommand; +import org.jabref.gui.exporter.ExportToClipboardAction; import org.jabref.gui.exporter.SaveAllAction; import org.jabref.gui.exporter.SaveDatabaseAction; -import org.jabref.gui.externalfiletype.ExternalFileTypeEditor; -import org.jabref.gui.groups.EntryTableTransferHandler; -import org.jabref.gui.groups.GroupSidePane; +import org.jabref.gui.externalfiles.FindUnlinkedFilesAction; +import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.help.AboutAction; import org.jabref.gui.help.HelpAction; -import org.jabref.gui.importer.ImportCustomizationDialog; -import org.jabref.gui.importer.ImportFormats; -import org.jabref.gui.importer.ImportInspectionDialog; +import org.jabref.gui.importer.ImportCommand; +import org.jabref.gui.importer.ImportEntriesDialog; import org.jabref.gui.importer.actions.OpenDatabaseAction; -import org.jabref.gui.importer.fetcher.GeneralFetcher; -import org.jabref.gui.journals.ManageJournalsAction; +import org.jabref.gui.integrity.IntegrityCheckAction; import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.gui.keyboard.KeyBindingAction; -import org.jabref.gui.menus.ChangeEntryTypeMenu; import org.jabref.gui.menus.FileHistoryMenu; -import org.jabref.gui.menus.RightClickMenu; -import org.jabref.gui.openoffice.OpenOfficePanel; -import org.jabref.gui.openoffice.OpenOfficeSidePanel; -import org.jabref.gui.preftabs.PreferencesDialog; -import org.jabref.gui.protectedterms.ProtectedTermsDialog; +import org.jabref.gui.mergeentries.MergeEntriesAction; +import org.jabref.gui.metadata.BibtexStringEditorAction; +import org.jabref.gui.metadata.PreambleEditor; +import org.jabref.gui.protectedterms.ManageProtectedTermsAction; import org.jabref.gui.push.PushToApplicationButton; import org.jabref.gui.push.PushToApplications; import org.jabref.gui.search.GlobalSearchBar; -import org.jabref.gui.specialfields.SpecialFieldDropDown; -import org.jabref.gui.specialfields.SpecialFieldValueViewModel; +import org.jabref.gui.specialfields.SpecialFieldMenuItemFactory; +import org.jabref.gui.undo.CountingUndoManager; +import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.DefaultTaskExecutor; -import org.jabref.gui.util.WindowLocation; -import org.jabref.gui.worker.MarkEntriesAction; import org.jabref.logic.autosaveandbackup.AutosaveManager; import org.jabref.logic.autosaveandbackup.BackupManager; -import org.jabref.logic.help.HelpFile; import org.jabref.logic.importer.IdFetcher; +import org.jabref.logic.importer.OpenDatabase; import org.jabref.logic.importer.OutputPrinter; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.WebFetchers; @@ -134,8 +127,7 @@ import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.database.shared.DatabaseLocation; import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.BibtexEntryTypes; -import org.jabref.model.entry.EntryType; +import org.jabref.model.entry.BiblatexEntryTypes; import org.jabref.model.entry.FieldName; import org.jabref.model.entry.specialfields.SpecialField; import org.jabref.preferences.JabRefPreferences; @@ -143,6 +135,12 @@ import org.jabref.preferences.SearchPreferences; import com.google.common.eventbus.Subscribe; +import com.jfoenix.controls.JFXSnackbar; +import com.jfoenix.controls.JFXSnackbar.SnackbarEvent; +import com.jfoenix.controls.JFXSnackbarLayout; +import org.eclipse.fx.ui.controls.tabpane.DndTabPane; +import org.eclipse.fx.ui.controls.tabpane.DndTabPaneFactory; +import org.fxmisc.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import osx.macadapter.MacAdapter; @@ -150,290 +148,21 @@ /** * The main window of the application. */ -public class JabRefFrame extends JFrame implements OutputPrinter { - private static final Logger LOGGER = LoggerFactory.getLogger(JabRefFrame.class); +public class JabRefFrame extends BorderPane implements OutputPrinter { // Frame titles. - private static final String FRAME_TITLE = "JabRef"; - private static final String ELLIPSES = "..."; - public final AbstractAction nextTab = new ChangeTabAction(true); - public final AbstractAction prevTab = new ChangeTabAction(false); - private final JSplitPane splitPane = new JSplitPane(); + public static final String FRAME_TITLE = "JabRef"; + + private static final Logger LOGGER = LoggerFactory.getLogger(JabRefFrame.class); + private static final Duration TOAST_MESSAGE_DISPLAY_TIME = Duration.millis(3000); + + private final SplitPane splitPane = new SplitPane(); private final JabRefPreferences prefs = Globals.prefs; - private final Insets marg = new Insets(1, 0, 2, 0); - private final IntegrityCheckAction checkIntegrity = new IntegrityCheckAction(this); - private final ToolBar tlb = new ToolBar(); private final GlobalSearchBar globalSearchBar = new GlobalSearchBar(this); - private final JMenuBar mb = new JMenuBar(); - private final JLabel statusLine = new JLabel("", SwingConstants.LEFT); - private final JLabel statusLabel = new JLabel( - Localization.lang("Status") - + ':', SwingConstants.LEFT); - private final JProgressBar progressBar = new JProgressBar(); + private final JFXSnackbar statusLine = new JFXSnackbar(this); + private final ProgressBar progressBar = new ProgressBar(); private final FileHistoryMenu fileHistory = new FileHistoryMenu(prefs, this); - private final OpenDatabaseAction open = new OpenDatabaseAction(this, true); - private final EditModeAction editModeAction = new EditModeAction(); - - // Here we instantiate menu/toolbar actions. Actions regarding - // the currently open database are defined as a GeneralAction - // with a unique command string. This causes the appropriate - // BasePanel's runCommand() method to be called with that command. - // Note: GeneralAction's constructor automatically gets translations - // for the name and message strings. - private final AbstractAction quit = new CloseAction(); - private final AbstractAction keyBindingAction = new KeyBindingAction(); - private final AbstractAction newBibtexDatabaseAction = new NewDatabaseAction(this, BibDatabaseMode.BIBTEX); - private final AbstractAction newBiblatexDatabaseAction = new NewDatabaseAction(this, BibDatabaseMode.BIBLATEX); - private final AbstractAction connectToSharedDatabaseAction = new ConnectToSharedDatabaseAction(this); - private final AbstractAction newSubDatabaseAction = new NewSubDatabaseAction(this); - private final AbstractAction jabrefWebPageAction = new OpenBrowserAction("https://jabref.org", - Localization.menuTitle("Website"), Localization.lang("Opens JabRef's website"), - IconTheme.getImage("about"), IconTheme.getImage("about")); - private final AbstractAction jabrefFacebookAction = new OpenBrowserAction("https://www.facebook.com/JabRef/", - "Facebook", Localization.lang("Opens JabRef's Facebook page"), - IconTheme.JabRefIcon.FACEBOOK.getSmallIcon(), IconTheme.JabRefIcon.FACEBOOK.getIcon()); - private final AbstractAction jabrefTwitterAction = new OpenBrowserAction("https://twitter.com/jabref_org", - "Twitter", Localization.lang("Opens JabRef's Twitter page"), - IconTheme.JabRefIcon.TWITTER.getSmallIcon(), IconTheme.JabRefIcon.TWITTER.getIcon()); - private final AbstractAction jabrefBlogAction = new OpenBrowserAction("https://blog.jabref.org/", - Localization.menuTitle("Blog"), Localization.lang("Opens JabRef's blog"), - IconTheme.JabRefIcon.BLOG.getSmallIcon(), IconTheme.JabRefIcon.BLOG.getIcon()); - private final AbstractAction developmentVersionAction = new OpenBrowserAction("https://builds.jabref.org/master/", - Localization.menuTitle("Development version"), - Localization.lang("Opens a link where the current development version can be downloaded")); - private final AbstractAction changeLogAction = new OpenBrowserAction( - "https://github.com/JabRef/jabref/blob/master/CHANGELOG.md", Localization.menuTitle("View change log"), - Localization.lang("See what has been changed in the JabRef versions")); - private final AbstractAction forkMeOnGitHubAction = new OpenBrowserAction("https://github.com/JabRef/jabref", - Localization.menuTitle("Fork me on GitHub"), Localization.lang("Opens JabRef's GitHub page"), IconTheme.JabRefIcon.GITHUB.getSmallIcon(), IconTheme.JabRefIcon.GITHUB.getIcon()); - private final AbstractAction donationAction = new OpenBrowserAction("https://donations.jabref.org", - Localization.menuTitle("Donate to JabRef"), Localization.lang("Donate to JabRef"), IconTheme.JabRefIcon.DONATE.getSmallIcon(), IconTheme.JabRefIcon.DONATE.getIcon()); - private final AbstractAction openForumAction = new OpenBrowserAction("http://discourse.jabref.org/", - Localization.menuTitle("Online help forum"), Localization.lang("Online help forum"), IconTheme.JabRefIcon.FORUM.getSmallIcon(), IconTheme.JabRefIcon.FORUM.getIcon()); - private final AbstractAction help = new HelpAction(Localization.menuTitle("Online help"), Localization.lang("Online help"), - HelpFile.CONTENTS, Globals.getKeyPrefs().getKey(KeyBinding.HELP)); - private final AbstractAction about = new AboutAction(Localization.menuTitle("About JabRef"), Localization.lang("About JabRef"), - IconTheme.getImage("about")); - private final AbstractAction editEntry = new GeneralAction(Actions.EDIT, Localization.menuTitle("Edit entry"), - Localization.lang("Edit entry"), Globals.getKeyPrefs().getKey(KeyBinding.EDIT_ENTRY), IconTheme.JabRefIcon.EDIT_ENTRY.getIcon()); - private final AbstractAction focusTable = new GeneralAction(Actions.FOCUS_TABLE, - Localization.menuTitle("Focus entry table"), - Localization.lang("Move the keyboard focus to the entry table"), Globals.getKeyPrefs().getKey(KeyBinding.FOCUS_ENTRY_TABLE)); - private final AbstractAction save = new GeneralAction(Actions.SAVE, Localization.menuTitle("Save library"), - Localization.lang("Save library"), Globals.getKeyPrefs().getKey(KeyBinding.SAVE_DATABASE), IconTheme.JabRefIcon.SAVE.getIcon()); - private final AbstractAction saveAs = new GeneralAction(Actions.SAVE_AS, - Localization.menuTitle("Save library as..."), Localization.lang("Save library as..."), - Globals.getKeyPrefs().getKey(KeyBinding.SAVE_DATABASE_AS)); - private final AbstractAction saveAll = new SaveAllAction(JabRefFrame.this); - private final AbstractAction saveSelectedAs = new GeneralAction(Actions.SAVE_SELECTED_AS, - Localization.menuTitle("Save selected as..."), Localization.lang("Save selected as...")); - private final AbstractAction saveSelectedAsPlain = new GeneralAction(Actions.SAVE_SELECTED_AS_PLAIN, - Localization.menuTitle("Save selected as plain BibTeX..."), - Localization.lang("Save selected as plain BibTeX...")); - private final AbstractAction importCurrent = ImportFormats.getImportAction(this, false); - private final AbstractAction importNew = ImportFormats.getImportAction(this, true); - private final AbstractAction sortTabs = new SortTabsAction(this); - private final AbstractAction undo = new GeneralAction(Actions.UNDO, Localization.menuTitle("Undo"), - Localization.lang("Undo"), Globals.getKeyPrefs().getKey(KeyBinding.UNDO), IconTheme.JabRefIcon.UNDO.getIcon()); - private final AbstractAction redo = new GeneralAction(Actions.REDO, Localization.menuTitle("Redo"), - Localization.lang("Redo"), Globals.getKeyPrefs().getKey(KeyBinding.REDO), IconTheme.JabRefIcon.REDO.getIcon()); - private final AbstractAction forward = new GeneralAction(Actions.FORWARD, Localization.menuTitle("Forward"), - Localization.lang("Forward"), Globals.getKeyPrefs().getKey(KeyBinding.FORWARD), IconTheme.JabRefIcon.RIGHT.getIcon()); - private final AbstractAction back = new GeneralAction(Actions.BACK, Localization.menuTitle("Back"), - Localization.lang("Back"), Globals.getKeyPrefs().getKey(KeyBinding.BACK), IconTheme.JabRefIcon.LEFT.getIcon()); - private final AbstractAction deleteEntry = new GeneralAction(Actions.DELETE, Localization.menuTitle("Delete entry"), - Localization.lang("Delete entry"), Globals.getKeyPrefs().getKey(KeyBinding.DELETE_ENTRY), IconTheme.JabRefIcon.DELETE_ENTRY.getIcon()); - private final AbstractAction copy = new EditAction(Actions.COPY, Localization.menuTitle("Copy"), - Localization.lang("Copy"), Globals.getKeyPrefs().getKey(KeyBinding.COPY), IconTheme.JabRefIcon.COPY.getIcon()); - private final AbstractAction paste = new EditAction(Actions.PASTE, Localization.menuTitle("Paste"), - Localization.lang("Paste"), Globals.getKeyPrefs().getKey(KeyBinding.PASTE), IconTheme.JabRefIcon.PASTE.getIcon()); - private final AbstractAction cut = new EditAction(Actions.CUT, Localization.menuTitle("Cut"), - Localization.lang("Cut"), Globals.getKeyPrefs().getKey(KeyBinding.CUT), IconTheme.JabRefIcon.CUT.getIcon()); - private final AbstractAction openConsole = new GeneralAction(Actions.OPEN_CONSOLE, - Localization.menuTitle("Open terminal here"), - Localization.lang("Open terminal here"), - Globals.getKeyPrefs().getKey(KeyBinding.OPEN_CONSOLE), - IconTheme.JabRefIcon.CONSOLE.getIcon()); - private final AbstractAction pullChangesFromSharedDatabase = new GeneralAction(Actions.PULL_CHANGES_FROM_SHARED_DATABASE, - Localization.menuTitle("Pull changes from shared database"), - Localization.lang("Pull changes from shared database"), - Globals.getKeyPrefs().getKey(KeyBinding.PULL_CHANGES_FROM_SHARED_DATABASE), - IconTheme.JabRefIcon.PULL.getIcon()); - private final AbstractAction mark = new GeneralAction(Actions.MARK_ENTRIES, Localization.menuTitle("Mark entries"), - Localization.lang("Mark entries"), Globals.getKeyPrefs().getKey(KeyBinding.MARK_ENTRIES), IconTheme.JabRefIcon.MARK_ENTRIES.getIcon()); - private final JMenu markSpecific = JabRefFrame.subMenu(Localization.menuTitle("Mark specific color")); - private final AbstractAction unmark = new GeneralAction(Actions.UNMARK_ENTRIES, - Localization.menuTitle("Unmark entries"), Localization.lang("Unmark entries"), - Globals.getKeyPrefs().getKey(KeyBinding.UNMARK_ENTRIES), IconTheme.JabRefIcon.UNMARK_ENTRIES.getIcon()); - private final AbstractAction unmarkAll = new GeneralAction(Actions.UNMARK_ALL, Localization.menuTitle("Unmark all")); - private final AbstractAction toggleRelevance = new GeneralAction( - new SpecialFieldValueViewModel(SpecialField.RELEVANCE.getValues().get(0)).getActionName(), - new SpecialFieldValueViewModel(SpecialField.RELEVANCE.getValues().get(0)).getMenuString(), - new SpecialFieldValueViewModel(SpecialField.RELEVANCE.getValues().get(0)).getToolTipText(), - IconTheme.JabRefIcon.RELEVANCE.getIcon()); - private final AbstractAction toggleQualityAssured = new GeneralAction( - new SpecialFieldValueViewModel(SpecialField.QUALITY.getValues().get(0)).getActionName(), - new SpecialFieldValueViewModel(SpecialField.QUALITY.getValues().get(0)).getMenuString(), - new SpecialFieldValueViewModel(SpecialField.QUALITY.getValues().get(0)).getToolTipText(), - IconTheme.JabRefIcon.QUALITY_ASSURED.getIcon()); - private final AbstractAction togglePrinted = new GeneralAction( - new SpecialFieldValueViewModel(SpecialField.PRINTED.getValues().get(0)).getActionName(), - new SpecialFieldValueViewModel(SpecialField.PRINTED.getValues().get(0)).getMenuString(), - new SpecialFieldValueViewModel(SpecialField.PRINTED.getValues().get(0)).getToolTipText(), - IconTheme.JabRefIcon.PRINTED.getIcon()); - private final AbstractAction normalSearch = new GeneralAction(Actions.SEARCH, Localization.menuTitle("Search"), - Localization.lang("Search"), Globals.getKeyPrefs().getKey(KeyBinding.SEARCH), IconTheme.JabRefIcon.SEARCH.getIcon()); - private final AbstractAction manageSelectors = new GeneralAction(Actions.MANAGE_SELECTORS, - Localization.menuTitle("Manage content selectors")); - private final AbstractAction copyPreview = new GeneralAction(Actions.COPY_CITATION_HTML, Localization.lang("Copy preview"), - Globals.getKeyPrefs().getKey(KeyBinding.COPY_PREVIEW)); - private final AbstractAction copyTitle = new GeneralAction(Actions.COPY_TITLE, Localization.menuTitle("Copy title"), - Globals.getKeyPrefs().getKey(KeyBinding.COPY_TITLE)); - private final AbstractAction copyKey = new GeneralAction(Actions.COPY_KEY, Localization.menuTitle("Copy BibTeX key"), - Globals.getKeyPrefs().getKey(KeyBinding.COPY_BIBTEX_KEY)); - private final AbstractAction copyCiteKey = new GeneralAction(Actions.COPY_CITE_KEY, Localization.menuTitle( - "Copy \\cite{BibTeX key}"), - Globals.getKeyPrefs().getKey(KeyBinding.COPY_CITE_BIBTEX_KEY)); - private final AbstractAction copyKeyAndTitle = new GeneralAction(Actions.COPY_KEY_AND_TITLE, - Localization.menuTitle("Copy BibTeX key and title"), - Globals.getKeyPrefs().getKey(KeyBinding.COPY_BIBTEX_KEY_AND_TITLE)); - private final AbstractAction copyKeyAndLink = new GeneralAction(Actions.COPY_KEY_AND_LINK, - Localization.menuTitle("Copy BibTeX key and link"), - Globals.getKeyPrefs().getKey(KeyBinding.COPY_BIBTEX_KEY_AND_LINK)); - private final AbstractAction mergeDatabaseAction = new GeneralAction(Actions.MERGE_DATABASE, - Localization.menuTitle("Append library"), - Localization.lang("Append contents from a BibTeX library into the currently viewed library")); - private final AbstractAction selectAll = new GeneralAction(Actions.SELECT_ALL, Localization.menuTitle("Select all"), - Globals.getKeyPrefs().getKey(KeyBinding.SELECT_ALL)); - private final AbstractAction replaceAll = new GeneralAction(Actions.REPLACE_ALL, - Localization.menuTitle("Replace string") + ELLIPSES, Globals.getKeyPrefs().getKey(KeyBinding.REPLACE_STRING)); - private final AbstractAction editPreamble = new GeneralAction(Actions.EDIT_PREAMBLE, - Localization.menuTitle("Edit preamble"), - Localization.lang("Edit preamble")); - private final AbstractAction editStrings = new GeneralAction(Actions.EDIT_STRINGS, - Localization.menuTitle("Edit strings"), - Localization.lang("Edit strings"), - Globals.getKeyPrefs().getKey(KeyBinding.EDIT_STRINGS), - IconTheme.JabRefIcon.EDIT_STRINGS.getIcon()); - private final AbstractAction customizeAction = new CustomizeEntryTypeAction(); - private final Action toggleToolbar = enableToggle(new AbstractAction(Localization.menuTitle("Hide/show toolbar")) { - - { - putValue(Action.SHORT_DESCRIPTION, Localization.lang("Hide/show toolbar")); - } - @Override - public void actionPerformed(ActionEvent e) { - tlb.setVisible(!tlb.isVisible()); - } - }); - private final AbstractAction showPdvViewer = new ShowDocumentViewerAction(); - private final AbstractAction addToGroup = new GeneralAction(Actions.ADD_TO_GROUP, Localization.lang("Add to group") + ELLIPSES); - private final AbstractAction removeFromGroup = new GeneralAction(Actions.REMOVE_FROM_GROUP, - Localization.lang("Remove from group") + ELLIPSES); - private final AbstractAction moveToGroup = new GeneralAction(Actions.MOVE_TO_GROUP, Localization.lang("Move to group") + ELLIPSES); - private final Action togglePreview = enableToggle(new GeneralAction(Actions.TOGGLE_PREVIEW, - Localization.menuTitle("Toggle entry preview"), - Localization.lang("Toggle entry preview"), - Globals.getKeyPrefs().getKey(KeyBinding.TOGGLE_ENTRY_PREVIEW), - IconTheme.JabRefIcon.TOGGLE_ENTRY_PREVIEW.getIcon())); - private final AbstractAction nextPreviewStyle = new GeneralAction(Actions.NEXT_PREVIEW_STYLE, - Localization.menuTitle("Next preview layout"), - Globals.getKeyPrefs().getKey(KeyBinding.NEXT_PREVIEW_LAYOUT)); - private final AbstractAction previousPreviewStyle = new GeneralAction(Actions.PREVIOUS_PREVIEW_STYLE, - Localization.menuTitle("Previous preview layout"), - Globals.getKeyPrefs().getKey(KeyBinding.PREVIOUS_PREVIEW_LAYOUT)); - private final AbstractAction makeKeyAction = new GeneralAction(Actions.MAKE_KEY, - Localization.menuTitle("Autogenerate BibTeX keys"), - Localization.lang("Autogenerate BibTeX keys"), - Globals.getKeyPrefs().getKey(KeyBinding.AUTOGENERATE_BIBTEX_KEYS), - IconTheme.JabRefIcon.MAKE_KEY.getIcon()); - private final AbstractAction writeXmpAction = new GeneralAction(Actions.WRITE_XMP, - Localization.menuTitle("Write XMP-metadata to PDFs"), - Localization.lang("Will write XMP-metadata to the PDFs linked from selected entries."), - Globals.getKeyPrefs().getKey(KeyBinding.WRITE_XMP)); - private final AbstractAction openFolder = new GeneralAction(Actions.OPEN_FOLDER, - Localization.menuTitle("Open folder"), Localization.lang("Open folder"), - Globals.getKeyPrefs().getKey(KeyBinding.OPEN_FOLDER)); - private final AbstractAction openFile = new GeneralAction(Actions.OPEN_EXTERNAL_FILE, - Localization.menuTitle("Open file"), - Localization.lang("Open file"), - Globals.getKeyPrefs().getKey(KeyBinding.OPEN_FILE), - IconTheme.JabRefIcon.FILE.getIcon()); - private final AbstractAction openUrl = new GeneralAction(Actions.OPEN_URL, - Localization.menuTitle("Open URL or DOI"), - Localization.lang("Open URL or DOI"), - Globals.getKeyPrefs().getKey(KeyBinding.OPEN_URL_OR_DOI), - IconTheme.JabRefIcon.WWW.getIcon()); - private final AbstractAction dupliCheck = new GeneralAction(Actions.DUPLI_CHECK, - Localization.menuTitle("Find duplicates"), IconTheme.JabRefIcon.FIND_DUPLICATES.getIcon()); - private final AbstractAction plainTextImport = new GeneralAction(Actions.PLAIN_TEXT_IMPORT, - Localization.menuTitle("New entry from plain text") + ELLIPSES, - Globals.getKeyPrefs().getKey(KeyBinding.NEW_FROM_PLAIN_TEXT)); - private final AbstractAction customExpAction = new CustomizeExportsAction(); - private final AbstractAction customImpAction = new CustomizeImportsAction(); - private final AbstractAction customFileTypesAction = ExternalFileTypeEditor.getAction(this); - private final AbstractAction exportToClipboard = new GeneralAction(Actions.EXPORT_TO_CLIPBOARD, - Localization.menuTitle("Export selected entries to clipboard"), - IconTheme.JabRefIcon.EXPORT_TO_CLIPBOARD.getIcon()); - private final AbstractAction autoSetFile = new GeneralAction(Actions.AUTO_SET_FILE, - Localization.lang("Synchronize file links") + ELLIPSES, - Globals.getKeyPrefs().getKey(KeyBinding.SYNCHRONIZE_FILES)); - private final AbstractAction abbreviateMedline = new GeneralAction(Actions.ABBREVIATE_MEDLINE, - Localization.menuTitle("Abbreviate journal names (MEDLINE)"), - Localization.lang("Abbreviate journal names of the selected entries (MEDLINE abbreviation)")); - private final AbstractAction abbreviateIso = new GeneralAction(Actions.ABBREVIATE_ISO, - Localization.menuTitle("Abbreviate journal names (ISO)"), - Localization.lang("Abbreviate journal names of the selected entries (ISO abbreviation)"), - Globals.getKeyPrefs().getKey(KeyBinding.ABBREVIATE)); - private final AbstractAction unabbreviate = new GeneralAction(Actions.UNABBREVIATE, - Localization.menuTitle("Unabbreviate journal names"), - Localization.lang("Unabbreviate journal names of the selected entries"), - Globals.getKeyPrefs().getKey(KeyBinding.UNABBREVIATE)); - private final AbstractAction exportLinkedFiles = new CopyFilesAction(); - private final AbstractAction manageJournals = new ManageJournalsAction(); - private final AbstractAction databaseProperties = new DatabasePropertiesAction(); - private final AbstractAction bibtexKeyPattern = new BibtexKeyPatternAction(); - private final AbstractAction errorConsole = new ErrorConsoleAction(); - private final AbstractAction cleanupEntries = new GeneralAction(Actions.CLEANUP, - Localization.menuTitle("Cleanup entries") + ELLIPSES, - Localization.lang("Cleanup entries"), - Globals.getKeyPrefs().getKey(KeyBinding.CLEANUP), - IconTheme.JabRefIcon.CLEANUP_ENTRIES.getIcon()); - private final AbstractAction mergeEntries = new GeneralAction(Actions.MERGE_ENTRIES, - Localization.menuTitle("Merge entries") + ELLIPSES, - Localization.lang("Merge entries"), - IconTheme.JabRefIcon.MERGE_ENTRIES.getIcon()); - private final AbstractAction downloadFullText = new GeneralAction(Actions.DOWNLOAD_FULL_TEXT, - Localization.menuTitle("Look up full text documents"), - Globals.getKeyPrefs().getKey(KeyBinding.DOWNLOAD_FULL_TEXT)); - private final AbstractAction increaseFontSize = new IncreaseTableFontSizeAction(); - private final AbstractAction defaultFontSize = new DefaultTableFontSizeAction(); - private final AbstractAction decreseFontSize = new DecreaseTableFontSizeAction(); - private final AbstractAction resolveDuplicateKeys = new GeneralAction(Actions.RESOLVE_DUPLICATE_KEYS, - Localization.menuTitle("Resolve duplicate BibTeX keys"), - Localization.lang("Find and remove duplicate BibTeX keys"), - Globals.getKeyPrefs().getKey(KeyBinding.RESOLVE_DUPLICATE_BIBTEX_KEYS)); - private final AbstractAction sendAsEmail = new GeneralAction(Actions.SEND_AS_EMAIL, - Localization.lang("Send as email"), IconTheme.JabRefIcon.EMAIL.getIcon()); - private final MassSetFieldAction massSetField = new MassSetFieldAction(this); - private final ManageKeywordsAction manageKeywords = new ManageKeywordsAction(this); - private final JMenu lookupIdentifiers = JabRefFrame.subMenu(Localization.menuTitle("Look up document identifier...")); - private final GeneralAction findUnlinkedFiles = new GeneralAction( - FindUnlinkedFilesDialog.ACTION_COMMAND, - FindUnlinkedFilesDialog.ACTION_MENU_TITLE, FindUnlinkedFilesDialog.ACTION_SHORT_DESCRIPTION, - Globals.getKeyPrefs().getKey(KeyBinding.FIND_UNLINKED_FILES) - ); - private final AutoLinkFilesAction autoLinkFile = new AutoLinkFilesAction(); - // The action for adding a new entry of unspecified type. - private final NewEntryAction newEntryAction = new NewEntryAction(this, Globals.getKeyPrefs().getKey(KeyBinding.NEW_ENTRY)); - private final List newSpecificEntryAction = getNewEntryActions(); - // The action for closing the current database and leaving the window open. - private final CloseDatabaseAction closeDatabaseAction = new CloseDatabaseAction(); - private final CloseAllDatabasesAction closeAllDatabasesAction = new CloseAllDatabasesAction(); - private final CloseOtherDatabasesAction closeOtherDatabasesAction = new CloseOtherDatabasesAction(); - // The action for opening the preferences dialog. - private final AbstractAction showPrefs = new ShowPrefsAction(); // Lists containing different subsets of actions for different purposes private final List specialFieldButtons = new LinkedList<>(); private final List openDatabaseOnlyActions = new LinkedList<>(); @@ -446,52 +175,18 @@ public void actionPerformed(ActionEvent e) { private final List oneEntryWithURLorDOIOnlyActions = new LinkedList<>(); private final List twoEntriesOnlyActions = new LinkedList<>(); private final List atLeastOneEntryActions = new LinkedList<>(); - private PreferencesDialog prefsDialog; - private int lastTabbedPanelSelectionIndex = -1; + private final Stage mainStage; // The sidepane manager takes care of populating the sidepane. private SidePaneManager sidePaneManager; - private JTabbedPane tabbedPane; // initialized at constructor - private final AbstractAction exportAll = ExportAction.getExportAction(this, false); - private final AbstractAction exportSelected = ExportAction.getExportAction(this, true); - /* References to the toggle buttons in the toolbar */ - private JToggleButton previewToggle; - private JMenu rankSubMenu; - private PushToApplicationButton pushExternalButton; + private TabPane tabbedPane; private PushToApplications pushApplications; - private GeneralFetcher generalFetcher; - private OpenOfficePanel openOfficePanel; - private GroupSidePane groupSidePane; - private int previousTabCount = -1; - private JMenu newSpec; + private final DialogService dialogService; + private SidePane sidePane; - public JabRefFrame() { + public JabRefFrame(Stage mainStage) { + this.mainStage = mainStage; + this.dialogService = new FXDialogService(mainStage); init(); - updateEnabledState(); - } - - private static Action enableToggle(Action a, boolean initialValue) { - // toggle only works correctly when the SELECTED_KEY is set to false or true explicitly upon start - a.putValue(Action.SELECTED_KEY, String.valueOf(initialValue)); - - return a; - } - - private static Action enableToggle(Action a) { - return enableToggle(a, false); - } - - public static JMenu subMenu(String name) { - int i = name.indexOf('&'); - JMenu res; - if (i >= 0) { - res = new JMenu(name.substring(0, i) + name.substring(i + 1)); - char mnemonic = Character.toUpperCase(name.charAt(i + 1)); - res.setMnemonic((int) mnemonic); - } else { - res = new JMenu(name); - } - - return res; } /** @@ -517,95 +212,57 @@ private static void setEnabled(List list, boolean enabled) { } } - private List getNewEntryActions() { - // only Bibtex - List actions = new ArrayList<>(); - for (EntryType type : BibtexEntryTypes.ALL) { - KeyStroke keyStroke = new ChangeEntryTypeMenu().entryShortCuts.get(type.getName()); - if (keyStroke == null) { - actions.add(new NewEntryAction(this, type.getName())); - } else { - actions.add(new NewEntryAction(this, type.getName(), keyStroke)); - } - } - return actions; - } - - private JPopupMenu tabPopupMenu() { - JPopupMenu popupMenu = new JPopupMenu(); - - // Close actions - JMenuItem close = new JMenuItem(Localization.lang("Close")); - JMenuItem closeOthers = new JMenuItem(Localization.lang("Close others")); - JMenuItem closeAll = new JMenuItem(Localization.lang("Close all")); - close.addActionListener(closeDatabaseAction); - closeOthers.addActionListener(closeOtherDatabasesAction); - closeAll.addActionListener(closeAllDatabasesAction); - popupMenu.add(close); - popupMenu.add(closeOthers); - popupMenu.add(closeAll); - - popupMenu.addSeparator(); - - JMenuItem databasePropertiesMenu = new JMenuItem(Localization.lang("Library properties")); - databasePropertiesMenu.addActionListener(this.databaseProperties); - popupMenu.add(databasePropertiesMenu); - - JMenuItem bibtexKeyPatternBtn = new JMenuItem(Localization.lang("BibTeX key patterns")); - bibtexKeyPatternBtn.addActionListener(bibtexKeyPattern); - popupMenu.add(bibtexKeyPatternBtn); - - return popupMenu; - } - private void init() { + sidePaneManager = new SidePaneManager(Globals.prefs, this); + sidePane = sidePaneManager.getPane(); - tabbedPane = new DragDropPopupPane(tabPopupMenu()); + Pane containerPane = DndTabPaneFactory.createDefaultDnDPane(DndTabPaneFactory.FeedbackType.MARKER, null); + tabbedPane = (DndTabPane) containerPane.getChildren().get(0); - MyGlassPane glassPane = new MyGlassPane(); - setGlassPane(glassPane); + initLayout(); - setTitle(FRAME_TITLE); - setIconImages(IconTheme.getLogoSet()); - setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - addWindowListener(new WindowAdapter() { + initActions(); - @Override - public void windowClosing(WindowEvent e) { + initKeyBindings(); - if (OS.OS_X) { - JabRefFrame.this.setVisible(false); - } else { - new CloseAction().actionPerformed(null); - } + tabbedPane.setOnDragOver(event -> { + if (event.getDragboard().hasFiles()) { + event.acceptTransferModes(TransferMode.COPY, TransferMode.MOVE, TransferMode.LINK); } }); - initSidePane(); - - initLayout(); + tabbedPane.setOnDragDropped(event -> { + boolean success = false; - initActions(); + if (event.getDragboard().hasContent(DataFormat.FILES)) { + List files = event.getDragboard().getFiles().stream().map(File::toPath).filter(FileUtil::isBibFile).collect(Collectors.toList()); + success = true; - // Show the toolbar if it was visible at last shutdown: - tlb.setVisible(Globals.prefs.getBoolean(JabRefPreferences.TOOLBAR_VISIBLE)); + for (Path file : files) { + ParserResult pr = OpenDatabase.loadDatabase(file.toString(), Globals.prefs.getImportFormatPreferences(), Globals.getFileUpdateMonitor()); + addParserResult(pr, true); + } + } - setBounds(GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds()); - WindowLocation pw = new WindowLocation(this, JabRefPreferences.POS_X, JabRefPreferences.POS_Y, JabRefPreferences.SIZE_X, - JabRefPreferences.SIZE_Y); - pw.displayWindowAtStoredLocation(); + event.setDropCompleted(success); + event.consume(); + }); - tabbedPane.setBorder(null); - tabbedPane.setForeground(GUIGlobals.INACTIVE_TABBED_COLOR); + //setBounds(GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds()); + //WindowLocation pw = new WindowLocation(this, JabRefPreferences.POS_X, JabRefPreferences.POS_Y, JabRefPreferences.SIZE_X, + // JabRefPreferences.SIZE_Y); + //pw.displayWindowAtStoredLocation(); /* * The following state listener makes sure focus is registered with the * correct database when the user switches tabs. Without this, * cut/paste/copy operations would some times occur in the wrong tab. */ - tabbedPane.addChangeListener(e -> { - - markActiveBasePanel(); + EasyBind.subscribe(tabbedPane.getSelectionModel().selectedItemProperty(), e -> { + if (e == null) { + Globals.stateManager.activeDatabaseProperty().setValue(Optional.empty()); + return; + } BasePanel currentBasePanel = getCurrentBasePanel(); if (currentBasePanel == null) { @@ -614,9 +271,7 @@ public void windowClosing(WindowEvent e) { // Poor-mans binding to global state // We need to invoke this in the JavaFX thread as all the listeners sit there - Platform.runLater(() -> - Globals.stateManager.activeDatabaseProperty().setValue(Optional.of(currentBasePanel.getBibDatabaseContext())) - ); + Platform.runLater(() -> Globals.stateManager.activeDatabaseProperty().setValue(Optional.of(currentBasePanel.getBibDatabaseContext()))); if (new SearchPreferences(Globals.prefs).isGlobalSearch()) { globalSearchBar.performSearch(); } else { @@ -628,19 +283,17 @@ public void windowClosing(WindowEvent e) { globalSearchBar.setSearchTerm(content); } - currentBasePanel.getPreviewPanel().updateLayout(); + currentBasePanel.getPreviewPanel().updateLayout(Globals.prefs.getPreviewPreferences()); + + // 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)); - groupSidePane.getToggleAction().setSelected(sidePaneManager.isComponentVisible(GroupSidePane.class)); - previewToggle.setSelected(Globals.prefs.getPreviewPreferences().isPreviewPanelEnabled()); - generalFetcher.getToggleAction().setSelected(sidePaneManager.isComponentVisible(GeneralFetcher.class)); - openOfficePanel.getToggleAction().setSelected(sidePaneManager.isComponentVisible(OpenOfficeSidePanel.class)); - Globals.getFocusListener().setFocused(currentBasePanel.getMainTable()); setWindowTitle(); - editModeAction.initName(); // Update search autocompleter with information for the correct database: currentBasePanel.updateSearchManager(); - // Set correct enabled state for Back and Forward actions: - currentBasePanel.setBackAndForwardEnabledState(); + currentBasePanel.getUndoManager().postUndoRedoEvent(); currentBasePanel.getMainTable().requestFocus(); }); @@ -659,6 +312,44 @@ public void windowClosing(WindowEvent e) { initShowTrackingNotification(); } + private void initKeyBindings() { + addEventFilter(KeyEvent.KEY_PRESSED, event -> { + Optional keyBinding = Globals.getKeyPrefs().mapToKeyBinding(event); + if (keyBinding.isPresent()) { + switch (keyBinding.get()) { + case FOCUS_ENTRY_TABLE: + getCurrentBasePanel().getMainTable().requestFocus(); + event.consume(); + break; + case NEXT_LIBRARY: + tabbedPane.getSelectionModel().selectNext(); + event.consume(); + break; + case PREVIOUS_LIBRARY: + tabbedPane.getSelectionModel().selectPrevious(); + event.consume(); + break; + case INCREASE_TABLE_FONT_SIZE: + increaseTableFontSize(); + event.consume(); + break; + case DECREASE_TABLE_FONT_SIZE: + decreaseTableFontSize(); + event.consume(); + break; + case DEFAULT_TABLE_FONT_SIZE: + setDefaultTableFontSize(); + event.consume(); + break; + case SEARCH: + getGlobalSearchBar().focus(); + break; + default: + } + } + }); + } + private void initShowTrackingNotification() { if (!Globals.prefs.shouldAskToCollectTelemetry()) { JabRefExecutorService.INSTANCE.submit(new TimerTask() { @@ -675,7 +366,6 @@ public void run() { private Void showTrackingNotification() { if (!Globals.prefs.shouldCollectTelemetry()) { - DialogService dialogService = new FXDialogService(); boolean shouldCollect = dialogService.showConfirmationDialogAndWait( Localization.lang("Telemetry: Help make JabRef better"), Localization.lang("To improve the user experience, we would like to collect anonymous statistics on the features you use. We will only record what features you access and how often you do it. We will neither collect any personal data nor the content of bibliographic items. If you choose to allow data collection, you can later disable it via Options -> Preferences -> General."), @@ -690,8 +380,11 @@ private Void showTrackingNotification() { } public void refreshTitleAndTabs() { - setWindowTitle(); - updateAllTabTitles(); + DefaultTaskExecutor.runInJavaFXThread(() -> { + + setWindowTitle(); + updateAllTabTitles(); + }); } /** @@ -702,7 +395,7 @@ public void setWindowTitle() { // no database open if (panel == null) { - setTitle(FRAME_TITLE); + //setTitle(FRAME_TITLE); return; } @@ -712,25 +405,17 @@ public void setWindowTitle() { if (panel.getBibDatabaseContext().getLocation() == DatabaseLocation.LOCAL) { String changeFlag = panel.isModified() && !isAutosaveEnabled ? "*" : ""; - String databaseFile = panel.getBibDatabaseContext().getDatabaseFile().map(File::getPath) - .orElse(GUIGlobals.UNTITLED_TITLE); - setTitle(FRAME_TITLE + " - " + databaseFile + changeFlag + modeInfo); + String databaseFile = panel.getBibDatabaseContext() + .getDatabaseFile() + .map(File::getPath) + .orElse(GUIGlobals.UNTITLED_TITLE); + //setTitle(FRAME_TITLE + " - " + databaseFile + changeFlag + modeInfo); } else if (panel.getBibDatabaseContext().getLocation() == DatabaseLocation.SHARED) { - setTitle(FRAME_TITLE + " - " + panel.getBibDatabaseContext().getDBMSSynchronizer().getDBName() + " [" - + Localization.lang("shared") + "]" + modeInfo); + //setTitle(FRAME_TITLE + " - " + panel.getBibDatabaseContext().getDBMSSynchronizer().getDBName() + " [" + // + Localization.lang("shared") + "]" + modeInfo); } } - private void initSidePane() { - sidePaneManager = new SidePaneManager(this); - - groupSidePane = new GroupSidePane(this, sidePaneManager); - openOfficePanel = new OpenOfficePanel(this, sidePaneManager); - generalFetcher = new GeneralFetcher(this, sidePaneManager); - - sidePaneManager.register(groupSidePane); - } - /** * The MacAdapter calls this method when a "BIB" file has been double-clicked from the Finder. */ @@ -740,27 +425,11 @@ public void openAction(String filePath) { getOpenDatabaseAction().openFile(file, true); } - // General info dialog. The MacAdapter calls this method when "About" - // is selected from the application menu. + /** + * The MacAdapter calls this method when "About" is selected from the application menu. + */ public void about() { - // reuse the normal about action - // null as parameter is OK as the code of actionPerformed does not rely on the data sent in the event. - about.actionPerformed(null); - } - - // General preferences dialog. The MacAdapter calls this method when "Preferences..." - // is selected from the application menu. - public void showPreferencesDialog() { - output(Localization.lang("Opening preferences...")); - if (prefsDialog == null) { - prefsDialog = new PreferencesDialog(JabRefFrame.this); - prefsDialog.setLocationRelativeTo(JabRefFrame.this); - } else { - prefsDialog.setValues(); - } - - prefsDialog.setVisible(true); - output(""); + HelpAction.getMainHelpPageCommand().execute(); } public JabRefPreferences prefs() { @@ -772,22 +441,12 @@ public JabRefPreferences prefs() { *

    * FIXME: Currently some threads remain and therefore hinder JabRef to be closed properly * - * @param filenames the filenames of all currently opened files - used for storing them if prefs openLastEdited is set to true + * @param filenames the filenames of all currently opened files - used for storing them if prefs openLastEdited is + * set to true */ private void tearDownJabRef(List filenames) { - Globals.stopBackgroundTasks(); - Globals.shutdownThreadPools(); - - dispose(); + //prefs.putBoolean(JabRefPreferences.WINDOW_MAXIMISED, getExtendedState() == Frame.MAXIMIZED_BOTH); - prefs.putBoolean(JabRefPreferences.WINDOW_MAXIMISED, getExtendedState() == Frame.MAXIMIZED_BOTH); - - prefs.putBoolean(JabRefPreferences.TOOLBAR_VISIBLE, tlb.isVisible()); - // Store divider location for side pane: - int width = splitPane.getDividerLocation(); - if (width > 0) { - prefs.putInt(JabRefPreferences.SIDE_PANE_WIDTH, width); - } if (prefs.getBoolean(JabRefPreferences.OPEN_LAST_EDITED)) { // Here we store the names of all current files. If // there is no current file, we remove any @@ -802,9 +461,6 @@ private void tearDownJabRef(List filenames) { } fileHistory.storeHistory(); - prefs.customExports.store(Globals.prefs); - prefs.customImports.store(); - prefs.flush(); // dispose all windows, even if they are not displayed anymore @@ -814,9 +470,9 @@ private void tearDownJabRef(List filenames) { } /** - * General info dialog. The MacAdapter calls this method when "Quit" - * is selected from the application menu, Cmd-Q is pressed, or "Quit" is selected from the Dock. - * The function returns a boolean indicating if quitting is ok or not. + * General info dialog. The MacAdapter calls this method when "Quit" is selected from the application menu, Cmd-Q + * is pressed, or "Quit" is selected from the Dock. The function returns a boolean indicating if quitting is ok or + * not. *

    * Non-OSX JabRef calls this when choosing "Quit" from the menu *

    @@ -825,98 +481,77 @@ private void tearDownJabRef(List filenames) { * @return true if the user chose to quit; false otherwise */ public boolean quit() { - // Ask here if the user really wants to close, if the base - // has not been saved since last save. - boolean close = true; - + // First ask if the user really wants to close, if the library has not been saved since last save. List filenames = new ArrayList<>(); - if (tabbedPane.getTabCount() > 0) { - for (int i = 0; i < tabbedPane.getTabCount(); i++) { - BibDatabaseContext context = getBasePanelAt(i).getBibDatabaseContext(); - - if (getBasePanelAt(i).isModified() && (context.getLocation() == DatabaseLocation.LOCAL)) { - tabbedPane.setSelectedIndex(i); - String filename = context.getDatabaseFile().map(File::getAbsolutePath).orElse(GUIGlobals.UNTITLED_TITLE); - int answer = showSaveDialog(filename); - - if ((answer == JOptionPane.CANCEL_OPTION) || - (answer == JOptionPane.CLOSED_OPTION)) { - return false; - } - if (answer == JOptionPane.YES_OPTION) { - // The user wants to save. - try { - //getCurrentBasePanel().runCommand("save"); - SaveDatabaseAction saveAction = new SaveDatabaseAction(getCurrentBasePanel()); - saveAction.runCommand(); - if (saveAction.isCanceled() || !saveAction.isSuccess()) { - // The action was either canceled or unsuccessful. - // Break! - output(Localization.lang("Unable to save library")); - close = false; - } - } catch (Throwable ex) { - // Something prevented the file - // from being saved. Break!!! - close = false; - break; - } - } - } else if (context.getLocation() == DatabaseLocation.SHARED) { - context.convertToLocalDatabase(); - context.getDBMSSynchronizer().closeSharedDatabase(); - context.clearDBMSSynchronizer(); + for (int i = 0; i < tabbedPane.getTabs().size(); i++) { + BasePanel panel = getBasePanelAt(i); + BibDatabaseContext context = panel.getBibDatabaseContext(); + + if (panel.isModified() && (context.getLocation() == DatabaseLocation.LOCAL)) { + tabbedPane.getSelectionModel().select(i); + if (!confirmClose(panel)) { + return false; } - AutosaveManager.shutdown(context); - BackupManager.shutdown(context); - context.getDatabaseFile().map(File::getAbsolutePath).ifPresent(filenames::add); + } else if (context.getLocation() == DatabaseLocation.SHARED) { + context.convertToLocalDatabase(); + context.getDBMSSynchronizer().closeSharedDatabase(); + context.clearDBMSSynchronizer(); } + AutosaveManager.shutdown(context); + BackupManager.shutdown(context); + context.getDatabaseFile().map(File::getAbsolutePath).ifPresent(filenames::add); } - if (close) { - for (int i = 0; i < tabbedPane.getTabCount(); i++) { - if (getBasePanelAt(i).isSaving()) { - // There is a database still being saved, so we need to wait. - WaitForSaveOperation w = new WaitForSaveOperation(this); - w.show(); // This method won't return until canceled or the save operation is done. - if (w.canceled()) { - return false; // The user clicked cancel. - } - } - } - - tearDownJabRef(filenames); - return true; - } + WaitForSaveFinishedDialog waitForSaveFinishedDialog = new WaitForSaveFinishedDialog(dialogService); + waitForSaveFinishedDialog.showAndWait(getBasePanelList()); - return false; + // Good bye! + tearDownJabRef(filenames); + Platform.exit(); + return true; } private void initLayout() { - setProgressBarVisible(false); - pushApplications = new PushToApplications(); - pushExternalButton = new PushToApplicationButton(this, pushApplications.getApplications()); - fillMenu(); - createToolBar(); - setJMenuBar(mb); - getContentPane().setLayout(new BorderLayout()); + pushApplications = new PushToApplications(this.getDialogService()); - JPanel toolbarPanel = new JPanel(new WrapLayout(FlowLayout.LEFT)); - toolbarPanel.add(tlb); - toolbarPanel.add(globalSearchBar); - getContentPane().add(toolbarPanel, BorderLayout.PAGE_START); + BorderPane head = new BorderPane(); + head.setTop(createMenu()); + head.setCenter(createToolbar()); + setTop(head); - splitPane.setDividerSize(2); - splitPane.setBorder(null); - splitPane.setRightComponent(tabbedPane); - splitPane.setLeftComponent(sidePaneManager.getPanel()); - getContentPane().add(splitPane, BorderLayout.CENTER); + SplitPane.setResizableWithParent(sidePane, Boolean.FALSE); + splitPane.getItems().addAll(sidePane, tabbedPane); - UIManager.put("TabbedPane.contentBorderInsets", new Insets(0, 0, 0, 0)); - sidePaneManager.updateView(); + // We need to wait with setting the divider since it gets reset a few times during the initial set-up + mainStage.showingProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, Boolean oldValue, Boolean showing) { + if (showing) { + setDividerPosition(); + + EasyBind.subscribe(sidePane.visibleProperty(), visible -> { + if (visible) { + if (!splitPane.getItems().contains(sidePane)) { + splitPane.getItems().add(0, sidePane); + setDividerPosition(); + } + } else { + splitPane.getItems().remove(sidePane); + } + }); + + mainStage.showingProperty().removeListener(this); + observable.removeListener(this); + } + } + }); + + setCenter(splitPane); + + /* GridBagLayout gbl = new GridBagLayout(); GridBagConstraints con = new GridBagConstraints(); con.fill = GridBagConstraints.BOTH; @@ -940,14 +575,73 @@ private void initLayout() { gbl.setConstraints(progressBar, con); status.add(progressBar); statusLabel.setForeground(GUIGlobals.ENTRY_EDITOR_LABEL_COLOR.darker()); - getContentPane().add(status, BorderLayout.PAGE_END); + */ + } + + private void setDividerPosition() { + splitPane.setDividerPositions(prefs.getDouble(JabRefPreferences.SIDE_PANE_WIDTH)); + if (!splitPane.getDividers().isEmpty()) { + EasyBind.subscribe(splitPane.getDividers().get(0).positionProperty(), + position -> prefs.putDouble(JabRefPreferences.SIDE_PANE_WIDTH, position.doubleValue())); + } + } + + private Node createToolbar() { + Pane leftSpacer = new Pane(); + HBox.setHgrow(leftSpacer, Priority.SOMETIMES); + Pane rightSpacer = new Pane(); + HBox.setHgrow(rightSpacer, Priority.SOMETIMES); - // Drag and drop for tabbedPane: - TransferHandler xfer = new EntryTableTransferHandler(null, this, null); - tabbedPane.setTransferHandler(xfer); - tlb.setTransferHandler(xfer); - mb.setTransferHandler(xfer); - sidePaneManager.getPanel().setTransferHandler(xfer); + ActionFactory factory = new ActionFactory(Globals.getKeyPrefs()); + + Button newLibrary; + if (Globals.prefs.getBoolean(JabRefPreferences.BIBLATEX_DEFAULT_MODE)) { + newLibrary = factory.createIconButton(StandardActions.NEW_LIBRARY_BIBLATEX, new NewDatabaseAction(this, BibDatabaseMode.BIBLATEX)); + } else { + newLibrary = factory.createIconButton(StandardActions.NEW_LIBRARY_BIBTEX, new NewDatabaseAction(this, BibDatabaseMode.BIBTEX)); + } + + HBox leftSide = new HBox( + newLibrary, + factory.createIconButton(StandardActions.OPEN_LIBRARY, new OpenDatabaseAction(this)), + factory.createIconButton(StandardActions.SAVE_LIBRARY, new OldDatabaseCommandWrapper(Actions.SAVE, this, Globals.stateManager)), + leftSpacer + ); + leftSide.setMinWidth(100); + leftSide.prefWidthProperty().bind(sidePane.widthProperty()); + leftSide.maxWidthProperty().bind(sidePane.widthProperty()); + PushToApplicationButton pushToExternal = new PushToApplicationButton(this, pushApplications.getApplications()); + HBox rightSide = new HBox( + factory.createIconButton(StandardActions.NEW_ARTICLE, new NewEntryAction(this, BiblatexEntryTypes.ARTICLE, dialogService, Globals.prefs)), + factory.createIconButton(StandardActions.DELETE_ENTRY, new OldDatabaseCommandWrapper(Actions.DELETE, this, Globals.stateManager)), + new Separator(Orientation.VERTICAL), + factory.createIconButton(StandardActions.UNDO, new OldDatabaseCommandWrapper(Actions.UNDO, this, Globals.stateManager)), + factory.createIconButton(StandardActions.REDO, new OldDatabaseCommandWrapper(Actions.REDO, this, Globals.stateManager)), + factory.createIconButton(StandardActions.CUT, new OldDatabaseCommandWrapper(Actions.CUT, this, Globals.stateManager)), + factory.createIconButton(StandardActions.COPY, new OldDatabaseCommandWrapper(Actions.COPY, this, Globals.stateManager)), + factory.createIconButton(StandardActions.PASTE, new OldDatabaseCommandWrapper(Actions.PASTE, this, Globals.stateManager)), + new Separator(Orientation.VERTICAL), + factory.createIconButton(pushToExternal.getMenuAction(), pushToExternal), + factory.createIconButton(StandardActions.GENERATE_CITE_KEYS, new OldDatabaseCommandWrapper(Actions.MAKE_KEY, this, Globals.stateManager)), + factory.createIconButton(StandardActions.CLEANUP_ENTRIES, new OldDatabaseCommandWrapper(Actions.CLEANUP, this, Globals.stateManager)), + new Separator(Orientation.VERTICAL), + factory.createIconButton(StandardActions.FORK_ME, new OpenBrowserAction("https://github.com/JabRef/jabref")), + factory.createIconButton(StandardActions.OPEN_FACEBOOK, new OpenBrowserAction("https://www.facebook.com/JabRef/")), + factory.createIconButton(StandardActions.OPEN_TWITTER, new OpenBrowserAction("https://twitter.com/jabref_org")) + ); + + HBox.setHgrow(globalSearchBar, Priority.ALWAYS); + + ToolBar toolBar = new ToolBar( + leftSide, + + globalSearchBar, + + rightSpacer, + rightSide); + toolBar.getStyleClass().add("mainToolbar"); + + return toolBar; } /** @@ -956,7 +650,7 @@ private void initLayout() { * @param i Index of base */ public BasePanel getBasePanelAt(int i) { - return (BasePanel) tabbedPane.getComponentAt(i); + return (BasePanel) tabbedPane.getTabs().get(i).getContent(); } /** @@ -965,491 +659,385 @@ public BasePanel getBasePanelAt(int i) { public List getBasePanelList() { List returnList = new ArrayList<>(); for (int i = 0; i < getBasePanelCount(); i++) { - returnList.add((BasePanel) tabbedPane.getComponentAt(i)); + returnList.add(getBasePanelAt(i)); } return returnList; } public void showBasePanelAt(int i) { - tabbedPane.setSelectedIndex(i); + tabbedPane.getSelectionModel().select(i); } public void showBasePanel(BasePanel bp) { - tabbedPane.setSelectedComponent(bp); + tabbedPane.getSelectionModel().select(getTab(bp)); } /** * Returns the currently viewed BasePanel. */ public BasePanel getCurrentBasePanel() { - if (tabbedPane == null) { + if ((tabbedPane == null) || (tabbedPane.getSelectionModel().getSelectedItem() == null)) { return null; } - return (BasePanel) tabbedPane.getSelectedComponent(); + return (BasePanel) tabbedPane.getSelectionModel().getSelectedItem().getContent(); } /** * @return the BasePanel count. */ public int getBasePanelCount() { - return tabbedPane.getComponentCount(); - } - - /** - * handle the color of active and inactive JTabbedPane tabs - */ - private void markActiveBasePanel() { - int now = tabbedPane.getSelectedIndex(); - int len = tabbedPane.getTabCount(); - if ((lastTabbedPanelSelectionIndex > -1) && (lastTabbedPanelSelectionIndex < len)) { - tabbedPane.setForegroundAt(lastTabbedPanelSelectionIndex, GUIGlobals.INACTIVE_TABBED_COLOR); - } - if ((now > -1) && (now < len)) { - tabbedPane.setForegroundAt(now, GUIGlobals.ACTIVE_TABBED_COLOR); - } - lastTabbedPanelSelectionIndex = now; + return tabbedPane.getTabs().size(); } - private int getTabIndex(JComponent comp) { - for (int i = 0; i < tabbedPane.getTabCount(); i++) { - if (tabbedPane.getComponentAt(i) == comp) { - return i; + private Tab getTab(BasePanel comp) { + for (Tab tab : tabbedPane.getTabs()) { + if (tab.getContent() == comp) { + return tab; } } - return -1; + return null; } - public JTabbedPane getTabbedPane() { + /** + * @deprecated do not operate on tabs but on BibDatabaseContexts + */ + @Deprecated + public TabPane getTabbedPane() { return tabbedPane; } - public void setTabTitle(JComponent comp, String title, String toolTip) { - int index = getTabIndex(comp); - tabbedPane.setTitleAt(index, title); - tabbedPane.setToolTipTextAt(index, toolTip); - } - - private void fillMenu() { - mb.setBorder(null); - JMenu file = JabRefFrame.subMenu(Localization.menuTitle("File")); - JMenu edit = JabRefFrame.subMenu(Localization.menuTitle("Edit")); - JMenu search = JabRefFrame.subMenu(Localization.menuTitle("Search")); - JMenu groups = JabRefFrame.subMenu(Localization.menuTitle("Groups")); - JMenu bibtex = JabRefFrame.subMenu("&BibTeX"); - JMenu quality = JabRefFrame.subMenu(Localization.menuTitle("Quality")); - JMenu view = JabRefFrame.subMenu(Localization.menuTitle("View")); - JMenu tools = JabRefFrame.subMenu(Localization.menuTitle("Tools")); - JMenu options = JabRefFrame.subMenu(Localization.menuTitle("Options")); - newSpec = JabRefFrame.subMenu(Localization.menuTitle("New entry by type...")); - JMenu helpMenu = JabRefFrame.subMenu(Localization.menuTitle("Help")); - - file.add(newBibtexDatabaseAction); - file.add(newBiblatexDatabaseAction); - file.add(getOpenDatabaseAction()); - file.add(mergeDatabaseAction); - file.add(save); - file.add(saveAs); - file.add(saveAll); - file.add(saveSelectedAs); - file.add(saveSelectedAsPlain); - file.addSeparator(); - file.add(importNew); - file.add(importCurrent); - file.add(exportAll); - file.add(exportSelected); - file.add(exportLinkedFiles); - file.addSeparator(); - file.add(connectToSharedDatabaseAction); - file.add(pullChangesFromSharedDatabase); - - file.addSeparator(); - file.add(databaseProperties); - file.add(editModeAction); - file.addSeparator(); - - file.add(fileHistory); - file.addSeparator(); - file.add(closeDatabaseAction); - file.add(quit); - mb.add(file); - - edit.add(undo); - edit.add(redo); - - edit.addSeparator(); - - edit.add(cut); - edit.add(copy); - edit.add(paste); - - edit.addSeparator(); - - edit.add(copyTitle); - edit.add(copyKey); - edit.add(copyCiteKey); - edit.add(copyKeyAndTitle); - edit.add(copyKeyAndLink); - edit.add(copyPreview); - edit.add(exportToClipboard); - edit.add(sendAsEmail); - - edit.addSeparator(); - edit.add(mark); - for (int i = 0; i < EntryMarker.MAX_MARKING_LEVEL; i++) { - markSpecific.add(new MarkEntriesAction(this, i).getMenuItem()); - } - edit.add(markSpecific); - edit.add(unmark); - edit.add(unmarkAll); - edit.addSeparator(); + 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")); + Menu edit = new Menu(Localization.lang("Edit")); + Menu library = new Menu(Localization.lang("Library")); + Menu quality = new Menu(Localization.lang("Quality")); + Menu view = new Menu(Localization.lang("View")); + Menu tools = new Menu(Localization.lang("Tools")); + Menu options = new Menu(Localization.lang("Options")); + Menu help = new Menu(Localization.lang("Help")); + + file.getItems().addAll( + factory.createSubMenu(StandardActions.NEW_LIBRARY, + factory.createMenuItem(StandardActions.NEW_LIBRARY_BIBTEX, new NewDatabaseAction(this, BibDatabaseMode.BIBTEX)), + factory.createMenuItem(StandardActions.NEW_LIBRARY_BIBLATEX, new NewDatabaseAction(this, BibDatabaseMode.BIBLATEX))), + + factory.createMenuItem(StandardActions.OPEN_LIBRARY, getOpenDatabaseAction()), + fileHistory, + factory.createMenuItem(StandardActions.SAVE_LIBRARY, new OldDatabaseCommandWrapper(Actions.SAVE, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.SAVE_LIBRARY_AS, new OldDatabaseCommandWrapper(Actions.SAVE_AS, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.SAVE_ALL, new SaveAllAction(this)), + + new SeparatorMenuItem(), + + factory.createSubMenu(StandardActions.IMPORT, + factory.createMenuItem(StandardActions.MERGE_DATABASE, new OldDatabaseCommandWrapper(Actions.MERGE_DATABASE, this, Globals.stateManager)), // TODO: merge with import + factory.createMenuItem(StandardActions.IMPORT_INTO_CURRENT_LIBRARY, new ImportCommand(this, false)), + factory.createMenuItem(StandardActions.IMPORT_INTO_NEW_LIBRARY, new ImportCommand(this, true))), + + factory.createSubMenu(StandardActions.EXPORT, + factory.createMenuItem(StandardActions.EXPORT_ALL, new ExportCommand(this, false, Globals.prefs)), + factory.createMenuItem(StandardActions.EXPORT_SELECTED, new ExportCommand(this, true, Globals.prefs)), + factory.createMenuItem(StandardActions.SAVE_SELECTED_AS_PLAIN_BIBTEX, new OldDatabaseCommandWrapper(Actions.SAVE_SELECTED_AS_PLAIN, this, Globals.stateManager))), + + factory.createMenuItem(StandardActions.CONNECT_TO_SHARED_DB, new ConnectToSharedDatabaseCommand(this)), + factory.createMenuItem(StandardActions.PULL_CHANGES_FROM_SHARED_DB, new OldDatabaseCommandWrapper(Actions.PULL_CHANGES_FROM_SHARED_DATABASE, this, Globals.stateManager)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.CLOSE_LIBRARY, new CloseDatabaseAction()), + factory.createMenuItem(StandardActions.QUIT, new CloseAction()) + ); + + edit.getItems().addAll( + factory.createMenuItem(StandardActions.UNDO, new OldDatabaseCommandWrapper(Actions.UNDO, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.REDO, new OldDatabaseCommandWrapper(Actions.REDO, this, Globals.stateManager)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.CUT, new EditAction(Actions.CUT)), + + factory.createMenuItem(StandardActions.COPY, new EditAction(Actions.COPY)), + factory.createSubMenu(StandardActions.COPY_MORE, + factory.createMenuItem(StandardActions.COPY_TITLE, new OldDatabaseCommandWrapper(Actions.COPY_TITLE, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.COPY_KEY, new OldDatabaseCommandWrapper(Actions.COPY_KEY, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.COPY_CITE_KEY, new OldDatabaseCommandWrapper(Actions.COPY_CITE_KEY, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.COPY_KEY_AND_TITLE, new OldDatabaseCommandWrapper(Actions.COPY_KEY_AND_TITLE, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.COPY_KEY_AND_LINK, new OldDatabaseCommandWrapper(Actions.COPY_KEY_AND_LINK, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.COPY_CITATION_PREVIEW, new OldDatabaseCommandWrapper(Actions.COPY_CITATION_HTML, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.EXPORT_SELECTED_TO_CLIPBOARD, new ExportToClipboardAction(this, dialogService))), + + factory.createMenuItem(StandardActions.PASTE, new EditAction(Actions.PASTE)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.MANAGE_KEYWORDS, new ManageKeywordsAction(this)) + ); + if (Globals.prefs.getBoolean(JabRefPreferences.SPECIALFIELDSENABLED)) { - boolean menuitem = false; + boolean menuItemAdded = false; if (Globals.prefs.getBoolean(JabRefPreferences.SHOWCOLUMN_RANKING)) { - rankSubMenu = new JMenu(); - RightClickMenu.populateSpecialFieldMenu(rankSubMenu, SpecialField.RANKING, this); - edit.add(rankSubMenu); - menuitem = true; + edit.getItems().add(SpecialFieldMenuItemFactory.createSpecialFieldMenuForActiveDatabase(SpecialField.RANKING, factory, Globals.undoManager)); + menuItemAdded = true; } + if (Globals.prefs.getBoolean(JabRefPreferences.SHOWCOLUMN_RELEVANCE)) { - edit.add(toggleRelevance); - menuitem = true; + edit.getItems().add(SpecialFieldMenuItemFactory.getSpecialFieldSingleItemForActiveDatabase(SpecialField.RELEVANCE, factory)); + menuItemAdded = true; } + if (Globals.prefs.getBoolean(JabRefPreferences.SHOWCOLUMN_QUALITY)) { - edit.add(toggleQualityAssured); - menuitem = true; - } - if (Globals.prefs.getBoolean(JabRefPreferences.SHOWCOLUMN_PRIORITY)) { - rankSubMenu = new JMenu(); - RightClickMenu.populateSpecialFieldMenu(rankSubMenu, SpecialField.PRIORITY, this); - edit.add(rankSubMenu); - menuitem = true; + edit.getItems().add(SpecialFieldMenuItemFactory.getSpecialFieldSingleItemForActiveDatabase(SpecialField.QUALITY, factory)); + menuItemAdded = true; } + if (Globals.prefs.getBoolean(JabRefPreferences.SHOWCOLUMN_PRINTED)) { - edit.add(togglePrinted); - menuitem = true; + edit.getItems().add(SpecialFieldMenuItemFactory.getSpecialFieldSingleItemForActiveDatabase(SpecialField.PRINTED, factory)); + menuItemAdded = true; + } + + if (Globals.prefs.getBoolean(JabRefPreferences.SHOWCOLUMN_PRIORITY)) { + edit.getItems().add(SpecialFieldMenuItemFactory.createSpecialFieldMenuForActiveDatabase(SpecialField.PRIORITY, factory, Globals.undoManager)); + menuItemAdded = true; } + if (Globals.prefs.getBoolean(JabRefPreferences.SHOWCOLUMN_READ)) { - rankSubMenu = new JMenu(); - RightClickMenu.populateSpecialFieldMenu(rankSubMenu, SpecialField.READ_STATUS, this); - edit.add(rankSubMenu); - menuitem = true; + edit.getItems().add(SpecialFieldMenuItemFactory.createSpecialFieldMenuForActiveDatabase(SpecialField.READ_STATUS, factory, Globals.undoManager)); + menuItemAdded = true; } - if (menuitem) { - edit.addSeparator(); + + if (menuItemAdded) { + edit.getItems().add(new SeparatorMenuItem()); } } + //@formatter:off + library.getItems().addAll( + factory.createMenuItem(StandardActions.NEW_ENTRY, new NewEntryAction(this, dialogService, Globals.prefs)), + factory.createMenuItem(StandardActions.NEW_ENTRY_FROM_PLAINTEX, new NewEntryFromPlainTextAction(this, Globals.prefs.getUpdateFieldPreferences(), dialogService, Globals.prefs)), + factory.createMenuItem(StandardActions.DELETE_ENTRY, new OldDatabaseCommandWrapper(Actions.DELETE, this, Globals.stateManager)), - edit.add(getManageKeywords()); - edit.add(getMassSetField()); - edit.addSeparator(); - edit.add(selectAll); - mb.add(edit); - - search.add(normalSearch); - search.add(replaceAll); - search.addSeparator(); - search.add(new JCheckBoxMenuItem(generalFetcher.getToggleAction())); - if (prefs.getBoolean(JabRefPreferences.WEB_SEARCH_VISIBLE)) { - sidePaneManager.register(generalFetcher); - sidePaneManager.show(GeneralFetcher.class); - } - mb.add(search); + new SeparatorMenuItem(), - groups.add(new JCheckBoxMenuItem(groupSidePane.getToggleAction())); - if (prefs.getBoolean(JabRefPreferences.GROUP_SIDEPANE_VISIBLE)) { - sidePaneManager.register(groupSidePane); - sidePaneManager.show(GroupSidePane.class); - } + factory.createMenuItem(StandardActions.LIBRARY_PROPERTIES, new LibraryPropertiesAction(this, dialogService)), + factory.createMenuItem(StandardActions.EDIT_PREAMBLE, new PreambleEditor(this)), + factory.createMenuItem(StandardActions.EDIT_STRINGS, new BibtexStringEditorAction(this)), + factory.createMenuItem(StandardActions.MANAGE_CITE_KEY_PATTERNS, new BibtexKeyPatternAction(this)), + factory.createMenuItem(StandardActions.MASS_SET_FIELDS, new MassSetFieldsAction(this)) + ); - groups.addSeparator(); - groups.add(addToGroup); - groups.add(removeFromGroup); - groups.add(moveToGroup); - mb.add(groups); - - view.add(getBackAction()); - view.add(getForwardAction()); - view.add(focusTable); - view.add(nextTab); - view.add(prevTab); - view.add(sortTabs); - view.addSeparator(); - view.add(increaseFontSize); - view.add(decreseFontSize); - view.add(defaultFontSize); - view.addSeparator(); - view.add(new JCheckBoxMenuItem(toggleToolbar)); - view.add(new JCheckBoxMenuItem(enableToggle(generalFetcher.getToggleAction()))); - view.add(new JCheckBoxMenuItem(groupSidePane.getToggleAction())); - view.add(new JCheckBoxMenuItem(togglePreview)); - view.add(showPdvViewer); - view.add(getNextPreviewStyleAction()); - view.add(getPreviousPreviewStyleAction()); - - mb.add(view); - - bibtex.add(newEntryAction); - - for (NewEntryAction a : newSpecificEntryAction) { - newSpec.add(a); + Menu lookupIdentifiers = factory.createSubMenu(StandardActions.LOOKUP_DOC_IDENTIFIER); + for (IdFetcher fetcher : WebFetchers.getIdFetchers(Globals.prefs.getImportFormatPreferences())) { + LookupIdentifierAction identifierAction = new LookupIdentifierAction<>(this, fetcher); + lookupIdentifiers.getItems().add(factory.createMenuItem(identifierAction.getAction(), identifierAction)); } - bibtex.add(newSpec); - - bibtex.add(plainTextImport); - bibtex.addSeparator(); - bibtex.add(editEntry); - bibtex.add(editPreamble); - bibtex.add(editStrings); - bibtex.addSeparator(); - bibtex.add(customizeAction); - bibtex.addSeparator(); - bibtex.add(deleteEntry); - mb.add(bibtex); - - quality.add(dupliCheck); - quality.add(mergeEntries); - quality.addSeparator(); - quality.add(resolveDuplicateKeys); - quality.add(checkIntegrity); - quality.add(cleanupEntries); - quality.add(massSetField); - quality.add(makeKeyAction); - quality.addSeparator(); - quality.add(autoSetFile); - quality.add(findUnlinkedFiles); - quality.add(autoLinkFile); - - for (IdFetcher fetcher : WebFetchers.getIdFetchers(Globals.prefs.getImportFormatPreferences())) { - lookupIdentifiers.add(new LookupIdentifierAction(this, fetcher)); - } - quality.add(lookupIdentifiers); - quality.add(downloadFullText); - mb.add(quality); - - tools.add(newSubDatabaseAction); - tools.add(writeXmpAction); - tools.add(new JCheckBoxMenuItem(openOfficePanel.getToggleAction())); - tools.add(pushExternalButton.getMenuAction()); - tools.addSeparator(); - tools.add(openFolder); - tools.add(openFile); - tools.add(openUrl); - tools.add(openConsole); - tools.addSeparator(); - tools.add(abbreviateIso); - tools.add(abbreviateMedline); - tools.add(unabbreviate); - mb.add(tools); - - options.add(showPrefs); - - AbstractAction genFieldsCustomization = new GenFieldsCustomizationAction(); - AbstractAction protectTerms = new ProtectedTermsAction(); - options.add(genFieldsCustomization); - options.add(customImpAction); - options.add(customExpAction); - options.add(customFileTypesAction); - options.add(manageJournals); - options.add(keyBindingAction); - options.add(protectTerms); - options.add(manageSelectors); - mb.add(options); - - helpMenu.add(help); - helpMenu.add(openForumAction); - helpMenu.addSeparator(); - helpMenu.add(errorConsole); - helpMenu.addSeparator(); - helpMenu.add(new SearchForUpdateAction()); - JMenu webMenu = JabRefFrame.subMenu(Localization.menuTitle("JabRef resources")); - webMenu.add(jabrefWebPageAction); - webMenu.add(jabrefBlogAction); - webMenu.add(jabrefFacebookAction); - webMenu.add(jabrefTwitterAction); - webMenu.addSeparator(); - webMenu.add(forkMeOnGitHubAction); - webMenu.add(developmentVersionAction); - webMenu.add(changeLogAction); - webMenu.addSeparator(); - webMenu.add(donationAction); - helpMenu.add(webMenu); - helpMenu.add(about); - mb.add(helpMenu); - - createDisabledIconsForMenuEntries(mb); - } - - public void addParserResult(ParserResult parserResult, boolean focusPanel) { - if (parserResult.toOpenTab()) { + + quality.getItems().addAll( + factory.createMenuItem(StandardActions.FIND_DUPLICATES, new DuplicateSearch(this, dialogService)), + factory.createMenuItem(StandardActions.MERGE_ENTRIES, new MergeEntriesAction(this)), + factory.createMenuItem(StandardActions.CHECK_INTEGRITY, new IntegrityCheckAction(this)), + factory.createMenuItem(StandardActions.CLEANUP_ENTRIES, new OldDatabaseCommandWrapper(Actions.CLEANUP, this, Globals.stateManager)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.SET_FILE_LINKS, new AutoLinkFilesAction()) + ); + + PushToApplicationButton pushToExternal = new PushToApplicationButton(this, pushApplications.getApplications()); + tools.getItems().addAll( + factory.createMenuItem(StandardActions.NEW_SUB_LIBRARY_FROM_AUX, new NewSubLibraryAction(this)), + factory.createMenuItem(StandardActions.FIND_UNLINKED_FILES, new FindUnlinkedFilesAction(this)), + factory.createMenuItem(StandardActions.WRITE_XMP, new OldDatabaseCommandWrapper(Actions.WRITE_XMP, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.COPY_LINKED_FILES, new CopyFilesAction(this)), + + new SeparatorMenuItem(), + + lookupIdentifiers, + factory.createMenuItem(StandardActions.DOWNLOAD_FULL_TEXT, new OldDatabaseCommandWrapper(Actions.DOWNLOAD_FULL_TEXT, this, Globals.stateManager)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.GENERATE_CITE_KEYS, new OldDatabaseCommandWrapper(Actions.MAKE_KEY, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.REPLACE_ALL, new OldDatabaseCommandWrapper(Actions.REPLACE_ALL, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.SEND_AS_EMAIL, new OldDatabaseCommandWrapper(Actions.SEND_AS_EMAIL, this, Globals.stateManager)), + factory.createMenuItem(pushToExternal.getMenuAction(), pushToExternal), + + factory.createSubMenu(StandardActions.ABBREVIATE, + factory.createMenuItem(StandardActions.ABBREVIATE_ISO, new OldDatabaseCommandWrapper(Actions.ABBREVIATE_ISO, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.ABBREVIATE_MEDLINE, new OldDatabaseCommandWrapper(Actions.ABBREVIATE_MEDLINE, this, Globals.stateManager))), + + factory.createMenuItem(StandardActions.UNABBREVIATE, new OldDatabaseCommandWrapper(Actions.UNABBREVIATE, this, Globals.stateManager)) + ); + + SidePaneComponent webSearch = sidePaneManager.getComponent(SidePaneType.WEB_SEARCH); + SidePaneComponent groups = sidePaneManager.getComponent(SidePaneType.GROUPS); + SidePaneComponent openOffice = sidePaneManager.getComponent(SidePaneType.OPEN_OFFICE); + + view.getItems().add(new SeparatorMenuItem()); + view.setOnShowing(event -> { + view.getItems().clear(); + view.getItems().addAll( + factory.createCheckMenuItem(webSearch.getToggleAction(), webSearch.getToggleCommand(), sidePaneManager.isComponentVisible(SidePaneType.WEB_SEARCH)), + factory.createCheckMenuItem(groups.getToggleAction(), groups.getToggleCommand(), sidePaneManager.isComponentVisible(SidePaneType.GROUPS)), + factory.createCheckMenuItem(openOffice.getToggleAction(), openOffice.getToggleCommand(), sidePaneManager.isComponentVisible(SidePaneType.OPEN_OFFICE)), + + new SeparatorMenuItem(), + + factory.createCheckMenuItem(StandardActions.TOGGLE_PREVIEW, new OldDatabaseCommandWrapper(Actions.TOGGLE_PREVIEW, this, Globals.stateManager), Globals.prefs.getPreviewPreferences().isPreviewPanelEnabled()), + factory.createMenuItem(StandardActions.NEXT_PREVIEW_STYLE, new OldDatabaseCommandWrapper(Actions.NEXT_PREVIEW_STYLE, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.PREVIOUS_PREVIEW_STYLE, new OldDatabaseCommandWrapper(Actions.PREVIOUS_PREVIEW_STYLE, this, Globals.stateManager)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.SHOW_PDF_VIEWER, new ShowDocumentViewerAction()), + factory.createMenuItem(StandardActions.EDIT_ENTRY, new OldDatabaseCommandWrapper(Actions.EDIT, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.OPEN_CONSOLE, new OldDatabaseCommandWrapper(Actions.OPEN_CONSOLE, this, Globals.stateManager)) + ); + }); + + options.getItems().addAll( + factory.createMenuItem(StandardActions.SHOW_PREFS, new ShowPreferencesAction(this, Globals.TASK_EXECUTOR)), + + new SeparatorMenuItem(), + + 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()), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.MANAGE_CONTENT_SELECTORS, new ManageContentSelectorAction(this)) + // TODO: Reenable customize entry types feature (https://github.com/JabRef/jabref/issues/4719) + //factory.createMenuItem(StandardActions.CUSTOMIZE_ENTRY_TYPES, new CustomizeEntryAction(this)), + ); + + help.getItems().addAll( + factory.createMenuItem(StandardActions.HELP, HelpAction.getMainHelpPageCommand()), + factory.createMenuItem(StandardActions.OPEN_FORUM, new OpenBrowserAction("http://discourse.jabref.org/")), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.ERROR_CONSOLE, new ErrorConsoleAction()), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.SEARCH_FOR_UPDATES, new SearchForUpdateAction(Globals.BUILD_INFO, prefs.getVersionPreferences(), dialogService, Globals.TASK_EXECUTOR)), + factory.createSubMenu(StandardActions.WEB_MENU, + factory.createMenuItem(StandardActions.OPEN_WEBPAGE, new OpenBrowserAction("https://jabref.org/")), + factory.createMenuItem(StandardActions.OPEN_BLOG, new OpenBrowserAction("https://blog.jabref.org/")), + factory.createMenuItem(StandardActions.OPEN_FACEBOOK, new OpenBrowserAction("https://www.facebook.com/JabRef/")), + factory.createMenuItem(StandardActions.OPEN_TWITTER, new OpenBrowserAction("https://twitter.com/jabref_org")), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.FORK_ME, new OpenBrowserAction("https://github.com/JabRef/jabref")), + factory.createMenuItem(StandardActions.OPEN_DEV_VERSION_LINK, new OpenBrowserAction("https://builds.jabref.org/master/")), + factory.createMenuItem(StandardActions.OPEN_CHANGELOG, new OpenBrowserAction("https://github.com/JabRef/jabref/blob/master/CHANGELOG.md")), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.DONATE, new OpenBrowserAction("https://donations.jabref.org")) + + ), + factory.createMenuItem(StandardActions.ABOUT, new AboutAction()) + ); + + //@formatter:on + MenuBar menu = new MenuBar(); + menu.getStyleClass().add("mainMenu"); + menu.getMenus().addAll( + file, + edit, + library, + quality, + tools, + view, + options, + help); + menu.setUseSystemMenuBar(true); + return menu; + } + + public void addParserResult(ParserResult pr, boolean focusPanel) { + if (pr.toOpenTab()) { // Add the entries to the open tab. BasePanel panel = getCurrentBasePanel(); if (panel == null) { // There is no open tab to add to, so we create a new tab: - panel = addTab(parserResult.getDatabaseContext(), focusPanel); - if (parserResult.wasChangedOnMigration()) { - panel.markBaseChanged(); - } + addTab(pr.getDatabaseContext(), focusPanel); } else { - List entries = new ArrayList<>(parserResult.getDatabase().getEntries()); - addImportedEntries(panel, entries, false); + List entries = new ArrayList<>(pr.getDatabase().getEntries()); + addImportedEntries(panel, entries); } } else { // only add tab if DB is not already open Optional panel = getBasePanelList().stream() - .filter(p -> p.getBibDatabaseContext().getDatabaseFile().equals(parserResult.getFile())).findFirst(); + .filter(p -> p.getBibDatabaseContext().getDatabasePath().equals(pr.getFile())) + .findFirst(); if (panel.isPresent()) { - tabbedPane.setSelectedComponent(panel.get()); + tabbedPane.getSelectionModel().select(getTab(panel.get())); } else { - BasePanel basePanel = addTab(parserResult.getDatabaseContext(), focusPanel); - if (parserResult.wasChangedOnMigration()) { - basePanel.markBaseChanged(); - } - } - } - } - - private void createToolBar() { - tlb.setBorder(null); - tlb.setRollover(true); - - tlb.setFloatable(false); - if (Globals.prefs.getBoolean(JabRefPreferences.BIBLATEX_DEFAULT_MODE)) { - tlb.addAction(newBiblatexDatabaseAction); - } else { - tlb.addAction(newBibtexDatabaseAction); - } - tlb.addAction(getOpenDatabaseAction()); - tlb.addAction(save); - tlb.addAction(saveAll); - - tlb.addSeparator(); - tlb.addAction(cut); - tlb.addAction(copy); - tlb.addAction(paste); - tlb.addAction(undo); - tlb.addAction(redo); - - tlb.addSeparator(); - tlb.addAction(getBackAction()); - tlb.addAction(getForwardAction()); - tlb.addSeparator(); - tlb.addAction(newEntryAction); - tlb.addAction(editEntry); - tlb.addAction(editStrings); - tlb.addAction(deleteEntry); - tlb.addSeparator(); - tlb.addAction(makeKeyAction); - tlb.addAction(cleanupEntries); - tlb.addAction(mergeEntries); - tlb.addAction(pullChangesFromSharedDatabase); - tlb.addAction(openConsole); - - tlb.addSeparator(); - tlb.addAction(mark); - tlb.addAction(unmark); - if (Globals.prefs.getBoolean(JabRefPreferences.SPECIALFIELDSENABLED)) { - if (Globals.prefs.getBoolean(JabRefPreferences.SHOWCOLUMN_RANKING)) { - JButton button = SpecialFieldDropDown - .generateSpecialFieldButtonWithDropDown(SpecialField.RANKING, this); - tlb.add(button); - specialFieldButtons.add(button); - } - if (Globals.prefs.getBoolean(JabRefPreferences.SHOWCOLUMN_RELEVANCE)) { - tlb.addAction(toggleRelevance); - } - if (Globals.prefs.getBoolean(JabRefPreferences.SHOWCOLUMN_QUALITY)) { - tlb.addAction(toggleQualityAssured); - } - if (Globals.prefs.getBoolean(JabRefPreferences.SHOWCOLUMN_PRIORITY)) { - JButton button = SpecialFieldDropDown - .generateSpecialFieldButtonWithDropDown(SpecialField.PRIORITY, this); - tlb.add(button); - specialFieldButtons.add(button); - } - if (Globals.prefs.getBoolean(JabRefPreferences.SHOWCOLUMN_PRINTED)) { - tlb.addAction(togglePrinted); - } - if (Globals.prefs.getBoolean(JabRefPreferences.SHOWCOLUMN_READ)) { - JButton button = SpecialFieldDropDown - .generateSpecialFieldButtonWithDropDown(SpecialField.READ_STATUS, this); - tlb.add(button); - specialFieldButtons.add(button); + addTab(pr.getDatabaseContext(), focusPanel); } } - tlb.addSeparator(); - - tlb.addJToggleButton(new JToggleButton(generalFetcher.getToggleAction())); - - previewToggle = new JToggleButton(togglePreview); - tlb.addJToggleButton(previewToggle); - - tlb.addJToggleButton(new JToggleButton(groupSidePane.getToggleAction())); - - tlb.addSeparator(); - - tlb.add(pushExternalButton.getComponent()); - tlb.addSeparator(); - tlb.add(donationAction); - tlb.add(forkMeOnGitHubAction); - tlb.add(jabrefFacebookAction); - tlb.add(jabrefTwitterAction); - - createDisabledIconsForButtons(tlb); } /** - * displays the String on the Status Line visible on the bottom of the JabRef mainframe + * Displays the given message at the bottom of the main frame + * + * @deprecated use {@link DialogService#notify(String)} instead. However, do not remove this method, it's called from the dialogService */ - public void output(final String s) { - SwingUtilities.invokeLater(() -> { - statusLine.setText(s); - statusLine.repaint(); - }); + @Deprecated + public void output(final String message) { + DefaultTaskExecutor.runInJavaFXThread(() -> statusLine.fireEvent(new SnackbarEvent(new JFXSnackbarLayout(message), TOAST_MESSAGE_DISPLAY_TIME, null))); } private void initActions() { + /* openDatabaseOnlyActions.clear(); openDatabaseOnlyActions.addAll(Arrays.asList(manageSelectors, mergeDatabaseAction, newSubDatabaseAction, save, copyPreview, - saveAs, saveSelectedAs, saveSelectedAsPlain, editModeAction, undo, redo, cut, deleteEntry, copy, paste, mark, markSpecific, unmark, + saveAs, saveSelectedAs, saveSelectedAsPlain, undo, redo, cut, deleteEntry, copy, paste, mark, markSpecific, unmark, unmarkAll, rankSubMenu, editEntry, selectAll, copyKey, copyCiteKey, copyKeyAndTitle, copyKeyAndLink, editPreamble, editStrings, - groupSidePane.getToggleAction(), makeKeyAction, normalSearch, generalFetcher.getToggleAction(), mergeEntries, cleanupEntries, exportToClipboard, replaceAll, - sendAsEmail, downloadFullText, lookupIdentifiers, writeXmpAction, openOfficePanel.getToggleAction(), findUnlinkedFiles, addToGroup, removeFromGroup, + groupSidePane.getToggleCommand(), makeKeyAction, normalSearch, generalFetcher.getToggleCommand(), mergeEntries, cleanupEntries, exportToClipboard, replaceAll, + sendAsEmail, downloadFullText, lookupIdentifiers, writeXmpAction, openOfficePanel.getToggleCommand(), findUnlinkedFiles, addToGroup, removeFromGroup, moveToGroup, autoLinkFile, resolveDuplicateKeys, openUrl, openFolder, openFile, togglePreview, dupliCheck, autoSetFile, newEntryAction, newSpec, customizeAction, plainTextImport, getMassSetField(), getManageKeywords(), pushExternalButton.getMenuAction(), closeDatabaseAction, getNextPreviewStyleAction(), getPreviousPreviewStyleAction(), checkIntegrity, databaseProperties, abbreviateIso, abbreviateMedline, unabbreviate, exportAll, exportSelected, importCurrent, saveAll, focusTable, increaseFontSize, decreseFontSize, defaultFontSize, toggleRelevance, toggleQualityAssured, togglePrinted, pushExternalButton.getComponent())); - openDatabaseOnlyActions.addAll(newSpecificEntryAction); - openDatabaseOnlyActions.addAll(specialFieldButtons); - severalDatabasesOnlyActions.clear(); severalDatabasesOnlyActions.addAll(Arrays .asList(nextTab, prevTab, sortTabs)); - openAndSavedDatabasesOnlyActions.addAll(Collections.singletonList(openConsole)); sharedDatabaseOnlyActions.addAll(Collections.singletonList(pullChangesFromSharedDatabase)); noSharedDatabaseActions.addAll(Arrays.asList(save, saveAll)); - oneEntryOnlyActions.clear(); oneEntryOnlyActions.addAll(Arrays.asList(editEntry)); - oneEntryWithFileOnlyActions.clear(); oneEntryWithFileOnlyActions.addAll(Arrays.asList(openFolder, openFile)); - oneEntryWithURLorDOIOnlyActions.clear(); oneEntryWithURLorDOIOnlyActions.addAll(Arrays.asList(openUrl)); - twoEntriesOnlyActions.clear(); twoEntriesOnlyActions.addAll(Arrays.asList(mergeEntries)); - atLeastOneEntryActions.clear(); atLeastOneEntryActions.addAll(Arrays.asList(downloadFullText, lookupIdentifiers, exportLinkedFiles)); - - tabbedPane.addChangeListener(event -> updateEnabledState()); + tabbedPane.getTabs().addListener(this::updateEnabledState); + */ } /** @@ -1457,16 +1045,16 @@ dupliCheck, autoSetFile, newEntryAction, newSpec, customizeAction, plainTextImpo *

    * The action that are affected are set in initActions. */ - public void updateEnabledState() { - int tabCount = tabbedPane.getTabCount(); - if (tabCount != previousTabCount) { - previousTabCount = tabCount; + public void updateEnabledState(ListChangeListener.Change change) { + int tabCount = tabbedPane.getTabs().size(); + if (!change.next()) { + return; + } + if (change.wasAdded() || change.wasRemoved()) { setEnabled(openDatabaseOnlyActions, tabCount > 0); setEnabled(severalDatabasesOnlyActions, tabCount > 1); } if (tabCount == 0) { - getBackAction().setEnabled(false); - getForwardAction().setEnabled(false); setEnabled(openAndSavedDatabasesOnlyActions, false); setEnabled(sharedDatabaseOnlyActions, false); setEnabled(oneEntryOnlyActions, false); @@ -1495,9 +1083,8 @@ public void updateEnabledState() { } /** - * 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 BasePanels to set up their tables anew. When called from PrefsDialog3, this updates + * to the new settings. */ public void setupAllTables() { // This action can be invoked without an open database, so @@ -1506,12 +1093,12 @@ public void setupAllTables() { // We want to notify all tabs about the changes to // avoid problems when changing the column set. - for (int i = 0; i < tabbedPane.getTabCount(); i++) { + for (int i = 0; i < tabbedPane.getTabs().size(); i++) { BasePanel bf = getBasePanelAt(i); // Update tables: if (bf.getDatabase() != null) { - bf.setupMainPanel(); + DefaultTaskExecutor.runInJavaFXThread(bf::setupMainPanel); } } } @@ -1550,48 +1137,49 @@ public void updateAllTabTitles() { if (!uniqPath.equals(file.get().getName()) && uniqPath.contains(File.separator)) { // remove filename uniqPath = uniqPath.substring(0, uniqPath.lastIndexOf(File.separator)); - tabbedPane.setTitleAt(i, getBasePanelAt(i).getTabTitle() + " \u2014 " + uniqPath); + tabbedPane.getTabs().get(i).setText(getBasePanelAt(i).getTabTitle() + " \u2014 " + uniqPath); } else { // set original filename (again) - tabbedPane.setTitleAt(i, getBasePanelAt(i).getTabTitle()); + tabbedPane.getTabs().get(i).setText(getBasePanelAt(i).getTabTitle()); } } else { - tabbedPane.setTitleAt(i, getBasePanelAt(i).getTabTitle()); + tabbedPane.getTabs().get(i).setText(getBasePanelAt(i).getTabTitle()); } - tabbedPane.setToolTipTextAt(i, file.map(File::getAbsolutePath).orElse(null)); + tabbedPane.getTabs().get(i).setTooltip(new Tooltip(file.map(File::getAbsolutePath).orElse(null))); } } - public BasePanel addTab(BasePanel basePanel, boolean raisePanel) { - // add tab - tabbedPane.add(basePanel.getTabTitle(), basePanel); + public void addTab(BasePanel basePanel, boolean raisePanel) { + DefaultTaskExecutor.runInJavaFXThread(() -> { + // add tab + Tab newTab = new Tab(basePanel.getTabTitle(), basePanel); + tabbedPane.getTabs().add(newTab); - // update all tab titles - updateAllTabTitles(); + // update all tab titles + updateAllTabTitles(); - if (raisePanel) { - tabbedPane.setSelectedComponent(basePanel); - } + if (raisePanel) { + tabbedPane.getSelectionModel().select(newTab); + } - // Register undo/redo listener - basePanel.getUndoManager().registerListener(new UndoRedoEventManager()); + // Register undo/redo listener + basePanel.getUndoManager().registerListener(new UndoRedoEventManager()); - BibDatabaseContext context = basePanel.getBibDatabaseContext(); + BibDatabaseContext context = basePanel.getBibDatabaseContext(); - if (readyForAutosave(context)) { - AutosaveManager autosaver = AutosaveManager.start(context); - autosaver.registerListener(new AutosaveUIManager(basePanel)); - } + if (readyForAutosave(context)) { + AutosaveManager autosaver = AutosaveManager.start(context); + autosaver.registerListener(new AutosaveUIManager(basePanel)); + } - BackupManager.start(context); + BackupManager.start(context); - // Track opening - trackOpenNewDatabase(basePanel); - return basePanel; + // Track opening + trackOpenNewDatabase(basePanel); + }); } private void trackOpenNewDatabase(BasePanel basePanel) { - Map properties = new HashMap<>(); Map measurements = new HashMap<>(); measurements.put("NumberOfEntries", (double) basePanel.getBibDatabaseContext().getDatabase().getEntryCount()); @@ -1601,69 +1189,30 @@ private void trackOpenNewDatabase(BasePanel basePanel) { public BasePanel addTab(BibDatabaseContext databaseContext, boolean raisePanel) { Objects.requireNonNull(databaseContext); - BasePanel basePanel = new BasePanel(JabRefFrame.this, databaseContext); - addTab(basePanel, raisePanel); - return basePanel; + + BasePanel bp = new BasePanel(this, BasePanelPreferences.from(Globals.prefs), databaseContext, ExternalFileTypes.getInstance()); + addTab(bp, raisePanel); + return bp; } private boolean readyForAutosave(BibDatabaseContext context) { return ((context.getLocation() == DatabaseLocation.SHARED) || - ((context.getLocation() == DatabaseLocation.LOCAL) && Globals.prefs.getBoolean(JabRefPreferences.LOCAL_AUTO_SAVE))) && + ((context.getLocation() == DatabaseLocation.LOCAL) && Globals.prefs.getBoolean(JabRefPreferences.LOCAL_AUTO_SAVE))) + && context.getDatabaseFile().isPresent(); } /** - * Creates icons for the disabled state for all JMenuItems with FontBasedIcons in the given menuElement. - * This is necessary as Swing is not able to generate default disabled icons for font based icons. - * - * @param menuElement the menuElement for which disabled icons should be generated - */ - public void createDisabledIconsForMenuEntries(MenuElement menuElement) { - for (MenuElement subElement : menuElement.getSubElements()) { - if ((subElement instanceof JMenu) || (subElement instanceof JPopupMenu)) { - createDisabledIconsForMenuEntries(subElement); - } else if (subElement instanceof JMenuItem) { - JMenuItem item = (JMenuItem) subElement; - if (item.getIcon() instanceof IconTheme.FontBasedIcon) { - item.setDisabledIcon(((IconTheme.FontBasedIcon) item.getIcon()).createDisabledIcon()); - } - } - } - } - - public void createDisabledIconsForButtons(Container container) { - for (int index = 0; index < container.getComponentCount(); index++) { - Component component = container.getComponent(index); - if (component instanceof JButton) { - JButton button = (JButton) component; - if (button.getIcon() instanceof IconTheme.FontBasedIcon) { - button.setDisabledIcon(((IconTheme.FontBasedIcon) button.getIcon()).createDisabledIcon()); - } - } else if (component instanceof JPanel) { - createDisabledIconsForButtons((JPanel) component); - } - } - } - - /** - * This method does the job of adding imported entries into the active - * database, or into a new one. It shows the ImportInspectionDialog if - * preferences indicate it should be used. Otherwise it imports directly. + * Opens the import inspection dialog to let the user decide which of the given entries to import. * - * @param panel The BasePanel to add to. - * @param entries The entries to add. - * @param openInNew Should the entries be imported into a new database? + * @param panel The BasePanel to add to. + * @param entries The entries to add. */ - private void addImportedEntries(final BasePanel panel, final List entries, final boolean openInNew) { - SwingUtilities.invokeLater(() -> { - ImportInspectionDialog diag = new ImportInspectionDialog(JabRefFrame.this, panel, - Localization.lang("Import"), openInNew); - diag.addEntries(entries); - diag.entryListComplete(); - diag.setLocationRelativeTo(JabRefFrame.this); - diag.setVisible(true); - diag.toFront(); - }); + private void addImportedEntries(final BasePanel panel, final List entries) { + BackgroundTask> task = BackgroundTask.wrap(() -> entries); + ImportEntriesDialog dialog = new ImportEntriesDialog(panel.getBibDatabaseContext(), task); + dialog.setTitle(Localization.lang("Import")); + dialog.showAndWait(); } public FileHistoryMenu getFileHistory() { @@ -1671,102 +1220,35 @@ public FileHistoryMenu getFileHistory() { } /** - * This method shows a wait cursor and blocks all input to the JFrame's contents. - */ - public void block() { - changeBlocking(true); - } - - /** - * This method reverts the cursor to normal, and stops blocking input to the JFrame's contents. - * There are no adverse effects of calling this method redundantly. - */ - public void unblock() { - changeBlocking(false); - } - - /** - * Do the actual blocking/unblocking - * - * @param blocked true if input should be blocked - */ - private void changeBlocking(boolean blocked) { - if (SwingUtilities.isEventDispatchThread()) { - getGlassPane().setVisible(blocked); - } else { - try { - SwingUtilities.invokeAndWait(() -> getGlassPane().setVisible(blocked)); - } catch (InvocationTargetException | InterruptedException e) { - LOGGER.error("Problem " + (blocked ? "" : "un") + "blocking UI", e); - } - } - } - - /** - * Set the visibility of the progress bar in the right end of the - * status line at the bottom of the frame. - *

    - * If not called on the event dispatch thread, this method uses - * SwingUtilities.invokeLater() to do the actual operation on the EDT. + * Set the visibility of the progress bar in the right end of the status line at the bottom of the frame. */ public void setProgressBarVisible(final boolean visible) { - if (SwingUtilities.isEventDispatchThread()) { - progressBar.setVisible(visible); - } else { - SwingUtilities.invokeLater(() -> progressBar.setVisible(visible)); - } - } - - /** - * Sets the current value of the progress bar. - *

    - * If not called on the event dispatch thread, this method uses - * SwingUtilities.invokeLater() to do the actual operation on the EDT. - */ - public void setProgressBarValue(final int value) { - if (SwingUtilities.isEventDispatchThread()) { - progressBar.setValue(value); - } else { - SwingUtilities.invokeLater(() -> progressBar.setValue(value)); - } + progressBar.setVisible(visible); } /** * Sets the indeterminate status of the progress bar. *

    - * If not called on the event dispatch thread, this method uses - * SwingUtilities.invokeLater() to do the actual operation on the EDT. + * If not called on the event dispatch thread, this method uses SwingUtilities.invokeLater() to do the actual + * operation on the EDT. */ public void setProgressBarIndeterminate(final boolean value) { + // TODO: Reimplement + /* if (SwingUtilities.isEventDispatchThread()) { progressBar.setIndeterminate(value); } else { SwingUtilities.invokeLater(() -> progressBar.setIndeterminate(value)); } - } - - /** - * Sets the maximum value of the progress bar. Always call this method - * before using the progress bar, to set a maximum value appropriate to - * the task at hand. - *

    - * If not called on the event dispatch thread, this method uses - * SwingUtilities.invokeLater() to do the actual operation on the EDT. - */ - public void setProgressBarMaximum(final int value) { - if (SwingUtilities.isEventDispatchThread()) { - progressBar.setMaximum(value); - } else { - SwingUtilities.invokeLater(() -> progressBar.setMaximum(value)); - } + */ } /** * 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 + * @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) { @@ -1780,8 +1262,8 @@ private boolean isExistFile(List selectEntryList) { * 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 + * @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) { @@ -1793,7 +1275,7 @@ private boolean isExistURLorDOI(List selectEntryList) { @Override public void showMessage(String message, String title, int msgType) { - JOptionPane.showMessageDialog(this, message, title, msgType); + JOptionPane.showMessageDialog(null, message, title, msgType); } @Override @@ -1803,18 +1285,48 @@ public void setStatus(String s) { @Override public void showMessage(String message) { - JOptionPane.showMessageDialog(this, message); + JOptionPane.showMessageDialog(null, message); } - private int showSaveDialog(String filename) { - Object[] options = {Localization.lang("Save changes"), - Localization.lang("Discard changes"), - Localization.lang("Return to JabRef")}; - - return JOptionPane.showOptionDialog(JabRefFrame.this, + /** + * 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(GUIGlobals.UNTITLED_TITLE); + + ButtonType saveChanges = new ButtonType(Localization.lang("Save changes"), ButtonBar.ButtonData.YES); + ButtonType discardChanges = new ButtonType(Localization.lang("Discard changes"), ButtonBar.ButtonData.NO); + ButtonType cancel = new ButtonType(Localization.lang("Return to JabRef"), ButtonBar.ButtonData.CANCEL_CLOSE); + + Optional response = dialogService.showCustomButtonDialogAndWait(Alert.AlertType.CONFIRMATION, + Localization.lang("Save before closing"), Localization.lang("Library '%0' has changed.", filename), - Localization.lang("Save before closing"), JOptionPane.YES_NO_CANCEL_OPTION, - JOptionPane.WARNING_MESSAGE, null, options, options[2]); + saveChanges, discardChanges, cancel); + + if (response.isPresent() && response.get().equals(saveChanges)) { + // The user wants to save. + try { + SaveDatabaseAction saveAction = new SaveDatabaseAction(panel, Globals.prefs); + if (saveAction.save()) { + // Saved, now exit. + return true; + } + // The action was either canceled or unsuccessful. + output(Localization.lang("Unable to save library")); + } catch (Throwable ex) { + LOGGER.error("A problem occurred when trying to save the file", ex); + dialogService.showErrorDialogAndWait(Localization.lang("Save library"), Localization.lang("Could not save file."), ex); + } + // Save was cancelled or an error occurred. + return false; + } + return !response.isPresent() || !response.get().equals(cancel); } private void closeTab(BasePanel panel) { @@ -1841,94 +1353,29 @@ private void closeTab(BasePanel panel) { BackupManager.shutdown(context); } - // Ask if the user really wants to close, if the base has not been saved - private boolean confirmClose(BasePanel panel) { - boolean close = false; - String filename; - - filename = panel.getBibDatabaseContext().getDatabaseFile().map(File::getAbsolutePath) - .orElse(GUIGlobals.UNTITLED_TITLE); - - int answer = showSaveDialog(filename); - if (answer == JOptionPane.YES_OPTION) { - // The user wants to save. - try { - SaveDatabaseAction saveAction = new SaveDatabaseAction(panel); - saveAction.runCommand(); - if (saveAction.isSuccess()) { - close = true; - } - } catch (Throwable ex) { - // do not close - } - } else if (answer == JOptionPane.NO_OPTION) { - // discard changes - close = true; - } - return close; - } - private void removeTab(BasePanel panel) { - panel.cleanUp(); - tabbedPane.remove(panel); - if (tabbedPane.getTabCount() > 0) { - markActiveBasePanel(); - } - setWindowTitle(); - updateEnabledState(); - output(Localization.lang("Closed library") + '.'); - // update tab titles - updateAllTabTitles(); + DefaultTaskExecutor.runInJavaFXThread(() -> { + panel.cleanUp(); + tabbedPane.getTabs().remove(getTab(panel)); + setWindowTitle(); + output(Localization.lang("Closed library") + '.'); + // update tab titles + updateAllTabTitles(); + }); } public void closeCurrentTab() { removeTab(getCurrentBasePanel()); } - public ManageKeywordsAction getManageKeywords() { - return manageKeywords; - } - - public MassSetFieldAction getMassSetField() { - return massSetField; - } - public OpenDatabaseAction getOpenDatabaseAction() { - return open; - } - - public String getStatusLineText() { - return statusLine.getText(); - } - - public AbstractAction getForwardAction() { - return forward; - } - - public AbstractAction getBackAction() { - return back; - } - - public AbstractAction getNextPreviewStyleAction() { - return nextPreviewStyle; - } - - public AbstractAction getPreviousPreviewStyleAction() { - return previousPreviewStyle; - } - - public JSplitPane getSplitPane() { - return splitPane; + return new OpenDatabaseAction(this); } public SidePaneManager getSidePaneManager() { return sidePaneManager; } - public void setPreviewToggle(boolean enabled) { - previewToggle.setSelected(enabled); - } - public PushToApplications getPushApplications() { return pushApplications; } @@ -1937,453 +1384,113 @@ public GlobalSearchBar getGlobalSearchBar() { return globalSearchBar; } - private static class MyGlassPane extends JPanel { - public MyGlassPane() { - addKeyListener(new KeyAdapter() { - // Nothing - }); - addMouseListener(new MouseAdapter() { - // Nothing - }); - /* infoLabel.setForeground(new Color(255, 100, 100, 124)); - - setLayout(new BorderLayout()); - add(infoLabel, BorderLayout.CENTER);*/ - super.setCursor( - Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - } - - // Override isOpaque() to prevent the glasspane from hiding the window contents: - @Override - public boolean isOpaque() { - return false; - } - } - - private class EditModeAction extends AbstractAction { - - public EditModeAction() { - initName(); - } - - public void initName() { - if (JabRefFrame.this.getCurrentBasePanel() == null) { - putValue(Action.NAME, Localization.menuTitle("Switch to %0 mode", "BibTeX/biblatex")); - } else { - BibDatabaseMode mode = JabRefFrame.this.getCurrentBasePanel().getBibDatabaseContext().getMode(); - String modeName = mode.getOppositeMode().getFormattedName(); - putValue(Action.NAME, Localization.menuTitle("Switch to %0 mode", modeName)); - } - } - - @Override - public void actionPerformed(ActionEvent evt) { - if (JabRefFrame.this.getCurrentBasePanel() == null) { - return; - } - - BibDatabaseMode newMode = JabRefFrame.this.getCurrentBasePanel().getBibDatabaseContext().getMode() - .getOppositeMode(); - JabRefFrame.this.getCurrentBasePanel().getBibDatabaseContext().setMode(newMode); - JabRefFrame.this.refreshTitleAndTabs(); - - initName(); - - // update all elements in current base panel - JabRefFrame.this.getCurrentBasePanel().hideBottomComponent(); - JabRefFrame.this.getCurrentBasePanel().updateEntryEditorIfShowing(); - } + public CountingUndoManager getUndoManager() { + return Globals.undoManager; } - private class GeneralAction extends MnemonicAwareAction { - - private final String command; - - public GeneralAction(String command, String text) { - this.command = command; - putValue(Action.NAME, text); - } - - public GeneralAction(String command, String text, String description) { - this.command = command; - putValue(Action.NAME, text); - putValue(Action.SHORT_DESCRIPTION, description); - } - - public GeneralAction(String command, String text, Icon icon) { - super(icon); - - this.command = command; - putValue(Action.NAME, text); - } - - public GeneralAction(String command, String text, String description, Icon icon) { - super(icon); - - this.command = command; - putValue(Action.NAME, text); - putValue(Action.SHORT_DESCRIPTION, description); - } - - public GeneralAction(String command, String text, KeyStroke key) { - this.command = command; - putValue(Action.NAME, text); - putValue(Action.ACCELERATOR_KEY, key); - } - - public GeneralAction(String command, String text, String description, KeyStroke key) { - this.command = command; - putValue(Action.NAME, text); - putValue(Action.SHORT_DESCRIPTION, description); - putValue(Action.ACCELERATOR_KEY, key); - } - - public GeneralAction(String command, String text, String description, KeyStroke key, Icon icon) { - super(icon); - - this.command = command; - putValue(Action.NAME, text); - putValue(Action.SHORT_DESCRIPTION, description); - putValue(Action.ACCELERATOR_KEY, key); - } - - @Override - public void actionPerformed(ActionEvent e) { - if (tabbedPane.getTabCount() > 0) { - try { - ((BasePanel) tabbedPane.getSelectedComponent()).runCommand(command); - } catch (Throwable ex) { - LOGGER.error("Problem with executing command: " + command, ex); - } - } else { - LOGGER.info("Action '" + command + "' must be disabled when no database is open."); - } - } + public DialogService getDialogService() { + return dialogService; } /** * The action concerned with closing the window. */ - private class CloseAction extends MnemonicAwareAction { - - public CloseAction() { - putValue(Action.NAME, Localization.menuTitle("Quit")); - putValue(Action.SHORT_DESCRIPTION, Localization.lang("Quit JabRef")); - putValue(Action.ACCELERATOR_KEY, Globals.getKeyPrefs().getKey(KeyBinding.QUIT_JABREF)); - } + private class CloseAction extends SimpleCommand { @Override - public void actionPerformed(ActionEvent e) { + public void execute() { quit(); - Platform.exit(); - } - } - - private class ShowPrefsAction extends MnemonicAwareAction { - - public ShowPrefsAction() { - super(IconTheme.JabRefIcon.PREFERENCES.getIcon()); - putValue(Action.NAME, Localization.menuTitle("Preferences")); - putValue(Action.SHORT_DESCRIPTION, Localization.lang("Preferences")); - } - - @Override - public void actionPerformed(ActionEvent e) { - showPreferencesDialog(); - } - } - - private class ChangeTabAction extends MnemonicAwareAction { - - private final boolean next; - - public ChangeTabAction(boolean next) { - putValue(Action.NAME, next ? Localization.menuTitle("Next tab") : - Localization.menuTitle("Previous tab")); - this.next = next; - putValue(Action.ACCELERATOR_KEY, - next ? Globals.getKeyPrefs().getKey(KeyBinding.NEXT_TAB) : Globals.getKeyPrefs().getKey(KeyBinding.PREVIOUS_TAB)); - } - - @Override - public void actionPerformed(ActionEvent e) { - int i = tabbedPane.getSelectedIndex(); - int newI = next ? i + 1 : i - 1; - if (newI < 0) { - newI = tabbedPane.getTabCount() - 1; - } - if (newI == tabbedPane.getTabCount()) { - newI = 0; - } - tabbedPane.setSelectedIndex(newI); } } /** - * Class for handling general actions; cut, copy and paste. The focused component is - * kept track of by Globals.focusListener, and we call the action stored under the - * relevant name in its action map. + * Class for handling general actions; cut, copy and paste. The focused component is kept track of by + * Globals.focusListener, and we call the action stored under the relevant name in its action map. */ - private class EditAction extends MnemonicAwareAction { + private class EditAction extends SimpleCommand { - private final String command; + private final Actions command; - public EditAction(String command, String menuTitle, String description, KeyStroke key, Icon icon) { - super(icon); + public EditAction(Actions command) { this.command = command; - putValue(Action.NAME, menuTitle); - putValue(Action.ACCELERATOR_KEY, key); - putValue(Action.SHORT_DESCRIPTION, description); - } - - @Override - public void actionPerformed(ActionEvent e) { - - LOGGER.debug(Globals.getFocusListener().getFocused().toString()); - JComponent source = Globals.getFocusListener().getFocused(); - Action action = source.getActionMap().get(command); - if (action != null) { - action.actionPerformed(new ActionEvent(source, 0, command)); - } - } - } - - private class CustomizeExportsAction extends MnemonicAwareAction { - - public CustomizeExportsAction() { - putValue(Action.NAME, Localization.menuTitle("Manage custom exports")); - } - - @Override - public void actionPerformed(ActionEvent e) { - ExportCustomizationDialog ecd = new ExportCustomizationDialog(JabRefFrame.this); - ecd.setVisible(true); - } - } - - private class CustomizeImportsAction extends MnemonicAwareAction { - - public CustomizeImportsAction() { - putValue(Action.NAME, Localization.menuTitle("Manage custom imports")); - } - - @Override - public void actionPerformed(ActionEvent e) { - ImportCustomizationDialog ecd = new ImportCustomizationDialog(JabRefFrame.this); - ecd.setVisible(true); - } - } - - private class CustomizeEntryTypeAction extends MnemonicAwareAction { - - public CustomizeEntryTypeAction() { - putValue(Action.NAME, Localization.menuTitle("Customize entry types")); - } - - @Override - public void actionPerformed(ActionEvent e) { - JDialog dl = new EntryCustomizationDialog(JabRefFrame.this); - dl.setLocationRelativeTo(JabRefFrame.this); - dl.setVisible(true); - } - } - - private class GenFieldsCustomizationAction extends MnemonicAwareAction { - - public GenFieldsCustomizationAction() { - putValue(Action.NAME, Localization.menuTitle("Set up general fields")); - } - - @Override - public void actionPerformed(ActionEvent e) { - GenFieldsCustomizer gf = new GenFieldsCustomizer(JabRefFrame.this); - gf.setLocationRelativeTo(JabRefFrame.this); - gf.setVisible(true); - } - } - - private class ProtectedTermsAction extends MnemonicAwareAction { - - public ProtectedTermsAction() { - putValue(Action.NAME, Localization.menuTitle("Manage protected terms")); - } - - @Override - public void actionPerformed(ActionEvent e) { - ProtectedTermsDialog protectTermsDialog = new ProtectedTermsDialog(JabRefFrame.this); - protectTermsDialog.setVisible(true); - } - } - - private class DatabasePropertiesAction extends MnemonicAwareAction { - - private DatabasePropertiesDialog propertiesDialog; - - public DatabasePropertiesAction() { - putValue(Action.NAME, Localization.menuTitle("Library properties")); - } - - @Override - public void actionPerformed(ActionEvent e) { - if (propertiesDialog == null) { - propertiesDialog = new DatabasePropertiesDialog(JabRefFrame.this); - } - propertiesDialog.setPanel(getCurrentBasePanel()); - propertiesDialog.updateEnableStatus(); - propertiesDialog.setLocationRelativeTo(JabRefFrame.this); - propertiesDialog.setVisible(true); - } - } - - private class BibtexKeyPatternAction extends MnemonicAwareAction { - - private BibtexKeyPatternDialog bibtexKeyPatternDialog; - - public BibtexKeyPatternAction() { - putValue(Action.NAME, Localization.lang("BibTeX key patterns")); } @Override - public void actionPerformed(ActionEvent e) { - JabRefPreferences.getInstance(); - if (bibtexKeyPatternDialog == null) { - // if no instance of BibtexKeyPatternDialog exists, create new one - bibtexKeyPatternDialog = new BibtexKeyPatternDialog(JabRefFrame.this, getCurrentBasePanel()); - } else { - // BibtexKeyPatternDialog allows for updating content based on currently selected panel - bibtexKeyPatternDialog.setPanel(getCurrentBasePanel()); + public void execute() { + Node focusOwner = mainStage.getScene().getFocusOwner(); + if (focusOwner != null) { + if (focusOwner instanceof TextInputControl) { + // Focus is on text field -> copy/paste/cut selected text + TextInputControl textInput = (TextInputControl) focusOwner; + switch (command) { + case COPY: + textInput.copy(); + break; + case CUT: + textInput.cut(); + break; + case PASTE: + textInput.paste(); + break; + default: + throw new IllegalStateException("Only cut/copy/paste supported but got " + command); + } + } else { + // Not sure what is selected -> copy/paste/cut selected entries + switch (command) { + case COPY: + getCurrentBasePanel().copy(); + break; + case CUT: + getCurrentBasePanel().cut(); + break; + case PASTE: + getCurrentBasePanel().paste(); + break; + default: + throw new IllegalStateException("Only cut/copy/paste supported but got " + command); + } + } } - bibtexKeyPatternDialog.setLocationRelativeTo(JabRefFrame.this); - bibtexKeyPatternDialog.setVisible(true); } } - private class DefaultTableFontSizeAction extends MnemonicAwareAction { - - public DefaultTableFontSizeAction() { - putValue(Action.NAME, Localization.menuTitle("Default table font size")); - putValue(Action.ACCELERATOR_KEY, Globals.getKeyPrefs().getKey(KeyBinding.DEFAULT_TABLE_FONT_SIZE)); - } - - @Override - public void actionPerformed(ActionEvent event) { - GUIGlobals.setFont(Globals.prefs.getIntDefault(JabRefPreferences.FONT_SIZE)); - for (BasePanel basePanel : getBasePanelList()) { - basePanel.updateTableFont(); - } - setStatus(Localization.lang("Table font size is %0", String.valueOf(GUIGlobals.currentFont.getSize()))); + private void setDefaultTableFontSize() { + GUIGlobals.setFont(Globals.prefs.getIntDefault(JabRefPreferences.FONT_SIZE)); + for (BasePanel basePanel : getBasePanelList()) { + basePanel.updateTableFont(); } + setStatus(Localization.lang("Table font size is %0", String.valueOf(GUIGlobals.currentFont.getSize()))); } - private class IncreaseTableFontSizeAction extends MnemonicAwareAction { - - public IncreaseTableFontSizeAction() { - putValue(Action.NAME, Localization.menuTitle("Increase table font size")); - putValue(Action.ACCELERATOR_KEY, Globals.getKeyPrefs().getKey(KeyBinding.INCREASE_TABLE_FONT_SIZE)); - } - - @Override - public void actionPerformed(ActionEvent event) { - GUIGlobals.setFont(GUIGlobals.currentFont.getSize() + 1); - for (BasePanel basePanel : getBasePanelList()) { - basePanel.updateTableFont(); - } - setStatus(Localization.lang("Table font size is %0", String.valueOf(GUIGlobals.currentFont.getSize()))); + private void increaseTableFontSize() { + GUIGlobals.setFont(GUIGlobals.currentFont.getSize() + 1); + for (BasePanel basePanel : getBasePanelList()) { + basePanel.updateTableFont(); } + setStatus(Localization.lang("Table font size is %0", String.valueOf(GUIGlobals.currentFont.getSize()))); } - private class DecreaseTableFontSizeAction extends MnemonicAwareAction { - - public DecreaseTableFontSizeAction() { - putValue(Action.NAME, Localization.menuTitle("Decrease table font size")); - putValue(Action.ACCELERATOR_KEY, Globals.getKeyPrefs().getKey(KeyBinding.DECREASE_TABLE_FONT_SIZE)); + private void decreaseTableFontSize() { + int currentSize = GUIGlobals.currentFont.getSize(); + if (currentSize < 2) { + return; } - - @Override - public void actionPerformed(ActionEvent event) { - int currentSize = GUIGlobals.currentFont.getSize(); - if (currentSize < 2) { - return; - } - GUIGlobals.setFont(currentSize - 1); - for (BasePanel basePanel : getBasePanelList()) { - basePanel.updateTableFont(); - } - setStatus(Localization.lang("Table font size is %0", String.valueOf(GUIGlobals.currentFont.getSize()))); + GUIGlobals.setFont(currentSize - 1); + for (BasePanel basePanel : getBasePanelList()) { + basePanel.updateTableFont(); } + setStatus(Localization.lang("Table font size is %0", String.valueOf(GUIGlobals.currentFont.getSize()))); } - private class CloseDatabaseAction extends MnemonicAwareAction { - - public CloseDatabaseAction() { - super(IconTheme.JabRefIcon.CLOSE.getSmallIcon()); - putValue(Action.NAME, Localization.menuTitle("Close library")); - putValue(Action.SHORT_DESCRIPTION, Localization.lang("Close the current library")); - putValue(Action.ACCELERATOR_KEY, Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DATABASE)); - } + private class CloseDatabaseAction extends SimpleCommand { @Override - public void actionPerformed(ActionEvent e) { + public void execute() { closeTab(getCurrentBasePanel()); } } - private class CloseAllDatabasesAction extends MnemonicAwareAction { - - @Override - public void actionPerformed(ActionEvent e) { - final Component[] panels = tabbedPane.getComponents(); - - for (Component p : panels) { - closeTab((BasePanel) p); - } - } - } - - private class CloseOtherDatabasesAction extends MnemonicAwareAction { - - @Override - public void actionPerformed(ActionEvent e) { - final BasePanel active = getCurrentBasePanel(); - final Component[] panels = tabbedPane.getComponents(); - - for (Component p : panels) { - if (!Objects.equals(p, active)) { - closeTab((BasePanel) p); - } - } - } - } - - private class ToolBar extends OSXCompatibleToolbar { - - public void addAction(Action a) { - JButton b = new JButton(a); - b.setText(null); - if (!OS.OS_X) { - b.setMargin(marg); - } - // create a disabled Icon for FontBasedIcons as Swing does not automatically create one - Object obj = a.getValue(Action.LARGE_ICON_KEY); - if (obj instanceof IconTheme.FontBasedIcon) { - b.setDisabledIcon(((IconTheme.FontBasedIcon) obj).createDisabledIcon()); - } - add(b); - } - - public void addJToggleButton(JToggleButton button) { - button.setText(null); - if (!OS.OS_X) { - button.setMargin(marg); - } - Object obj = button.getAction().getValue(Action.LARGE_ICON_KEY); - if (obj instanceof IconTheme.FontBasedIcon) { - button.setDisabledIcon(((IconTheme.FontBasedIcon) obj).createDisabledIcon()); - } - add(button); - } - } - private class UndoRedoEventManager { @Subscribe @@ -2398,12 +1505,14 @@ public void listen(AddUndoableActionEvent event) { } private void updateTexts(UndoChangeEvent event) { + /* TODO SwingUtilities.invokeLater(() -> { undo.putValue(Action.SHORT_DESCRIPTION, event.getUndoDescription()); undo.setEnabled(event.isCanUndo()); redo.putValue(Action.SHORT_DESCRIPTION, event.getRedoDescription()); redo.setEnabled(event.isCanRedo()); }); + */ } } } diff --git a/src/main/java/org/jabref/gui/Main.css b/src/main/java/org/jabref/gui/Main.css deleted file mode 100644 index 5f01e7afd41..00000000000 --- a/src/main/java/org/jabref/gui/Main.css +++ /dev/null @@ -1,118 +0,0 @@ -/* - * The base css file defining the style that is valid for every pane and dialog. - */ - -.root { - /* - * A strong blue to indicate something neutral. - */ - -fx-neutral: #2980b9; - - /* - * A strong green to indicate something positive. - */ - -fx-positive: #27ae60; - - /* - * A strong red to indicate something negative. - */ - -fx-negative: #c0392b; - - /* - * A dark grayish blue to display information (but kind of highlighted). - */ - -fx-info: #2c3e50; - - /* - * A light gray for accented background - */ - -fx-accented-background: #dadad8; - - /* - * A dark gray as background - */ - -fx-dark-background: #757575; - - /* - * A strong white as background - */ - -fx-light-background: #ffffff; - - /* - * A strong blue for backgrounds of active items (toggle button, selected list item) - */ - -fx-active-background: #6A9FCD; - -} - -.hyperlink { - -fx-padding: 0; - -fx-underline: false; - -fx-border-style: null; - -fx-border-color: null; -} - -.hyperlink:visited { - -fx-text-fill: -fx-accent; -} - - .icon { - -fx-font-family: 'Material Design Icons'; - -fx-font-size: 16.0; - } - -.glyph-icon { - -fx-font-size: 20.0; - -glyph-size: 20.0; -} - -.tooltip { - -fx-background-color: #616161; - -fx-opacity: 95%; - -fx-text-fill: rgba(255, 255, 255, 0.9); - -fx-font-size: 1em; -} - -.tooltip > TextFlow > Text { - -fx-fill: rgba(255, 255, 255, 0.9); - -fx-font-size: 1em; -} - -.tooltip > TextFlow > .tooltip-text-bold { - -fx-font-weight: bold; -} - -.tooltip > TextFlow > .tooltip-text-italic { - -fx-font-style: italic; -} - -.tooltip > TextFlow > .tooltip-text-monospaced { - -fx-font-family: monospace; -} - -.flatButtonNoSpaceBottom, -.flatButtonNoSpaceTop, -.flatButton { - -fx-shadow-highlight-color: transparent; - -fx-outer-border: transparent; - -fx-inner-border: transparent; - -fx-focus-color: #6A9FCD; - -fx-faint-focus-color: transparent; - -fx-background-color: transparent; - -fx-text-background-color: dimgray; - -fx-padding: 0.5em; -} - -.flatButton:selected { - -fx-background-color: -fx-active-background; - -fx-text-fill: white; - -fx-fill: white; -} - -.flatButtonNoSpaceBottom { - -fx-padding: 0.5em 0.5em -0.1em 0.5em; -} - -.flatButtonNoSpaceTop { - -fx-padding: -0.1em 0.5em 0.5em 0.5em; -} diff --git a/src/main/java/org/jabref/gui/MergeDialog.java b/src/main/java/org/jabref/gui/MergeDialog.java deleted file mode 100644 index 805e19010a2..00000000000 --- a/src/main/java/org/jabref/gui/MergeDialog.java +++ /dev/null @@ -1,121 +0,0 @@ -package org.jabref.gui; - -import java.awt.BorderLayout; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; - -import javax.swing.AbstractAction; -import javax.swing.ActionMap; -import javax.swing.BorderFactory; -import javax.swing.InputMap; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JPanel; - -import org.jabref.Globals; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.logic.l10n.Localization; - -/** - *

    Title: MergeDialog

    - *

    Description: Asks for details about merge database operation.

    - *

    Copyright: Copyright (c) 2003

    - * @author Morten O. Alver - */ - -public class MergeDialog extends JabRefDialog { - - private final JPanel panel1 = new JPanel(); - private final BorderLayout borderLayout1 = new BorderLayout(); - private final JPanel jPanel1 = new JPanel(); - private final JPanel jPanel2 = new JPanel(); - private final JButton ok = new JButton(); - private final JButton cancel = new JButton(); - private final JCheckBox entries = new JCheckBox(); - private final JCheckBox strings = new JCheckBox(); - private final GridBagLayout gridBagLayout1 = new GridBagLayout(); - private final JCheckBox groups = new JCheckBox(); - private final JCheckBox selector = new JCheckBox(); - - - private boolean okPressed; - - public MergeDialog(JabRefFrame frame, String title, boolean modal) { - super(frame, title, modal, MergeDialog.class); - jbInit(); - pack(); - } - - public boolean isOkPressed() { - return okPressed; - } - - private void jbInit() { - panel1.setLayout(borderLayout1); - ok.setText(Localization.lang("OK")); - ok.addActionListener(e -> { - okPressed = true; - dispose(); - }); - cancel.setText(Localization.lang("Cancel")); - cancel.addActionListener(e -> dispose()); - jPanel1.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - jPanel1.setLayout(gridBagLayout1); - entries.setSelected(true); - entries.setText(Localization.lang("Import entries")); - strings.setSelected(true); - strings.setText(Localization.lang("Import strings")); - groups.setText(Localization.lang("Import group definitions")); - selector.setText(Localization.lang("Import word selector definitions")); - - this.setModal(true); - this.setResizable(false); - getContentPane().add(panel1); - panel1.add(jPanel2, BorderLayout.SOUTH); - jPanel2.add(ok, null); - jPanel2.add(cancel, null); - panel1.add(jPanel1, BorderLayout.CENTER); - jPanel1.add(entries, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0 - , GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); - jPanel1.add(strings, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0 - , GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - jPanel1.add(groups, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0 - , GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - jPanel1.add(selector, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, - GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - - // Key bindings: - ActionMap am = jPanel1.getActionMap(); - InputMap im = jPanel1.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DIALOG), "close"); - am.put("close", new AbstractAction() { - - @Override - public void actionPerformed(ActionEvent e) { - dispose(); - } - }); - - } - - public boolean importEntries() { - return entries.isSelected(); - } - - public boolean importGroups() { - return groups.isSelected(); - } - - public boolean importStrings() { - return strings.isSelected(); - } - - public boolean importSelectorWords() { - return selector.isSelected(); - } -} - - diff --git a/src/main/java/org/jabref/gui/PreambleEditor.java b/src/main/java/org/jabref/gui/PreambleEditor.java deleted file mode 100644 index ffcfb095c7a..00000000000 --- a/src/main/java/org/jabref/gui/PreambleEditor.java +++ /dev/null @@ -1,214 +0,0 @@ -package org.jabref.gui; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Container; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.FocusAdapter; -import java.awt.event.FocusEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; - -import javax.swing.AbstractAction; -import javax.swing.Action; -import javax.swing.JPanel; -import javax.swing.LayoutFocusTraversalPolicy; -import javax.swing.text.JTextComponent; - -import org.jabref.Globals; -import org.jabref.gui.actions.Actions; -import org.jabref.gui.fieldeditors.FieldEditor; -import org.jabref.gui.fieldeditors.TextArea; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.gui.undo.UndoablePreambleChange; -import org.jabref.gui.util.WindowLocation; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.database.BibDatabase; -import org.jabref.preferences.JabRefPreferences; - -class PreambleEditor extends JabRefDialog { - // A reference to the entry this object works on. - private final BibDatabase database; - private final BasePanel panel; - - private final FieldEditor editor; - - private final UndoAction undoAction = new UndoAction(); - private final StoreFieldAction storeFieldAction = new StoreFieldAction(); - private final RedoAction redoAction = new RedoAction(); - // The action concerned with closing the window. - private final CloseAction closeAction = new CloseAction(); - - public PreambleEditor(JabRefFrame baseFrame, BasePanel panel, BibDatabase database) { - super(baseFrame, PreambleEditor.class); - this.panel = panel; - this.database = database; - - addWindowListener(new WindowAdapter() { - - @Override - public void windowClosing(WindowEvent e) { - closeAction.actionPerformed(null); - } - - @Override - public void windowOpened(WindowEvent e) { - editor.requestFocus(); - } - }); - setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() { - - @Override - protected boolean accept(Component c) { - return super.accept(c) && (c instanceof FieldEditor); - } - }); - - JPanel pan = new JPanel(); - GridBagLayout gbl = new GridBagLayout(); - pan.setLayout(gbl); - GridBagConstraints con = new GridBagConstraints(); - con.fill = GridBagConstraints.BOTH; - con.weighty = 1; - con.insets = new Insets(10, 5, 10, 5); - - editor = new TextArea(Localization.lang("Preamble"), database.getPreamble().orElse("")); - - // TODO: Reenable this - //setupJTextComponent((TextArea) editor); - - //gbl.setConstraints(editor.getLabel(), con); - //pan.add(editor.getLabel()); - - con.weightx = 1; - - gbl.setConstraints(editor.getPane(), con); - pan.add(editor.getPane()); - - Container conPane = getContentPane(); - conPane.add(pan, BorderLayout.CENTER); - setTitle(Localization.lang("Edit preamble")); - - WindowLocation pw = new WindowLocation(this, JabRefPreferences.PREAMBLE_POS_X, JabRefPreferences.PREAMBLE_POS_Y, - JabRefPreferences.PREAMBLE_SIZE_X, JabRefPreferences.PREAMBLE_SIZE_Y); - pw.displayWindowAtStoredLocation(); - } - - private void setupJTextComponent(JTextComponent ta) { - // Set up key bindings and focus listener for the FieldEditor. - ta.getInputMap().put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DIALOG), "close"); - ta.getActionMap().put("close", closeAction); - ta.getInputMap().put(Globals.getKeyPrefs().getKey(KeyBinding.PREAMBLE_EDITOR_STORE_CHANGES), "store"); - ta.getActionMap().put("store", storeFieldAction); - - ta.getInputMap().put(Globals.getKeyPrefs().getKey(KeyBinding.UNDO), "undo"); - ta.getActionMap().put(Actions.UNDO, undoAction); - ta.getInputMap().put(Globals.getKeyPrefs().getKey(KeyBinding.REDO), "redo"); - ta.getActionMap().put(Actions.REDO, redoAction); - - ta.addFocusListener(new FieldListener()); - } - - public void updatePreamble() { - editor.setText(database.getPreamble().orElse("")); - } - - public FieldEditor getFieldEditor() { - return editor; - } - - public void storeCurrentEdit() { - storeFieldAction.actionPerformed(null); - } - - private class FieldListener extends FocusAdapter { - - /* - * Focus listener that fires the storeFieldAction when a TextArea - * loses focus. - */ - @Override - public void focusLost(FocusEvent e) { - if (!e.isTemporary()) { - storeFieldAction.actionPerformed(new ActionEvent(e.getSource(), 0, "")); - } - } - - } - - class StoreFieldAction extends AbstractAction { - - public StoreFieldAction() { - super("Store field value"); - putValue(Action.SHORT_DESCRIPTION, "Store field value"); - } - - @Override - public void actionPerformed(ActionEvent e) { - String toSet = editor.getText(); - - // We check if the field has changed, since we don't want to mark the - // base as changed unless we have a real change. - if (!database.getPreamble().orElse("").equals(toSet)) { - panel.getUndoManager().addEdit( - new UndoablePreambleChange(database, panel, database.getPreamble().orElse(null), toSet)); - database.setPreamble(toSet); - //if ((toSet == null) || toSet.isEmpty()) { - // editor.setLabelColor(GUIGlobals.NULL_FIELD_COLOR); - //} else { - // editor.setLabelColor(GUIGlobals.ENTRY_EDITOR_LABEL_COLOR); - //} - editor.setValidBackgroundColor(); - if (editor.hasFocus()) { - editor.setActiveBackgroundColor(); - } - panel.markBaseChanged(); - } - - } - } - - class UndoAction extends AbstractAction { - - public UndoAction() { - super("Undo", IconTheme.JabRefIcon.UNDO.getIcon()); - putValue(Action.SHORT_DESCRIPTION, "Undo"); - } - - @Override - public void actionPerformed(ActionEvent e) { - panel.runCommand(Actions.UNDO); - } - } - - class RedoAction extends AbstractAction { - - public RedoAction() { - super("Redo", IconTheme.JabRefIcon.REDO.getIcon()); - putValue(Action.SHORT_DESCRIPTION, "Redo"); - } - - @Override - public void actionPerformed(ActionEvent e) { - panel.runCommand(Actions.REDO); - } - } - - class CloseAction extends AbstractAction { - - public CloseAction() { - super(Localization.lang("Close window")); - } - - @Override - public void actionPerformed(ActionEvent e) { - storeFieldAction.actionPerformed(null); - panel.preambleEditorClosing(); - dispose(); - } - } - -} diff --git a/src/main/java/org/jabref/gui/PreviewPanel.java b/src/main/java/org/jabref/gui/PreviewPanel.java index fedb268ae15..796fafdd016 100644 --- a/src/main/java/org/jabref/gui/PreviewPanel.java +++ b/src/main/java/org/jabref/gui/PreviewPanel.java @@ -1,10 +1,15 @@ package org.jabref.gui; +import java.io.File; import java.io.IOException; import java.io.StringReader; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.Future; import java.util.regex.Pattern; +import java.util.stream.Collectors; import javafx.print.PrinterJob; import javafx.scene.control.ContextMenu; @@ -12,12 +17,16 @@ import javafx.scene.control.ScrollPane; import javafx.scene.control.SeparatorMenuItem; import javafx.scene.input.ClipboardContent; +import javafx.scene.input.DataFormat; import javafx.scene.input.Dragboard; import javafx.scene.input.KeyEvent; import javafx.scene.input.TransferMode; import javafx.scene.web.WebView; import org.jabref.Globals; +import org.jabref.gui.externalfiles.ExternalFilesEntryLinker; +import org.jabref.gui.externalfiletype.ExternalFileTypes; +import org.jabref.gui.icon.IconTheme; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.util.BackgroundTask; @@ -26,6 +35,7 @@ import org.jabref.logic.exporter.ExporterFactory; import org.jabref.logic.l10n.Localization; import org.jabref.logic.layout.Layout; +import org.jabref.logic.layout.LayoutFormatterPreferences; import org.jabref.logic.layout.LayoutHelper; import org.jabref.logic.search.SearchQueryHighlightListener; import org.jabref.model.database.BibDatabaseContext; @@ -36,6 +46,9 @@ import com.google.common.eventbus.Subscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; /** * Displays an BibEntry using the given layout format. @@ -48,6 +61,9 @@ public class PreviewPanel extends ScrollPane implements SearchQueryHighlightList private final DialogService dialogService; private final KeyBindingRepository keyBindingRepository; + private final String defaultPreviewStyle = "Preview"; + private String previewStyle; + private CitationStyle citationStyle; private Optional basePanel = Optional.empty(); private boolean fixedLayout; @@ -60,58 +76,94 @@ public class PreviewPanel extends ScrollPane implements SearchQueryHighlightList /** * If a database is set, the preview will attempt to resolve strings in the previewed entry using that database. */ - private Optional databaseContext = Optional.empty(); - private WebView previewView; + private BibDatabaseContext databaseContext; + private final WebView previewView; private Optional> citationStyleFuture = Optional.empty(); + private final ExternalFilesEntryLinker fileLinker; + /** * @param panel (may be null) Only set this if the preview is associated to the main window. - * @param databaseContext (may be null) Used for resolving pdf directories for links. + * @param databaseContext Used for resolving pdf directories for links. Must not be null. */ - public PreviewPanel(BasePanel panel, BibDatabaseContext databaseContext) { - this.databaseContext = Optional.ofNullable(databaseContext); + public PreviewPanel(BasePanel panel, BibDatabaseContext databaseContext, KeyBindingRepository keyBindingRepository, PreviewPreferences preferences, DialogService dialogService, ExternalFileTypes externalFileTypes) { + this.databaseContext = Objects.requireNonNull(databaseContext); this.basePanel = Optional.ofNullable(panel); - this.clipBoardManager = new ClipBoardManager(); - this.dialogService = new FXDialogService(); - this.keyBindingRepository = Globals.getKeyPrefs(); + this.dialogService = dialogService; + this.clipBoardManager = Globals.clipboardManager; + this.keyBindingRepository = keyBindingRepository; + + fileLinker = new ExternalFilesEntryLinker(externalFileTypes, Globals.prefs.getFilePreferences(), databaseContext); + + // Set up scroll pane for preview pane + setFitToHeight(true); + setFitToWidth(true); + previewView = new WebView(); + setContent(previewView); + previewView.setContextMenuEnabled(false); + setContextMenu(createPopupMenu()); + + if (this.basePanel.isPresent()) { + // Handler for drag content of preview to different window + // only created for main window (not for windows like the search results dialog) + setOnDragDetected(event -> { + startFullDrag(); + + Dragboard dragboard = startDragAndDrop(TransferMode.COPY); + ClipboardContent content = new ClipboardContent(); + content.putHtml((String) previewView.getEngine().executeScript("window.getSelection().toString()")); + dragboard.setContent(content); + + event.consume(); + }); + } + this.previewView.setOnDragOver(event -> { + if (event.getDragboard().hasFiles()) { + event.acceptTransferModes(TransferMode.COPY, TransferMode.MOVE, TransferMode.LINK); + } + event.consume(); + }); - DefaultTaskExecutor.runInJavaFXThread(() -> { - // Set up scroll pane for preview pane - setFitToHeight(true); - setFitToWidth(true); - previewView = new WebView(); - setContent(previewView); - previewView.setContextMenuEnabled(false); - setContextMenu(createPopupMenu()); - - if (this.basePanel.isPresent()) { - // Handler for drag content of preview to different window - // only created for main window (not for windows like the search results dialog) - setOnDragDetected(event -> { - Dragboard dragboard = startDragAndDrop(TransferMode.COPY); - ClipboardContent content = new ClipboardContent(); - content.putHtml((String) previewView.getEngine().executeScript("window.getSelection().toString()")); - dragboard.setContent(content); - - event.consume(); - } - ); + this.previewView.setOnDragDropped(event -> { + BibEntry entry = this.getEntry(); + boolean success = false; + if (event.getDragboard().hasContent(DataFormat.FILES)) { + List files = event.getDragboard().getFiles().stream().map(File::toPath).collect(Collectors.toList()); + + if (event.getTransferMode() == TransferMode.MOVE) { + LOGGER.debug("Mode MOVE"); //shift on win or no modifier + fileLinker.moveFilesToFileDirAndAddToEntry(entry, files); + } + if (event.getTransferMode() == TransferMode.LINK) { + LOGGER.debug("Node LINK"); //alt on win + fileLinker.addFilesToEntry(entry, files); + } + if (event.getTransferMode() == TransferMode.COPY) { + LOGGER.debug("Mode Copy"); //ctrl on win, no modifier on Xubuntu + fileLinker.copyFilesToFileDirAndAddToEntry(entry, files); + } + success = true; } - createKeyBindings(); - updateLayout(); + + event.setDropCompleted(success); + event.consume(); + }); + + createKeyBindings(); + updateLayout(preferences, true); } private void createKeyBindings() { addEventFilter(KeyEvent.KEY_PRESSED, event -> { - Optional keyBinding = Globals.getKeyPrefs().mapToKeyBinding(event); + Optional keyBinding = keyBindingRepository.mapToKeyBinding(event); if (keyBinding.isPresent()) { switch (keyBinding.get()) { case COPY_PREVIEW: copyPreviewToClipBoard(); event.consume(); break; - case CLOSE_DIALOG: + case CLOSE: close(); event.consume(); break; @@ -122,15 +174,15 @@ private void createKeyBindings() { } private ContextMenu createPopupMenu() { - MenuItem copyPreview = new MenuItem(Localization.lang("Copy preview"), IconTheme.JabRefIcon.COPY.getGraphicNode()); + MenuItem copyPreview = new MenuItem(Localization.lang("Copy preview"), IconTheme.JabRefIcons.COPY.getGraphicNode()); copyPreview.setAccelerator(keyBindingRepository.getKeyCombination(KeyBinding.COPY_PREVIEW)); copyPreview.setOnAction(event -> copyPreviewToClipBoard()); - MenuItem printEntryPreview = new MenuItem(Localization.lang("Print entry preview"), IconTheme.JabRefIcon.PRINTED.getGraphicNode()); + MenuItem printEntryPreview = new MenuItem(Localization.lang("Print entry preview"), IconTheme.JabRefIcons.PRINTED.getGraphicNode()); printEntryPreview.setOnAction(event -> print()); - MenuItem previousPreviewLayout = new MenuItem(Localization.menuTitleFX("Previous preview layout")); + MenuItem previousPreviewLayout = new MenuItem(Localization.lang("Previous preview layout")); previousPreviewLayout.setAccelerator(keyBindingRepository.getKeyCombination(KeyBinding.PREVIOUS_PREVIEW_LAYOUT)); previousPreviewLayout.setOnAction(event -> basePanel.ifPresent(BasePanel::previousPreviewStyle)); - MenuItem nextPreviewLayout = new MenuItem(Localization.menuTitleFX("Next preview layout")); + MenuItem nextPreviewLayout = new MenuItem(Localization.lang("Next preview layout")); nextPreviewLayout.setAccelerator(keyBindingRepository.getKeyCombination(KeyBinding.NEXT_PREVIEW_LAYOUT)); nextPreviewLayout.setOnAction(event -> basePanel.ifPresent(BasePanel::nextPreviewStyle)); @@ -144,7 +196,7 @@ private ContextMenu createPopupMenu() { } public void setDatabaseContext(BibDatabaseContext databaseContext) { - this.databaseContext = Optional.ofNullable(databaseContext); + this.databaseContext = databaseContext; } public Optional getBasePanel() { @@ -156,40 +208,47 @@ public void setBasePanel(BasePanel basePanel) { } public void updateLayout(PreviewPreferences previewPreferences) { + updateLayout(previewPreferences, false); + } + + private void updateLayout(PreviewPreferences previewPreferences, boolean init) { if (fixedLayout) { LOGGER.debug("cannot change the layout because the layout is fixed"); return; } - String style = previewPreferences.getPreviewCycle().get(previewPreferences.getPreviewCyclePosition()); - - if (CitationStyle.isCitationStyleFile(style)) { - if (basePanel.isPresent()) { + String style = previewPreferences.getCurrentPreviewStyle(); + if (previewStyle == null) { + previewStyle = style; + CitationStyle.createCitationStyleFromFile(style).ifPresent(cs -> citationStyle = cs); + } + if (basePanel.isPresent() && !previewStyle.equals(style)) { + if (CitationStyle.isCitationStyleFile(style)) { layout = Optional.empty(); CitationStyle.createCitationStyleFromFile(style) - .ifPresent(citationStyle -> { - basePanel.get().getCitationStyleCache().setCitationStyle(citationStyle); - basePanel.get().output(Localization.lang("Preview style changed to: %0", citationStyle.getTitle())); - }); + .ifPresent(cs -> { + citationStyle = cs; + if (!init) { + basePanel.get().output(Localization.lang("Preview style changed to: %0", citationStyle.getTitle())); + } + }); + previewStyle = style; } } else { - updatePreviewLayout(previewPreferences.getPreviewStyle()); - basePanel.ifPresent(panel -> panel.output(Localization.lang("Preview style changed to: %0", Localization.lang("Preview")))); + previewStyle = defaultPreviewStyle; + updatePreviewLayout(previewPreferences.getPreviewStyle(), previewPreferences.getLayoutFormatterPreferences()); + if (!init) { + basePanel.get().output(Localization.lang("Preview style changed to: %0", Localization.lang("Preview"))); + } } update(); } - public void updateLayout() { - updateLayout(Globals.prefs.getPreviewPreferences()); - } - - private void updatePreviewLayout(String layoutFile) { + private void updatePreviewLayout(String layoutFile, LayoutFormatterPreferences layoutFormatterPreferences) { StringReader sr = new StringReader(layoutFile.replace("__NEWLINE__", "\n")); try { - layout = Optional.of( - new LayoutHelper(sr, Globals.prefs.getLayoutFormatterPreferences(Globals.journalAbbreviationLoader)) - .getLayoutFromText()); + layout = Optional.of(new LayoutHelper(sr, layoutFormatterPreferences).getLayoutFromText()); } catch (IOException e) { layout = Optional.empty(); LOGGER.debug("no layout could be set", e); @@ -234,22 +293,25 @@ public void update() { if (layout.isPresent()) { StringBuilder sb = new StringBuilder(); bibEntry.ifPresent(entry -> sb.append(layout.get() - .doLayout(entry, databaseContext.map(BibDatabaseContext::getDatabase).orElse(null)))); + .doLayout(entry, databaseContext.getDatabase()))); setPreviewLabel(sb.toString()); } else if (basePanel.isPresent() && bibEntry.isPresent()) { + if ((citationStyle != null) && !previewStyle.equals(defaultPreviewStyle)) { + basePanel.get().getCitationStyleCache().setCitationStyle(citationStyle); + } Future citationStyleWorker = BackgroundTask - .wrap(() -> basePanel.get().getCitationStyleCache().getCitationFor(bibEntry.get())) - .onRunning(() -> { - CitationStyle citationStyle = basePanel.get().getCitationStyleCache().getCitationStyle(); - setPreviewLabel("" + Localization.lang("Processing %0", Localization.lang("Citation Style")) + - ": " + citationStyle.getTitle() + " ..." + ""); - }) - .onSuccess(this::setPreviewLabel) - .onFailure(exception -> { - LOGGER.error("Error while generating citation style", exception); - setPreviewLabel(Localization.lang("Error while generating citation style")); - }) - .executeWith(Globals.TASK_EXECUTOR); + .wrap(() -> basePanel.get().getCitationStyleCache().getCitationFor(bibEntry.get())) + .onRunning(() -> { + CitationStyle citationStyle = basePanel.get().getCitationStyleCache().getCitationStyle(); + setPreviewLabel("" + Localization.lang("Processing %0", Localization.lang("Citation Style")) + + ": " + citationStyle.getTitle() + " ..." + ""); + }) + .onSuccess(this::setPreviewLabel) + .onFailure(exception -> { + LOGGER.error("Error while generating citation style", exception); + setPreviewLabel(Localization.lang("Error while generating citation style")); + }) + .executeWith(Globals.TASK_EXECUTOR); this.citationStyleFuture = Optional.of(citationStyleWorker); } } @@ -274,7 +336,7 @@ public void highlightPattern(Optional newPattern) { */ public void setFixedLayout(String layout) { this.fixedLayout = true; - updatePreviewLayout(layout); + updatePreviewLayout(layout, Globals.prefs.getLayoutFormatterPreferences(Globals.journalAbbreviationLoader)); } public void print() { @@ -290,16 +352,30 @@ public void print() { job.endJob(); return null; }) - .onFailure(exception -> dialogService.showErrorDialogAndWait(Localization.lang("Could not print preview"), exception)) - .executeWith(Globals.TASK_EXECUTOR); + .onFailure(exception -> dialogService.showErrorDialogAndWait(Localization.lang("Could not print preview"), exception)) + .executeWith(Globals.TASK_EXECUTOR); } public void close() { - basePanel.ifPresent(BasePanel::hideBottomComponent); + basePanel.ifPresent(BasePanel::closeBottomPane); } private void copyPreviewToClipBoard() { - String previewContent = (String) previewView.getEngine().executeScript("document.documentElement.outerHTML"); - clipBoardManager.setClipboardContents(previewContent); + StringBuilder previewStringContent = new StringBuilder(); + Document document = previewView.getEngine().getDocument(); + + NodeList nodeList = document.getElementsByTagName("html"); + + //Nodelist does not implement iterable + for (int i = 0; i < nodeList.getLength(); i++) { + Element element = (Element) nodeList.item(i); + previewStringContent.append(element.getTextContent()); + } + + ClipboardContent content = new ClipboardContent(); + content.putString(previewStringContent.toString()); + content.putHtml((String) previewView.getEngine().executeScript("document.documentElement.outerHTML")); + + clipBoardManager.setContent(content); } } diff --git a/src/main/java/org/jabref/gui/ReplaceStringDialog.java b/src/main/java/org/jabref/gui/ReplaceStringDialog.java deleted file mode 100644 index 6d5175bd9be..00000000000 --- a/src/main/java/org/jabref/gui/ReplaceStringDialog.java +++ /dev/null @@ -1,222 +0,0 @@ -package org.jabref.gui; - -import java.awt.BorderLayout; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.Locale; - -import javax.swing.AbstractAction; -import javax.swing.ActionMap; -import javax.swing.BorderFactory; -import javax.swing.ButtonGroup; -import javax.swing.InputMap; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JRadioButton; -import javax.swing.JTextField; - -import org.jabref.Globals; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableFieldChange; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.entry.BibEntry; - -/** - * Dialog for replacing strings. - */ -class ReplaceStringDialog extends JabRefDialog { - - private final JTextField fieldsField = new JTextField("", 30); - private final JTextField fromField = new JTextField("", 30); - private final JTextField toField = new JTextField("", 30); - - private final JCheckBox selOnly = new JCheckBox(Localization.lang("Limit to selected entries"), false); - private final JRadioButton allFi = new JRadioButton(Localization.lang("All fields"), true); - private final JRadioButton field = new JRadioButton(Localization.lang("Limit to fields") + ":", false); - private boolean okPressed; - private String[] fieldStrings; - private String fromString; - private String toString; - - - public ReplaceStringDialog(JabRefFrame parent) { - super(parent, Localization.lang("Replace string"), true, ReplaceStringDialog.class); - - ButtonGroup bg = new ButtonGroup(); - bg.add(allFi); - bg.add(field); - ActionListener okListener = e -> { - fromString = fromField.getText(); - toString = toField.getText(); - if ("".equals(fromString)) { - return; - } - okPressed = true; - fieldStrings = fieldsField.getText().toLowerCase(Locale.ROOT).split(";"); - dispose(); - }; - JButton ok = new JButton(Localization.lang("OK")); - ok.addActionListener(okListener); - toField.addActionListener(okListener); - fieldsField.addActionListener(okListener); - AbstractAction cancelAction = new AbstractAction() { - - @Override - public void actionPerformed(ActionEvent e) { - dispose(); - } - }; - JButton cancel = new JButton(Localization.lang("Cancel")); - cancel.addActionListener(cancelAction); - - // Key bindings: - JPanel settings = new JPanel(); - ActionMap am = settings.getActionMap(); - InputMap im = settings.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DIALOG), "close"); - am.put("close", cancelAction); - - // Layout starts here. - GridBagLayout gbl = new GridBagLayout(); - settings.setLayout(gbl); - JPanel opt = new JPanel(); - opt.setLayout(gbl); - JPanel main = new JPanel(); - main.setLayout(gbl); - - settings.setBorder(BorderFactory.createTitledBorder - (BorderFactory.createEtchedBorder(), - Localization.lang("Replace string"))); - main.setBorder(BorderFactory.createTitledBorder - (BorderFactory.createEtchedBorder(), - Localization.lang("Strings"))); - - // Settings panel: - GridBagConstraints con = new GridBagConstraints(); - con.fill = GridBagConstraints.HORIZONTAL; - con.gridwidth = 2; - con.weightx = 0; - con.anchor = GridBagConstraints.WEST; - con.gridy = 0; - con.gridx = 0; - con.insets = new Insets(3, 5, 3, 5); - gbl.setConstraints(selOnly, con); - settings.add(selOnly); - con.gridy = 1; - con.insets = new Insets(13, 5, 3, 5); - gbl.setConstraints(allFi, con); - settings.add(allFi); - con.gridwidth = 1; - con.gridy = 2; - con.gridx = 0; - con.insets = new Insets(3, 5, 3, 5); - gbl.setConstraints(field, con); - settings.add(field); - con.gridx = 1; - con.weightx = 1; - gbl.setConstraints(fieldsField, con); - settings.add(fieldsField); - - con.weightx = 0; - con.gridx = 0; - con.gridy = 0; - JLabel fl = new JLabel(Localization.lang("Search for") + ":"); - gbl.setConstraints(fl, con); - main.add(fl); - con.gridy = 1; - JLabel tl = new JLabel(Localization.lang("Replace with") + ":"); - gbl.setConstraints(tl, con); - main.add(tl); - con.weightx = 1; - con.gridx = 1; - con.gridy = 0; - gbl.setConstraints(fromField, con); - main.add(fromField); - con.gridy = 1; - gbl.setConstraints(toField, con); - main.add(toField); - - // Option buttons: - con.gridx = GridBagConstraints.RELATIVE; - con.gridy = GridBagConstraints.RELATIVE; - con.weightx = 1; - con.gridwidth = 1; - con.anchor = GridBagConstraints.EAST; - con.fill = GridBagConstraints.NONE; - gbl.setConstraints(ok, con); - opt.add(ok); - con.anchor = GridBagConstraints.WEST; - con.gridwidth = GridBagConstraints.REMAINDER; - gbl.setConstraints(cancel, con); - opt.add(cancel); - - getContentPane().add(main, BorderLayout.NORTH); - getContentPane().add(settings, BorderLayout.CENTER); - getContentPane().add(opt, BorderLayout.SOUTH); - - pack(); - - this.setLocationRelativeTo(parent); - } - - public boolean okPressed() { - return okPressed; - } - - private boolean allFields() { - return allFi.isSelected(); - } - - public boolean selOnly() { - return selOnly.isSelected(); - } - - /** - * Does the actual operation on a Bibtex entry based on the - * settings specified in this same dialog. Returns the number of - * occurences replaced. - */ - public int replace(BibEntry be, NamedCompound ce) { - int counter = 0; - if (allFields()) { - for (String s : be.getFieldNames()) { - counter += replaceField(be, s, ce); - } - } else { - for (String fld : fieldStrings) { - counter += replaceField(be, fld, ce); - } - } - return counter; - } - - private int replaceField(BibEntry be, String fieldname, NamedCompound ce) { - if (!be.hasField(fieldname)) { - return 0; - } - String txt = be.getField(fieldname).get(); - StringBuilder sb = new StringBuilder(); - int ind; - int piv = 0; - int counter = 0; - int len1 = fromString.length(); - while ((ind = txt.indexOf(fromString, piv)) >= 0) { - counter++; - sb.append(txt.substring(piv, ind)); // Text leading up to s1 - sb.append(toString); // Insert s2 - piv = ind + len1; - } - sb.append(txt.substring(piv)); - String newStr = sb.toString(); - be.setField(fieldname, newStr); - ce.addEdit(new UndoableFieldChange(be, fieldname, txt, newStr)); - return counter; - } -} diff --git a/src/main/java/org/jabref/gui/SaveOrderConfigDisplay.fxml b/src/main/java/org/jabref/gui/SaveOrderConfigDisplay.fxml new file mode 100644 index 00000000000..4bee6851ab0 --- /dev/null +++ b/src/main/java/org/jabref/gui/SaveOrderConfigDisplay.fxml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/jabref/gui/SaveOrderConfigDisplay.java b/src/main/java/org/jabref/gui/SaveOrderConfigDisplay.java deleted file mode 100644 index a2d8d023174..00000000000 --- a/src/main/java/org/jabref/gui/SaveOrderConfigDisplay.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.jabref.gui; - -import java.awt.Component; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Objects; - -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JPanel; - -import org.jabref.logic.l10n.Localization; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.InternalBibtexFields; -import org.jabref.model.metadata.SaveOrderConfig; - -import com.jgoodies.forms.builder.FormBuilder; -import com.jgoodies.forms.layout.FormLayout; - -public class SaveOrderConfigDisplay { - - private JPanel panel; - private JComboBox savePriSort; - private JComboBox saveSecSort; - private JComboBox saveTerSort; - private JCheckBox savePriDesc; - private JCheckBox saveSecDesc; - private JCheckBox saveTerDesc; - - - public SaveOrderConfigDisplay() { - init(); - } - - private void init() { - List fieldNames = InternalBibtexFields.getAllPublicFieldNames(); - fieldNames.add(BibEntry.KEY_FIELD); - Collections.sort(fieldNames); - String[] allPlusKey = fieldNames.toArray(new String[fieldNames.size()]); - savePriSort = new JComboBox<>(allPlusKey); - savePriSort.setEditable(true); - saveSecSort = new JComboBox<>(allPlusKey); - saveSecSort.setEditable(true); - saveTerSort = new JComboBox<>(allPlusKey); - saveTerSort.setEditable(true); - - savePriDesc = new JCheckBox(Localization.lang("Descending")); - saveSecDesc = new JCheckBox(Localization.lang("Descending")); - saveTerDesc = new JCheckBox(Localization.lang("Descending")); - - FormLayout layout = new FormLayout("right:pref, 8dlu, fill:pref, 4dlu, fill:60dlu, 4dlu, left:pref", - "pref, 2dlu, pref, 2dlu, pref"); - FormBuilder builder = FormBuilder.create().layout(layout); - builder.add(Localization.lang("Primary sort criterion")).xy(1, 1); - builder.add(savePriSort).xy(3, 1); - builder.add(savePriDesc).xy(5, 1); - - builder.add(Localization.lang("Secondary sort criterion")).xy(1, 3); - builder.add(saveSecSort).xy(3, 3); - builder.add(saveSecDesc).xy(5, 3); - - builder.add(Localization.lang("Tertiary sort criterion")).xy(1, 5); - builder.add(saveTerSort).xy(3, 5); - builder.add(saveTerDesc).xy(5, 5); - - panel = builder.build(); - } - - public Component getPanel() { - return panel; - } - - public void setEnabled(boolean enabled) { - savePriSort.setEnabled(enabled); - savePriDesc.setEnabled(enabled); - saveSecSort.setEnabled(enabled); - saveSecDesc.setEnabled(enabled); - saveTerSort.setEnabled(enabled); - saveTerDesc.setEnabled(enabled); - } - - public void setSaveOrderConfig(SaveOrderConfig saveOrderConfig) { - Objects.requireNonNull(saveOrderConfig); - - savePriSort.setSelectedItem(saveOrderConfig.sortCriteria[0].field); - savePriDesc.setSelected(saveOrderConfig.sortCriteria[0].descending); - saveSecSort.setSelectedItem(saveOrderConfig.sortCriteria[1].field); - saveSecDesc.setSelected(saveOrderConfig.sortCriteria[1].descending); - saveTerSort.setSelectedItem(saveOrderConfig.sortCriteria[2].field); - saveTerDesc.setSelected(saveOrderConfig.sortCriteria[2].descending); - - } - - public SaveOrderConfig getSaveOrderConfig() { - SaveOrderConfig saveOrderConfig = new SaveOrderConfig(); - saveOrderConfig.sortCriteria[0].field = getSelectedItemAsLowerCaseTrim(savePriSort); - saveOrderConfig.sortCriteria[0].descending = savePriDesc.isSelected(); - saveOrderConfig.sortCriteria[1].field = getSelectedItemAsLowerCaseTrim(saveSecSort); - saveOrderConfig.sortCriteria[1].descending = saveSecDesc.isSelected(); - saveOrderConfig.sortCriteria[2].field = getSelectedItemAsLowerCaseTrim(saveTerSort); - saveOrderConfig.sortCriteria[2].descending = saveTerDesc.isSelected(); - - return saveOrderConfig; - } - - private String getSelectedItemAsLowerCaseTrim(JComboBox sortBox) { - return sortBox.getSelectedItem().toString().toLowerCase(Locale.ROOT).trim(); - } -} diff --git a/src/main/java/org/jabref/gui/SaveOrderConfigDisplayView.java b/src/main/java/org/jabref/gui/SaveOrderConfigDisplayView.java new file mode 100644 index 00000000000..aeca6f4ddad --- /dev/null +++ b/src/main/java/org/jabref/gui/SaveOrderConfigDisplayView.java @@ -0,0 +1,81 @@ +package org.jabref.gui; + +import javax.inject.Inject; + +import javafx.fxml.FXML; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ComboBox; +import javafx.scene.control.RadioButton; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.GridPane; + +import org.jabref.logic.l10n.Localization; +import org.jabref.model.metadata.SaveOrderConfig; +import org.jabref.preferences.PreferencesService; + +import com.airhacks.afterburner.views.ViewLoader; + +public class SaveOrderConfigDisplayView extends GridPane { + + private final SaveOrderConfig config; + + @FXML private ToggleGroup saveOrderToggleGroup; + @FXML private ComboBox savePriSort; + @FXML private ComboBox saveSecSort; + @FXML private ComboBox saveTerSort; + @FXML private RadioButton exportInSpecifiedOrder; + @FXML private RadioButton exportInTableOrder; + @FXML private RadioButton exportInOriginalOrder; + @FXML private CheckBox savePriDesc; + @FXML private CheckBox saveSecDesc; + @FXML private CheckBox saveTerDesc; + @Inject private PreferencesService preferencesService; + + private SaveOrderConfigDisplayViewModel viewModel; + + public SaveOrderConfigDisplayView(SaveOrderConfig config) { + this.config = config; + + ViewLoader.view(this) + .root(this) + .load(); + } + + @FXML + private void initialize() { + + viewModel = new SaveOrderConfigDisplayViewModel(config, preferencesService); + + exportInSpecifiedOrder.selectedProperty().bindBidirectional(viewModel.saveInSpecifiedOrderProperty()); + exportInTableOrder.selectedProperty().bindBidirectional(viewModel.saveInTableOrderProperty()); + exportInOriginalOrder.selectedProperty().bindBidirectional(viewModel.saveInOriginalProperty()); + + savePriSort.itemsProperty().bindBidirectional(viewModel.priSortFieldsProperty()); + saveSecSort.itemsProperty().bindBidirectional(viewModel.secSortFieldsProperty()); + saveTerSort.itemsProperty().bindBidirectional(viewModel.terSortFieldsProperty()); + + savePriSort.valueProperty().bindBidirectional(viewModel.savePriSortSelectedValueProperty()); + saveSecSort.valueProperty().bindBidirectional(viewModel.saveSecSortSelectedValueProperty()); + saveTerSort.valueProperty().bindBidirectional(viewModel.saveTerSortSelectedValueProperty()); + + savePriDesc.selectedProperty().bindBidirectional(viewModel.savePriDescPropertySelected()); + saveSecDesc.selectedProperty().bindBidirectional(viewModel.saveSecDescPropertySelected()); + saveTerDesc.selectedProperty().bindBidirectional(viewModel.saveTerDescPropertySelected()); + + } + + public void changeExportDescriptionToSave() { + exportInOriginalOrder.setText(Localization.lang("Save entries in their original order")); + exportInSpecifiedOrder.setText(Localization.lang("Save entries ordered as specified")); + exportInTableOrder.setText(Localization.lang("Save in current table sort order")); + } + + public void storeConfig() { + viewModel.storeConfigInPrefs(); + } + + public SaveOrderConfig getSaveOrderConfig() { + return viewModel.getSaveOrderConfig(); + } + +} diff --git a/src/main/java/org/jabref/gui/SaveOrderConfigDisplayViewModel.java b/src/main/java/org/jabref/gui/SaveOrderConfigDisplayViewModel.java new file mode 100644 index 00000000000..0846ee4866c --- /dev/null +++ b/src/main/java/org/jabref/gui/SaveOrderConfigDisplayViewModel.java @@ -0,0 +1,141 @@ +package org.jabref.gui; + +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ListProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; + +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.InternalBibtexFields; +import org.jabref.model.metadata.SaveOrderConfig; +import org.jabref.model.metadata.SaveOrderConfig.SortCriterion; +import org.jabref.preferences.PreferencesService; + +public class SaveOrderConfigDisplayViewModel { + + private final ListProperty priSortFieldsProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty secSortFieldsProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty terSortFieldsProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + + private final BooleanProperty savePriDescPropertySelected = new SimpleBooleanProperty(); + private final BooleanProperty saveSecDescPropertySelected = new SimpleBooleanProperty(); + private final BooleanProperty saveTerDescPropertySelected = new SimpleBooleanProperty(); + + private final StringProperty savePriSortSelectedValueProperty = new SimpleStringProperty(""); + private final StringProperty saveSecSortSelectedValueProperty = new SimpleStringProperty(""); + private final StringProperty saveTerSortSelectedValueProperty = new SimpleStringProperty(""); + + private final BooleanProperty saveInOriginalProperty = new SimpleBooleanProperty(); + private final BooleanProperty saveInTableOrderProperty = new SimpleBooleanProperty(); + private final BooleanProperty saveInSpecifiedOrderProperty = new SimpleBooleanProperty(); + + private final PreferencesService prefs; + + public SaveOrderConfigDisplayViewModel(SaveOrderConfig config, PreferencesService prefs) { + this.prefs = prefs; + + List fieldNames = InternalBibtexFields.getAllPublicFieldNames(); + fieldNames.add(BibEntry.KEY_FIELD); + Collections.sort(fieldNames); + + priSortFieldsProperty.addAll(fieldNames); + secSortFieldsProperty.addAll(fieldNames); + terSortFieldsProperty.addAll(fieldNames); + + setSaveOrderConfig(config); + } + + public ListProperty priSortFieldsProperty() { + return priSortFieldsProperty; + } + + public ListProperty secSortFieldsProperty() { + return secSortFieldsProperty; + } + + public ListProperty terSortFieldsProperty() { + return terSortFieldsProperty; + } + + public SaveOrderConfig getSaveOrderConfig() { + SortCriterion primary = new SortCriterion(getSelectedItemAsLowerCaseTrim(savePriSortSelectedValueProperty), savePriDescPropertySelected.getValue()); + SortCriterion secondary = new SortCriterion(getSelectedItemAsLowerCaseTrim(saveSecSortSelectedValueProperty), saveSecDescPropertySelected.getValue()); + SortCriterion tertiary = new SortCriterion(getSelectedItemAsLowerCaseTrim(saveTerSortSelectedValueProperty), saveTerDescPropertySelected.getValue()); + + SaveOrderConfig saveOrderConfig = new SaveOrderConfig(saveInOriginalProperty.getValue(), saveInSpecifiedOrderProperty.getValue(), primary, secondary, tertiary); + + return saveOrderConfig; + } + + public void setSaveOrderConfig(SaveOrderConfig saveOrderConfig) { + Objects.requireNonNull(saveOrderConfig); + + savePriSortSelectedValueProperty.setValue(saveOrderConfig.getSortCriteria().get(0).field); + savePriDescPropertySelected.setValue(saveOrderConfig.getSortCriteria().get(0).descending); + saveSecSortSelectedValueProperty.setValue(saveOrderConfig.getSortCriteria().get(1).field); + saveSecDescPropertySelected.setValue(saveOrderConfig.getSortCriteria().get(1).descending); + saveTerSortSelectedValueProperty.setValue(saveOrderConfig.getSortCriteria().get(2).field); + saveTerDescPropertySelected.setValue(saveOrderConfig.getSortCriteria().get(2).descending); + + if (saveOrderConfig.saveInOriginalOrder()) { + saveInOriginalProperty.setValue(true); + } else if (saveOrderConfig.saveInSpecifiedOrder()) { + saveInSpecifiedOrderProperty.setValue(true); + } else { + saveInTableOrderProperty.setValue(true); + } + + } + + private String getSelectedItemAsLowerCaseTrim(StringProperty string) { + return string.getValue().toLowerCase(Locale.ROOT).trim(); + } + + public BooleanProperty savePriDescPropertySelected() { + return savePriDescPropertySelected; + } + + public BooleanProperty saveSecDescPropertySelected() { + return saveSecDescPropertySelected; + } + + public BooleanProperty saveTerDescPropertySelected() { + return saveTerDescPropertySelected; + } + + public StringProperty savePriSortSelectedValueProperty() { + return savePriSortSelectedValueProperty; + } + + public StringProperty saveSecSortSelectedValueProperty() { + return saveSecSortSelectedValueProperty; + } + + public StringProperty saveTerSortSelectedValueProperty() { + return saveTerSortSelectedValueProperty; + } + + public void storeConfigInPrefs() { + prefs.storeExportSaveOrder(this.getSaveOrderConfig()); + } + + public BooleanProperty saveInOriginalProperty() { + return saveInOriginalProperty; + } + + public BooleanProperty saveInTableOrderProperty() { + return saveInTableOrderProperty; + } + + public BooleanProperty saveInSpecifiedOrderProperty() { + return this.saveInSpecifiedOrderProperty; + } +} diff --git a/src/main/java/org/jabref/gui/SidePane.java b/src/main/java/org/jabref/gui/SidePane.java index 353b74bfc6f..0e6c47dd815 100644 --- a/src/main/java/org/jabref/gui/SidePane.java +++ b/src/main/java/org/jabref/gui/SidePane.java @@ -1,94 +1,32 @@ package org.jabref.gui; -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; import java.util.Collection; -import javax.swing.Box; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.ScrollPaneConstants; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; /** - * The side pane is displayed at the left side of JabRef and shows instances of - * SidePaneComponents, for instance the GroupSelector, or the SearchManager. + * The side pane is displayed at the left side of JabRef and shows instances of {@link SidePaneComponent}. */ -public class SidePane extends JPanel { - - private final Dimension PREFERRED_SIZE = new Dimension(220, 100); - - private final GridBagLayout gridBagLayout = new GridBagLayout(); - - private final GridBagConstraints constraint = new GridBagConstraints(); - - private final JPanel mainPanel = new JPanel(); +public class SidePane extends BorderPane { + private final VBox mainPanel = new VBox(); public SidePane() { - // For debugging the border: - // setBorder(BorderFactory.createLineBorder(Color.BLUE)); - - setLayout(new BorderLayout()); - mainPanel.setLayout(gridBagLayout); - - // Initialize constraint - constraint.anchor = GridBagConstraints.NORTH; - constraint.fill = GridBagConstraints.BOTH; - constraint.gridwidth = GridBagConstraints.REMAINDER; - constraint.insets = new Insets(1, 1, 1, 1); - constraint.gridheight = 1; - constraint.weightx = 1; - - /* - * Added Scrollpane to fix: - */ - JScrollPane sp = new JScrollPane(mainPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, - ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); - sp.setBorder(null); - - // To remove the scroll panel just change sp to mainPanel and comment - // the JScrollPane declaration - super.add(sp); + setId("sidePane"); + setCenter(mainPanel); } - public void setComponents(Collection comps) { - mainPanel.removeAll(); + public void setComponents(Collection components) { + mainPanel.getChildren().clear(); - int totalWeights = 0; - for (SidePaneComponent c : comps) { - constraint.weighty = c.getRescalingWeight(); - totalWeights += c.getRescalingWeight(); - gridBagLayout.setConstraints(c, constraint); - mainPanel.add(c); + for (SidePaneComponent component : components) { + BorderPane node = new BorderPane(); + node.getStyleClass().add("sidePaneComponent"); + node.setTop(component.getHeader()); + node.setCenter(component.getContentPane()); + mainPanel.getChildren().add(node); + VBox.setVgrow(node, component.getResizePolicy()); } - if (totalWeights <= 0) { - // Fill vertical space so that components start at top - constraint.weighty = 1; - Component bx = Box.createVerticalGlue(); - gridBagLayout.setConstraints(bx, constraint); - mainPanel.add(bx); - } - - revalidate(); - repaint(); - } - - @Override - public void remove(Component c) { - mainPanel.remove(c); - } - - @Override - public Dimension getMaximumSize() { - return getPreferredSize(); - } - - @Override - public Dimension getPreferredSize() { - return PREFERRED_SIZE; } } diff --git a/src/main/java/org/jabref/gui/SidePaneComponent.java b/src/main/java/org/jabref/gui/SidePaneComponent.java index d6d54678b57..603a1bb1407 100644 --- a/src/main/java/org/jabref/gui/SidePaneComponent.java +++ b/src/main/java/org/jabref/gui/SidePaneComponent.java @@ -1,165 +1,158 @@ package org.jabref.gui; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.InputEvent; - -import javax.swing.Action; -import javax.swing.BorderFactory; -import javax.swing.Icon; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JToolBar; -import javax.swing.KeyStroke; - -import org.jabref.gui.actions.MnemonicAwareAction; - -public abstract class SidePaneComponent extends JPanel { - - protected final JButton close = new JButton(IconTheme.JabRefIcon.CLOSE.getSmallIcon()); - - protected final SidePaneManager manager; - - protected BasePanel panel; - - - public SidePaneComponent(SidePaneManager manager, Icon icon, String title) { - super(new BorderLayout()); +import java.util.Collections; +import java.util.List; + +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; + +import org.jabref.gui.actions.Action; +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.icon.JabRefIcon; +import org.jabref.logic.l10n.Localization; + +public abstract class SidePaneComponent { + + private final SidePaneManager manager; + private final ToggleCommand toggleCommand; + private final JabRefIcon icon; + private final String title; + private Node contentNode; + + public SidePaneComponent(SidePaneManager manager, JabRefIcon icon, String title) { this.manager = manager; + this.icon = icon; + this.title = title; + this.toggleCommand = new ToggleCommand(this); - setBorder(BorderFactory.createEmptyBorder()); - - close.setMargin(new Insets(0, 0, 0, 0)); - close.setBorder(null); - close.addActionListener(e -> hideAway()); - - JButton up = new JButton(IconTheme.JabRefIcon.UP.getSmallIcon()); - up.setMargin(new Insets(0, 0, 0, 0)); - up.setBorder(null); - up.addActionListener(e -> moveUp()); - - JButton down = new JButton(IconTheme.JabRefIcon.DOWN.getSmallIcon()); - down.setMargin(new Insets(0, 0, 0, 0)); - down.setBorder(null); - down.addActionListener(e -> moveDown()); - - JPanel titlePanel = new JPanel(new BorderLayout()); - titlePanel.add(new JLabel(icon), BorderLayout.WEST); - JLabel titleLabel = new JLabel(title); - titleLabel.setOpaque(true); - titleLabel.setForeground(new Color(79, 95, 143)); - titlePanel.add(titleLabel, BorderLayout.CENTER); - - JToolBar toolbar = new OSXCompatibleToolbar(); - toolbar.add(up); - toolbar.add(down); - toolbar.add(close); - toolbar.setOpaque(false); - toolbar.setFloatable(false); - - titlePanel.add(toolbar, BorderLayout.EAST); - - - this.add(titlePanel, BorderLayout.NORTH); } - public void setContentContainer(JPanel panel) { - this.add(panel, BorderLayout.CENTER); + protected void hide() { + manager.hide(this.getType()); } - private void hideAway() { - manager.hideComponent(this); + protected void show() { + manager.show(this.getType()); } - private void moveUp() { + protected void moveUp() { manager.moveUp(this); } - private void moveDown() { + protected void moveDown() { manager.moveDown(this); } - public void setActiveBasePanel(BasePanel panel) { - this.panel = panel; + /** + * Override this method if the component needs to make any changes before it can close. + */ + public void beforeClosing() { + // Nothing to do by default } - public BasePanel getActiveBasePanel() { - return panel; + /** + * Override this method if the component needs to do any actions after it is shown. + */ + public void afterOpening() { + // Nothing to do by default } /** - * Override this method if the component needs to make any changes before it can close. + * Specifies how to this side pane component behaves if there is additional vertical space. + */ + public abstract Priority getResizePolicy(); + + /** + * @return the command which toggles this {@link SidePaneComponent} + */ + public ToggleCommand getToggleCommand() { + return toggleCommand; + } + + /** + * @return the action to toggle this {@link SidePaneComponent} + */ + public abstract Action getToggleAction(); + + /** + * @return the content of this component */ - public void componentClosing() { - // Nothing right now + public final Node getContentPane() { + if (contentNode == null) { + contentNode = createContentPane(); + } + + return contentNode; } /** - * Override this method if the component needs to do any actions when opening. + * @return the header pane for this component */ - public void componentOpening() { - // Nothing right now + public final Node getHeader() { + Button close = IconTheme.JabRefIcons.CLOSE.asButton(); + close.setTooltip(new Tooltip(Localization.lang("Hide panel"))); + close.setOnAction(event -> hide()); + + Button up = IconTheme.JabRefIcons.UP.asButton(); + up.setTooltip(new Tooltip(Localization.lang("Move panel up"))); + up.setOnAction(event -> moveUp()); + + Button down = IconTheme.JabRefIcons.DOWN.asButton(); + down.setTooltip(new Tooltip(Localization.lang("Move panel down"))); + down.setOnAction(event -> moveDown()); + + final HBox buttonContainer = new HBox(); + buttonContainer.getChildren().addAll(up, down); + buttonContainer.getChildren().addAll(getAdditionalHeaderButtons()); + buttonContainer.getChildren().add(close); + + BorderPane graphic = new BorderPane(); + graphic.setCenter(icon.getGraphicNode()); + + final Label label = new Label(title); + BorderPane container = new BorderPane(); + container.setCenter(label); + container.setRight(buttonContainer); + container.getStyleClass().add("sidePaneComponentHeader"); + + return container; } - @Override - public Dimension getMinimumSize() { - return getPreferredSize(); + protected List getAdditionalHeaderButtons() { + return Collections.emptyList(); } /** - * Specifies how to distribute extra vertical space between side pane components. - * 0: fixed height, 1: fill the remaining space + * Create the content of this component + * + * @implNote The {@link SidePaneManager} always creates an instance of every side component (e.g., to get the toggle action) + * but we only want to create the content view if the component is shown to save resources. + * This is the reason for the lazy loading. */ - public abstract int getRescalingWeight(); + protected abstract Node createContentPane(); /** - * @return the action which toggles this {@link SidePaneComponent} + * @return the type of this component */ - public abstract ToggleAction getToggleAction(); + public abstract SidePaneType getType(); - public class ToggleAction extends MnemonicAwareAction { + public class ToggleCommand extends SimpleCommand { - public ToggleAction(String text, String description, KeyStroke key, IconTheme.JabRefIcon icon) { - super(icon.getIcon()); - putValue(Action.NAME, text); - putValue(Action.ACCELERATOR_KEY, key); - putValue(Action.SHORT_DESCRIPTION, description); - } + private final SidePaneComponent component; - public ToggleAction(String text, String description, KeyStroke key, Icon icon) { - super(icon); - putValue(Action.NAME, text); - putValue(Action.ACCELERATOR_KEY, key); - putValue(Action.SHORT_DESCRIPTION, description); + public ToggleCommand(SidePaneComponent component) { + this.component = component; } @Override - public void actionPerformed(ActionEvent e) { - if (!manager.hasComponent(SidePaneComponent.this.getClass())) { - manager.register(SidePaneComponent.this); - } - - // if clicked by mouse just toggle - if ((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0) { - manager.toggle(SidePaneComponent.this.getClass()); - } else { - manager.toggleThreeWay(SidePaneComponent.this.getClass()); - } - putValue(Action.SELECTED_KEY, manager.isComponentVisible(SidePaneComponent.this.getClass())); - } - - public void setSelected(boolean selected) { - putValue(Action.SELECTED_KEY, selected); - } - - public boolean isSelected() { - return Boolean.TRUE.equals(getValue(Action.SELECTED_KEY)); + public void execute() { + manager.toggle(component.getType()); } - } - } diff --git a/src/main/java/org/jabref/gui/SidePaneManager.java b/src/main/java/org/jabref/gui/SidePaneManager.java index d9af9434c18..324810dd6ed 100644 --- a/src/main/java/org/jabref/gui/SidePaneManager.java +++ b/src/main/java/org/jabref/gui/SidePaneManager.java @@ -1,233 +1,141 @@ package org.jabref.gui; -import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; - -import javax.swing.SwingUtilities; +import java.util.stream.Stream; import org.jabref.Globals; -import org.jabref.gui.maintable.MainTable; +import org.jabref.gui.groups.GroupSidePane; +import org.jabref.gui.importer.fetcher.WebSearchPane; +import org.jabref.gui.openoffice.OpenOfficeSidePanel; +import org.jabref.logic.openoffice.OpenOfficePreferences; import org.jabref.preferences.JabRefPreferences; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** - * Manages visibility of SideShowComponents in a given newly constructed - * sidePane. + * Manages which {@link SidePaneComponent}s are shown. */ public class SidePaneManager { - private static final Logger LOGGER = LoggerFactory.getLogger(SidePaneManager.class); - private final JabRefFrame frame; + private final SidePane sidePane; + private final Map components = new LinkedHashMap<>(); + private final List visibleComponents = new LinkedList<>(); + private final JabRefPreferences preferences; + + public SidePaneManager(JabRefPreferences preferences, JabRefFrame frame) { + this.preferences = preferences; + this.sidePane = new SidePane(); - private final SidePane sidep; + OpenOfficePreferences openOfficePreferences = preferences.getOpenOfficePreferences(); + Stream.of( + new GroupSidePane(this, preferences, frame.getDialogService()), + new WebSearchPane(this, preferences, frame), + new OpenOfficeSidePanel(this, preferences, frame)) + .forEach(pane -> components.put(pane.getType(), pane)); + + if (preferences.getBoolean(JabRefPreferences.GROUP_SIDEPANE_VISIBLE)) { + show(SidePaneType.GROUPS); + } - private final Map, SidePaneComponent> components = new LinkedHashMap<>(); + if (openOfficePreferences.getShowPanel()) { + show(SidePaneType.OPEN_OFFICE); + } - private final List visible = new LinkedList<>(); + if (preferences.getBoolean(JabRefPreferences.WEB_SEARCH_VISIBLE)) { + show(SidePaneType.WEB_SEARCH); + } - public SidePaneManager(JabRefFrame frame) { - this.frame = frame; - /* - * Change by Morten Alver 2005.12.04: By postponing the updating of the - * side pane components, we get rid of the annoying latency when - * switching tabs: - */ - frame.getTabbedPane().addChangeListener(event -> SwingUtilities.invokeLater( - () -> setActiveBasePanel(SidePaneManager.this.frame.getCurrentBasePanel()))); - sidep = new SidePane(); - sidep.setVisible(false); + updateView(); } - public SidePane getPanel() { - return sidep; + public SidePane getPane() { + return sidePane; } - public synchronized boolean hasComponent(Class sidePaneComponent) { - return components.containsKey(sidePaneComponent); + public boolean isComponentVisible(SidePaneType type) { + return visibleComponents.contains(getComponent(type)); } - public synchronized boolean isComponentVisible(Class sidePaneComponent) { - SidePaneComponent component = components.get(sidePaneComponent); + public SidePaneComponent getComponent(SidePaneType type) { + SidePaneComponent component = components.get(type); if (component == null) { - return false; + throw new IllegalStateException("Side component " + type + " not registered."); } else { - return visible.contains(component); + return component; } } /** - * If panel is visible it will be hidden and the other way around + * If the given component is visible it will be hidden and the other way around. */ - public synchronized void toggle(Class sidePaneComponent) { - if (isComponentVisible(sidePaneComponent)) { - hide(sidePaneComponent); + public void toggle(SidePaneType type) { + if (isComponentVisible(type)) { + hide(type); } else { - show(sidePaneComponent); + show(type); } } /** - * If panel is hidden it will be shown and focused - * If panel is visible but not focused it will be focused - * If panel is visible and focused it will be hidden + * Makes sure that the given component is visible. */ - public synchronized void toggleThreeWay(Class sidePaneComponent) { - boolean isPanelFocused = Globals.getFocusListener().getFocused() == components.get(sidePaneComponent); - if (isComponentVisible(sidePaneComponent) && isPanelFocused) { - hide(sidePaneComponent); - } else { - show(sidePaneComponent); - } - } - - public synchronized void show(Class sidePaneComponent) { - SidePaneComponent component = components.get(sidePaneComponent); - if (component == null) { - LOGGER.warn("Side pane component '" + sidePaneComponent + "' unknown."); - } else { - show(component); - } - } - - public synchronized void hide(Class sidePaneComponent) { - SidePaneComponent component = components.get(sidePaneComponent); - if (component == null) { - LOGGER.warn("Side pane component '" + sidePaneComponent + "' unknown."); - } else { - hideComponent(component); - if (frame.getCurrentBasePanel() != null) { - MainTable mainTable = frame.getCurrentBasePanel().getMainTable(); - mainTable.setSelected(mainTable.getSelectedRow()); - mainTable.requestFocus(); - } - } - } - - public synchronized void register(SidePaneComponent comp) { - components.put(comp.getClass(), comp); - } - - private synchronized void show(SidePaneComponent component) { - if (!visible.contains(component)) { - // Put the new component at the top of the group - visible.add(0, component); + public void show(SidePaneType type) { + SidePaneComponent component = getComponent(type); + if (!visibleComponents.contains(component)) { + // Add the new component + visibleComponents.add(component); // Sort the visible components by their preferred position - Collections.sort(visible, new PreferredIndexSort()); + visibleComponents.sort(new PreferredIndexSort()); updateView(); - component.componentOpening(); + + component.afterOpening(); } - Globals.getFocusListener().setFocused(component); - component.grabFocus(); } - public synchronized SidePaneComponent getComponent(Class sidePaneComponent) { - return components.get(sidePaneComponent); - } + /** + * Makes sure that the given component is not visible. + */ + public void hide(SidePaneType type) { + SidePaneComponent component = getComponent(type); + if (visibleComponents.contains(component)) { + component.beforeClosing(); - public synchronized void hideComponent(SidePaneComponent comp) { - if (visible.contains(comp)) { - comp.componentClosing(); - visible.remove(comp); - updateView(); - } - } + visibleComponents.remove(component); - public synchronized void hideComponent(Class sidePaneComponent) { - SidePaneComponent component = components.get(sidePaneComponent); - if (component == null) { - return; - } - if (visible.contains(component)) { - component.componentClosing(); - visible.remove(component); updateView(); } } - private static Map, Integer> getPreferredPositions() { - Map, Integer> preferredPositions = new HashMap<>(); - - List componentNames = Globals.prefs.getStringList(JabRefPreferences.SIDE_PANE_COMPONENT_NAMES); - List componentPositions = Globals.prefs - .getStringList(JabRefPreferences.SIDE_PANE_COMPONENT_PREFERRED_POSITIONS); - - for (int i = 0; i < componentNames.size(); ++i) { - String componentName = componentNames.get(i); - try { - Class componentClass = (Class) Class.forName(componentName); - preferredPositions.put(componentClass, Integer.parseInt(componentPositions.get(i))); - } catch (ClassNotFoundException e) { - LOGGER.debug("Following side pane could not be found: " + componentName, e); - } catch (ClassCastException e) { - LOGGER.debug("Following Class is no side pane: '" + componentName, e); - } catch (NumberFormatException e) { - LOGGER.debug("Invalid number format for side pane component '" + componentName + "'.", e); - } - } - - return preferredPositions; - } - + /** + * Stores the current configuration of visible components in the preferences, + * so that we show components at the preferred position next time. + */ private void updatePreferredPositions() { - Map, Integer> preferredPositions = getPreferredPositions(); + Map preferredPositions = preferences.getSidePanePreferredPositions(); - // Update the preferred positions of all visible components + // Use the currently shown positions of all visible components int index = 0; - for (SidePaneComponent comp : visible) { - preferredPositions.put(comp.getClass(), index); + for (SidePaneComponent comp : visibleComponents) { + preferredPositions.put(comp.getType(), index); index++; } - - // Split the map into a pair of parallel String lists suitable for storage - List tmpComponentNames = preferredPositions.keySet().parallelStream() - .map(Class::getName) - .collect(Collectors.toList()); - - List componentPositions = preferredPositions.values().stream().map(Object::toString) - .collect(Collectors.toList()); - - Globals.prefs.putStringList(JabRefPreferences.SIDE_PANE_COMPONENT_NAMES, tmpComponentNames); - Globals.prefs.putStringList(JabRefPreferences.SIDE_PANE_COMPONENT_PREFERRED_POSITIONS, componentPositions); + preferences.storeSidePanePreferredPositions(preferredPositions); } - /** - * Helper class for sorting visible components based on their preferred position + * Moves the given component up. */ - private class PreferredIndexSort implements Comparator { - - private final Map, Integer> preferredPositions; - - - public PreferredIndexSort() { - preferredPositions = getPreferredPositions(); - } - - @Override - public int compare(SidePaneComponent comp1, SidePaneComponent comp2) { - int pos1 = preferredPositions.getOrDefault(comp1.getClass(), 0); - int pos2 = preferredPositions.getOrDefault(comp2.getClass(), 0); - return Integer.valueOf(pos1).compareTo(pos2); - } - } - - public synchronized void moveUp(SidePaneComponent comp) { - if (visible.contains(comp)) { - int currIndex = visible.indexOf(comp); - if (currIndex > 0) { - int newIndex = currIndex - 1; - visible.remove(currIndex); - visible.add(newIndex, comp); + public void moveUp(SidePaneComponent component) { + if (visibleComponents.contains(component)) { + int currentPosition = visibleComponents.indexOf(component); + if (currentPosition > 0) { + int newPosition = currentPosition - 1; + visibleComponents.remove(currentPosition); + visibleComponents.add(newPosition, component); updatePreferredPositions(); updateView(); @@ -235,13 +143,16 @@ public synchronized void moveUp(SidePaneComponent comp) { } } - public synchronized void moveDown(SidePaneComponent comp) { - if (visible.contains(comp)) { - int currIndex = visible.indexOf(comp); - if (currIndex < (visible.size() - 1)) { - int newIndex = currIndex + 1; - visible.remove(currIndex); - visible.add(newIndex, comp); + /** + * Moves the given component down. + */ + public void moveDown(SidePaneComponent comp) { + if (visibleComponents.contains(comp)) { + int currentPosition = visibleComponents.indexOf(comp); + if (currentPosition < (visibleComponents.size() - 1)) { + int newPosition = currentPosition + 1; + visibleComponents.remove(currentPosition); + visibleComponents.add(newPosition, comp); updatePreferredPositions(); updateView(); @@ -249,40 +160,35 @@ public synchronized void moveDown(SidePaneComponent comp) { } } - public synchronized void unregisterComponent(Class sidePaneComponent) { - components.remove(sidePaneComponent); + /** + * Updates the view to reflect changes to visible components. + */ + private void updateView() { + sidePane.setComponents(visibleComponents); + + if (visibleComponents.isEmpty()) { + sidePane.setVisible(false); + } else { + sidePane.setVisible(true); + } } /** - * Update all side pane components to show information from the given - * BasePanel. - * - * @param panel + * Helper class for sorting visible components based on their preferred position. */ - private synchronized void setActiveBasePanel(BasePanel panel) { - for (SidePaneComponent component : components.values()) { - component.setActiveBasePanel(panel); + private class PreferredIndexSort implements Comparator { + + private final Map preferredPositions; + + public PreferredIndexSort() { + preferredPositions = Globals.prefs.getSidePanePreferredPositions(); } - } - public synchronized void updateView() { - sidep.setComponents(visible); - if (visible.isEmpty()) { - if (sidep.isVisible()) { - Globals.prefs.putInt(JabRefPreferences.SIDE_PANE_WIDTH, frame.getSplitPane().getDividerLocation()); - } - sidep.setVisible(false); - } else { - boolean wasVisible = sidep.isVisible(); - sidep.setVisible(true); - if (!wasVisible) { - int width = Globals.prefs.getInt(JabRefPreferences.SIDE_PANE_WIDTH); - if (width > 0) { - frame.getSplitPane().setDividerLocation(width); - } else { - frame.getSplitPane().setDividerLocation(getPanel().getPreferredSize().width); - } - } + @Override + public int compare(SidePaneComponent comp1, SidePaneComponent comp2) { + int pos1 = preferredPositions.getOrDefault(comp1.getType(), 0); + int pos2 = preferredPositions.getOrDefault(comp2.getType(), 0); + return Integer.compare(pos1, pos2); } } } diff --git a/src/main/java/org/jabref/gui/SidePaneType.java b/src/main/java/org/jabref/gui/SidePaneType.java new file mode 100644 index 00000000000..01cec0cedb1 --- /dev/null +++ b/src/main/java/org/jabref/gui/SidePaneType.java @@ -0,0 +1,10 @@ +package org.jabref.gui; + +/** + * Definition of all possible components in the side pane. + */ +public enum SidePaneType { + OPEN_OFFICE, + WEB_SEARCH, + GROUPS +} diff --git a/src/main/java/org/jabref/gui/StateManager.java b/src/main/java/org/jabref/gui/StateManager.java index c09a7024372..2acc073ae7b 100644 --- a/src/main/java/org/jabref/gui/StateManager.java +++ b/src/main/java/org/jabref/gui/StateManager.java @@ -14,6 +14,7 @@ import javafx.collections.ObservableList; import javafx.collections.ObservableMap; +import org.jabref.logic.search.SearchQuery; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.groups.GroupTreeNode; @@ -36,6 +37,7 @@ public class StateManager { private final ReadOnlyListWrapper activeGroups = new ReadOnlyListWrapper<>(FXCollections.observableArrayList()); private final ObservableList selectedEntries = FXCollections.observableArrayList(); private final ObservableMap> selectedGroups = FXCollections.observableHashMap(); + private final ObjectProperty> activeSearchQuery = new SimpleObjectProperty<>(Optional.empty()); public StateManager() { MonadicBinding currentDatabase = EasyBind.map(activeDatabase, database -> database.orElse(null)); @@ -46,6 +48,10 @@ public ObjectProperty> activeDatabaseProperty() { return activeDatabase; } + public ObjectProperty> activeSearchQueryProperty() { + return activeSearchQuery; + } + public ReadOnlyListProperty activeGroupProperty() { return activeGroups.getReadOnlyProperty(); } @@ -80,4 +86,12 @@ public List getEntriesInCurrentDatabase() { return OptionalUtil.flatMap(activeDatabase.get(), BibDatabaseContext::getEntries) .collect(Collectors.toList()); } + + public void clearSearchQuery() { + activeSearchQuery.setValue(Optional.empty()); + } + + public void setSearchQuery(SearchQuery searchQuery) { + activeSearchQuery.setValue(Optional.of(searchQuery)); + } } diff --git a/src/main/java/org/jabref/gui/StringDialog.java b/src/main/java/org/jabref/gui/StringDialog.java deleted file mode 100644 index d79dbb67383..00000000000 --- a/src/main/java/org/jabref/gui/StringDialog.java +++ /dev/null @@ -1,455 +0,0 @@ -package org.jabref.gui; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Container; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.event.ActionEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.swing.AbstractAction; -import javax.swing.Action; -import javax.swing.ActionMap; -import javax.swing.DefaultCellEditor; -import javax.swing.InputMap; -import javax.swing.JComponent; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.JTextField; -import javax.swing.JToolBar; -import javax.swing.LayoutFocusTraversalPolicy; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.TableColumnModel; -import javax.swing.undo.CompoundEdit; - -import org.jabref.Globals; -import org.jabref.gui.actions.Actions; -import org.jabref.gui.help.HelpAction; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.gui.undo.UndoableInsertString; -import org.jabref.gui.undo.UndoableRemoveString; -import org.jabref.gui.undo.UndoableStringChange; -import org.jabref.gui.util.WindowLocation; -import org.jabref.logic.bibtex.InvalidFieldValueException; -import org.jabref.logic.bibtex.LatexFieldFormatter; -import org.jabref.logic.bibtex.comparator.BibtexStringComparator; -import org.jabref.logic.help.HelpFile; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.database.BibDatabase; -import org.jabref.model.database.KeyCollisionException; -import org.jabref.model.entry.BibtexString; -import org.jabref.preferences.JabRefPreferences; - -class StringDialog extends JabRefDialog { - - private static final String STRINGS_TITLE = Localization.lang("Strings for library"); - // A reference to the entry this object works on. - private final BibDatabase base; - private final BasePanel panel; - private final StringTable table; - private final HelpAction helpAction; - - private final SaveDatabaseAction saveAction = new SaveDatabaseAction(this); - - // The action concerned with closing the window. - private final CloseAction closeAction = new CloseAction(); - private List strings; - - - public StringDialog(JabRefFrame frame, BasePanel panel, BibDatabase base) { - super(frame, StringDialog.class); - this.panel = panel; - this.base = base; - - sortStrings(); - - helpAction = new HelpAction(Localization.lang("Help"), HelpFile.STRING_EDITOR); - - addWindowListener(new WindowAdapter() { - - @Override - public void windowClosing(WindowEvent e) { - closeAction.actionPerformed(null); - } - }); - - // We replace the default FocusTraversalPolicy with a subclass - // that only allows the StringTable to gain keyboard focus. - setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() { - - @Override - protected boolean accept(Component c) { - return super.accept(c) && (c instanceof StringTable); - } - }); - - JPanel pan = new JPanel(); - GridBagLayout gbl = new GridBagLayout(); - pan.setLayout(gbl); - GridBagConstraints con = new GridBagConstraints(); - con.fill = GridBagConstraints.BOTH; - con.weighty = 1; - con.weightx = 1; - - StringTableModel stm = new StringTableModel(this, base); - table = new StringTable(stm); - if (!base.hasNoStrings()) { - table.setRowSelectionInterval(0, 0); - } - - gbl.setConstraints(table.getPane(), con); - pan.add(table.getPane()); - - JToolBar tlb = new OSXCompatibleToolbar(); - InputMap im = tlb.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); - ActionMap am = tlb.getActionMap(); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.STRING_DIALOG_ADD_STRING), "add"); - NewStringAction newStringAction = new NewStringAction(this); - am.put("add", newStringAction); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.STRING_DIALOG_REMOVE_STRING), "remove"); - RemoveStringAction removeStringAction = new RemoveStringAction(this); - am.put("remove", removeStringAction); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.SAVE_DATABASE), "save"); - am.put("save", saveAction); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DIALOG), "close"); - am.put("close", closeAction); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.HELP), "help"); - am.put("help", helpAction); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.UNDO), "undo"); - UndoAction undoAction = new UndoAction(); - am.put("undo", undoAction); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.REDO), "redo"); - RedoAction redoAction = new RedoAction(); - am.put("redo", redoAction); - - tlb.add(newStringAction); - tlb.add(removeStringAction); - tlb.addSeparator(); - tlb.add(helpAction); - Container conPane = getContentPane(); - conPane.add(tlb, BorderLayout.NORTH); - conPane.add(pan, BorderLayout.CENTER); - - setTitle(STRINGS_TITLE + ": " - + panel.getBibDatabaseContext().getDatabaseFile().map(File::getName).orElse(GUIGlobals.UNTITLED_TITLE)); - WindowLocation pw = new WindowLocation(this, JabRefPreferences.STRINGS_POS_X, JabRefPreferences.STRINGS_POS_Y, - JabRefPreferences.STRINGS_SIZE_X, JabRefPreferences.STRINGS_SIZE_Y); - pw.displayWindowAtStoredLocation(); - } - - private static boolean isNumber(String name) { - // A pure integer number cannot be used as a string label, - // since Bibtex will read it as a number. - try { - Integer.parseInt(name); - return true; - } catch (NumberFormatException ex) { - return false; - } - } - - private void sortStrings() { - // Rebuild our sorted set of strings: - strings = new ArrayList<>(); - for (String s : base.getStringKeySet()) { - strings.add(base.getString(s)); - } - Collections.sort(strings, new BibtexStringComparator(false)); - } - - public void refreshTable() { - sortStrings(); - table.revalidate(); - table.clearSelection(); - table.repaint(); - } - - public void saveDatabase() { - panel.runCommand(Actions.SAVE); - } - - public void assureNotEditing() { - if (table.isEditing()) { - int col = table.getEditingColumn(); - int row = table.getEditingRow(); - table.getCellEditor(row, col).stopCellEditing(); - } - } - - static class SaveDatabaseAction extends AbstractAction { - - private final StringDialog parent; - - - public SaveDatabaseAction(StringDialog parent) { - super("Save library", IconTheme.JabRefIcon.SAVE.getIcon()); - putValue(Action.SHORT_DESCRIPTION, Localization.lang("Save library")); - this.parent = parent; - } - - @Override - public void actionPerformed(ActionEvent e) { - parent.saveDatabase(); - } - } - - class StringTable extends JTable { - - private final JScrollPane sp = new JScrollPane(this); - - - public StringTable(StringTableModel stm) { - super(stm); - setShowVerticalLines(true); - setShowHorizontalLines(true); - setColumnSelectionAllowed(true); - DefaultCellEditor dce = new DefaultCellEditor(new JTextField()); - dce.setClickCountToStart(2); - setDefaultEditor(String.class, dce); - TableColumnModel cm = getColumnModel(); - cm.getColumn(0).setPreferredWidth(800); - cm.getColumn(1).setPreferredWidth(2000); - sp.getViewport().setBackground(Globals.prefs.getColor(JabRefPreferences.TABLE_BACKGROUND)); - getInputMap().put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DIALOG), "close"); - getActionMap().put("close", closeAction); - getInputMap().put(Globals.getKeyPrefs().getKey(KeyBinding.HELP), "help"); - getActionMap().put("help", helpAction); - } - - public JComponent getPane() { - return sp; - } - - } - - class StringTableModel extends AbstractTableModel { - - private final BibDatabase tbase; - private final StringDialog parent; - - - public StringTableModel(StringDialog parent, BibDatabase base) { - this.parent = parent; - this.tbase = base; - } - - @Override - public Object getValueAt(int row, int col) { - return col == 0 ? strings.get(row).getName() : strings.get(row).getContent(); - } - - @Override - public void setValueAt(Object value, int row, int col) { - if (col == 0) { - // Change name of string. - if (!value.equals(strings.get(row).getName())) { - if (tbase.hasStringLabel((String) value)) { - JOptionPane.showMessageDialog(parent, Localization.lang("A string with that label already exists"), - Localization.lang("Label"), JOptionPane.ERROR_MESSAGE); - } else if (((String) value).contains(" ")) { - JOptionPane.showMessageDialog(parent, Localization.lang("The label of the string cannot contain spaces."), - Localization.lang("Label"), JOptionPane.ERROR_MESSAGE); - } else if (((String) value).contains("#")) { - JOptionPane.showMessageDialog(parent, Localization.lang("The label of the string cannot contain the '#' character."), - Localization.lang("Label"), JOptionPane.ERROR_MESSAGE); - } else if (isNumber((String) value)) { - JOptionPane.showMessageDialog(parent, Localization.lang("The label of the string cannot be a number."), - Localization.lang("Label"), JOptionPane.ERROR_MESSAGE); - } else { - // Store undo information. - BibtexString subject = strings.get(row); - panel.getUndoManager().addEdit( - new UndoableStringChange(panel, subject, true, subject.getName(), (String) value)); - subject.setName((String) value); - panel.markBaseChanged(); - refreshTable(); - } - } - } else { - // Change content of string. - BibtexString subject = strings.get(row); - - if (!value.equals(subject.getContent())) { - try { - new LatexFieldFormatter(Globals.prefs.getLatexFieldFormatterPreferences()) - .format((String) value, "__dummy"); - } catch (InvalidFieldValueException ex) { - return; - } - // Store undo information. - panel.getUndoManager().addEdit( - new UndoableStringChange(panel, subject, false, subject.getContent(), (String) value)); - - subject.setContent((String) value); - panel.markBaseChanged(); - } - } - } - - @Override - public int getColumnCount() { - return 2; - } - - @Override - public int getRowCount() { - return strings.size(); - } - - @Override - public String getColumnName(int col) { - return col == 0 ? Localization.lang("Label") : - Localization.lang("Content"); - } - - @Override - public boolean isCellEditable(int row, int col) { - return true; - } - } - - class CloseAction extends AbstractAction { - - public CloseAction() { - super("Close window"); - putValue(Action.SHORT_DESCRIPTION, Localization.lang("Close dialog")); - } - - @Override - public void actionPerformed(ActionEvent e) { - panel.stringsClosing(); - dispose(); - } - } - - class NewStringAction extends AbstractAction { - - private final StringDialog parent; - - - public NewStringAction(StringDialog parent) { - super("New string", IconTheme.JabRefIcon.ADD.getIcon()); - putValue(Action.SHORT_DESCRIPTION, Localization.lang("New string")); - this.parent = parent; - } - - @Override - public void actionPerformed(ActionEvent e) { - String name = JOptionPane.showInputDialog(parent, Localization.lang("Please enter the string's label")); - if (name == null) { - return; - } - if (isNumber(name)) { - JOptionPane.showMessageDialog(parent, Localization.lang("The label of the string cannot be a number."), - Localization.lang("Label"), JOptionPane.ERROR_MESSAGE); - return; - } - if (name.contains("#")) { - JOptionPane.showMessageDialog(parent, Localization.lang("The label of the string cannot contain the '#' character."), - Localization.lang("Label"), JOptionPane.ERROR_MESSAGE); - return; - } - if (name.contains(" ")) { - JOptionPane.showMessageDialog(parent, Localization.lang("The label of the string cannot contain spaces."), - Localization.lang("Label"), JOptionPane.ERROR_MESSAGE); - return; - } - try { - BibtexString bs = new BibtexString(name, ""); - - // Store undo information: - panel.getUndoManager().addEdit(new UndoableInsertString(panel, panel.getDatabase(), bs)); - - base.addString(bs); - refreshTable(); - panel.markBaseChanged(); - } catch (KeyCollisionException ex) { - JOptionPane.showMessageDialog(parent, - Localization.lang("A string with that label already exists"), - Localization.lang("Label"), JOptionPane.ERROR_MESSAGE); - } - } - } - - class RemoveStringAction extends AbstractAction { - - private final StringDialog parent; - - - public RemoveStringAction(StringDialog parent) { - super("Remove selected strings", IconTheme.JabRefIcon.REMOVE.getIcon()); - putValue(Action.SHORT_DESCRIPTION, Localization.lang("Remove selected strings")); - this.parent = parent; - } - - @Override - public void actionPerformed(ActionEvent e) { - int[] sel = table.getSelectedRows(); - if (sel.length > 0) { - - // Make sure no cell is being edited, as caused by the - // keystroke. This makes the content hang on the screen. - assureNotEditing(); - - String msg = (sel.length > 1 ? Localization.lang("Really delete the %0 selected entries?", - Integer.toString(sel.length)) : Localization.lang("Really delete the selected entry?")); - int answer = JOptionPane.showConfirmDialog(parent, msg, Localization.lang("Delete strings"), - JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); - if (answer == JOptionPane.YES_OPTION) { - CompoundEdit ce = new CompoundEdit(); - for (int i = sel.length - 1; i >= 0; i--) { - // Delete the strings backwards to avoid moving indexes. - - BibtexString subject = strings.get(sel[i]); - - // Store undo information: - ce.addEdit(new UndoableRemoveString(panel, base, subject)); - - base.removeString(subject.getId()); - } - ce.end(); - panel.getUndoManager().addEdit(ce); - - refreshTable(); - if (!base.hasNoStrings()) { - table.setRowSelectionInterval(0, 0); - } - } - } - } - } - - class UndoAction extends AbstractAction { - - public UndoAction() { - super("Undo", IconTheme.JabRefIcon.UNDO.getIcon()); - putValue(Action.SHORT_DESCRIPTION, Localization.lang("Undo")); - } - - @Override - public void actionPerformed(ActionEvent e) { - panel.runCommand(Actions.UNDO); - } - } - - class RedoAction extends AbstractAction { - - public RedoAction() { - super("Redo", IconTheme.JabRefIcon.REDO.getIcon()); - putValue(Action.SHORT_DESCRIPTION, Localization.lang("Redo")); - } - - @Override - public void actionPerformed(ActionEvent e) { - panel.runCommand(Actions.REDO); - } - } -} diff --git a/src/main/java/org/jabref/gui/TransferableBibtexEntry.java b/src/main/java/org/jabref/gui/TransferableBibtexEntry.java deleted file mode 100644 index 742faea2cc9..00000000000 --- a/src/main/java/org/jabref/gui/TransferableBibtexEntry.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.jabref.gui; - -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.Transferable; -import java.awt.datatransfer.UnsupportedFlavorException; -import java.io.IOException; -import java.io.StringWriter; -import java.util.List; - -import javax.swing.JOptionPane; - -import org.jabref.Globals; -import org.jabref.logic.bibtex.BibEntryWriter; -import org.jabref.logic.bibtex.LatexFieldFormatter; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.database.BibDatabaseMode; -import org.jabref.model.entry.BibEntry; - -/* - * A transferable object containing an array of BibEntry objects. Used - * for copy-paste operations. - */ -public class TransferableBibtexEntry implements Transferable { - - public static final DataFlavor ENTRY_FLAVOR = new DataFlavor(BibEntry.class, "JabRef entry"); - private final List data; - - - public TransferableBibtexEntry(List bes) { - this.data = bes; - } - - @Override - public DataFlavor[] getTransferDataFlavors() { - return new DataFlavor[]{TransferableBibtexEntry.ENTRY_FLAVOR, - DataFlavor.stringFlavor}; - } - - @Override - public boolean isDataFlavorSupported(DataFlavor flavor) { - return flavor.equals(TransferableBibtexEntry.ENTRY_FLAVOR) || flavor.equals(DataFlavor.stringFlavor); - } - - @Override - public Object getTransferData(DataFlavor flavor) - throws UnsupportedFlavorException { - if (flavor.equals(TransferableBibtexEntry.ENTRY_FLAVOR)) { - return data; - } else if (flavor.equals(DataFlavor.stringFlavor)) { - try { - StringWriter sw = new StringWriter(); - BibEntryWriter bibtexEntryWriter = new BibEntryWriter( - new LatexFieldFormatter(Globals.prefs.getLatexFieldFormatterPreferences()), false); - for (BibEntry entry : data) { - bibtexEntryWriter.write(entry, sw, BibDatabaseMode.BIBTEX); - } - return sw.toString(); - } catch (IOException ex) { - JOptionPane.showMessageDialog(null, - Localization.lang("Could not paste entry as text:") + "\n" + ex.getLocalizedMessage(), - Localization.lang("Clipboard"), JOptionPane.ERROR_MESSAGE); - return ""; - } - } else { - throw new UnsupportedFlavorException(flavor); - } - } -} diff --git a/src/main/java/org/jabref/gui/WaitForSaveFinishedDialog.java b/src/main/java/org/jabref/gui/WaitForSaveFinishedDialog.java new file mode 100644 index 00000000000..f8758ab8e1e --- /dev/null +++ b/src/main/java/org/jabref/gui/WaitForSaveFinishedDialog.java @@ -0,0 +1,43 @@ +package org.jabref.gui; + +import java.util.List; + +import javafx.concurrent.Task; + +import org.jabref.logic.l10n.Localization; + +/** + * Dialog shown when closing of application needs to wait for a save operation to finish. + */ +public class WaitForSaveFinishedDialog { + + private final DialogService dialogService; + + public WaitForSaveFinishedDialog(DialogService dialogService) { + this.dialogService = dialogService; + } + + public void showAndWait(List basePanels) { + if (basePanels.stream().anyMatch(BasePanel::isSaving)) { + Task waitForSaveFinished = new Task() { + @Override + protected Void call() throws Exception { + while (basePanels.stream().anyMatch(BasePanel::isSaving)) { + if (isCancelled()) { + return null; + } else { + Thread.sleep(100); + } + } + return null; + } + }; + + dialogService.showProgressDialogAndWait( + Localization.lang("Please wait..."), + Localization.lang("Waiting for save operation to finish") + "...", + waitForSaveFinished + ); + } + } +} diff --git a/src/main/java/org/jabref/gui/WaitForSaveOperation.java b/src/main/java/org/jabref/gui/WaitForSaveOperation.java deleted file mode 100644 index 8d10d066884..00000000000 --- a/src/main/java/org/jabref/gui/WaitForSaveOperation.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.jabref.gui; - -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JLabel; -import javax.swing.JProgressBar; -import javax.swing.Timer; - -import org.jabref.logic.l10n.Localization; - -import com.jgoodies.forms.builder.ButtonBarBuilder; - -/** - * Dialog shown when closing of application needs to wait for a save operation to finish. - */ -public class WaitForSaveOperation implements ActionListener { - - private final JabRefFrame frame; - private final JDialog diag; - private final Timer t = new Timer(500, this); - private boolean canceled; - - - public WaitForSaveOperation(JabRefFrame frame) { - this.frame = frame; - - JButton cancel = new JButton(Localization.lang("Cancel")); - JProgressBar prog = new JProgressBar(0); - prog.setIndeterminate(true); - prog.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - diag = new JDialog(frame, Localization.lang("Please wait..."), true); - - ButtonBarBuilder bb = new ButtonBarBuilder(); - bb.addGlue(); - bb.addButton(cancel); - bb.addGlue(); - cancel.addActionListener(e -> { - canceled = true; - t.stop(); - diag.dispose(); - }); - - JLabel message = new JLabel(Localization.lang("Waiting for save operation to finish") + "..."); - message.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - - diag.getContentPane().add(message, BorderLayout.NORTH); - diag.getContentPane().add(bb.getPanel(), BorderLayout.SOUTH); - diag.getContentPane().add(prog, BorderLayout.CENTER); - diag.pack(); - } - - public void show() { - diag.setLocationRelativeTo(frame); - t.start(); - diag.setVisible(true); - - } - - public boolean canceled() { - return canceled; - } - - @Override - public void actionPerformed(ActionEvent actionEvent) { - boolean anySaving = false; - for (BasePanel basePanel : frame.getBasePanelList()) { - if (basePanel.isSaving()) { - anySaving = true; - break; - } - } - if (!anySaving) { - t.stop(); - diag.dispose(); - } - } -} diff --git a/src/main/java/org/jabref/gui/WrapLayout.java b/src/main/java/org/jabref/gui/WrapLayout.java deleted file mode 100644 index 999c7abd3f0..00000000000 --- a/src/main/java/org/jabref/gui/WrapLayout.java +++ /dev/null @@ -1,175 +0,0 @@ -package org.jabref.gui; - -import java.awt.Component; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.Insets; - -import javax.swing.JScrollPane; -import javax.swing.SwingUtilities; - -/** - * FlowLayout subclass that fully supports wrapping of components. - */ -public class WrapLayout extends FlowLayout { - /** - * Constructs a new WrapLayout with a left - * alignment and a default 5-unit horizontal and vertical gap. - */ - public WrapLayout() { - super(); - } - - /** - * Constructs a new FlowLayout with the specified - * alignment and a default 5-unit horizontal and vertical gap. - * The value of the alignment argument must be one of - * WrapLayout, WrapLayout, - * or WrapLayout. - * @param align the alignment value - */ - public WrapLayout(int align) { - super(align); - } - - /** - * Creates a new flow layout manager with the indicated alignment - * and the indicated horizontal and vertical gaps. - *

    - * The value of the alignment argument must be one of - * WrapLayout, WrapLayout, - * or WrapLayout. - * @param align the alignment value - * @param hgap the horizontal gap between components - * @param vgap the vertical gap between components - */ - public WrapLayout(int align, int hgap, int vgap) { - super(align, hgap, vgap); - } - - /** - * Returns the preferred dimensions for this layout given the - * visible components in the specified target container. - * @param target the component which needs to be laid out - * @return the preferred dimensions to lay out the - * subcomponents of the specified container - */ - @Override - public Dimension preferredLayoutSize(Container target) { - return layoutSize(target, true); - } - - /** - * Returns the minimum dimensions needed to layout the visible - * components contained in the specified target container. - * @param target the component which needs to be laid out - * @return the minimum dimensions to lay out the - * subcomponents of the specified container - */ - @Override - public Dimension minimumLayoutSize(Container target) { - Dimension minimum = layoutSize(target, false); - minimum.width -= (getHgap() + 1); - return minimum; - } - - /** - * Returns the minimum or preferred dimension needed to layout the target - * container. - * - * @param target target to get layout size for - * @param preferred should preferred size be calculated - * @return the dimension to layout the target container - */ - private Dimension layoutSize(Container target, boolean preferred) { - synchronized (target.getTreeLock()) { - // Each row must fit with the width allocated to the container. - // When the container width = 0, the preferred width of the container - // has not yet been calculated so lets ask for the maximum. - - int targetWidth = target.getSize().width; - - if (targetWidth == 0) { - targetWidth = Integer.MAX_VALUE; - } - - int hgap = getHgap(); - int vgap = getVgap(); - Insets insets = target.getInsets(); - int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2); - int maxWidth = targetWidth - horizontalInsetsAndGap; - - // Fit components into the allowed width - - Dimension dim = new Dimension(0, 0); - int rowWidth = 0; - int rowHeight = 0; - - int nmembers = target.getComponentCount(); - - for (int i = 0; i < nmembers; i++) { - Component m = target.getComponent(i); - - if (m.isVisible()) { - Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize(); - - if (d != null) { - // Can't add the component to current row. Start a new row. - - if ((rowWidth + d.width) > maxWidth) { - addRow(dim, rowWidth, rowHeight); - rowWidth = 0; - rowHeight = 0; - } - - // Add a horizontal gap for all components after the first - - if (rowWidth != 0) { - rowWidth += hgap; - } - - rowWidth += d.width; - rowHeight = Math.max(rowHeight, d.height); - } - } - } - - addRow(dim, rowWidth, rowHeight); - - dim.width += horizontalInsetsAndGap; - dim.height += insets.top + insets.bottom + (vgap * 2); - - // When using a scroll pane or the DecoratedLookAndFeel we need to - // make sure the preferred size is less than the size of the - // target container so shrinking the container size works - // correctly. Removing the horizontal gap is an easy way to do this. - - Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target); - - if (scrollPane != null) { - dim.width -= (hgap + 1); - } - - return dim; - } - } - - /* - * A new row has been completed. Use the dimensions of this row - * to update the preferred size for the container. - * - * @param dim update the width and height when appropriate - * @param rowWidth the width of the row to add - * @param rowHeight the height of the row to add - */ - private void addRow(Dimension dim, int rowWidth, int rowHeight) { - dim.width = Math.max(dim.width, rowWidth); - - if (dim.height > 0) { - dim.height += getVgap(); - } - - dim.height += rowHeight; - } -} diff --git a/src/main/java/org/jabref/gui/actions/Action.java b/src/main/java/org/jabref/gui/actions/Action.java new file mode 100644 index 00000000000..5d940ffba55 --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/Action.java @@ -0,0 +1,16 @@ +package org.jabref.gui.actions; + +import java.util.Optional; + +import org.jabref.gui.icon.JabRefIcon; +import org.jabref.gui.keyboard.KeyBinding; + +public interface Action { + Optional getIcon(); + + Optional getKeyBinding(); + + String getText(); + + String getDescription(); +} diff --git a/src/main/java/org/jabref/gui/actions/ActionFactory.java b/src/main/java/org/jabref/gui/actions/ActionFactory.java new file mode 100644 index 00000000000..69c5531db65 --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/ActionFactory.java @@ -0,0 +1,100 @@ +package org.jabref.gui.actions; + +import java.util.Objects; + +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonBase; +import javafx.scene.control.CheckMenuItem; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; + +import org.jabref.gui.keyboard.KeyBindingRepository; + +import de.saxsys.mvvmfx.utils.commands.Command; +import org.controlsfx.control.action.ActionUtils; + +/** + * Helper class to create and style controls according to an {@link Action}. + */ +public class ActionFactory { + + private final KeyBindingRepository keyBindingRepository; + + public ActionFactory(KeyBindingRepository keyBindingRepository) { + this.keyBindingRepository = Objects.requireNonNull(keyBindingRepository); + } + + /** + * For some reason the graphic is not set correctly by the {@link ActionUtils} class, so we have to fix this by hand + */ + private static void setGraphic(MenuItem node, Action action) { + node.graphicProperty().unbind(); + action.getIcon().ifPresent(icon -> node.setGraphic(icon.getGraphicNode())); + } + + public MenuItem configureMenuItem(Action action, Command command, MenuItem menuItem) { + return ActionUtils.configureMenuItem(new JabRefAction(action, command, keyBindingRepository), menuItem); + } + + public MenuItem createMenuItem(Action action, Command command) { + MenuItem menuItem = ActionUtils.createMenuItem(new JabRefAction(action, command, keyBindingRepository)); + setGraphic(menuItem, action); + + return menuItem; + } + + public CheckMenuItem createCheckMenuItem(Action action, Command command, boolean selected) { + CheckMenuItem checkMenuItem = ActionUtils.createCheckMenuItem(new JabRefAction(action, command, keyBindingRepository)); + checkMenuItem.setSelected(selected); + setGraphic(checkMenuItem, action); + + return checkMenuItem; + } + + public Menu createMenu(Action action) { + Menu menu = ActionUtils.createMenu(new JabRefAction(action, keyBindingRepository)); + + // For some reason the graphic is not set correctly, so let's fix this + setGraphic(menu, action); + return menu; + } + + public Menu createSubMenu(Action action, MenuItem... children) { + Menu menu = createMenu(action); + menu.getItems().addAll(children); + return menu; + } + + public Button createIconButton(Action action, Command command) { + Button button = ActionUtils.createButton(new JabRefAction(action, command, keyBindingRepository), ActionUtils.ActionTextBehavior.HIDE); + + button.getStyleClass().setAll("icon-button"); + + // For some reason the graphic is not set correctly, so let's fix this + button.graphicProperty().unbind(); + action.getIcon().ifPresent(icon -> button.setGraphic(icon.getGraphicNode())); + + return button; + } + + public ButtonBase configureIconButton(Action action, Command command, ButtonBase button) { + ActionUtils.configureButton( + new JabRefAction(action, command, keyBindingRepository), + button, + ActionUtils.ActionTextBehavior.HIDE); + + button.getStyleClass().add("icon-button"); + + // For some reason the graphic is not set correctly, so let's fix this + button.graphicProperty().unbind(); + action.getIcon().ifPresent(icon -> { + // ToDO: Find a way to reuse JabRefIconView + Node graphicNode = icon.getGraphicNode(); + graphicNode.setStyle(String.format("-fx-font-family: %s; -fx-font-size: %s;", icon.fontFamily(), "1em")); + button.setGraphic(graphicNode); + }); + + return button; + } +} diff --git a/src/main/java/org/jabref/gui/actions/Actions.java b/src/main/java/org/jabref/gui/actions/Actions.java index 8f93cd76970..f115eda059c 100644 --- a/src/main/java/org/jabref/gui/actions/Actions.java +++ b/src/main/java/org/jabref/gui/actions/Actions.java @@ -3,73 +3,70 @@ /** * Global String constants for GUI actions */ -public class Actions { +public enum Actions { - public static final String ABBREVIATE_ISO = "abbreviateIso"; - public static final String ABBREVIATE_MEDLINE = "abbreviateMedline"; - public static final String ADD_FILE_LINK = "addFileLink"; - public static final String ADD_TO_GROUP = "addToGroup"; - public static final String AUTO_SET_FILE = "autoSetFile"; - public static final String BACK = "back"; - public static final String CLEANUP = "Cleanup"; - public static final String COPY = "copy"; - public static final String COPY_CITATION_ASCII_DOC = "copyCitaitonAsciidoc"; - public static final String COPY_CITATION_XSLFO = "copyCitaitonFo"; - public static final String COPY_CITATION_HTML = "copyCitaitonHtml"; - public static final String COPY_CITATION_RTF = "copyCitaitonRtf"; - public static final String COPY_CITATION_TEXT = "copyCitaitonText"; - public static final String COPY_KEY = "copyKey"; - public static final String COPY_CITE_KEY = "copyCiteKey"; - public static final String COPY_KEY_AND_TITLE = "copyKeyAndTitle"; - public static final String COPY_KEY_AND_LINK = "copyKeyAndLink"; - public static final String COPY_TITLE = "copyTitle"; - public static final String CUT = "cut"; - public static final String DELETE = "delete"; - public static final String DOWNLOAD_FULL_TEXT = "downloadFullText"; - public static final String DUPLI_CHECK = "dupliCheck"; - public static final String EDIT = "edit"; - public static final String EDIT_PREAMBLE = "editPreamble"; - public static final String EDIT_STRINGS = "editStrings"; - public static final String EXPORT_TO_CLIPBOARD = "exportToClipboard"; - public static final String FOCUS_TABLE = "focusTable"; - public static final String FORWARD = "forward"; - public static final String MAKE_KEY = "makeKey"; - public static final String MANAGE_SELECTORS = "manageSelectors"; - public static final String MARK_ENTRIES = "markEntries"; - public static final String MERGE_DATABASE = "mergeDatabase"; - public static final String MERGE_ENTRIES = "mergeEntries"; - public static final String MERGE_WITH_FETCHED_ENTRY = "mergeWithFetchedEntry"; - public static final String NEXT_PREVIEW_STYLE = "nextPreviewStyle"; - public static final String MOVE_TO_GROUP = "moveToGroup"; - public static final String OPEN_CONSOLE = "openConsole"; - public static final String OPEN_EXTERNAL_FILE = "openExternalFile"; - public static final String OPEN_FOLDER = "openFolder"; - public static final String OPEN_URL = "openUrl"; - public static final String PASTE = "paste"; - public static final String PLAIN_TEXT_IMPORT = "plainTextImport"; - public static final String PREVIOUS_PREVIEW_STYLE = "previousPreviewStyle"; - public static final String PULL_CHANGES_FROM_SHARED_DATABASE = "pullChangesFromSharedDatabase"; - public static final String REDO = "redo"; - public static final String REMOVE_FROM_GROUP = "removeFromGroup"; - public static final String REPLACE_ALL = "replaceAll"; - public static final String RESOLVE_DUPLICATE_KEYS = "resolveDuplicateKeys"; - public static final String SAVE = "save"; - public static final String SAVE_AS = "saveAs"; - public static final String SAVE_SELECTED_AS = "saveSelectedAs"; - public static final String SAVE_SELECTED_AS_PLAIN = "saveSelectedAsPlain"; - public static final String SEARCH = "search"; - public static final String GLOBAL_SEARCH = "globalSearch"; - public static final String SELECT_ALL = "selectAll"; - public static final String SEND_AS_EMAIL = "sendAsEmail"; - public static final String TOGGLE_GROUPS = "toggleGroups"; - public static final String TOGGLE_PREVIEW = "togglePreview"; - public static final String UNABBREVIATE = "unabbreviate"; - public static final String UNDO = "undo"; - public static final String UNMARK_ALL = "unmarkAll"; - public static final String UNMARK_ENTRIES = "unmarkEntries"; - public static final String WRITE_XMP = "writeXMP"; - public static final String PRINT_PREVIEW = "printPreview"; - - private Actions() { - } + ABBREVIATE_ISO, + ABBREVIATE_MEDLINE, + ADD_FILE_LINK, + CLEANUP, + COPY, + COPY_CITATION_ASCII_DOC, + COPY_CITATION_XSLFO, + COPY_CITATION_HTML, + COPY_CITATION_RTF, + COPY_CITATION_TEXT, + COPY_KEY, + COPY_CITE_KEY, + COPY_KEY_AND_TITLE, + COPY_KEY_AND_LINK, + COPY_TITLE, + CUT, + DELETE, + DOWNLOAD_FULL_TEXT, + EDIT, + EDIT_PREAMBLE, + EDIT_STRINGS, + EXPORT_TO_CLIPBOARD, + MAKE_KEY, + MANAGE_SELECTORS, + MERGE_DATABASE, + MERGE_ENTRIES, + MERGE_WITH_FETCHED_ENTRY, + NEXT_PREVIEW_STYLE, + OPEN_CONSOLE, + OPEN_EXTERNAL_FILE, + OPEN_FOLDER, + OPEN_URL, + PASTE, + PREVIOUS_PREVIEW_STYLE, + PULL_CHANGES_FROM_SHARED_DATABASE, + REDO, + REPLACE_ALL, + SAVE, + SAVE_AS, + SAVE_SELECTED_AS_PLAIN, + SELECT_ALL, + SEND_AS_EMAIL, + TOGGLE_GROUPS, + TOGGLE_PREVIEW, + UNABBREVIATE, + UNDO, + WRITE_XMP, + PRINT_PREVIEW, + TOGGLE_PRINTED, + CLEAR_PRIORITY, + SET_PRIORITY_1, + SET_PRIORITY_2, + SET_PRIORITY_3, + TOGGLE_QUALITY_ASSURED, + CLEAR_RANK, + SET_RANK_1, + SET_RANK_2, + SET_RANK_3, + SET_RANK_4, + SET_RANK_5, + CLEAR_READ_STATUS, + SET_READ_STATUS_TO_READ, + SET_READ_STATUS_TO_SKIMMED, + TOGGLE_RELEVANCE } diff --git a/src/main/java/org/jabref/gui/actions/AutoLinkFilesAction.java b/src/main/java/org/jabref/gui/actions/AutoLinkFilesAction.java index db9931a8f00..7b77c9f59b4 100644 --- a/src/main/java/org/jabref/gui/actions/AutoLinkFilesAction.java +++ b/src/main/java/org/jabref/gui/actions/AutoLinkFilesAction.java @@ -1,18 +1,13 @@ package org.jabref.gui.actions; -import java.awt.event.ActionEvent; import java.util.List; -import javax.swing.AbstractAction; -import javax.swing.Action; import javax.swing.JDialog; +import javax.swing.JFrame; -import org.jabref.Globals; import org.jabref.JabRefExecutorService; import org.jabref.JabRefGUI; -import org.jabref.gui.IconTheme; import org.jabref.gui.externalfiles.AutoSetLinks; -import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.undo.NamedCompound; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntry; @@ -21,24 +16,21 @@ * This Action may only be used in a menu or button. * Never in the entry editor. FileListEditor and EntryEditor have other ways to update the file links */ -public class AutoLinkFilesAction extends AbstractAction { +public class AutoLinkFilesAction extends SimpleCommand { public AutoLinkFilesAction() { - putValue(Action.SMALL_ICON, IconTheme.JabRefIcon.AUTO_FILE_LINK.getSmallIcon()); - putValue(Action.LARGE_ICON_KEY, IconTheme.JabRefIcon.AUTO_FILE_LINK.getIcon()); - putValue(Action.NAME, Localization.lang("Automatically set file links")); - putValue(Action.ACCELERATOR_KEY, Globals.getKeyPrefs().getKey(KeyBinding.AUTOMATICALLY_LINK_FILES)); + } @Override - public void actionPerformed(ActionEvent event) { + public void execute() { List entries = JabRefGUI.getMainFrame().getCurrentBasePanel().getSelectedEntries(); if (entries.isEmpty()) { JabRefGUI.getMainFrame().getCurrentBasePanel() .output(Localization.lang("This operation requires one or more entries to be selected.")); return; } - JDialog diag = new JDialog(JabRefGUI.getMainFrame(), true); + JDialog diag = new JDialog((JFrame) null, true); final NamedCompound nc = new NamedCompound(Localization.lang("Automatically set file links")); Runnable runnable = AutoSetLinks.autoSetLinks(entries, nc, null, JabRefGUI.getMainFrame().getCurrentBasePanel().getBibDatabaseContext(), e -> { diff --git a/src/main/java/org/jabref/gui/actions/BibtexKeyPatternAction.java b/src/main/java/org/jabref/gui/actions/BibtexKeyPatternAction.java new file mode 100644 index 00000000000..7eaae3af4df --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/BibtexKeyPatternAction.java @@ -0,0 +1,18 @@ +package org.jabref.gui.actions; + +import org.jabref.gui.JabRefFrame; +import org.jabref.gui.bibtexkeypattern.BibtexKeyPatternDialog; + +public class BibtexKeyPatternAction extends SimpleCommand { + + private final JabRefFrame frame; + + public BibtexKeyPatternAction(JabRefFrame frame) { + this.frame = frame; + } + + @Override + public void execute() { + new BibtexKeyPatternDialog(frame.getCurrentBasePanel()).showAndWait(); + } +} diff --git a/src/main/java/org/jabref/gui/actions/CleanupAction.java b/src/main/java/org/jabref/gui/actions/CleanupAction.java index 89e8d94ab90..648af8313b7 100644 --- a/src/main/java/org/jabref/gui/actions/CleanupAction.java +++ b/src/main/java/org/jabref/gui/actions/CleanupAction.java @@ -1,19 +1,15 @@ package org.jabref.gui.actions; import java.util.List; -import java.util.Objects; - -import javax.swing.JOptionPane; +import java.util.Optional; import org.jabref.Globals; import org.jabref.gui.BasePanel; -import org.jabref.gui.JabRefFrame; -import org.jabref.gui.cleanup.CleanupPresetPanel; +import org.jabref.gui.DialogService; +import org.jabref.gui.cleanup.CleanupDialog; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableFieldChange; -import org.jabref.gui.util.DefaultTaskExecutor; -import org.jabref.gui.util.component.CheckBoxMessage; -import org.jabref.gui.worker.AbstractWorker; +import org.jabref.gui.util.BackgroundTask; import org.jabref.logic.cleanup.CleanupPreset; import org.jabref.logic.cleanup.CleanupWorker; import org.jabref.logic.l10n.Localization; @@ -21,142 +17,123 @@ import org.jabref.model.entry.BibEntry; import org.jabref.preferences.JabRefPreferences; -public class CleanupAction extends AbstractWorker { +public class CleanupAction implements BaseAction { private final BasePanel panel; - private final JabRefFrame frame; + private final DialogService dialogService; - /** - * Global variable to count unsuccessful renames - */ - private int unsuccessfulRenames; - - private boolean canceled; + private boolean isCanceled; private int modifiedEntriesCount; private final JabRefPreferences preferences; public CleanupAction(BasePanel panel, JabRefPreferences preferences) { this.panel = panel; - this.frame = panel.frame(); - this.preferences = Objects.requireNonNull(preferences); + this.preferences = preferences; + this.dialogService = panel.frame().getDialogService(); } @Override + public void action() { + init(); + if (isCanceled) { + return; + } + CleanupDialog cleanupDialog = new CleanupDialog(panel.getBibDatabaseContext(), preferences.getCleanupPreset(), preferences.getFilePreferences()); + + Optional chosenPreset = cleanupDialog.showAndWait(); + + if (chosenPreset.isPresent()) { + if (chosenPreset.get().isRenamePDFActive() && preferences.getBoolean(JabRefPreferences.ASK_AUTO_NAMING_PDFS_AGAIN)) { + boolean confirmed = dialogService.showConfirmationDialogWithOptOutAndWait(Localization.lang("Autogenerate PDF Names"), + Localization.lang("Auto-generating PDF-Names does not support undo. Continue?"), + Localization.lang("Autogenerate PDF Names"), + Localization.lang("Cancel"), + Localization.lang("Disable this confirmation dialog"), + optOut -> Globals.prefs.putBoolean(JabRefPreferences.ASK_AUTO_NAMING_PDFS_AGAIN, !optOut)); + + if (!confirmed) { + isCanceled = true; + return; + } + } + + preferences.setCleanupPreset(chosenPreset.get()); + + BackgroundTask.wrap(() -> cleanup(chosenPreset.get())) + .onSuccess(result -> showResults()) + .executeWith(Globals.TASK_EXECUTOR); + } + } + public void init() { - canceled = false; + isCanceled = false; modifiedEntriesCount = 0; if (panel.getSelectedEntries().isEmpty()) { // None selected. Inform the user to select entries first. - JOptionPane.showMessageDialog(frame, Localization.lang("First select entries to clean up."), - Localization.lang("Cleanup entry"), JOptionPane.INFORMATION_MESSAGE); - canceled = true; + dialogService.showInformationDialogAndWait(Localization.lang("Cleanup entry"), Localization.lang("First select entries to clean up.")); + isCanceled = true; return; } - frame.block(); panel.output(Localization.lang("Doing a cleanup for %0 entries...", Integer.toString(panel.getSelectedEntries().size()))); } - @Override - public void run() { - if (canceled) { - return; - } - CleanupPresetPanel presetPanel = new CleanupPresetPanel(panel.getBibDatabaseContext(), - CleanupPreset.loadFromPreferences(preferences)); - int choice = showDialog(presetPanel); - if (choice != JOptionPane.OK_OPTION) { - canceled = true; + /** + * Runs the cleanup on the entry and records the change. + */ + private void doCleanup(CleanupPreset preset, BibEntry entry, NamedCompound ce) { + // Create and run cleaner + CleanupWorker cleaner = new CleanupWorker(panel.getBibDatabaseContext(), preferences.getCleanupPreferences( + Globals.journalAbbreviationLoader)); + List changes = cleaner.cleanup(preset, entry); + + if (changes.isEmpty()) { return; } - CleanupPreset cleanupPreset = presetPanel.getCleanupPreset(); - cleanupPreset.storeInPreferences(preferences); - - if (cleanupPreset.isRenamePDF() && Globals.prefs.getBoolean(JabRefPreferences.ASK_AUTO_NAMING_PDFS_AGAIN)) { - CheckBoxMessage cbm = new CheckBoxMessage( - Localization.lang("Auto-generating PDF-Names does not support undo. Continue?"), - Localization.lang("Disable this confirmation dialog"), false); - int answer = JOptionPane.showConfirmDialog(frame, cbm, Localization.lang("Autogenerate PDF Names"), - JOptionPane.YES_NO_OPTION); - if (cbm.isSelected()) { - Globals.prefs.putBoolean(JabRefPreferences.ASK_AUTO_NAMING_PDFS_AGAIN, false); - } - if (answer == JOptionPane.NO_OPTION) { - canceled = true; - return; - } - } - for (BibEntry entry : panel.getSelectedEntries()) { - // undo granularity is on entry level - NamedCompound ce = new NamedCompound(Localization.lang("Cleanup entry")); - - doCleanup(cleanupPreset, entry, ce); - - ce.end(); - if (ce.hasEdits()) { - modifiedEntriesCount++; - panel.getUndoManager().addEdit(ce); - } + // Register undo action + for (FieldChange change : changes) { + ce.addEdit(new UndoableFieldChange(change)); } } - @Override - public void update() { - if (canceled) { - frame.unblock(); + private void showResults() { + if (isCanceled) { return; } - if (unsuccessfulRenames > 0) { //Rename failed for at least one entry - JOptionPane.showMessageDialog(frame, - Localization.lang("File rename failed for %0 entries.", Integer.toString(unsuccessfulRenames)), - Localization.lang("Autogenerate PDF Names"), JOptionPane.INFORMATION_MESSAGE); - } + if (modifiedEntriesCount > 0) { panel.updateEntryEditorIfShowing(); panel.markBaseChanged(); } String message; switch (modifiedEntriesCount) { - case 0: - message = Localization.lang("No entry needed a clean up"); - break; - case 1: - message = Localization.lang("One entry needed a clean up"); - break; - default: - message = Localization.lang("%0 entries needed a clean up", Integer.toString(modifiedEntriesCount)); - break; + case 0: + message = Localization.lang("No entry needed a clean up"); + break; + case 1: + message = Localization.lang("One entry needed a clean up"); + break; + default: + message = Localization.lang("%0 entries needed a clean up", Integer.toString(modifiedEntriesCount)); + break; } panel.output(message); - frame.unblock(); } - private int showDialog(CleanupPresetPanel presetPanel) { - String dialogTitle = Localization.lang("Cleanup entries"); - Object[] messages = {Localization.lang("What would you like to clean up?"), presetPanel.getScrollPane()}; - return JOptionPane.showConfirmDialog(frame, messages, dialogTitle, JOptionPane.OK_CANCEL_OPTION, - JOptionPane.QUESTION_MESSAGE); - } - - /** - * Runs the cleanup on the entry and records the change. - */ - private void doCleanup(CleanupPreset preset, BibEntry entry, NamedCompound ce) { - // Create and run cleaner - CleanupWorker cleaner = new CleanupWorker(panel.getBibDatabaseContext(), preferences.getCleanupPreferences( - Globals.journalAbbreviationLoader)); - List changes = DefaultTaskExecutor.runInJavaFXThread(() -> cleaner.cleanup(preset, entry)); + private void cleanup(CleanupPreset cleanupPreset) { + preferences.setCleanupPreset(cleanupPreset); - unsuccessfulRenames = cleaner.getUnsuccessfulRenames(); + for (BibEntry entry : panel.getSelectedEntries()) { + // undo granularity is on entry level + NamedCompound ce = new NamedCompound(Localization.lang("Cleanup entry")); - if (changes.isEmpty()) { - return; - } + doCleanup(cleanupPreset, entry, ce); - // Register undo action - for (FieldChange change : changes) { - ce.addEdit(new UndoableFieldChange(change)); + ce.end(); + if (ce.hasEdits()) { + modifiedEntriesCount++; + panel.getUndoManager().addEdit(ce); + } } } - } diff --git a/src/main/java/org/jabref/gui/actions/ConnectToSharedDatabaseAction.java b/src/main/java/org/jabref/gui/actions/ConnectToSharedDatabaseAction.java deleted file mode 100644 index 1e3f46ec437..00000000000 --- a/src/main/java/org/jabref/gui/actions/ConnectToSharedDatabaseAction.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.jabref.gui.actions; - -import java.awt.event.ActionEvent; - -import javax.swing.Action; - -import org.jabref.gui.JabRefFrame; -import org.jabref.gui.shared.ConnectToSharedDatabaseDialog; -import org.jabref.logic.l10n.Localization; - -/** - * The action concerned with opening a shared database. - */ -public class ConnectToSharedDatabaseAction extends MnemonicAwareAction { - - private final JabRefFrame jabRefFrame; - - - public ConnectToSharedDatabaseAction(JabRefFrame jabRefFrame) { - super(); - this.jabRefFrame = jabRefFrame; - putValue(Action.NAME, Localization.menuTitle("Connect to shared database")); - putValue(Action.SHORT_DESCRIPTION, Localization.lang("Connect to shared database")); - } - - @Override - public void actionPerformed(ActionEvent e) { - ConnectToSharedDatabaseDialog connectToSharedDatabaseDialog = new ConnectToSharedDatabaseDialog(jabRefFrame); - connectToSharedDatabaseDialog.setVisible(true); - } -} diff --git a/src/main/java/org/jabref/gui/actions/ConnectToSharedDatabaseCommand.java b/src/main/java/org/jabref/gui/actions/ConnectToSharedDatabaseCommand.java new file mode 100644 index 00000000000..792aff5bf34 --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/ConnectToSharedDatabaseCommand.java @@ -0,0 +1,21 @@ +package org.jabref.gui.actions; + +import org.jabref.gui.JabRefFrame; +import org.jabref.gui.shared.SharedDatabaseLoginDialogView; + +/** + * Opens a shared database. + */ +public class ConnectToSharedDatabaseCommand extends SimpleCommand { + + private final JabRefFrame jabRefFrame; + + public ConnectToSharedDatabaseCommand(JabRefFrame jabRefFrame) { + this.jabRefFrame = jabRefFrame; + } + + @Override + public void execute() { + new SharedDatabaseLoginDialogView(jabRefFrame).showAndWait(); + } +} diff --git a/src/main/java/org/jabref/gui/actions/CopyBibTeXKeyAndLinkAction.java b/src/main/java/org/jabref/gui/actions/CopyBibTeXKeyAndLinkAction.java index d5b1652b6c4..6f55af1e27b 100644 --- a/src/main/java/org/jabref/gui/actions/CopyBibTeXKeyAndLinkAction.java +++ b/src/main/java/org/jabref/gui/actions/CopyBibTeXKeyAndLinkAction.java @@ -3,10 +3,8 @@ import java.util.List; import java.util.stream.Collectors; -import javafx.scene.input.Clipboard; -import javafx.scene.input.ClipboardContent; - import org.jabref.JabRefGUI; +import org.jabref.gui.ClipBoardManager; import org.jabref.gui.maintable.MainTable; import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.logic.l10n.Localization; @@ -22,9 +20,11 @@ public class CopyBibTeXKeyAndLinkAction implements BaseAction { private final MainTable mainTable; + private final ClipBoardManager clipboardManager; - public CopyBibTeXKeyAndLinkAction(MainTable mainTable) { + public CopyBibTeXKeyAndLinkAction(MainTable mainTable, ClipBoardManager clipboardManager) { this.mainTable = mainTable; + this.clipboardManager = clipboardManager; } @Override @@ -47,13 +47,7 @@ public void action() throws Exception { sb.append(OS.NEWLINE); } - // This works on Mac and Windows 10, but not on Ubuntu 16.04 - DefaultTaskExecutor.runInJavaFXThread(() -> { - final Clipboard clipboard = Clipboard.getSystemClipboard(); - final ClipboardContent content = new ClipboardContent(); - content.putHtml(sb.toString()); - clipboard.setContent(content); - }); + DefaultTaskExecutor.runInJavaFXThread(() -> clipboardManager.setHtmlContent(sb.toString())); int copied = entriesWithKey.size(); int toCopy = entries.size(); diff --git a/src/main/java/org/jabref/gui/actions/CopyDoiUrlAction.java b/src/main/java/org/jabref/gui/actions/CopyDoiUrlAction.java index d511de57caf..aa8ee344abe 100644 --- a/src/main/java/org/jabref/gui/actions/CopyDoiUrlAction.java +++ b/src/main/java/org/jabref/gui/actions/CopyDoiUrlAction.java @@ -7,8 +7,8 @@ import javafx.scene.control.TextArea; +import org.jabref.Globals; import org.jabref.JabRefGUI; -import org.jabref.gui.ClipBoardManager; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.identifier.DOI; @@ -21,7 +21,7 @@ public class CopyDoiUrlAction extends AbstractAction { private String identifier; public CopyDoiUrlAction(String identifier) { - super(Localization.menuTitle("Copy DOI url")); + super(Localization.lang("Copy DOI url")); this.identifier = identifier; } @@ -36,8 +36,7 @@ public void actionPerformed(ActionEvent e) { Optional urlOptional = DOI.parse(identifier).map(DOI::getURIAsASCIIString); if (urlOptional.isPresent()) { - ClipBoardManager clipBoard = new ClipBoardManager(); - clipBoard.setClipboardContents(urlOptional.get()); + Globals.clipboardManager.setContent(urlOptional.get()); JabRefGUI.getMainFrame().output(Localization.lang("The link has been copied to the clipboard.")); } else { JabRefGUI.getMainFrame().output(Localization.lang("Invalid DOI: '%0'.", identifier)); diff --git a/src/main/java/org/jabref/gui/actions/CopyFilesAction.java b/src/main/java/org/jabref/gui/actions/CopyFilesAction.java new file mode 100644 index 00000000000..d45a78eb803 --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/CopyFilesAction.java @@ -0,0 +1,66 @@ +package org.jabref.gui.actions; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; + +import javafx.concurrent.Task; + +import org.jabref.Globals; +import org.jabref.gui.DialogService; +import org.jabref.gui.JabRefFrame; +import org.jabref.gui.copyfiles.CopyFilesDialogView; +import org.jabref.gui.copyfiles.CopyFilesResultItemViewModel; +import org.jabref.gui.copyfiles.CopyFilesResultListDependency; +import org.jabref.gui.copyfiles.CopyFilesTask; +import org.jabref.gui.util.DirectoryDialogConfiguration; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.preferences.JabRefPreferences; + +public class CopyFilesAction extends SimpleCommand { + + private final DialogService dialogService; + private BibDatabaseContext databaseContext; + private List entries; + private final JabRefFrame frame; + + public CopyFilesAction(JabRefFrame frame) { + this.frame = frame; + this.dialogService = frame.getDialogService(); + } + + private void showDialog(List data) { + if (data.isEmpty()) { + dialogService.showInformationDialogAndWait(Localization.lang("Copy linked files to folder..."), Localization.lang("No linked files found for export.")); + return; + } + CopyFilesDialogView dialog = new CopyFilesDialogView(databaseContext, new CopyFilesResultListDependency(data)); + dialog.showAndWait(); + } + + @Override + public void execute() { + DirectoryDialogConfiguration dirDialogConfiguration = new DirectoryDialogConfiguration.Builder() + .withInitialDirectory(Paths.get(Globals.prefs.get(JabRefPreferences.EXPORT_WORKING_DIRECTORY))) + .build(); + entries = frame.getCurrentBasePanel().getSelectedEntries(); + + Optional exportPath = dialogService.showDirectorySelectionDialog(dirDialogConfiguration); + + exportPath.ifPresent(path -> { + databaseContext = frame.getCurrentBasePanel().getBibDatabaseContext(); + + Task> exportTask = new CopyFilesTask(databaseContext, entries, path); + dialogService.showProgressDialogAndWait( + Localization.lang("Copy linked files to folder..."), + Localization.lang("Copy linked files to folder..."), + exportTask); + Globals.TASK_EXECUTOR.execute(exportTask); + exportTask.setOnSucceeded((e) -> showDialog(exportTask.getValue())); + }); + + } +} diff --git a/src/main/java/org/jabref/gui/actions/CopyVersionToClipboardAction.java b/src/main/java/org/jabref/gui/actions/CopyVersionToClipboardAction.java deleted file mode 100644 index c0de2fcd01d..00000000000 --- a/src/main/java/org/jabref/gui/actions/CopyVersionToClipboardAction.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.jabref.gui.actions; - -import java.awt.event.ActionEvent; - -import javax.swing.AbstractAction; - -import org.jabref.Globals; -import org.jabref.JabRefGUI; -import org.jabref.gui.ClipBoardManager; -import org.jabref.logic.l10n.Localization; -import org.jabref.logic.util.BuildInfo; - -public class CopyVersionToClipboardAction extends AbstractAction { - - @Override - public void actionPerformed(ActionEvent e) { - String info = String.format("JabRef %s%n%s %s %s %nJava %s", Globals.BUILD_INFO.getVersion(), BuildInfo.OS, - BuildInfo.OS_VERSION, BuildInfo.OS_ARCH, BuildInfo.JAVA_VERSION); - new ClipBoardManager().setClipboardContents(info); - JabRefGUI.getMainFrame().output(Localization.lang("Copied version to clipboard")); - } - -} diff --git a/src/main/java/org/jabref/gui/actions/CustomizeKeyBindingAction.java b/src/main/java/org/jabref/gui/actions/CustomizeKeyBindingAction.java new file mode 100644 index 00000000000..fdaa5538ff4 --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/CustomizeKeyBindingAction.java @@ -0,0 +1,12 @@ +package org.jabref.gui.actions; + +import org.jabref.gui.keyboard.KeyBindingsDialogView; + +public class CustomizeKeyBindingAction extends SimpleCommand { + + @Override + public void execute() { + new KeyBindingsDialogView().show(); + } + +} diff --git a/src/main/java/org/jabref/gui/actions/EditExternalFileTypesAction.java b/src/main/java/org/jabref/gui/actions/EditExternalFileTypesAction.java new file mode 100644 index 00000000000..c5ea26bb8cb --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/EditExternalFileTypesAction.java @@ -0,0 +1,12 @@ +package org.jabref.gui.actions; + +import org.jabref.gui.externalfiletype.CustomizeExternalFileTypesDialog; + +public class EditExternalFileTypesAction extends SimpleCommand { + + @Override + public void execute() { + CustomizeExternalFileTypesDialog editor = new CustomizeExternalFileTypesDialog(); + editor.showAndWait(); + } +} diff --git a/src/main/java/org/jabref/gui/actions/ErrorConsoleAction.java b/src/main/java/org/jabref/gui/actions/ErrorConsoleAction.java index f81b8ce5bae..2ac2dbf6623 100644 --- a/src/main/java/org/jabref/gui/actions/ErrorConsoleAction.java +++ b/src/main/java/org/jabref/gui/actions/ErrorConsoleAction.java @@ -1,14 +1,6 @@ package org.jabref.gui.actions; -import java.awt.event.ActionEvent; - -import javax.swing.AbstractAction; -import javax.swing.Action; - -import javafx.application.Platform; - import org.jabref.gui.errorconsole.ErrorConsoleView; -import org.jabref.logic.l10n.Localization; /** * Such an error console can be @@ -17,16 +9,11 @@ *

    * It offers a separate tab for the log output. */ -public class ErrorConsoleAction extends AbstractAction { - - public ErrorConsoleAction() { - super(Localization.menuTitle("View event log")); - putValue(Action.SHORT_DESCRIPTION, Localization.lang("Display all error messages")); - } +public class ErrorConsoleAction extends SimpleCommand { @Override - public void actionPerformed(ActionEvent e) { - Platform.runLater(() -> new ErrorConsoleView().show()); + public void execute() { + new ErrorConsoleView().show(); } } diff --git a/src/main/java/org/jabref/gui/actions/GenerateBibtexKeyAction.java b/src/main/java/org/jabref/gui/actions/GenerateBibtexKeyAction.java new file mode 100644 index 00000000000..90b61800215 --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/GenerateBibtexKeyAction.java @@ -0,0 +1,105 @@ +package org.jabref.gui.actions; + +import java.util.List; + +import org.jabref.Globals; +import org.jabref.gui.BasePanel; +import org.jabref.gui.DialogService; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableKeyChange; +import org.jabref.gui.util.BackgroundTask; +import org.jabref.logic.bibtexkeypattern.BibtexKeyGenerator; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.entry.BibEntry; +import org.jabref.preferences.JabRefPreferences; + +public class GenerateBibtexKeyAction implements BaseAction { + + private final DialogService dialogService; + private final BasePanel basePanel; + private List entries; + private boolean isCanceled; + + public GenerateBibtexKeyAction(BasePanel basePanel, DialogService dialogService) { + this.basePanel = basePanel; + this.dialogService = dialogService; + } + + public void init() { + entries = basePanel.getSelectedEntries(); + + if (entries.isEmpty()) { + dialogService.showWarningDialogAndWait(Localization.lang("Autogenerate BibTeX keys"), + Localization.lang("First select the entries you want keys to be generated for.")); + return; + } + basePanel.output(formatOutputMessage(Localization.lang("Generating BibTeX key for"), entries.size())); + } + + public static boolean confirmOverwriteKeys(DialogService dialogService) { + if (Globals.prefs.getBoolean(JabRefPreferences.WARN_BEFORE_OVERWRITING_KEY)) { + return dialogService.showConfirmationDialogWithOptOutAndWait( + Localization.lang("Overwrite keys"), + Localization.lang("One or more keys will be overwritten. Continue?"), + Localization.lang("Overwrite keys"), + Localization.lang("Cancel"), + Localization.lang("Disable this confirmation dialog"), + optOut -> Globals.prefs.putBoolean(JabRefPreferences.WARN_BEFORE_OVERWRITING_KEY, !optOut)); + + } else { + // Always overwrite keys by default + return true; + } + } + + 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)) { + boolean overwriteKeys = confirmOverwriteKeys(dialogService); + + // The user doesn't want to override cite keys + if (!overwriteKeys) { + isCanceled = true; + return; + } + } + } + + private void generateKeys() { + if (isCanceled) { + return; + } + // generate the new cite keys for each entry + final NamedCompound compound = new NamedCompound(Localization.lang("Autogenerate BibTeX keys")); + BibtexKeyGenerator keyGenerator = new BibtexKeyGenerator(basePanel.getBibDatabaseContext(), Globals.prefs.getBibtexKeyPatternPreferences()); + for (BibEntry entry : entries) { + keyGenerator.generateAndSetKey(entry) + .ifPresent(fieldChange -> compound.addEdit(new UndoableKeyChange(fieldChange))); + } + compound.end(); + + // register the undo event only if new cite keys were generated + if (compound.hasEdits()) { + basePanel.getUndoManager().addEdit(compound); + } + + basePanel.markBaseChanged(); + basePanel.output(formatOutputMessage(Localization.lang("Generated BibTeX key for"), entries.size())); + } + + private String formatOutputMessage(String start, int count) { + return String.format("%s %d %s.", start, count, + (count > 1 ? Localization.lang("entries") : Localization.lang("entry"))); + } + + @Override + public void action() { + init(); + checkOverwriteKeysChosen(); + BackgroundTask.wrap(this::generateKeys) + .executeWith(Globals.TASK_EXECUTOR); + } +} diff --git a/src/main/java/org/jabref/gui/actions/GenerateBibtexKeySingleAction.java b/src/main/java/org/jabref/gui/actions/GenerateBibtexKeySingleAction.java new file mode 100644 index 00000000000..0ad4345cd4a --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/GenerateBibtexKeySingleAction.java @@ -0,0 +1,41 @@ +package org.jabref.gui.actions; + +import javax.swing.undo.UndoManager; + +import org.jabref.gui.DialogService; +import org.jabref.gui.entryeditor.EntryEditorPreferences; +import org.jabref.gui.undo.UndoableKeyChange; +import org.jabref.logic.bibtexkeypattern.BibtexKeyGenerator; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; + +public class GenerateBibtexKeySingleAction extends SimpleCommand { + + private DialogService dialogService; + private BibDatabaseContext databaseContext; + private EntryEditorPreferences preferences; + private BibEntry entry; + private UndoManager undoManager; + + public GenerateBibtexKeySingleAction(BibEntry entry, BibDatabaseContext databaseContext, DialogService dialogService, EntryEditorPreferences preferences, UndoManager undoManager) { + this.entry = entry; + this.databaseContext = databaseContext; + this.dialogService = dialogService; + this.preferences = preferences; + this.undoManager = undoManager; + + if (preferences.avoidOverwritingCiteKey()) { + // Only make command executable if cite key is empty + this.executable.bind(entry.getCiteKeyBinding().isNull()); + } + } + + @Override + public void execute() { + if (!entry.hasCiteKey() || GenerateBibtexKeyAction.confirmOverwriteKeys(dialogService)) { + new BibtexKeyGenerator(databaseContext, preferences.getBibtexKeyPatternPreferences()) + .generateAndSetKey(entry) + .ifPresent(change -> undoManager.addEdit(new UndoableKeyChange(change))); + } + } +} diff --git a/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java b/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java deleted file mode 100644 index 25a186f33e7..00000000000 --- a/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java +++ /dev/null @@ -1,207 +0,0 @@ -package org.jabref.gui.actions; - -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.swing.Action; -import javax.swing.JButton; -import javax.swing.JCheckBoxMenuItem; -import javax.swing.JDialog; -import javax.swing.JOptionPane; -import javax.swing.JPopupMenu; -import javax.swing.JProgressBar; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.ListSelectionModel; -import javax.swing.RowFilter; -import javax.swing.SwingWorker; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.TableColumnModel; -import javax.swing.table.TableModel; -import javax.swing.table.TableRowSorter; - -import org.jabref.Globals; -import org.jabref.gui.JabRefFrame; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.logic.integrity.IntegrityCheck; -import org.jabref.logic.integrity.IntegrityMessage; -import org.jabref.logic.l10n.Localization; -import org.jabref.preferences.JabRefPreferences; - -import com.jgoodies.forms.builder.FormBuilder; -import com.jgoodies.forms.layout.FormLayout; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class IntegrityCheckAction extends MnemonicAwareAction { - - private static final Logger LOGGER = LoggerFactory.getLogger(IntegrityCheckAction.class); - private static final String ELLIPSES = "..."; - - private final JabRefFrame frame; - - public IntegrityCheckAction(JabRefFrame frame) { - this.frame = frame; - putValue(Action.NAME, Localization.menuTitle("Check integrity") + ELLIPSES); - putValue(Action.ACCELERATOR_KEY, Globals.getKeyPrefs().getKey(KeyBinding.CHECK_INTEGRITY)); - } - - @Override - public void actionPerformed(ActionEvent e) { - IntegrityCheck check = new IntegrityCheck(frame.getCurrentBasePanel().getBibDatabaseContext(), - Globals.prefs.getFileDirectoryPreferences(), - Globals.prefs.getBibtexKeyPatternPreferences(), - Globals.journalAbbreviationLoader.getRepository(Globals.prefs.getJournalAbbreviationPreferences()), - Globals.prefs.getBoolean(JabRefPreferences.ENFORCE_LEGAL_BIBTEX_KEY)); - - final JDialog integrityDialog = new JDialog(frame, true); - integrityDialog.setUndecorated(true); - integrityDialog.setLocationRelativeTo(frame); - JProgressBar integrityProgressBar = new JProgressBar(); - integrityProgressBar.setIndeterminate(true); - integrityProgressBar.setStringPainted(true); - integrityProgressBar.setString(Localization.lang("Checking integrity...")); - integrityDialog.add(integrityProgressBar); - integrityDialog.pack(); - SwingWorker, Void> worker = new SwingWorker, Void>() { - @Override - protected List doInBackground() { - List messages = check.checkBibtexDatabase(); - return messages; - } - - @Override - protected void done() { - integrityDialog.dispose(); - } - }; - worker.execute(); - integrityDialog.setVisible(true); - List messages = null; - try { - messages = worker.get(); - } catch (Exception ex) { - LOGGER.error("Integrity check failed.", ex); - } - - if (messages.isEmpty()) { - JOptionPane.showMessageDialog(frame.getCurrentBasePanel(), Localization.lang("No problems found.")); - } else { - Map showMessage = new HashMap<>(); - // prepare data model - Object[][] model = new Object[messages.size()][4]; - int i = 0; - for (IntegrityMessage message : messages) { - model[i][0] = message.getEntry().getId(); - model[i][1] = message.getEntry().getCiteKeyOptional().orElse(""); - model[i][2] = message.getFieldName(); - model[i][3] = message.getMessage(); - showMessage.put(message.getMessage(), true); - i++; - } - - // construct view - JTable table = new JTable(model, - new Object[] {"ID", Localization.lang("BibTeX key"), Localization.lang("Field"), - Localization.lang("Message")}); - - // hide IDs - TableColumnModel columnModel = table.getColumnModel(); - columnModel.removeColumn(columnModel.getColumn(0)); - - RowFilter filter = new RowFilter() { - - @Override - public boolean include(Entry entry) { - return showMessage.get(entry.getStringValue(3)); - } - }; - - TableRowSorter sorter = new TableRowSorter<>(table.getModel()); - sorter.setRowFilter(filter); - table.setRowSorter(sorter); - table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - table.setDefaultEditor(Object.class, null); - ListSelectionModel selectionModel = table.getSelectionModel(); - - selectionModel.addListSelectionListener(event -> { - if (!event.getValueIsAdjusting()) { - try { - String entryId = (String) model[table.convertRowIndexToModel(table.getSelectedRow())][0]; - String fieldName = (String) model[table.convertRowIndexToModel(table.getSelectedRow())][2]; - frame.getCurrentBasePanel().editEntryByIdAndFocusField(entryId, fieldName); - } catch (ArrayIndexOutOfBoundsException exception) { - // Ignore -- most likely caused by filtering out the earlier selected row - } - } - }); - - // BibTeX key - table.getColumnModel().getColumn(0).setPreferredWidth(100); - // field name - table.getColumnModel().getColumn(1).setPreferredWidth(60); - // message - table.getColumnModel().getColumn(2).setPreferredWidth(400); - table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); - JScrollPane scrollPane = new JScrollPane(table); - String title = Localization.lang("%0 problem(s) found", String.valueOf(messages.size())); - JDialog dialog = new JDialog(frame, title, false); - - JPopupMenu menu = new JPopupMenu(); - for (String messageString : showMessage.keySet()) { - JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(messageString, true); - menuItem.addActionListener(event -> { - showMessage.put(messageString, menuItem.isSelected()); - ((AbstractTableModel) table.getModel()).fireTableDataChanged(); - }); - menu.add(menuItem); - } - - JButton menuButton = new JButton(Localization.lang("Filter")); - menuButton.addActionListener(entry -> menu.show(menuButton, 0, menuButton.getHeight())); - FormBuilder builder = FormBuilder.create() - .layout(new FormLayout("fill:pref:grow", "fill:pref:grow, 2dlu, pref")); - - JButton filterNoneButton = new JButton(Localization.lang("Filter None")); - filterNoneButton.addActionListener(event -> { - for (Component component : menu.getComponents()) { - if (component instanceof JCheckBoxMenuItem) { - JCheckBoxMenuItem checkBox = (JCheckBoxMenuItem) component; - if (checkBox.isSelected()) { - checkBox.setSelected(false); - showMessage.put(checkBox.getText(), checkBox.isSelected()); - } - } - ((AbstractTableModel) table.getModel()).fireTableDataChanged(); - } - }); - - JButton filterAllButton = new JButton(Localization.lang("Filter All")); - filterAllButton.addActionListener(event -> { - for (Component component : menu.getComponents()) { - if (component instanceof JCheckBoxMenuItem) { - JCheckBoxMenuItem checkBox = (JCheckBoxMenuItem) component; - if (!checkBox.isSelected()) { - checkBox.setSelected(true); - showMessage.put(checkBox.getText(), checkBox.isSelected()); - } - } - ((AbstractTableModel) table.getModel()).fireTableDataChanged(); - } - }); - - builder.add(filterNoneButton).xy(1, 3, "left, b"); - builder.add(filterAllButton).xy(1, 3, "right, b"); - builder.add(scrollPane).xy(1, 1); - builder.add(menuButton).xy(1, 3, "c, b"); - dialog.add(builder.getPanel()); - dialog.setSize(600, 600); - - // show view - dialog.setVisible(true); - } - } -} diff --git a/src/main/java/org/jabref/gui/actions/JabRefAction.java b/src/main/java/org/jabref/gui/actions/JabRefAction.java new file mode 100644 index 00000000000..a1188ad8115 --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/JabRefAction.java @@ -0,0 +1,40 @@ +package org.jabref.gui.actions; + +import org.jabref.Globals; +import org.jabref.gui.keyboard.KeyBindingRepository; + +import de.saxsys.mvvmfx.utils.commands.Command; + +/** + * Wrapper around one of our actions from {@link Action} to convert them to controlsfx {@link org.controlsfx.control.action.Action}. + */ +class JabRefAction extends org.controlsfx.control.action.Action { + + + public JabRefAction(Action action, KeyBindingRepository keyBindingRepository) { + super(action.getText()); + action.getIcon() + .ifPresent(icon -> setGraphic(icon.getGraphicNode())); + action.getKeyBinding() + .ifPresent(keyBinding -> setAccelerator(keyBindingRepository.getKeyCombination(keyBinding))); + + setLongText(action.getDescription()); + + } + + public JabRefAction(Action action, Command command, KeyBindingRepository keyBindingRepository) { + this(action, keyBindingRepository); + + setEventHandler(event -> { + command.execute(); + trackExecute(); + }); + + disabledProperty().bind(command.executableProperty().not()); + } + + private void trackExecute() { + Globals.getTelemetryClient() + .ifPresent(telemetryClient -> telemetryClient.trackEvent(getText())); + } +} diff --git a/src/main/java/org/jabref/gui/actions/LibraryPropertiesAction.java b/src/main/java/org/jabref/gui/actions/LibraryPropertiesAction.java new file mode 100644 index 00000000000..1b2299e3f06 --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/LibraryPropertiesAction.java @@ -0,0 +1,24 @@ +package org.jabref.gui.actions; + +import org.jabref.gui.DialogService; +import org.jabref.gui.JabRefFrame; +import org.jabref.gui.libraryproperties.LibraryPropertiesDialogView; + +public class LibraryPropertiesAction extends SimpleCommand { + + private final JabRefFrame frame; + private final DialogService dialogService; + + public LibraryPropertiesAction(JabRefFrame frame, DialogService dialogService) { + this.frame = frame; + this.dialogService = dialogService; + } + + @Override + public void execute() { + LibraryPropertiesDialogView propertiesDialog = new LibraryPropertiesDialogView(frame.getCurrentBasePanel(), dialogService); + propertiesDialog.showAndWait(); + + } + +} diff --git a/src/main/java/org/jabref/gui/actions/LookupIdentifierAction.java b/src/main/java/org/jabref/gui/actions/LookupIdentifierAction.java index 2f488fdb45b..f1f6e6f0846 100644 --- a/src/main/java/org/jabref/gui/actions/LookupIdentifierAction.java +++ b/src/main/java/org/jabref/gui/actions/LookupIdentifierAction.java @@ -1,38 +1,112 @@ package org.jabref.gui.actions; -import java.awt.event.ActionEvent; - -import javax.swing.Action; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import org.jabref.Globals; import org.jabref.gui.BasePanel; import org.jabref.gui.JabRefFrame; -import org.jabref.gui.worker.LookupIdentifiersWorker; +import org.jabref.gui.icon.JabRefIcon; +import org.jabref.gui.keyboard.KeyBinding; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableFieldChange; +import org.jabref.gui.util.BackgroundTask; +import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.IdFetcher; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.FieldChange; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.identifier.Identifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class LookupIdentifierAction extends MnemonicAwareAction { +public class LookupIdentifierAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(LookupIdentifierAction.class); private final JabRefFrame frame; - private final IdFetcher fetcher; - public LookupIdentifierAction(JabRefFrame frame, IdFetcher fetcher) { - super(); + private final IdFetcher fetcher; + + public LookupIdentifierAction(JabRefFrame frame, IdFetcher fetcher) { this.frame = frame; this.fetcher = fetcher; - - putValue(Action.NAME, fetcher.getIdentifierName()); } @Override - public void actionPerformed(ActionEvent actionEvent) { + public void execute() { try { - BasePanel.runWorker(new LookupIdentifiersWorker(frame, fetcher)); + BackgroundTask.wrap(this::lookupIdentifiers) + .onSuccess(frame::output) + .executeWith(Globals.TASK_EXECUTOR); } catch (Exception e) { LOGGER.error("Problem running ID Worker", e); } } + + public Action getAction() { + return new Action() { + + @Override + public Optional getIcon() { + return Optional.empty(); + } + + @Override + public Optional getKeyBinding() { + return Optional.empty(); + } + + @Override + public String getText() { + return fetcher.getIdentifierName(); + } + + @Override + public String getDescription() { + return ""; + } + }; + } + + private String lookupIdentifiers() { + BasePanel basePanel = Objects.requireNonNull(frame.getCurrentBasePanel()); + List bibEntries = basePanel.getSelectedEntries(); + if (bibEntries.isEmpty()) { + return Localization.lang("This operation requires one or more entries to be selected."); + } + + String totalCount = Integer.toString(bibEntries.size()); + NamedCompound namedCompound = new NamedCompound(Localization.lang("Look up %0", fetcher.getIdentifierName())); + int count = 0; + int foundCount = 0; + for (BibEntry bibEntry : bibEntries) { + count++; + frame.output(Localization.lang("Looking up %0... - entry %1 out of %2 - found %3", + fetcher.getIdentifierName(), Integer.toString(count), totalCount, Integer.toString(foundCount))); + Optional identifier = Optional.empty(); + try { + identifier = fetcher.findIdentifier(bibEntry); + } catch (FetcherException e) { + LOGGER.error("Could not fetch " + fetcher.getIdentifierName(), e); + } + if (identifier.isPresent() && !bibEntry.hasField(identifier.get().getDefaultField())) { + Optional fieldChange = bibEntry.setField(identifier.get().getDefaultField(), identifier.get().getNormalized()); + if (fieldChange.isPresent()) { + namedCompound.addEdit(new UndoableFieldChange(fieldChange.get())); + foundCount++; + frame.output(Localization.lang("Looking up %0... - entry %1 out of %2 - found %3", + Integer.toString(count), totalCount, Integer.toString(foundCount))); + } + } + } + namedCompound.end(); + if (foundCount > 0) { + basePanel.getUndoManager().addEdit(namedCompound); + basePanel.markBaseChanged(); + } + return Localization.lang("Determined %0 for %1 entries", fetcher.getIdentifierName(), Integer.toString(foundCount)); + } } diff --git a/src/main/java/org/jabref/gui/actions/ManageContentSelectorAction.java b/src/main/java/org/jabref/gui/actions/ManageContentSelectorAction.java new file mode 100644 index 00000000000..31e2b1e1149 --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/ManageContentSelectorAction.java @@ -0,0 +1,29 @@ +package org.jabref.gui.actions; + +import org.jabref.gui.BasePanel; +import org.jabref.gui.JabRefFrame; +import org.jabref.gui.contentselector.ContentSelectorDialogView; +import org.jabref.logic.l10n.Localization; + +public class ManageContentSelectorAction extends SimpleCommand { + + private final JabRefFrame jabRefFrame; + + public ManageContentSelectorAction(JabRefFrame jabRefFrame) { + this.jabRefFrame = jabRefFrame; + } + + @Override + public void execute() { + BasePanel basePanel = jabRefFrame.getCurrentBasePanel(); + if (noActiveConnectionExists(basePanel)) { + jabRefFrame.getDialogService().showErrorDialogAndWait(Localization.lang("Active database connection do not exists!")); + return; + } + new ContentSelectorDialogView(basePanel).showAndWait(); + } + + private boolean noActiveConnectionExists(BasePanel basePanel) { + return basePanel == null || basePanel.getBibDatabaseContext() == null; + } +} diff --git a/src/main/java/org/jabref/gui/actions/ManageCustomExportsAction.java b/src/main/java/org/jabref/gui/actions/ManageCustomExportsAction.java new file mode 100644 index 00000000000..60298e05555 --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/ManageCustomExportsAction.java @@ -0,0 +1,12 @@ +package org.jabref.gui.actions; + +import org.jabref.gui.exporter.ExportCustomizationDialogView; + +public class ManageCustomExportsAction extends SimpleCommand { + + @Override + public void execute() { + new ExportCustomizationDialogView().show(); + } + +} diff --git a/src/main/java/org/jabref/gui/actions/ManageCustomImportsAction.java b/src/main/java/org/jabref/gui/actions/ManageCustomImportsAction.java new file mode 100644 index 00000000000..e36ea7ac07c --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/ManageCustomImportsAction.java @@ -0,0 +1,15 @@ +package org.jabref.gui.actions; + +import org.jabref.gui.importer.ImportCustomizationDialog; + +public class ManageCustomImportsAction extends SimpleCommand { + + public ManageCustomImportsAction() { + } + + @Override + public void execute() { + new ImportCustomizationDialog().showAndWait(); + } + +} diff --git a/src/main/java/org/jabref/gui/actions/ManageJournalsAction.java b/src/main/java/org/jabref/gui/actions/ManageJournalsAction.java new file mode 100644 index 00000000000..b089a505a9e --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/ManageJournalsAction.java @@ -0,0 +1,12 @@ +package org.jabref.gui.actions; + +import org.jabref.gui.journals.ManageJournalAbbreviationsView; + +public class ManageJournalsAction extends SimpleCommand { + + @Override + public void execute() { + new ManageJournalAbbreviationsView().show(); + } + +} diff --git a/src/main/java/org/jabref/gui/actions/ManageKeywordsAction.java b/src/main/java/org/jabref/gui/actions/ManageKeywordsAction.java deleted file mode 100644 index 1e3304323c1..00000000000 --- a/src/main/java/org/jabref/gui/actions/ManageKeywordsAction.java +++ /dev/null @@ -1,357 +0,0 @@ -package org.jabref.gui.actions; - -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.util.Enumeration; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; - -import javax.swing.AbstractAction; -import javax.swing.Action; -import javax.swing.ActionMap; -import javax.swing.BorderFactory; -import javax.swing.ButtonGroup; -import javax.swing.DefaultListModel; -import javax.swing.InputMap; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JList; -import javax.swing.JRadioButton; -import javax.swing.JScrollPane; -import javax.swing.JTextField; - -import org.jabref.Globals; -import org.jabref.gui.BasePanel; -import org.jabref.gui.JabRefFrame; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableFieldChange; -import org.jabref.logic.l10n.Localization; -import org.jabref.logic.specialfields.SpecialFieldsUtils; -import org.jabref.model.FieldChange; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.Keyword; -import org.jabref.model.entry.KeywordList; -import org.jabref.model.strings.StringUtil; - -import com.jgoodies.forms.builder.ButtonBarBuilder; -import com.jgoodies.forms.builder.FormBuilder; -import com.jgoodies.forms.layout.FormLayout; - -/** - * An Action for launching keyword managing dialog - * - */ -public class ManageKeywordsAction extends MnemonicAwareAction { - - private final JabRefFrame frame; - private final KeywordList sortedKeywordsOfAllEntriesBeforeUpdateByUser = new KeywordList(); - private JDialog diag; - private DefaultListModel keywordListModel; - private JRadioButton intersectKeywords; - private JRadioButton mergeKeywords; - private boolean canceled; - - - public ManageKeywordsAction(JabRefFrame frame) { - putValue(Action.NAME, Localization.menuTitle("Manage keywords")); - this.frame = frame; - } - - private void createDialog() { - if (diag != null) { - return; - } - // keyword to add - JTextField keyword = new JTextField(); - - keywordListModel = new DefaultListModel<>(); - JList keywordList = new JList<>(keywordListModel); - keywordList.setVisibleRowCount(8); - JScrollPane kPane = new JScrollPane(keywordList); - - diag = new JDialog(frame, Localization.lang("Manage keywords"), true); - - JButton ok = new JButton(Localization.lang("OK")); - JButton cancel = new JButton(Localization.lang("Cancel")); - JButton add = new JButton(Localization.lang("Add")); - JButton remove = new JButton(Localization.lang("Remove")); - JButton replace = new JButton(Localization.lang("Replace")); - JButton join = new JButton(Localization.lang("Join")); - - join.setToolTipText(Localization.lang("Joins selected keywords and deletes selected keywords.")); - - keywordList.setVisibleRowCount(10); - - intersectKeywords = new JRadioButton(Localization.lang("Display keywords appearing in ALL entries")); - mergeKeywords = new JRadioButton(Localization.lang("Display keywords appearing in ANY entry")); - ButtonGroup group = new ButtonGroup(); - group.add(intersectKeywords); - group.add(mergeKeywords); - ActionListener stateChanged = e -> fillKeyWordList(); - intersectKeywords.addActionListener(stateChanged); - mergeKeywords.addActionListener(stateChanged); - intersectKeywords.setSelected(true); - - FormBuilder builder = FormBuilder.create().layout(new FormLayout("fill:200dlu:grow, pref, fill:pref", - "pref, 2dlu, pref, 1dlu, pref, 2dlu, fill:100dlu:grow, 4dlu, pref, 4dlu, pref, ")); - - builder.addSeparator(Localization.lang("Keywords of selected entries")).xyw(1, 1, 3); - builder.add(intersectKeywords).xyw(1, 3, 3); - builder.add(mergeKeywords).xyw(1, 5, 3); - builder.add(kPane).xywh(1, 7, 1, 3); - builder.add(join).xy(2,9); - builder.add(replace).xy(3, 9); - builder.add(keyword).xy(1, 11); - builder.add(add).xy(2, 11); - builder.add(remove).xy(3, 11); - - ButtonBarBuilder bb = new ButtonBarBuilder(); - bb.addGlue(); - bb.addButton(ok); - bb.addButton(cancel); - bb.addGlue(); - builder.getPanel().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - bb.getPanel().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - - ok.addActionListener(e -> { - canceled = false; - diag.dispose(); - }); - - Action cancelAction = new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - canceled = true; - diag.dispose(); - } - }; - cancel.addActionListener(cancelAction); - - final ActionListener addActionListener = arg0 -> addButtonActionListener(keyword); - - add.addActionListener(addActionListener); - - final ActionListener removeActionListener = arg0 -> { - // keywordList.getSelectedIndices(); does not work, therefore we operate on the values - List values = keywordList.getSelectedValuesList(); - - for (Keyword val : values) { - keywordListModel.removeElement(val); - } - }; - - remove.addActionListener(removeActionListener); - - final ActionListener joinActionListener = arg0 -> { - List values = keywordList.getSelectedValuesList(); - String joinedKeyword = values.stream().map(currentKeyword -> currentKeyword.get()).collect(Collectors.joining(" ")); - this.addKeywordToKeywordListModel(joinedKeyword); - - for (Keyword val : values) { - this.keywordListModel.removeElement(val); - } - }; - - join.addActionListener(joinActionListener); - - final ActionListener replaceActionListener = arg0 -> { - List values = keywordList.getSelectedValuesList(); - - for (Keyword val : values) { - keywordListModel.removeElement(val); - } - addButtonActionListener(keyword); - }; - - replace.addActionListener(replaceActionListener); - - //enable a user to press Delete to delete a keyword - keywordList.addKeyListener(new KeyAdapter() { - - @Override - public void keyPressed(KeyEvent arg0) { - if (arg0.getKeyCode() == KeyEvent.VK_DELETE) { - removeActionListener.actionPerformed(null); - } - } - }); - - //enable a user to press Enter to add a keyword - keyword.addKeyListener(new KeyAdapter() { - - @Override - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ENTER) { - addActionListener.actionPerformed(null); - } - } - }); - - // Key bindings: - ActionMap am = builder.getPanel().getActionMap(); - InputMap im = builder.getPanel().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DIALOG), "close"); - am.put("close", cancelAction); - - diag.getContentPane().add(builder.getPanel(), BorderLayout.CENTER); - diag.getContentPane().add(bb.getPanel(), BorderLayout.SOUTH); - } - - private void addButtonActionListener(JTextField keywordTextField) { - if (StringUtil.isBlank(keywordTextField.getText())) { - return; // nothing to add - } - addKeywordToKeywordListModel(keywordTextField.getText()); - keywordTextField.setText(null); - keywordTextField.requestFocusInWindow(); - - } - - /** - * Adds given keyword to the keyword list model - */ - private void addKeywordToKeywordListModel(String keyword) { - String keywordToAdd = Objects.requireNonNull(keyword).trim(); - Keyword newKeyword = new Keyword(keywordToAdd); - if (keywordListModel.isEmpty()) { - keywordListModel.addElement(newKeyword); - } else { - int idx = 0; - Keyword element = keywordListModel.getElementAt(idx); - while ((idx < keywordListModel.size()) && (element.compareTo(newKeyword) < 0)) { - idx++; - } - if (idx == keywordListModel.size()) { - // list is empty or word is greater than last word in list - keywordListModel.addElement(newKeyword); - } else if (element.compareTo(newKeyword) == 0) { - // nothing to do, word already in table - } else { - keywordListModel.add(idx, newKeyword); - } - } - } - - @Override - public void actionPerformed(ActionEvent e) { - BasePanel bp = frame.getCurrentBasePanel(); - if (bp == null) { - return; - } - if (bp.getSelectedEntries().isEmpty()) { - bp.output(Localization.lang("Select at least one entry to manage keywords.")); - return; - } - - // Lazy creation of the dialog: - createDialog(); - - canceled = true; - - fillKeyWordList(); - - diag.pack(); - diag.setLocationRelativeTo(frame); - diag.setVisible(true); - if (canceled) { - return; - } - - KeywordList keywordsToAdd = new KeywordList(); - KeywordList userSelectedKeywords = new KeywordList(); - // build keywordsToAdd and userSelectedKeywords in parallel - for (Enumeration keywords = keywordListModel.elements(); keywords.hasMoreElements();) { - Keyword keyword = keywords.nextElement(); - userSelectedKeywords.add(keyword); - if (!sortedKeywordsOfAllEntriesBeforeUpdateByUser.contains(keyword)) { - keywordsToAdd.add(keyword); - } - } - - KeywordList keywordsToRemove = new KeywordList(); - for (Keyword kword : sortedKeywordsOfAllEntriesBeforeUpdateByUser) { - if (!userSelectedKeywords.contains(kword)) { - keywordsToRemove.add(kword); - } - } - - if (keywordsToAdd.isEmpty() && keywordsToRemove.isEmpty()) { - // nothing to be done if nothing is new and nothing is obsolete - return; - } - - if (Globals.prefs.isKeywordSyncEnabled() && !keywordsToAdd.isEmpty()) { - SpecialFieldsUtils.synchronizeSpecialFields(keywordsToAdd, keywordsToRemove); - } - - NamedCompound ce = updateKeywords(bp.getSelectedEntries(), keywordsToAdd, keywordsToRemove); - bp.getUndoManager().addEdit(ce); - bp.markBaseChanged(); - } - - private NamedCompound updateKeywords(List entries, KeywordList keywordsToAdd, - KeywordList keywordsToRemove) { - NamedCompound ce = new NamedCompound(Localization.lang("Update keywords")); - for (BibEntry entry : entries) { - KeywordList keywords = entry.getKeywords(Globals.prefs.getKeywordDelimiter()); - - // update keywords - keywords.removeAll(keywordsToRemove); - keywords.addAll(keywordsToAdd); - - // put keywords back - Optional change = entry.putKeywords(keywords, Globals.prefs.getKeywordDelimiter()); - if (change.isPresent()) { - ce.addEdit(new UndoableFieldChange(change.get())); - } - - if (Globals.prefs.isKeywordSyncEnabled()) { - SpecialFieldsUtils.syncSpecialFieldsFromKeywords(entry, Globals.prefs.getKeywordDelimiter()); - } - } - ce.end(); - return ce; - } - - private void fillKeyWordList() { - BasePanel bp = frame.getCurrentBasePanel(); - List entries = bp.getSelectedEntries(); - - // fill dialog with values - keywordListModel.clear(); - sortedKeywordsOfAllEntriesBeforeUpdateByUser.clear(); - - if (mergeKeywords.isSelected()) { - for (BibEntry entry : entries) { - KeywordList separatedKeywords = entry.getKeywords(Globals.prefs.getKeywordDelimiter()); - sortedKeywordsOfAllEntriesBeforeUpdateByUser.addAll(separatedKeywords); - } - } else { - assert intersectKeywords.isSelected(); - - // all keywords from first entry have to be added - BibEntry firstEntry = entries.get(0); - KeywordList separatedKeywords = firstEntry.getKeywords(Globals.prefs.getKeywordDelimiter()); - sortedKeywordsOfAllEntriesBeforeUpdateByUser.addAll(separatedKeywords); - - // for the remaining entries, intersection has to be used - // this approach ensures that one empty keyword list leads to an empty set of common keywords - for (int i = 1; i < entries.size(); i++) { - BibEntry entry = entries.get(i); - separatedKeywords = entry.getKeywords(Globals.prefs.getKeywordDelimiter()); - sortedKeywordsOfAllEntriesBeforeUpdateByUser.retainAll(separatedKeywords); - } - } - for (Keyword keyword : sortedKeywordsOfAllEntriesBeforeUpdateByUser) { - keywordListModel.addElement(keyword); - } - } - -} diff --git a/src/main/java/org/jabref/gui/actions/MassSetFieldAction.java b/src/main/java/org/jabref/gui/actions/MassSetFieldAction.java deleted file mode 100644 index 6743657960a..00000000000 --- a/src/main/java/org/jabref/gui/actions/MassSetFieldAction.java +++ /dev/null @@ -1,368 +0,0 @@ -package org.jabref.gui.actions; - -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.util.Collection; -import java.util.List; -import java.util.Locale; -import java.util.Optional; -import java.util.Set; - -import javax.swing.AbstractAction; -import javax.swing.Action; -import javax.swing.ActionMap; -import javax.swing.BorderFactory; -import javax.swing.ButtonGroup; -import javax.swing.InputMap; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JOptionPane; -import javax.swing.JRadioButton; -import javax.swing.JTextField; -import javax.swing.undo.UndoableEdit; - -import org.jabref.Globals; -import org.jabref.gui.BasePanel; -import org.jabref.gui.JabRefFrame; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableFieldChange; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.entry.BibEntry; - -import com.jgoodies.forms.builder.ButtonBarBuilder; -import com.jgoodies.forms.builder.FormBuilder; -import com.jgoodies.forms.layout.FormLayout; - -/** - * An Action for launching mass field. - * - * Functionality: - * * Defaults to selected entries, or all entries if none are selected. - * * Input field name - * * Either set field, or clear field. - */ -public class MassSetFieldAction extends MnemonicAwareAction { - - private final JabRefFrame frame; - private JDialog diag; - private JRadioButton all; - private JRadioButton selected; - private JRadioButton clear; - private JRadioButton set; - private JRadioButton append; - private JRadioButton rename; - private JComboBox field; - private JTextField textFieldSet; - private JTextField textFieldAppend; - private JTextField textFieldRename; - private boolean canceled = true; - private JCheckBox overwrite; - - - public MassSetFieldAction(JabRefFrame frame) { - putValue(Action.NAME, Localization.menuTitle("Set/clear/append/rename fields") + "..."); - this.frame = frame; - } - - private void createDialog() { - diag = new JDialog(frame, Localization.lang("Set/clear/append/rename fields"), true); - - field = new JComboBox<>(); - field.setEditable(true); - textFieldSet = new JTextField(); - textFieldSet.setEnabled(false); - textFieldAppend = new JTextField(); - textFieldAppend.setEnabled(false); - textFieldRename = new JTextField(); - textFieldRename.setEnabled(false); - - JButton ok = new JButton(Localization.lang("OK")); - JButton cancel = new JButton(Localization.lang("Cancel")); - - all = new JRadioButton(Localization.lang("All entries")); - selected = new JRadioButton(Localization.lang("Selected entries")); - clear = new JRadioButton(Localization.lang("Clear fields")); - set = new JRadioButton(Localization.lang("Set fields")); - append = new JRadioButton(Localization.lang("Append to fields")); - rename = new JRadioButton(Localization.lang("Rename field to") + ":"); - rename.setToolTipText(Localization.lang("Move contents of a field into a field with a different name")); - - Set allFields = frame.getCurrentBasePanel().getDatabase().getAllVisibleFields(); - - for (String f : allFields) { - field.addItem(f); - } - - set.addChangeListener(e -> - // Entering a setText is only relevant if we are setting, not clearing: - textFieldSet.setEnabled(set.isSelected())); - - append.addChangeListener(e -> { - // Text to append is only required if we are appending: - textFieldAppend.setEnabled(append.isSelected()); - // Overwrite protection makes no sense if we are appending to a field: - overwrite.setEnabled(!clear.isSelected() && !append.isSelected()); - }); - - clear.addChangeListener(e -> - // Overwrite protection makes no sense if we are clearing the field: - overwrite.setEnabled(!clear.isSelected() && !append.isSelected())); - - rename.addChangeListener(e -> - // Entering a setText is only relevant if we are renaming - textFieldRename.setEnabled(rename.isSelected())); - - overwrite = new JCheckBox(Localization.lang("Overwrite existing field values"), true); - ButtonGroup bg = new ButtonGroup(); - bg.add(all); - bg.add(selected); - bg = new ButtonGroup(); - bg.add(clear); - bg.add(set); - bg.add(append); - bg.add(rename); - FormBuilder builder = FormBuilder.create().layout(new FormLayout( - "left:pref, 4dlu, fill:100dlu:grow", "pref, 2dlu, pref, 2dlu, pref, 2dlu, pref, 2dlu, pref, 2dlu, pref, 2dlu, pref, 2dlu, pref, 2dlu, pref, 2dlu, pref, 2dlu, pref")); - builder.addSeparator(Localization.lang("Field name")).xyw(1, 1, 3); - builder.add(Localization.lang("Field name")).xy(1, 3); - builder.add(field).xy(3, 3); - builder.addSeparator(Localization.lang("Include entries")).xyw(1, 5, 3); - builder.add(all).xyw(1, 7, 3); - builder.add(selected).xyw(1, 9, 3); - builder.addSeparator(Localization.lang("New field value")).xyw(1, 11, 3); - builder.add(set).xy(1, 13); - builder.add(textFieldSet).xy(3, 13); - builder.add(clear).xyw(1, 15, 3); - builder.add(append).xy(1, 17); - builder.add(textFieldAppend).xy(3, 17); - builder.add(rename).xy(1, 19); - builder.add(textFieldRename).xy(3, 19); - builder.add(overwrite).xyw(1, 21, 3); - - ButtonBarBuilder bb = new ButtonBarBuilder(); - bb.addGlue(); - bb.addButton(ok); - bb.addButton(cancel); - bb.addGlue(); - builder.getPanel().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - bb.getPanel().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - diag.getContentPane().add(builder.getPanel(), BorderLayout.CENTER); - diag.getContentPane().add(bb.getPanel(), BorderLayout.SOUTH); - diag.pack(); - - ok.addActionListener(e -> { - // Check that any field name is set - String fieldText = (String) field.getSelectedItem(); - if ((fieldText == null) || fieldText.trim().isEmpty()) { - JOptionPane.showMessageDialog(diag, Localization.lang("You must enter at least one field name"), "", - JOptionPane.ERROR_MESSAGE); - return; // Do not close the dialog. - } - - // Check if the user tries to rename multiple fields: - if (rename.isSelected()) { - String[] fields = getFieldNames(fieldText); - if (fields.length > 1) { - JOptionPane.showMessageDialog(diag, Localization.lang("You can only rename one field at a time"), - "", JOptionPane.ERROR_MESSAGE); - return; // Do not close the dialog. - } - } - canceled = false; - diag.dispose(); - }); - - Action cancelAction = new AbstractAction() { - - @Override - public void actionPerformed(ActionEvent e) { - canceled = true; - diag.dispose(); - } - }; - cancel.addActionListener(cancelAction); - - // Key bindings: - ActionMap am = builder.getPanel().getActionMap(); - InputMap im = builder.getPanel().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DIALOG), "close"); - am.put("close", cancelAction); - } - - private void prepareDialog(boolean selection) { - selected.setEnabled(selection); - if (selection) { - selected.setSelected(true); - } else { - all.setSelected(true); - } - // Make sure one of the following ones is selected: - if (!set.isSelected() && !clear.isSelected() && !rename.isSelected()) { - set.setSelected(true); - } - } - - @Override - public void actionPerformed(ActionEvent e) { - BasePanel bp = frame.getCurrentBasePanel(); - if (bp == null) { - return; - } - List entries = bp.getSelectedEntries(); - // Lazy creation of the dialog: - if (diag == null) { - createDialog(); - } - canceled = true; - prepareDialog(!entries.isEmpty()); - if (diag != null) { - diag.setLocationRelativeTo(frame); - diag.setVisible(true); - } - if (canceled) { - return; - } - - Collection entryList; - // If all entries should be treated, change the entries array: - if (all.isSelected()) { - entryList = bp.getDatabase().getEntries(); - } else { - entryList = entries; - } - - String toSet = textFieldSet.getText(); - if (toSet.isEmpty()) { - toSet = null; - } - - String[] fields = getFieldNames(((String) field.getSelectedItem()).trim().toLowerCase(Locale.ROOT)); - NamedCompound compoundEdit = new NamedCompound(Localization.lang("Set field")); - if (rename.isSelected()) { - if (fields.length > 1) { - JOptionPane.showMessageDialog(diag, Localization.lang("You can only rename one field at a time"), "", - JOptionPane.ERROR_MESSAGE); - return; // Do not close the dialog. - } else { - compoundEdit.addEdit(MassSetFieldAction.massRenameField(entryList, fields[0], textFieldRename.getText(), - overwrite.isSelected())); - } - } else if (append.isSelected()) { - for (String field : fields) { - compoundEdit.addEdit(MassSetFieldAction.massAppendField(entryList, field, textFieldAppend.getText())); - } - } else { - for (String field : fields) { - compoundEdit.addEdit(MassSetFieldAction.massSetField(entryList, field, - set.isSelected() ? toSet : null, - overwrite.isSelected())); - } - } - compoundEdit.end(); - bp.getUndoManager().addEdit(compoundEdit); - bp.markBaseChanged(); - } - - /** - * Set a given field to a given value for all entries in a Collection. This method DOES NOT update any UndoManager, - * but returns a relevant CompoundEdit that should be registered by the caller. - * - * @param entries The entries to set the field for. - * @param field The name of the field to set. - * @param textToSet The value to set. This value can be null, indicating that the field should be cleared. - * @param overwriteValues Indicate whether the value should be set even if an entry already has the field set. - * @return A CompoundEdit for the entire operation. - */ - private static UndoableEdit massSetField(Collection entries, String field, String textToSet, - boolean overwriteValues) { - - NamedCompound compoundEdit = new NamedCompound(Localization.lang("Set field")); - for (BibEntry entry : entries) { - Optional oldValue = entry.getField(field); - // If we are not allowed to overwrite values, check if there is a - // nonempty - // value already for this entry: - if (!overwriteValues && (oldValue.isPresent()) && !oldValue.get().isEmpty()) { - continue; - } - if (textToSet == null) { - entry.clearField(field); - } else { - entry.setField(field, textToSet); - } - compoundEdit.addEdit(new UndoableFieldChange(entry, field, oldValue.orElse(null), textToSet)); - } - compoundEdit.end(); - return compoundEdit; - } - - /** - * Append a given value to a given field for all entries in a Collection. This method DOES NOT update any UndoManager, - * but returns a relevant CompoundEdit that should be registered by the caller. - * - * @param entries The entries to process the operation for. - * @param field The name of the field to append to. - * @param textToAppend The value to set. A null in this case will simply preserve the current field state. - * @return A CompoundEdit for the entire operation. - */ - private static UndoableEdit massAppendField(Collection entries, String field, String textToAppend) { - - String newValue = ""; - - if (textToAppend != null) { - newValue = textToAppend; - } - - NamedCompound compoundEdit = new NamedCompound(Localization.lang("Append field")); - for (BibEntry entry : entries) { - Optional oldValue = entry.getField(field); - entry.setField(field, oldValue.orElse("") + newValue); - compoundEdit.addEdit(new UndoableFieldChange(entry, field, oldValue.orElse(null), newValue)); - } - compoundEdit.end(); - return compoundEdit; - } - - /** - * Move contents from one field to another for a Collection of entries. - * - * @param entries The entries to do this operation for. - * @param field The field to move contents from. - * @param newField The field to move contents into. - * @param overwriteValues If true, overwrites any existing values in the new field. If false, makes no change for - * entries with existing value in the new field. - * @return A CompoundEdit for the entire operation. - */ - private static UndoableEdit massRenameField(Collection entries, String field, String newField, - boolean overwriteValues) { - NamedCompound compoundEdit = new NamedCompound(Localization.lang("Rename field")); - for (BibEntry entry : entries) { - Optional valToMove = entry.getField(field); - // If there is no value, do nothing: - if ((!valToMove.isPresent()) || valToMove.get().isEmpty()) { - continue; - } - // If we are not allowed to overwrite values, check if there is a - // non-empty value already for this entry for the new field: - Optional valInNewField = entry.getField(newField); - if (!overwriteValues && (valInNewField.isPresent()) && !valInNewField.get().isEmpty()) { - continue; - } - - entry.setField(newField, valToMove.get()); - compoundEdit.addEdit(new UndoableFieldChange(entry, newField, valInNewField.orElse(null), valToMove.get())); - entry.clearField(field); - compoundEdit.addEdit(new UndoableFieldChange(entry, field, valToMove.get(), null)); - } - compoundEdit.end(); - return compoundEdit; - } - - private static String[] getFieldNames(String s) { - return s.split("[\\s;,]"); - } -} diff --git a/src/main/java/org/jabref/gui/actions/MnemonicAwareAction.java b/src/main/java/org/jabref/gui/actions/MnemonicAwareAction.java index 13ba654cc38..b1bfa9f87d5 100644 --- a/src/main/java/org/jabref/gui/actions/MnemonicAwareAction.java +++ b/src/main/java/org/jabref/gui/actions/MnemonicAwareAction.java @@ -4,7 +4,7 @@ import javax.swing.Action; import javax.swing.Icon; -import org.jabref.gui.IconTheme; +import org.jabref.gui.icon.IconTheme; /** * This class extends {@link AbstractAction} with the ability to set diff --git a/src/main/java/org/jabref/gui/actions/NewDatabaseAction.java b/src/main/java/org/jabref/gui/actions/NewDatabaseAction.java index 5aa52aa62b0..90b2f8ced30 100644 --- a/src/main/java/org/jabref/gui/actions/NewDatabaseAction.java +++ b/src/main/java/org/jabref/gui/actions/NewDatabaseAction.java @@ -1,10 +1,5 @@ package org.jabref.gui.actions; -import java.awt.event.ActionEvent; - -import javax.swing.Action; - -import org.jabref.gui.IconTheme; import org.jabref.gui.JabRefFrame; import org.jabref.logic.l10n.Localization; import org.jabref.model.Defaults; @@ -12,24 +7,20 @@ import org.jabref.model.database.BibDatabaseMode; /** - * The action concerned with opening a new database. + * Create a new, empty, database. */ -public class NewDatabaseAction extends MnemonicAwareAction { +public class NewDatabaseAction extends SimpleCommand { private final JabRefFrame jabRefFrame; private final BibDatabaseMode mode; public NewDatabaseAction(JabRefFrame jabRefFrame, BibDatabaseMode mode) { - super(IconTheme.JabRefIcon.NEW.getIcon()); this.jabRefFrame = jabRefFrame; this.mode = mode; - putValue(Action.NAME, Localization.menuTitle("New %0 library", mode.getFormattedName())); - putValue(Action.SHORT_DESCRIPTION, Localization.lang("New %0 library", mode.getFormattedName())); } @Override - public void actionPerformed(ActionEvent e) { - // Create a new, empty, database. + public void execute() { BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(new Defaults(BibDatabaseMode.BIBTEX)); bibDatabaseContext.setMode(mode); jabRefFrame.addTab(bibDatabaseContext, true); diff --git a/src/main/java/org/jabref/gui/actions/NewEntryAction.java b/src/main/java/org/jabref/gui/actions/NewEntryAction.java index c8f4a198591..a5b483a4d67 100644 --- a/src/main/java/org/jabref/gui/actions/NewEntryAction.java +++ b/src/main/java/org/jabref/gui/actions/NewEntryAction.java @@ -1,84 +1,71 @@ package org.jabref.gui.actions; -import java.awt.event.ActionEvent; import java.util.HashMap; import java.util.Map; - -import javax.swing.Action; -import javax.swing.KeyStroke; +import java.util.Optional; import org.jabref.Globals; -import org.jabref.gui.EntryTypeDialog; -import org.jabref.gui.IconTheme; +import org.jabref.gui.DialogService; +import org.jabref.gui.EntryTypeView; import org.jabref.gui.JabRefFrame; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.EntryTypes; +import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.EntryType; -import org.jabref.model.strings.StringUtil; +import org.jabref.preferences.JabRefPreferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class NewEntryAction extends MnemonicAwareAction { +public class NewEntryAction extends SimpleCommand { + private static final Logger LOGGER = LoggerFactory.getLogger(NewEntryAction.class); private final JabRefFrame jabRefFrame; - private String type; // The type of item to create. - - public NewEntryAction(JabRefFrame jabRefFrame, KeyStroke key) { - // This action leads to a dialog asking for entry type. - super(IconTheme.JabRefIcon.ADD_ENTRY.getIcon()); - this.jabRefFrame = jabRefFrame; - putValue(Action.NAME, Localization.menuTitle("New entry") + "..."); - putValue(Action.ACCELERATOR_KEY, key); - putValue(Action.SHORT_DESCRIPTION, Localization.lang("New BibTeX entry")); - } + /** + * The type of the entry to create. + */ + private final Optional type; + private final DialogService dialogService; + private final JabRefPreferences preferences; - public NewEntryAction(JabRefFrame jabRefFrame, String type) { + public NewEntryAction(JabRefFrame jabRefFrame, DialogService dialogService, JabRefPreferences preferences) { this.jabRefFrame = jabRefFrame; - // This action leads to the creation of a specific entry. - putValue(Action.NAME, StringUtil.capitalizeFirst(type)); - this.type = type; + this.type = Optional.empty(); + this.dialogService = dialogService; + this.preferences = preferences; } - public NewEntryAction(JabRefFrame jabRefFrame, String type, KeyStroke key) { + public NewEntryAction(JabRefFrame jabRefFrame, EntryType type, DialogService dialogService, JabRefPreferences preferences) { this.jabRefFrame = jabRefFrame; - // This action leads to the creation of a specific entry. - putValue(Action.NAME, StringUtil.capitalizeFirst(type)); - putValue(Action.ACCELERATOR_KEY, key); - this.type = type; + this.type = Optional.of(type); + this.dialogService = dialogService; + this.preferences = preferences; } @Override - public void actionPerformed(ActionEvent e) { - String thisType = type; - if (thisType == null) { - EntryTypeDialog etd = new EntryTypeDialog(jabRefFrame); - etd.setLocationRelativeTo(jabRefFrame); - etd.setVisible(true); - EntryType tp = etd.getChoice(); - if (tp == null) { - return; - } - thisType = tp.getName(); - - trackNewEntry(tp); + public void execute() { + if (jabRefFrame.getBasePanelCount() <= 0) { + LOGGER.error("Action 'New entry' must be disabled when no database is open."); + return; } - if (jabRefFrame.getBasePanelCount() > 0) { - jabRefFrame.getCurrentBasePanel().newEntry( - EntryTypes.getType(thisType, jabRefFrame.getCurrentBasePanel().getBibDatabaseContext().getMode()) - .get()); + if (type.isPresent()) { + jabRefFrame.getCurrentBasePanel().insertEntry(new BibEntry(type.get())); } else { - LOGGER.info("Action 'New entry' must be disabled when no database is open."); + EntryTypeView typeChoiceDialog = new EntryTypeView(jabRefFrame.getCurrentBasePanel(), dialogService, preferences); + EntryType selectedType = typeChoiceDialog.showAndWait().orElse(null); + if (selectedType == null) { + return; + } + + trackNewEntry(selectedType); + jabRefFrame.getCurrentBasePanel().insertEntry(new BibEntry(selectedType)); } } private void trackNewEntry(EntryType type) { Map properties = new HashMap<>(); properties.put("EntryType", type.getName()); - Map measurements = new HashMap<>(); - Globals.getTelemetryClient().ifPresent(client -> client.trackEvent("NewEntry", properties, measurements)); + Globals.getTelemetryClient().ifPresent(client -> client.trackEvent("NewEntry", properties, new HashMap<>())); } } diff --git a/src/main/java/org/jabref/gui/actions/NewEntryFromPlainTextAction.java b/src/main/java/org/jabref/gui/actions/NewEntryFromPlainTextAction.java new file mode 100644 index 00000000000..b4c3faa9a2b --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/NewEntryFromPlainTextAction.java @@ -0,0 +1,54 @@ +package org.jabref.gui.actions; + +import org.jabref.gui.DialogService; +import org.jabref.gui.EntryTypeView; +import org.jabref.gui.JabRefFrame; +import org.jabref.gui.plaintextimport.TextInputDialog; +import org.jabref.logic.util.UpdateField; +import org.jabref.logic.util.UpdateFieldPreferences; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.EntryType; +import org.jabref.preferences.JabRefPreferences; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NewEntryFromPlainTextAction extends SimpleCommand { + + private static final Logger LOGGER = LoggerFactory.getLogger(NewEntryFromPlainTextAction.class); + + private final UpdateFieldPreferences prefs; + private final JabRefFrame jabRefFrame; + private final DialogService dialogService; + private final JabRefPreferences preferences; + + public NewEntryFromPlainTextAction(JabRefFrame jabRefFrame, UpdateFieldPreferences prefs, DialogService dialogService, JabRefPreferences preferences) { + this.jabRefFrame = jabRefFrame; + this.prefs = prefs; + this.dialogService = dialogService; + this.preferences = preferences; + + } + + @Override + public void execute() { + if (jabRefFrame.getBasePanelCount() <= 0) { + LOGGER.error("Action 'New entry' must be disabled when no database is open."); + return; + } + + EntryTypeView typeChoiceDialog = new EntryTypeView(jabRefFrame.getCurrentBasePanel(), dialogService, preferences); + EntryType selectedType = typeChoiceDialog.showAndWait().orElse(null); + if (selectedType == null) { + return; + } + BibEntry bibEntry = new BibEntry(selectedType); + + TextInputDialog tidialog = new TextInputDialog(jabRefFrame, bibEntry); + tidialog.setVisible(true); + if (tidialog.okPressed()) { + UpdateField.setAutomaticFields(bibEntry, false, false, prefs); + jabRefFrame.getCurrentBasePanel().insertEntry(bibEntry); + } + } +} diff --git a/src/main/java/org/jabref/gui/actions/NewSubDatabaseAction.java b/src/main/java/org/jabref/gui/actions/NewSubDatabaseAction.java deleted file mode 100644 index 199f0400e4d..00000000000 --- a/src/main/java/org/jabref/gui/actions/NewSubDatabaseAction.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.jabref.gui.actions; - -import java.awt.event.ActionEvent; - -import javax.swing.Action; - -import org.jabref.Globals; -import org.jabref.gui.BasePanel; -import org.jabref.gui.IconTheme; -import org.jabref.gui.JabRefFrame; -import org.jabref.gui.auximport.FromAuxDialog; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.Defaults; -import org.jabref.model.database.BibDatabaseContext; - -/** - * The action concerned with generate a new (sub-)database from latex AUX file. - */ -public class NewSubDatabaseAction extends MnemonicAwareAction { - - private final JabRefFrame jabRefFrame; - - public NewSubDatabaseAction(JabRefFrame jabRefFrame) { - super(IconTheme.JabRefIcon.NEW.getIcon()); - this.jabRefFrame = jabRefFrame; - putValue(Action.NAME, Localization.menuTitle("New sublibrary based on AUX file") + "..."); - putValue(Action.SHORT_DESCRIPTION, Localization.lang("New BibTeX sublibrary")); - } - - @Override - public void actionPerformed(ActionEvent e) { - // Create a new, empty, database. - - FromAuxDialog dialog = new FromAuxDialog(jabRefFrame, "", true, jabRefFrame.getTabbedPane()); - - dialog.setLocationRelativeTo(jabRefFrame); - dialog.setVisible(true); - - if (dialog.generatePressed()) { - Defaults defaults = new Defaults(Globals.prefs.getDefaultBibDatabaseMode()); - BasePanel bp = new BasePanel(jabRefFrame, new BibDatabaseContext(dialog.getGenerateDB(), defaults)); - jabRefFrame.addTab(bp, true); - jabRefFrame.output(Localization.lang("New library created.")); - } - } -} diff --git a/src/main/java/org/jabref/gui/actions/NewSubLibraryAction.java b/src/main/java/org/jabref/gui/actions/NewSubLibraryAction.java new file mode 100644 index 00000000000..a4f42eca838 --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/NewSubLibraryAction.java @@ -0,0 +1,22 @@ +package org.jabref.gui.actions; + +import org.jabref.gui.JabRefFrame; +import org.jabref.gui.auximport.FromAuxDialog; + +/** + * The action concerned with generate a new (sub-)database from latex AUX file. + */ +public class NewSubLibraryAction extends SimpleCommand { + + private final JabRefFrame jabRefFrame; + + public NewSubLibraryAction(JabRefFrame jabRefFrame) { + this.jabRefFrame = jabRefFrame; + } + + @Override + public void execute() { + FromAuxDialog dialog = new FromAuxDialog(jabRefFrame); + dialog.showAndWait(); + } +} diff --git a/src/main/java/org/jabref/gui/actions/OldCommandWrapper.java b/src/main/java/org/jabref/gui/actions/OldCommandWrapper.java new file mode 100644 index 00000000000..c187b412684 --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/OldCommandWrapper.java @@ -0,0 +1,50 @@ +package org.jabref.gui.actions; + +import javafx.beans.property.ReadOnlyDoubleProperty; + +import org.jabref.gui.BasePanel; +import org.jabref.gui.util.BindingsHelper; + +import de.saxsys.mvvmfx.utils.commands.CommandBase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This wraps the old Swing commands so that they fit into the new infrastructure. + * In the long term, this class should be removed. + */ +public class OldCommandWrapper extends CommandBase { + + private static final Logger LOGGER = LoggerFactory.getLogger(OldCommandWrapper.class); + + private final Actions command; + private final BasePanel panel; + + public OldCommandWrapper(Actions command, BasePanel panel) { + this.command = command; + this.panel = panel; + } + + @Override + public void execute() { + try { + panel.runCommand(command); + } catch (Throwable ex) { + LOGGER.debug("Cannot execute command " + command + ".", ex); + } + } + + @Override + public double getProgress() { + return 0; + } + + @Override + public ReadOnlyDoubleProperty progressProperty() { + return null; + } + + public void setExecutable(boolean executable) { + this.executable.bind(BindingsHelper.constantOf(executable)); + } +} diff --git a/src/main/java/org/jabref/gui/actions/OldCommandWrapperForActiveDatabase.java b/src/main/java/org/jabref/gui/actions/OldCommandWrapperForActiveDatabase.java new file mode 100644 index 00000000000..d0807faee27 --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/OldCommandWrapperForActiveDatabase.java @@ -0,0 +1,48 @@ +package org.jabref.gui.actions; + +import javafx.beans.property.ReadOnlyDoubleProperty; + +import org.jabref.JabRefGUI; +import org.jabref.gui.util.BindingsHelper; + +import de.saxsys.mvvmfx.utils.commands.CommandBase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This wraps the old Swing commands so that they fit into the new infrastructure. + * In the long term, this class should be removed. + */ +public class OldCommandWrapperForActiveDatabase extends CommandBase { + + private static final Logger LOGGER = LoggerFactory.getLogger(OldCommandWrapperForActiveDatabase.class); + + private final Actions command; + + public OldCommandWrapperForActiveDatabase(Actions command) { + this.command = command; + } + + @Override + public void execute() { + try { + JabRefGUI.getMainFrame().getCurrentBasePanel().runCommand(command); + } catch (Throwable ex) { + LOGGER.debug("Cannot execute command " + command + ".", ex); + } + } + + @Override + public double getProgress() { + return 0; + } + + @Override + public ReadOnlyDoubleProperty progressProperty() { + return null; + } + + public void setExecutable(boolean executable) { + this.executable.bind(BindingsHelper.constantOf(executable)); + } +} diff --git a/src/main/java/org/jabref/gui/actions/OldDatabaseCommandWrapper.java b/src/main/java/org/jabref/gui/actions/OldDatabaseCommandWrapper.java new file mode 100644 index 00000000000..a752da3ce9c --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/OldDatabaseCommandWrapper.java @@ -0,0 +1,55 @@ +package org.jabref.gui.actions; + +import java.util.Optional; + +import javafx.beans.property.ReadOnlyDoubleProperty; + +import org.jabref.gui.JabRefFrame; +import org.jabref.gui.StateManager; + +import de.saxsys.mvvmfx.utils.commands.CommandBase; +import org.fxmisc.easybind.EasyBind; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A command that is only executable if a database is open. + */ +public class OldDatabaseCommandWrapper extends CommandBase { + + private static final Logger LOGGER = LoggerFactory.getLogger(OldDatabaseCommandWrapper.class); + + private final Actions command; + private final JabRefFrame frame; + + public OldDatabaseCommandWrapper(Actions command, JabRefFrame frame, StateManager stateManager) { + this.command = command; + this.frame = frame; + + this.executable.bind( + EasyBind.map(stateManager.activeDatabaseProperty(), Optional::isPresent)); + } + + @Override + public void execute() { + if (frame.getTabbedPane().getTabs().size() > 0) { + try { + frame.getCurrentBasePanel().runCommand(command); + } catch (Throwable ex) { + LOGGER.error("Problem with executing command: " + command, ex); + } + } else { + LOGGER.info("Action '" + command + "' must be disabled when no database is open."); + } + } + + @Override + public double getProgress() { + return 0; + } + + @Override + public ReadOnlyDoubleProperty progressProperty() { + return null; + } +} diff --git a/src/main/java/org/jabref/gui/actions/OpenBrowserAction.java b/src/main/java/org/jabref/gui/actions/OpenBrowserAction.java index 56457b4c3d5..9cd0b92524e 100644 --- a/src/main/java/org/jabref/gui/actions/OpenBrowserAction.java +++ b/src/main/java/org/jabref/gui/actions/OpenBrowserAction.java @@ -1,49 +1,19 @@ package org.jabref.gui.actions; -import java.awt.event.ActionEvent; - -import javax.swing.AbstractAction; -import javax.swing.Action; -import javax.swing.Icon; - import org.jabref.gui.desktop.JabRefDesktop; -public class OpenBrowserAction extends AbstractAction { +public class OpenBrowserAction extends SimpleCommand { private final String urlToOpen; - /** - * OpenBrowserAction without icons - only to be used for menus - * - * @param urlToOpen URL string of an URL to be shown in the default web browser - * @param menuTitle title of the menu entry; should already be localized - * @param description description shown in a tooltip hovering over the menu/icon bar entry; should already be localized - */ - public OpenBrowserAction(String urlToOpen, String menuTitle, String description) { - super(menuTitle); - this.urlToOpen = urlToOpen; - putValue(Action.SHORT_DESCRIPTION, description); - } - - /** - * OpenBrowserAction with icons - * - * @param urlToOpen URL string of an URL to be shown in the default web browser - * @param menuTitle title of the menu entry; should already be localized - * @param description description shown in a tooltip hovering over the menu/icon bar entry; should already be localized - * @param smallIcon smallIcon to be shown in the menus - * @param largeIcon larger icon to be shown in the icon bar - */ - public OpenBrowserAction(String urlToOpen, String menuTitle, String description, Icon smallIcon, Icon largeIcon) { - super(menuTitle, smallIcon); + public OpenBrowserAction(String urlToOpen) { this.urlToOpen = urlToOpen; - putValue(Action.SHORT_DESCRIPTION, description); - putValue(Action.LARGE_ICON_KEY, largeIcon); } @Override - public void actionPerformed(ActionEvent e) { + public void execute() { JabRefDesktop.openBrowserShowPopup(urlToOpen); + } } diff --git a/src/main/java/org/jabref/gui/actions/SearchForUpdateAction.java b/src/main/java/org/jabref/gui/actions/SearchForUpdateAction.java index acf9807c849..be3415c2120 100644 --- a/src/main/java/org/jabref/gui/actions/SearchForUpdateAction.java +++ b/src/main/java/org/jabref/gui/actions/SearchForUpdateAction.java @@ -1,20 +1,28 @@ package org.jabref.gui.actions; -import java.awt.event.ActionEvent; +import org.jabref.gui.DialogService; +import org.jabref.gui.help.VersionWorker; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.util.BuildInfo; +import org.jabref.preferences.VersionPreferences; -import javax.swing.AbstractAction; +public class SearchForUpdateAction extends SimpleCommand { -import org.jabref.JabRefGUI; -import org.jabref.logic.l10n.Localization; + private final BuildInfo buildInfo; + private final VersionPreferences versionPreferences; + private final DialogService dialogService; + private final TaskExecutor taskExecutor; -public class SearchForUpdateAction extends AbstractAction { - - public SearchForUpdateAction() { - super(Localization.lang("Check for updates")); + public SearchForUpdateAction(BuildInfo buildInfo, VersionPreferences versionPreferences, DialogService dialogService, TaskExecutor taskExecutor) { + this.buildInfo = buildInfo; + this.versionPreferences = versionPreferences; + this.dialogService = dialogService; + this.taskExecutor = taskExecutor; } @Override - public void actionPerformed(ActionEvent e) { - JabRefGUI.checkForNewVersion(true); + public void execute() { + new VersionWorker(buildInfo.getVersion(), versionPreferences.getIgnoredVersion(), dialogService, taskExecutor) + .checkForNewVersionAsync(true); } } diff --git a/src/main/java/org/jabref/gui/actions/SetupGeneralFieldsAction.java b/src/main/java/org/jabref/gui/actions/SetupGeneralFieldsAction.java new file mode 100644 index 00000000000..a6254ef288c --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/SetupGeneralFieldsAction.java @@ -0,0 +1,13 @@ +package org.jabref.gui.actions; + +import org.jabref.gui.customizefields.CustomizeGeneralFieldsDialogView; + +public class SetupGeneralFieldsAction extends SimpleCommand { + + @Override + public void execute() { + new CustomizeGeneralFieldsDialogView().show(); + + } + +} diff --git a/src/main/java/org/jabref/gui/actions/ShowDocumentViewerAction.java b/src/main/java/org/jabref/gui/actions/ShowDocumentViewerAction.java new file mode 100644 index 00000000000..7c15ae6d119 --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/ShowDocumentViewerAction.java @@ -0,0 +1,12 @@ +package org.jabref.gui.actions; + +import org.jabref.gui.documentviewer.DocumentViewerView; + +public class ShowDocumentViewerAction extends SimpleCommand { + + @Override + public void execute() { + new DocumentViewerView().show(); + } + +} diff --git a/src/main/java/org/jabref/gui/actions/ShowPreferencesAction.java b/src/main/java/org/jabref/gui/actions/ShowPreferencesAction.java new file mode 100644 index 00000000000..e6c0a29684c --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/ShowPreferencesAction.java @@ -0,0 +1,22 @@ +package org.jabref.gui.actions; + +import org.jabref.gui.JabRefFrame; +import org.jabref.gui.preferences.PreferencesDialog; +import org.jabref.gui.util.TaskExecutor; + +public class ShowPreferencesAction extends SimpleCommand { + + private final JabRefFrame jabRefFrame; + private final TaskExecutor taskExecutor; + + public ShowPreferencesAction(JabRefFrame jabRefFrame, TaskExecutor taskExecutor) { + this.jabRefFrame = jabRefFrame; + this.taskExecutor = taskExecutor; + } + + @Override + public void execute() { + PreferencesDialog preferencesDialog = new PreferencesDialog(jabRefFrame, taskExecutor); + preferencesDialog.showAndWait(); + } +} diff --git a/src/main/java/org/jabref/gui/actions/SimpleCommand.java b/src/main/java/org/jabref/gui/actions/SimpleCommand.java new file mode 100644 index 00000000000..78b52e7f206 --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/SimpleCommand.java @@ -0,0 +1,26 @@ +package org.jabref.gui.actions; + +import javafx.beans.property.ReadOnlyDoubleProperty; + +import org.jabref.gui.util.BindingsHelper; + +import de.saxsys.mvvmfx.utils.commands.CommandBase; + +/** + * A simple command that does not track progress of the action. + */ +public abstract class SimpleCommand extends CommandBase { + @Override + public double getProgress() { + return 0; + } + + @Override + public ReadOnlyDoubleProperty progressProperty() { + return null; + } + + public void setExecutable(boolean executable) { + this.executable.bind(BindingsHelper.constantOf(executable)); + } +} diff --git a/src/main/java/org/jabref/gui/actions/SortTabsAction.java b/src/main/java/org/jabref/gui/actions/SortTabsAction.java deleted file mode 100644 index 55f2432b0ce..00000000000 --- a/src/main/java/org/jabref/gui/actions/SortTabsAction.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.jabref.gui.actions; - -import java.awt.event.ActionEvent; -import java.util.Comparator; -import java.util.Locale; -import java.util.Map; -import java.util.TreeMap; - -import javax.swing.Action; - -import org.jabref.gui.BasePanel; -import org.jabref.gui.JabRefFrame; -import org.jabref.logic.l10n.Localization; - -/** - * This action rearranges all tabs in the main tabbed pane of the given JabRefFrame - * in alphabetical order. - */ -public class SortTabsAction extends MnemonicAwareAction implements Comparator { - private final JabRefFrame frame; - - public SortTabsAction(JabRefFrame frame) { - putValue(Action.NAME, Localization.menuTitle("Sort tabs")); - putValue(Action.SHORT_DESCRIPTION, Localization.lang("Rearrange tabs alphabetically by title")); - this.frame = frame; - } - - @Override - public void actionPerformed(ActionEvent e) { - // Make a sorted Map that compares case-insensitively: - Map map = new TreeMap<>(this); - - for (BasePanel panel : frame.getBasePanelList()) { - map.put(panel.getTabTitle(), panel); - } - - frame.getTabbedPane().removeAll(); - for (Map.Entry entry : map.entrySet()) { - frame.addTab(entry.getValue(), false); - } - } - - @Override - public int compare(String o1, String o2) { - return o1.toLowerCase(Locale.ROOT).compareTo(o2.toLowerCase(Locale.ROOT)); - } -} diff --git a/src/main/java/org/jabref/gui/actions/StandardActions.java b/src/main/java/org/jabref/gui/actions/StandardActions.java new file mode 100644 index 00000000000..6e7493f32d7 --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -0,0 +1,233 @@ +package org.jabref.gui.actions; + +import java.util.Optional; + +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.icon.JabRefIcon; +import org.jabref.gui.keyboard.KeyBinding; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseMode; + +public enum StandardActions implements Action { + + COPY_MORE(Localization.lang("Copy") + "..."), + COPY_TITLE(Localization.lang("Copy title"), KeyBinding.COPY_TITLE), + COPY_KEY(Localization.lang("Copy BibTeX key"), KeyBinding.COPY_BIBTEX_KEY), + COPY_CITE_KEY(Localization.lang("Copy \\cite{BibTeX key}"), KeyBinding.COPY_CITE_BIBTEX_KEY), + COPY_KEY_AND_TITLE(Localization.lang("Copy BibTeX key and title"), KeyBinding.COPY_BIBTEX_KEY_AND_TITLE), + COPY_KEY_AND_LINK(Localization.lang("Copy BibTeX key and link"), KeyBinding.COPY_BIBTEX_KEY_AND_LINK), + COPY_CITATION_HTML(Localization.lang("Copy citation") + " (HTML)", KeyBinding.COPY_PREVIEW), + COPY_CITATION_MORE(Localization.lang("Copy citation") + "..."), + COPY_CITATION_TEXT("Text"), + COPY_CITATION_RTF("RTF"), + COPY_CITATION_ASCII_DOC("AsciiDoc"), + COPY_CITATION_XSLFO("XSL-FO"), + COPY_CITATION_PREVIEW(Localization.lang("Copy preview"), KeyBinding.COPY_PREVIEW), + EXPORT_TO_CLIPBOARD(Localization.lang("Export to clipboard"), IconTheme.JabRefIcons.EXPORT_TO_CLIPBOARD), + EXPORT_SELECTED_TO_CLIPBOARD(Localization.lang("Export selected entries to clipboard"), IconTheme.JabRefIcons.EXPORT_TO_CLIPBOARD), + COPY(Localization.lang("Copy"), IconTheme.JabRefIcons.COPY, KeyBinding.COPY), + PASTE(Localization.lang("Paste"), IconTheme.JabRefIcons.PASTE, KeyBinding.PASTE), + CUT(Localization.lang("Cut"), IconTheme.JabRefIcons.CUT, KeyBinding.CUT), + DELETE(Localization.lang("Delete"), IconTheme.JabRefIcons.DELETE_ENTRY), + DELETE_ENTRY(Localization.lang("Delete Entry"), IconTheme.JabRefIcons.DELETE_ENTRY, KeyBinding.DELETE_ENTRY), + SEND_AS_EMAIL(Localization.lang("Send as email"), IconTheme.JabRefIcons.EMAIL), + 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), + MERGE_WITH_FETCHED_ENTRY(Localization.lang("Get BibTeX 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")), + PRIORITY_HIGH(Localization.lang("Set priority to high"), IconTheme.JabRefIcons.PRIORITY_HIGH), + PRIORITY_MEDIUM(Localization.lang("Set priority to medium"), IconTheme.JabRefIcons.PRIORITY_MEDIUM), + PRIORITY_LOW(Localization.lang("Set priority to low"), IconTheme.JabRefIcons.PRIORITY_LOW), + QUALITY(Localization.lang("Quality"), IconTheme.JabRefIcons.QUALITY), + QUALITY_ASSURED(Localization.lang("Toggle quality assured"), IconTheme.JabRefIcons.QUALITY_ASSURED), + RANKING(Localization.lang("Rank"), IconTheme.JabRefIcons.RANKING), + CLEAR_RANK(Localization.lang("Clear rank")), + RANK_1("", IconTheme.JabRefIcons.RANK1), + RANK_2("", IconTheme.JabRefIcons.RANK2), + RANK_3("", IconTheme.JabRefIcons.RANK3), + RANK_4("", IconTheme.JabRefIcons.RANK4), + RANK_5("", IconTheme.JabRefIcons.RANK5), + PRINTED(Localization.lang("Printed"), IconTheme.JabRefIcons.PRINTED), + TOGGLE_PRINTED(Localization.lang("Toggle print status"), IconTheme.JabRefIcons.PRINTED), + READ_STATUS(Localization.lang("Read status"), IconTheme.JabRefIcons.READ_STATUS), + CLEAR_READ_STATUS(Localization.lang("Clear read status")), + READ(Localization.lang("Set read status to read"), IconTheme.JabRefIcons.READ_STATUS_READ), + SKIMMED(Localization.lang("Set read status to skimmed"), IconTheme.JabRefIcons.READ_STATUS_SKIMMED), + RELEVANCE(Localization.lang("Relevance"), IconTheme.JabRefIcons.RELEVANCE), + RELEVANT(Localization.lang("Toggle relevance"), IconTheme.JabRefIcons.RELEVANCE), + NEW_LIBRARY(Localization.lang("New library"), IconTheme.JabRefIcons.NEW), + NEW_LIBRARY_BIBTEX(Localization.lang("New %0 library", BibDatabaseMode.BIBTEX.getFormattedName()), IconTheme.JabRefIcons.NEW), + NEW_LIBRARY_BIBLATEX(Localization.lang("New %0 library", BibDatabaseMode.BIBLATEX.getFormattedName()), IconTheme.JabRefIcons.NEW), + OPEN_LIBRARY(Localization.lang("Open library"), IconTheme.JabRefIcons.OPEN, KeyBinding.OPEN_DATABASE), + IMPORT(Localization.lang("Import"), IconTheme.JabRefIcons.IMPORT), + EXPORT(Localization.lang("Export"), IconTheme.JabRefIcons.EXPORT, KeyBinding.EXPORT), + MERGE_DATABASE(Localization.lang("Append library"), Localization.lang("Append contents from a BibTeX library into the currently viewed library")), + SAVE_LIBRARY(Localization.lang("Save library"), IconTheme.JabRefIcons.SAVE, KeyBinding.SAVE_DATABASE), + SAVE_LIBRARY_AS(Localization.lang("Save library as..."), KeyBinding.SAVE_DATABASE_AS), + SAVE_SELECTED_AS_PLAIN_BIBTEX(Localization.lang("Save selected as plain BibTeX...")), + SAVE_ALL(Localization.lang("Save all"), Localization.lang("Save all open libraries"), IconTheme.JabRefIcons.SAVE_ALL, KeyBinding.SAVE_ALL), + IMPORT_INTO_NEW_LIBRARY(Localization.lang("Import into new library"), KeyBinding.IMPORT_INTO_NEW_DATABASE), + IMPORT_INTO_CURRENT_LIBRARY(Localization.lang("Import into current library"), KeyBinding.IMPORT_INTO_CURRENT_DATABASE), + EXPORT_ALL(Localization.lang("Export all entries")), + EXPORT_SELECTED(Localization.lang("Export selected entries"), KeyBinding.EXPORT_SELECTED), + CONNECT_TO_SHARED_DB(Localization.lang("Connect to shared database"), IconTheme.JabRefIcons.CONNECT_DB), + PULL_CHANGES_FROM_SHARED_DB(Localization.lang("Pull changes from shared database"), KeyBinding.PULL_CHANGES_FROM_SHARED_DATABASE), + CLOSE_LIBRARY(Localization.lang("Close library"), Localization.lang("Close the current library"), IconTheme.JabRefIcons.CLOSE, KeyBinding.CLOSE_DATABASE), + QUIT(Localization.lang("Quit"), Localization.lang("Quit JabRef"), IconTheme.JabRefIcons.CLOSE_JABREF, KeyBinding.QUIT_JABREF), + UNDO(Localization.lang("Undo"), IconTheme.JabRefIcons.UNDO, KeyBinding.UNDO), + REDO(Localization.lang("Redo"), IconTheme.JabRefIcons.REDO, KeyBinding.REDO), + REPLACE_ALL(Localization.lang("Find and replace"), KeyBinding.REPLACE_STRING), + MANAGE_KEYWORDS(Localization.lang("Manage keywords")), + MASS_SET_FIELDS(Localization.lang("Manage field names & content")), + TOGGLE_GROUPS(Localization.lang("Groups interface"), IconTheme.JabRefIcons.TOGGLE_GROUPS, KeyBinding.TOGGLE_GROUPS_INTERFACE), + TOOGLE_OO(Localization.lang("OpenOffice/LibreOffice"), IconTheme.JabRefIcons.FILE_OPENOFFICE, KeyBinding.OPEN_OPEN_OFFICE_LIBRE_OFFICE_CONNECTION), + TOGGLE_WEB_SEARCH(Localization.lang("Web search"), Localization.lang("Toggle web search interface"), IconTheme.JabRefIcons.WWW, KeyBinding.WEB_SEARCH), + + NEW_SUB_LIBRARY_FROM_AUX(Localization.lang("New sublibrary based on AUX file") + "...", Localization.lang("New BibTeX sublibrary") + Localization.lang("This feature generates a new library based on which entries are needed in an existing LaTeX document."), IconTheme.JabRefIcons.NEW), + WRITE_XMP(Localization.lang("Write XMP-metadata to PDFs"), Localization.lang("Will write XMP-metadata to the PDFs linked from selected entries."), KeyBinding.WRITE_XMP), + OPEN_FOLDER(Localization.lang("Open folder"), Localization.lang("Open folder"), KeyBinding.OPEN_FOLDER), + OPEN_FILE(Localization.lang("Open file"), Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE), + OPEN_CONSOLE(Localization.lang("Open terminal here"), Localization.lang("Open terminal here"), IconTheme.JabRefIcons.CONSOLE, KeyBinding.OPEN_CONSOLE), + COPY_LINKED_FILES(Localization.lang("Copy linked files to folder...")), + ABBREVIATE(Localization.lang("Abbreviate journal names")), + ABBREVIATE_ISO("ISO", Localization.lang("Abbreviate journal names of the selected entries (ISO abbreviation)"), KeyBinding.ABBREVIATE), + ABBREVIATE_MEDLINE("MEDLINE", Localization.lang("Abbreviate journal names of the selected entries (MEDLINE abbreviation)")), + UNABBREVIATE(Localization.lang("Unabbreviate journal names"), Localization.lang("Unabbreviate journal names of the selected entries"), KeyBinding.UNABBREVIATE), + + MANAGE_CUSTOM_EXPORTS(Localization.lang("Manage custom exports")), + 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")), + BIBTEX_KEY_PATTERN(Localization.lang("BibTeX key patterns")), + SHOW_PREFS(Localization.lang("Preferences")), + MANAGE_JOURNALS(Localization.lang("Manage journal abbreviations")), + CUSTOMIZE_KEYBINDING(Localization.lang("Customize key bindings"), IconTheme.JabRefIcons.KEY_BINDINGS), + MANAGE_CONTENT_SELECTORS(Localization.lang("Manage content selectors"), IconTheme.JabRefIcons.PREFERENCES), + MANAGE_CITE_KEY_PATTERNS(Localization.lang("BibTeX key patterns")), + + 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), + TOGGLE_PREVIEW(Localization.lang("Entry preview"), IconTheme.JabRefIcons.TOGGLE_ENTRY_PREVIEW, KeyBinding.TOGGLE_ENTRY_PREVIEW), + NEXT_PREVIEW_STYLE(Localization.lang("Next citation style"), KeyBinding.NEXT_PREVIEW_LAYOUT), + PREVIOUS_PREVIEW_STYLE(Localization.lang("Previous citation 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), + NEW_ARTICLE(Localization.lang("New article"), IconTheme.JabRefIcons.ADD_ENTRY), + NEW_ENTRY_FROM_PLAINTEX(Localization.lang("New entry from plain text"), KeyBinding.NEW_FROM_PLAIN_TEXT), + LIBRARY_PROPERTIES(Localization.lang("Library properties")), + EDIT_PREAMBLE(Localization.lang("Edit preamble")), + EDIT_STRINGS(Localization.lang("Edit string constants"), IconTheme.JabRefIcons.EDIT_STRINGS, KeyBinding.EDIT_STRINGS), + + FIND_DUPLICATES(Localization.lang("Find duplicates"), IconTheme.JabRefIcons.FIND_DUPLICATES), + MERGE_ENTRIES(Localization.lang("Merge entries"), IconTheme.JabRefIcons.MERGE_ENTRIES), + RESOLVE_DUPLICATE_KEYS(Localization.lang("Resolve duplicate BibTeX keys"), Localization.lang("Find and remove duplicate BibTeX keys"), KeyBinding.RESOLVE_DUPLICATE_BIBTEX_KEYS), + CHECK_INTEGRITY(Localization.lang("Check integrity"), KeyBinding.CHECK_INTEGRITY), + FIND_UNLINKED_FILES(Localization.lang("Search for unlinked local files"), IconTheme.JabRefIcons.SEARCH, KeyBinding.FIND_UNLINKED_FILES), + AUTO_LINK_FILES(Localization.lang("Automatically set file links"), IconTheme.JabRefIcons.AUTO_FILE_LINK, KeyBinding.AUTOMATICALLY_LINK_FILES), + LOOKUP_DOC_IDENTIFIER(Localization.lang("Search document identifier online")), + LOOKUP_FULLTEXT(Localization.lang("Search full text documents online"), IconTheme.JabRefIcons.FILE_SEARCH, KeyBinding.DOWNLOAD_FULL_TEXT), + GENERATE_CITE_KEY(Localization.lang("Generate BibTeX key"), IconTheme.JabRefIcons.MAKE_KEY, KeyBinding.AUTOGENERATE_BIBTEX_KEYS), + GENERATE_CITE_KEYS(Localization.lang("Generate BibTeX keys"), IconTheme.JabRefIcons.MAKE_KEY, KeyBinding.AUTOGENERATE_BIBTEX_KEYS), + DOWNLOAD_FULL_TEXT(Localization.lang("Search full text documents online"), IconTheme.JabRefIcons.FILE_SEARCH, KeyBinding.DOWNLOAD_FULL_TEXT), + CLEANUP_ENTRIES(Localization.lang("Cleanup entries"), IconTheme.JabRefIcons.CLEANUP_ENTRIES, KeyBinding.CLEANUP), + SET_FILE_LINKS(Localization.lang("Automatically set file links"), KeyBinding.AUTOMATICALLY_LINK_FILES), + + HELP(Localization.lang("Online help"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), + WEB_MENU(Localization.lang("JabRef resources")), + OPEN_WEBPAGE(Localization.lang("Website"), Localization.lang("Opens JabRef's website")), + OPEN_FACEBOOK("Facebook", Localization.lang("Opens JabRef's Facebook page"), IconTheme.JabRefIcons.FACEBOOK), + OPEN_TWITTER("Twitter", Localization.lang("Opens JabRef's Twitter page"), IconTheme.JabRefIcons.TWITTER), + OPEN_BLOG(Localization.lang("Blog"), Localization.lang("Opens JabRef's blog"), IconTheme.JabRefIcons.BLOG), + OPEN_DEV_VERSION_LINK(Localization.lang("Development version"), Localization.lang("Opens a link where the current development version can be downloaded")), + OPEN_CHANGELOG(Localization.lang("View change log"), Localization.lang("See what has been changed in the JabRef versions")), + FORK_ME(Localization.lang("Fork me on GitHub"), Localization.lang("Opens JabRef's GitHub page"), IconTheme.JabRefIcons.GITHUB), + DONATE(Localization.lang("Donate to JabRef"), Localization.lang("Donate to JabRef"), IconTheme.JabRefIcons.DONATE), + OPEN_FORUM(Localization.lang("Online help forum"), Localization.lang("Online help forum"), IconTheme.JabRefIcons.FORUM), + ERROR_CONSOLE(Localization.lang("View event log"), Localization.lang("Display all error messages")), + SEARCH_FOR_UPDATES(Localization.lang("Check for updates")), + ABOUT(Localization.lang("About JabRef"), Localization.lang("About JabRef")); + + + private final String text; + private final String description; + private final Optional icon; + private final Optional keyBinding; + + StandardActions(String text) { + this(text, ""); + } + + StandardActions(String text, IconTheme.JabRefIcons icon) { + this.text = text; + this.description = ""; + this.icon = Optional.of(icon); + this.keyBinding = Optional.empty(); + } + + StandardActions(String text, IconTheme.JabRefIcons icon, KeyBinding keyBinding) { + this.text = text; + this.description = ""; + this.icon = Optional.of(icon); + this.keyBinding = Optional.of(keyBinding); + } + + StandardActions(String text, String description, IconTheme.JabRefIcons icon) { + this.text = text; + this.description = description; + this.icon = Optional.of(icon); + this.keyBinding = Optional.empty(); + } + + StandardActions(String text, String description, IconTheme.JabRefIcons icon, KeyBinding keyBinding) { + this.text = text; + this.description = description; + this.icon = Optional.of(icon); + this.keyBinding = Optional.of(keyBinding); + } + + StandardActions(String text, KeyBinding keyBinding) { + this.text = text; + this.description = ""; + this.keyBinding = Optional.of(keyBinding); + this.icon = Optional.empty(); + } + + StandardActions(String text, String description) { + this.text = text; + this.description = description; + this.icon = Optional.empty(); + this.keyBinding = Optional.empty(); + } + + StandardActions(String text, String description, KeyBinding keyBinding) { + this.text = text; + this.description = description; + this.icon = Optional.empty(); + this.keyBinding = Optional.of(keyBinding); + } + + @Override + public Optional getIcon() { + return icon; + } + + @Override + public Optional getKeyBinding() { + return keyBinding; + } + + @Override + public String getText() { + return text; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/src/main/java/org/jabref/gui/actions/WriteXMPAction.java b/src/main/java/org/jabref/gui/actions/WriteXMPAction.java new file mode 100644 index 00000000000..de4face8b53 --- /dev/null +++ b/src/main/java/org/jabref/gui/actions/WriteXMPAction.java @@ -0,0 +1,237 @@ +package org.jabref.gui.actions; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import javafx.application.Platform; +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextArea; +import javafx.scene.input.KeyCode; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.GridPane; +import javafx.scene.paint.Color; +import javafx.stage.Stage; + +import org.jabref.Globals; +import org.jabref.gui.BasePanel; +import org.jabref.gui.DialogService; +import org.jabref.gui.FXDialog; +import org.jabref.gui.util.BackgroundTask; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.xmp.XmpUtilWriter; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; + +public class WriteXMPAction extends SimpleCommand { + + private final BasePanel basePanel; + private OptionsDialog optionsDialog; + + private Collection entries; + + private BibDatabase database; + + private boolean shouldContinue = true; + + private int skipped; + private int entriesChanged; + private int errors; + private final DialogService dialogService; + + public WriteXMPAction(BasePanel basePanel) { + this.basePanel = basePanel; + dialogService = basePanel.frame().getDialogService(); + } + + @Override + public void execute() { + init(); + BackgroundTask.wrap(this::writeXMP) + .executeWith(Globals.TASK_EXECUTOR); + } + + public void init() { + database = basePanel.getDatabase(); + // Get entries and check if it makes sense to perform this operation + entries = basePanel.getSelectedEntries(); + + if (entries.isEmpty()) { + + entries = database.getEntries(); + + if (entries.isEmpty()) { + dialogService.showErrorDialogAndWait( + Localization.lang("Write XMP-metadata"), + Localization.lang("This operation requires one or more entries to be selected.")); + shouldContinue = false; + return; + + } else { + boolean confirm = dialogService.showConfirmationDialogAndWait( + Localization.lang("Write XMP-metadata"), + Localization.lang("Write XMP-metadata for all PDFs in current library?")); + if (confirm) { + shouldContinue = false; + return; + } + } + } + + errors = entriesChanged = skipped = 0; + + if (optionsDialog == null) { + optionsDialog = new OptionsDialog(); + } + optionsDialog.open(); + + basePanel.output(Localization.lang("Writing XMP-metadata...")); + } + + private void writeXMP() { + if (!shouldContinue) { + return; + } + + for (BibEntry entry : entries) { + + // Make a list of all PDFs linked from this entry: + List files = entry.getFiles().stream() + .filter(file -> file.getFileType().equalsIgnoreCase("pdf")) + .map(file -> file.findIn(basePanel.getBibDatabaseContext(), Globals.prefs.getFilePreferences())) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + + Platform.runLater(() -> optionsDialog.getProgressArea() + .appendText(entry.getCiteKeyOptional().orElse(Localization.lang("undefined")) + "\n")); + + if (files.isEmpty()) { + skipped++; + Platform.runLater(() -> optionsDialog.getProgressArea() + .appendText(" " + Localization.lang("Skipped - No PDF linked") + ".\n")); + } else { + for (Path file : files) { + if (Files.exists(file)) { + try { + XmpUtilWriter.writeXmp(file, entry, database, Globals.prefs.getXMPPreferences()); + Platform.runLater( + () -> optionsDialog.getProgressArea().appendText(" " + Localization.lang("OK") + ".\n")); + entriesChanged++; + } catch (Exception e) { + Platform.runLater(() -> { + optionsDialog.getProgressArea().appendText(" " + Localization.lang("Error while writing") + " '" + + file.toString() + "':\n"); + optionsDialog.getProgressArea().appendText(" " + e.getLocalizedMessage() + "\n"); + }); + errors++; + } + } else { + skipped++; + Platform.runLater(() -> { + optionsDialog.getProgressArea() + .appendText(" " + Localization.lang("Skipped - PDF does not exist") + ":\n"); + optionsDialog.getProgressArea().appendText(" " + file.toString() + "\n"); + }); + } + } + } + + if (optionsDialog.isCanceled()) { + Platform.runLater( + () -> optionsDialog.getProgressArea().appendText("\n" + Localization.lang("Operation canceled.") + "\n")); + break; + } + } + Platform.runLater(() -> { + optionsDialog.getProgressArea() + .appendText("\n" + + Localization.lang("Finished writing XMP for %0 file (%1 skipped, %2 errors).", String + .valueOf(entriesChanged), String.valueOf(skipped), String.valueOf(errors))); + optionsDialog.done(); + }); + + if (!shouldContinue) { + return; + } + + basePanel.output(Localization.lang("Finished writing XMP for %0 file (%1 skipped, %2 errors).", + String.valueOf(entriesChanged), String.valueOf(skipped), String.valueOf(errors))); + } + + class OptionsDialog extends FXDialog { + + private final Button okButton = new Button(Localization.lang("OK")); + private final Button cancelButton = new Button(Localization.lang("Cancel")); + + private boolean isCancelled; + + private final TextArea progressArea; + + public OptionsDialog() { + super(AlertType.NONE, Localization.lang("Writing XMP-metadata for selected entries..."), false); + okButton.setDisable(true); + okButton.setOnAction(e -> dispose()); + okButton.setPrefSize(100, 30); + cancelButton.setOnAction(e -> isCancelled = true); + cancelButton.setOnKeyPressed(e -> { + if (e.getCode() == KeyCode.ESCAPE) { + isCancelled = true; + } + }); + cancelButton.setPrefSize(100, 30); + progressArea = new TextArea(); + ScrollPane scrollPane = new ScrollPane(progressArea); + progressArea.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY))); + progressArea.setEditable(false); + progressArea.setText(""); + + GridPane tmpPanel = new GridPane(); + getDialogPane().setContent(tmpPanel); + tmpPanel.setHgap(450); + tmpPanel.setVgap(10); + tmpPanel.add(scrollPane, 0, 0, 2, 1); + tmpPanel.add(okButton, 0, 1); + tmpPanel.add(cancelButton, 1, 1); + tmpPanel.setGridLinesVisible(false); + this.setResizable(false); + + } + + private void dispose() { + ((Stage) (getDialogPane().getScene().getWindow())).close(); + } + + public void done() { + okButton.setDisable(false); + cancelButton.setDisable(true); + } + + public void open() { + progressArea.setText(""); + isCancelled = false; + + okButton.setDisable(true); + cancelButton.setDisable(false); + + okButton.requestFocus(); + + optionsDialog.show(); + } + + public boolean isCanceled() { + return isCancelled; + } + + public TextArea getProgressArea() { + return progressArea; + } + } +} diff --git a/src/main/java/org/jabref/gui/autocompleter/AutoCompletionStrategy.java b/src/main/java/org/jabref/gui/autocompleter/AutoCompletionStrategy.java index 50e835a3a9f..efde39c6561 100644 --- a/src/main/java/org/jabref/gui/autocompleter/AutoCompletionStrategy.java +++ b/src/main/java/org/jabref/gui/autocompleter/AutoCompletionStrategy.java @@ -1,6 +1,5 @@ package org.jabref.gui.autocompleter; public interface AutoCompletionStrategy { - - public AutoCompletionInput analyze(String input); + AutoCompletionInput analyze(String input); } diff --git a/src/main/java/org/jabref/gui/auximport/AuxParserResultViewModel.java b/src/main/java/org/jabref/gui/auximport/AuxParserResultViewModel.java index 6a21d96b83e..e30753c8542 100644 --- a/src/main/java/org/jabref/gui/auximport/AuxParserResultViewModel.java +++ b/src/main/java/org/jabref/gui/auximport/AuxParserResultViewModel.java @@ -18,12 +18,11 @@ public String getInformation(boolean includeMissingEntries) { StringBuilder result = new StringBuilder(); result.append(Localization.lang("keys in library")).append(' ').append(this.auxParserResult.getMasterDatabase().getEntryCount()).append('\n') - .append(Localization.lang("found in AUX file")).append(' ').append(this.auxParserResult.getFoundKeysInAux()).append('\n') - .append(Localization.lang("resolved")).append(' ').append(this.auxParserResult.getResolvedKeysCount()).append('\n') - .append(Localization.lang("not found")).append(' ').append(this.auxParserResult.getUnresolvedKeysCount()).append('\n') - .append(Localization.lang("crossreferenced entries included")).append(' ') - .append(this.auxParserResult.getCrossRefEntriesCount()).append(Localization.lang("strings included")).append(' ') - .append(this.auxParserResult.getInsertedStrings()).append('\n'); + .append(Localization.lang("found in AUX file")).append(' ').append(this.auxParserResult.getFoundKeysInAux()).append('\n') + .append(Localization.lang("resolved")).append(' ').append(this.auxParserResult.getResolvedKeysCount()).append('\n') + .append(Localization.lang("not found")).append(' ').append(this.auxParserResult.getUnresolvedKeysCount()).append('\n') + .append(Localization.lang("crossreferenced entries included")).append(' ').append(this.auxParserResult.getCrossRefEntriesCount()).append('\n') + .append(Localization.lang("strings included")).append(' ').append(this.auxParserResult.getInsertedStrings()).append('\n'); if (includeMissingEntries && (this.auxParserResult.getUnresolvedKeysCount() > 0)) { for (String entry : this.auxParserResult.getUnresolvedKeys()) { diff --git a/src/main/java/org/jabref/gui/auximport/FromAuxDialog.fxml b/src/main/java/org/jabref/gui/auximport/FromAuxDialog.fxml new file mode 100644 index 00000000000..7b509209f36 --- /dev/null +++ b/src/main/java/org/jabref/gui/auximport/FromAuxDialog.fxml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + +