diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 1426417c5bf..00000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,26 +0,0 @@ - - -- [ ] I have tested the latest master version from http://builds.jabref.org/master/ and the problem persists - - -JabRef version on - - - -Steps to reproduce: - -1. ... -2. ... -3. ... - - - -
- Log File - - ``` - Paste an excerpt of your log file here - ``` -
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..32a3c7f37fb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + + + +JabRef version on + +- [ ] I have tested the latest development version from http://builds.jabref.org/master/ and the problem persists + + + +Steps to reproduce the behavior: +1. ... +2. ... +3. ... + + + +
+ Log File + + ``` + Paste an excerpt of your log file here + ``` +
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000000..d2ae96c562d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,9 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +Please use the GitHub issue tracker only for bug reports and suggestions for improvements. +Feature requests, questions and general feedback is now handled at http://discourse.jabref.org. +Thanks! diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 00000000000..7e9c25fe4b4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,9 @@ +--- +name: Question +about: Ask a question about JabRef + +--- + +Please use the GitHub issue tracker only for bug reports and suggestions for improvements. +Feature requests, questions and general feedback is now handled at http://discourse.jabref.org. +Thanks! diff --git a/.github/ISSUE_TEMPLATE/suggestion-for-improvement.md b/.github/ISSUE_TEMPLATE/suggestion-for-improvement.md new file mode 100644 index 00000000000..ec6d48cc1dc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/suggestion-for-improvement.md @@ -0,0 +1,20 @@ +--- +name: Suggestion for improvement +about: Suggest an enhancement + +--- + + + +**Is your suggestion for improvement related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.travis.yml b/.travis.yml index e270879be38..489044d8ec4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,7 +47,7 @@ before_script: script: # --scan enables the Gradle build scan, which can be used to investigate the time each action consumes # For more information see https://gradle.com/scans/get-started - - if [ "$TEST_SUITE" != "guiTest" ] && [ "$TEST_SUITE" != "checkstyle" ] && [ "$TEST_SUITE" != "codecov" ]; then ./gradlew $TEST_SUITE $OPTIONS --scan; fi + - if [ "$TEST_SUITE" != "guiTest" ] && [ "$TEST_SUITE" != "checkstyle" ] && [ "$TEST_SUITE" != "codecov" ]; then ./gradlew $TEST_SUITE $OPTIONS -x checkstyleJmh -x checkstyleMain -x checkstyleTest --scan; fi - if [ "$TEST_SUITE" == "checkstyle" ]; then ./gradlew checkstyleMain checkstyleTest checkstyleJmh; fi - if [ "$TEST_SUITE" == "guiTest" ]; then ./buildres/gui-tests.sh; fi - if [ "$TEST_SUITE" == "codecov" ]; then ./gradlew jacocoTestReport; bash <(curl -s https://codecov.io/bash); fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 378e01c4634..9848a45705f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# ## [Unreleased] ### Changed +- We updated the dialog for setting up general fields. +- URL field formatting is updated. All whitespace chars, located at the beginning/ending of the url, are trimmed automatically - We changed the behavior of the field formatting dialog such that the `bibtexkey` is not changed when formatting all fields or all text fields. - We added a "Move file to file directory and rename file" option for simultaneously moving and renaming of document file. [#4166](https://github.com/JabRef/jabref/issues/4166) - Use integrated graphics card instead of discrete on macOS [#4070](https://github.com/JabRef/jabref/issues/4070) @@ -31,7 +33,9 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - We removed the redundant new lines of markings and wrapped the summary in the File annotation tab. [#3823](https://github.com/JabRef/jabref/issues/3823) - We add auto url formatting when user paste link to URL field in entry editor. [#254](https://github.com/koppor/jabref/issues/254) - We added a minimal height for the entry editor so that it can no longer be hidden by accident. [#4279](https://github.com/JabRef/jabref/issues/4279) - +- We added a new keyboard shortcut so that the entry editor could be closed by Ctrl + E. [#4222] (https://github.com/JabRef/jabref/issues/4222) +- We added an option in the preference dialog box, that allows user to pick the dark or light theme option. [#4130] (https://github.com/JabRef/jabref/issues/4130) +- We updated updated the Related Articles tab to accept JSON from the new version of the Mr. DLib service @@ -39,6 +43,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# ### Fixed +- We fixed an issue where corresponding groups are sometimes not highlighted when clicking on entries [#3112](https://github.com/JabRef/jabref/issues/3112) - We fixed an issue where custom exports could not be selected in the 'Export (selected) entries' dialog [#4013](https://github.com/JabRef/jabref/issues/4013) - Italic text is now rendered correctly. https://github.com/JabRef/jabref/issues/3356 - The entry editor no longer gets corrupted after using the source tab. https://github.com/JabRef/jabref/issues/3532 https://github.com/JabRef/jabref/issues/3608 https://github.com/JabRef/jabref/issues/3616 @@ -56,6 +61,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - We fixed an issue where the order of fields in customized entry types was not saved correctly. [#4033](http://github.com/JabRef/jabref/issues/4033) - We fixed an issue where the groups tree of the last database was still shown even after the database was already closed. - We fixed an issue where the "Open file dialog" may disappear behind other windows. https://github.com/JabRef/jabref/issues/3410 +- We fixed an issue where the number of entries matched was not updated correctly upon adding or removing an entry. [#3537](https://github.com/JabRef/jabref/issues/3537) - We fixed an issue where the default icon of a group was not colored correctly. - We fixed an issue where the first field in entry editor was not focused when adding a new entry. [#4024](https://github.com/JabRef/jabref/issues/4024) - We reworked the "Edit file" dialog to make it resizeable and improved the workflow for adding and editing files https://github.com/JabRef/jabref/issues/2970 @@ -68,6 +74,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - We fixed an issue where files added via the "Attach file" contextmenu of an entry were not made relative. [#4201](https://github.com/JabRef/jabref/issues/4201) and [#4241](https://github.com/JabRef/jabref/issues/4241) - We fixed an issue where author list parser can't generate bibtex for Chinese author. [#4169](https://github.com/JabRef/jabref/issues/4169) - We fixed an issue where the list of XMP Exclusion fields in the preferences was not be saved [#4072](https://github.com/JabRef/jabref/issues/4072) +- We fixed an issue where the ArXiv Fetcher did not support HTTP URLs [#4367](https://github.com/JabRef/jabref/pull/4367) diff --git a/README.md b/README.md index 5cd2607dbc4..cec54b33f74 100644 --- a/README.md +++ b/README.md @@ -101,9 +101,7 @@ When you want to develop, it is necessary to generate additional sources using ` and then generate the Eclipse `gradlew eclipse`. For IntelliJ IDEA, just import the project via a Gradle Import by pointing at the `build.gradle`. -`gradlew test` executes the normal unit tests. -If you want to test the UI, execute `gradlew integrationTest`. -Sources for the integration test are kept in `src/integrationTest`. +`gradlew test` executes all tests. We use [Travis CI](https://travis-ci.org/) for executing the tests after each commit. For developing, it is sufficient to locally only run the associated test for the classes you changed. Travis will report any other failure. ## Acknowledgements diff --git a/build.gradle b/build.gradle index c9c2f90604d..a88052930d2 100644 --- a/build.gradle +++ b/build.gradle @@ -16,12 +16,12 @@ buildscript { plugins { id 'com.gradle.build-scan' version '1.16' - id 'com.install4j.gradle' version '7.0.7' + id 'com.install4j.gradle' version '7.0.8' id 'com.github.johnrengelman.shadow' version '2.0.4' id "de.sebastianboegl.shadow.transformer.log4j" version "2.1.1" id "com.simonharrer.modernizer" version '1.6.0-1' id 'me.champeau.gradle.jmh' version '0.4.7' - id 'net.ltgt.errorprone' version '0.0.16' + id 'net.ltgt.errorprone' version '0.6' id 'com.github.ben-manes.versions' version '0.20.0' } @@ -65,7 +65,7 @@ sourceSets { srcDirs = ["src/main/java", "src/main/resources"] } } - test{ + test { java { srcDirs = ["src/test/java"] } @@ -96,9 +96,9 @@ dependencies { compile 'com.jgoodies:jgoodies-common:1.8.1' compile 'com.jgoodies:jgoodies-forms:1.9.0' - compile 'org.apache.pdfbox:pdfbox:2.0.11' - compile 'org.apache.pdfbox:fontbox:2.0.11' - compile 'org.apache.pdfbox:xmpbox:2.0.11' + compile 'org.apache.pdfbox:pdfbox:2.0.12' + compile 'org.apache.pdfbox:fontbox:2.0.12' + compile 'org.apache.pdfbox:xmpbox:2.0.12' // required for reading write-protected PDFs - see https://github.com/JabRef/jabref/pull/942#issuecomment-209252635 compile 'org.bouncycastle:bcprov-jdk15on:1.60' @@ -119,13 +119,13 @@ dependencies { antlr4 'org.antlr:antlr4:4.7.1' compile 'org.antlr:antlr4-runtime:4.7.1' - compile 'mysql:mysql-connector-java:8.0.12' + compile 'mysql:mysql-connector-java:8.0.13' compile 'org.postgresql:postgresql:42.2.5' compile 'net.java.dev.glazedlists:glazedlists_java15:1.9.1' - compile 'com.google.guava:guava:26.0-jre' + compile 'com.google.guava:guava:27.0-jre' // JavaFX stuff compile 'de.jensd:fontawesomefx-materialdesignfont:1.7.22-4' @@ -158,30 +158,31 @@ dependencies { compile 'com.github.tomtung:latex2unicode_2.12:0.2.2' - errorprone 'com.google.errorprone:error_prone_core:2.3.1' + errorproneJavac 'com.google.errorprone:javac:1.8.0-u20' - compile group: 'com.microsoft.azure', name: 'applicationinsights-core', version: '2.1.2' - compile group: 'com.microsoft.azure', name: 'applicationinsights-logging-log4j2', version: '2.1.2' + compile group: 'com.microsoft.azure', name: 'applicationinsights-core', version: '2.2.0' + compile group: 'com.microsoft.azure', name: 'applicationinsights-logging-log4j2', version: '2.2.0' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.0' - testCompile 'org.junit.jupiter:junit-jupiter-params:5.3.0' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.0' - testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.3.0' - testCompile 'org.junit.platform:junit-platform-launcher:1.3.0' - testCompile 'org.junit-pioneer:junit-pioneer:0.1.2' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1' + testCompile 'org.junit.jupiter:junit-jupiter-params:5.3.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1' + testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.3.1' + testCompile 'org.junit.platform:junit-platform-launcher:1.3.1' + testCompile 'org.junit-pioneer:junit-pioneer:0.2.2' testRuntime 'org.apache.logging.log4j:log4j-core:2.11.1' testRuntime 'org.apache.logging.log4j:log4j-jul:2.11.1' - testCompile 'org.mockito:mockito-core:2.21.0' - testCompile 'com.github.tomakehurst:wiremock:2.18.0' + testCompile 'org.mockito:mockito-core:2.23.0' + testCompile 'com.github.tomakehurst:wiremock:2.19.0' testCompile 'org.assertj:assertj-swing-junit:3.8.0' testCompile 'org.reflections:reflections:0.9.11' testCompile 'org.xmlunit:xmlunit-core:2.6.2' testCompile 'org.xmlunit:xmlunit-matchers:2.6.2' - testCompile 'com.tngtech.archunit:archunit-junit:0.8.3' + testCompile 'com.tngtech.archunit:archunit-junit5-api:0.9.1' + testRuntime 'com.tngtech.archunit:archunit-junit5-engine:0.9.1' testCompile "org.testfx:testfx-core:4.0.+" testCompile "org.testfx:testfx-junit5:4.0.+" - checkstyle 'com.puppycrawl.tools:checkstyle:8.12' + checkstyle 'com.puppycrawl.tools:checkstyle:8.14' } jacoco { @@ -199,30 +200,51 @@ dependencyUpdates.revision = 'integration' dependencyUpdates.resolutionStrategy = { componentSelection { rules -> rules.all { ComponentSelection selection -> - if ( selection.candidate.module!="javax.inject" && selection.candidate.version ==~ /[0-9].*SNAPSHOT/ ) { + if (selection.candidate.version ==~ /[0-9].*SNAPSHOT/) { selection.reject("Ignore SNAPSHOT releases") } } + rules.withModule("com.gradle.build-scan:com.gradle.build-scan.gradle.plugin") { ComponentSelection selection -> + if (selection.candidate.version ==~ /2.*/) { + selection.reject("Cannot be upgraded to version 2 until we upgrade to gradle 5") + } + } rules.withModule("org.controlsfx:controlsfx") { ComponentSelection selection -> if (selection.candidate.version ==~ /9.*/) { // Reject version 9 or higher selection.reject("Cannot be updated to 9.*.* until Jabref works with Java 9") } } rules.withModule("com.github.tomtung:latex2unicode_2.12") { ComponentSelection selection -> - if (selection.candidate.version ==~ /0.2.2/) { // Reject version higher than 2.0.2 + if (selection.candidate.version ==~ /0.2.[3,4]/) { + // Reject version higher than 2.0.2. see https://github.com/JabRef/jabref/pull/3781 selection.reject("Cannot be updated to 0.2.4 until JabRef is prepared for it") } } + rules.withModule("com.github.johnrengelman.shadow:com.github.johnrengelman.shadow.gradle.plugin") { ComponentSelection selection -> + if (selection.candidate.version ==~ /4.*/) { + selection.reject("Version 4.X breaks the release process.") + } + } rules.withModule("de.jensd:fontawesomefx-materialdesignfont") { ComponentSelection selection -> if (selection.candidate.version ==~ /2.*/) { selection.reject("Cannot be upgraded to version 2") } } - rules.withModule("com.jfoenix:jfoenix") { ComponentSelection selection -> + rules.withModule("com.jfoenix:jfoenix") { ComponentSelection selection -> if (selection.candidate.version ==~ /9.*/) { // Reject version 9 or higher selection.reject("Cannot be updated to 9.*.* until Jabref works with Java 9") } } + rules.withModule("com.google.errorprone:javac") { ComponentSelection selection -> + if (selection.candidate.version ==~ /1.9.*/ || selection.candidate.version ==~ /9.*/) { + selection.reject("Cannot be updated to 9.*.* until Jabref works with Java 9") + } + } + rules.withModule("com.sun.xml.bind:jaxb-xjc") { ComponentSelection selection -> + if (!(selection.candidate.version ==~ /2.2.4.*/) || selection.candidate.version ==~ /2.[3-9].*/) { + selection.reject("Cannot be updated to 2.2.5 or higher.") + } + } } } @@ -386,12 +408,7 @@ jacocoTestReport { // Code quality tasks checkstyle { - // do not use other packages for checkstyle, excluding gen(erated) sources - checkstyleMain.source = "src/main/java" - toolVersion = '8.5' - // do not perform checkstyle checks by default - sourceSets = [] } modernizer { diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml index 7e853704d9d..71943814250 100644 --- a/config/checkstyle/suppressions.xml +++ b/config/checkstyle/suppressions.xml @@ -6,5 +6,4 @@ - diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 0d4a9516871..13536770052 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 115e6ac0aab..e0b3fb8d70b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/jmh/java/org/jabref/benchmarks/Benchmarks.java b/src/jmh/java/org/jabref/benchmarks/Benchmarks.java index c261a64589c..c17b4c6e76b 100644 --- a/src/jmh/java/org/jabref/benchmarks/Benchmarks.java +++ b/src/jmh/java/org/jabref/benchmarks/Benchmarks.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.StringReader; +import java.io.StringWriter; import java.util.List; import java.util.Random; import java.util.stream.Collectors; @@ -9,9 +10,7 @@ import org.jabref.Globals; import org.jabref.logic.exporter.BibtexDatabaseWriter; import org.jabref.logic.exporter.SavePreferences; -import org.jabref.logic.exporter.StringSaveSession; import org.jabref.logic.formatter.bibtexfields.HtmlToLatexFormatter; -import org.jabref.logic.importer.ParseException; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.logic.layout.format.HTMLChars; @@ -37,6 +36,8 @@ import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.runner.RunnerException; +import static org.mockito.Mockito.mock; + @State(Scope.Thread) public class Benchmarks { @@ -61,11 +62,11 @@ public void init() throws Exception { entry.setField("rnd", "2" + randomizer.nextInt()); database.insertEntry(entry); } - BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter<>(StringSaveSession::new); - StringSaveSession saveSession = databaseWriter.savePartOfDatabase( - new BibDatabaseContext(database, new MetaData(), new Defaults()), database.getEntries(), - new SavePreferences()); - bibtexString = saveSession.getStringValue(); + StringWriter outputWriter = new StringWriter(); + BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(outputWriter, mock(SavePreferences.class)); + databaseWriter.savePartOfDatabase( + new BibDatabaseContext(database, new MetaData(), new Defaults()), database.getEntries()); + bibtexString = outputWriter.toString(); latexConversionString = "{A} \\textbf{bold} approach {\\it to} ${{\\Sigma}}{\\Delta}$ modulator \\textsuperscript{2} \\$"; @@ -80,11 +81,10 @@ public ParserResult parse() throws IOException { @Benchmark public String write() throws Exception { - BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter<>(StringSaveSession::new); - StringSaveSession saveSession = databaseWriter.savePartOfDatabase( - new BibDatabaseContext(database, new MetaData(), new Defaults()), database.getEntries(), - new SavePreferences()); - return saveSession.getStringValue(); + StringWriter outputWriter = new StringWriter(); + BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(outputWriter, mock(SavePreferences.class)); + databaseWriter.savePartOfDatabase(new BibDatabaseContext(database, new MetaData(), new Defaults()), database.getEntries()); + return outputWriter.toString(); } @Benchmark @@ -125,7 +125,7 @@ public String htmlToLatexConversion() { } @Benchmark - public boolean keywordGroupContains() throws ParseException { + public boolean keywordGroupContains() { KeywordGroup group = new WordKeywordGroup("testGroup", GroupHierarchyType.INDEPENDENT, "keyword", "testkeyword", false, ',', false); return group.containsAll(database.getEntries()); } diff --git a/src/main/java/org/jabref/Globals.java b/src/main/java/org/jabref/Globals.java index 0e0b4457f4d..b86cc83cbd2 100644 --- a/src/main/java/org/jabref/Globals.java +++ b/src/main/java/org/jabref/Globals.java @@ -7,7 +7,6 @@ import javafx.stage.Screen; import org.jabref.gui.ClipBoardManager; -import org.jabref.gui.GlobalFocusListener; import org.jabref.gui.StateManager; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.util.DefaultFileUpdateMonitor; @@ -60,8 +59,6 @@ public class Globals { public static ExporterFactory exportFactory; // Key binding preferences private static KeyBindingRepository keyBindingRepository; - // Background tasks - private static GlobalFocusListener focusListener; private static DefaultFileUpdateMonitor fileUpdateMonitor; private static ThemeLoader themeLoader; private static TelemetryClient telemetryClient; @@ -78,13 +75,11 @@ public static synchronized KeyBindingRepository getKeyPrefs() { } // Background tasks - public static void startBackgroundTasks() { - Globals.focusListener = new GlobalFocusListener(); - + public static void startBackgroundTasks() throws JabRefException { Globals.fileUpdateMonitor = new DefaultFileUpdateMonitor(); JabRefExecutorService.INSTANCE.executeInterruptableTask(Globals.fileUpdateMonitor, "FileUpdateMonitor"); - themeLoader = new ThemeLoader(fileUpdateMonitor); + themeLoader = new ThemeLoader(fileUpdateMonitor, prefs); if (Globals.prefs.shouldCollectTelemetry() && !GraphicsEnvironment.isHeadless()) { startTelemetryClient(); @@ -117,10 +112,6 @@ private static void startTelemetryClient() { telemetryClient.trackSessionState(SessionState.Start); } - public static GlobalFocusListener getFocusListener() { - return focusListener; - } - public static FileUpdateMonitor getFileUpdateMonitor() { return fileUpdateMonitor; } diff --git a/src/main/java/org/jabref/JabRefGUI.java b/src/main/java/org/jabref/JabRefGUI.java index 84ea60e87cb..ac996a79899 100644 --- a/src/main/java/org/jabref/JabRefGUI.java +++ b/src/main/java/org/jabref/JabRefGUI.java @@ -15,21 +15,21 @@ import org.jabref.gui.GUIGlobals; import org.jabref.gui.JabRefFrame; import org.jabref.gui.dialogs.BackupUIManager; +import org.jabref.gui.help.VersionWorker; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.importer.ParserResultWarningDialog; import org.jabref.gui.importer.actions.OpenDatabaseAction; import org.jabref.gui.shared.SharedDatabaseUIManager; -import org.jabref.gui.worker.VersionWorker; import org.jabref.logic.autosaveandbackup.BackupManager; import org.jabref.logic.importer.OpenDatabase; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.l10n.Localization; import org.jabref.logic.shared.exception.InvalidDBMSConnectionPropertiesException; import org.jabref.logic.shared.exception.NotASharedDatabaseException; -import org.jabref.logic.util.Version; import org.jabref.model.database.shared.DatabaseNotSupportedException; import org.jabref.preferences.JabRefPreferences; +import impl.org.controlsfx.skin.DecorationPane; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,13 +60,8 @@ public JabRefGUI(Stage mainStage, List argsDatabases, boolean isBl .orElse(Globals.prefs.get(JabRefPreferences.LAST_FOCUSED)); openWindow(mainStage); - JabRefGUI.checkForNewVersion(false); - } - - public static void checkForNewVersion(boolean manualExecution) { - Version toBeIgnored = Globals.prefs.getVersionPreferences().getIgnoredVersion(); - Version currentVersion = Globals.BUILD_INFO.getVersion(); - new VersionWorker(JabRefGUI.getMainFrame(), manualExecution, currentVersion, toBeIgnored).execute(); + new VersionWorker(Globals.BUILD_INFO.getVersion(), Globals.prefs.getVersionPreferences().getIgnoredVersion(), JabRefGUI.getMainFrame().getDialogService(), Globals.TASK_EXECUTOR) + .checkForNewVersionAsync(false); } private void openWindow(Stage mainStage) { @@ -149,7 +144,12 @@ private void openWindow(Stage mainStage) { mainStage.setHeight(Globals.prefs.getDouble(JabRefPreferences.SIZE_Y)); } - Scene scene = new Scene(JabRefGUI.mainFrame, 800, 800); + // We create a decoration pane ourselves for performance reasons + // (otherwise it has to be injected later, leading to a complete redraw/relayout of the complete scene) + DecorationPane root = new DecorationPane(); + root.getChildren().add(JabRefGUI.mainFrame); + + Scene scene = new Scene(root, 800, 800); Globals.getThemeLoader().installBaseCss(scene, Globals.prefs); mainStage.setTitle(JabRefFrame.FRAME_TITLE); mainStage.getIcons().addAll(IconTheme.getLogoSetFX()); diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index 120fd25c775..a487e1cb348 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -16,14 +16,12 @@ import org.jabref.JabRefException; import org.jabref.gui.externalfiles.AutoSetLinks; import org.jabref.logic.bibtexkeypattern.BibtexKeyGenerator; +import org.jabref.logic.exporter.AtomicFileWriter; import org.jabref.logic.exporter.BibDatabaseWriter; import org.jabref.logic.exporter.BibtexDatabaseWriter; import org.jabref.logic.exporter.Exporter; import org.jabref.logic.exporter.ExporterFactory; -import org.jabref.logic.exporter.FileSaveSession; -import org.jabref.logic.exporter.SaveException; import org.jabref.logic.exporter.SavePreferences; -import org.jabref.logic.exporter.SaveSession; import org.jabref.logic.exporter.TemplateExporter; import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.ImportException; @@ -374,27 +372,7 @@ private boolean generateAux(List loaded, String[] data) { // write an output, if something could be resolved if ((newBase != null) && newBase.hasEntries()) { String subName = StringUtil.getCorrectFileName(data[1], "bib"); - - try { - System.out.println(Localization.lang("Saving") + ": " + subName); - SavePreferences prefs = Globals.prefs.loadForSaveFromPreferences(); - BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter<>(FileSaveSession::new); - Defaults defaults = new Defaults(Globals.prefs.getDefaultBibDatabaseMode()); - SaveSession session = databaseWriter.saveDatabase(new BibDatabaseContext(newBase, defaults), prefs); - - // Show just a warning message if encoding did not work for all characters: - if (!session.getWriter().couldEncodeAll()) { - System.err.println(Localization.lang("Warning") + ": " - + Localization.lang( - "The chosen encoding '%0' could not encode the following characters:", - session.getEncoding().displayName()) - + " " + session.getWriter().getProblemCharacters()); - } - session.commit(subName); - } catch (SaveException ex) { - System.err.println(Localization.lang("Could not save file.") + "\n" + ex.getLocalizedMessage()); - } - + saveDatabase(newBase, subName); notSavedMsg = true; } @@ -407,6 +385,28 @@ private boolean generateAux(List loaded, String[] data) { } } + private void saveDatabase(BibDatabase newBase, String subName) { + try { + System.out.println(Localization.lang("Saving") + ": " + subName); + SavePreferences prefs = Globals.prefs.loadForSaveFromPreferences(); + AtomicFileWriter fileWriter = new AtomicFileWriter(Paths.get(subName), prefs.getEncoding()); + BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter(fileWriter, prefs); + Defaults defaults = new Defaults(Globals.prefs.getDefaultBibDatabaseMode()); + databaseWriter.saveDatabase(new BibDatabaseContext(newBase, defaults)); + + // Show just a warning message if encoding did not work for all characters: + if (fileWriter.hasEncodingProblems()) { + System.err.println(Localization.lang("Warning") + ": " + + Localization.lang( + "The chosen encoding '%0' could not encode the following characters:", + prefs.getEncoding().displayName()) + + " " + fileWriter.getEncodingProblems()); + } + } catch (IOException ex) { + System.err.println(Localization.lang("Could not save file.") + "\n" + ex.getLocalizedMessage()); + } + } + private void exportFile(List loaded, String[] data) { if (data.length == 1) { // This signals that the latest import should be stored in BibTeX @@ -414,27 +414,7 @@ private void exportFile(List loaded, String[] data) { if (!loaded.isEmpty()) { ParserResult pr = loaded.get(loaded.size() - 1); if (!pr.isInvalid()) { - try { - System.out.println(Localization.lang("Saving") + ": " + data[0]); - SavePreferences prefs = Globals.prefs.loadForSaveFromPreferences(); - Defaults defaults = new Defaults(Globals.prefs.getDefaultBibDatabaseMode()); - BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter<>( - FileSaveSession::new); - SaveSession session = databaseWriter.saveDatabase( - new BibDatabaseContext(pr.getDatabase(), pr.getMetaData(), defaults), prefs); - - // Show just a warning message if encoding did not work for all characters: - if (!session.getWriter().couldEncodeAll()) { - System.err.println(Localization.lang("Warning") + ": " - + Localization.lang( - "The chosen encoding '%0' could not encode the following characters:", - session.getEncoding().displayName()) - + " " + session.getWriter().getProblemCharacters()); - } - session.commit(data[0]); - } catch (SaveException ex) { - System.err.println(Localization.lang("Could not save file.") + "\n" + ex.getLocalizedMessage()); - } + saveDatabase(pr.getDatabase(), data[0]); } } else { System.err.println(Localization.lang("The output option depends on a valid import option.")); @@ -454,7 +434,7 @@ private void exportFile(List loaded, String[] data) { BibDatabaseContext databaseContext = pr.getDatabaseContext(); databaseContext.setDatabaseFile(theFile); Globals.prefs.fileDirForDatabase = databaseContext - .getFileDirectories(Globals.prefs.getFileDirectoryPreferences()); + .getFileDirectories(Globals.prefs.getFilePreferences()); System.out.println(Localization.lang("Exporting") + ": " + data[0]); Optional exporter = Globals.exportFactory.getExporterByName(data[1]); if (!exporter.isPresent()) { diff --git a/src/main/java/org/jabref/gui/AbstractViewModel.java b/src/main/java/org/jabref/gui/AbstractViewModel.java index f04b77cd7a8..ff9f1fca633 100644 --- a/src/main/java/org/jabref/gui/AbstractViewModel.java +++ b/src/main/java/org/jabref/gui/AbstractViewModel.java @@ -1,5 +1,5 @@ package org.jabref.gui; public class AbstractViewModel { - + //empty } diff --git a/src/main/java/org/jabref/gui/Base.css b/src/main/java/org/jabref/gui/Base.css index 6ec8d5c9482..ed4bbd29f96 100644 --- a/src/main/java/org/jabref/gui/Base.css +++ b/src/main/java/org/jabref/gui/Base.css @@ -247,6 +247,7 @@ -fx-underline: false; -fx-border-style: null; -fx-border-color: null; + -fx-text-fill: -jr-theme; } .hyperlink:visited { @@ -390,12 +391,32 @@ } .check-box > .box > .mark { - -fx-background-color: white; + -fx-background-color: -fx-control-inner-background; -fx-padding: 0.2em 0.2em 0.2em 0.2em; -fx-shape: "M6.61 11.89L3.5 8.78 2.44 9.84 6.61 14l8.95-8.95L14.5 4z"; -fx-stroke-width: 5; } +.radio-button > .radio { + -fx-background-radius: 1.0em; /* large value to make sure this remains circular */ + -fx-padding: 0.35em; /* padding from outside edge to the inner dot */ + -fx-background-color: rgba(0, 0, 0, 0.54), -fx-control-inner-background; + -fx-background-insets: 0, 2px; +} + +.radio-button:selected > .radio { + -fx-background-color: -jr-checked, -fx-background; +} + +.radio-button > .radio > .dot { + -fx-padding: 0.25em; /* radius of the inner dot when selected */ + -fx-background-insets: 0; +} + +.radio-button:selected > .radio > .dot { + -fx-background-color: -jr-checked; +} + .menu-bar { -fx-background-color: -jr-menu-background; -fx-background-insets: 0; @@ -667,6 +688,7 @@ -fx-background-radius: 0em; } + .scroll-bar:horizontal .thumb, .scroll-bar:vertical .thumb { -fx-background-color: -jr-scrollbar-thumb; @@ -714,7 +736,8 @@ .tree-view:hover .scroll-bar, .table-view:hover .scroll-bar, .tree-table-view:hover .scroll-bar, -.text-input:hover .scroll-bar { +.text-input:hover .scroll-bar, +.scroll-pane:hover .scroll-bar { -fx-opacity: 1; } diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/BasePanel.java index c83ec818dff..324b6805ccd 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/BasePanel.java @@ -4,22 +4,17 @@ import java.io.IOException; import java.io.StringReader; import java.lang.reflect.InvocationTargetException; -import java.nio.charset.Charset; -import java.nio.charset.UnsupportedCharsetException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import javax.swing.JOptionPane; -import javax.swing.JTextArea; import javax.swing.SwingUtilities; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; @@ -49,6 +44,7 @@ import org.jabref.gui.collab.FileUpdatePanel; import org.jabref.gui.contentselector.ContentSelectorDialog; import org.jabref.gui.desktop.JabRefDesktop; +import org.jabref.gui.edit.ReplaceStringAction; import org.jabref.gui.entryeditor.EntryEditor; import org.jabref.gui.exporter.SaveDatabaseAction; import org.jabref.gui.externalfiles.FindFullTextAction; @@ -74,27 +70,17 @@ import org.jabref.gui.undo.UndoableChangeType; import org.jabref.gui.undo.UndoableFieldChange; import org.jabref.gui.undo.UndoableInsertEntry; -import org.jabref.gui.undo.UndoableKeyChange; import org.jabref.gui.undo.UndoableRemoveEntry; import org.jabref.gui.util.DefaultTaskExecutor; -import org.jabref.gui.util.FileDialogConfiguration; import org.jabref.gui.worker.CitationStyleToClipboardWorker; import org.jabref.gui.worker.SendAsEMailAction; -import org.jabref.logic.bibtexkeypattern.BibtexKeyGenerator; import org.jabref.logic.citationstyle.CitationStyleCache; import org.jabref.logic.citationstyle.CitationStyleOutputFormat; -import org.jabref.logic.exporter.BibtexDatabaseWriter; -import org.jabref.logic.exporter.FileSaveSession; -import org.jabref.logic.exporter.SaveException; -import org.jabref.logic.exporter.SavePreferences; -import org.jabref.logic.exporter.SaveSession; -import org.jabref.logic.l10n.Encodings; import org.jabref.logic.l10n.Localization; import org.jabref.logic.layout.Layout; import org.jabref.logic.layout.LayoutHelper; import org.jabref.logic.pdf.FileAnnotationCache; import org.jabref.logic.search.SearchQuery; -import org.jabref.logic.util.StandardFileType; import org.jabref.logic.util.UpdateField; import org.jabref.logic.util.io.FileFinder; import org.jabref.logic.util.io.FileFinders; @@ -110,7 +96,6 @@ import org.jabref.model.database.shared.DatabaseLocation; import org.jabref.model.database.shared.DatabaseSynchronizer; import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.EntryType; import org.jabref.model.entry.FieldName; import org.jabref.model.entry.InternalBibtexFields; import org.jabref.model.entry.LinkedFile; @@ -118,13 +103,10 @@ import org.jabref.model.entry.event.EntryEventSource; import org.jabref.model.entry.specialfields.SpecialField; import org.jabref.model.entry.specialfields.SpecialFieldValue; -import org.jabref.model.strings.StringUtil; import org.jabref.preferences.JabRefPreferences; import org.jabref.preferences.PreviewPreferences; import com.google.common.eventbus.Subscribe; -import com.jgoodies.forms.builder.FormBuilder; -import com.jgoodies.forms.layout.FormLayout; import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; import org.slf4j.Logger; @@ -193,7 +175,7 @@ public BasePanel(JabRefFrame frame, BasePanelPreferences preferences, BibDatabas this.tableModel = new MainTableDataModel(getBibDatabaseContext()); citationStyleCache = new CitationStyleCache(bibDatabaseContext); - annotationCache = new FileAnnotationCache(bibDatabaseContext, Globals.prefs.getFileDirectoryPreferences()); + annotationCache = new FileAnnotationCache(bibDatabaseContext, Globals.prefs.getFilePreferences()); setupMainPanel(); @@ -222,7 +204,7 @@ public BasePanel(JabRefFrame frame, BasePanelPreferences preferences, BibDatabas this.getDatabase().registerListener(new UpdateTimestampListener(Globals.prefs)); - this.entryEditor = new EntryEditor(this, preferences.getEntryEditorPreferences(), Globals.getFileUpdateMonitor(), dialogService, externalFileTypes); + this.entryEditor = new EntryEditor(this, preferences.getEntryEditorPreferences(), Globals.getFileUpdateMonitor(), dialogService, externalFileTypes, Globals.TASK_EXECUTOR); this.preview = new PreviewPanel(this, getBibDatabaseContext(), preferences.getKeyBindings(), preferences.getPreviewPreferences(), dialogService, externalFileTypes); frame().getGlobalSearchBar().getSearchQueryHighlightObservable().addSearchListener(preview); @@ -300,18 +282,18 @@ private void setupActions() { actions.put(Actions.EDIT, this::showAndEdit); // The action for saving a database. - actions.put(Actions.SAVE, saveAction); + actions.put(Actions.SAVE, saveAction::save); actions.put(Actions.SAVE_AS, saveAction::saveAs); - actions.put(Actions.SAVE_SELECTED_AS_PLAIN, new SaveSelectedAction(SavePreferences.DatabaseSaveType.PLAIN_BIBTEX)); + actions.put(Actions.SAVE_SELECTED_AS_PLAIN, saveAction::saveSelectedAsPlain); // The action for copying selected entries. - actions.put(Actions.COPY, mainTable::copy); + actions.put(Actions.COPY, this::copy); actions.put(Actions.PRINT_PREVIEW, new PrintPreviewAction()); - actions.put(Actions.CUT, mainTable::cut); + actions.put(Actions.CUT, this::cut); actions.put(Actions.DELETE, () -> delete(false)); @@ -321,7 +303,7 @@ private void setupActions() { // This allows you to (a) paste entire bibtex entries from a text editor, web browser, etc // (b) copy and paste entries between multiple instances of JabRef (since // only the text representation seems to get as far as the X clipboard, at least on my system) - actions.put(Actions.PASTE, mainTable::paste); + actions.put(Actions.PASTE, this::paste); actions.put(Actions.SELECT_ALL, mainTable.getSelectionModel()::selectAll); @@ -370,7 +352,7 @@ private void setupActions() { actions.put(Actions.OPEN_EXTERNAL_FILE, this::openExternalFile); actions.put(Actions.OPEN_FOLDER, () -> JabRefExecutorService.INSTANCE.execute(() -> { - final List files = FileUtil.getListOfLinkedFiles(mainTable.getSelectedEntries(), bibDatabaseContext.getFileDirectoriesAsPaths(Globals.prefs.getFileDirectoryPreferences())); + final List files = FileUtil.getListOfLinkedFiles(mainTable.getSelectedEntries(), bibDatabaseContext.getFileDirectoriesAsPaths(Globals.prefs.getFilePreferences())); for (final Path f : files) { try { JabRefDesktop.openFolderAndSelectFile(f.toAbsolutePath()); @@ -672,87 +654,9 @@ public void runCommand(final Actions command) { } } - /** - * FIXME: high code duplication with {@link SaveDatabaseAction#saveDatabase(File, boolean, Charset)} - */ - private boolean saveDatabase(File file, boolean selectedOnly, Charset encoding, - SavePreferences.DatabaseSaveType saveType) - throws SaveException { - SaveSession session; - final String SAVE_DATABASE = Localization.lang("Save library"); - try { - SavePreferences prefs = Globals.prefs.loadForSaveFromPreferences() - .withEncoding(encoding) - .withSaveType(saveType); - - BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter<>( - FileSaveSession::new); - if (selectedOnly) { - session = databaseWriter.savePartOfDatabase(bibDatabaseContext, mainTable.getSelectedEntries(), prefs); - } else { - session = databaseWriter.saveDatabase(bibDatabaseContext, prefs); - } - - registerUndoableChanges(session); - } - // FIXME: not sure if this is really thrown anywhere - catch (UnsupportedCharsetException ex) { - frame.getDialogService().showErrorDialogAndWait(Localization.lang("Save library"), Localization.lang("Could not save file.") - + Localization.lang("Character encoding '%0' is not supported.", encoding.displayName())); - throw new SaveException("rt"); - } catch (SaveException ex) { - if (ex.specificEntry()) { - // Error occurred during processing of the entry. Highlight it: - clearAndSelect(ex.getEntry()); - showAndEdit(ex.getEntry()); - } else { - LOGGER.warn("Could not save", ex); - } - - dialogService.showErrorDialogAndWait(SAVE_DATABASE, Localization.lang("Could not save file."), ex); - throw new SaveException("rt"); - } - - boolean commit = true; - if (!session.getWriter().couldEncodeAll()) { - FormBuilder builder = FormBuilder.create() - .layout(new FormLayout("left:pref, 4dlu, fill:pref", "pref, 4dlu, pref")); - JTextArea ta = new JTextArea(session.getWriter().getProblemCharacters()); - ta.setEditable(false); - builder.add(Localization.lang("The chosen encoding '%0' could not encode the following characters:", session.getEncoding().displayName())).xy(1, 1); - builder.add(ta).xy(3, 1); - builder.add(Localization.lang("What do you want to do?")).xy(1, 3); - String tryDiff = Localization.lang("Try different encoding"); - int answer = JOptionPane.showOptionDialog(null, builder.getPanel(), SAVE_DATABASE, JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, new String[] {Localization.lang("Save"), tryDiff, Localization.lang("Cancel")}, tryDiff); - - if (answer == JOptionPane.NO_OPTION) { - - // The user wants to use another encoding. - Object choice = JOptionPane.showInputDialog(null, Localization.lang("Select encoding"), SAVE_DATABASE, JOptionPane.QUESTION_MESSAGE, null, Encodings.ENCODINGS_DISPLAYNAMES, encoding); - if (choice == null) { - commit = false; - } else { - Charset newEncoding = Charset.forName((String) choice); - return saveDatabase(file, selectedOnly, newEncoding, saveType); - } - } else if (answer == JOptionPane.CANCEL_OPTION) { - commit = false; - } - } - - if (commit) { - session.commit(file.toPath()); - this.bibDatabaseContext.getMetaData().setEncoding(encoding); // Make sure to remember which encoding we used. - } else { - session.cancel(); - } - - return commit; - } - - public void registerUndoableChanges(SaveSession session) { + public void registerUndoableChanges(List changes) { NamedCompound ce = new NamedCompound(Localization.lang("Save actions")); - for (FieldChange change : session.getFieldChanges()) { + for (FieldChange change : changes) { ce.addEdit(new UndoableFieldChange(change)); } ce.end(); @@ -761,58 +665,6 @@ public void registerUndoableChanges(SaveSession session) { } } - /** - * This method is called from JabRefFrame when the user wants to create a new entry. If the argument is null, the - * user is prompted for an entry type. - * - * @param type The type of the entry to create. - * @return The newly created BibEntry or null the operation was canceled by the user. - */ - public BibEntry newEntry(EntryType type) { - EntryType actualType = type; - if (actualType == null) { - // Find out what type is wanted. - final EntryTypeDialog etd = new EntryTypeDialog(frame); - // We want to center the dialog, to make it look nicer. - etd.setVisible(true); - actualType = etd.getChoice(); - } - if (actualType != null) { // Only if the dialog was not canceled. - final BibEntry be = new BibEntry(actualType.getName()); - try { - bibDatabaseContext.getDatabase().insertEntry(be); - // Set owner/timestamp if options are enabled: - List list = new ArrayList<>(); - list.add(be); - UpdateField.setAutomaticFields(list, true, true, Globals.prefs.getUpdateFieldPreferences()); - - // Create an UndoableInsertEntry object. - getUndoManager().addEdit(new UndoableInsertEntry(bibDatabaseContext.getDatabase(), be)); - output(Localization.lang("Added new '%0' entry.", actualType.getName().toLowerCase(Locale.ROOT))); - - // We are going to select the new entry. Before that, make sure that we are in - // show-entry mode. If we aren't already in that mode, enter the WILL_SHOW_EDITOR - // mode which makes sure the selection will trigger display of the entry editor - // and adjustment of the splitter. - if (mode != BasePanelMode.SHOWING_EDITOR) { - mode = BasePanelMode.WILL_SHOW_EDITOR; - } - - clearAndSelect(be); - - // The database just changed. - markBaseChanged(); - - this.showAndEdit(be); - - return be; - } catch (KeyCollisionException ex) { - LOGGER.info(ex.getMessage(), ex); - } - } - return null; - } - /** * This method is called from JabRefFrame when the user wants to create a new entry. * @@ -822,10 +674,10 @@ public void insertEntry(final BibEntry bibEntry) { if (bibEntry != null) { try { bibDatabaseContext.getDatabase().insertEntry(bibEntry); - if (Globals.prefs.getBoolean(JabRefPreferences.USE_OWNER)) { - // Set owner field to default value - UpdateField.setAutomaticFields(bibEntry, true, true, Globals.prefs.getUpdateFieldPreferences()); - } + + // Set owner and timestamp + UpdateField.setAutomaticFields(bibEntry, true, true, Globals.prefs.getUpdateFieldPreferences()); + // Create an UndoableInsertEntry object. getUndoManager().addEdit(new UndoableInsertEntry(bibDatabaseContext.getDatabase(), bibEntry)); output(Localization.lang("Added new '%0' entry.", bibEntry.getType())); @@ -1261,30 +1113,6 @@ public boolean showDeleteConfirmationDialog(int numberOfEntries) { } } - /** - * If the relevant option is set, autogenerate keys for all entries that are lacking keys. - */ - public void autoGenerateKeysBeforeSaving() { - if (Globals.prefs.getBoolean(JabRefPreferences.GENERATE_KEYS_BEFORE_SAVING)) { - NamedCompound ce = new NamedCompound(Localization.lang("Autogenerate BibTeX keys")); - - BibtexKeyGenerator keyGenerator = new BibtexKeyGenerator(bibDatabaseContext, Globals.prefs.getBibtexKeyPatternPreferences()); - for (BibEntry bes : bibDatabaseContext.getDatabase().getEntries()) { - Optional oldKey = bes.getCiteKeyOptional(); - if (StringUtil.isBlank(oldKey)) { - Optional change = keyGenerator.generateAndSetKey(bes); - change.ifPresent(fieldChange -> ce.addEdit(new UndoableKeyChange(fieldChange))); - } - } - - // Store undo information, if any: - if (ce.hasEdits()) { - ce.end(); - getUndoManager().addEdit(ce); - } - } - } - /** * Depending on whether a preview or an entry editor is showing, save the current divider location in the correct preference setting. */ @@ -1433,6 +1261,18 @@ public Path getTempFile() { return changeMonitor.map(DatabaseChangeMonitor::getTempFile).orElse(null); } + public void copy() { + mainTable.copy(); + } + + public void paste() { + mainTable.paste(); + } + + public void cut() { + mainTable.cut(); + } + private static class SearchAndOpenFile { private final BibEntry entry; @@ -1452,7 +1292,7 @@ public void searchAndOpen() { } final Set types = ExternalFileTypes.getInstance().getExternalFileTypeSelection(); - final List dirs = basePanel.getBibDatabaseContext().getFileDirectoriesAsPaths(Globals.prefs.getFileDirectoryPreferences()); + final List dirs = basePanel.getBibDatabaseContext().getFileDirectoriesAsPaths(Globals.prefs.getFilePreferences()); final List extensions = types.stream().map(ExternalFileType::getExtension).collect(Collectors.toList()); // Run the search operation: @@ -1639,30 +1479,4 @@ public void action() { preview.print(); } } - - private class SaveSelectedAction implements BaseAction { - - private final SavePreferences.DatabaseSaveType saveType; - - public SaveSelectedAction(SavePreferences.DatabaseSaveType saveType) { - this.saveType = saveType; - } - - @Override - public void action() throws SaveException { - FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .withDefaultExtension(StandardFileType.BIBTEX_DB) - .addExtensionFilter(String.format("%1s %2s", "BibTex", Localization.lang("Library")), StandardFileType.BIBTEX_DB) - .withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)) - .build(); - - Optional chosenFile = dialogService.showFileSaveDialog(fileDialogConfiguration); - if (chosenFile.isPresent()) { - Path path = chosenFile.get(); - saveDatabase(path.toFile(), true, Globals.prefs.getDefaultEncoding(), saveType); - frame.getFileHistory().newFile(path.toString()); - frame.output(Localization.lang("Saved selected to '%0'.", path.toString())); - } - } - } } diff --git a/src/main/java/org/jabref/gui/EntryType.fxml b/src/main/java/org/jabref/gui/EntryType.fxml new file mode 100644 index 00000000000..e08551310df --- /dev/null +++ b/src/main/java/org/jabref/gui/EntryType.fxml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/jabref/gui/EntryTypeDialog.java b/src/main/java/org/jabref/gui/EntryTypeDialog.java deleted file mode 100644 index 93cc5a73a3d..00000000000 --- a/src/main/java/org/jabref/gui/EntryTypeDialog.java +++ /dev/null @@ -1,366 +0,0 @@ -package org.jabref.gui; - -import java.awt.BorderLayout; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutionException; - -import javax.swing.AbstractAction; -import javax.swing.BorderFactory; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JTextField; -import javax.swing.SwingUtilities; -import javax.swing.SwingWorker; - -import org.jabref.Globals; -import org.jabref.gui.importer.ImportInspectionDialog; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.gui.util.DefaultTaskExecutor; -import org.jabref.logic.bibtex.DuplicateCheck; -import org.jabref.logic.bibtexkeypattern.BibtexKeyGenerator; -import org.jabref.logic.importer.FetcherException; -import org.jabref.logic.importer.IdBasedFetcher; -import org.jabref.logic.importer.WebFetchers; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.EntryTypes; -import org.jabref.model.database.BibDatabaseMode; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.BiblatexEntryTypes; -import org.jabref.model.entry.BibtexEntryTypes; -import org.jabref.model.entry.EntryType; -import org.jabref.model.entry.IEEETranEntryTypes; -import org.jabref.preferences.JabRefPreferences; - -import com.jgoodies.forms.builder.ButtonBarBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Dialog that prompts the user to choose a type for an entry. - * Returns null if canceled. - */ -public class EntryTypeDialog extends JabRefDialog implements ActionListener { - - private static final Logger LOGGER = LoggerFactory.getLogger(EntryTypeDialog.class); - private static final int COLUMN = 3; - private final JabRefFrame frame; - private final CancelAction cancelAction = new CancelAction(); - private EntryType type; - private SwingWorker, Void> fetcherWorker = new FetcherWorker(); - private JButton generateButton; - private JTextField idTextField; - private JComboBox comboBox; - - public EntryTypeDialog(JabRefFrame frame) { - // modal dialog - super(true, EntryTypeDialog.class); - - this.frame = frame; - - setTitle(Localization.lang("Select entry type")); - - addWindowListener(new WindowAdapter() { - - @Override - public void windowClosing(WindowEvent e) { - cancelAction.actionPerformed(null); - } - }); - - getContentPane().setLayout(new BorderLayout()); - getContentPane().add(createCancelButtonBarPanel(), BorderLayout.SOUTH); - getContentPane().add(createEntryGroupsPanel(), BorderLayout.CENTER); - - pack(); - setResizable(false); - } - - private JPanel createEntryGroupsPanel() { - JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); - - if (frame.getCurrentBasePanel().getBibDatabaseContext().isBiblatexMode()) { - panel.add(createEntryGroupPanel("biblatex", BiblatexEntryTypes.ALL)); - - List customTypes = EntryTypes.getAllCustomTypes(BibDatabaseMode.BIBLATEX); - if (!customTypes.isEmpty()) { - panel.add(createEntryGroupPanel(Localization.lang("Custom"), customTypes)); - } - } else { - panel.add(createEntryGroupPanel("BibTeX", BibtexEntryTypes.ALL)); - panel.add(createEntryGroupPanel("IEEETran", IEEETranEntryTypes.ALL)); - - List customTypes = EntryTypes.getAllCustomTypes(BibDatabaseMode.BIBTEX); - if (!customTypes.isEmpty()) { - panel.add(createEntryGroupPanel(Localization.lang("Custom"), customTypes)); - } - } - panel.add(createIdFetcherPanel()); - - return panel; - } - - private JPanel createCancelButtonBarPanel() { - JButton cancel = new JButton(Localization.lang("Cancel")); - cancel.addActionListener(this); - - // Make ESC close dialog, equivalent to clicking Cancel. - cancel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE), "close"); - cancel.getActionMap().put("close", cancelAction); - - JPanel buttons = new JPanel(); - ButtonBarBuilder bb = new ButtonBarBuilder(buttons); - bb.addGlue(); - bb.addButton(cancel); - bb.addGlue(); - return buttons; - } - - private JPanel createEntryGroupPanel(String groupTitle, Collection entries) { - JPanel panel = new JPanel(); - GridBagLayout bagLayout = new GridBagLayout(); - panel.setLayout(bagLayout); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.anchor = GridBagConstraints.WEST; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.insets = new Insets(4, 4, 4, 4); - // column count - int col = 0; - - for (EntryType entryType : entries) { - TypeButton entryButton = new TypeButton(entryType.getName(), entryType); - entryButton.addActionListener(this); - // Check if we should finish the row. - col++; - if (col == EntryTypeDialog.COLUMN) { - col = 0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - } else { - constraints.gridwidth = 1; - } - bagLayout.setConstraints(entryButton, constraints); - panel.add(entryButton); - } - panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), groupTitle)); - - return panel; - } - - private JPanel createIdFetcherPanel() { - JLabel fetcherLabel = new JLabel(Localization.lang("ID type")); - JLabel idLabel = new JLabel(Localization.lang("ID")); - generateButton = new JButton(Localization.lang("Generate")); - idTextField = new JTextField(""); - comboBox = new JComboBox<>(); - - WebFetchers.getIdBasedFetchers(Globals.prefs.getImportFormatPreferences()).forEach(fetcher -> comboBox.addItem(fetcher.getName())); - - comboBox.setSelectedItem(Globals.prefs.get(JabRefPreferences.ID_ENTRY_GENERATOR)); - - generateButton.addActionListener(action -> { - fetcherWorker.execute(); - }); - - comboBox.addActionListener(e -> { - idTextField.requestFocus(); - idTextField.selectAll(); - }); - - idTextField.addActionListener(event -> fetcherWorker.execute()); - - JPanel jPanel = new JPanel(); - - GridBagConstraints constraints = new GridBagConstraints(); - constraints.insets = new Insets(4, 4, 4, 4); - - GridBagLayout layout = new GridBagLayout(); - jPanel.setLayout(layout); - - constraints.fill = GridBagConstraints.HORIZONTAL; - - constraints.gridx = 0; - constraints.gridy = 0; - constraints.weightx = 1; - jPanel.add(fetcherLabel, constraints); - - constraints.gridx = 1; - constraints.gridy = 0; - constraints.weightx = 2; - jPanel.add(comboBox, constraints); - - constraints.gridx = 0; - constraints.gridy = 1; - constraints.weightx = 1; - jPanel.add(idLabel, constraints); - - constraints.gridx = 1; - constraints.gridy = 1; - constraints.weightx = 2; - jPanel.add(idTextField, constraints); - - constraints.gridy = 2; - constraints.gridx = 0; - constraints.gridwidth = 2; - constraints.fill = GridBagConstraints.NONE; - jPanel.add(generateButton, constraints); - - jPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), Localization.lang("ID-based entry generator"))); - - SwingUtilities.invokeLater(() -> idTextField.requestFocus()); - - return jPanel; - } - - private void stopFetching() { - if (fetcherWorker.getState() == SwingWorker.StateValue.STARTED) { - fetcherWorker.cancel(true); - } - } - - @Override - public void actionPerformed(ActionEvent e) { - if (e.getSource() instanceof TypeButton) { - type = ((TypeButton) e.getSource()).getType(); - } - stopFetching(); - dispose(); - } - - public EntryType getChoice() { - return type; - } - - static class TypeButton extends JButton implements Comparable { - - private final EntryType type; - - TypeButton(String label, EntryType type) { - super(label); - this.type = type; - } - - @Override - public int compareTo(TypeButton o) { - return type.getName().compareTo(o.type.getName()); - } - - public EntryType getType() { - return type; - } - } - - class CancelAction extends AbstractAction { - - public CancelAction() { - super("Cancel"); - } - - @Override - public void actionPerformed(ActionEvent e) { - stopFetching(); - dispose(); - } - } - - private class FetcherWorker extends SwingWorker, Void> { - - private boolean fetcherException = false; - private String fetcherExceptionMessage = ""; - private IdBasedFetcher fetcher = null; - private String searchID = ""; - - @Override - protected Optional doInBackground() throws Exception { - Optional bibEntry = Optional.empty(); - SwingUtilities.invokeLater(() -> { - generateButton.setEnabled(false); - generateButton.setText(Localization.lang("Searching...")); - }); - - Globals.prefs.put(JabRefPreferences.ID_ENTRY_GENERATOR, String.valueOf(comboBox.getSelectedItem())); - fetcher = WebFetchers.getIdBasedFetchers(Globals.prefs.getImportFormatPreferences()).get(comboBox.getSelectedIndex()); - searchID = idTextField.getText(); - if (!searchID.isEmpty()) { - try { - bibEntry = fetcher.performSearchById(searchID); - } catch (FetcherException e) { - LOGGER.error(e.getMessage(), e); - fetcherException = true; - fetcherExceptionMessage = e.getMessage(); - } - } - return bibEntry; - } - - @Override - protected void done() { - try { - Optional result = get(); - if (result.isPresent()) { - final BibEntry bibEntry = result.get(); - if ((DuplicateCheck.containsDuplicate(frame.getCurrentBasePanel().getDatabase(), bibEntry, frame.getCurrentBasePanel().getBibDatabaseContext().getMode()).isPresent())) { - //If there are duplicates starts ImportInspectionDialog - final BasePanel panel = frame.getCurrentBasePanel(); - - ImportInspectionDialog diag = new ImportInspectionDialog(frame, panel, Localization.lang("Import"), false); - diag.addEntries(Collections.singletonList(bibEntry)); - diag.entryListComplete(); - diag.setVisible(true); - diag.toFront(); - } else { - // Regenerate CiteKey of imported BibEntry - new BibtexKeyGenerator(frame.getCurrentBasePanel().getBibDatabaseContext(), Globals.prefs.getBibtexKeyPatternPreferences()).generateAndSetKey(bibEntry); - // Update Timestamps - if (Globals.prefs.getTimestampPreferences().includeCreatedTimestamp()) { - bibEntry.setField(Globals.prefs.getTimestampPreferences().getTimestampField(), Globals.prefs.getTimestampPreferences().now()); - } - DefaultTaskExecutor.runInJavaFXThread(() -> frame.getCurrentBasePanel().insertEntry(bibEntry)); - } - - dispose(); - } else if (searchID.trim().isEmpty()) { - DefaultTaskExecutor.runInJavaFXThread(() -> { - frame.getDialogService().showWarningDialogAndWait( - Localization.lang("Empty search ID"), - Localization.lang("The given search ID was empty.")); - }); - } else if (!fetcherException) { - DefaultTaskExecutor.runInJavaFXThread(() -> { - frame.getDialogService().showErrorDialogAndWait( - Localization.lang("No files found."), - Localization.lang("Fetcher '%0' did not find an entry for id '%1'.", fetcher.getName(), searchID) + "\n" + fetcherExceptionMessage); - }); - } else { - DefaultTaskExecutor.runInJavaFXThread(() -> { - frame.getDialogService().showErrorDialogAndWait( - Localization.lang("Error"), - Localization.lang("Error while fetching from %0", fetcher.getName()) + "." + "\n" + fetcherExceptionMessage); - }); - } - fetcherWorker = new FetcherWorker(); - SwingUtilities.invokeLater(() -> { - idTextField.requestFocus(); - idTextField.selectAll(); - generateButton.setText(Localization.lang("Generate")); - generateButton.setEnabled(true); - }); - } catch (ExecutionException | InterruptedException e) { - LOGGER.error(String.format("Exception during fetching when using fetcher '%s' with entry id '%s'.", searchID, fetcher.getName()), e); - } - } - } -} diff --git a/src/main/java/org/jabref/gui/EntryTypeView.java b/src/main/java/org/jabref/gui/EntryTypeView.java new file mode 100644 index 00000000000..733eb80ef29 --- /dev/null +++ b/src/main/java/org/jabref/gui/EntryTypeView.java @@ -0,0 +1,168 @@ +package org.jabref.gui; + +import java.util.Collection; +import java.util.List; + +import javafx.event.Event; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.ComboBox; +import javafx.scene.control.TextField; +import javafx.scene.control.TitledPane; +import javafx.scene.layout.FlowPane; + +import org.jabref.gui.util.BaseDialog; +import org.jabref.gui.util.ControlHelper; +import org.jabref.gui.util.ViewModelListCellFactory; +import org.jabref.logic.importer.IdBasedFetcher; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.EntryTypes; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BiblatexEntryTypes; +import org.jabref.model.entry.BibtexEntryTypes; +import org.jabref.model.entry.EntryType; +import org.jabref.model.entry.IEEETranEntryTypes; +import org.jabref.preferences.JabRefPreferences; + +import com.airhacks.afterburner.views.ViewLoader; +import org.fxmisc.easybind.EasyBind; + +/** + * Dialog that prompts the user to choose a type for an entry. + * Returns null if canceled. + */ +public class EntryTypeView extends BaseDialog { + + @FXML private ButtonType generateButton; + @FXML private TextField idTextField; + @FXML private ComboBox idBasedFetchers; + @FXML private FlowPane biblatexPane; + @FXML private FlowPane bibTexPane; + @FXML private FlowPane ieeetranPane; + @FXML private FlowPane customPane; + @FXML private TitledPane biblatexTitlePane; + @FXML private TitledPane bibTexTitlePane; + @FXML private TitledPane ieeeTranTitlePane; + @FXML private TitledPane customTitlePane; + + private final BasePanel basePanel; + private final DialogService dialogService; + private final JabRefPreferences prefs; + + private EntryType type; + private EntryTypeViewModel viewModel; + + public EntryTypeView(BasePanel basePanel, DialogService dialogService, JabRefPreferences preferences) { + this.basePanel = basePanel; + this.dialogService = dialogService; + this.prefs = preferences; + + this.setTitle(Localization.lang("Select entry type")); + ViewLoader.view(this) + .load() + .setAsDialogPane(this); + + ControlHelper.setAction(generateButton, this.getDialogPane(), event -> viewModel.runFetcherWorker()); + + setResultConverter(button -> { + //The buttonType will always be cancel, even if we pressed one of the entry type buttons + return type; + }); + + Button btnGenerate = (Button) this.getDialogPane().lookupButton(generateButton); + + btnGenerate.textProperty().bind(EasyBind.map(viewModel.searchingProperty(), searching -> (searching) ? Localization.lang("Searching...") : Localization.lang("Generate"))); + btnGenerate.disableProperty().bind(viewModel.searchingProperty()); + + EasyBind.subscribe(viewModel.searchSuccesfulProperty(), value -> { + if (value) { + setEntryTypeForReturnAndClose(null); + } + }); + + } + + private void addEntriesToPane(FlowPane pane, Collection entries) { + + for (EntryType entryType : entries) { + Button entryButton = new Button(entryType.getName()); + entryButton.setUserData(entryType); + entryButton.setOnAction(event -> setEntryTypeForReturnAndClose(entryType)); + pane.getChildren().add(entryButton); + } + } + + @FXML + public void initialize() { + viewModel = new EntryTypeViewModel(prefs, basePanel, dialogService); + + idBasedFetchers.itemsProperty().bind(viewModel.fetcherItemsProperty()); + idTextField.textProperty().bindBidirectional(viewModel.idTextProperty()); + idBasedFetchers.valueProperty().bindBidirectional(viewModel.selectedItemProperty()); + + EasyBind.subscribe(viewModel.getFocusAndSelectAllProperty(), evt -> { + if (evt) { + idTextField.requestFocus(); + idTextField.selectAll(); + } + }); + + new ViewModelListCellFactory().withText(item -> item.getName()).install(idBasedFetchers); + + //we set the managed property so that they will only be rendered when they are visble so that the Nodes only take the space when visible + //avoids removing and adding from the scence graph + bibTexTitlePane.managedProperty().bind(bibTexTitlePane.visibleProperty()); + ieeeTranTitlePane.managedProperty().bind(ieeeTranTitlePane.visibleProperty()); + biblatexTitlePane.managedProperty().bind(biblatexTitlePane.visibleProperty()); + customTitlePane.managedProperty().bind(customTitlePane.visibleProperty()); + + if (basePanel.getBibDatabaseContext().isBiblatexMode()) { + addEntriesToPane(biblatexPane, BiblatexEntryTypes.ALL); + + bibTexTitlePane.setVisible(false); + ieeeTranTitlePane.setVisible(false); + + List customTypes = EntryTypes.getAllCustomTypes(BibDatabaseMode.BIBLATEX); + if (customTypes.isEmpty()) { + customTitlePane.setVisible(false); + } else { + addEntriesToPane(customPane, customTypes); + } + + } else { + biblatexTitlePane.setVisible(false); + addEntriesToPane(bibTexPane, BibtexEntryTypes.ALL); + addEntriesToPane(ieeetranPane, IEEETranEntryTypes.ALL); + + List customTypes = EntryTypes.getAllCustomTypes(BibDatabaseMode.BIBTEX); + if (customTypes.isEmpty()) { + customTitlePane.setVisible(false); + } else { + addEntriesToPane(customPane, customTypes); + } + } + + } + + public EntryType getChoice() { + return type; + } + + @FXML + private void runFetcherWorker(Event event) { + viewModel.runFetcherWorker(); + } + + @FXML + private void focusTextField(Event event) { + idTextField.requestFocus(); + idTextField.selectAll(); + } + + private void setEntryTypeForReturnAndClose(EntryType entryType) { + type = entryType; + viewModel.stopFetching(); + this.close(); + } +} diff --git a/src/main/java/org/jabref/gui/EntryTypeViewModel.java b/src/main/java/org/jabref/gui/EntryTypeViewModel.java new file mode 100644 index 00000000000..d023aa48900 --- /dev/null +++ b/src/main/java/org/jabref/gui/EntryTypeViewModel.java @@ -0,0 +1,166 @@ +package org.jabref.gui; + +import java.util.Arrays; +import java.util.Optional; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ListProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; +import javafx.concurrent.Task; +import javafx.concurrent.Worker; + +import org.jabref.gui.importer.ImportInspectionDialog; +import org.jabref.logic.bibtex.DuplicateCheck; +import org.jabref.logic.bibtexkeypattern.BibtexKeyGenerator; +import org.jabref.logic.importer.FetcherException; +import org.jabref.logic.importer.IdBasedFetcher; +import org.jabref.logic.importer.WebFetchers; +import org.jabref.logic.importer.fetcher.DoiFetcher; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.strings.StringUtil; +import org.jabref.preferences.JabRefPreferences; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EntryTypeViewModel { + + private static final Logger LOGGER = LoggerFactory.getLogger(EntryTypeViewModel.class); + + private final JabRefPreferences prefs; + private final BooleanProperty searchingProperty = new SimpleBooleanProperty(); + private final BooleanProperty searchSuccesfulProperty = new SimpleBooleanProperty(); + private final ObjectProperty selectedItemProperty = new SimpleObjectProperty<>(); + private final ListProperty fetchers = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final StringProperty idText = new SimpleStringProperty(); + private final BooleanProperty focusAndSelectAllProperty = new SimpleBooleanProperty(); + private Task> fetcherWorker = new FetcherWorker(); + private final BasePanel basePanel; + private final DialogService dialogService; + + public EntryTypeViewModel(JabRefPreferences preferences, BasePanel basePanel, DialogService dialogService) { + this.basePanel = basePanel; + this.prefs = preferences; + this.dialogService = dialogService; + fetchers.addAll(WebFetchers.getIdBasedFetchers(preferences.getImportFormatPreferences())); + selectedItemProperty.setValue(getLastSelectedFetcher()); + + } + + public BooleanProperty searchSuccesfulProperty() { + return searchSuccesfulProperty; + } + + public BooleanProperty searchingProperty() { + return searchingProperty; + } + + public ObjectProperty selectedItemProperty() { + return selectedItemProperty; + } + + public StringProperty idTextProperty() { + return idText; + } + + public BooleanProperty getFocusAndSelectAllProperty() { + return focusAndSelectAllProperty; + } + + public void storeSelectedFetcher() { + prefs.setIdBasedFetcherForEntryGenerator(selectedItemProperty.getValue().getName()); + } + + private IdBasedFetcher getLastSelectedFetcher() { + return fetchers.stream().filter(fetcher -> fetcher.getName().equals(prefs.getIdBasedFetcherForEntryGenerator())) + .findFirst().orElse(new DoiFetcher(prefs.getImportFormatPreferences())); + } + + public ListProperty fetcherItemsProperty() { + return fetchers; + } + + public void stopFetching() { + if (fetcherWorker.getState() == Worker.State.RUNNING) { + fetcherWorker.cancel(true); + } + } + + private class FetcherWorker extends Task> { + + private IdBasedFetcher fetcher = null; + private String searchID = ""; + + @Override + protected Optional call() throws InterruptedException, FetcherException { + Optional bibEntry = Optional.empty(); + + searchingProperty().setValue(true); + storeSelectedFetcher(); + fetcher = selectedItemProperty().getValue(); + searchID = idText.getValue(); + if (!searchID.isEmpty()) { + bibEntry = fetcher.performSearchById(searchID); + } + return bibEntry; + } + + } + + public void runFetcherWorker() { + searchSuccesfulProperty.set(false); + fetcherWorker.run(); + fetcherWorker.setOnFailed(event -> { + Throwable exception = fetcherWorker.getException(); + String fetcherExceptionMessage = exception.getMessage(); + String fetcher = selectedItemProperty().getValue().getName(); + String searchId = idText.getValue(); + if (exception instanceof FetcherException) { + dialogService.showErrorDialogAndWait(Localization.lang("Error"), Localization.lang("Error while fetching from %0", fetcher + "." + "\n" + fetcherExceptionMessage)); + } else { + dialogService.showErrorDialogAndWait(Localization.lang("No files found.", Localization.lang("Fetcher '%0' did not find an entry for id '%1'.", fetcher, searchId) + "\n" + fetcherExceptionMessage)); + } + LOGGER.error(String.format("Exception during fetching when using fetcher '%s' with entry id '%s'.", searchId, fetcher), exception); + + searchingProperty.set(false); + + fetcherWorker = new FetcherWorker(); + + }); + + fetcherWorker.setOnSucceeded(evt -> { + Optional result = fetcherWorker.getValue(); + if (result.isPresent()) { + final BibEntry bibEntry = result.get(); + if ((DuplicateCheck.containsDuplicate(basePanel.getDatabase(), bibEntry, basePanel.getBibDatabaseContext().getMode()).isPresent())) { + //If there are duplicates starts ImportInspectionDialog + ImportInspectionDialog diag = new ImportInspectionDialog(basePanel.frame(), basePanel, Localization.lang("Import"), false); + diag.addEntries(Arrays.asList(bibEntry)); + diag.entryListComplete(); + diag.setVisible(true); + diag.toFront(); + } else { + // Regenerate CiteKey of imported BibEntry + new BibtexKeyGenerator(basePanel.getBibDatabaseContext(), prefs.getBibtexKeyPatternPreferences()).generateAndSetKey(bibEntry); + basePanel.insertEntry(bibEntry); + } + searchSuccesfulProperty.set(true); + + } else if (StringUtil.isBlank(idText.getValue())) { + dialogService.showWarningDialogAndWait(Localization.lang("Empty search ID"), Localization.lang("The given search ID was empty.")); + } + fetcherWorker = new FetcherWorker(); + + focusAndSelectAllProperty.set(true); + searchingProperty().setValue(false); + + }); + } +} diff --git a/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java b/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java deleted file mode 100644 index e41f30dc616..00000000000 --- a/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java +++ /dev/null @@ -1,1226 +0,0 @@ -package org.jabref.gui; - -import java.awt.Component; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.awt.event.ComponentListener; -import java.awt.event.KeyEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.swing.AbstractAction; -import javax.swing.Action; -import javax.swing.BorderFactory; -import javax.swing.DefaultListCellRenderer; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JProgressBar; -import javax.swing.JRootPane; -import javax.swing.JScrollPane; -import javax.swing.JTextField; -import javax.swing.JTree; -import javax.swing.KeyStroke; -import javax.swing.SwingConstants; -import javax.swing.WindowConstants; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.filechooser.FileSystemView; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeCellRenderer; -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreeModel; -import javax.swing.tree.TreeNode; -import javax.swing.tree.TreePath; - -import org.jabref.Globals; -import org.jabref.JabRefExecutorService; -import org.jabref.JabRefGUI; -import org.jabref.gui.desktop.JabRefDesktop; -import org.jabref.gui.externalfiletype.ExternalFileTypes; -import org.jabref.gui.importer.EntryFromFileCreator; -import org.jabref.gui.importer.EntryFromFileCreatorManager; -import org.jabref.gui.importer.UnlinkedFilesCrawler; -import org.jabref.gui.importer.UnlinkedPDFFileFilter; -import org.jabref.gui.util.DefaultTaskExecutor; -import org.jabref.gui.util.DirectoryDialogConfiguration; -import org.jabref.gui.util.FileDialogConfiguration; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.EntryTypes; -import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibtexEntryType; -import org.jabref.model.entry.EntryType; -import org.jabref.model.entry.FieldName; -import org.jabref.preferences.JabRefPreferences; - -import com.jgoodies.forms.builder.ButtonBarBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * GUI Dialog for the feature "Find unlinked files". - */ -public class FindUnlinkedFilesDialog extends JabRefDialog { - - private static final Logger LOGGER = LoggerFactory.getLogger(FindUnlinkedFilesDialog.class); - private static final String GLOBAL_PREFS_WORKING_DIRECTORY_KEY = "findUnlinkedFilesWD"; - - private static final String GLOBAL_PREFS_DIALOG_SIZE_KEY = "findUnlinkedFilesDialogSize"; - private final JabRefFrame frame; - private final BibDatabaseContext databaseContext; - private final EntryFromFileCreatorManager creatorManager; - - private final UnlinkedFilesCrawler crawler; - private Path lastSelectedDirectory; - - private TreeModel treeModel; - /* PANELS */ - private JPanel panelDirectory; - private JPanel panelSearchArea; - private JPanel panelFiles; - private JPanel panelOptions; - private JPanel panelButtons; - private JPanel panelEntryTypesSelection; - - private JPanel panelImportArea; - private JButton buttonBrowse; - private JButton buttonScan; - private JButton buttonExport; - private JButton buttonApply; - - private JButton buttonClose; - /* Options for the TreeView */ - private JButton buttonOptionSelectAll; - private JButton buttonOptionDeselectAll; - private JButton buttonOptionExpandAll; - private JButton buttonOptionCollapseAll; - - private JCheckBox checkboxCreateKeywords; - private JTextField textfieldDirectoryPath; - private JLabel labelDirectoryDescription; - private JLabel labelFileTypesDescription; - private JLabel labelFilesDescription; - private JLabel labelEntryTypeDescription; - private JLabel labelSearchingDirectoryInfo; - - private JLabel labelImportingInfo; - private JLabel labelExportingInfo; - private JTree tree; - private JScrollPane scrollpaneTree; - private JComboBox comboBoxFileTypeSelection; - - private JComboBox comboBoxEntryTypeSelection; - private JProgressBar progressBarSearching; - private JProgressBar progressBarImporting; - - private MouseListener treeMouseListener; - private Action actionSelectAll; - private Action actionUnselectAll; - private Action actionExpandTree; - - private Action actionCollapseTree; - - private ComponentListener dialogPositionListener; - private final AtomicBoolean threadState = new AtomicBoolean(); - - private boolean checkBoxWhyIsThereNoGetSelectedStupidSwing; - - public FindUnlinkedFilesDialog(JabRefFrame frame) { - super(Localization.lang("Find unlinked files"), true, FindUnlinkedFilesDialog.class); - this.frame = frame; - - restoreSizeOfDialog(); - - databaseContext = frame.getCurrentBasePanel().getBibDatabaseContext(); - creatorManager = new EntryFromFileCreatorManager(ExternalFileTypes.getInstance()); - - crawler = new UnlinkedFilesCrawler(databaseContext); - - lastSelectedDirectory = loadLastSelectedDirectory(); - - initialize(); - buttonApply.setEnabled(false); - buttonExport.setEnabled(false); - } - - /** - * Close dialog when pressing escape - */ - @Override - protected JRootPane createRootPane() { - ActionListener actionListener = actionEvent -> setVisible(false); - JRootPane rPane = new JRootPane(); - KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); - rPane.registerKeyboardAction(actionListener, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW); - - return rPane; - } - - /** - * Stores the current size of this dialog persistently. - */ - private void storeSizeOfDialog() { - Dimension dim = getSize(); - String store = dim.width + ";" + dim.height; - Globals.prefs.put(FindUnlinkedFilesDialog.GLOBAL_PREFS_DIALOG_SIZE_KEY, store); - } - - /** - * Restores the location and size of this dialog from the persistent storage. - */ - private void restoreSizeOfDialog() { - - String store = Globals.prefs.get(FindUnlinkedFilesDialog.GLOBAL_PREFS_DIALOG_SIZE_KEY); - - Dimension dimension = null; - - if (store != null) { - try { - String[] dim = store.split(";"); - dimension = new Dimension(Integer.valueOf(dim[0]), Integer.valueOf(dim[1])); - } catch (NumberFormatException ignoredEx) { - LOGGER.debug("RestoreSizeDialog Exception ", ignoredEx); - } - } - if (dimension != null) { - setPreferredSize(dimension); - } - } - - /** - * Initializes the components, the layout, the data structure and the - * actions in this dialog. - */ - private void initialize() { - - initializeActions(); - initComponents(); - createTree(); - createFileTypesCombobox(); - createEntryTypesCombobox(); - initLayout(); - setupActions(); - pack(); - } - - /** - * Initializes action objects.
- * Does not assign actions to components yet! - */ - private void initializeActions() { - - actionSelectAll = new AbstractAction(Localization.lang("Select all")) { - - @Override - public void actionPerformed(ActionEvent e) { - CheckableTreeNode rootNode = (CheckableTreeNode) tree.getModel().getRoot(); - rootNode.setSelected(true); - tree.invalidate(); - tree.repaint(); - } - }; - - actionUnselectAll = new AbstractAction(Localization.lang("Unselect all")) { - - @Override - public void actionPerformed(ActionEvent e) { - CheckableTreeNode rootNode = (CheckableTreeNode) tree.getModel().getRoot(); - rootNode.setSelected(false); - tree.invalidate(); - tree.repaint(); - } - }; - - actionExpandTree = new AbstractAction(Localization.lang("Expand all")) { - - @Override - public void actionPerformed(ActionEvent e) { - CheckableTreeNode rootNode = (CheckableTreeNode) tree.getModel().getRoot(); - expandTree(tree, new TreePath(rootNode), true); - } - }; - - actionCollapseTree = new AbstractAction(Localization.lang("Collapse all")) { - - @Override - public void actionPerformed(ActionEvent e) { - CheckableTreeNode rootNode = (CheckableTreeNode) tree.getModel().getRoot(); - expandTree(tree, new TreePath(rootNode), false); - } - }; - - dialogPositionListener = new ComponentAdapter() { - - /* (non-Javadoc) - * @see java.awt.event.ComponentAdapter#componentResized(java.awt.event.ComponentEvent) - */ - @Override - public void componentResized(ComponentEvent e) { - storeSizeOfDialog(); - } - - /* (non-Javadoc) - * @see java.awt.event.ComponentAdapter#componentMoved(java.awt.event.ComponentEvent) - */ - @Override - public void componentMoved(ComponentEvent e) { - storeSizeOfDialog(); - } - }; - - } - - /** - * Stores the working directory path for this view in the global - * preferences. - * - * @param lastSelectedDir - * directory that is used as the working directory in this view. - */ - private void storeLastSelectedDirectory(Path lastSelectedDir) { - lastSelectedDirectory = lastSelectedDir; - if (lastSelectedDirectory != null) { - Globals.prefs.put(FindUnlinkedFilesDialog.GLOBAL_PREFS_WORKING_DIRECTORY_KEY, - lastSelectedDirectory.toAbsolutePath().toString()); - } - } - - /** - * Loads the working directory path which is persistantly stored for this - * view and returns it as a {@link File}-Object.
- *
- * If there is no working directory path stored, the general working - * directory will be consulted. - * - * @return The persistently stored working directory path for this view. - */ - private Path loadLastSelectedDirectory() { - String workingDirectory = Globals.prefs.get(FindUnlinkedFilesDialog.GLOBAL_PREFS_WORKING_DIRECTORY_KEY); - if (workingDirectory == null) { - workingDirectory = Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY); - } - lastSelectedDirectory = Paths.get(workingDirectory); - - return lastSelectedDirectory; - } - - /** - * Disables or enables all visible Elements in this Dialog.
- *
- * This also removes the {@link MouseListener} from the Tree-View to prevent - * it from receiving mouse events when in disabled-state. - * - * @param enable - * true when the elements shall get enabled, - * false when they shall get disabled. - */ - private void disOrEnableDialog(boolean enable) { - - if (enable) { - tree.addMouseListener(treeMouseListener); - } else { - tree.removeMouseListener(treeMouseListener); - } - disOrEnableAllElements(FindUnlinkedFilesDialog.this, enable); - } - - /** - * Recursively disables or enables all swing and awt components in this - * dialog, starting with but not including the container - * startContainer. - * - * @param startContainer - * The GUI Element to start with. - * @param enable - * true, if all elements will get enabled, - * false if all elements will get disabled. - */ - private void disOrEnableAllElements(Container startContainer, boolean enable) { - Component[] children = startContainer.getComponents(); - for (Component child : children) { - if (child instanceof Container) { - disOrEnableAllElements((Container) child, enable); - } - child.setEnabled(enable); - } - } - - /** - * Expands or collapses the specified tree according to the - * expand-parameter. - */ - private void expandTree(JTree currentTree, TreePath parent, boolean expand) { - TreeNode node = (TreeNode) parent.getLastPathComponent(); - if (node.getChildCount() >= 0) { - for (Enumeration e = node.children(); e.hasMoreElements();) { - TreePath path = parent.pathByAddingChild(e.nextElement()); - expandTree(currentTree, path, expand); - } - } - if (expand) { - currentTree.expandPath(parent); - } else { - currentTree.collapsePath(parent); - } - } - - /** - * Starts the search of unlinked files according to the current dialog - * state.
- *
- * This state is made of:
- *
  • The value of the "directory"-input-textfield and
  • The file type - * selection.
    - * The search will process in a seperate thread and the progress bar behind - * the "search" button will be displayed.
    - *
    - * When the search has completed, the - * {@link #searchFinishedHandler(CheckableTreeNode)} handler method is - * invoked. - */ - private void startSearch() { - - Path directory = Paths.get(textfieldDirectoryPath.getText()); - if (Files.notExists(directory)) { - directory = Paths.get(System.getProperty("user.dir")); - } - if (!Files.isDirectory(directory)) { - directory = directory.getParent(); - } - - //this addtional statement is needed because for the lamdba the variable must be effetively final - Path dir = directory; - - storeLastSelectedDirectory(directory); - - progressBarSearching.setMinimumSize( - new Dimension(buttonScan.getSize().width, progressBarSearching.getMinimumSize().height)); - progressBarSearching.setVisible(true); - progressBarSearching.setString(""); - - labelSearchingDirectoryInfo.setVisible(true); - buttonScan.setVisible(false); - - disOrEnableDialog(false); - labelSearchingDirectoryInfo.setEnabled(true); - - final FileFilter selectedFileFilter = (FileFilter) comboBoxFileTypeSelection.getSelectedItem(); - - threadState.set(true); - JabRefExecutorService.INSTANCE.execute(() -> { - UnlinkedPDFFileFilter unlinkedPDFFileFilter = new UnlinkedPDFFileFilter(selectedFileFilter, - databaseContext); - CheckableTreeNode rootNode = crawler.searchDirectory(dir.toFile(), unlinkedPDFFileFilter, threadState, - new ChangeListener() { - - int counter; - - @Override - public void stateChanged(ChangeEvent e) { - counter++; - String message; - if (counter == 1) { - message = Localization.lang("One file found"); - } else { - message = Localization.lang("%0 files found", Integer.toString(counter)); - } - progressBarSearching.setString(message); - } - }); - searchFinishedHandler(rootNode); - }); - - } - - /** - * This will start the import of all file of all selected nodes in this - * dialogs tree view.
    - *
    - * The import itself will run in a seperate thread, whilst this dialog will - * be showing a progress bar, until the thread has finished its work.
    - *
    - * When the import has finished, the {@link #importFinishedHandler(java.util.List)} is - * invoked. - */ - private void startImport() { - - if (treeModel == null) { - return; - } - setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - - CheckableTreeNode root = (CheckableTreeNode) treeModel.getRoot(); - - final List fileList = getFileListFromNode(root); - - if ((fileList == null) || fileList.isEmpty()) { - return; - } - - progressBarImporting.setVisible(true); - labelImportingInfo.setVisible(true); - buttonExport.setVisible(false); - buttonApply.setVisible(false); - buttonClose.setVisible(false); - disOrEnableDialog(false); - - labelImportingInfo.setEnabled(true); - - progressBarImporting.setMinimum(0); - progressBarImporting.setMaximum(fileList.size()); - progressBarImporting.setValue(0); - progressBarImporting.setString(""); - - final EntryType entryType = ((BibtexEntryTypeWrapper) comboBoxEntryTypeSelection.getSelectedItem()) - .getEntryType(); - - threadState.set(true); - JabRefExecutorService.INSTANCE.execute(() -> { - List errors = new LinkedList<>(); - creatorManager.addEntriesFromFiles(fileList, databaseContext.getDatabase(), frame.getCurrentBasePanel(), - entryType, checkBoxWhyIsThereNoGetSelectedStupidSwing, new ChangeListener() { - - int counter; - - @Override - public void stateChanged(ChangeEvent e) { - counter++; - progressBarImporting.setValue(counter); - progressBarImporting.setString(Localization.lang("%0 of %1", Integer.toString(counter), - Integer.toString(progressBarImporting.getMaximum()))); - } - }, errors); - importFinishedHandler(errors); - }); - } - - /** - * This starts the export of all files of all selected nodes in this - * dialogs tree view.
    - *
    - * The export itself will run in a seperate thread, whilst this dialog will - * be showing a progress bar, until the thread has finished its work.
    - *
    - * When the export has finished, the {@link #exportFinishedHandler()} is - * invoked. - */ - private void startExport() { - if (treeModel == null) { - return; - } - setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - - CheckableTreeNode root = (CheckableTreeNode) treeModel.getRoot(); - - final List fileList = getFileListFromNode(root); - if ((fileList == null) || fileList.isEmpty()) { - return; - } - - buttonExport.setVisible(false); - buttonApply.setVisible(false); - buttonClose.setVisible(false); - disOrEnableDialog(false); - - FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)).build(); - DialogService ds = new FXDialogService(); - - Optional exportPath = DefaultTaskExecutor - .runInJavaFXThread(() -> ds.showFileSaveDialog(fileDialogConfiguration)); - - if (!exportPath.isPresent()) { - exportFinishedHandler(); - return; - } - - threadState.set(true); - JabRefExecutorService.INSTANCE.execute(() -> { - try (BufferedWriter writer = - Files.newBufferedWriter(exportPath.get(), StandardCharsets.UTF_8, - StandardOpenOption.CREATE)) { - for (File file : fileList) { - writer.write(file.toString() + "\n"); - } - - } catch (IOException e) { - LOGGER.warn("IO Error.", e); - } - }); - - exportFinishedHandler(); - } - - private void exportFinishedHandler() { - buttonExport.setVisible(true); - buttonApply.setVisible(true); - buttonClose.setVisible(true); - disOrEnableDialog(true); - setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - frame.getCurrentBasePanel().markBaseChanged(); - } - - /** - * - * @param errors - */ - private void importFinishedHandler(List errors) { - - if ((errors != null) && !errors.isEmpty()) { - String message; - if (errors.size() == 1) { - message = Localization.lang("There was one file that could not be imported."); - } else { - message = Localization.lang("There were %0 files which could not be imported.", - Integer.toString(errors.size())); - } - JOptionPane.showMessageDialog(this, - Localization.lang("The import finished with warnings:") + "\n" + message, - Localization.lang("Warning"), JOptionPane.WARNING_MESSAGE); - } - - progressBarImporting.setVisible(false); - labelImportingInfo.setVisible(false); - buttonExport.setVisible(true); - buttonApply.setVisible(true); - buttonClose.setVisible(true); - disOrEnableDialog(true); - setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - frame.getCurrentBasePanel().markBaseChanged(); - } - - /** - * Will be called from the Thread in which the "unlinked files search" is - * processed. As the result of the search, the root node of the determined - * file structure is passed. - * - * @param rootNode - * The root of the file structure as the result of the search. - */ - private void searchFinishedHandler(CheckableTreeNode rootNode) { - treeModel = new DefaultTreeModel(rootNode); - tree.setModel(treeModel); - tree.setRootVisible(rootNode.getChildCount() > 0); - - tree.invalidate(); - tree.repaint(); - - progressBarSearching.setVisible(false); - labelSearchingDirectoryInfo.setVisible(false); - buttonScan.setVisible(true); - actionSelectAll.actionPerformed(null); - - disOrEnableDialog(true); - buttonApply.setEnabled(true); - buttonExport.setEnabled(true); - } - - /** - * Sets up the actions for the components. - */ - private void setupActions() { - - DirectoryDialogConfiguration directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)).build(); - DialogService ds = frame.getDialogService(); - /** - * Stores the selected directory. - */ - buttonBrowse.addActionListener(e -> { - Optional selectedDirectory = DefaultTaskExecutor - .runInJavaFXThread(() -> ds.showDirectorySelectionDialog(directoryDialogConfiguration)); - selectedDirectory.ifPresent(d -> { - textfieldDirectoryPath.setText(d.toAbsolutePath().toString()); - storeLastSelectedDirectory(d); - }); - }); - - buttonScan.addActionListener(e -> startSearch()); - - /** - * Action for the button "Import...".
    - *
    - * Actions on this button will start the import of all file of all - * selected nodes in this dialogs tree view.
    - */ - buttonExport.addActionListener(e -> startExport()); - buttonApply.addActionListener(e -> startImport()); - buttonClose.addActionListener(e -> dispose()); - } - - /** - * Creates a list of {@link File}s for all leaf nodes in the tree structure - * node, which have been marked as selected.
    - *
    - * Selected nodes correspond to those entries in the tree, - * whose checkbox is checked. - * - * SIDE EFFECT: The checked nodes are removed from the tree. - * - * @param node - * The root node representing a tree structure. - * @return A list of files of all checked leaf nodes. - */ - private List getFileListFromNode(CheckableTreeNode node) { - List filesList = new ArrayList<>(); - Enumeration children = node.depthFirstEnumeration(); - List nodesToRemove = new ArrayList<>(); - for (CheckableTreeNode child : Collections.list(children)) { - if (child.isLeaf() && child.isSelected()) { - File nodeFile = ((FileNodeWrapper) child.getUserObject()).file; - if ((nodeFile != null) && nodeFile.isFile()) { - filesList.add(nodeFile); - nodesToRemove.add(child); - } - } - } - - // remove imported files from tree - DefaultTreeModel model = (DefaultTreeModel) tree.getModel(); - for (CheckableTreeNode nodeToRemove : nodesToRemove) { - DefaultMutableTreeNode parent = (DefaultMutableTreeNode) nodeToRemove.getParent(); - model.removeNodeFromParent(nodeToRemove); - - // remove empty parent node - while ((parent != null) && parent.isLeaf()) { - DefaultMutableTreeNode pp = (DefaultMutableTreeNode) parent.getParent(); - if (pp != null) { - model.removeNodeFromParent(parent); - } - parent = pp; - } - // TODO: update counter / see: getTreeCellRendererComponent for label generation - } - tree.invalidate(); - tree.repaint(); - - return filesList; - } - - /** - * Initializes the visible components in this dialog. - */ - private void initComponents() { - - this.addComponentListener(dialogPositionListener); - /* Interrupts the searchThread by setting the State-Array to 0 */ - this.addWindowListener(new WindowAdapter() { - - @Override - public void windowClosing(WindowEvent e) { - threadState.set(false); - } - }); - - panelDirectory = new JPanel(); - panelSearchArea = new JPanel(); - panelFiles = new JPanel(); - panelOptions = new JPanel(); - panelEntryTypesSelection = new JPanel(); - panelButtons = new JPanel(); - panelImportArea = new JPanel(); - - buttonBrowse = new JButton(Localization.lang("Browse")); - buttonBrowse.setMnemonic('B'); - buttonBrowse.setToolTipText(Localization.lang("Opens the file browser.")); - buttonScan = new JButton(Localization.lang("Scan directory")); - buttonScan.setMnemonic('S'); - buttonScan.setToolTipText(Localization.lang("Searches the selected directory for unlinked files.")); - buttonExport = new JButton(Localization.lang("Export")); - buttonExport.setMnemonic('E'); - buttonExport.setToolTipText(Localization.lang("Export to text file.")); - buttonApply = new JButton(Localization.lang("Apply")); - buttonApply.setMnemonic('I'); - buttonApply.setToolTipText(Localization.lang("Starts the import of BibTeX entries.")); - buttonClose = new JButton(Localization.lang("Close")); - buttonClose.setToolTipText(Localization.lang("Leave this dialog.")); - buttonClose.setMnemonic('C'); - - /* Options for the TreeView */ - buttonOptionSelectAll = new JButton(); - buttonOptionSelectAll.setMnemonic('A'); - buttonOptionSelectAll.setAction(actionSelectAll); - buttonOptionDeselectAll = new JButton(); - buttonOptionDeselectAll.setMnemonic('U'); - buttonOptionDeselectAll.setAction(actionUnselectAll); - buttonOptionExpandAll = new JButton(); - buttonOptionExpandAll.setMnemonic('E'); - buttonOptionExpandAll.setAction(actionExpandTree); - buttonOptionCollapseAll = new JButton(); - buttonOptionCollapseAll.setMnemonic('L'); - buttonOptionCollapseAll.setAction(actionCollapseTree); - - checkboxCreateKeywords = new JCheckBox(Localization.lang("Create directory based keywords")); - checkboxCreateKeywords - .setToolTipText(Localization.lang("Creates keywords in created entrys with directory pathnames")); - checkboxCreateKeywords.setSelected(checkBoxWhyIsThereNoGetSelectedStupidSwing); - checkboxCreateKeywords.addItemListener( - e -> checkBoxWhyIsThereNoGetSelectedStupidSwing = !checkBoxWhyIsThereNoGetSelectedStupidSwing); - - textfieldDirectoryPath = new JTextField(); - textfieldDirectoryPath - .setText(lastSelectedDirectory == null ? "" : lastSelectedDirectory.toAbsolutePath().toString()); - - labelDirectoryDescription = new JLabel(Localization.lang("Select a directory where the search shall start.")); - labelFileTypesDescription = new JLabel(Localization.lang("Select file type:")); - labelFilesDescription = new JLabel(Localization.lang("These files are not linked in the active library.")); - labelEntryTypeDescription = new JLabel(Localization.lang("Entry type to be created:")); - labelSearchingDirectoryInfo = new JLabel(Localization.lang("Searching file system...")); - labelSearchingDirectoryInfo.setHorizontalAlignment(SwingConstants.CENTER); - labelSearchingDirectoryInfo.setVisible(false); - labelImportingInfo = new JLabel(Localization.lang("Importing into Library...")); - labelImportingInfo.setHorizontalAlignment(SwingConstants.CENTER); - labelImportingInfo.setVisible(false); - labelExportingInfo = new JLabel(Localization.lang("Exporting into file...")); - labelExportingInfo.setHorizontalAlignment(SwingConstants.CENTER); - labelExportingInfo.setVisible(false); - - tree = new JTree(); - - scrollpaneTree = new JScrollPane(tree); - scrollpaneTree.setWheelScrollingEnabled(true); - - progressBarSearching = new JProgressBar(); - progressBarSearching.setIndeterminate(true); - progressBarSearching.setVisible(false); - progressBarSearching.setStringPainted(true); - - progressBarImporting = new JProgressBar(); - progressBarImporting.setIndeterminate(false); - progressBarImporting.setVisible(false); - progressBarImporting.setStringPainted(true); - - } - - /** - * Initializes the layout for the visible components in this menu. A - * {@link GridBagLayout} is used. - */ - private void initLayout() { - - GridBagLayout gbl = new GridBagLayout(); - - panelDirectory.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), - Localization.lang("Select directory"))); - panelFiles.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), - Localization.lang("Select files"))); - panelEntryTypesSelection.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), - Localization.lang("BibTeX entry creation"))); - - Insets basicInsets = new Insets(6, 6, 6, 6); - Insets smallInsets = new Insets(3, 2, 3, 1); - Insets noInsets = new Insets(0, 0, 0, 0); - - // x, y, w, h, wx,wy,ix,iy - FindUnlinkedFilesDialog.addComponent(gbl, panelSearchArea, buttonScan, GridBagConstraints.HORIZONTAL, - GridBagConstraints.EAST, noInsets, 0, 1, 1, 1, 1, 1, 40, 10); - FindUnlinkedFilesDialog.addComponent(gbl, panelSearchArea, labelSearchingDirectoryInfo, - GridBagConstraints.HORIZONTAL, GridBagConstraints.EAST, noInsets, 0, 2, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelSearchArea, progressBarSearching, GridBagConstraints.HORIZONTAL, - GridBagConstraints.EAST, noInsets, 0, 3, 1, 1, 0, 0, 0, 0); - - FindUnlinkedFilesDialog.addComponent(gbl, panelDirectory, labelDirectoryDescription, null, - GridBagConstraints.WEST, new Insets(6, 6, 0, 6), 0, 0, 3, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelDirectory, textfieldDirectoryPath, GridBagConstraints.HORIZONTAL, - null, basicInsets, 0, 1, 2, 1, 1, 1, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelDirectory, buttonBrowse, GridBagConstraints.HORIZONTAL, - GridBagConstraints.EAST, basicInsets, 2, 1, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelDirectory, labelFileTypesDescription, GridBagConstraints.NONE, - GridBagConstraints.WEST, new Insets(18, 6, 18, 3), 0, 3, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelDirectory, comboBoxFileTypeSelection, - GridBagConstraints.HORIZONTAL, GridBagConstraints.WEST, new Insets(18, 3, 18, 6), 1, 3, 1, 1, 1, 0, 0, - 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelDirectory, panelSearchArea, GridBagConstraints.HORIZONTAL, - GridBagConstraints.EAST, new Insets(18, 6, 18, 6), 2, 3, 1, 1, 0, 0, 0, 0); - - FindUnlinkedFilesDialog.addComponent(gbl, panelFiles, labelFilesDescription, GridBagConstraints.HORIZONTAL, - GridBagConstraints.WEST, new Insets(6, 6, 0, 6), 0, 0, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelFiles, scrollpaneTree, GridBagConstraints.BOTH, - GridBagConstraints.CENTER, basicInsets, 0, 1, 1, 1, 1, 1, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelFiles, panelOptions, GridBagConstraints.NONE, - GridBagConstraints.NORTHEAST, basicInsets, 1, 1, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelOptions, buttonOptionSelectAll, GridBagConstraints.HORIZONTAL, - GridBagConstraints.NORTH, noInsets, 0, 0, 1, 1, 1, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelOptions, buttonOptionDeselectAll, GridBagConstraints.HORIZONTAL, - GridBagConstraints.NORTH, noInsets, 0, 1, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelOptions, buttonOptionExpandAll, GridBagConstraints.HORIZONTAL, - GridBagConstraints.NORTH, new Insets(6, 0, 0, 0), 0, 2, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelOptions, buttonOptionCollapseAll, GridBagConstraints.HORIZONTAL, - GridBagConstraints.NORTH, noInsets, 0, 3, 1, 1, 0, 0, 0, 0); - - FindUnlinkedFilesDialog.addComponent(gbl, panelEntryTypesSelection, labelEntryTypeDescription, - GridBagConstraints.NONE, GridBagConstraints.WEST, basicInsets, 0, 0, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelEntryTypesSelection, comboBoxEntryTypeSelection, - GridBagConstraints.NONE, GridBagConstraints.WEST, basicInsets, 1, 0, 1, 1, 1, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelEntryTypesSelection, checkboxCreateKeywords, - GridBagConstraints.HORIZONTAL, GridBagConstraints.WEST, basicInsets, 0, 1, 2, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelImportArea, labelImportingInfo, GridBagConstraints.HORIZONTAL, - GridBagConstraints.CENTER, new Insets(6, 6, 0, 6), 0, 1, 1, 1, 1, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelImportArea, labelExportingInfo, GridBagConstraints.HORIZONTAL, - GridBagConstraints.CENTER, new Insets(6, 6, 0, 6), 0, 1, 1, 1, 1, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelImportArea, progressBarImporting, GridBagConstraints.HORIZONTAL, - GridBagConstraints.CENTER, new Insets(0, 6, 6, 6), 0, 2, 1, 1, 1, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, panelButtons, panelImportArea, GridBagConstraints.NONE, - GridBagConstraints.EAST, smallInsets, 1, 0, 1, 1, 0, 0, 0, 0); - - FindUnlinkedFilesDialog.addComponent(gbl, getContentPane(), panelDirectory, GridBagConstraints.HORIZONTAL, - GridBagConstraints.CENTER, basicInsets, 0, 0, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, getContentPane(), panelFiles, GridBagConstraints.BOTH, - GridBagConstraints.NORTHWEST, new Insets(12, 6, 2, 2), 0, 1, 1, 1, 1, 1, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, getContentPane(), panelEntryTypesSelection, - GridBagConstraints.HORIZONTAL, GridBagConstraints.SOUTHWEST, new Insets(12, 6, 2, 2), 0, 2, 1, 1, 0, 0, - 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, getContentPane(), panelButtons, GridBagConstraints.HORIZONTAL, - GridBagConstraints.CENTER, new Insets(10, 6, 10, 6), 0, 3, 1, 1, 0, 0, 0, 0); - - ButtonBarBuilder bb = new ButtonBarBuilder(); - bb.addGlue(); - bb.addButton(buttonExport); - bb.addButton(buttonApply); - bb.addButton(buttonClose); - bb.addGlue(); - - bb.getPanel().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - panelImportArea.add(bb.getPanel(), GridBagConstraints.NONE); - pack(); - - } - - /** - * Adds a component to a container, using the specified gridbag-layout and - * the supplied parameters.
    - *
    - * This method is simply used to ged rid of thousands of lines of code, - * which inevitably rise when layouts such as the gridbag-layout is being - * used. - * - * @param layout - * The layout to be used. - * @param container - * The {@link Container}, to which the component will be added. - * @param component - * An AWT {@link Component}, that will be added to the container. - * @param fill - * A constant describing the fill behaviour (see - * {@link GridBagConstraints}). Can be null, if no - * filling wants to be specified. - * @param anchor - * A constant describing the anchor of the element in its parent - * container (see {@link GridBagConstraints}). Can be - * null, if no specification is needed. - * @param gridX - * The relative grid-X coordinate. - * @param gridY - * The relative grid-Y coordinate. - * @param width - * The relative width of the component. - * @param height - * The relative height of the component. - * @param weightX - * A value for the horizontal weight. - * @param weightY - * A value for the vertical weight. - * @param insets - * Insets of the component. Can be null. - */ - private static void addComponent(GridBagLayout layout, Container container, Component component, Integer fill, - Integer anchor, Insets insets, int gridX, int gridY, int width, int height, double weightX, double weightY, - int ipadX, int ipadY) { - container.setLayout(layout); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.gridx = gridX; - constraints.gridy = gridY; - constraints.gridwidth = width; - constraints.gridheight = height; - constraints.weightx = weightX; - constraints.weighty = weightY; - constraints.ipadx = ipadX; - constraints.ipady = ipadY; - if (fill != null) { - constraints.fill = fill; - } - if (insets != null) { - constraints.insets = insets; - } - if (anchor != null) { - constraints.anchor = anchor; - } - layout.setConstraints(component, constraints); - container.add(component); - } - - /** - * Creates the tree view, that holds the data structure.
    - *
    - * Initially, the root node is not visible, so that the tree appears empty at the beginning. - */ - private void createTree() { - - /** - * Mouse listener to listen for mouse events on the tree.
    - * This will mark the selected tree entry as "selected" or "unselected", - * which will cause this nodes checkbox to appear as either "checked" or - * "unchecked". - */ - treeMouseListener = new MouseAdapter() { - - @Override - public void mousePressed(MouseEvent e) { - int x = e.getX(); - int y = e.getY(); - - int row = tree.getRowForLocation(x, y); - - TreePath path = tree.getPathForRow(row); - if (path != null) { - CheckableTreeNode node = (CheckableTreeNode) path.getLastPathComponent(); - if (e.getClickCount() == 2) { - Object userObject = node.getUserObject(); - if ((userObject instanceof FileNodeWrapper) && node.isLeaf()) { - FileNodeWrapper fnw = (FileNodeWrapper) userObject; - try { - JabRefDesktop.openExternalViewer( - JabRefGUI.getMainFrame().getCurrentBasePanel().getBibDatabaseContext(), - fnw.file.getAbsolutePath(), FieldName.PDF); - } catch (IOException e1) { - LOGGER.info("Error opening file", e1); - } - } - } else { - node.check(); - tree.invalidate(); - tree.repaint(); - } - } - } - - }; - - CheckableTreeNode startNode = new CheckableTreeNode("ROOT"); - DefaultTreeModel model = new DefaultTreeModel(startNode); - - tree.setModel(model); - tree.setRootVisible(false); - - DefaultTreeCellRenderer renderer = new CheckboxTreeCellRenderer(); - tree.setCellRenderer(renderer); - - tree.addMouseListener(treeMouseListener); - - } - - /** - * Initialises the combobox that contains the available file types which - * bibtex entries can be created of. - */ - private void createFileTypesCombobox() { - - List fileFilterList = creatorManager.getFileFilterList(); - - comboBoxFileTypeSelection = new JComboBox<>(fileFilterList.toArray(new FileFilter[fileFilterList.size()])); - - comboBoxFileTypeSelection.setRenderer(new DefaultListCellRenderer() { - - /* (non-Javadoc) - * @see javax.swing.DefaultListCellRenderer#getListCellRendererComponent(javax.swing.JList, java.lang.Object, int, boolean, boolean) - */ - @Override - public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, - boolean cellHasFocus) { - JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, - cellHasFocus); - if (value instanceof EntryFromFileCreator) { - EntryFromFileCreator creator = (EntryFromFileCreator) value; - if (creator.getExternalFileType() != null) { - label.setIcon(creator.getExternalFileType().getIcon().getSmallIcon()); - } - } - return label; - } - }); - - } - - /** - * Creates the ComboBox-View for the Listbox that holds the Bibtex entry - * types. - */ - private void createEntryTypesCombobox() { - - Iterator iterator = EntryTypes - .getAllValues(frame.getCurrentBasePanel().getBibDatabaseContext().getMode()).iterator(); - List list = new ArrayList<>(); - list.add( - new BibtexEntryTypeWrapper(null)); - while (iterator.hasNext()) { - list.add(new BibtexEntryTypeWrapper(iterator.next())); - } - comboBoxEntryTypeSelection = new JComboBox<>(list.toArray(new BibtexEntryTypeWrapper[list.size()])); - } - - /** - * Wrapper for displaying the Type {@link BibtexEntryType} in a Combobox. - */ - private static class BibtexEntryTypeWrapper { - - private final EntryType entryType; - - BibtexEntryTypeWrapper(EntryType bibtexType) { - this.entryType = bibtexType; - } - - @Override - public String toString() { - if (entryType == null) { - return Localization.lang(""); - } - return entryType.getName(); - } - - public EntryType getEntryType() { - return entryType; - } - } - - public static class CheckableTreeNode extends DefaultMutableTreeNode { - - private boolean isSelected; - private final JCheckBox checkbox; - - public CheckableTreeNode(Object userObject) { - super(userObject); - checkbox = new JCheckBox(); - } - - /** - * @return the checkbox - */ - public JCheckBox getCheckbox() { - return checkbox; - } - - public void check() { - setSelected(!isSelected); - } - - public void setSelected(boolean bSelected) { - isSelected = bSelected; - Enumeration tmpChildren = this.children(); - for (CheckableTreeNode child : Collections.list(tmpChildren)) { - child.setSelected(bSelected); - } - - } - - public boolean isSelected() { - return isSelected; - } - - } - - private static class CheckboxTreeCellRenderer extends DefaultTreeCellRenderer { - - private final FileSystemView fsv = FileSystemView.getFileSystemView(); - - @Override - public Component getTreeCellRendererComponent(final JTree tree, Object value, boolean sel, boolean expanded, - boolean leaf, int row, boolean hasFocus) { - - Component nodeComponent = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, - hasFocus); - CheckableTreeNode node = (CheckableTreeNode) value; - - FileNodeWrapper userObject = (FileNodeWrapper) node.getUserObject(); - - JPanel newPanel = new JPanel(); - - JCheckBox checkbox = node.getCheckbox(); - checkbox.setSelected(node.isSelected()); - - try { - setIcon(fsv.getSystemIcon(userObject.file)); - } catch (Exception ignored) { - // Ignored - } - - newPanel.setBackground(nodeComponent.getBackground()); - checkbox.setBackground(nodeComponent.getBackground()); - - GridBagLayout gbl = new GridBagLayout(); - FindUnlinkedFilesDialog.addComponent(gbl, newPanel, checkbox, null, null, null, 0, 0, 1, 1, 0, 0, 0, 0); - FindUnlinkedFilesDialog.addComponent(gbl, newPanel, nodeComponent, GridBagConstraints.HORIZONTAL, null, - new Insets(1, 2, 0, 0), 1, 0, 1, 1, 1, 0, 0, 0); - - if (userObject.fileCount > 0) { - JLabel label = new JLabel( - "(" + userObject.fileCount + " file" + (userObject.fileCount > 1 ? "s" : "") + ")"); - FindUnlinkedFilesDialog.addComponent(gbl, newPanel, label, null, null, new Insets(1, 2, 0, 0), 2, 0, 1, - 1, 0, 0, 0, 0); - } - return newPanel; - } - - } - - public static class FileNodeWrapper { - - public final File file; - public final int fileCount; - - public FileNodeWrapper(File aFile) { - this(aFile, 0); - } - - /** - * @param aDirectory - * @param fileCount - */ - public FileNodeWrapper(File aDirectory, int fileCount) { - this.file = aDirectory; - this.fileCount = fileCount; - } - - /* - * (non-Javadoc) - * - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return file.getName(); - } - } - -} diff --git a/src/main/java/org/jabref/gui/GenFieldsCustomizer.java b/src/main/java/org/jabref/gui/GenFieldsCustomizer.java deleted file mode 100644 index f913bbe5cc8..00000000000 --- a/src/main/java/org/jabref/gui/GenFieldsCustomizer.java +++ /dev/null @@ -1,175 +0,0 @@ -package org.jabref.gui; - -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import javax.swing.AbstractAction; -import javax.swing.ActionMap; -import javax.swing.BorderFactory; -import javax.swing.InputMap; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; - -import org.jabref.Globals; -import org.jabref.gui.help.HelpAction; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.logic.bibtexkeypattern.BibtexKeyGenerator; -import org.jabref.logic.help.HelpFile; -import org.jabref.logic.l10n.Localization; -import org.jabref.preferences.JabRefPreferences; - -import com.jgoodies.forms.builder.ButtonBarBuilder; -import com.jgoodies.forms.layout.Sizes; - -public class GenFieldsCustomizer extends JabRefDialog { - - private final JPanel buttons = new JPanel(); - private final JButton ok = new JButton(); - private final JButton cancel = new JButton(); - private final JButton helpBut; - private final JLabel jLabel1 = new JLabel(); - private final JPanel jPanel3 = new JPanel(); - private final JPanel jPanel4 = new JPanel(); - private final GridBagLayout gridBagLayout1 = new GridBagLayout(); - private final JScrollPane jScrollPane1 = new JScrollPane(); - private final JLabel jLabel2 = new JLabel(); - private final JTextArea fieldsArea = new JTextArea(); - private final GridBagLayout gridBagLayout2 = new GridBagLayout(); - private final JButton revert = new JButton(); - - public GenFieldsCustomizer(JabRefFrame frame) { - super(Localization.lang("Set general fields"), false, GenFieldsCustomizer.class); - helpBut = new HelpAction(HelpFile.GENERAL_FIELDS).getHelpButton(); - jbInit(); - setSize(new Dimension(650, 300)); - } - - private void jbInit() { - ok.setText(Localization.lang("OK")); - ok.addActionListener(e -> okActionPerformed()); - cancel.setText(Localization.lang("Cancel")); - cancel.addActionListener(e -> dispose()); - jLabel1.setText(Localization.lang("Delimit fields with semicolon, ex.") + ": url;pdf;note"); - jPanel3.setLayout(gridBagLayout2); - jPanel4.setBorder(BorderFactory.createEtchedBorder()); - jPanel4.setLayout(gridBagLayout1); - jLabel2.setText(Localization.lang("General fields")); - - setFieldsText(); - - revert.setText(Localization.lang("Default")); - revert.addActionListener(e -> revertActionPerformed()); - this.getContentPane().add(buttons, BorderLayout.SOUTH); - ButtonBarBuilder bb = new ButtonBarBuilder(buttons); - buttons.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); - bb.addGlue(); - bb.addButton(ok); - bb.addButton(revert); - bb.addButton(cancel); - bb.addStrut(Sizes.DLUX5); - bb.addButton(helpBut); - bb.addGlue(); - - this.getContentPane().add(jPanel3, BorderLayout.CENTER); - jPanel3.add(jLabel1, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0 - , GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); - jPanel3.add(jPanel4, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0 - , GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 318, 193)); - jPanel4.add(jScrollPane1, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0 - , GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); - jScrollPane1.getViewport().add(fieldsArea, null); - jPanel4.add(jLabel2, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0 - , GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); - - // Key bindings: - ActionMap am = buttons.getActionMap(); - InputMap im = buttons.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE), "close"); - am.put("close", new AbstractAction() { - - @Override - public void actionPerformed(ActionEvent e) { - dispose(); - } - }); - - } - - private void okActionPerformed() { - String[] lines = fieldsArea.getText().split("\n"); - int i = 0; - for (; i < lines.length; i++) { - String[] parts = lines[i].split(":"); - if (parts.length != 2) { - // Report error and exit. - String field = Localization.lang("field"); - JOptionPane.showMessageDialog(this, Localization.lang("Each line must be on the following form") + " '" + - Localization.lang("Tabname") + ':' + field + "1;" + field + "2;...;" + field + "N'", - Localization.lang("Error"), JOptionPane.ERROR_MESSAGE); - return; - } - String testString = BibtexKeyGenerator.cleanKey(parts[1], - Globals.prefs.getBoolean(JabRefPreferences.ENFORCE_LEGAL_BIBTEX_KEY)); - if (!testString.equals(parts[1]) || (parts[1].indexOf('&') >= 0)) { - // Report error and exit. - JOptionPane.showMessageDialog(this, Localization.lang("Field names are not allowed to contain white space or the following " - + "characters") + ": # { } ~ , ^ &", - Localization.lang("Error"), JOptionPane.ERROR_MESSAGE); - - return; - } - - Globals.prefs.put((JabRefPreferences.CUSTOM_TAB_NAME + i), parts[0]); - Globals.prefs.put((JabRefPreferences.CUSTOM_TAB_FIELDS + i), parts[1].toLowerCase(Locale.ROOT)); - } - Globals.prefs.purgeSeries(JabRefPreferences.CUSTOM_TAB_NAME, i); - Globals.prefs.purgeSeries(JabRefPreferences.CUSTOM_TAB_FIELDS, i); - Globals.prefs.updateEntryEditorTabList(); - - dispose(); - } - - private void setFieldsText() { - StringBuilder sb = new StringBuilder(); - - for (Map.Entry> tab : Globals.prefs.getEntryEditorTabList().entrySet()) { - sb.append(tab.getKey()); - sb.append(':'); - sb.append(String.join(";", tab.getValue())); - sb.append('\n'); - } - - fieldsArea.setText(sb.toString()); - } - - private void revertActionPerformed() { - StringBuilder sb = new StringBuilder(); - String name; - String fields; - int i = 0; - while ((name = (String) Globals.prefs.defaults.get - (JabRefPreferences.CUSTOM_TAB_NAME + "_def" + i)) != null) { - sb.append(name); - fields = (String) Globals.prefs.defaults.get - (JabRefPreferences.CUSTOM_TAB_FIELDS + "_def" + i); - sb.append(':'); - sb.append(fields); - sb.append('\n'); - i++; - } - fieldsArea.setText(sb.toString()); - - } -} diff --git a/src/main/java/org/jabref/gui/GlobalFocusListener.java b/src/main/java/org/jabref/gui/GlobalFocusListener.java deleted file mode 100644 index 865de194b33..00000000000 --- a/src/main/java/org/jabref/gui/GlobalFocusListener.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.jabref.gui; - -import java.awt.Component; -import java.awt.event.FocusEvent; -import java.awt.event.FocusListener; - -import javax.swing.JComponent; - -public class GlobalFocusListener implements FocusListener { - private Component focused; - - @Override - public void focusGained(FocusEvent e) { - if (!e.isTemporary()) { - focused = (Component) e.getSource(); - } - } - - @Override - public void focusLost(FocusEvent e) { - // Do nothing - } - - public JComponent getFocused() { - return (JComponent) focused; - } - - public void setFocused(Component c) { - focused = c; - } -} diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index b894a5e3f05..596fe1972c9 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -2,7 +2,6 @@ import java.awt.Component; import java.awt.Window; -import java.awt.event.ActionEvent; import java.io.File; import java.io.IOException; import java.nio.file.Path; @@ -18,7 +17,6 @@ import java.util.stream.Collectors; import javax.swing.Action; -import javax.swing.JComponent; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.SwingUtilities; @@ -39,6 +37,7 @@ import javafx.scene.control.SplitPane; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; +import javafx.scene.control.TextInputControl; import javafx.scene.control.ToolBar; import javafx.scene.control.Tooltip; import javafx.scene.input.DataFormat; @@ -63,7 +62,6 @@ import org.jabref.gui.actions.DatabasePropertiesAction; import org.jabref.gui.actions.EditExternalFileTypesAction; import org.jabref.gui.actions.ErrorConsoleAction; -import org.jabref.gui.actions.FindUnlinkedFilesAction; import org.jabref.gui.actions.IntegrityCheckAction; import org.jabref.gui.actions.LookupIdentifierAction; import org.jabref.gui.actions.ManageCustomExportsAction; @@ -71,7 +69,6 @@ import org.jabref.gui.actions.ManageJournalsAction; import org.jabref.gui.actions.ManageKeywordsAction; import org.jabref.gui.actions.ManageProtectedTermsAction; -import org.jabref.gui.actions.MassSetFieldAction; import org.jabref.gui.actions.MergeEntriesAction; import org.jabref.gui.actions.NewDatabaseAction; import org.jabref.gui.actions.NewEntryAction; @@ -86,10 +83,12 @@ import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.actions.StandardActions; import org.jabref.gui.dialogs.AutosaveUIManager; +import org.jabref.gui.edit.MassSetFieldsAction; import org.jabref.gui.exporter.ExportCommand; import org.jabref.gui.exporter.ExportToClipboardAction; import org.jabref.gui.exporter.SaveAllAction; import org.jabref.gui.exporter.SaveDatabaseAction; +import org.jabref.gui.externalfiles.FindUnlinkedFilesAction; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.help.AboutAction; import org.jabref.gui.help.HelpAction; @@ -153,7 +152,6 @@ public class JabRefFrame extends BorderPane implements OutputPrinter { private final JabRefPreferences prefs = Globals.prefs; private final GlobalSearchBar globalSearchBar = new GlobalSearchBar(this); private final JFXSnackbar statusLine = new JFXSnackbar(this); - ; private final ProgressBar progressBar = new ProgressBar(); private final FileHistoryMenu fileHistory = new FileHistoryMenu(prefs, this); @@ -284,8 +282,7 @@ private void init() { //previewToggle.setSelected(Globals.prefs.getPreviewPreferences().isPreviewPanelEnabled()); //generalFetcher.getToggleCommand().setSelected(sidePaneManager.isComponentVisible(WebSearchPane.class)); //openOfficePanel.getToggleCommand().setSelected(sidePaneManager.isComponentVisible(OpenOfficeSidePanel.class)); - // TODO: Can't notify focus listener since it is expecting a swing component - //Globals.getFocusListener().setFocused(currentBasePanel.getMainTable()); + setWindowTitle(); // Update search autocompleter with information for the correct database: currentBasePanel.updateSearchManager(); @@ -621,7 +618,7 @@ private Node createToolbar() { PushToApplicationButton pushToExternal = new PushToApplicationButton(this, pushApplications.getApplications()); HBox rightSide = new HBox( - factory.createIconButton(StandardActions.NEW_ENTRY, new NewEntryAction(this, BiblatexEntryTypes.ARTICLE)), + factory.createIconButton(StandardActions.NEW_ARTICLE, new NewEntryAction(this, BiblatexEntryTypes.ARTICLE, dialogService, Globals.prefs)), factory.createIconButton(StandardActions.DELETE_ENTRY, new OldDatabaseCommandWrapper(Actions.DELETE, this, Globals.stateManager)), factory.createIconButton(StandardActions.UNDO, new OldDatabaseCommandWrapper(Actions.UNDO, this, Globals.stateManager)), @@ -833,14 +830,14 @@ private MenuBar createMenu() { edit.getItems().addAll( factory.createMenuItem(StandardActions.MANAGE_KEYWORDS, new ManageKeywordsAction(this)), factory.createMenuItem(StandardActions.REPLACE_ALL, new OldDatabaseCommandWrapper(Actions.REPLACE_ALL, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.MASS_SET_FIELDS, new MassSetFieldAction(this)) + factory.createMenuItem(StandardActions.MASS_SET_FIELDS, new MassSetFieldsAction(this)) ); library.getItems().addAll( - factory.createMenuItem(StandardActions.NEW_ARTICLE, new NewEntryAction(this, BibtexEntryTypes.ARTICLE)), - factory.createMenuItem(StandardActions.NEW_ENTRY, new NewEntryAction(this)), - factory.createMenuItem(StandardActions.NEW_ENTRY_FROM_PLAINTEX, new NewEntryFromPlainTextAction(this, Globals.prefs.getUpdateFieldPreferences())), + factory.createMenuItem(StandardActions.NEW_ARTICLE, new NewEntryAction(this, BibtexEntryTypes.ARTICLE, dialogService, Globals.prefs)), + factory.createMenuItem(StandardActions.NEW_ENTRY, new NewEntryAction(this, dialogService, Globals.prefs)), + factory.createMenuItem(StandardActions.NEW_ENTRY_FROM_PLAINTEX, new NewEntryFromPlainTextAction(this, Globals.prefs.getUpdateFieldPreferences(), dialogService, Globals.prefs)), new SeparatorMenuItem(), @@ -929,7 +926,7 @@ private MenuBar createMenu() { new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.SETUP_GENERAL_FIELDS, new SetupGeneralFieldsAction(this)), + factory.createMenuItem(StandardActions.SETUP_GENERAL_FIELDS, new SetupGeneralFieldsAction()), factory.createMenuItem(StandardActions.MANAGE_CUSTOM_IMPORTS, new ManageCustomImportsAction(this)), factory.createMenuItem(StandardActions.MANAGE_CUSTOM_EXPORTS, new ManageCustomExportsAction(this)), factory.createMenuItem(StandardActions.MANAGE_EXTERNAL_FILETYPES, new EditExternalFileTypesAction()), @@ -954,7 +951,7 @@ private MenuBar createMenu() { new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.SEARCH_FOR_UPDATES, new SearchForUpdateAction()), + factory.createMenuItem(StandardActions.SEARCH_FOR_UPDATES, new SearchForUpdateAction(Globals.BUILD_INFO, prefs.getVersionPreferences(), dialogService, Globals.TASK_EXECUTOR)), factory.createSubMenu(StandardActions.WEB_MENU, factory.createMenuItem(StandardActions.OPEN_WEBPAGE, new OpenBrowserAction("https://jabref.org/")), factory.createMenuItem(StandardActions.OPEN_BLOG, new OpenBrowserAction("https://blog.jabref.org/")), @@ -1022,7 +1019,7 @@ public void addParserResult(ParserResult pr, boolean focusPanel) { */ @Deprecated public void output(final String s) { - statusLine.show(s, 3000); + DefaultTaskExecutor.runInJavaFXThread(() -> statusLine.show(s, 3000)); } private void initActions() { @@ -1339,8 +1336,7 @@ private boolean confirmClose(BasePanel panel) { // The user wants to save. try { SaveDatabaseAction saveAction = new SaveDatabaseAction(panel); - saveAction.runCommand(); - if (saveAction.isCanceled() || !saveAction.isSuccess()) { + if (!saveAction.save()) { // The action was either canceled or unsuccessful. output(Localization.lang("Unable to save library")); return false; @@ -1442,10 +1438,40 @@ public EditAction(Actions command) { @Override public void execute() { - JComponent source = Globals.getFocusListener().getFocused(); - Action action = source.getActionMap().get(command); - if (action != null) { - action.actionPerformed(new ActionEvent(source, 0, command.name())); + Node focusOwner = mainStage.getScene().getFocusOwner(); + if (focusOwner != null) { + if (focusOwner instanceof TextInputControl) { + // Focus is on text field -> copy/paste/cut selected text + TextInputControl textInput = (TextInputControl) focusOwner; + switch (command) { + case COPY: + textInput.copy(); + break; + case CUT: + textInput.cut(); + break; + case PASTE: + textInput.paste(); + break; + default: + throw new IllegalStateException("Only cut/copy/paste supported but got " + command); + } + } else { + // Not sure what is selected -> copy/paste/cut selected entries + switch (command) { + case COPY: + getCurrentBasePanel().copy(); + break; + case CUT: + getCurrentBasePanel().cut(); + break; + case PASTE: + getCurrentBasePanel().paste(); + break; + default: + throw new IllegalStateException("Only cut/copy/paste supported but got " + command); + } + } } } } diff --git a/src/main/java/org/jabref/gui/MergeDialog.java b/src/main/java/org/jabref/gui/MergeDialog.java deleted file mode 100644 index 09ddfb6677d..00000000000 --- a/src/main/java/org/jabref/gui/MergeDialog.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.jabref.gui; - -import java.awt.BorderLayout; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; - -import javax.swing.AbstractAction; -import javax.swing.ActionMap; -import javax.swing.BorderFactory; -import javax.swing.InputMap; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JPanel; - -import org.jabref.Globals; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.logic.l10n.Localization; - -/** - * Asks for details about merge database operation. - */ -public class MergeDialog extends JabRefDialog { - - private final JPanel panel1 = new JPanel(); - private final BorderLayout borderLayout1 = new BorderLayout(); - private final JPanel jPanel1 = new JPanel(); - private final JPanel jPanel2 = new JPanel(); - private final JButton ok = new JButton(); - private final JButton cancel = new JButton(); - private final JCheckBox entries = new JCheckBox(); - private final JCheckBox strings = new JCheckBox(); - private final GridBagLayout gridBagLayout1 = new GridBagLayout(); - private final JCheckBox groups = new JCheckBox(); - private final JCheckBox selector = new JCheckBox(); - - - private boolean okPressed; - - public MergeDialog(JabRefFrame frame, String title, boolean modal) { - super(title, modal, MergeDialog.class); - jbInit(); - pack(); - } - - public boolean isOkPressed() { - return okPressed; - } - - private void jbInit() { - panel1.setLayout(borderLayout1); - ok.setText(Localization.lang("OK")); - ok.addActionListener(e -> { - okPressed = true; - dispose(); - }); - cancel.setText(Localization.lang("Cancel")); - cancel.addActionListener(e -> dispose()); - jPanel1.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - jPanel1.setLayout(gridBagLayout1); - entries.setSelected(true); - entries.setText(Localization.lang("Import entries")); - strings.setSelected(true); - strings.setText(Localization.lang("Import strings")); - groups.setText(Localization.lang("Import group definitions")); - selector.setText(Localization.lang("Import word selector definitions")); - - this.setModal(true); - this.setResizable(false); - getContentPane().add(panel1); - panel1.add(jPanel2, BorderLayout.SOUTH); - jPanel2.add(ok, null); - jPanel2.add(cancel, null); - panel1.add(jPanel1, BorderLayout.CENTER); - jPanel1.add(entries, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0 - , GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); - jPanel1.add(strings, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0 - , GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - jPanel1.add(groups, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0 - , GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - jPanel1.add(selector, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, - GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - - // Key bindings: - ActionMap am = jPanel1.getActionMap(); - InputMap im = jPanel1.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE), "close"); - am.put("close", new AbstractAction() { - - @Override - public void actionPerformed(ActionEvent e) { - dispose(); - } - }); - - } - - public boolean importEntries() { - return entries.isSelected(); - } - - public boolean importGroups() { - return groups.isSelected(); - } - - public boolean importStrings() { - return strings.isSelected(); - } - - public boolean importSelectorWords() { - return selector.isSelected(); - } -} - - diff --git a/src/main/java/org/jabref/gui/PreviewPanel.java b/src/main/java/org/jabref/gui/PreviewPanel.java index 7a0b16a16a7..58fe5a684b2 100644 --- a/src/main/java/org/jabref/gui/PreviewPanel.java +++ b/src/main/java/org/jabref/gui/PreviewPanel.java @@ -41,7 +41,6 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.event.FieldChangedEvent; -import org.jabref.preferences.JabRefPreferences; import org.jabref.preferences.PreviewPreferences; import com.google.common.eventbus.Subscribe; @@ -89,12 +88,10 @@ public PreviewPanel(BasePanel panel, BibDatabaseContext databaseContext, KeyBind this.keyBindingRepository = keyBindingRepository; fileHandler = new NewDroppedFileHandler(dialogService, databaseContext, externalFileTypes, - Globals.prefs.getFileDirectoryPreferences(), - Globals.prefs.getCleanupPreferences(Globals.journalAbbreviationLoader).getFileDirPattern(), + Globals.prefs.getFilePreferences(), Globals.prefs.getImportFormatPreferences(), Globals.prefs.getUpdateFieldPreferences(), - Globals.getFileUpdateMonitor(), - Globals.prefs.get(JabRefPreferences.IMPORT_FILENAMEPATTERN)); + Globals.getFileUpdateMonitor()); // Set up scroll pane for preview pane setFitToHeight(true); @@ -153,8 +150,7 @@ public PreviewPanel(BasePanel panel, BibDatabaseContext databaseContext, KeyBind }); createKeyBindings(); - updateLayout(preferences); - + updateLayout(preferences, true); } private void createKeyBindings() { @@ -211,6 +207,10 @@ public void setBasePanel(BasePanel basePanel) { } public void updateLayout(PreviewPreferences previewPreferences) { + updateLayout(previewPreferences, false); + } + + private void updateLayout(PreviewPreferences previewPreferences, boolean init) { if (fixedLayout) { LOGGER.debug("cannot change the layout because the layout is fixed"); return; @@ -223,12 +223,16 @@ public void updateLayout(PreviewPreferences previewPreferences) { CitationStyle.createCitationStyleFromFile(style) .ifPresent(citationStyle -> { basePanel.get().getCitationStyleCache().setCitationStyle(citationStyle); - basePanel.get().output(Localization.lang("Preview style changed to: %0", citationStyle.getTitle())); + if (!init) { + basePanel.get().output(Localization.lang("Preview style changed to: %0", citationStyle.getTitle())); + } }); } } else { updatePreviewLayout(previewPreferences.getPreviewStyle(), previewPreferences.getLayoutFormatterPreferences()); - basePanel.ifPresent(panel -> panel.output(Localization.lang("Preview style changed to: %0", Localization.lang("Preview")))); + if (!init) { + basePanel.ifPresent(panel -> panel.output(Localization.lang("Preview style changed to: %0", Localization.lang("Preview")))); + } } update(); diff --git a/src/main/java/org/jabref/gui/SaveOrderConfigDisplay.java b/src/main/java/org/jabref/gui/SaveOrderConfigDisplay.java index c55dd92fd35..5103fba8d29 100644 --- a/src/main/java/org/jabref/gui/SaveOrderConfigDisplay.java +++ b/src/main/java/org/jabref/gui/SaveOrderConfigDisplay.java @@ -17,6 +17,7 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.InternalBibtexFields; import org.jabref.model.metadata.SaveOrderConfig; +import org.jabref.model.metadata.SaveOrderConfig.SortCriterion; public class SaveOrderConfigDisplay { @@ -83,30 +84,29 @@ public void setEnabled(boolean enabled) { saveTerDesc.setDisable(!enabled); } - public void setSaveOrderConfig(SaveOrderConfig saveOrderConfig) { - Objects.requireNonNull(saveOrderConfig); - - savePriSort.setValue(saveOrderConfig.sortCriteria[0].field); - savePriDesc.setSelected(saveOrderConfig.sortCriteria[0].descending); - saveSecSort.setValue(saveOrderConfig.sortCriteria[1].field); - saveSecDesc.setSelected(saveOrderConfig.sortCriteria[1].descending); - saveTerSort.setValue(saveOrderConfig.sortCriteria[2].field); - saveTerDesc.setSelected(saveOrderConfig.sortCriteria[2].descending); - - } - public SaveOrderConfig getSaveOrderConfig() { SaveOrderConfig saveOrderConfig = new SaveOrderConfig(); - saveOrderConfig.sortCriteria[0].field = getSelectedItemAsLowerCaseTrim(savePriSort); - saveOrderConfig.sortCriteria[0].descending = savePriDesc.isSelected(); - saveOrderConfig.sortCriteria[1].field = getSelectedItemAsLowerCaseTrim(saveSecSort); - saveOrderConfig.sortCriteria[1].descending = saveSecDesc.isSelected(); - saveOrderConfig.sortCriteria[2].field = getSelectedItemAsLowerCaseTrim(saveTerSort); - saveOrderConfig.sortCriteria[2].descending = saveTerDesc.isSelected(); + SortCriterion primary = new SortCriterion(getSelectedItemAsLowerCaseTrim(savePriSort), savePriDesc.isSelected()); + saveOrderConfig.getSortCriteria().add(primary); + SortCriterion secondary = new SortCriterion(getSelectedItemAsLowerCaseTrim(saveSecSort), saveSecDesc.isSelected()); + saveOrderConfig.getSortCriteria().add(secondary); + SortCriterion tertiary = new SortCriterion(getSelectedItemAsLowerCaseTrim(saveTerSort), saveTerDesc.isSelected()); + saveOrderConfig.getSortCriteria().add(tertiary); return saveOrderConfig; } + public void setSaveOrderConfig(SaveOrderConfig saveOrderConfig) { + Objects.requireNonNull(saveOrderConfig); + + savePriSort.setValue(saveOrderConfig.getSortCriteria().get(0).field); + savePriDesc.setSelected(saveOrderConfig.getSortCriteria().get(0).descending); + saveSecSort.setValue(saveOrderConfig.getSortCriteria().get(1).field); + saveSecDesc.setSelected(saveOrderConfig.getSortCriteria().get(1).descending); + saveTerSort.setValue(saveOrderConfig.getSortCriteria().get(2).field); + saveTerDesc.setSelected(saveOrderConfig.getSortCriteria().get(2).descending); + } + private String getSelectedItemAsLowerCaseTrim(ComboBox sortBox) { return sortBox.getValue().toLowerCase(Locale.ROOT).trim(); } diff --git a/src/main/java/org/jabref/gui/actions/CleanupAction.java b/src/main/java/org/jabref/gui/actions/CleanupAction.java index ef179b58b52..a4814735c8d 100644 --- a/src/main/java/org/jabref/gui/actions/CleanupAction.java +++ b/src/main/java/org/jabref/gui/actions/CleanupAction.java @@ -74,8 +74,6 @@ private void doCleanup(CleanupPreset preset, BibEntry entry, NamedCompound ce) { Globals.journalAbbreviationLoader)); List changes = cleaner.cleanup(preset, entry); - unsuccessfulRenames = cleaner.getUnsuccessfulRenames(); - if (changes.isEmpty()) { return; } @@ -90,11 +88,7 @@ private void showResults() { if (isCanceled) { return; } - if (unsuccessfulRenames > 0) { //Rename failed for at least one entry - dialogService.showErrorDialogAndWait( - Localization.lang("Autogenerate PDF Names"), - Localization.lang("File rename failed for %0 entries.", Integer.toString(unsuccessfulRenames))); - } + if (modifiedEntriesCount > 0) { panel.updateEntryEditorIfShowing(); panel.markBaseChanged(); diff --git a/src/main/java/org/jabref/gui/actions/GenerateBibtexKeyAction.java b/src/main/java/org/jabref/gui/actions/GenerateBibtexKeyAction.java index 5fa5eb5e76c..90b61800215 100644 --- a/src/main/java/org/jabref/gui/actions/GenerateBibtexKeyAction.java +++ b/src/main/java/org/jabref/gui/actions/GenerateBibtexKeyAction.java @@ -14,8 +14,9 @@ import org.jabref.preferences.JabRefPreferences; public class GenerateBibtexKeyAction implements BaseAction { + private final DialogService dialogService; - private BasePanel basePanel; + private final BasePanel basePanel; private List entries; private boolean isCanceled; @@ -29,7 +30,7 @@ public void init() { if (entries.isEmpty()) { dialogService.showWarningDialogAndWait(Localization.lang("Autogenerate BibTeX keys"), - Localization.lang("First select the entries you want keys to be generated for.")); + Localization.lang("First select the entries you want keys to be generated for.")); return; } basePanel.output(formatOutputMessage(Localization.lang("Generating BibTeX key for"), entries.size())); @@ -38,19 +39,20 @@ public void init() { public static boolean confirmOverwriteKeys(DialogService dialogService) { if (Globals.prefs.getBoolean(JabRefPreferences.WARN_BEFORE_OVERWRITING_KEY)) { return dialogService.showConfirmationDialogWithOptOutAndWait( - Localization.lang("Overwrite keys"), - Localization.lang("One or more keys will be overwritten. Continue?"), - Localization.lang("Overwrite keys"), - Localization.lang("Cancel"), - Localization.lang("Disable this confirmation dialog"), - optOut -> Globals.prefs.putBoolean(JabRefPreferences.WARN_BEFORE_OVERWRITING_KEY, !optOut)); + Localization.lang("Overwrite keys"), + Localization.lang("One or more keys will be overwritten. Continue?"), + Localization.lang("Overwrite keys"), + Localization.lang("Cancel"), + Localization.lang("Disable this confirmation dialog"), + optOut -> Globals.prefs.putBoolean(JabRefPreferences.WARN_BEFORE_OVERWRITING_KEY, !optOut)); + } else { // Always overwrite keys by default return true; } } - private void generateKeys() { + private void checkOverwriteKeysChosen() { // We don't want to generate keys for entries which already have one thus remove the entries if (Globals.prefs.getBoolean(JabRefPreferences.AVOID_OVERWRITING_KEY)) { entries.removeIf(BibEntry::hasCiteKey); @@ -64,7 +66,12 @@ private void generateKeys() { return; } } + } + private void generateKeys() { + if (isCanceled) { + return; + } // generate the new cite keys for each entry final NamedCompound compound = new NamedCompound(Localization.lang("Autogenerate BibTeX keys")); BibtexKeyGenerator keyGenerator = new BibtexKeyGenerator(basePanel.getBibDatabaseContext(), Globals.prefs.getBibtexKeyPatternPreferences()); @@ -79,21 +86,19 @@ private void generateKeys() { basePanel.getUndoManager().addEdit(compound); } - if (isCanceled) { - return; - } basePanel.markBaseChanged(); basePanel.output(formatOutputMessage(Localization.lang("Generated BibTeX key for"), entries.size())); } private String formatOutputMessage(String start, int count) { return String.format("%s %d %s.", start, count, - (count > 1 ? Localization.lang("entries") : Localization.lang("entry"))); + (count > 1 ? Localization.lang("entries") : Localization.lang("entry"))); } @Override public void action() { init(); + checkOverwriteKeysChosen(); BackgroundTask.wrap(this::generateKeys) .executeWith(Globals.TASK_EXECUTOR); } diff --git a/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java b/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java index d5702800f5f..ad07a4dde80 100644 --- a/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java +++ b/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java @@ -48,7 +48,7 @@ public IntegrityCheckAction(JabRefFrame frame) { @Override public void execute() { IntegrityCheck check = new IntegrityCheck(frame.getCurrentBasePanel().getBibDatabaseContext(), - Globals.prefs.getFileDirectoryPreferences(), + Globals.prefs.getFilePreferences(), Globals.prefs.getBibtexKeyPatternPreferences(), Globals.journalAbbreviationLoader.getRepository(Globals.prefs.getJournalAbbreviationPreferences()), Globals.prefs.getBoolean(JabRefPreferences.ENFORCE_LEGAL_BIBTEX_KEY)); diff --git a/src/main/java/org/jabref/gui/actions/MassSetFieldAction.java b/src/main/java/org/jabref/gui/actions/MassSetFieldAction.java deleted file mode 100644 index a93aaebe2b8..00000000000 --- a/src/main/java/org/jabref/gui/actions/MassSetFieldAction.java +++ /dev/null @@ -1,366 +0,0 @@ -package org.jabref.gui.actions; - -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.util.Collection; -import java.util.List; -import java.util.Locale; -import java.util.Optional; -import java.util.Set; - -import javax.swing.AbstractAction; -import javax.swing.Action; -import javax.swing.ActionMap; -import javax.swing.BorderFactory; -import javax.swing.ButtonGroup; -import javax.swing.InputMap; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JFrame; -import javax.swing.JRadioButton; -import javax.swing.JTextField; -import javax.swing.undo.UndoableEdit; - -import org.jabref.Globals; -import org.jabref.gui.BasePanel; -import org.jabref.gui.JabRefFrame; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableFieldChange; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.entry.BibEntry; - -import com.jgoodies.forms.builder.ButtonBarBuilder; -import com.jgoodies.forms.builder.FormBuilder; -import com.jgoodies.forms.layout.FormLayout; - -/** - * An Action for launching mass field. - * - * Functionality: - * * Defaults to selected entries, or all entries if none are selected. - * * Input field name - * * Either set field, or clear field. - */ -public class MassSetFieldAction extends SimpleCommand { - - private final JabRefFrame frame; - private JDialog diag; - private JRadioButton all; - private JRadioButton selected; - private JRadioButton clear; - private JRadioButton set; - private JRadioButton append; - private JRadioButton rename; - private JComboBox field; - private JTextField textFieldSet; - private JTextField textFieldAppend; - private JTextField textFieldRename; - private boolean canceled = true; - private JCheckBox overwrite; - - public MassSetFieldAction(JabRefFrame frame) { - this.frame = frame; - } - - /** - * Set a given field to a given value for all entries in a Collection. This method DOES NOT update any UndoManager, - * but returns a relevant CompoundEdit that should be registered by the caller. - * - * @param entries The entries to set the field for. - * @param field The name of the field to set. - * @param textToSet The value to set. This value can be null, indicating that the field should be cleared. - * @param overwriteValues Indicate whether the value should be set even if an entry already has the field set. - * @return A CompoundEdit for the entire operation. - */ - private static UndoableEdit massSetField(Collection entries, String field, String textToSet, - boolean overwriteValues) { - - NamedCompound compoundEdit = new NamedCompound(Localization.lang("Set field")); - for (BibEntry entry : entries) { - Optional oldValue = entry.getField(field); - // If we are not allowed to overwrite values, check if there is a - // nonempty - // value already for this entry: - if (!overwriteValues && (oldValue.isPresent()) && !oldValue.get().isEmpty()) { - continue; - } - if (textToSet == null) { - entry.clearField(field); - } else { - entry.setField(field, textToSet); - } - compoundEdit.addEdit(new UndoableFieldChange(entry, field, oldValue.orElse(null), textToSet)); - } - compoundEdit.end(); - return compoundEdit; - } - - private void prepareDialog(boolean selection) { - selected.setEnabled(selection); - if (selection) { - selected.setSelected(true); - } else { - all.setSelected(true); - } - // Make sure one of the following ones is selected: - if (!set.isSelected() && !clear.isSelected() && !rename.isSelected()) { - set.setSelected(true); - } - } - - @Override - public void execute() { - BasePanel bp = frame.getCurrentBasePanel(); - if (bp == null) { - return; - } - List entries = bp.getSelectedEntries(); - // Lazy creation of the dialog: - if (diag == null) { - createDialog(); - } - canceled = true; - prepareDialog(!entries.isEmpty()); - if (diag != null) { - diag.setVisible(true); - } - if (canceled) { - return; - } - - Collection entryList; - // If all entries should be treated, change the entries array: - if (all.isSelected()) { - entryList = bp.getDatabase().getEntries(); - } else { - entryList = entries; - } - - String toSet = textFieldSet.getText(); - if (toSet.isEmpty()) { - toSet = null; - } - - String[] fields = getFieldNames(((String) field.getSelectedItem()).trim().toLowerCase(Locale.ROOT)); - NamedCompound compoundEdit = new NamedCompound(Localization.lang("Set field")); - if (rename.isSelected()) { - if (fields.length > 1) { - frame.getDialogService().showErrorDialogAndWait(Localization.lang("You can only rename one field at a time")); - return; // Do not close the dialog. - } else { - compoundEdit.addEdit(MassSetFieldAction.massRenameField(entryList, fields[0], textFieldRename.getText(), - overwrite.isSelected())); - } - } else if (append.isSelected()) { - for (String field : fields) { - compoundEdit.addEdit(MassSetFieldAction.massAppendField(entryList, field, textFieldAppend.getText())); - } - } else { - for (String field : fields) { - compoundEdit.addEdit(MassSetFieldAction.massSetField(entryList, field, - set.isSelected() ? toSet : null, - overwrite.isSelected())); - } - } - compoundEdit.end(); - bp.getUndoManager().addEdit(compoundEdit); - bp.markBaseChanged(); - } - - /** - * Append a given value to a given field for all entries in a Collection. This method DOES NOT update any UndoManager, - * but returns a relevant CompoundEdit that should be registered by the caller. - * - * @param entries The entries to process the operation for. - * @param field The name of the field to append to. - * @param textToAppend The value to set. A null in this case will simply preserve the current field state. - * @return A CompoundEdit for the entire operation. - */ - private static UndoableEdit massAppendField(Collection entries, String field, String textToAppend) { - - String newValue = ""; - - if (textToAppend != null) { - newValue = textToAppend; - } - - NamedCompound compoundEdit = new NamedCompound(Localization.lang("Append field")); - for (BibEntry entry : entries) { - Optional oldValue = entry.getField(field); - entry.setField(field, oldValue.orElse("") + newValue); - compoundEdit.addEdit(new UndoableFieldChange(entry, field, oldValue.orElse(null), newValue)); - } - compoundEdit.end(); - return compoundEdit; - } - - /** - * Move contents from one field to another for a Collection of entries. - * - * @param entries The entries to do this operation for. - * @param field The field to move contents from. - * @param newField The field to move contents into. - * @param overwriteValues If true, overwrites any existing values in the new field. If false, makes no change for - * entries with existing value in the new field. - * @return A CompoundEdit for the entire operation. - */ - private static UndoableEdit massRenameField(Collection entries, String field, String newField, - boolean overwriteValues) { - NamedCompound compoundEdit = new NamedCompound(Localization.lang("Rename field")); - for (BibEntry entry : entries) { - Optional valToMove = entry.getField(field); - // If there is no value, do nothing: - if ((!valToMove.isPresent()) || valToMove.get().isEmpty()) { - continue; - } - // If we are not allowed to overwrite values, check if there is a - // non-empty value already for this entry for the new field: - Optional valInNewField = entry.getField(newField); - if (!overwriteValues && (valInNewField.isPresent()) && !valInNewField.get().isEmpty()) { - continue; - } - - entry.setField(newField, valToMove.get()); - compoundEdit.addEdit(new UndoableFieldChange(entry, newField, valInNewField.orElse(null), valToMove.get())); - entry.clearField(field); - compoundEdit.addEdit(new UndoableFieldChange(entry, field, valToMove.get(), null)); - } - compoundEdit.end(); - return compoundEdit; - } - - private void createDialog() { - diag = new JDialog((JFrame) null, Localization.lang("Set/clear/append/rename fields"), true); - - field = new JComboBox<>(); - field.setEditable(true); - textFieldSet = new JTextField(); - textFieldSet.setEnabled(false); - textFieldAppend = new JTextField(); - textFieldAppend.setEnabled(false); - textFieldRename = new JTextField(); - textFieldRename.setEnabled(false); - - JButton ok = new JButton(Localization.lang("OK")); - JButton cancel = new JButton(Localization.lang("Cancel")); - - all = new JRadioButton(Localization.lang("All entries")); - selected = new JRadioButton(Localization.lang("Selected entries")); - clear = new JRadioButton(Localization.lang("Clear fields")); - set = new JRadioButton(Localization.lang("Set fields")); - append = new JRadioButton(Localization.lang("Append to fields")); - rename = new JRadioButton(Localization.lang("Rename field to") + ":"); - rename.setToolTipText(Localization.lang("Move contents of a field into a field with a different name")); - - Set allFields = frame.getCurrentBasePanel().getDatabase().getAllVisibleFields(); - - for (String f : allFields) { - field.addItem(f); - } - - set.addChangeListener(e -> - // Entering a setText is only relevant if we are setting, not clearing: - textFieldSet.setEnabled(set.isSelected())); - - append.addChangeListener(e -> { - // Text to append is only required if we are appending: - textFieldAppend.setEnabled(append.isSelected()); - // Overwrite protection makes no sense if we are appending to a field: - overwrite.setEnabled(!clear.isSelected() && !append.isSelected()); - }); - - clear.addChangeListener(e -> - // Overwrite protection makes no sense if we are clearing the field: - overwrite.setEnabled(!clear.isSelected() && !append.isSelected())); - - rename.addChangeListener(e -> - // Entering a setText is only relevant if we are renaming - textFieldRename.setEnabled(rename.isSelected())); - - overwrite = new JCheckBox(Localization.lang("Overwrite existing field values"), true); - ButtonGroup bg = new ButtonGroup(); - bg.add(all); - bg.add(selected); - bg = new ButtonGroup(); - bg.add(clear); - bg.add(set); - bg.add(append); - bg.add(rename); - FormBuilder builder = FormBuilder.create().layout(new FormLayout( - "left:pref, 4dlu, fill:100dlu:grow", "pref, 2dlu, pref, 2dlu, pref, 2dlu, pref, 2dlu, pref, 2dlu, pref, 2dlu, pref, 2dlu, pref, 2dlu, pref, 2dlu, pref, 2dlu, pref")); - builder.addSeparator(Localization.lang("Field name")).xyw(1, 1, 3); - builder.add(Localization.lang("Field name")).xy(1, 3); - builder.add(field).xy(3, 3); - builder.addSeparator(Localization.lang("Include entries")).xyw(1, 5, 3); - builder.add(all).xyw(1, 7, 3); - builder.add(selected).xyw(1, 9, 3); - builder.addSeparator(Localization.lang("New field value")).xyw(1, 11, 3); - builder.add(set).xy(1, 13); - builder.add(textFieldSet).xy(3, 13); - builder.add(clear).xyw(1, 15, 3); - builder.add(append).xy(1, 17); - builder.add(textFieldAppend).xy(3, 17); - builder.add(rename).xy(1, 19); - builder.add(textFieldRename).xy(3, 19); - builder.add(overwrite).xyw(1, 21, 3); - - ButtonBarBuilder bb = new ButtonBarBuilder(); - bb.addGlue(); - bb.addButton(ok); - bb.addButton(cancel); - bb.addGlue(); - builder.getPanel().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - bb.getPanel().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - diag.getContentPane().add(builder.getPanel(), BorderLayout.CENTER); - diag.getContentPane().add(bb.getPanel(), BorderLayout.SOUTH); - diag.pack(); - - ok.addActionListener(e -> { - // Check that any field name is set - String fieldText = (String) field.getSelectedItem(); - if ((fieldText == null) || fieldText.trim().isEmpty()) { - - frame.getDialogService().showErrorDialogAndWait(Localization.lang("You must enter at least one field name")); - - return; // Do not close the dialog. - } - - // Check if the user tries to rename multiple fields: - if (rename.isSelected()) { - String[] fields = getFieldNames(fieldText); - if (fields.length > 1) { - - frame.getDialogService().showErrorDialogAndWait(Localization.lang("You can only rename one field at a time")); - - return; // Do not close the dialog. - } - } - canceled = false; - diag.dispose(); - }); - - Action cancelAction = new AbstractAction() { - - @Override - public void actionPerformed(ActionEvent e) { - canceled = true; - diag.dispose(); - } - }; - cancel.addActionListener(cancelAction); - - // Key bindings: - ActionMap am = builder.getPanel().getActionMap(); - InputMap im = builder.getPanel().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE), "close"); - am.put("close", cancelAction); - } - - private static String[] getFieldNames(String s) { - return s.split("[\\s;,]"); - } -} diff --git a/src/main/java/org/jabref/gui/actions/NewEntryAction.java b/src/main/java/org/jabref/gui/actions/NewEntryAction.java index dd8c0add054..a5b483a4d67 100644 --- a/src/main/java/org/jabref/gui/actions/NewEntryAction.java +++ b/src/main/java/org/jabref/gui/actions/NewEntryAction.java @@ -4,17 +4,19 @@ import java.util.Map; import java.util.Optional; -import javax.swing.SwingUtilities; - import org.jabref.Globals; -import org.jabref.gui.EntryTypeDialog; +import org.jabref.gui.DialogService; +import org.jabref.gui.EntryTypeView; import org.jabref.gui.JabRefFrame; +import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.EntryType; +import org.jabref.preferences.JabRefPreferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class NewEntryAction extends SimpleCommand { + private static final Logger LOGGER = LoggerFactory.getLogger(NewEntryAction.class); private final JabRefFrame jabRefFrame; @@ -22,16 +24,21 @@ public class NewEntryAction extends SimpleCommand { * The type of the entry to create. */ private final Optional type; + private final DialogService dialogService; + private final JabRefPreferences preferences; - - public NewEntryAction(JabRefFrame jabRefFrame) { + public NewEntryAction(JabRefFrame jabRefFrame, DialogService dialogService, JabRefPreferences preferences) { this.jabRefFrame = jabRefFrame; this.type = Optional.empty(); + this.dialogService = dialogService; + this.preferences = preferences; } - public NewEntryAction(JabRefFrame jabRefFrame, EntryType type) { + public NewEntryAction(JabRefFrame jabRefFrame, EntryType type, DialogService dialogService, JabRefPreferences preferences) { this.jabRefFrame = jabRefFrame; this.type = Optional.of(type); + this.dialogService = dialogService; + this.preferences = preferences; } @Override @@ -42,27 +49,23 @@ public void execute() { } if (type.isPresent()) { - jabRefFrame.getCurrentBasePanel().newEntry(type.get()); + jabRefFrame.getCurrentBasePanel().insertEntry(new BibEntry(type.get())); } else { - SwingUtilities.invokeLater(() -> { - EntryTypeDialog typeChoiceDialog = new EntryTypeDialog(jabRefFrame); - typeChoiceDialog.setVisible(true); - EntryType selectedType = typeChoiceDialog.getChoice(); - if (selectedType == null) { - return; - } + EntryTypeView typeChoiceDialog = new EntryTypeView(jabRefFrame.getCurrentBasePanel(), dialogService, preferences); + EntryType selectedType = typeChoiceDialog.showAndWait().orElse(null); + if (selectedType == null) { + return; + } - trackNewEntry(selectedType); - jabRefFrame.getCurrentBasePanel().newEntry(selectedType); - }); + trackNewEntry(selectedType); + jabRefFrame.getCurrentBasePanel().insertEntry(new BibEntry(selectedType)); } } private void trackNewEntry(EntryType type) { Map properties = new HashMap<>(); properties.put("EntryType", type.getName()); - Map measurements = new HashMap<>(); - Globals.getTelemetryClient().ifPresent(client -> client.trackEvent("NewEntry", properties, measurements)); + Globals.getTelemetryClient().ifPresent(client -> client.trackEvent("NewEntry", properties, new HashMap<>())); } } diff --git a/src/main/java/org/jabref/gui/actions/NewEntryFromPlainTextAction.java b/src/main/java/org/jabref/gui/actions/NewEntryFromPlainTextAction.java index 0c048ea7ed8..b4c3faa9a2b 100644 --- a/src/main/java/org/jabref/gui/actions/NewEntryFromPlainTextAction.java +++ b/src/main/java/org/jabref/gui/actions/NewEntryFromPlainTextAction.java @@ -1,12 +1,14 @@ package org.jabref.gui.actions; -import org.jabref.gui.EntryTypeDialog; +import org.jabref.gui.DialogService; +import org.jabref.gui.EntryTypeView; import org.jabref.gui.JabRefFrame; import org.jabref.gui.plaintextimport.TextInputDialog; import org.jabref.logic.util.UpdateField; import org.jabref.logic.util.UpdateFieldPreferences; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.EntryType; +import org.jabref.preferences.JabRefPreferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,10 +19,14 @@ public class NewEntryFromPlainTextAction extends SimpleCommand { private final UpdateFieldPreferences prefs; private final JabRefFrame jabRefFrame; + private final DialogService dialogService; + private final JabRefPreferences preferences; - public NewEntryFromPlainTextAction(JabRefFrame jabRefFrame, UpdateFieldPreferences prefs) { + public NewEntryFromPlainTextAction(JabRefFrame jabRefFrame, UpdateFieldPreferences prefs, DialogService dialogService, JabRefPreferences preferences) { this.jabRefFrame = jabRefFrame; this.prefs = prefs; + this.dialogService = dialogService; + this.preferences = preferences; } @@ -31,13 +37,12 @@ public void execute() { return; } - EntryTypeDialog typeChoiceDialog = new EntryTypeDialog(jabRefFrame); - typeChoiceDialog.setVisible(true); - EntryType selectedType = typeChoiceDialog.getChoice(); + EntryTypeView typeChoiceDialog = new EntryTypeView(jabRefFrame.getCurrentBasePanel(), dialogService, preferences); + EntryType selectedType = typeChoiceDialog.showAndWait().orElse(null); if (selectedType == null) { return; } - BibEntry bibEntry = new BibEntry(selectedType.getName()); + BibEntry bibEntry = new BibEntry(selectedType); TextInputDialog tidialog = new TextInputDialog(jabRefFrame, bibEntry); tidialog.setVisible(true); diff --git a/src/main/java/org/jabref/gui/actions/SearchForUpdateAction.java b/src/main/java/org/jabref/gui/actions/SearchForUpdateAction.java index 5a8dc415b74..be3415c2120 100644 --- a/src/main/java/org/jabref/gui/actions/SearchForUpdateAction.java +++ b/src/main/java/org/jabref/gui/actions/SearchForUpdateAction.java @@ -1,11 +1,28 @@ package org.jabref.gui.actions; -import org.jabref.JabRefGUI; +import org.jabref.gui.DialogService; +import org.jabref.gui.help.VersionWorker; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.util.BuildInfo; +import org.jabref.preferences.VersionPreferences; public class SearchForUpdateAction extends SimpleCommand { + private final BuildInfo buildInfo; + private final VersionPreferences versionPreferences; + private final DialogService dialogService; + private final TaskExecutor taskExecutor; + + public SearchForUpdateAction(BuildInfo buildInfo, VersionPreferences versionPreferences, DialogService dialogService, TaskExecutor taskExecutor) { + this.buildInfo = buildInfo; + this.versionPreferences = versionPreferences; + this.dialogService = dialogService; + this.taskExecutor = taskExecutor; + } + @Override public void execute() { - JabRefGUI.checkForNewVersion(true); + new VersionWorker(buildInfo.getVersion(), versionPreferences.getIgnoredVersion(), dialogService, taskExecutor) + .checkForNewVersionAsync(true); } } diff --git a/src/main/java/org/jabref/gui/actions/SetupGeneralFieldsAction.java b/src/main/java/org/jabref/gui/actions/SetupGeneralFieldsAction.java index 3f75fc14c20..a6254ef288c 100644 --- a/src/main/java/org/jabref/gui/actions/SetupGeneralFieldsAction.java +++ b/src/main/java/org/jabref/gui/actions/SetupGeneralFieldsAction.java @@ -1,20 +1,12 @@ package org.jabref.gui.actions; -import org.jabref.gui.GenFieldsCustomizer; -import org.jabref.gui.JabRefFrame; +import org.jabref.gui.customizefields.CustomizeGeneralFieldsDialogView; public class SetupGeneralFieldsAction extends SimpleCommand { - private final JabRefFrame jabRefFrame; - - public SetupGeneralFieldsAction(JabRefFrame jabRefFrame) { - this.jabRefFrame = jabRefFrame; - } - @Override public void execute() { - GenFieldsCustomizer gf = new GenFieldsCustomizer(jabRefFrame); - gf.setVisible(true); + new CustomizeGeneralFieldsDialogView().show(); } diff --git a/src/main/java/org/jabref/gui/actions/WriteXMPAction.java b/src/main/java/org/jabref/gui/actions/WriteXMPAction.java index e2d549e2c4e..de4face8b53 100644 --- a/src/main/java/org/jabref/gui/actions/WriteXMPAction.java +++ b/src/main/java/org/jabref/gui/actions/WriteXMPAction.java @@ -1,9 +1,5 @@ package org.jabref.gui.actions; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.event.ActionEvent; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; @@ -11,31 +7,29 @@ import java.util.Optional; import java.util.stream.Collectors; -import javax.swing.AbstractAction; -import javax.swing.ActionMap; -import javax.swing.BorderFactory; -import javax.swing.InputMap; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; -import javax.swing.ScrollPaneConstants; -import javax.swing.SwingUtilities; +import javafx.application.Platform; +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextArea; +import javafx.scene.input.KeyCode; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.GridPane; +import javafx.scene.paint.Color; +import javafx.stage.Stage; import org.jabref.Globals; import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; -import org.jabref.gui.JabRefDialog; -import org.jabref.gui.keyboard.KeyBinding; +import org.jabref.gui.FXDialog; import org.jabref.gui.util.BackgroundTask; import org.jabref.logic.l10n.Localization; import org.jabref.logic.xmp.XmpUtilWriter; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; -import com.jgoodies.forms.builder.ButtonBarBuilder; - public class WriteXMPAction extends SimpleCommand { private final BasePanel basePanel; @@ -111,54 +105,54 @@ private void writeXMP() { // Make a list of all PDFs linked from this entry: List files = entry.getFiles().stream() .filter(file -> file.getFileType().equalsIgnoreCase("pdf")) - .map(file -> file.findIn(basePanel.getBibDatabaseContext(), Globals.prefs.getFileDirectoryPreferences())) + .map(file -> file.findIn(basePanel.getBibDatabaseContext(), Globals.prefs.getFilePreferences())) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); - SwingUtilities.invokeLater(() -> optionsDialog.getProgressArea() - .append(entry.getCiteKeyOptional().orElse(Localization.lang("undefined")) + "\n")); + Platform.runLater(() -> optionsDialog.getProgressArea() + .appendText(entry.getCiteKeyOptional().orElse(Localization.lang("undefined")) + "\n")); if (files.isEmpty()) { skipped++; - SwingUtilities.invokeLater(() -> optionsDialog.getProgressArea() - .append(" " + Localization.lang("Skipped - No PDF linked") + ".\n")); + Platform.runLater(() -> optionsDialog.getProgressArea() + .appendText(" " + Localization.lang("Skipped - No PDF linked") + ".\n")); } else { for (Path file : files) { if (Files.exists(file)) { try { XmpUtilWriter.writeXmp(file, entry, database, Globals.prefs.getXMPPreferences()); - SwingUtilities.invokeLater( - () -> optionsDialog.getProgressArea().append(" " + Localization.lang("OK") + ".\n")); + Platform.runLater( + () -> optionsDialog.getProgressArea().appendText(" " + Localization.lang("OK") + ".\n")); entriesChanged++; } catch (Exception e) { - SwingUtilities.invokeLater(() -> { - optionsDialog.getProgressArea().append(" " + Localization.lang("Error while writing") + " '" + Platform.runLater(() -> { + optionsDialog.getProgressArea().appendText(" " + Localization.lang("Error while writing") + " '" + file.toString() + "':\n"); - optionsDialog.getProgressArea().append(" " + e.getLocalizedMessage() + "\n"); + optionsDialog.getProgressArea().appendText(" " + e.getLocalizedMessage() + "\n"); }); errors++; } } else { skipped++; - SwingUtilities.invokeLater(() -> { + Platform.runLater(() -> { optionsDialog.getProgressArea() - .append(" " + Localization.lang("Skipped - PDF does not exist") + ":\n"); - optionsDialog.getProgressArea().append(" " + file.toString() + "\n"); + .appendText(" " + Localization.lang("Skipped - PDF does not exist") + ":\n"); + optionsDialog.getProgressArea().appendText(" " + file.toString() + "\n"); }); } } } if (optionsDialog.isCanceled()) { - SwingUtilities.invokeLater( - () -> optionsDialog.getProgressArea().append("\n" + Localization.lang("Operation canceled.") + "\n")); + Platform.runLater( + () -> optionsDialog.getProgressArea().appendText("\n" + Localization.lang("Operation canceled.") + "\n")); break; } } - SwingUtilities.invokeLater(() -> { + Platform.runLater(() -> { optionsDialog.getProgressArea() - .append("\n" + .appendText("\n" + Localization.lang("Finished writing XMP for %0 file (%1 skipped, %2 errors).", String .valueOf(entriesChanged), String.valueOf(skipped), String.valueOf(errors))); optionsDialog.done(); @@ -172,92 +166,71 @@ private void writeXMP() { String.valueOf(entriesChanged), String.valueOf(skipped), String.valueOf(errors))); } - class OptionsDialog extends JabRefDialog { + class OptionsDialog extends FXDialog { - private final JButton okButton = new JButton(Localization.lang("OK")); - private final JButton cancelButton = new JButton(Localization.lang("Cancel")); + private final Button okButton = new Button(Localization.lang("OK")); + private final Button cancelButton = new Button(Localization.lang("Cancel")); private boolean isCancelled; - private final JTextArea progressArea; + private final TextArea progressArea; public OptionsDialog() { - super(Localization.lang("Writing XMP-metadata for selected entries..."), false, WriteXMPAction.OptionsDialog.class); - okButton.setEnabled(false); - - okButton.addActionListener(e -> dispose()); - - AbstractAction cancel = new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { + super(AlertType.NONE, Localization.lang("Writing XMP-metadata for selected entries..."), false); + okButton.setDisable(true); + okButton.setOnAction(e -> dispose()); + okButton.setPrefSize(100, 30); + cancelButton.setOnAction(e -> isCancelled = true); + cancelButton.setOnKeyPressed(e -> { + if (e.getCode() == KeyCode.ESCAPE) { isCancelled = true; } - }; - cancelButton.addActionListener(cancel); - - InputMap im = cancelButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); - ActionMap am = cancelButton.getActionMap(); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE), "close"); - am.put("close", cancel); - - progressArea = new JTextArea(15, 60); - - JScrollPane scrollPane = new JScrollPane(progressArea, - ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - Dimension d = progressArea.getPreferredSize(); - d.height += scrollPane.getHorizontalScrollBar().getHeight() + 15; - d.width += scrollPane.getVerticalScrollBar().getWidth() + 15; - - progressArea.setBackground(Color.WHITE); + }); + cancelButton.setPrefSize(100, 30); + progressArea = new TextArea(); + ScrollPane scrollPane = new ScrollPane(progressArea); + progressArea.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY))); progressArea.setEditable(false); - progressArea.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, - 3)); progressArea.setText(""); - JPanel tmpPanel = new JPanel(); - tmpPanel.setBorder(BorderFactory.createEmptyBorder(3, 2, 3, 2)); - tmpPanel.add(scrollPane); - - // progressArea.setPreferredSize(new Dimension(300, 300)); - - ButtonBarBuilder bb = new ButtonBarBuilder(); - bb.addGlue(); - bb.addButton(okButton); - bb.addRelatedGap(); - bb.addButton(cancelButton); - bb.addGlue(); - JPanel bbPanel = bb.getPanel(); - bbPanel.setBorder(BorderFactory.createEmptyBorder(0, 3, 3, 3)); - getContentPane().add(tmpPanel, BorderLayout.CENTER); - getContentPane().add(bbPanel, BorderLayout.SOUTH); - - pack(); + GridPane tmpPanel = new GridPane(); + getDialogPane().setContent(tmpPanel); + tmpPanel.setHgap(450); + tmpPanel.setVgap(10); + tmpPanel.add(scrollPane, 0, 0, 2, 1); + tmpPanel.add(okButton, 0, 1); + tmpPanel.add(cancelButton, 1, 1); + tmpPanel.setGridLinesVisible(false); this.setResizable(false); } + private void dispose() { + ((Stage) (getDialogPane().getScene().getWindow())).close(); + } + public void done() { - okButton.setEnabled(true); - cancelButton.setEnabled(false); + okButton.setDisable(false); + cancelButton.setDisable(true); } public void open() { progressArea.setText(""); isCancelled = false; - okButton.setEnabled(false); - cancelButton.setEnabled(true); + okButton.setDisable(true); + cancelButton.setDisable(false); okButton.requestFocus(); - optionsDialog.setVisible(true); + optionsDialog.show(); } public boolean isCanceled() { return isCancelled; } - public JTextArea getProgressArea() { + public TextArea getProgressArea() { return progressArea; } } diff --git a/src/main/java/org/jabref/gui/bibtexkeypattern/BibtexKeyPatternPanel.java b/src/main/java/org/jabref/gui/bibtexkeypattern/BibtexKeyPatternPanel.java index daf7a4ac105..08a101c740d 100644 --- a/src/main/java/org/jabref/gui/bibtexkeypattern/BibtexKeyPatternPanel.java +++ b/src/main/java/org/jabref/gui/bibtexkeypattern/BibtexKeyPatternPanel.java @@ -36,6 +36,8 @@ public class BibtexKeyPatternPanel extends Pane { protected final TextField defaultPat = new TextField(); private final HelpAction help; + private final int COLUMNS = 2; + // one field for each type private final Map textFields = new HashMap<>(); private final BasePanel panel; @@ -44,25 +46,12 @@ public class BibtexKeyPatternPanel extends Pane { public BibtexKeyPatternPanel(BasePanel panel) { this.panel = panel; help = new HelpAction(Localization.lang("Help on key patterns"), HelpFile.BIBTEX_KEY_PATTERN); + gridPane.setHgap(10); + gridPane.setVgap(5); buildGUI(); } private void buildGUI() { - // The header - can be removed - Label label = new Label(Localization.lang("Entry type")); - gridPane.add(label, 1, 1); - - Label keyPattern = new Label(Localization.lang("Key pattern")); - gridPane.add(keyPattern, 3, 1); - - Label defaultPattern = new Label(Localization.lang("Default pattern")); - gridPane.add(defaultPattern, 1, 2); - gridPane.add(defaultPat, 3, 2); - - Button button = new Button("Default"); - button.setOnAction(e-> defaultPat.setText((String) Globals.prefs.defaults.get(JabRefPreferences.DEFAULT_BIBTEX_KEY_PATTERN))); - gridPane.add(button, 4, 2); - BibDatabaseMode mode; // check mode of currently used DB if (panel != null) { @@ -72,24 +61,47 @@ private void buildGUI() { mode = Globals.prefs.getDefaultBibDatabaseMode(); } - int rowIndex = 3; + int rowIndex = 1; + int columnIndex = 0; + // The header - can be removed + for (int i = 0; i < COLUMNS; i++) { + Label label = new Label(Localization.lang("Entry type")); + Label keyPattern = new Label(Localization.lang("Key pattern")); + gridPane.add(label, ++columnIndex, rowIndex); + gridPane.add(keyPattern, ++columnIndex, rowIndex); + ++columnIndex; //3 + } + + rowIndex++; + Label defaultPattern = new Label(Localization.lang("Default pattern")); + Button button = new Button("Default"); + button.setOnAction(e-> defaultPat.setText((String) Globals.prefs.defaults.get(JabRefPreferences.DEFAULT_BIBTEX_KEY_PATTERN))); + gridPane.add(defaultPattern, 1, rowIndex); + gridPane.add(defaultPat, 2, rowIndex); + gridPane.add(button, 3, rowIndex); + + columnIndex = 1; for (EntryType type : EntryTypes.getAllValues(mode)) { Label label1 = new Label(type.getName()); - TextField textField = new TextField(); - Button button1 = new Button("Default"); button1.setOnAction(e1 -> textField.setText((String) Globals.prefs.defaults.get(JabRefPreferences.DEFAULT_BIBTEX_KEY_PATTERN))); - gridPane.add(label1, 1, rowIndex); - gridPane.add(textField, 3, rowIndex); - gridPane.add(button1, 4, rowIndex); + gridPane.add(label1, 1 + (columnIndex * 3) , rowIndex); + gridPane.add(textField, 2 + (columnIndex * 3), rowIndex); + gridPane.add(button1, 3 + (columnIndex * 3), rowIndex); textFields.put(type.getName().toLowerCase(Locale.ROOT), textField); - rowIndex++; + if (columnIndex == COLUMNS - 1) { + columnIndex = 0; + rowIndex++; + } else + columnIndex++; } + rowIndex++; + Button help1 = new Button("?"); help1.setOnAction(e->new HelpAction(Localization.lang("Help on key patterns"), HelpFile.BIBTEX_KEY_PATTERN).getHelpButton().doClick()); gridPane.add(help1, 1, rowIndex); @@ -102,9 +114,10 @@ private void buildGUI() { } defaultPat.setText((String) Globals.prefs.defaults.get(JabRefPreferences.DEFAULT_BIBTEX_KEY_PATTERN)); }); - gridPane.add(btnDefaultAll1, 3, rowIndex); + gridPane.add(btnDefaultAll1, 2, rowIndex); } + /** * fill the given LabelPattern by values generated from the text fields */ diff --git a/src/main/java/org/jabref/gui/cleanup/CleanupDialog.java b/src/main/java/org/jabref/gui/cleanup/CleanupDialog.java index 4ac358e8434..4e672761366 100644 --- a/src/main/java/org/jabref/gui/cleanup/CleanupDialog.java +++ b/src/main/java/org/jabref/gui/cleanup/CleanupDialog.java @@ -1,11 +1,8 @@ package org.jabref.gui.cleanup; -import javax.swing.JScrollPane; - import javafx.scene.control.ButtonType; import org.jabref.gui.util.BaseDialog; -import org.jabref.gui.util.ControlHelper; import org.jabref.logic.cleanup.CleanupPreset; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; @@ -18,9 +15,7 @@ public CleanupDialog(BibDatabaseContext databaseContext, CleanupPreset initialPr getDialogPane().getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL); CleanupPresetPanel presetPanel = new CleanupPresetPanel(databaseContext, initialPreset); - presetPanel.getScrollPane().setVisible(true); - JScrollPane scrollPane = presetPanel.getScrollPane(); - + getDialogPane().setContent(presetPanel); setResultConverter(button -> { if (button == ButtonType.OK) { return presetPanel.getCleanupPreset(); @@ -28,7 +23,5 @@ public CleanupDialog(BibDatabaseContext databaseContext, CleanupPreset initialPr return null; } }); - - ControlHelper.setSwingContent(getDialogPane(), scrollPane); } } diff --git a/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java b/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java index 32c3dbeb61b..4bd64ec1f62 100644 --- a/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java +++ b/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java @@ -6,11 +6,11 @@ import java.util.Optional; import java.util.Set; -import javax.swing.ButtonGroup; -import javax.swing.JCheckBox; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; +import javafx.scene.Group; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.GridPane; import org.jabref.Globals; import org.jabref.logic.cleanup.CleanupPreset; @@ -20,25 +20,20 @@ import org.jabref.model.entry.FieldName; import org.jabref.preferences.JabRefPreferences; -import com.jgoodies.forms.builder.FormBuilder; -import com.jgoodies.forms.layout.FormLayout; - -public class CleanupPresetPanel { +public class CleanupPresetPanel extends ScrollPane { private final BibDatabaseContext databaseContext; - private JCheckBox cleanUpDOI; - private JCheckBox cleanUpISSN; - private JCheckBox cleanUpMovePDF; - private JCheckBox cleanUpMakePathsRelative; - private JCheckBox cleanUpRenamePDF; - private JCheckBox cleanUpRenamePDFonlyRelativePaths; - private JCheckBox cleanUpUpgradeExternalLinks; - private JCheckBox cleanUpBiblatex; - private JCheckBox cleanUpBibtex; + private CheckBox cleanUpDOI; + private CheckBox cleanUpISSN; + private CheckBox cleanUpMovePDF; + private CheckBox cleanUpMakePathsRelative; + private CheckBox cleanUpRenamePDF; + private CheckBox cleanUpRenamePDFonlyRelativePaths; + private CheckBox cleanUpUpgradeExternalLinks; + private CheckBox cleanUpBiblatex; + private CheckBox cleanUpBibtex; private FieldFormatterCleanupsPanel cleanUpFormatters; - private JPanel panel; - private JScrollPane scrollPane; private CleanupPreset cleanupPreset; public CleanupPresetPanel(BibDatabaseContext databaseContext, CleanupPreset cleanupPreset) { @@ -48,76 +43,69 @@ public CleanupPresetPanel(BibDatabaseContext databaseContext, CleanupPreset clea } private void init() { - cleanUpDOI = new JCheckBox( + cleanUpDOI = new CheckBox( Localization.lang("Move DOIs from note and URL field to DOI field and remove http prefix")); - cleanUpISSN = new JCheckBox(Localization.lang("Reformat ISSN")); - + cleanUpISSN = new CheckBox(Localization.lang("Reformat ISSN")); Optional firstExistingDir = databaseContext - .getFirstExistingFileDir(JabRefPreferences.getInstance().getFileDirectoryPreferences()); + .getFirstExistingFileDir(JabRefPreferences.getInstance().getFilePreferences()); if (firstExistingDir.isPresent()) { - cleanUpMovePDF = new JCheckBox(Localization.lang("Move linked files to default file directory %0", + cleanUpMovePDF = new CheckBox(Localization.lang("Move linked files to default file directory %0", firstExistingDir.get().toString())); } else { - cleanUpMovePDF = new JCheckBox(Localization.lang("Move linked files to default file directory %0", "...")); - cleanUpMovePDF.setEnabled(false); + cleanUpMovePDF = new CheckBox(Localization.lang("Move linked files to default file directory %0", "...")); + cleanUpMovePDF.setDisable(true); // Since the directory does not exist, we cannot move it to there. So, this option is not checked - regardless of the presets stored in the preferences. cleanUpMovePDF.setSelected(false); } - - cleanUpMakePathsRelative = new JCheckBox( + cleanUpMakePathsRelative = new CheckBox( Localization.lang("Make paths of linked files relative (if possible)")); - cleanUpRenamePDF = new JCheckBox(Localization.lang("Rename PDFs to given filename format pattern")); - cleanUpRenamePDF.addChangeListener( - event -> cleanUpRenamePDFonlyRelativePaths.setEnabled(cleanUpRenamePDF.isSelected())); - cleanUpRenamePDFonlyRelativePaths = new JCheckBox(Localization.lang("Rename only PDFs having a relative path")); - cleanUpUpgradeExternalLinks = new JCheckBox( + cleanUpRenamePDF = new CheckBox(Localization.lang("Rename PDFs to given filename format pattern")); + cleanUpRenamePDF.selectedProperty().addListener( + event -> cleanUpRenamePDFonlyRelativePaths.setDisable(!cleanUpRenamePDF.isSelected())); + cleanUpRenamePDFonlyRelativePaths = new CheckBox(Localization.lang("Rename only PDFs having a relative path")); + cleanUpUpgradeExternalLinks = new CheckBox( Localization.lang("Upgrade external PDF/PS links to use the '%0' field.", FieldName.FILE)); - cleanUpBiblatex = new JCheckBox(Localization.lang( + cleanUpBiblatex = new CheckBox(Localization.lang( "Convert to biblatex format (for example, move the value of the 'journal' field to 'journaltitle')")); - cleanUpBibtex = new JCheckBox(Localization.lang( + cleanUpBibtex = new CheckBox(Localization.lang( "Convert to BibTeX format (for example, move the value of the 'journaltitle' field to 'journal')")); - ButtonGroup biblatexConversion = new ButtonGroup(); // Only make "to Biblatex" or "to BibTeX" selectable - biblatexConversion.add(cleanUpBiblatex); - biblatexConversion.add(cleanUpBibtex); + Group biblatexConversion = new Group(); // Only make "to Biblatex" or "to BibTeX" selectable + biblatexConversion.getChildren().add(cleanUpBiblatex); + biblatexConversion.getChildren().add(cleanUpBibtex); cleanUpFormatters = new FieldFormatterCleanupsPanel(Localization.lang("Run field formatter:"), Cleanups.DEFAULT_SAVE_ACTIONS); updateDisplay(cleanupPreset); - FormLayout layout = new FormLayout("left:15dlu, fill:pref:grow", - "pref, pref, pref, pref, pref, fill:pref:grow, pref,pref, pref, pref,190dlu, fill:pref:grow,"); - - FormBuilder builder = FormBuilder.create().layout(layout); - builder.add(cleanUpDOI).xyw(1, 1, 2); - builder.add(cleanUpUpgradeExternalLinks).xyw(1, 2, 2); - builder.add(cleanUpMovePDF).xyw(1, 3, 2); - builder.add(cleanUpMakePathsRelative).xyw(1, 4, 2); - builder.add(cleanUpRenamePDF).xyw(1, 5, 2); + GridPane container = new GridPane(); + container.add(cleanUpDOI, 0, 0); + container.add(cleanUpUpgradeExternalLinks, 0, 1); + container.add(cleanUpMovePDF, 0, 2); + container.add(cleanUpMakePathsRelative, 0, 3); + container.add(cleanUpRenamePDF, 0, 4); String currentPattern = Localization.lang("Filename format pattern").concat(": "); currentPattern = currentPattern.concat(Globals.prefs.get(JabRefPreferences.IMPORT_FILENAMEPATTERN)); - builder.add(new JLabel(currentPattern)).xy(2, 6); - builder.add(cleanUpRenamePDFonlyRelativePaths).xy(2, 7); - builder.add(cleanUpBibtex).xyw(1, 8, 2); - builder.add(cleanUpBiblatex).xyw(1, 9, 2); - builder.add(cleanUpISSN).xyw(1, 10, 2); - builder.add(cleanUpFormatters).xyw(1, 11, 2); - panel = builder.build(); - scrollPane = new JScrollPane(panel); - scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); - scrollPane.setVisible(true); - scrollPane.setBorder(null); + container.add(new Label(currentPattern), 0, 5); + container.add(cleanUpRenamePDFonlyRelativePaths, 0, 6); + container.add(cleanUpBibtex, 0, 7); + container.add(cleanUpBiblatex, 0, 8); + container.add(cleanUpISSN, 0, 9); + container.add(cleanUpFormatters, 0, 10); + + setContent(container); + setVbarPolicy(ScrollBarPolicy.AS_NEEDED); } private void updateDisplay(CleanupPreset preset) { cleanUpDOI.setSelected(preset.isCleanUpDOI()); - if (cleanUpMovePDF.isEnabled()) { + if (!cleanUpMovePDF.isDisabled()) { cleanUpMovePDF.setSelected(preset.isMovePDF()); } cleanUpMakePathsRelative.setSelected(preset.isMakePathsRelative()); cleanUpRenamePDF.setSelected(preset.isRenamePDF()); cleanUpRenamePDFonlyRelativePaths.setSelected(preset.isRenamePdfOnlyRelativePaths()); - cleanUpRenamePDFonlyRelativePaths.setEnabled(cleanUpRenamePDF.isSelected()); + cleanUpRenamePDFonlyRelativePaths.setDisable(!cleanUpRenamePDF.isSelected()); cleanUpUpgradeExternalLinks.setSelected(preset.isCleanUpUpgradeExternalLinks()); cleanUpBiblatex.setSelected(preset.isConvertToBiblatex()); cleanUpBibtex.setSelected(preset.isConvertToBibtex()); @@ -125,10 +113,6 @@ private void updateDisplay(CleanupPreset preset) { cleanUpFormatters.setValues(preset.getFormatterCleanups()); } - public JScrollPane getScrollPane() { - return scrollPane; - } - public CleanupPreset getCleanupPreset() { Set activeJobs = EnumSet.noneOf(CleanupPreset.CleanupStep.class); diff --git a/src/main/java/org/jabref/gui/cleanup/FieldFormatterCleanupsPanel.java b/src/main/java/org/jabref/gui/cleanup/FieldFormatterCleanupsPanel.java index 9f91f376465..b2aaec4108c 100644 --- a/src/main/java/org/jabref/gui/cleanup/FieldFormatterCleanupsPanel.java +++ b/src/main/java/org/jabref/gui/cleanup/FieldFormatterCleanupsPanel.java @@ -1,32 +1,27 @@ package org.jabref.gui.cleanup; -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionAdapter; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; - -import javax.swing.DefaultListCellRenderer; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JList; -import javax.swing.JPanel; -import javax.swing.JTextArea; -import javax.swing.ListSelectionModel; -import javax.swing.UIManager; -import javax.swing.event.ListDataEvent; -import javax.swing.event.ListDataListener; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.control.SelectionMode; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.RowConstraints; import org.jabref.Globals; import org.jabref.JabRefGUI; +import org.jabref.gui.util.ViewModelListCellFactory; import org.jabref.logic.cleanup.Cleanups; import org.jabref.logic.formatter.casechanger.ProtectTermsFormatter; import org.jabref.logic.l10n.Localization; @@ -38,30 +33,29 @@ import org.jabref.model.entry.InternalBibtexFields; import org.jabref.model.metadata.MetaData; -import com.jgoodies.forms.builder.FormBuilder; -import com.jgoodies.forms.layout.FormLayout; +import org.fxmisc.easybind.EasyBind; -public class FieldFormatterCleanupsPanel extends JPanel { +public class FieldFormatterCleanupsPanel extends GridPane { private static final String DESCRIPTION = Localization.lang("Description") + ": "; - private final JCheckBox cleanupEnabled; + private final CheckBox cleanupEnabled; private FieldFormatterCleanups fieldFormatterCleanups; - private JList actionsList; - private JComboBox formattersCombobox; - private JComboBox selectFieldCombobox; - private JButton addButton; - private JTextArea descriptionAreaText; - private JButton removeButton; - private JButton resetButton; - private JButton recommendButton; + private ListView actionsList; + private ComboBox formattersCombobox; + private ComboBox selectFieldCombobox; + private Button addButton; + private Label descriptionAreaText; + private Button removeButton; + private Button resetButton; + private Button recommendButton; private final FieldFormatterCleanups defaultFormatters; - private List availableFormatters; - + private final List availableFormatters; + private ObservableList actions; public FieldFormatterCleanupsPanel(String description, FieldFormatterCleanups defaultFormatters) { this.defaultFormatters = Objects.requireNonNull(defaultFormatters); - cleanupEnabled = new JCheckBox(description); + cleanupEnabled = new CheckBox(description); availableFormatters = Cleanups.getBuiltInFormatters(); availableFormatters.add(new ProtectTermsFormatter(Globals.protectedTermsLoader)); } @@ -76,127 +70,84 @@ public void setValues(FieldFormatterCleanups formatterCleanups) { fieldFormatterCleanups = formatterCleanups; // first clear existing content - this.removeAll(); + this.getChildren().clear(); List configuredActions = fieldFormatterCleanups.getConfiguredActions(); - //The copy is necessary because the original List is unmodifiable - List actionsToDisplay = new ArrayList<>(configuredActions); - buildLayout(actionsToDisplay); - + actions = FXCollections.observableArrayList(configuredActions); + buildLayout(); } - private void buildLayout(List actionsToDisplay) { - FormBuilder builder = FormBuilder.create().layout(new FormLayout( - "left:pref, 13dlu, left:pref:grow, 4dlu, pref, 4dlu, pref", - "pref, 2dlu, pref, 2dlu, pref, 4dlu, pref, 2dlu, fill:pref:grow, 2dlu, pref, 2dlu, pref, 2dlu, pref, 2dlu, pref, 2dlu")); - builder.add(cleanupEnabled).xyw(1, 1, 7); - - actionsList = new JList<>(new CleanupActionsListModel(actionsToDisplay)); - actionsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - actionsList.addMouseMotionListener(new MouseMotionAdapter() { - - @Override - public void mouseMoved(MouseEvent e) { - super.mouseMoved(e); - CleanupActionsListModel m = (CleanupActionsListModel) actionsList.getModel(); - int index = actionsList.locationToIndex(e.getPoint()); - if (index > -1) { - actionsList.setToolTipText(m.getElementAt(index).getFormatter().getDescription()); - } - } - }); - - actionsList.getModel().addListDataListener(new ListDataListener() { - - @Override - public void intervalRemoved(ListDataEvent e) { - //index0 is sufficient, because of SingleSelection - if (e.getIndex0() == 0) { - //when an item gets deleted, the next one becomes the new 0 - actionsList.setSelectedIndex(e.getIndex0()); - } - if (e.getIndex0() > 0) { - actionsList.setSelectedIndex(e.getIndex0() - 1); - } - - } - - @Override - public void intervalAdded(ListDataEvent e) { - //empty, not needed - - } - - @Override - public void contentsChanged(ListDataEvent e) { - //empty, not needed - - } - }); - - builder.add(actionsList).xyw(3, 5, 5); - - resetButton = new JButton(Localization.lang("Reset")); - resetButton.addActionListener(e -> ((CleanupActionsListModel) actionsList.getModel()).reset(defaultFormatters)); + private void buildLayout() { + ColumnConstraints first = new ColumnConstraints(); + first.setPrefWidth(25); + ColumnConstraints second = new ColumnConstraints(); + second.setPrefWidth(175); + ColumnConstraints third = new ColumnConstraints(); + third.setPrefWidth(200); + ColumnConstraints fourth = new ColumnConstraints(); + fourth.setPrefWidth(200); + getColumnConstraints().addAll(first, second, third, fourth); + RowConstraints firstR = new RowConstraints(); + firstR.setPrefHeight(25); + RowConstraints secondR = new RowConstraints(); + secondR.setPrefHeight(100); + RowConstraints thirdR = new RowConstraints(); + thirdR.setPrefHeight(50); + RowConstraints fourthR = new RowConstraints(); + fourthR.setPrefHeight(50); + RowConstraints fifthR = new RowConstraints(); + fifthR.setPrefHeight(50); + getRowConstraints().addAll(firstR, secondR, thirdR, fourthR, fifthR); + add(cleanupEnabled, 0, 0, 4, 1); + + actionsList = new ListView<>(actions); + actionsList.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); + new ViewModelListCellFactory() + .withText(action -> action.getFormatter().getName()) + .withTooltip(action -> action.getFormatter().getDescription()) + .install(actionsList); + add(actionsList, 1, 1, 3, 1); + + resetButton = new Button(Localization.lang("Reset")); + resetButton.setOnAction(e -> actions.setAll(defaultFormatters.getConfiguredActions())); BibDatabaseContext databaseContext = JabRefGUI.getMainFrame().getCurrentBasePanel().getBibDatabaseContext(); - recommendButton = new JButton(Localization.lang("Recommended for %0", databaseContext.getMode().getFormattedName())); + recommendButton = new Button(Localization.lang("Recommended for %0", databaseContext.getMode().getFormattedName())); boolean isBiblatex = databaseContext.isBiblatexMode(); - recommendButton.addActionListener(e -> { + recommendButton.setOnAction(e -> { if (isBiblatex) { - ((CleanupActionsListModel) actionsList.getModel()).reset(Cleanups.RECOMMEND_BIBLATEX_ACTIONS); + actions.setAll(Cleanups.RECOMMEND_BIBLATEX_ACTIONS.getConfiguredActions()); } else { - ((CleanupActionsListModel) actionsList.getModel()).reset(Cleanups.RECOMMEND_BIBTEX_ACTIONS); + actions.setAll(Cleanups.RECOMMEND_BIBTEX_ACTIONS.getConfiguredActions()); } }); - removeButton = new JButton(Localization.lang("Remove selected")); - removeButton.addActionListener( - e -> ((CleanupActionsListModel) actionsList.getModel()).removeAtIndex(actionsList.getSelectedIndex())); + removeButton = new Button(Localization.lang("Remove selected")); + removeButton.setOnAction(e -> actions.remove(actionsList.getSelectionModel().getSelectedItem())); + descriptionAreaText = new Label(DESCRIPTION); + descriptionAreaText.setWrapText(true); - builder.add(removeButton).xy(7, 11); - builder.add(resetButton).xy(3, 11); - builder.add(recommendButton).xy(5, 11); - builder.add(getSelectorPanel()).xyw(3, 15, 5); - - makeDescriptionTextAreaLikeJLabel(); - builder.add(descriptionAreaText).xyw(3, 17, 5); - this.setLayout(new BorderLayout()); - this.add(builder.getPanel(), BorderLayout.WEST); + add(removeButton, 3, 2, 1, 1); + add(resetButton, 1, 2, 1, 1); + add(recommendButton, 2, 2, 1, 1); + add(getSelectorPanel(), 1, 3, 3, 1); + add(descriptionAreaText, 1, 4, 3, 1); updateDescription(); // make sure the layout is set according to the checkbox - cleanupEnabled.addActionListener(new EnablementStatusListener(fieldFormatterCleanups.isEnabled())); + cleanupEnabled.selectedProperty().addListener(new EnablementStatusListener<>(fieldFormatterCleanups.isEnabled())); cleanupEnabled.setSelected(fieldFormatterCleanups.isEnabled()); - - } - - /** - * Create a TextArea that looks and behaves like a JLabel. Has the advantage of supporting multine and wordwrap - */ - private void makeDescriptionTextAreaLikeJLabel() { - - descriptionAreaText = new JTextArea(DESCRIPTION); - descriptionAreaText.setLineWrap(true); - descriptionAreaText.setWrapStyleWord(true); - descriptionAreaText.setColumns(6); - descriptionAreaText.setEditable(false); - descriptionAreaText.setOpaque(false); - descriptionAreaText.setFocusable(false); - descriptionAreaText.setCursor(null); - descriptionAreaText.setFont(UIManager.getFont("Label.font")); - } private void updateDescription() { FieldFormatterCleanup formatterCleanup = getFieldFormatterCleanup(); - if (formatterCleanup != null) { + if (formatterCleanup.getFormatter() != null) { descriptionAreaText.setText(DESCRIPTION + formatterCleanup.getFormatter().getDescription()); } else { - Formatter selectedFormatter = getFieldFormatter(); + Formatter selectedFormatter = formattersCombobox.getValue(); if (selectedFormatter != null) { descriptionAreaText.setText(DESCRIPTION + selectedFormatter.getDescription()); } else { @@ -207,55 +158,35 @@ private void updateDescription() { /** * This panel contains the two comboboxes and the Add button - * @return Returns the created JPanel */ - private JPanel getSelectorPanel() { - FormBuilder builder = FormBuilder.create() - .layout(new FormLayout("left:pref:grow, 4dlu, left:pref:grow, 4dlu, fill:pref:grow, 4dlu, right:pref", - "fill:pref:grow, 2dlu, pref, 2dlu")); - + private GridPane getSelectorPanel() { + GridPane builder = new GridPane(); List fieldNames = InternalBibtexFields.getAllPublicAndInternalFieldNames(); fieldNames.add(BibEntry.KEY_FIELD); Collections.sort(fieldNames); - String[] allPlusKey = fieldNames.toArray(new String[fieldNames.size()]); - - selectFieldCombobox = new JComboBox<>(allPlusKey); + selectFieldCombobox = new ComboBox<>(FXCollections.observableArrayList(fieldNames)); selectFieldCombobox.setEditable(true); - builder.add(selectFieldCombobox).xy(1, 1); - - List formatterNames = availableFormatters.stream() - .map(Formatter::getName).collect(Collectors.toList()); - List formatterDescriptions = availableFormatters.stream() - .map(Formatter::getDescription).collect(Collectors.toList()); - formattersCombobox = new JComboBox<>(formatterNames.toArray()); - formattersCombobox.setRenderer(new DefaultListCellRenderer() { - - @Override - public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, - boolean cellHasFocus) { - if ((-1 < index) && (index < formatterDescriptions.size()) && (value != null)) { - setToolTipText(formatterDescriptions.get(index)); - - } - return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - } - }); - formattersCombobox.addItemListener(e -> updateDescription()); - builder.add(formattersCombobox).xy(3, 1); - - addButton = new JButton(Localization.lang("Add")); - addButton.addActionListener(e -> { + builder.add(selectFieldCombobox, 1, 1); + + formattersCombobox = new ComboBox<>(FXCollections.observableArrayList(availableFormatters)); + new ViewModelListCellFactory() + .withText(Formatter::getName) + .withTooltip(Formatter::getDescription) + .install(formattersCombobox); + EasyBind.subscribe(formattersCombobox.valueProperty(), e -> updateDescription()); + builder.add(formattersCombobox, 3, 1); + + addButton = new Button(Localization.lang("Add")); + addButton.setOnAction(e -> { FieldFormatterCleanup newAction = getFieldFormatterCleanup(); - if (newAction == null) { - return; - } - - ((CleanupActionsListModel) actionsList.getModel()).addCleanupAction(newAction); + if (!actions.contains(newAction)) { + actions.add(newAction); + } }); - builder.add(addButton).xy(5, 1); + builder.add(addButton, 5, 1); - return builder.getPanel(); + return builder; } public void storeSettings(MetaData metaData) { @@ -273,7 +204,6 @@ public void storeSettings(MetaData metaData) { } public FieldFormatterCleanups getFormatterCleanups() { - List actions = ((CleanupActionsListModel) actionsList.getModel()).getAllActions(); return new FieldFormatterCleanups(cleanupEnabled.isSelected(), actions); } @@ -286,47 +216,30 @@ public boolean isDefaultSaveActions() { } private FieldFormatterCleanup getFieldFormatterCleanup() { - Formatter selectedFormatter = getFieldFormatter(); - - String fieldKey = selectFieldCombobox.getSelectedItem().toString(); + Formatter selectedFormatter = formattersCombobox.getValue(); + String fieldKey = selectFieldCombobox.getValue(); return new FieldFormatterCleanup(fieldKey, selectedFormatter); - - } - - private Formatter getFieldFormatter() { - Formatter selectedFormatter = null; - String selectedFormatterName = formattersCombobox.getSelectedItem().toString(); - for (Formatter formatter : availableFormatters) { - if (formatter.getName().equals(selectedFormatterName)) { - selectedFormatter = formatter; - break; - } - } - return selectedFormatter; } - class EnablementStatusListener implements ActionListener { + class EnablementStatusListener implements ChangeListener { public EnablementStatusListener(boolean initialStatus) { setStatus(initialStatus); } - @Override - public void actionPerformed(ActionEvent e) { - boolean enablementStatus = cleanupEnabled.isSelected(); - setStatus(enablementStatus); - - } - private void setStatus(boolean status) { - actionsList.setEnabled(status); - selectFieldCombobox.setEnabled(status); - formattersCombobox.setEnabled(status); - addButton.setEnabled(status); - removeButton.setEnabled(status); - resetButton.setEnabled(status); - recommendButton.setEnabled(status); + actionsList.setDisable(!status); + selectFieldCombobox.setDisable(!status); + formattersCombobox.setDisable(!status); + addButton.setDisable(!status); + removeButton.setDisable(!status); + resetButton.setDisable(!status); + recommendButton.setDisable(!status); + } + @Override + public void changed(ObservableValue observable, T oldValue, T newValue) { + setStatus(cleanupEnabled.isSelected()); } } diff --git a/src/main/java/org/jabref/gui/collab/ChangeScanner.java b/src/main/java/org/jabref/gui/collab/ChangeScanner.java index ba99990d46d..7fb1b74e388 100644 --- a/src/main/java/org/jabref/gui/collab/ChangeScanner.java +++ b/src/main/java/org/jabref/gui/collab/ChangeScanner.java @@ -1,5 +1,6 @@ package org.jabref.gui.collab; +import java.io.IOException; import java.nio.file.Path; import java.util.Comparator; import java.util.List; @@ -16,12 +17,10 @@ import org.jabref.logic.bibtex.comparator.BibDatabaseDiff; import org.jabref.logic.bibtex.comparator.BibEntryDiff; import org.jabref.logic.bibtex.comparator.BibStringDiff; +import org.jabref.logic.exporter.AtomicFileWriter; import org.jabref.logic.exporter.BibDatabaseWriter; import org.jabref.logic.exporter.BibtexDatabaseWriter; -import org.jabref.logic.exporter.FileSaveSession; -import org.jabref.logic.exporter.SaveException; import org.jabref.logic.exporter.SavePreferences; -import org.jabref.logic.exporter.SaveSession; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.OpenDatabase; import org.jabref.logic.importer.ParserResult; @@ -105,10 +104,9 @@ private void storeTempDatabase() { .getEncoding() .orElse(Globals.prefs.getDefaultEncoding())); - BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter<>(FileSaveSession::new); - SaveSession ss = databaseWriter.saveDatabase(databaseInTemp, prefs); - ss.commit(tempFile); - } catch (SaveException ex) { + BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter(new AtomicFileWriter(tempFile, prefs.getEncoding()), prefs); + databaseWriter.saveDatabase(databaseInTemp); + } catch (IOException ex) { LOGGER.warn("Problem updating tmp file after accepting external changes", ex); } }); diff --git a/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java b/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java index 7c300bcc5d8..701679fed5f 100644 --- a/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java +++ b/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java @@ -11,7 +11,6 @@ import org.jabref.gui.BasePanel; import org.jabref.gui.SidePaneManager; import org.jabref.gui.SidePaneType; -import org.jabref.logic.util.io.FileBasedLock; import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.util.FileUpdateListener; @@ -61,21 +60,6 @@ public void fileUpdated() { updatedExternally = true; final ChangeScanner scanner = new ChangeScanner(panel.frame(), panel, database.getDatabasePath().orElse(null), tmpFile); - - // Test: running scan automatically in background - if (database.getDatabasePath().isPresent() && !FileBasedLock.waitForFileLock(database.getDatabasePath().get())) { - // The file is locked even after the maximum wait. Do nothing. - LOGGER.error("File updated externally, but change scan failed because the file is locked."); - - // Wait a bit and then try again - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // Nothing to do - } - fileUpdated(); - return; - } JabRefExecutorService.INSTANCE.executeInterruptableTaskAndWait(scanner); // Adding the sidepane component is Swing work, so we must do this in the Swing diff --git a/src/main/java/org/jabref/gui/copyfiles/CopyFilesTask.java b/src/main/java/org/jabref/gui/copyfiles/CopyFilesTask.java index 7dd83dbd4be..aec95051171 100644 --- a/src/main/java/org/jabref/gui/copyfiles/CopyFilesTask.java +++ b/src/main/java/org/jabref/gui/copyfiles/CopyFilesTask.java @@ -74,7 +74,7 @@ protected List call() throws InterruptedException, LinkedFile fileName = files.get(j); - Optional fileToExport = fileName.findIn(databaseContext, Globals.prefs.getFileDirectoryPreferences()); + Optional fileToExport = fileName.findIn(databaseContext, Globals.prefs.getFilePreferences()); newPath = OptionalUtil.combine(Optional.of(exportPath), fileToExport, resolvePathFilename); diff --git a/src/main/java/org/jabref/gui/customizefields/CustomizeGeneralFieldsDialog.fxml b/src/main/java/org/jabref/gui/customizefields/CustomizeGeneralFieldsDialog.fxml new file mode 100644 index 00000000000..0e848e4c13a --- /dev/null +++ b/src/main/java/org/jabref/gui/customizefields/CustomizeGeneralFieldsDialog.fxml @@ -0,0 +1,24 @@ + + + + + + + + + + + +