diff --git a/.gitignore b/.gitignore index d901b0dbeff..eef074f6e45 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,6 @@ # no generated files in version control src/main/gen/ -# do not distribute Oracle's JDBC driver -lib/ojdbc.jar - # private data /buildres/jabref-cert-2016.p12 @@ -13,13 +10,6 @@ status.md # Install4J install4j6/ -# Ant -buildant/ -build.number -classes/ -user.properties -src/main/resources/windows/nsis/dist/ - # Gradle # generated when `gradlew --gui` is called ui/ @@ -29,18 +19,12 @@ ui/ *.ipr *.iml -# Eclipse -.classpath -.project - -# markdown-toc -node_modules/ -npm-debug.log - # UNKNWON jabref.xml *.sonargraph +# Snapcraft - JabRef places the files into buildres/snapcraft +snap/ @@ -51,66 +35,7 @@ jabref.xml -# Created by https://www.gitignore.io/api/gradle,java,jabref,intellij,eclipse,netbeans,windows,linux,macos - -### JabRef ### -# JabRef - https://www.jabref.org/ -*.bak -*.sav - - -### Intellij ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff: -.idea/workspace.xml -.idea/tasks.xml - -# Sensitive or high-churn files: -.idea/dataSources/ -.idea/dataSources.ids -.idea/dataSources.xml -.idea/dataSources.local.xml -.idea/sqlDataSources.xml -.idea/dynamic.xml -.idea/uiDesigner.xml - -# Gradle: -.idea/gradle.xml -.idea/libraries - -# Mongo Explorer plugin: -.idea/mongoSettings.xml - -## File-based project format: -*.iws - -## Plugin-specific files: - -# IntelliJ -/out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -### Intellij Patch ### -# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 - -# *.iml -# modules.xml -# .idea/misc.xml -# *.ipr - +# Created by https://www.gitignore.io/api/gradle,java,jabref,intellij,eclipse,netbeans,windows,linux,macos,node,snapcraft ### Eclipse ### @@ -118,6 +43,7 @@ fabric.properties bin/ tmp/ *.tmp +*.bak *.swp *~.nib local.properties @@ -125,9 +51,6 @@ local.properties .loadpath .recommenders -# Eclipse Core -.project - # External tool builders .externalToolBuilders/ @@ -140,9 +63,6 @@ local.properties # CDT-specific (C/C++ Development Tooling) .cproject -# JDT-specific (Eclipse Java Development Tools) -.classpath - # Java annotation processor (APT) .factorypath @@ -164,36 +84,107 @@ local.properties # Code Recommenders .recommenders/ +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet -### NetBeans ### -nbproject/private/ -build/ -nbbuild/ -dist/ -nbdist/ -.nb-gradle/ +### Eclipse Patch ### +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath -### Windows ### -# Windows image file caches -Thumbs.db -ehthumbs.db +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 -# Folder config file -Desktop.ini +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries -# Recycle Bin used on file shares -$RECYCLE.BIN/ +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml -# Windows Installer files -*.cab -*.msi -*.msm -*.msp +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries -# Windows shortcuts -*.lnk +# CMake +cmake-build-debug/ +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### JabRef ### +# JabRef - https://www.jabref.org/ +*.sav + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* ### Linux ### *~ @@ -210,7 +201,6 @@ $RECYCLE.BIN/ # .nfs files are created when an open file is removed but is still being accessed .nfs* - ### macOS ### *.DS_Store .AppleDouble @@ -218,8 +208,10 @@ $RECYCLE.BIN/ # Icon must end with two \r Icon + # Thumbnails ._* + # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd @@ -228,6 +220,7 @@ Icon .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent + # Directories potentially created on remote AFP share .AppleDB .AppleDesktop @@ -235,25 +228,102 @@ Network Trash Folder Temporary Items .apdisk +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +### Node ### +# Logs +logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* -### Java ### -*.class +# Runtime data +pids +*.pid +*.seed +*.pid.lock -# BlueJ files -*.ctxt +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov -# Mobile Tools for Java (J2ME) -.mtj.tmp/ +# Coverage directory used by tools like istanbul +coverage -# Package Files # -*.jar -*.war -*.ear +# nyc test coverage +.nyc_output -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +### Snapcraft ### +# Snapcraft +parts/ +prime/ +stage/ +*.snap + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + ### Gradle ### .gradle /build/ @@ -269,4 +339,12 @@ gradle-app.setting # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 # gradle/wrapper/gradle-wrapper.properties -scenicView.properties + +# End of https://www.gitignore.io/api/gradle,java,jabref,intellij,eclipse,netbeans,windows,linux,macos,node,snapcraft + + +# we really version .jar files - needs to be go after the www.gitignore.io-generated ones, because they ignore *.jar files +!/lib/*.jar + +# do not distribute Oracle's JDBC driver +lib/ojdbc.jar diff --git a/.mailmap b/.mailmap index ec2c436a6d8..c34c06f8331 100644 --- a/.mailmap +++ b/.mailmap @@ -121,3 +121,8 @@ Anita Armbruster Fabian Bauer <125m125@users.noreply.github.com> Jong-Ho Shinn Nadeem Mahmood +Foivos Christoulakis +Predrag Milanovic +Karsten Hiekmann +Andrew Levit + diff --git a/AUTHORS b/AUTHORS index 794782a7931..3feee8f69bf 100644 --- a/AUTHORS +++ b/AUTHORS @@ -12,7 +12,9 @@ Alexsandro Lauber Ali Ayan Ambrogio Oliva Andreas Amann +Andreas Buhr Andreas Rudert +Andrew Levit Anh Nghia Tran Anita Armbruster Antonio Ribeiro @@ -56,6 +58,7 @@ Felix Langner Felix Wilke Fernando Santagata Florian Straßer +Foivos Christoulakis Francois Charette Frank Steimle Fred @@ -89,6 +92,7 @@ Jörg Zieren Jørgen Kvalsvik Jürgen Lange Kai Mindermann +Karsten Hiekmann Koji Yokota Kolja Brix Krunoslav Zubrinic @@ -121,7 +125,6 @@ Michel Baylac Mike Smoot Moritz Ringler Morten Alver -mpele Mélanie Tremblay Nadeem Mahmood Nathan Dunn @@ -140,6 +143,7 @@ Paul Martin payload Peter Ansell Philip Johnson +Predrag Milanovic Raik Nagel Renato Massao Robert Jäschke diff --git a/CHANGELOG.md b/CHANGELOG.md index b42797ef18e..2af5edc93d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,22 +8,45 @@ Here, the categories "Changed" for added and changed functionality, We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `#NUM`. + ## [Unreleased] ### Changed -- 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) +- We add 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) ### 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 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 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 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) ### Removed +- We removed support for LatexEditor, as it is not under active development. [#3199](https://github.com/JabRef/jabref/issues/3199) @@ -74,6 +97,34 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# +## [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 @@ -905,7 +956,8 @@ Since much functionality has changed during development, a release of this versi The changelog of 2.11 and versions before is maintained as [text file](https://github.com/JabRef/jabref/blob/v2.11.1/CHANGELOG) in the [v2.11.1 tag](https://github.com/JabRef/jabref/tree/v2.11.1). -[unreleased]: https://github.com/JabRef/jabref/compare/v4.0-beta2...HEAD +[unreleased]: https://github.com/JabRef/jabref/compare/v4.0-beta3...HEAD +[4.0-beta3]: https://github.com/JabRef/jabref/compare/v4.0-beta2...v4.0-beta3 [4.0-beta2]: https://github.com/JabRef/jabref/compare/v4.0-beta...v4.0-beta2 [4.0-beta]: https://github.com/JabRef/jabref/compare/v3.8.2...v4.0-beta [3.8.2]: https://github.com/JabRef/jabref/compare/v3.8.1...v3.8.2 diff --git a/build.gradle b/build.gradle index 2026fd3ed75..049ef4feadd 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,7 @@ apply from: 'xjc.gradle' group = "org.jabref" version = "4.0-dev" -project.ext.threeDotVersion = "4.0.1.1" +project.ext.threeDotVersion = "4.0.2.1" project.ext.install4jDir = hasProperty("install4jDir") ? getProperty("install4jDir") : (OperatingSystem.current().isWindows() ? 'C:/Program Files/install4j6' : 'install4j6') sourceCompatibility = 1.8 targetCompatibility = 1.8 @@ -72,7 +72,7 @@ dependencies { compile 'org.apache.pdfbox:jempbox:1.8.13' // required for reading write-protected PDFs - see https://github.com/JabRef/jabref/pull/942#issuecomment-209252635 - compile 'org.bouncycastle:bcprov-jdk15on:1.57' + compile 'org.bouncycastle:bcprov-jdk15on:1.58' compile 'commons-cli:commons-cli:1.4' @@ -82,6 +82,7 @@ dependencies { compile 'org.openoffice:unoil:4.1.2' compile 'com.github.bkromhout:java-diff-utils:2.1.1' + compile 'info.debatty:java-string-similarity:1.0.0' antlr3 'org.antlr:antlr:3.5.2' compile 'org.antlr:antlr-runtime:3.5.2' @@ -90,35 +91,31 @@ dependencies { compile 'org.antlr:antlr4-runtime:4.7' // 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.42' + compile 'mysql:mysql-connector-java:5.1.43' compile 'com.impossibl.pgjdbc-ng:pgjdbc-ng:0.7.1' compile 'net.java.dev.glazedlists:glazedlists_java15:1.9.1' - compile 'com.google.guava:guava:22.0' + compile 'com.google.guava:guava:23.0' // JavaFX stuff compile 'com.airhacks:afterburner.fx:1.7.0' compile 'de.codecentric.centerdevice:javafxsvg:1.2.1' + compile 'de.jensd:fontawesomefx-materialdesignfont:1.7.22-4' + compile 'de.saxsys:mvvmfx-validation:1.6.0' compile 'org.controlsfx:controlsfx:8.40.13' compile 'org.fxmisc.easybind:easybind:1.0.3' - compile 'org.fxmisc.flowless:flowless:0.5.2' - compile 'de.jensd:fontawesomefx-materialdesignfont:1.7.22-4' compile 'org.fxmisc.richtext:richtextfx:0.7-M5' - compile 'commons-logging:commons-logging:1.2' - compile 'org.jsoup:jsoup:1.10.3' compile 'com.mashape.unirest:unirest-java:1.4.9' - compile 'info.debatty:java-string-similarity:0.24' - compile 'org.apache.logging.log4j:log4j-jcl:2.8.2' - compile 'org.apache.logging.log4j:log4j-api:2.8.2' - compile 'org.apache.logging.log4j:log4j-core:2.8.2' - compile 'org.xmlunit:xmlunit-core:2.4.0' - compile 'org.xmlunit:xmlunit-matchers:2.4.0' + compile 'commons-logging:commons-logging:1.2' + compile 'org.apache.logging.log4j:log4j-jcl:2.9.0' + compile 'org.apache.logging.log4j:log4j-api:2.9.0' + compile 'org.apache.logging.log4j:log4j-core:2.9.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' @@ -131,11 +128,12 @@ dependencies { compile group: 'com.microsoft.azure', name: 'applicationinsights-logging-log4j2', version: '1.0.+' testCompile 'junit:junit:4.12' - testCompile 'org.mockito:mockito-core:2.8.47' - testCompile 'com.github.tomakehurst:wiremock:2.7.0' - testCompile 'org.assertj:assertj-swing-junit:3.7.0' + testCompile 'org.mockito:mockito-core:2.10.0' + testCompile 'com.github.tomakehurst:wiremock:2.8.0' + testCompile 'org.assertj:assertj-swing-junit:3.8.0' testCompile 'org.reflections:reflections:0.9.11' - + testCompile 'org.xmlunit:xmlunit-core:2.5.0' + testCompile 'org.xmlunit:xmlunit-matchers:2.5.0' testCompile 'com.tngtech.archunit:archunit-junit:0.4.0' testCompile 'org.slf4j:slf4j-jcl:1.7.25' // required by ArchUnit to enable logging over jcl } @@ -153,6 +151,8 @@ sourceSets { } processResources { + filteringCharset = 'UTF-8' + filesMatching("build.properties") { expand(version: project.version, "year": String.valueOf(Calendar.getInstance().get(Calendar.YEAR)), @@ -161,7 +161,6 @@ processResources { "azureInstrumentationKey": System.getenv('AzureInstrumentationKey')) filteringCharset = 'UTF-8' } - filteringCharset = 'UTF-8' filesMatching("resource/**/meta.xml") { expand version: project.version @@ -368,19 +367,23 @@ task release(dependsOn: ["media", "releaseJar"]) { task releaseJar(dependsOn: "shadowJar") { group = 'JabRef - Release' description "Creates a Jar release." - doLast { copy { from("$buildDir/libs/JabRef-${project.version}-fat.jar") into("$buildDir/releases") - rename { String fileName -> fileName.replace('-fat', '') } } + // set executable with read permissions (first true) and for all (false) + file("$buildDir/releases/JabRef-${project.version}.jar").setExecutable(true, false); } } +task snapJar(dependsOn: "releaseJar", type: Delete) { + delete fileTree(dir: "$buildDir/releases/", exclude: "JabRef-${project.version}.jar") +} + jmh { warmupIterations = 5 iterations = 10 diff --git a/buildres/snapcraft/JabRef-icon-256.png b/buildres/snapcraft/JabRef-icon-256.png new file mode 100644 index 00000000000..518f6e270b7 Binary files /dev/null and b/buildres/snapcraft/JabRef-icon-256.png differ diff --git a/buildres/snapcraft/jabref.desktop b/buildres/snapcraft/jabref.desktop new file mode 100644 index 00000000000..ef63a896d2d --- /dev/null +++ b/buildres/snapcraft/jabref.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Name=JabRef +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 +Exec=jabref %U +Keywords=bibtex;biblatex;latex;bibliography +Categories=Office; +StartupWMClass=org-jabref-JabRefMain +MimeType=text/x-bibtex; diff --git a/config/README.md b/config/README.md index 7afbce1b539..057f389a28c 100644 --- a/config/README.md +++ b/config/README.md @@ -2,12 +2,13 @@ 1. Press Ctrl + Alt + S 2. Go to "Editor > Code Style" -3. Click "Manage..." (right of "Scheme:") -4. Click "Import..." -5. Choose `IntelliJ Code Style.xml` -6. Press "OK" +3. Click the gear (right of "Scheme: ...") +4. Click "Import Scheme >" +5. Choose `IntelliJ IDEA code style XML` +6. Select the file `config\IntelliJ Code Style.xml` 7. Press "OK" -8. Press "Close" -9. Press "OK" +8. Press "OK" +9. Press "Close" +10. Press "OK" * Please let `.editorconfig` override the settings of IntelliJ diff --git a/external-libraries.txt b/external-libraries.txt index 02e257a40bc..a9a610bbf7e 100644 --- a/external-libraries.txt +++ b/external-libraries.txt @@ -28,41 +28,28 @@ Note: It is important to include v1.5.54 or later as v1.5.54 is the first ver # Libraries +(Sorted alphabetically by Id) + +Id: com.airhacks:afterburner.fx +Project: afterburner.fx +URL: https://github.com/AdamBien/afterburner.fx +License: Apache 2.0 + Id: com.apple:AppleJavaExtensions Project: AppleJavaExtensions URL: https://developer.apple.com/legacy/library/samplecode/AppleJavaExtensions/Introduction/Intro.html License: Apple License -Id: org.antlr:antlr -Project: ANTLR 3 -URL: http://www.antlr.org/ -License: BSD - -Id: org.antlr:antlr4 -Project: ANTLR 4 -URL: http://www.antlr.org/ -License: BSD - -Id: commons-cli:commons-cli -Project: Apache Commons CLI -URL: http://commons.apache.org/cli/ -License: Apache-2.0 - -Id: commons-logging:commons-logging -Project: Apache Commons Logging -URL: http://commons.apache.org/logging/ -License: Apache-2.0 - -Id: org.apache.commons:commons-lang3 -Project: Apache Commons Lang -URL: https://commons.apache.org/proper/commons-lang/ -License: Apache-2.0 - 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 +License: Apache 2.0 + Id: com.impossibl.pgjdbc-ng:pgjdbc-ng Project: pgjdbc-ng URL: http://impossibl.github.io/pgjdbc-ng @@ -88,6 +75,46 @@ Project: Unirest for Java URL: https://github.com/Mashape/unirest-java License: MIT +Id: com.microsoft.azure:applicationinsights-core +Project: Application Insights SDK for Java +URL: https://github.com/Microsoft/ApplicationInsights-Java +License: MIT + +Id: com.microsoft.azure:applicationinsights-logging-log4j2 +Project: Application Insights SDK for Java +URL: https://github.com/Microsoft/ApplicationInsights-Java +License: MIT + +Id: commons-cli:commons-cli +Project: Apache Commons CLI +URL: http://commons.apache.org/cli/ +License: Apache-2.0 + +Id: commons-logging:commons-logging +Project: Apache Commons Logging +URL: http://commons.apache.org/logging/ +License: Apache-2.0 + +Id: de.codecentric.centerdevice +Project: javafxsvg +URL: https://github.com/codecentric/javafxsvg +License: BSD 3-Clause + +Id: de.jensd:fontawesomefx-materialdesignfont +Project: FontAwesomeFX +URL: https://bitbucket.org/Jerady/fontawesomefx +License: Apache-2.0 + +Id: de.saxsys:mvvmfx-validation +Project: mvvm(fx) +URL: https://github.com/sialcasa/mvvmFX +License: Apache-2.0 + +Id: de.undercouch.citeproc-java +Project: Citeproc-Java +URL: http://michel-kraemer.github.io/citeproc-java/ +Licence: Apache-2.0 + Id: info.debatty:java-string-similarity Project: Java String Similarity URL: https://github.com/tdebatty/java-string-similarity @@ -103,6 +130,21 @@ Project: Glazed Lists URL: http://www.glazedlists.com/ License: LGPL-2.1 (not explicitly, but no comments in the source header) and MPL-2.0 +Id: org.antlr:antlr +Project: ANTLR 3 +URL: http://www.antlr.org/ +License: BSD + +Id: org.antlr:antlr4 +Project: ANTLR 4 +URL: http://www.antlr.org/ +License: BSD + +Id: org.apache.commons:commons-lang3 +Project: Apache Commons Lang +URL: https://commons.apache.org/proper/commons-lang/ +License: Apache-2.0 + Id: org.apache.logging.log4j Project: Apache Log2j 2 URL: http://logging.apache.org/log4j/2.x/ @@ -128,12 +170,22 @@ Project: The Legion of the Bouncy Castle URL: https://www.bouncycastle.org/ License: MIT +Id: org.citationstyles.styles +Project: CSL Styles +URL: https://github.com/citation-style-language/styles +Licence: Creative Commons Attribution-ShareAlike 3.0 Unported license + +Id: org.citationstyles.locales +Project: CSL Locales +URL: https://github.com/citation-style-language/locales +Licence: CC-BY-SA-3.0 + Id: org.controlsfx:controlsfx Project: ControlsFX URL: http://fxexperience.com/controlsfx/ License: BSD-3-Clause -Id: org.fx.misc.easybin:easybind +Id: org.fx.misc.easybind:easybind Project: EasyBind URL: https://github.com/TomasMikula/EasyBind License: BSD-2-Clause @@ -148,11 +200,6 @@ Project: RichTextFX URL: https://github.com/TomasMikula/RichTextFX License: BSD-2-Clause -Id: de.jensd:fontawesomefx-materialdesignfont -Project: FontAwesomeFX -URL: https://bitbucket.org/Jerady/fontawesomefx -License: Apache-2.0 - Id: org.jsoup:jsoup Project: jsoup URL: https://github.com/jhy/jsoup/ @@ -178,6 +225,11 @@ Project: OpenOffice.org URL: http://www.openoffice.org/api/SDK License: Apache-2.0 +Id: org.swinglabs.swingx:swingx-core +Project: SwingX +URL: https://swingx.java.net/ +License: LGPL-3.0 + Id: org.xmlunit:xmlunit-core Project: XMLUnit URL: http://www.xmlunit.org/ @@ -188,45 +240,10 @@ Project: XMLUnit URL: http://www.xmlunit.org/ License: Apache 2.0 -Id: org.swinglabs.swingx:swingx-core -Project: SwingX -URL: https://swingx.java.net/ -License: LGPL-3.0 - Id: spin Path: lib/spin.jar Project: Spin URL: http://spin.sourceforge.net/ License: LGPL-2.1+ -Id: org.citationstyles.styles -Project: CSL Styles -URL: https://github.com/citation-style-language/styles -Licence: Creative Commons Attribution-ShareAlike 3.0 Unported license - -Id: org.citationstyles.locales -Project: CSL Locales -URL: https://github.com/citation-style-language/locales -Licence: CC-BY-SA-3.0 - -Id: de.undercouch.citeproc-java -Project: Citeproc-Java -URL: http://michel-kraemer.github.io/citeproc-java/ -Licence: Apache-2.0 - -Id: com.airhacks:afterburner.fx -Project: afterburner.fx -URL: https://github.com/AdamBien/afterburner.fx -License: Apache 2.0 - -Id: de.codecentric.centerdevice -Project: javafxsvg -URL: https://github.com/codecentric/javafxsvg -License: BSD 3-Clause - -Id: com.github.tomtung -Project: latex2unicode -URL: https://github.com/tomtung/latex2unicode -License: Apache 2.0 - 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 bdcb4a44637..31ee17f693f 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 a8a33a0a7d3..ba97f1fcffb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Aug 03 11:08:08 CEST 2017 +#Mon Aug 14 21:03:12 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-bin.zip diff --git a/jabref.install4j b/jabref.install4j index 73ba46a1bc8..957539c879d 100644 --- a/jabref.install4j +++ b/jabref.install4j @@ -1409,7 +1409,7 @@ return console.askYesNo(message, true); - + @@ -1422,7 +1422,7 @@ return console.askYesNo(message, true); - + @@ -1435,7 +1435,7 @@ return console.askYesNo(message, true); - + diff --git a/lib/customjfx-1.0.0.jar b/lib/customjfx-1.0.0.jar new file mode 100644 index 00000000000..4636cdd8975 Binary files /dev/null and b/lib/customjfx-1.0.0.jar differ diff --git a/scripts/prepare-install4j.sh b/scripts/prepare-install4j.sh index fe40b23683d..36fd3ec3411 100755 --- a/scripts/prepare-install4j.sh +++ b/scripts/prepare-install4j.sh @@ -21,6 +21,6 @@ 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_141.tar.gz -wget --quiet -nc https://files.jabref.org/jres/windows-amd64-1.8.0_141.tar.gz -wget --quiet -nc https://files.jabref.org/jres/macosx-amd64-1.8.0_141_unpacked.tar.gz +wget --quiet -nc https://files.jabref.org/jres/windows-x86-1.8.0_144.tar.gz +wget --quiet -nc https://files.jabref.org/jres/windows-amd64-1.8.0_144.tar.gz +wget --quiet -nc https://files.jabref.org/jres/macosx-amd64-1.8.0_144_unpacked.tar.gz diff --git a/scripts/syncLang.py b/scripts/syncLang.py index d15081de5f9..6482662fb7e 100644 --- a/scripts/syncLang.py +++ b/scripts/syncLang.py @@ -11,6 +11,7 @@ RES_DIR = "src/main/resources/l10n" STATUS_FILE = "status.md" +URL_BASE = "https://github.com/JabRef/jabref/tree/master/src/main/resources/l10n/" def get_current_branch(): @@ -413,12 +414,13 @@ def write_properties(property_files): num_keys_translated = num_keys - num_keys_missing_value percent_translated = int((num_keys_translated / float(num_keys)) * 100) if num_keys != 0 else 0 - markdown.append("| {file} | {num_keys} | {num_keys_translated} | {num_keys_missing} | {percent_translated} |\n" - .format(file=get_filename(filepath=file), num_keys=num_keys, num_keys_translated=num_keys_translated, num_keys_missing=num_keys_missing_value, percent_translated=percent_translated)) + markdown.append("| [{file}]({url_base}{file}) | {num_keys} | {num_keys_translated} | {num_keys_missing} | {percent_translated} |\n" + .format(url_base=URL_BASE, file=os.path.basename(file), num_keys=num_keys, num_keys_translated=num_keys_translated, num_keys_missing=num_keys_missing_value, percent_translated=percent_translated)) markdown = [] date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M") - markdown.append("### Localization files status ({date} - Branch `{branch}` `{hash}`)\n".format(date=date, branch=get_current_branch(), hash=get_current_hash_short())) + markdown.append("### Localization files status ({date} - Branch `{branch}` `{hash}`)\n\n".format(date=date, branch=get_current_branch(), hash=get_current_hash_short())) + markdown.append("Note: To get the current status from your local repository, run `python ./scripts/syncLang.py markdown`\n") write_properties(property_files=get_all_jabref_properties()) write_properties(property_files=get_all_menu_properties()) diff --git a/snapcraft.yaml b/snapcraft.yaml new file mode 100644 index 00000000000..7efc6989c07 --- /dev/null +++ b/snapcraft.yaml @@ -0,0 +1,35 @@ +# based on https://github.com/snapcore/snapcraft/blob/master/demos/gradle/snap/snapcraft.yaml + +name: jabref + +# the version string 4.0-dev is replaced by scripts/run-snapcraft.sh with the current version provided in build.gradle +version: '4.0-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.0-dev.jar + plugs: [home, network-bind, x11] + 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/main/java/org/jabref/FallbackExceptionHandler.java b/src/main/java/org/jabref/FallbackExceptionHandler.java index 9bc9c9fb136..1521b67cf60 100644 --- a/src/main/java/org/jabref/FallbackExceptionHandler.java +++ b/src/main/java/org/jabref/FallbackExceptionHandler.java @@ -1,16 +1,14 @@ package org.jabref; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.MarkerManager; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** * Catch and log any unhandled exceptions. */ public class FallbackExceptionHandler implements Thread.UncaughtExceptionHandler { - private static final Marker UncaughtException_MARKER = MarkerManager.getMarker("UncaughtException"); + private static final Log LOGGER = LogFactory.getLog(FallbackExceptionHandler.class); public static void installExceptionHandler() { Thread.setDefaultUncaughtExceptionHandler(new FallbackExceptionHandler()); @@ -18,7 +16,6 @@ public static void installExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable exception) { - Logger logger = LogManager.getLogger(FallbackExceptionHandler.class); - logger.error(UncaughtException_MARKER, "Uncaught exception Occurred in " + thread, exception); + LOGGER.error("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 5e719a57184..27fa65c92a6 100644 --- a/src/main/java/org/jabref/Globals.java +++ b/src/main/java/org/jabref/Globals.java @@ -1,6 +1,7 @@ package org.jabref; import java.awt.Toolkit; +import java.util.Optional; import java.util.UUID; import org.jabref.collab.FileUpdateMonitor; @@ -73,12 +74,16 @@ public static void startBackgroundTasks() { Globals.fileUpdateMonitor = new FileUpdateMonitor(); JabRefExecutorService.INSTANCE.executeInterruptableTask(Globals.fileUpdateMonitor, "FileUpdateMonitor"); - startTelemetryClient(); + if (Globals.prefs.shouldCollectTelemetry()) { + startTelemetryClient(); + } } private static void stopTelemetryClient() { - telemetryClient.trackSessionState(SessionState.End); - telemetryClient.flush(); + if (Globals.prefs.shouldCollectTelemetry()) { + getTelemetryClient().ifPresent(client -> client.trackSessionState(SessionState.End)); + getTelemetryClient().ifPresent(client -> client.flush()); + } } private static void startTelemetryClient() { @@ -115,7 +120,7 @@ public static void stopBackgroundTasks() { stopTelemetryClient(); } - public static TelemetryClient getTelemetryClient() { - return telemetryClient; + public static Optional getTelemetryClient() { + return Optional.ofNullable(telemetryClient); } } diff --git a/src/main/java/org/jabref/JabRefMain.java b/src/main/java/org/jabref/JabRefMain.java index 0f98ba7e7d4..c30d1c30e30 100644 --- a/src/main/java/org/jabref/JabRefMain.java +++ b/src/main/java/org/jabref/JabRefMain.java @@ -77,12 +77,13 @@ private static void start(String[] args) { PreferencesMigrations.upgradeLabelPatternToBibtexKeyPattern(); PreferencesMigrations.upgradeStoredCustomEntryTypes(); PreferencesMigrations.upgradeKeyBindingsToJavaFX(); + PreferencesMigrations.addCrossRefRelatedFieldsForAutoComplete(); // Update handling of special fields based on preferences InternalBibtexFields .updateSpecialFields(Globals.prefs.getBoolean(JabRefPreferences.SERIALIZESPECIALFIELDS)); // Update name of the time stamp field based on preferences - InternalBibtexFields.updateTimeStampField(Globals.prefs.get(JabRefPreferences.TIME_STAMP_FIELD)); + InternalBibtexFields.updateTimeStampField(Globals.prefs.getTimestampPreferences().getTimestampField()); // Update which fields should be treated as numeric, based on preferences: InternalBibtexFields.setNumericFields(Globals.prefs.getStringList(JabRefPreferences.NUMERIC_FIELDS)); diff --git a/src/main/java/org/jabref/gui/AbstractView.java b/src/main/java/org/jabref/gui/AbstractView.java index 79df1bbcf66..33b8a1cc4ca 100644 --- a/src/main/java/org/jabref/gui/AbstractView.java +++ b/src/main/java/org/jabref/gui/AbstractView.java @@ -1,6 +1,7 @@ package org.jabref.gui; import java.util.Optional; +import java.util.function.Function; import javafx.scene.Parent; import javafx.stage.Stage; @@ -10,8 +11,13 @@ import com.airhacks.afterburner.views.FXMLView; public class AbstractView extends FXMLView { + public AbstractView() { - super(); + this(f -> null); + } + + public AbstractView(Function injectionContext) { + super(injectionContext); // Set resource bundle to internal localizations bundle = Localization.getMessages(); @@ -26,9 +32,12 @@ public Parent getView() { // Notify controller about the stage, where it is displayed view.sceneProperty().addListener((observable, oldValue, newValue) -> { - Stage stage = (Stage) newValue.getWindow(); - if (stage != null) { - getController().ifPresent(controller -> controller.setStage(stage)); + + if (newValue.getWindow() instanceof Stage) { + Stage stage = (Stage) newValue.getWindow(); + if (stage != null) { + getController().ifPresent(controller -> controller.setStage(stage)); + } } }); return view; @@ -36,6 +45,6 @@ public Parent getView() { private Optional getController() { return Optional.ofNullable(presenterProperty.get()).map( - presenter -> (AbstractController)presenter); + presenter -> (AbstractController) presenter); } } diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/BasePanel.java index 493c6abec6b..8d31ddfb8d6 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/BasePanel.java @@ -127,6 +127,7 @@ import org.jabref.model.database.DatabaseLocation; import org.jabref.model.database.KeyCollisionException; import org.jabref.model.database.event.BibDatabaseContextChangedEvent; +import org.jabref.model.database.event.CoarseChangeFilter; import org.jabref.model.database.event.EntryAddedEvent; import org.jabref.model.database.event.EntryRemovedEvent; import org.jabref.model.entry.BibEntry; @@ -220,6 +221,7 @@ public BasePanel(JabRefFrame frame, BibDatabaseContext bibDatabaseContext) { setupActions(); this.getDatabase().registerListener(new SearchListener()); + this.getDatabase().registerListener(new EntryRemovedListener()); // ensure that at each addition of a new entry, the entry is added to the groups interface this.bibDatabaseContext.getDatabase().registerListener(new GroupTreeListener()); @@ -1396,7 +1398,8 @@ private void setupAutoCompletion() { suggestionProviders = new SuggestionProviders(autoCompletePreferences, Globals.journalAbbreviationLoader); suggestionProviders.indexDatabase(getDatabase()); // Ensure that the suggestion providers are in sync with entries - this.getDatabase().registerListener(new AutoCompleteUpdater(suggestionProviders)); + CoarseChangeFilter changeFilter = new CoarseChangeFilter(bibDatabaseContext); + changeFilter.registerListener(new AutoCompleteUpdater(suggestionProviders)); } else { // Create empty suggestion providers if auto completion is deactivated suggestionProviders = new SuggestionProviders(); @@ -2095,6 +2098,21 @@ public void listen(EntryAddedEvent addedEntryEvent) { } } + 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 (isShowingEditor() && currentEditor.getEntry().equals(entryRemovedEvent.getBibEntry())) { + currentEditor.close(); + } + + if (selectionListener.getPreview().getEntry().equals(entryRemovedEvent.getBibEntry())) { + selectionListener.setPreviewActive(false); + } + } + } + /** * 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 diff --git a/src/main/java/org/jabref/gui/DragAndDropDataFormats.java b/src/main/java/org/jabref/gui/DragAndDropDataFormats.java index 7a4328de855..9ffe3125c10 100644 --- a/src/main/java/org/jabref/gui/DragAndDropDataFormats.java +++ b/src/main/java/org/jabref/gui/DragAndDropDataFormats.java @@ -8,5 +8,6 @@ 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"); } diff --git a/src/main/java/org/jabref/gui/EntryTypeDialog.java b/src/main/java/org/jabref/gui/EntryTypeDialog.java index f9b09fea886..13b711f284d 100644 --- a/src/main/java/org/jabref/gui/EntryTypeDialog.java +++ b/src/main/java/org/jabref/gui/EntryTypeDialog.java @@ -316,10 +316,14 @@ protected void done() { diag.entryListComplete(); diag.setLocationRelativeTo(frame); diag.setVisible(true); - diag.toFront(); + diag.toFront(); } else { // Regenerate CiteKey of imported BibEntry BibtexKeyPatternUtil.makeAndSetLabel(Globals.prefs.getBibtexKeyPatternPreferences().getKeyPattern(), frame.getCurrentBasePanel().getDatabase(), bibEntry, Globals.prefs.getBibtexKeyPatternPreferences()); + // Update Timestamps + if (Globals.prefs.getTimestampPreferences().includeCreatedTimestamp()) { + bibEntry.setField(Globals.prefs.getTimestampPreferences().getTimestampField(), Globals.prefs.getTimestampPreferences().now()); + } frame.getCurrentBasePanel().insertEntry(bibEntry); } diff --git a/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java b/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java index 678fa520942..7a30dd412a9 100644 --- a/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java +++ b/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java @@ -90,6 +90,7 @@ * GUI Dialog for the feature "Find unlinked files". */ public class FindUnlinkedFilesDialog extends JabRefDialog { + /** * Keys to be used for referencing this Action. */ diff --git a/src/main/java/org/jabref/gui/IconTheme.java b/src/main/java/org/jabref/gui/IconTheme.java index 375628bf98b..09488c333f6 100644 --- a/src/main/java/org/jabref/gui/IconTheme.java +++ b/src/main/java/org/jabref/gui/IconTheme.java @@ -166,7 +166,7 @@ public static List getLogoSet() { return jabrefLogos; } - + public enum JabRefIcon { ADD(MaterialDesignIcon.PLUS_BOX), diff --git a/src/main/java/org/jabref/gui/JabRefDialog.java b/src/main/java/org/jabref/gui/JabRefDialog.java index 9bce43511d7..3a70de1a368 100644 --- a/src/main/java/org/jabref/gui/JabRefDialog.java +++ b/src/main/java/org/jabref/gui/JabRefDialog.java @@ -54,6 +54,6 @@ public JabRefDialog(Window owner, String title, Class void trackDialogOpening(Class clazz) { - Globals.getTelemetryClient().trackPageView(clazz.getName()); + Globals.getTelemetryClient().ifPresent(client -> client.trackPageView(clazz.getName())); } } diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 1b25660beb2..92095b78852 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -1584,7 +1584,7 @@ private void trackOpenNewDatabase(BasePanel basePanel) { Map measurements = new HashMap<>(); measurements.put("NumberOfEntries", (double)basePanel.getDatabaseContext().getDatabase().getEntryCount()); - Globals.getTelemetryClient().trackEvent("OpenNewDatabase", properties, measurements); + Globals.getTelemetryClient().ifPresent(client -> client.trackEvent("OpenNewDatabase", properties, measurements)); } public BasePanel addTab(BibDatabaseContext databaseContext, boolean raisePanel) { diff --git a/src/main/java/org/jabref/gui/ReplaceStringDialog.java b/src/main/java/org/jabref/gui/ReplaceStringDialog.java index 8198f93d983..6d5175bd9be 100644 --- a/src/main/java/org/jabref/gui/ReplaceStringDialog.java +++ b/src/main/java/org/jabref/gui/ReplaceStringDialog.java @@ -186,19 +186,13 @@ public boolean selOnly() { public int replace(BibEntry be, NamedCompound ce) { int counter = 0; if (allFields()) { - for (String s : be.getFieldNames()) { - if (!s.equals(BibEntry.KEY_FIELD)) { - counter += replaceField(be, s, ce); - } + counter += replaceField(be, s, ce); } } else { for (String fld : fieldStrings) { - if (!fld.equals(BibEntry.KEY_FIELD)) { counter += replaceField(be, fld, ce); - } } - } return counter; } diff --git a/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java b/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java index 4f0d0edbb91..d458fc3b692 100644 --- a/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java +++ b/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java @@ -1,5 +1,6 @@ package org.jabref.gui.actions; +import java.awt.Component; import java.awt.event.ActionEvent; import java.util.HashMap; import java.util.List; @@ -123,11 +124,42 @@ public boolean include(Entry entry) { }); 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()); diff --git a/src/main/java/org/jabref/gui/actions/ManageKeywordsAction.java b/src/main/java/org/jabref/gui/actions/ManageKeywordsAction.java index a55f13f56d2..1e3304323c1 100644 --- a/src/main/java/org/jabref/gui/actions/ManageKeywordsAction.java +++ b/src/main/java/org/jabref/gui/actions/ManageKeywordsAction.java @@ -3,11 +3,13 @@ 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.awt.event.KeyListener; 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; @@ -80,6 +82,10 @@ private void createDialog() { 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); @@ -93,16 +99,18 @@ private void createDialog() { mergeKeywords.addActionListener(stateChanged); intersectKeywords.setSelected(true); - FormBuilder builder = FormBuilder.create().layout(new FormLayout("fill:200dlu:grow, 4dlu, fill:pref", + 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(remove).xy(3, 9); + builder.add(join).xy(2,9); + builder.add(replace).xy(3, 9); builder.add(keyword).xy(1, 11); - builder.add(add).xy(3, 11); + builder.add(add).xy(2, 11); + builder.add(remove).xy(3, 11); ButtonBarBuilder bb = new ButtonBarBuilder(); bb.addGlue(); @@ -130,7 +138,7 @@ public void actionPerformed(ActionEvent e) { add.addActionListener(addActionListener); - final ActionListener removeActionListenter = arg0 -> { + final ActionListener removeActionListener = arg0 -> { // keywordList.getSelectedIndices(); does not work, therefore we operate on the values List values = keywordList.getSelectedValuesList(); @@ -139,24 +147,49 @@ public void actionPerformed(ActionEvent e) { } }; - remove.addActionListener(removeActionListenter); + remove.addActionListener(removeActionListener); - keywordList.addKeyListener(new KeyListener() { + final ActionListener joinActionListener = arg0 -> { + List values = keywordList.getSelectedValuesList(); + String joinedKeyword = values.stream().map(currentKeyword -> currentKeyword.get()).collect(Collectors.joining(" ")); + this.addKeywordToKeywordListModel(joinedKeyword); - @Override - public void keyTyped(KeyEvent arg0) { - // Do nothing + for (Keyword val : values) { + this.keywordListModel.removeElement(val); } + }; - @Override - public void keyReleased(KeyEvent arg0) { - // Do nothing + 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) { - removeActionListenter.actionPerformed(null); + 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); } } }); @@ -175,8 +208,18 @@ private void addButtonActionListener(JTextField keywordTextField) { if (StringUtil.isBlank(keywordTextField.getText())) { return; // nothing to add } + addKeywordToKeywordListModel(keywordTextField.getText()); + keywordTextField.setText(null); + keywordTextField.requestFocusInWindow(); + + } - Keyword newKeyword = new Keyword(keywordTextField.getText().trim()); + /** + * 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 { @@ -194,9 +237,6 @@ private void addButtonActionListener(JTextField keywordTextField) { keywordListModel.add(idx, newKeyword); } } - keywordTextField.setText(null); - keywordTextField.requestFocusInWindow(); - } @Override @@ -257,7 +297,7 @@ public void actionPerformed(ActionEvent e) { } private NamedCompound updateKeywords(List entries, KeywordList keywordsToAdd, - KeywordList keywordsToRemove) { + KeywordList keywordsToRemove) { NamedCompound ce = new NamedCompound(Localization.lang("Update keywords")); for (BibEntry entry : entries) { KeywordList keywords = entry.getKeywords(Globals.prefs.getKeywordDelimiter()); diff --git a/src/main/java/org/jabref/gui/actions/NewEntryAction.java b/src/main/java/org/jabref/gui/actions/NewEntryAction.java index 63e009fa665..b404dfea3af 100644 --- a/src/main/java/org/jabref/gui/actions/NewEntryAction.java +++ b/src/main/java/org/jabref/gui/actions/NewEntryAction.java @@ -1,10 +1,13 @@ 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 org.jabref.Globals; import org.jabref.gui.EntryTypeDialog; import org.jabref.gui.IconTheme; import org.jabref.gui.JabRefFrame; @@ -58,6 +61,8 @@ public void actionPerformed(ActionEvent e) { return; } thisType = tp.getName(); + + trackNewEntry(tp); } if (jabRefFrame.getBasePanelCount() > 0) { @@ -68,4 +73,12 @@ public void actionPerformed(ActionEvent e) { LOGGER.info("Action 'New entry' must be disabled when no database is open."); } } + + 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)); + } } diff --git a/src/main/java/org/jabref/gui/autocompleter/AutoCompletePreferences.java b/src/main/java/org/jabref/gui/autocompleter/AutoCompletePreferences.java index 48226d3ec33..1a54be767f1 100644 --- a/src/main/java/org/jabref/gui/autocompleter/AutoCompletePreferences.java +++ b/src/main/java/org/jabref/gui/autocompleter/AutoCompletePreferences.java @@ -13,15 +13,15 @@ public class AutoCompletePreferences { private AutoCompleteFirstNameMode firstNameMode; private boolean onlyCompleteLastFirst; private boolean onlyCompleteFirstLast; - private List completeNames; - private JournalAbbreviationPreferences journalAbbreviationPreferences; + private List completeFields; + private final JournalAbbreviationPreferences journalAbbreviationPreferences; - public AutoCompletePreferences(boolean shouldAutoComplete, AutoCompleteFirstNameMode firstNameMode, boolean onlyCompleteLastFirst, boolean onlyCompleteFirstLast, List completeNames, JournalAbbreviationPreferences journalAbbreviationPreferences) { + public AutoCompletePreferences(boolean shouldAutoComplete, AutoCompleteFirstNameMode firstNameMode, boolean onlyCompleteLastFirst, boolean onlyCompleteFirstLast, List completeFields, JournalAbbreviationPreferences journalAbbreviationPreferences) { this.shouldAutoComplete = shouldAutoComplete; this.firstNameMode = firstNameMode; this.onlyCompleteLastFirst = onlyCompleteLastFirst; this.onlyCompleteFirstLast = onlyCompleteFirstLast; - this.completeNames = completeNames; + this.completeFields = completeFields; this.journalAbbreviationPreferences = journalAbbreviationPreferences; } @@ -60,20 +60,24 @@ public void setOnlyCompleteFirstLast(boolean onlyCompleteFirstLast) { this.onlyCompleteFirstLast = onlyCompleteFirstLast; } - public List getCompleteNames() { - return completeNames; + /** + * Returns the list of fields for which autocomplete is enabled + * @return List of field names + */ + public List getCompleteFields() { + return completeFields; } - public void setCompleteNames(List completeNames) { - this.completeNames = completeNames; + public void setCompleteFields(List completeFields) { + this.completeFields = completeFields; } public void setCompleteNames(String input) { - setCompleteNames(Arrays.asList(input.split(DELIMITER))); + setCompleteFields(Arrays.asList(input.split(DELIMITER))); } public String getCompleteNamesAsString() { - return completeNames.stream().collect(Collectors.joining(DELIMITER)); + return completeFields.stream().collect(Collectors.joining(DELIMITER)); } public JournalAbbreviationPreferences getJournalAbbreviationPreferences() { diff --git a/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java b/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java index 568b697996a..db1e4546b6b 100644 --- a/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java +++ b/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java @@ -10,7 +10,7 @@ /** * Delivers possible completions as a list of {@link BibEntry} based on their cite key. */ -class BibEntrySuggestionProvider extends SuggestionProvider implements AutoCompleteSuggestionProvider { +public class BibEntrySuggestionProvider extends SuggestionProvider implements AutoCompleteSuggestionProvider { @Override public void indexEntry(BibEntry entry) { diff --git a/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java b/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java index a2ea193f5ca..a4bf835a19b 100644 --- a/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java +++ b/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java @@ -27,10 +27,10 @@ public SuggestionProviders() { } public SuggestionProviders(AutoCompletePreferences preferences, - JournalAbbreviationLoader abbreviationLoader) { + JournalAbbreviationLoader abbreviationLoader) { Objects.requireNonNull(preferences); - List completeFields = preferences.getCompleteNames(); + List completeFields = preferences.getCompleteFields(); for (String field : completeFields) { AutoCompleteSuggestionProvider autoCompleter = initalizeSuggestionProvider(field, preferences, abbreviationLoader); providers.put(field, autoCompleter); diff --git a/src/main/java/org/jabref/gui/customjfx/CustomJFXPanel.java b/src/main/java/org/jabref/gui/customjfx/CustomJFXPanel.java new file mode 100644 index 00000000000..c58308a0f79 --- /dev/null +++ b/src/main/java/org/jabref/gui/customjfx/CustomJFXPanel.java @@ -0,0 +1,65 @@ +package org.jabref.gui.customjfx; + +import java.awt.event.InputMethodEvent; +import java.lang.reflect.Field; + +import javafx.embed.swing.JFXPanel; + +import org.jabref.gui.customjfx.support.InputMethodSupport; + +import com.sun.javafx.embed.EmbeddedSceneInterface; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/*** + * WARNING: THIS IS A CUSTOM HACK TO PREVENT A BUG WITH ACCENTED CHARACTERS PRODUCING AN NPE IN LINUX
+ * So far the bug has only been resolved in openjfx10: https://bugs.openjdk.java.net/browse/JDK-8185792 + * + */ +public class CustomJFXPanel extends JFXPanel { + + private static final Log LOGGER = LogFactory.getLog(CustomJFXPanel.class); + private Field scenePeerField = null; + + public CustomJFXPanel() { + super(); + try { + scenePeerField = this.getClass().getSuperclass().getDeclaredField("scenePeer"); + scenePeerField.setAccessible(true); + } catch (NoSuchFieldException | SecurityException e) { + LOGGER.error("Could not access scenePeer Field", e); + + } + } + + @Override + protected void processInputMethodEvent(InputMethodEvent e) { + if (e.getID() == InputMethodEvent.INPUT_METHOD_TEXT_CHANGED) { + sendInputMethodEventToFX(e); + } + + } + + private void sendInputMethodEventToFX(InputMethodEvent e) { + String t = InputMethodSupport.getTextForEvent(e); + + int insertionIndex = 0; + if (e.getCaret() != null) { + insertionIndex = e.getCaret().getInsertionIndex(); + } + + EmbeddedSceneInterface myScencePeer = null; + try { + //the variable must be named different to the original, otherwise reflection does not find the right ones + myScencePeer = (EmbeddedSceneInterface) scenePeerField.get(this); + } catch (IllegalArgumentException | IllegalAccessException ex) { + LOGGER.error("Could not access scenePeer Field", ex); + } + + myScencePeer.inputMethodEvent( + javafx.scene.input.InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, + InputMethodSupport.inputMethodEventComposed(t, e.getCommittedCharacterCount()), + t.substring(0, e.getCommittedCharacterCount()), + insertionIndex); + } +} \ No newline at end of file diff --git a/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java b/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java index 9f7bee418b8..db8e820adc8 100644 --- a/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java +++ b/src/main/java/org/jabref/gui/desktop/JabRefDesktop.java @@ -5,7 +5,6 @@ import java.net.URI; 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.List; @@ -51,6 +50,7 @@ * http://stackoverflow.com/questions/18004150/desktop-api-is-not-supported-on-the-current-platform */ public class JabRefDesktop { + private static final Log LOGGER = LogFactory.getLog(JabRefDesktop.class); private static final NativeDesktop NATIVE_DESKTOP = getNativeDesktop(); @@ -139,7 +139,7 @@ public static boolean openExternalFileAnyFormat(final BibDatabaseContext databas } // For other platforms we'll try to find the file type: - Path file = Paths.get(link); + Path file = null; if (!httpLink) { Optional tmp = FileHelper.expandFilename(databaseContext, link, Globals.prefs.getFileDirectoryPreferences()); @@ -149,7 +149,7 @@ public static boolean openExternalFileAnyFormat(final BibDatabaseContext databas } // Check if we have arrived at a file type, and either an http link or an existing file: - if (httpLink || Files.exists(file) && (type.isPresent())) { + if (httpLink || ((file != null) && Files.exists(file) && (type.isPresent()))) { // Open the file: String filePath = httpLink ? link : file.toString(); openExternalFilePlatformIndependent(type, filePath); @@ -160,8 +160,7 @@ public static boolean openExternalFileAnyFormat(final BibDatabaseContext databas } } - public static boolean openExternalFileAnyFormat(Path file, final BibDatabaseContext databaseContext, - final Optional type) throws IOException { + public static boolean openExternalFileAnyFormat(Path file, final BibDatabaseContext databaseContext, final Optional type) throws IOException { return openExternalFileAnyFormat(databaseContext, file.toString(), type); } diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css index 431f4d853ed..7ff9150d3e3 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css @@ -31,3 +31,7 @@ -fx-text-fill: text-area-foreground; -fx-text-origin: baseline; } + +.list-cell:odd { + -fx-background-color: text-area-background; +} diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index d5ae811676b..b483727e305 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -11,12 +11,12 @@ import java.awt.event.KeyAdapter; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; import javax.swing.AbstractAction; import javax.swing.Action; @@ -32,7 +32,10 @@ import javax.swing.JToolBar; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; +import javax.swing.undo.UndoableEdit; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.embed.swing.JFXPanel; import javafx.scene.Scene; import javafx.scene.control.Tab; @@ -47,6 +50,8 @@ import org.jabref.gui.JabRefFrame; import org.jabref.gui.OSXCompatibleToolbar; import org.jabref.gui.actions.Actions; +import org.jabref.gui.customjfx.CustomJFXPanel; +import org.jabref.gui.entryeditor.fileannotationtab.FileAnnotationTab; import org.jabref.gui.externalfiles.WriteXMPEntryEditorAction; import org.jabref.gui.fieldeditors.FieldEditor; import org.jabref.gui.fieldeditors.TextField; @@ -68,20 +73,25 @@ import org.jabref.logic.help.HelpFile; import org.jabref.logic.importer.EntryBasedFetcher; import org.jabref.logic.importer.WebFetchers; +import org.jabref.logic.integrity.BracesCorrector; import org.jabref.logic.l10n.Localization; import org.jabref.logic.search.SearchQueryHighlightListener; +import org.jabref.logic.util.OS; import org.jabref.logic.util.UpdateField; import org.jabref.model.EntryTypes; -import org.jabref.model.FieldChange; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.EntryType; +import org.jabref.model.entry.event.EntryChangedEvent; +import org.jabref.model.entry.event.FieldAddedOrRemovedEvent; import org.jabref.preferences.JabRefPreferences; +import com.google.common.eventbus.Subscribe; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.fxmisc.easybind.EasyBind; + /** * GUI component that allows editing of the fields of a BibEntry (i.e. the * one that shows up, when you double click on an entry in the table) @@ -96,6 +106,11 @@ public class EntryEditor extends JPanel implements EntryContainer { private static final Log LOGGER = LogFactory.getLog(EntryEditor.class); + /** + * The default index number of the other fields tab + */ + private static final int OTHER_FIELDS_DEFAULTPOSITION = 4; + /** * A reference to the entry this object works on. */ @@ -136,16 +151,22 @@ public class EntryEditor extends JPanel implements EntryContainer { private final UndoAction undoAction = new UndoAction(); private final RedoAction redoAction = new RedoAction(); private final List searchListeners = new ArrayList<>(); + private final JFXPanel container; /** * Indicates that we are about to go to the next or previous entry */ - private boolean movingToDifferentEntry; + private final BooleanProperty movingToDifferentEntry = new SimpleBooleanProperty(); + private final EntryType entryType; + private SourceTab sourceTab; public EntryEditor(JabRefFrame frame, BasePanel panel, BibEntry entry, String lastTabName) { this.frame = frame; this.panel = panel; this.entry = Objects.requireNonNull(entry); + entry.registerListener(this); + entryType = EntryTypes.getTypeOrDefault(entry.getType(), + this.frame.getCurrentBasePanel().getBibDatabaseContext().getMode()); displayedBibEntryType = entry.getType(); @@ -155,7 +176,7 @@ public EntryEditor(JabRefFrame frame, BasePanel panel, BibEntry entry, String la setLayout(borderLayout); setupToolBar(); - JFXPanel container = new JFXPanel(); + container = OS.LINUX ? new CustomJFXPanel() : new JFXPanel(); container.addKeyListener(new KeyAdapter() { @@ -186,6 +207,9 @@ public void keyPressed(java.awt.event.KeyEvent e) { }); DefaultTaskExecutor.runInJavaFXThread(() -> { addTabs(lastTabName); + + tabbed.setStyle("-fx-font-size: " + Globals.prefs.getFontSizeFX() + "pt;"); + container.setScene(new Scene(tabbed)); }); add(container, BorderLayout.CENTER); @@ -200,6 +224,62 @@ public void keyPressed(java.awt.event.KeyEvent e) { setupKeyBindings(); } + @Subscribe + public synchronized void listen(FieldAddedOrRemovedEvent event) { + // other field deleted -> update other fields tab + if (OtherFieldsTab.isOtherField(entryType, event.getFieldName())) { + DefaultTaskExecutor.runInJavaFXThread(() -> rebuildOtherFieldsTab()); + } + } + + @Subscribe + public synchronized void listen(EntryChangedEvent event) { + sourceTab.updateSourcePane(); + } + + private void rebuildOtherFieldsTab() { + int index = -1; + boolean isOtherFieldsTabSelected = false; + + // find tab index and selection status + for (Tab tab : tabbed.getTabs()) { + if (tab instanceof OtherFieldsTab) { + index = tabbed.getTabs().indexOf(tab); + isOtherFieldsTabSelected = tabbed.getSelectionModel().isSelected(index); + break; + } + } + + // rebuild tab at index and with prior selection status + if (index != -1) { + readdOtherFieldsTab(index, isOtherFieldsTabSelected); + } else { + // maybe the tab wasn't there but needs to be now + addNewOtherFieldsTabIfNeeded(); + } + } + + private void readdOtherFieldsTab(int index, boolean isOtherFieldsTabSelected) { + tabbed.getTabs().remove(index); + OtherFieldsTab tab = new OtherFieldsTab(frame, panel, entryType, this, entry); + // if there are no other fields left, no need to readd the tab + if (!(tab.getFields().size() == 0)) { + tabbed.getTabs().add(index, tab); + } + // select the new tab if it was selected before + if (isOtherFieldsTabSelected) { + tabbed.getSelectionModel().select(tab); + } + } + + private void addNewOtherFieldsTabIfNeeded() { + OtherFieldsTab tab = new OtherFieldsTab(frame, panel, entryType, this, entry); + if (tab.getFields().size() > 0) { + // add it at default index, but that is just a guess + tabbed.getTabs().add(OTHER_FIELDS_DEFAULTPOSITION, tab); + } + } + private void selectLastUsedTab(String lastTabName) { tabbed.getTabs().stream().filter(tab -> lastTabName.equals(tab.getText())).findFirst().ifPresent(tab -> tabbed.getSelectionModel().select(tab)); } @@ -237,22 +317,24 @@ private void setupKeyBindings() { }); } + public void close() { + closeAction.actionPerformed(null); + } + private void addTabs(String lastTabName) { - EntryType type = EntryTypes.getTypeOrDefault(entry.getType(), - this.frame.getCurrentBasePanel().getBibDatabaseContext().getMode()); List tabs = new ArrayList<>(); // Required fields - tabs.add(new RequiredFieldsTab(frame, panel, type, this, entry)); + tabs.add(new RequiredFieldsTab(frame, panel, entryType, this, entry)); // Optional fields - tabs.add(new OptionalFieldsTab(frame, panel, type, this, entry)); - tabs.add(new OptionalFields2Tab(frame, panel, type, this, entry)); - tabs.add(new DeprecatedFieldsTab(frame, panel, type, this, entry)); + tabs.add(new OptionalFieldsTab(frame, panel, entryType, this, entry)); + tabs.add(new OptionalFields2Tab(frame, panel, entryType, this, entry)); + tabs.add(new DeprecatedFieldsTab(frame, panel, entryType, this, entry)); // Other fields - tabs.add(new OtherFieldsTab(frame, panel, type, this, entry)); + tabs.add(new OtherFieldsTab(frame, panel, entryType, this, entry)); // General fields from preferences EntryEditorTabList tabList = Globals.prefs.getEntryEditorTabList(); @@ -265,11 +347,11 @@ private void addTabs(String lastTabName) { // Special tabs tabs.add(new MathSciNetTab(entry)); - tabs.add(new FileAnnotationTab(entry, this, panel.getAnnotationCache())); + tabs.add(new FileAnnotationTab(panel.getAnnotationCache(), entry)); tabs.add(new RelatedArticlesTab(entry)); // Source tab - SourceTab sourceTab = new SourceTab(panel.getBibDatabaseContext(), entry); + sourceTab = new SourceTab(panel, entry, movingToDifferentEntry); tabs.add(sourceTab); tabbed.getTabs().clear(); @@ -285,6 +367,7 @@ private void addTabs(String lastTabName) { } else { selectLastUsedTab(lastTabName); } + } public String getDisplayedBibEntryType() { @@ -411,10 +494,7 @@ private void removeSearchListeners() { @Override public void requestFocus() { - EntryEditorTab activeTab = (EntryEditorTab) tabbed.getSelectionModel().getSelectedItem(); - if (activeTab != null) { - activeTab.requestFocus(); - } + container.requestFocus(); } /** @@ -456,12 +536,14 @@ public void setFocusToField(String fieldName) { } public void setMovingToDifferentEntry() { - movingToDifferentEntry = true; + movingToDifferentEntry.set(true); unregisterListeners(); } private void unregisterListeners() { + this.entry.unregisterListener(this); removeSearchListeners(); + } private void showChangeEntryTypePopupMenu() { @@ -479,21 +561,6 @@ private void warnEmptyBibtexkey() { + Localization.lang("Grouping may not work for this entry.")); } - private boolean updateTimeStampIsSet() { - return Globals.prefs.getBoolean(JabRefPreferences.USE_TIME_STAMP) - && Globals.prefs.getBoolean(JabRefPreferences.UPDATE_TIMESTAMP); - } - - /** - * Updates the timestamp of the given entry and returns the FieldChange - */ - private Optional doUpdateTimeStamp() { - String timeStampField = Globals.prefs.get(JabRefPreferences.TIME_STAMP_FIELD); - String timeStampFormat = Globals.prefs.get(JabRefPreferences.TIME_STAMP_FORMAT); - String timestamp = DateTimeFormatter.ofPattern(timeStampFormat).format(LocalDateTime.now()); - return UpdateField.updateField(entry, timeStampField, timestamp); - } - private class TypeButton extends JButton { private TypeButton() { @@ -568,7 +635,6 @@ public void actionPerformed(ActionEvent e) { } private class CloseAction extends AbstractAction { - private CloseAction() { super(Localization.lang("Close window"), IconTheme.JabRefIcon.CLOSE.getSmallIcon()); putValue(Action.SHORT_DESCRIPTION, Localization.lang("Close window")); @@ -576,6 +642,15 @@ private CloseAction() { @Override public void actionPerformed(ActionEvent e) { + Map cleanedEntries = entry + .getFieldMap() + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, f -> BracesCorrector.apply(f.getValue()))); + if (!cleanedEntries.equals(entry.getFieldMap())) { + frame.output(Localization.lang("Added missing braces.")); + } + entry.setField(cleanedEntries); panel.entryEditorClosing(EntryEditor.this); } } @@ -589,8 +664,8 @@ public StoreFieldAction() { @Override public void actionPerformed(ActionEvent event) { - boolean movingAway = movingToDifferentEntry; - movingToDifferentEntry = false; + boolean movingAway = movingToDifferentEntry.get(); + movingToDifferentEntry.set(false); if (event.getSource() instanceof TextField) { // Storage from bibtex key field. @@ -636,15 +711,7 @@ public void actionPerformed(ActionEvent event) { // Add an UndoableKeyChange to the baseframe's undoManager. UndoableKeyChange undoableKeyChange = new UndoableKeyChange(entry, oldValue, newValue); - if (updateTimeStampIsSet()) { - NamedCompound ce = new NamedCompound(undoableKeyChange.getPresentationName()); - ce.addEdit(undoableKeyChange); - doUpdateTimeStamp().ifPresent(fieldChange -> ce.addEdit(new UndoableFieldChange(fieldChange))); - ce.end(); - panel.getUndoManager().addEdit(ce); - } else { - panel.getUndoManager().addEdit(undoableKeyChange); - } + updateTimestamp(undoableKeyChange); textField.setValidBackgroundColor(); @@ -706,18 +773,7 @@ public void actionPerformed(ActionEvent event) { // Add an UndoableFieldChange to the baseframe's undoManager. UndoableFieldChange undoableFieldChange = new UndoableFieldChange(entry, fieldEditor.getFieldName(), oldValue, toSet); - if (updateTimeStampIsSet()) { - NamedCompound ce = new NamedCompound(undoableFieldChange.getPresentationName()); - ce.addEdit(undoableFieldChange); - - doUpdateTimeStamp() - .ifPresent(fieldChange -> ce.addEdit(new UndoableFieldChange(fieldChange))); - ce.end(); - - panel.getUndoManager().addEdit(ce); - } else { - panel.getUndoManager().addEdit(undoableFieldChange); - } + updateTimestamp(undoableFieldChange); panel.markBaseChanged(); } catch (InvalidFieldValueException ex) { @@ -744,6 +800,18 @@ public void actionPerformed(ActionEvent event) { }); } } + + private void updateTimestamp(UndoableEdit undoableEdit) { + if (Globals.prefs.getTimestampPreferences().includeTimestamps()) { + NamedCompound compound = new NamedCompound(undoableEdit.getPresentationName()); + compound.addEdit(undoableEdit); + UpdateField.updateField(entry, Globals.prefs.getTimestampPreferences().getTimestampField(), Globals.prefs.getTimestampPreferences().now()).ifPresent(fieldChange -> compound.addEdit(new UndoableFieldChange(fieldChange))); + compound.end(); + panel.getUndoManager().addEdit(compound); + } else { + panel.getUndoManager().addEdit(undoableEdit); + } + } } private class NextEntryAction extends AbstractAction { @@ -814,7 +882,7 @@ public void actionPerformed(ActionEvent e) { } BibtexKeyPatternUtil.makeAndSetLabel(panel.getBibDatabaseContext().getMetaData() - .getCiteKeyPattern(Globals.prefs.getBibtexKeyPatternPreferences().getKeyPattern()), + .getCiteKeyPattern(Globals.prefs.getBibtexKeyPatternPreferences().getKeyPattern()), panel.getDatabase(), entry, Globals.prefs.getBibtexKeyPatternPreferences()); @@ -839,7 +907,7 @@ private UndoAction() { @Override public void actionPerformed(ActionEvent e) { - panel.runCommand(Actions.UNDO); + DefaultTaskExecutor.runInJavaFXThread(() -> panel.runCommand(Actions.UNDO)); } } @@ -861,7 +929,8 @@ private class AutoLinkAction extends AbstractAction { private AutoLinkAction() { putValue(Action.SMALL_ICON, IconTheme.JabRefIcon.AUTO_FILE_LINK.getIcon()); putValue(Action.SHORT_DESCRIPTION, - Localization.lang("Automatically set file links for this entry") + " (Alt-F)"); + Localization.lang("Automatically set file links for this entry") + + Globals.getKeyPrefs().get(KeyBinding.AUTOMATICALLY_LINK_FILES).map(b -> " (" + b + ")").orElse("")); } @Override diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditorTab.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditorTab.java index 5c0072202af..4c752af5170 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditorTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditorTab.java @@ -7,7 +7,7 @@ public abstract class EntryEditorTab extends Tab { /** * Used for lazy-loading of the tab content. */ - private boolean isInitialized = false; + protected boolean isInitialized = false; public abstract boolean shouldShow(); diff --git a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java index 6956ba518d3..db7d64534f9 100644 --- a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java @@ -154,6 +154,8 @@ private Region setupPanel(JabRefFrame frame, BasePanel bPanel, boolean compresse gridPane.getColumnConstraints().addAll(columnDoNotContract, columnExpand, new ColumnConstraints(10), columnDoNotContract, columnExpand); + + setCompressedRowLayout(gridPane, rows); } else { rows = fields.size(); @@ -161,20 +163,10 @@ private Region setupPanel(JabRefFrame frame, BasePanel bPanel, boolean compresse addColumn(gridPane, 1, editors.values().stream().map(FieldEditorFX::getNode)); gridPane.getColumnConstraints().addAll(columnDoNotContract, columnExpand); - } - RowConstraints rowExpand = new RowConstraints(); - rowExpand.setVgrow(Priority.ALWAYS); - rowExpand.setValignment(VPos.TOP); - if (rows == 0) { - rowExpand.setPercentHeight(100); - } else { - rowExpand.setPercentHeight(100 / rows); - } - for (int i = 0; i < rows; i++) { - gridPane.getRowConstraints().add(rowExpand); + setRegularRowLayout(gridPane, rows); } - + if (GUIGlobals.currentFont != null) { gridPane.setStyle( "text-area-background: " + convertToHex(GUIGlobals.validFieldBackgroundColor) + ";" @@ -193,6 +185,36 @@ private Region setupPanel(JabRefFrame frame, BasePanel bPanel, boolean compresse return scrollPane; } + private void setRegularRowLayout(GridPane gridPane, int rows) { + List constraints = new ArrayList<>(rows); + for (String field : fields) { + RowConstraints rowExpand = new RowConstraints(); + rowExpand.setVgrow(Priority.ALWAYS); + rowExpand.setValignment(VPos.TOP); + if (rows == 0) { + rowExpand.setPercentHeight(100); + } else { + rowExpand.setPercentHeight(100 / rows * editors.get(field).getWeight()); + } + constraints.add(rowExpand); + } + gridPane.getRowConstraints().addAll(constraints); + } + + private void setCompressedRowLayout(GridPane gridPane, int rows) { + RowConstraints rowExpand = new RowConstraints(); + rowExpand.setVgrow(Priority.ALWAYS); + rowExpand.setValignment(VPos.TOP); + if (rows == 0) { + rowExpand.setPercentHeight(100); + } else { + rowExpand.setPercentHeight(100 / rows); + } + for (int i = 0; i < rows; i++) { + gridPane.getRowConstraints().add(rowExpand); + } + } + private String getPrompt(String field) { Set fieldProperties = InternalBibtexFields.getFieldProperties(field); @@ -205,12 +227,12 @@ private String getPrompt(String field) { } switch (field) { - case FieldName.YEAR: - return "YYYY"; - case FieldName.MONTH: - return "MM or #mmm#"; - case FieldName.URL: - return "https://"; + case FieldName.YEAR: + return "YYYY"; + case FieldName.MONTH: + return "MM or #mmm#"; + case FieldName.URL: + return "https://"; } return ""; diff --git a/src/main/java/org/jabref/gui/entryeditor/FileAnnotationTab.java b/src/main/java/org/jabref/gui/entryeditor/FileAnnotationTab.java deleted file mode 100644 index 77ed0558732..00000000000 --- a/src/main/java/org/jabref/gui/entryeditor/FileAnnotationTab.java +++ /dev/null @@ -1,390 +0,0 @@ -package org.jabref.gui.entryeditor; - -import java.awt.Component; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; -import java.util.Comparator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.StringJoiner; - -import javax.swing.DefaultListCellRenderer; -import javax.swing.DefaultListModel; -import javax.swing.JButton; -import javax.swing.JComboBox; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; -import javax.swing.SwingUtilities; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; - -import javafx.embed.swing.SwingNode; -import javafx.scene.control.Tooltip; - -import org.jabref.gui.ClipBoardManager; -import org.jabref.gui.GUIGlobals; -import org.jabref.gui.IconTheme; -import org.jabref.logic.l10n.Localization; -import org.jabref.logic.pdf.FileAnnotationCache; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.FieldName; -import org.jabref.model.pdf.FileAnnotation; - -import com.jgoodies.forms.builder.FormBuilder; -import com.jgoodies.forms.factories.Paddings; - -import static org.jabref.model.pdf.FileAnnotationType.NONE; - -class FileAnnotationTab extends EntryEditorTab { - - private final JList annotationList = new JList<>(); - private final JScrollPane annotationScrollPane = new JScrollPane(); - private final JLabel fileNameLabel = new JLabel(Localization.lang("Filename"), JLabel.CENTER); - private final JComboBox fileNameComboBox = new JComboBox<>(); - private final JScrollPane fileNameScrollPane = new JScrollPane(); - private final JLabel authorLabel = new JLabel(Localization.lang("Author"), JLabel.CENTER); - private final JTextArea authorArea = new JTextArea("author"); - private final JScrollPane authorScrollPane = new JScrollPane(); - private final JLabel dateLabel = new JLabel(Localization.lang("Date"), JLabel.CENTER); - private final JTextArea dateArea = new JTextArea("date"); - private final JScrollPane dateScrollPane = new JScrollPane(); - private final JLabel pageLabel = new JLabel(Localization.lang("Page"), JLabel.CENTER); - private final JTextArea pageArea = new JTextArea("page"); - private final JScrollPane pageScrollPane = new JScrollPane(); - private final JLabel annotationTextLabel = new JLabel(Localization.lang("Content"), JLabel.CENTER); - private final JTextArea contentTxtArea = new JTextArea(); - private final JLabel markedTextLabel = new JLabel(Localization.lang("Marking"), JLabel.CENTER); - private final JTextArea markedTxtArea = new JTextArea(); - private final JScrollPane annotationTextScrollPane = new JScrollPane(); - private final JScrollPane markedTextScrollPane = new JScrollPane(); - private final JButton copyToClipboardButton = new JButton(); - private final JButton reloadAnnotationsButton = new JButton(); - private final BibEntry entry; - private final FileAnnotationCache fileAnnotationCache; - private final SwingNode swingNode = new SwingNode(); - private final EntryEditor parent; - private DefaultListModel listModel; - - FileAnnotationTab(BibEntry entry, EntryEditor parent, FileAnnotationCache cache) { - this.entry = entry; - this.fileAnnotationCache = cache; - this.parent = parent; - listModel = new DefaultListModel<>(); - - this.setText(Localization.lang("File annotations")); - this.setTooltip(new Tooltip(Localization.lang("Show file annotations"))); - this.setContent(swingNode); - } - - /** - * Adds pdf annotations from all attached pdf files belonging to the entry selected in the main table and - * shows those from the first file in the file annotations tab - */ - private void addAnnotations() { - if (parent.getEntry().getField(FieldName.FILE).isPresent()) { - if (!annotationList.getModel().equals(listModel)) { - annotationList.setModel(listModel); - annotationList.addListSelectionListener(new AnnotationListSelectionListener()); - annotationList.setCellRenderer(new AnnotationListCellRenderer()); - } - - //set up the comboBox for representing the selected file - fileNameComboBox.removeAllItems(); - final Map> fileAnnotations = fileAnnotationCache.getFromCache(parent.getEntry()); - fileAnnotations.keySet().forEach(fileNameComboBox::addItem); - - //show the annotationsOfFiles attached to the selected file - updateShownAnnotations(fileAnnotations.get(fileNameComboBox.getSelectedItem() == null ? - fileNameComboBox.getItemAt(0) : fileNameComboBox.getSelectedItem().toString())); - //select the first annotation - if (annotationList.isSelectionEmpty()) { - annotationList.setSelectedIndex(0); - } - } - } - - /** - * Updates the list model to show the given notes without those with no content - * - * @param annotations value is the annotation name and the value is a pdfAnnotation object to add to the list model - */ - private void updateShownAnnotations(List annotations) { - listModel.clear(); - if (annotations == null || annotations.isEmpty()) { - listModel.addElement(new FileAnnotation("", LocalDateTime.now(), 0, Localization.lang("File has no attached annotations"), NONE, Optional.empty())); - } else { - Comparator byPage = Comparator.comparingInt(FileAnnotation::getPage); - annotations.stream() - .filter(annotation -> (null != annotation.getContent())) - .sorted(byPage) - .forEach(annotation -> listModel.addElement(new FileAnnotationViewModel(annotation))); - } - } - - - /** - * Updates the text fields showing meta data and the content from the selected annotation - * - * @param annotation pdf annotation which data should be shown in the text fields - */ - private void updateTextFields(FileAnnotation annotation) { - authorArea.setText(annotation.getAuthor()); - dateArea.setText(annotation.getTimeModified().format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM))); - pageArea.setText(String.valueOf(annotation.getPage())); - updateContentAndMarkedTextAreas(annotation); - } - - /** - * Updates the selection of files that are attached to the pdf file - */ - private void updateFileNameComboBox() { - int indexSelectedByComboBox; - if (fileNameComboBox.getItemCount() == 0) { - indexSelectedByComboBox = 0; - } else { - indexSelectedByComboBox = fileNameComboBox.getSelectedIndex(); - } - fileNameComboBox.removeAllItems(); - final Map> fileAnnotations = fileAnnotationCache.getFromCache(parent.getEntry()); - fileAnnotations.keySet().stream().filter(filename -> filename.toLowerCase(Locale.ROOT).endsWith(".pdf")). - forEach((fileNameComboBox::addItem)); - fileNameComboBox.setSelectedIndex(indexSelectedByComboBox); - updateShownAnnotations(fileAnnotations.get(fileNameComboBox.getSelectedItem().toString())); - } - - private void setUpGui() { - JPanel annotationPanel = FormBuilder.create() - .columns("pref, $lcgap, pref:grow") - .rows("pref, $lg, fill:pref:grow, $lg, pref") - .padding(Paddings.DIALOG) - .add(fileNameLabel).xy(1, 1, "left, top") - .add(fileNameScrollPane).xyw(2, 1, 2) - .add(annotationScrollPane).xyw(1, 3, 3) - .build(); - annotationScrollPane.setViewportView(annotationList); - - JPanel informationPanel = FormBuilder.create() - .columns("pref, $lcgap, pref:grow") - .rows("pref, $lg, pref, $lg, pref, $lg, pref, $lg, pref:grow, $lg, pref:grow, $lg, fill:pref") - .padding(Paddings.DIALOG) - .add(authorLabel).xy(1, 3, "left, top") - .add(authorScrollPane).xy(3, 3) - .add(dateLabel).xy(1, 5, "left, top") - .add(dateScrollPane).xy(3, 5) - .add(pageLabel).xy(1, 7, "left, top") - .add(pageScrollPane).xy(3, 7) - .add(annotationTextLabel).xy(1, 9, "left, top") - .add(annotationTextScrollPane).xywh(3, 9, 1, 2) - .add(markedTextLabel).xy(1, 11, "left, top") - .add(markedTextScrollPane).xywh(3, 11, 1, 2) - .add(this.setUpButtons()).xyw(1, 13, 3) - .build(); - - fileNameScrollPane.setViewportView(fileNameComboBox); - fileNameLabel.setForeground(GUIGlobals.ENTRY_EDITOR_LABEL_COLOR); - authorLabel.setForeground(GUIGlobals.ENTRY_EDITOR_LABEL_COLOR); - dateLabel.setForeground(GUIGlobals.ENTRY_EDITOR_LABEL_COLOR); - pageLabel.setForeground(GUIGlobals.ENTRY_EDITOR_LABEL_COLOR); - annotationTextLabel.setForeground(GUIGlobals.ENTRY_EDITOR_LABEL_COLOR); - markedTextLabel.setForeground(GUIGlobals.ENTRY_EDITOR_LABEL_COLOR); - fileNameScrollPane.setBorder(null); - authorScrollPane.setViewportView(authorArea); - authorScrollPane.setBorder(null); - dateScrollPane.setViewportView(dateArea); - dateScrollPane.setBorder(null); - pageScrollPane.setViewportView(pageArea); - pageScrollPane.setBorder(null); - annotationTextScrollPane.setViewportView(contentTxtArea); - markedTextScrollPane.setViewportView(markedTxtArea); - authorArea.setEditable(false); - dateArea.setEditable(false); - pageArea.setEditable(false); - contentTxtArea.setEditable(false); - contentTxtArea.setLineWrap(true); - markedTxtArea.setEditable(false); - markedTxtArea.setLineWrap(true); - fileNameComboBox.setEditable(false); - fileNameComboBox.addActionListener(e -> updateFileNameComboBox()); - - swingNode.setContent(FormBuilder.create() - .columns("0:grow, $lcgap, 0:grow") - .rows("fill:pref:grow") - .add(annotationPanel).xy(1, 1) - .add(informationPanel).xy(3, 1) - .build()); - } - - private JPanel setUpButtons() { - JPanel buttonPanel = new JPanel(new GridBagLayout()); - GridBagConstraints buttonConstraints = new GridBagConstraints(); - - copyToClipboardButton.setText(Localization.lang("Copy to clipboard")); - copyToClipboardButton.addActionListener(e -> copyToClipboard()); - reloadAnnotationsButton.setText(Localization.lang("Reload annotations")); - reloadAnnotationsButton.addActionListener(e -> reloadAnnotations()); - - buttonConstraints.gridy = 10; - buttonConstraints.gridx = 3; - - buttonPanel.add(copyToClipboardButton, buttonConstraints); - - buttonConstraints.gridx = 2; - - buttonConstraints.gridx = 1; - buttonPanel.add(reloadAnnotationsButton, buttonConstraints); - - return buttonPanel; - } - - /** - * Copies the meta and content information of the pdf annotation to the clipboard - */ - private void copyToClipboard() { - StringJoiner sj = new StringJoiner(System.getProperty("line.separator")); - sj.add(Localization.lang("Author") + ": " + authorArea.getText()); - sj.add(Localization.lang("Date") + ": " + dateArea.getText()); - sj.add(Localization.lang("Page") + ": " + pageArea.getText()); - sj.add(Localization.lang("Content") + ": " + contentTxtArea.getText()); - sj.add(Localization.lang("Marking") + ": " + markedTxtArea.getText()); - - new ClipBoardManager().setClipboardContents(sj.toString()); - } - - private void reloadAnnotations() { - fileAnnotationCache.remove(parent.getEntry()); - initialize(); - } - - - /** - * Fills the TextAreas of the content and the highlighted or underlined text with the corresponding text and also - * changes the label accordingly. - * - * @param annotation either a text annotation or a marking from a PDF - */ - private void updateContentAndMarkedTextAreas(final FileAnnotation annotation) { - updateMarkingType(annotation); - - if (annotation.hasLinkedAnnotation()) { - // isPresent() of the optional is already checked in annotation.hasLinkedAnnotation() - if (!annotation.getLinkedFileAnnotation().getContent().isEmpty()) { - contentTxtArea.setText(annotation.getLinkedFileAnnotation().getContent()); - contentTxtArea.setEnabled(true); - } else { - contentTxtArea.setText("N/A"); - contentTxtArea.setEnabled(false); - } - - if (annotation.getContent().isEmpty()) { - markedTxtArea.setEnabled(false); - markedTxtArea.setText(Localization.lang("The marked area does not contain any legible text!")); - } else { - markedTxtArea.setEnabled(true); - markedTxtArea.setText(annotation.getContent()); - } - } else { - contentTxtArea.setEnabled(true); - if ("File has no attached annotations.".equals(annotation.getContent())) { - authorArea.setText("N/A"); - authorArea.setEnabled(false); - dateArea.setText("N/A"); - dateArea.setEnabled(false); - pageArea.setText("N/A"); - pageArea.setEnabled(false); - contentTxtArea.setEnabled(false); - } - contentTxtArea.setText(annotation.getContent()); - markedTxtArea.setText("N/A"); - markedTxtArea.setEnabled(false); - } - } - - private void updateMarkingType(FileAnnotation annotation) { - switch (annotation.getAnnotationType()) { - case UNDERLINE: - markedTextLabel.setText(Localization.lang("Underline")); - break; - case HIGHLIGHT: - markedTextLabel.setText(Localization.lang("Highlight")); - break; - default: - markedTextLabel.setText(Localization.lang("Marking")); - break; - } - } - - @Override - public boolean shouldShow() { - return entry.getField(FieldName.FILE).isPresent(); - } - - @Override - protected void initialize() { - addAnnotations(); - SwingUtilities.invokeLater(this::setUpGui); - this.parent.repaint(); - } - - private class AnnotationListSelectionListener implements ListSelectionListener { - @Override - public void valueChanged(ListSelectionEvent e) { - - int annotationListSelectedIndex = 0; - if (annotationList.getSelectedIndex() >= 0) { - int index = annotationList.getSelectedIndex(); - updateTextFields(listModel.get(index)); - annotationListSelectedIndex = index; - } - annotationList.setSelectedIndex(annotationListSelectedIndex); - //repaint the list to refresh the linked annotation - annotationList.repaint(); - } - } - - /** - * Cell renderer that shows different icons dependent on the annotation subtype - */ - class AnnotationListCellRenderer extends DefaultListCellRenderer { - - JLabel label; - - AnnotationListCellRenderer() { - this.label = new JLabel(); - } - - @Override - public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - - FileAnnotation annotation = (FileAnnotation) value; - - //call the super method so that the cell selection is done as usual - label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - - //If more different annotation types should be reflected by icons in the list, add them here - switch (annotation.getAnnotationType()) { - case HIGHLIGHT: - label.setIcon(IconTheme.JabRefIcon.MARKER.getSmallIcon()); - break; - case UNDERLINE: - label.setIcon(IconTheme.JabRefIcon.MARKER.getSmallIcon()); - break; - default: - label.setIcon(IconTheme.JabRefIcon.OPTIONAL.getSmallIcon()); - break; - } - - label.setToolTipText(annotation.getAnnotationType().toString()); - label.setText(annotation.toString()); - - return label; - } - } -} diff --git a/src/main/java/org/jabref/gui/entryeditor/FileAnnotationViewModel.java b/src/main/java/org/jabref/gui/entryeditor/FileAnnotationViewModel.java deleted file mode 100644 index ea2a9ac7fa9..00000000000 --- a/src/main/java/org/jabref/gui/entryeditor/FileAnnotationViewModel.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.jabref.gui.entryeditor; - -import java.util.Optional; - -import org.jabref.logic.l10n.Localization; -import org.jabref.model.pdf.FileAnnotation; -import org.jabref.model.pdf.FileAnnotationType; - -public class FileAnnotationViewModel extends FileAnnotation { - - public FileAnnotationViewModel(FileAnnotation annotation) { - super(annotation.getAuthor(), annotation.getTimeModified(), annotation.getPage(), annotation.getContent(), - annotation.getAnnotationType(), annotation.hasLinkedAnnotation() ? Optional.of(annotation.getLinkedFileAnnotation()) : Optional.empty()); - } - - @Override - public String toString() { - if (this.hasLinkedAnnotation() && this.getContent().isEmpty()) { - if (FileAnnotationType.UNDERLINE.equals(this.getAnnotationType())) { - return Localization.lang("Empty Underline"); - } - if (FileAnnotationType.HIGHLIGHT.equals(this.getAnnotationType())) { - return Localization.lang("Empty Highlight"); - } - return Localization.lang("Empty Marking"); - } - - if (FileAnnotationType.UNDERLINE.equals(this.getAnnotationType())) { - return Localization.lang("Underline") + ": " + this.getContent(); - } - if (FileAnnotationType.HIGHLIGHT.equals(this.getAnnotationType())) { - return Localization.lang("Highlight") + ": " + this.getContent(); - } - - return super.toString(); - } -} diff --git a/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java index df61449426c..0c004ad48d7 100644 --- a/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java @@ -34,4 +34,17 @@ private static List getOtherFields(EntryType entryType, BibEntry entry) otherFields.removeAll(Globals.prefs.getCustomTabFieldNames()); return otherFields; } + + public static boolean isOtherField(EntryType entryType, String fieldToCheck) { + List allKnownFields = entryType.getAllFields().stream().map(String::toLowerCase) + .collect(Collectors.toList()); + if (allKnownFields.contains(fieldToCheck) || + entryType.getDeprecatedFields().contains(fieldToCheck) || + BibEntry.KEY_FIELD.equals(fieldToCheck) || + Globals.prefs.getCustomTabFieldNames().contains(fieldToCheck)) { + return false; + } else { + return true; + } + } } diff --git a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java index b7624098d25..69a30451f79 100644 --- a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java @@ -6,16 +6,21 @@ import java.util.Map; import java.util.Objects; +import javax.swing.undo.UndoManager; + +import javafx.beans.property.BooleanProperty; import javafx.scene.Node; import javafx.scene.control.Tooltip; import org.jabref.Globals; +import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.FXDialogService; import org.jabref.gui.IconTheme; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableChangeType; import org.jabref.gui.undo.UndoableFieldChange; +import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.logic.bibtex.BibEntryWriter; import org.jabref.logic.bibtex.InvalidFieldValueException; import org.jabref.logic.bibtex.LatexFieldFormatter; @@ -23,13 +28,10 @@ import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabase; -import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.InternalBibtexFields; -import org.jabref.model.entry.event.EntryChangedEvent; -import com.google.common.eventbus.Subscribe; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.fxmisc.easybind.EasyBind; @@ -41,20 +43,33 @@ public class SourceTab extends EntryEditorTab { private static final Log LOGGER = LogFactory.getLog(SourceTab.class); private final BibDatabaseMode mode; private final BibEntry entry; + private final BasePanel panel; private CodeArea codeArea; + private BooleanProperty movingToDifferentEntry; + private UndoManager undoManager; - public SourceTab(BibDatabaseContext context, BibEntry entry) { - this.mode = context.getMode(); + public SourceTab(BasePanel panel, BibEntry entry, BooleanProperty movingToDifferentEntry) { + this.mode = panel.getBibDatabaseContext().getMode(); this.entry = entry; - context.getDatabase().registerListener(this); + this.panel = panel; + this.movingToDifferentEntry = movingToDifferentEntry; this.setText(Localization.lang("%0 source", mode.getFormattedName())); this.setTooltip(new Tooltip(Localization.lang("Show/edit %0 source", mode.getFormattedName()))); this.setGraphic(IconTheme.JabRefIcon.SOURCE.getGraphicNode()); + this.undoManager = panel.getUndoManager(); + } + + private static String getSourceString(BibEntry entry, BibDatabaseMode type) throws IOException { + StringWriter stringWriter = new StringWriter(200); + LatexFieldFormatter formatter = LatexFieldFormatter + .buildIgnoreHashes(Globals.prefs.getLatexFieldFormatterPreferences()); + new BibEntryWriter(formatter, false).writeWithoutPrependedNewlines(entry, stringWriter, type); + + return stringWriter.getBuffer().toString(); } - @Subscribe - public void listen(EntryChangedEvent event) { - if (codeArea != null && this.entry.equals(event.getBibEntry())) { + public void updateSourcePane() { + if (codeArea != null) { try { codeArea.clear(); codeArea.appendText(getSourceString(entry, mode)); @@ -67,25 +82,25 @@ public void listen(EntryChangedEvent event) { } } - private static String getSourceString(BibEntry entry, BibDatabaseMode type) throws IOException { - StringWriter stringWriter = new StringWriter(200); - LatexFieldFormatter formatter = LatexFieldFormatter - .buildIgnoreHashes(Globals.prefs.getLatexFieldFormatterPreferences()); - new BibEntryWriter(formatter, false).writeWithoutPrependedNewlines(entry, stringWriter, type); - - return stringWriter.getBuffer().toString(); - } - private Node createSourceEditor(BibEntry entry, BibDatabaseMode mode) { codeArea = new CodeArea(); codeArea.setWrapText(true); - //codeArea.(Font.font("Monospaced", Globals.prefs.getInt(JabRefPreferences.FONT_SIZE))); + codeArea.lookup(".styled-text-area").setStyle( + "-fx-font-size: " + Globals.prefs.getFontSizeFX() + "pt;"); + // store source if new tab is selected (if this one is not focused anymore) EasyBind.subscribe(codeArea.focusedProperty(), focused -> { if (!focused) { storeSource(); } }); + // store source if new entry is selected in the maintable and the source tab is focused + EasyBind.subscribe(movingToDifferentEntry, newEntrySelected -> { + if (newEntrySelected && codeArea.focusedProperty().get()) { + DefaultTaskExecutor.runInJavaFXThread(() -> storeSource()); + } + }); + try { String srcString = getSourceString(entry, mode); codeArea.appendText(srcString); @@ -96,6 +111,13 @@ private Node createSourceEditor(BibEntry entry, BibDatabaseMode mode) { LOGGER.debug("Incorrect entry", ex); } + // set the database to dirty when something is changed in the source tab + EasyBind.subscribe(codeArea.beingUpdatedProperty(), updated -> { + if (updated) { + panel.markBaseChanged(); + } + }); + return new VirtualizedScrollPane<>(codeArea); } @@ -135,8 +157,6 @@ private void storeSource() { NamedCompound compound = new NamedCompound(Localization.lang("source edit")); BibEntry newEntry = database.getEntries().get(0); String newKey = newEntry.getCiteKeyOptional().orElse(null); - boolean entryChanged = false; - boolean emptyWarning = (newKey == null) || newKey.isEmpty(); if (newKey != null) { entry.setCiteKey(newKey); @@ -153,7 +173,6 @@ private void storeSource() { compound.addEdit( new UndoableFieldChange(entry, fieldName, fieldValue, null)); entry.clearField(fieldName); - entryChanged = true; } } @@ -169,7 +188,6 @@ private void storeSource() { compound.addEdit(new UndoableFieldChange(entry, fieldName, oldValue, newValue)); entry.setField(fieldName, newValue); - entryChanged = true; } } @@ -177,23 +195,10 @@ private void storeSource() { if (!Objects.equals(newEntry.getType(), entry.getType())) { compound.addEdit(new UndoableChangeType(entry, entry.getType(), newEntry.getType())); entry.setType(newEntry.getType()); - entryChanged = true; } compound.end(); + undoManager.addEdit(compound); - // TODO: Add undo - //panel.getUndoManager().addEdit(compound); - - // TODO: Warn about duplicate/empty bibtext key - /* - if (panel.getDatabase().getDuplicationChecker().isDuplicateCiteKeyExisting(entry)) { - warnDuplicateBibtexkey(); - } else if (emptyWarning) { - warnEmptyBibtexkey(); - } else { - panel.output(Localization.lang("Stored entry") + '.'); - } - */ } catch (InvalidFieldValueException | IOException ex) { // The source couldn't be parsed, so the user is given an // error message, and the choice to keep or revert the contents diff --git a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.fxml b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.fxml new file mode 100644 index 00000000000..7f6abc4d52f --- /dev/null +++ b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.fxml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+ + + + + + + + + + + + + + + + + +