From 31a6279f91782bed199569177ec1e2d9203020f7 Mon Sep 17 00:00:00 2001 From: Abraham Polk Date: Wed, 19 Feb 2020 03:44:03 -0500 Subject: [PATCH 01/11] Change syntax for Oracle multi-row insert SQL statement (#5837) Co-authored-by: Tobias Diez --- .../jabref/logic/shared/DBMSProcessor.java | 2 +- .../jabref/logic/shared/OracleProcessor.java | 39 +++++++++++++++++++ .../org/jabref/logic/shared/TestManager.java | 20 +++++++--- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/jabref/logic/shared/DBMSProcessor.java b/src/main/java/org/jabref/logic/shared/DBMSProcessor.java index 8b9d55f800e..0e6d9550e5c 100644 --- a/src/main/java/org/jabref/logic/shared/DBMSProcessor.java +++ b/src/main/java/org/jabref/logic/shared/DBMSProcessor.java @@ -210,7 +210,7 @@ private boolean checkForBibEntryExistence(BibEntry bibEntry) { * * @param bibEntry {@link BibEntry} to be inserted */ - private void insertIntoFieldTable(BibEntry bibEntry) { + protected void insertIntoFieldTable(BibEntry bibEntry) { try { // Inserting into FIELD table // Coerce to ArrayList in order to use List.get() diff --git a/src/main/java/org/jabref/logic/shared/OracleProcessor.java b/src/main/java/org/jabref/logic/shared/OracleProcessor.java index fcccc4875d9..6e4b820bcf4 100644 --- a/src/main/java/org/jabref/logic/shared/OracleProcessor.java +++ b/src/main/java/org/jabref/logic/shared/OracleProcessor.java @@ -1,11 +1,16 @@ package org.jabref.logic.shared; +import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; import org.jabref.logic.shared.listener.OracleNotificationListener; import org.jabref.model.database.shared.DatabaseConnection; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.Field; import oracle.jdbc.OracleConnection; import oracle.jdbc.OracleStatement; @@ -97,6 +102,40 @@ public void startNotificationListener(DBMSSynchronizer dbmsSynchronizer) { } + @Override + protected void insertIntoFieldTable(BibEntry bibEntry) { + try { + // Inserting into FIELD table + // Coerce to ArrayList in order to use List.get() + List fields = new ArrayList<>(bibEntry.getFields()); + StringBuilder insertFieldQuery = new StringBuilder() + .append("INSERT ALL"); + for (Field field : fields) { + insertFieldQuery.append(" INTO ") + .append(escape("FIELD")) + .append(" (") + .append(escape("ENTRY_SHARED_ID")) + .append(", ") + .append(escape("NAME")) + .append(", ") + .append(escape("VALUE")) + .append(") VALUES (?, ?, ?)"); + } + insertFieldQuery.append(" SELECT * FROM DUAL"); + try (PreparedStatement preparedFieldStatement = connection.prepareStatement(insertFieldQuery.toString())) { + for (int i = 0; i < fields.size(); i++) { + // columnIndex starts with 1 + preparedFieldStatement.setInt((3 * i) + 1, bibEntry.getSharedBibEntryData().getSharedID()); + preparedFieldStatement.setString((3 * i) + 2, fields.get(i).getName()); + preparedFieldStatement.setString((3 * i) + 3, bibEntry.getField(fields.get(i)).get()); + } + preparedFieldStatement.executeUpdate(); + } + } catch (SQLException e) { + LOGGER.error("SQL Error: ", e); + } + } + @Override public void stopNotificationListener() { try { diff --git a/src/test/java/org/jabref/logic/shared/TestManager.java b/src/test/java/org/jabref/logic/shared/TestManager.java index f49d226157c..55c79812083 100644 --- a/src/test/java/org/jabref/logic/shared/TestManager.java +++ b/src/test/java/org/jabref/logic/shared/TestManager.java @@ -32,12 +32,22 @@ public static void clearTables(DBMSConnection dbmsConnection) throws SQLExceptio dbmsConnection.getConnection().createStatement().executeUpdate("DROP TABLE IF EXISTS \"METADATA\""); } else if (dbmsType == DBMSType.ORACLE) { dbmsConnection.getConnection().createStatement() - .executeUpdate("BEGIN\n" + "EXECUTE IMMEDIATE 'DROP TABLE \"FIELD\"';\n" - + "EXECUTE IMMEDIATE 'DROP TABLE \"ENTRY\"';\n" - + "EXECUTE IMMEDIATE 'DROP TABLE \"METADATA\"';\n" + .executeUpdate("BEGIN\n" + + "EXECUTE IMMEDIATE 'DROP TABLE \"FIELD\"';\n" + "EXCEPTION\n" + "WHEN OTHERS THEN\n" + + "IF SQLCODE != -942 THEN\n" + "RAISE;\n" + "END IF;\n" + "END;\n"); + dbmsConnection.getConnection().createStatement() + .executeUpdate("BEGIN\n" + + "EXECUTE IMMEDIATE 'DROP TABLE \"ENTRY\"';\n" + "EXCEPTION\n" + "WHEN OTHERS THEN\n" + + "IF SQLCODE != -942 THEN\n" + "RAISE;\n" + "END IF;\n" + "END;\n"); + dbmsConnection.getConnection().createStatement() + .executeUpdate("BEGIN\n" + + "EXECUTE IMMEDIATE 'DROP TABLE \"METADATA\"';\n" + "EXCEPTION\n" + "WHEN OTHERS THEN\n" + + "IF SQLCODE != -942 THEN\n" + "RAISE;\n" + "END IF;\n" + "END;\n"); + dbmsConnection.getConnection().createStatement() + // Sequence does not exist has a different error code than table does not exist + .executeUpdate("BEGIN\n" + "EXECUTE IMMEDIATE 'DROP SEQUENCE \"ENTRY_SEQ\"';\n" + "EXCEPTION\n" + "WHEN OTHERS THEN\n" - + "IF SQLCODE != -942 THEN\n" + "RAISE;\n" + "END IF;\n" + "END;"); + + "IF SQLCODE != -2289 THEN\n" + "RAISE;\n" + "END IF;\n" + "END;\n"); } } - } From d9e59c2d63a225f422c0930077638c393bbf1dca Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Wed, 19 Feb 2020 09:51:23 +0100 Subject: [PATCH 02/11] Restructure and improve docs for setting up IntelliJ (#5960) Co-authored-by: Patrick Scheibe --- ...elines-for-setting-up-a-local-workspace.md | 157 ++++++++++++------ 1 file changed, 102 insertions(+), 55 deletions(-) diff --git a/docs/guidelines-for-setting-up-a-local-workspace.md b/docs/guidelines-for-setting-up-a-local-workspace.md index b5c5221c3d3..1b803d7f70e 100644 --- a/docs/guidelines-for-setting-up-a-local-workspace.md +++ b/docs/guidelines-for-setting-up-a-local-workspace.md @@ -25,7 +25,7 @@ If you do not yet have a GitHub account, please [create one](https://github.com/ ### IDE -We suggest [IntelliJ](https://www.jetbrains.com/idea/) or [Eclipse](https://eclipse.org/) (`2019-12` or newer). +We suggest [IntelliJ IDEA](https://www.jetbrains.com/idea/) or [Eclipse](https://eclipse.org/) (`2019-12` or newer). Under Ubuntu Linux, you can follow the [documentation from the Ubuntu Community](https://help.ubuntu.com/community/EclipseIDE#Download_Eclipse) or the [step-by-step guideline from Krizna](www.krizna.com/ubuntu/install-eclipse-in-ubuntu-12-04/) to install Eclipse. Under Windows, download it from [www.eclipse.org](http://www.eclipse.org/downloads/) and run the installer. @@ -49,51 +49,80 @@ Under Ubuntu Linux, you can follow the [documentation from the Ubuntu Community] ## Configure your IDE -### Setup for IntelliJ - -1. Open `jabref/build.gradle` as a project -2. Enable annotation processors: - * File -> Settings -> Build, Execution, Deployment -> Compiler -> Annotation processors: - * Check "Enable annotation processing" -3. Configure module settings: Right click on project -> Open Module Settings: - * Ensure that the projects' SDK is Java 13: - * Project Settings -> Project -> Project SDK: Choose Java 13 - * Ensure that standard SDK is Java 13: - * Platform Settings -> SDK -> Choose Java 13 -4. Specify additional compiler arguments: - * File -> Settings -> Build, Execution, Deployment -> Compiler -> Java Compiler: - * Under "Override compiler parameters per-module" add ([+]) the following compiler arguments for the `JabRef.main` module: +### Setup for IntelliJ IDEA +IntelliJ IDEA fully supports Gradle as a build tool, but also has an internal +build system which is usually faster. For JabRef, Gradle is required to make a +full build but once set up, IntelliJ IDEA's internal system can be used for sub-sequent +builds. + +To configure IntelliJ IDEA for developing JabRef, you should first ensure that +you have enabled both bundled plugins *Gradle* and *Gradle Extension* + +* Navigate to **File | Settings | Plugins | Installed** and check that you have +the *Gradle* and *Gradle Extension* enabled. + +After that, you can open `jabref/build.gradle` as a project. +It is crucial that Java 13 is used consistently for the JabRef project which +includes ensuring the right settings for your project structure, Gradle build, +and run configurations. + +* Ensure you have a Java 13 SDK configured by navigating to +**File | Project Structure | Platform Settings | SDKs**. If you don't have one, add a new Java JDK and point it to the +location of a JDK 13. +* Navigate to **File | Project Structure | Project** and ensure that the projects' SDK is Java 13 +* Navigate to **File | Settings | Build, Execution, Deployment | Build Tools | Gradle** and select the Java 13 SDK as +the Gradle JVM at the bottom. + +To prepare IntelliJ's build system two additional steps are required + +* Navigate to **File | Settings | Build, Execution, Deployment | Compiler | Java Compiler**, and under +"Override compiler parameters per-module" add ([+]) the following compiler arguments for the `JabRef.main` module: ``` --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref --add-exports=org.controlsfx.controls/impl.org.controlsfx.skin=org.jabref ``` +* Enable annotation processors by navigating to +**File | Settings | Build, Execution, Deployment | Compiler | Annotation processors** and check "Enable annotation processing" + +#### Using Gradle from within IntelliJ IDEA + +Ensuring JabRef builds with Gradle should always the first step because, e.g. it generates additional sources that are +required for compiling the code. +After adjusting all settings mentioned earlier, your first step should be to + +* Open the Gradle Tool Window with the small button that can usually be found on the right side of IDEA or navigate to +**View | Tool Windows | Gradle**. +* In the Gradle Tool Window, press the "Reimport All Gradle Projects" button to ensure that all settings are up-to-date +with the setting changes. + +After that, you can use the Gradle Tool Window to build all parts JabRef and run it. To do so, expand the JabRef +project in the Gradle Tool Window and navigate to Tasks. From there, you can + +* Build and run JabRef by double-clicking **JabRef | Tasks | application | run**. +After that a new entry called "jabref [run]" will appear in the run configurations. +* Now you can also select "jabref [run]" and either run or debug the application from within IntelliJ. + +You can run any other development task in a similar way. +Equivalently, this can also be executed from the terminal by running `./gradlew run`. -5. Use IntelliJ to build and run (instead of gradle): - * File -> Settings -> Build, Execution, Deployment -> Build Tools -> Gradle: - * Set "Build and run using" and "Run tests using" both to "IntelliJ IDEA" -6. Ensure that the JDK13 is enabled for Gradle: Use IntelliJ to build and run (instead of gradle): - * File -> Settings -> Build, Execution, Deployment -> Build Tools -> Gradle -> Gradle: - * Set "Gradle JVM" to "JDK13" -7. Use the provided code style: - 1. Install the [CheckStyle-IDEA plugin](http://plugins.jetbrains.com/plugin/1065?pr=idea), it can be found via the plug-in repository: - 1. File > Settings > Plugins > Marketplace - 2. Search for "Checkstyle" and choose "CheckStyle-IDEA" - 3. Close the settings afterwards and restart IntelliJ - 2. Go to File > Settings > Editor > Code Style: - 1. Click on the settings wheel (next to the scheme chooser), then click "Import Scheme" and choose "IntelliJ Code Style.xml". - 2. Select the IntelliJ configuration file `config/IntelliJ Code Style.xml`. - 3. Click "OK". - 4. Go to File -> Settings -> Other Settings -> Checkstyle -> Configuration File: - 1. Import the CheckStyle configuration file by clicking the [+] button - 2. For the description provide e.g. "CheckStyle" - 3. Click "Browse" and choose `config/checkstyle/checkstyle.xml` - 4. Click "Next" and "Finish" - 5. Activate the CheckStyle configuration file by ticking it in the list - 6. Save settings by clicking "OK" -8. Open Run -> Edit Configurations... -> Select Application -> JabRef Main - * Verify, that "JRE" is set to "Default (JDK 13)" or to JDK 13 - * Set "VM Options" to the following: +#### Using IntelliJ's internal build system + +You can use IntelliJ IDEA's internal build system for compiling and running JabRef during development which is +usually more responsive. However, **it's important** that you understand that JabRef relies on generated sources +which are only build through Gradle. Therefore, to build or update these dependencies you need to run the `assemble` +Gradle task at least once. + +To use IntelliJ IDEA's internal build system when you build JabRef through **Build | Build Project** or use the provided +"JabRef Main" run configuration, ensure that + +* in **File | Settings | Build, Execution, Deployment | Build Tools | Gradle** the setting "Build and run using" and +"Test using" is set to "IntelliJ IDEA". + +To use the "JabRef Main" run configuration, open **Run | Edit Configurations... | Application | JabRef Main** and + +* Verify, that your JDK 13 is used +* Set "VM Options" to the following: ``` --patch-module org.jabref=build/resources/main @@ -131,20 +160,38 @@ Under Ubuntu Linux, you can follow the [documentation from the Ubuntu Community] --patch-module test3=sourcecode_2.12-0.1.4.jar ``` -9. If you have configured Eclipse for the same project (the required steps are described below), then the additionally added file `Log4jPlugins.java` must be excluded from the compilation process, otherwise an error will occur during the compilation of the project: - * File -> Settings -> Build, Execution, Deployment -> Compiler -> Excludes: Add the following file to the list ([+]), in order to exclude it: - * `src/main/java/org/jabref/gui/logging/plugins/Log4jPlugins.java` -10. Use the provided run configuration: Run -> Run "JabRef Main" - * This uses now IntelliJ for building and running/debugging the application, which is recommended, since the application starts quite fast. -11. Alternatively, the project can also be built and run/debugged by using gradle from within the IDE, which is not recommended, since the application starts significantly slower: - 1. Open the gradle tool window: - * View -> Tool Windows -> Gradle - 2. Navigate to the following entry in the gradle tool window: - * JabRef -> Tasks -> application -> run - 3. Double click "run" to build and run the application - 4. After that a new entry called "jabref [run]" will appear in the run configurations. - 5. Now you can also select "jabref [run]" and either run or debug the application from within IntelliJ. - * Equivalently, this can also be executed from the terminal by running `./gradlew run`. +Essentially, you now have the best of both worlds: You can run Gradle tasks using the Gradle Tool Window and unless you +haven't made changes to input files that generate sources, you can compile and run with IntelliJ's faster internal +build system. + +#### Using JabRef's code-style + +Contributions to JabRef's source code need to have a code formatting that is consistent with existing source code. +For that purpose, JabRef provides code-style and check-style definitions. + +* Install the [CheckStyle-IDEA plugin](http://plugins.jetbrains.com/plugin/1065?pr=idea), it can be found via the plug-in repository: + 1. Navigate to **File | Settings | Plugins | Marketplace** and search for "Checkstyle" and choose "CheckStyle-IDEA" + 2. Close the settings afterwards and restart IntelliJ +* Go to **File | Settings | Editor | Code Style** +* Click on the settings wheel (next to the scheme chooser), then click "Import Scheme" +* Select the IntelliJ configuration file `config/IntelliJ Code Style.xml`. +* Go to **File | Settings | Other Settings | Checkstyle | Configuration File** + 1. Import the CheckStyle configuration file by clicking the [+] button + 2. For the description provide e.g. "CheckStyle" + 3. Click "Browse" and choose `config/checkstyle/checkstyle.xml` + 4. Click "Next" and "Finish" + 5. Activate the CheckStyle configuration file by ticking it in the list + 6. Save settings by clicking "OK" + +#### Troubleshooting when using both IDEA and Eclipse + +If you have configured Eclipse for the same project (the required steps are described below), +then the additionally added file `Log4jPlugins.java` must be excluded from the compilation process, +otherwise an error will occur during the compilation of the project: + +* **File | Settings | Build, Execution, Deployment | Compiler | Excludes** and add the following file to the +list ([+]), in order to exclude it: + * `src/main/java/org/jabref/gui/logging/plugins/Log4jPlugins.java` ### Setup for Eclipse @@ -163,7 +210,7 @@ Make sure your Eclipse installation us up to date, Eclipse 2019-12 or newer is r 5. Create a run/debug configuration for the main class `org.jabref.JabRefLauncher` and/or for `org.jabref.JabRefMain` (both can be used equivalently) * In the tab "Arguments" of the run/debug configuration, enter the following runtime VM arguments: * Set "VM Arguments" to: - + ``` --patch-module test=fastparse_2.12-1.0.0.jar --patch-module test2=fastparse-utils_2.12-1.0.0.jar From 4dfedcdc86ab68b414f2633f32615694c9feb6b8 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Wed, 19 Feb 2020 09:56:25 +0100 Subject: [PATCH 03/11] MySQL: Allow public key retrieval (#5909) --- .../jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java b/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java index 23e96e4d1c8..8fb16ee9f87 100644 --- a/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java +++ b/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java @@ -118,6 +118,8 @@ public boolean openDatabase() { .setUser(user.getValue()) .setPassword(password.getValue()) .setUseSSL(useSSL.getValue()) + // Authorize client to retrieve RSA server public key when serverRsaPublicKeyFile is not set (for sha256_password and caching_sha2_password authentication password) + .setAllowPublicKeyRetrieval(true) .setKeyStore(keystore.getValue()) .setServerTimezone(serverTimezone.getValue()) .createDBMSConnectionProperties(); From c5209bafd26db1b436bacdf2f3ffbe29969a0ee1 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 19 Feb 2020 10:00:05 +0100 Subject: [PATCH 04/11] Bump unirest-java from 3.4.03 to 3.5.00 (#5953) Bumps [unirest-java](https://github.com/Kong/unirest-java) from 3.4.03 to 3.5.00. - [Release notes](https://github.com/Kong/unirest-java/releases) - [Changelog](https://github.com/Kong/unirest-java/blob/master/CHANGELOG.md) - [Commits](https://github.com/Kong/unirest-java/compare/v3.4.03...v3.5.00) Signed-off-by: dependabot-preview[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e23ca7f337c..790090cf46d 100644 --- a/build.gradle +++ b/build.gradle @@ -169,7 +169,7 @@ dependencies { compile 'org.controlsfx:controlsfx:11.0.1' compile 'org.jsoup:jsoup:1.12.2' - compile 'com.konghq:unirest-java:3.4.03' + compile 'com.konghq:unirest-java:3.5.00' compile 'org.slf4j:slf4j-api:2.0.0-alpha1' compile group: 'org.apache.logging.log4j', name: 'log4j-jcl', version: '3.0.0-SNAPSHOT' From 58f1db59f646be975372ea560b060b55907aeee3 Mon Sep 17 00:00:00 2001 From: Christoph Date: Wed, 19 Feb 2020 10:04:57 +0100 Subject: [PATCH 05/11] Reimplement custom entry types dialog (#5799) --- CHANGELOG.md | 1 + src/main/java/module-info.java | 1 + src/main/java/org/jabref/gui/JabRefFrame.java | 6 +- .../CustomEntryTypeDialogViewModel.java | 216 ++++++++++ .../CustomizeEntryAction.java | 18 +- .../CustomizeEntryTypeDialog.fxml | 101 +++++ .../CustomizeEntryTypeDialogView.java | 143 +++++++ .../EntryTypeCustomizationDialog.java | 377 ------------------ .../gui/customentrytypes/FieldViewModel.java | 65 +++ .../CustomizeGeneralFieldsDialog.fxml | 1 + .../gui/preferences/TableColumnsTabView.java | 2 +- .../org/jabref/gui/util/RadioButtonCell.java | 55 +++ .../model/entry/BibEntryTypesManager.java | 12 + .../jabref/preferences/JabRefPreferences.java | 4 +- .../preferences/PreferencesService.java | 4 + src/main/resources/l10n/JabRef_en.properties | 20 +- 16 files changed, 634 insertions(+), 392 deletions(-) create mode 100644 src/main/java/org/jabref/gui/customentrytypes/CustomEntryTypeDialogViewModel.java create mode 100644 src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryTypeDialog.fxml create mode 100644 src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryTypeDialogView.java delete mode 100644 src/main/java/org/jabref/gui/customentrytypes/EntryTypeCustomizationDialog.java create mode 100644 src/main/java/org/jabref/gui/customentrytypes/FieldViewModel.java create mode 100644 src/main/java/org/jabref/gui/util/RadioButtonCell.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aa0ccb56a7..bcbcb4953df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - We cleaned up the group add/edit dialog. [#5826](https://github.com/JabRef/jabref/pull/5826) - We reintroduced the index column. [#5844](https://github.com/JabRef/jabref/pull/5844) +- We reimplemented and improved the dialog "Customize entry types" [#4719](https://github.com/JabRef/jabref/issues/4719) - Filenames of external files can no longer contain curly braces diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 1ec7fcc330d..0573733ae7a 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -76,4 +76,5 @@ requires org.antlr.antlr4.runtime; requires flowless; requires org.apache.tika.core; + requires javafx.base; } diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 7fd1df234f9..8eef085787b 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -55,6 +55,7 @@ import org.jabref.gui.bibtexkeypattern.BibtexKeyPatternAction; import org.jabref.gui.contentselector.ManageContentSelectorAction; import org.jabref.gui.copyfiles.CopyFilesAction; +import org.jabref.gui.customentrytypes.CustomizeEntryAction; import org.jabref.gui.customizefields.SetupGeneralFieldsAction; import org.jabref.gui.dialogs.AutosaveUIManager; import org.jabref.gui.documentviewer.ShowDocumentViewerAction; @@ -829,9 +830,8 @@ private MenuBar createMenu() { new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.MANAGE_CONTENT_SELECTORS, new ManageContentSelectorAction(this, stateManager)) - // TODO: Reenable customize entry types feature (https://github.com/JabRef/jabref/issues/4719) - //factory.createMenuItem(StandardActions.CUSTOMIZE_ENTRY_TYPES, new CustomizeEntryAction(this)), + factory.createMenuItem(StandardActions.MANAGE_CONTENT_SELECTORS, new ManageContentSelectorAction(this, stateManager)), + factory.createMenuItem(StandardActions.CUSTOMIZE_ENTRY_TYPES, new CustomizeEntryAction(stateManager, Globals.entryTypesManager)) ); help.getItems().addAll( diff --git a/src/main/java/org/jabref/gui/customentrytypes/CustomEntryTypeDialogViewModel.java b/src/main/java/org/jabref/gui/customentrytypes/CustomEntryTypeDialogViewModel.java new file mode 100644 index 00000000000..fbe2a206c91 --- /dev/null +++ b/src/main/java/org/jabref/gui/customentrytypes/CustomEntryTypeDialogViewModel.java @@ -0,0 +1,216 @@ +package org.jabref.gui.customentrytypes; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import javafx.beans.Observable; +import javafx.beans.property.ListProperty; +import javafx.beans.property.ObjectProperty; +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.collections.ObservableList; +import javafx.util.StringConverter; + +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BibEntryType; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.field.BibField; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.FieldFactory; +import org.jabref.model.entry.field.FieldPriority; +import org.jabref.model.entry.field.OrFields; +import org.jabref.model.entry.field.UnknownField; +import org.jabref.model.entry.types.EntryType; +import org.jabref.model.entry.types.UnknownEntryType; +import org.jabref.preferences.PreferencesService; + +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; +import org.fxmisc.easybind.EasyBind; + +public class CustomEntryTypeDialogViewModel { + + public static final StringConverter FIELD_STRING_CONVERTER = new StringConverter<>() { + + @Override + public String toString(Field object) { + return object != null ? object.getDisplayName() : ""; + } + + @Override + public Field fromString(String string) { + return new UnknownField(string); + } + }; + + private final ListProperty entryTypes; + private final ListProperty fields; + private final ObjectProperty selectedEntryTypes = new SimpleObjectProperty<>(); + private final ListProperty fieldsForType; + private final ObjectProperty selectedFieldToAdd = new SimpleObjectProperty<>(); + private final StringProperty entryTypeToAdd = new SimpleStringProperty(""); + private final ObservableList allEntryTypes; + private final ObservableList allFieldsForType = FXCollections.observableArrayList(extractor -> new Observable[] {extractor.fieldName(), extractor.fieldType()}); + private final ObjectProperty newFieldToAdd = new SimpleObjectProperty<>(); + private final BibDatabaseMode mode; + private final Map> typesWithFields = new HashMap<>(); + private final List typesToRemove = new ArrayList<>(); + + private final PreferencesService preferencesService; + private final BibEntryTypesManager entryTypesManager; + + private final Validator entryTypeValidator; + private final Validator fieldValidator; + + public CustomEntryTypeDialogViewModel(BibDatabaseMode mode, PreferencesService preferencesService, BibEntryTypesManager entryTypesManager) { + this.mode = mode; + this.preferencesService = preferencesService; + this.entryTypesManager = entryTypesManager; + + Collection allTypes = entryTypesManager.getAllTypes(mode); + allTypes.addAll(entryTypesManager.getAllCustomTypes(mode)); + + allEntryTypes = FXCollections.observableArrayList(allTypes); + entryTypes = new SimpleListProperty<>(allEntryTypes); + + fields = new SimpleListProperty<>(FXCollections.observableArrayList(FieldFactory.getCommonFields())); + + for (BibEntryType entryType : allTypes) { + List fields = entryType.getAllFields().stream().map(bibField -> new FieldViewModel(bibField.getField(), entryType.isRequired(bibField.getField()), bibField.getPriority(), entryType)).collect(Collectors.toList()); + typesWithFields.put(entryType, fields); + } + + this.fieldsForType = new SimpleListProperty<>(allFieldsForType); + + EasyBind.subscribe(selectedEntryTypes, type -> { + if (type != null) { + allFieldsForType.setAll(typesWithFields.get(type)); + } + }); + + Predicate notEmpty = input -> (input != null) && !input.trim().isEmpty(); + entryTypeValidator = new FunctionBasedValidator<>(entryTypeToAdd, notEmpty, ValidationMessage.error(Localization.lang("Entry type cannot be empty. Please enter a name."))); + fieldValidator = new FunctionBasedValidator<>(newFieldToAdd, + input -> input != null && !input.getDisplayName().isEmpty(), + ValidationMessage.error(Localization.lang("Field cannot be empty. Please enter a name."))); + } + + public ListProperty entryTypes() { + return this.entryTypes; + } + + public ListProperty fields() { + return this.fields; + } + + public enum FieldType { + + REQUIRED(Localization.lang("Required")), + OPTIONAL(Localization.lang("Optional")); + + private String name; + + FieldType(String name) { + this.name = name; + } + + public String getDisplayName() { + return this.name; + } + + @Override + public String toString() { + return this.name; + } + } + + public void addNewField() { + Field field = newFieldToAdd.getValue(); + FieldViewModel model = new FieldViewModel(field, true, FieldPriority.IMPORTANT, selectedEntryTypes.getValue()); + typesWithFields.computeIfAbsent(selectedEntryTypes.getValue(), key -> new ArrayList<>()).add(model); + allFieldsForType.add(model); + newFieldToAddProperty().setValue(null); + } + + public void addNewCustomEntryType() { + EntryType newentryType = new UnknownEntryType(entryTypeToAdd.getValue()); + BibEntryType type = new BibEntryType(newentryType, new ArrayList<>(), Collections.emptyList()); + this.allEntryTypes.add(type); + this.entryTypeToAdd.setValue(""); + this.typesWithFields.put(type, new ArrayList<>()); + } + + public ObjectProperty selectedEntryTypeProperty() { + return this.selectedEntryTypes; + } + + public ListProperty fieldsforTypesProperty() { + return this.fieldsForType; + } + + public ObjectProperty selectedFieldToAddProperty() { + return this.selectedFieldToAdd; + } + + public StringProperty entryTypeToAddProperty() { + return this.entryTypeToAdd; + } + + public ObjectProperty newFieldToAddProperty() { + return this.newFieldToAdd; + } + + public ValidationStatus entryTypeValidationStatus() { + return entryTypeValidator.getValidationStatus(); + } + + public ValidationStatus fieldValidationStatus() { + return fieldValidator.getValidationStatus(); + } + + public void removeEntryType(BibEntryType focusedItem) { + typesToRemove.add(focusedItem); + typesWithFields.remove(focusedItem); + allEntryTypes.remove(focusedItem); + } + + public void removeField(FieldViewModel focusedItem) { + typesWithFields.computeIfAbsent(selectedEntryTypes.getValue(), key -> new ArrayList<>()).remove(focusedItem); + allFieldsForType.remove(focusedItem); + } + + public void apply() { + + for (var typeWithField : typesWithFields.entrySet()) { + BibEntryType type = typeWithField.getKey(); + List allFields = typeWithField.getValue(); + + List requiredFields = allFields.stream().filter(field -> field.getFieldType() == FieldType.REQUIRED).map(FieldViewModel::getField).map(OrFields::new).collect(Collectors.toList()); + List otherFields = allFields.stream().filter(field -> field.getFieldType() == FieldType.OPTIONAL).map(bibField -> new BibField(bibField.getField(), bibField.getFieldPriority())).collect(Collectors.toList()); + + BibEntryType newType = new BibEntryType(type.getType(), otherFields, requiredFields); + entryTypesManager.addCustomOrModifiedType(newType, mode); + } + + for (var type : typesToRemove) { + entryTypesManager.removeCustomOrModifiedEntryType(type, mode); + } + preferencesService.saveCustomEntryTypes(); + //Reload types from preferences to make sure any modifications are present when reopening the dialog + entryTypesManager.addCustomOrModifiedTypes(preferencesService.loadBibEntryTypes(BibDatabaseMode.BIBTEX), + preferencesService.loadBibEntryTypes(BibDatabaseMode.BIBLATEX)); + } + +} diff --git a/src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryAction.java b/src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryAction.java index 3fb6a635a53..a320dcc0b45 100644 --- a/src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryAction.java +++ b/src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryAction.java @@ -1,19 +1,27 @@ package org.jabref.gui.customentrytypes; -import org.jabref.gui.JabRefFrame; +import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntryTypesManager; + +import static org.jabref.gui.actions.ActionHelper.needsDatabase; public class CustomizeEntryAction extends SimpleCommand { - private final JabRefFrame frame; + private final StateManager stateManager; + private final BibEntryTypesManager entryTypesManager; - public CustomizeEntryAction(JabRefFrame frame) { - this.frame = frame; + public CustomizeEntryAction(StateManager stateManager, BibEntryTypesManager entryTypesManager) { + this.stateManager = stateManager; + this.executable.bind(needsDatabase(this.stateManager)); + this.entryTypesManager = entryTypesManager; } @Override public void execute() { - EntryTypeCustomizationDialog dialog = new EntryTypeCustomizationDialog(); + BibDatabaseContext database = stateManager.getActiveDatabase().orElseThrow(() -> new NullPointerException("Database null")); + CustomizeEntryTypeDialogView dialog = new CustomizeEntryTypeDialogView(database, entryTypesManager); dialog.showAndWait(); } } diff --git a/src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryTypeDialog.fxml b/src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryTypeDialog.fxml new file mode 100644 index 00000000000..4d228eb39f4 --- /dev/null +++ b/src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryTypeDialog.fxml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryTypeDialogView.java b/src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryTypeDialogView.java new file mode 100644 index 00000000000..f978102f4b1 --- /dev/null +++ b/src/main/java/org/jabref/gui/customentrytypes/CustomizeEntryTypeDialogView.java @@ -0,0 +1,143 @@ +package org.jabref.gui.customentrytypes; + +import java.util.EnumSet; + +import javax.inject.Inject; + +import javafx.application.Platform; +import javafx.beans.property.ReadOnlyStringWrapper; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonBar.ButtonData; +import javafx.scene.control.ButtonType; +import javafx.scene.control.ComboBox; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.TextField; + +import org.jabref.gui.customentrytypes.CustomEntryTypeDialogViewModel.FieldType; +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.util.BaseDialog; +import org.jabref.gui.util.RadioButtonCell; +import org.jabref.gui.util.ValueTableCellFactory; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BibEntryType; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.field.Field; +import org.jabref.preferences.PreferencesService; + +import com.airhacks.afterburner.views.ViewLoader; +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; + +public class CustomizeEntryTypeDialogView extends BaseDialog { + + private final BibDatabaseMode mode; + private final BibEntryTypesManager entryTypesManager; + + @FXML private TableView entryTypes; + @FXML private TableColumn entryTypColumn; + @FXML private TableColumn entryTypeActionsColumn; + @FXML private TextField addNewEntryType; + @FXML private TableView fields; + @FXML private TableColumn fieldNameColumn; + @FXML private TableColumn fieldTypeColumn; + @FXML private TableColumn fieldTypeActionColumn; + @FXML private ComboBox addNewField; + @FXML private ButtonType applyButton; + @FXML private Button addNewEntryTypeButton; + @FXML private Button addNewFieldButton; + + @Inject private PreferencesService preferencesService; + + private CustomEntryTypeDialogViewModel viewModel; + private final ControlsFxVisualizer visualizer = new ControlsFxVisualizer(); + + public CustomizeEntryTypeDialogView(BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager) { + this.mode = bibDatabaseContext.getMode(); + this.entryTypesManager = entryTypesManager; + + ViewLoader.view(this) + .load() + .setAsDialogPane(this); + + setResultConverter(button -> { + if (button.getButtonData() == ButtonData.OK_DONE) { + viewModel.apply(); + } + return null; + }); + } + + @FXML + private void initialize() { + viewModel = new CustomEntryTypeDialogViewModel(mode, preferencesService, entryTypesManager); + setupTable(); + + addNewEntryTypeButton.disableProperty().bind(viewModel.entryTypeValidationStatus().validProperty().not()); + addNewFieldButton.disableProperty().bind(viewModel.fieldValidationStatus().validProperty().not()); + + Platform.runLater(() -> { + visualizer.initVisualization(viewModel.entryTypeValidationStatus(), addNewEntryType, true); + visualizer.initVisualization(viewModel.fieldValidationStatus(), addNewField, true); + }); + } + + private void setupTable() { + + fields.setEditable(true); //Table View must be editable, otherwise the change of the Radiobuttons does not propagate the commit event + entryTypColumn.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue().getType().getDisplayName())); + entryTypes.itemsProperty().bind(viewModel.entryTypes()); + entryTypes.getSelectionModel().selectFirst(); + + entryTypeActionsColumn.setSortable(false); + entryTypeActionsColumn.setReorderable(false); + entryTypeActionsColumn.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue().getType().getDisplayName())); + new ValueTableCellFactory() + .withGraphic(item -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) + .withTooltip(name -> Localization.lang("Remove entry type") + " " + name) + .withOnMouseClickedEvent(item -> evt -> viewModel.removeEntryType(entryTypes.getSelectionModel().getSelectedItem())) + .install(entryTypeActionsColumn); + + fieldTypeColumn.setCellFactory(cellData -> new RadioButtonCell<>(EnumSet.allOf(FieldType.class))); + fieldTypeColumn.setCellValueFactory(item -> item.getValue().fieldType()); + + fieldTypeColumn.setEditable(true); + fieldTypeColumn.setOnEditCommit(event -> { + event.getTableView().getItems().get(event.getTablePosition().getRow()).setFieldType(event.getNewValue()); + }); + + fieldNameColumn.setCellValueFactory(item -> item.getValue().fieldName()); + + viewModel.selectedEntryTypeProperty().bind(entryTypes.getSelectionModel().selectedItemProperty()); + viewModel.entryTypeToAddProperty().bindBidirectional(addNewEntryType.textProperty()); + + addNewField.setItems(viewModel.fields()); + addNewField.setConverter(viewModel.FIELD_STRING_CONVERTER); + + fieldTypeActionColumn.setSortable(false); + fieldTypeActionColumn.setReorderable(false); + fieldTypeActionColumn.setCellValueFactory(cellData -> cellData.getValue().fieldName()); + + new ValueTableCellFactory() + .withGraphic(item -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) + .withTooltip(name -> Localization.lang("Remove field %0 from currently selected entry type", name)) + .withOnMouseClickedEvent(item -> evt -> viewModel.removeField(fields.getSelectionModel().getSelectedItem())) + .install(fieldTypeActionColumn); + + viewModel.newFieldToAddProperty().bindBidirectional(addNewField.valueProperty()); + fields.itemsProperty().bindBidirectional(viewModel.fieldsforTypesProperty()); + } + + @FXML + void addEntryType() { + viewModel.addNewCustomEntryType(); + } + + @FXML + void addNewField() { + viewModel.addNewField(); + } + +} diff --git a/src/main/java/org/jabref/gui/customentrytypes/EntryTypeCustomizationDialog.java b/src/main/java/org/jabref/gui/customentrytypes/EntryTypeCustomizationDialog.java deleted file mode 100644 index ba034b8bdb2..00000000000 --- a/src/main/java/org/jabref/gui/customentrytypes/EntryTypeCustomizationDialog.java +++ /dev/null @@ -1,377 +0,0 @@ -package org.jabref.gui.customentrytypes; - -import org.jabref.gui.util.BaseDialog; - -public class EntryTypeCustomizationDialog extends BaseDialog { - - // TODO: Re-implement customize entry types feature (https://github.com/JabRef/jabref/issues/4719) - /* - protected GridBagLayout gbl = new GridBagLayout(); - protected GridBagConstraints con = new GridBagConstraints(); - protected JButton delete; - private final JabRefFrame frame; - private FieldSetComponent reqComp; - private FieldSetComponent optComp; - private FieldSetComponent optComp2; - private EntryTypeList typeComp; - private final List preset = InternalBibtexFields.getAllPublicFieldNames(); - private String lastSelected; - private final Map> reqLists = new HashMap<>(); - private final Map> optLists = new HashMap<>(); - private final Map> opt2Lists = new HashMap<>(); - private final Set defaulted = new HashSet<>(); - private final Set changed = new HashSet<>(); - - private boolean biblatexMode; - private BibDatabaseMode bibDatabaseMode; - - public EntryTypeCustomizationDialog(JabRefFrame frame) { - super(Localization.lang("Customize entry types"), false, EntryTypeCustomizationDialog.class); - - this.frame = frame; - initGui(); - } - - private void initGui() { - Container pane = getContentPane(); - pane.setLayout(new BorderLayout()); - - if (frame.getCurrentBasePanel() == null) { - bibDatabaseMode = Globals.prefs.getDefaultBibDatabaseMode(); - } else { - bibDatabaseMode = frame.getCurrentBasePanel().getBibDatabaseContext().getMode(); - } - biblatexMode = BibDatabaseMode.BIBLATEX.equals(bibDatabaseMode); - - JPanel main = new JPanel(); - JPanel buttons = new JPanel(); - JPanel right = new JPanel(); - main.setLayout(new BorderLayout()); - right.setLayout(new GridLayout(biblatexMode ? 2 : 1, 2)); - - List entryTypes = new ArrayList<>(); - entryTypes.addAll(EntryTypeFactory.getAllTypes(bibDatabaseMode)); - - typeComp = new EntryTypeList(frame.getDialogService(), entryTypes, bibDatabaseMode); - typeComp.addListSelectionListener(this); - typeComp.addAdditionActionListener(e -> typeComp.selectField(e.getActionCommand())); - typeComp.addDefaultActionListener(new DefaultListener()); - typeComp.setListSelectionMode(ListSelectionModel.SINGLE_SELECTION); - - //typeComp.setEnabled(false); - reqComp = new FieldSetComponent(Localization.lang("Required fields"), new ArrayList<>(), preset, true, true); - reqComp.setEnabled(false); - reqComp.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); - ListDataListener dataListener = new DataListener(); - reqComp.addListDataListener(dataListener); - optComp = new FieldSetComponent(Localization.lang("Optional fields"), new ArrayList<>(), preset, true, true); - optComp.setEnabled(false); - optComp.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); - optComp.addListDataListener(dataListener); - right.add(reqComp); - right.add(optComp); - - if (biblatexMode) { - optComp2 = new FieldSetComponent(Localization.lang("Optional fields") + " 2", new ArrayList<>(), preset, true, true); - optComp2.setEnabled(false); - optComp2.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); - optComp2.addListDataListener(dataListener); - right.add(new JPanel()); - right.add(optComp2); - } - - //right.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), Globals.lang("Fields"))); - right.setBorder(BorderFactory.createEtchedBorder()); - JButton ok = new JButton(Localization.lang("OK")); - JButton cancel = new JButton(Localization.lang("Cancel")); - JButton apply = new JButton(Localization.lang("Apply")); - ok.addActionListener(e -> { - applyChanges(); - dispose(); - }); - apply.addActionListener(e -> applyChanges()); - cancel.addActionListener(e -> dispose()); - ButtonBarBuilder bb = new ButtonBarBuilder(buttons); - buttons.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); - bb.addGlue(); - bb.addButton(ok); - bb.addButton(apply); - bb.addButton(cancel); - bb.addGlue(); - - AbstractAction closeAction = new AbstractAction() { - - @Override - public void actionPerformed(ActionEvent e) { - dispose(); - } - }; - ActionMap am = main.getActionMap(); - InputMap im = main.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE), "close"); - am.put("close", closeAction); - - //con.fill = GridBagConstraints.BOTH; - //con.weightx = 0.3; - //con.weighty = 1; - //gbl.setConstraints(typeComp, con); - main.add(typeComp, BorderLayout.WEST); - main.add(right, BorderLayout.CENTER); - main.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - pane.add(main, BorderLayout.CENTER); - pane.add(buttons, BorderLayout.SOUTH); - pack(); - } - - @Override - public void valueChanged(ListSelectionEvent e) { - if (e.getValueIsAdjusting()) { - return; - } - - if (lastSelected != null) { - // The entry type lastSelected is now unselected, so we store the current settings - // for that type in our two maps. - reqLists.put(lastSelected, reqComp.getFields()); - optLists.put(lastSelected, optComp.getFields()); - if (biblatexMode) { - opt2Lists.put(lastSelected, optComp2.getFields()); - } - } - - String selectedTypeName = typeComp.getFirstSelected(); - if (selectedTypeName == null) { - return; - } - Set requiredFieldsSelectedType = reqLists.get(selectedTypeName); - if (requiredFieldsSelectedType == null) { - Optional type = EntryTypeFactory.getType(selectedTypeName, bibDatabaseMode); - if (type.isPresent()) { - Set req = type.get().getRequiredFields(); - - Set opt; - if (biblatexMode) { - opt = type.get().getPrimaryOptionalFields(); - - Set opt2 = type.get().getSecondaryOptionalFields(); - - optComp2.setFields(opt2); - optComp2.setEnabled(true); - } else { - opt = type.get().getOptionalFields(); - } - reqComp.setFields(req); - reqComp.setEnabled(true); - optComp.setFields(opt); - optComp.setEnabled(true); - } else { - // New entry - reqComp.setFields(new HashSet<>()); - reqComp.setEnabled(true); - optComp.setFields(new HashSet<>()); - optComp.setEnabled(true); - if (biblatexMode) { - optComp2.setFields(new HashSet<>()); - optComp2.setEnabled(true); - } - reqComp.requestFocus(); - } - } else { - reqComp.setFields(requiredFieldsSelectedType); - optComp.setFields(optLists.get(selectedTypeName)); - if (biblatexMode) { - optComp2.setFields(opt2Lists.get(selectedTypeName)); - } - } - - lastSelected = selectedTypeName; - typeComp.enable(selectedTypeName, changed.contains(lastSelected) && !defaulted.contains(lastSelected)); - } - - private void applyChanges() { - valueChanged(new ListSelectionEvent(new JList<>(), 0, 0, false)); - - List actuallyChangedTypes = new ArrayList<>(); - - // Iterate over our map of required fields, and list those types if necessary: - Set types = typeComp.getFields(); - for (Map.Entry> stringListEntry : reqLists.entrySet()) { - if (!types.contains(stringListEntry.getKey())) { - continue; - } - - Set requiredFieldsList = stringListEntry.getValue(); - Set optionalFieldsList = optLists.get(stringListEntry.getKey()); - Set secondaryOptionalFieldsLists = opt2Lists.get(stringListEntry.getKey()); - - if (secondaryOptionalFieldsLists == null) { - secondaryOptionalFieldsLists = new HashSet<>(0); - } - - // If this type is already existing, check if any changes have - // been made - boolean changesMade = true; - - if (defaulted.contains(stringListEntry.getKey())) { - // This type should be reverted to its default setup. - EntryTypeFactory.removeType(stringListEntry.getKey(), bibDatabaseMode); - - actuallyChangedTypes.add(stringListEntry.getKey().toLowerCase(Locale.ENGLISH)); - defaulted.remove(stringListEntry.getKey()); - continue; - } - - Optional oldType = EntryTypeFactory.getType(stringListEntry.getKey(), bibDatabaseMode); - if (oldType.isPresent()) { - Set oldRequiredFieldsList = oldType.get().getRequiredFieldsFlat(); - Set oldOptionalFieldsList = oldType.get().getOptionalFields(); - if (biblatexMode) { - Set oldPrimaryOptionalFieldsLists = oldType.get().getPrimaryOptionalFields(); - Set oldSecondaryOptionalFieldsList = oldType.get().getSecondaryOptionalFields(); - if (Arrays.equals(oldRequiredFieldsList.toArray(), requiredFieldsList.toArray()) - && Arrays.equals(oldPrimaryOptionalFieldsLists.toArray(), optionalFieldsList.toArray()) - && Arrays.equals(oldSecondaryOptionalFieldsList.toArray(), secondaryOptionalFieldsLists.toArray())) { - changesMade = false; - } - } else if (Arrays.equals(oldRequiredFieldsList.toArray(), requiredFieldsList.toArray()) - && Arrays.equals(oldOptionalFieldsList.toArray(), optionalFieldsList.toArray())) { - changesMade = false; - } - } - - if (changesMade) { - BibEntryType customType = biblatexMode ? - new BibEntryType(StringUtil.capitalizeFirst(stringListEntry.getKey()), requiredFieldsList, optionalFieldsList, secondaryOptionalFieldsLists) : - new BibEntryType(StringUtil.capitalizeFirst(stringListEntry.getKey()), requiredFieldsList, optionalFieldsList); - - EntryTypeFactory.addOrModifyBibEntryType(customType, bibDatabaseMode); - actuallyChangedTypes.add(customType.getName().toLowerCase(Locale.ENGLISH)); - } - } - - // update all affected entries if something has been changed - if (!actuallyChangedTypes.isEmpty()) { - updateEntriesForChangedTypes(actuallyChangedTypes); - } - - Set typesToRemove = new HashSet<>(); - for (String existingType : EntryTypeFactory.getAllTypes(bibDatabaseMode)) { - if (!types.contains(existingType)) { - typesToRemove.add(existingType); - } - } - - // Remove those that should be removed: - if (!typesToRemove.isEmpty()) { - for (String typeToRemove : typesToRemove) { - deleteType(typeToRemove); - } - } - - BibEntryTypesManager.saveBibEntryTypes(Globals.prefs); - } - - private void deleteType(String name) { - Optional type = EntryTypeFactory.getType(name, bibDatabaseMode); - - if (type.isPresent() && (type.get() instanceof BibEntryType)) { - if (!EntryTypeFactory.getStandardType(name, bibDatabaseMode).isPresent()) { - - boolean deleteCustomClicked = frame.getDialogService().showConfirmationDialogAndWait(Localization.lang("Delete custom format") + - " '" + StringUtil.capitalizeFirst(name) + '\'', Localization.lang("All entries of this " - + "type will be declared " - + "typeless. Continue?"), - Localization.lang("Delete custom format"), Localization.lang("Cancel")); - - if (!deleteCustomClicked) { - return; - } - } - EntryTypeFactory.removeType(name, bibDatabaseMode); - updateEntriesForChangedTypes(Collections.singletonList(name.toLowerCase(Locale.ENGLISH))); - changed.remove(name); - reqLists.remove(name); - optLists.remove(name); - if (biblatexMode) { - opt2Lists.remove(name); - } - } - } - - private void updateEntriesForChangedTypes(List actuallyChangedTypes) { - for (BasePanel bp : frame.getBasePanelList()) { - // get all affected entries - List filtered = bp.getDatabase().getEntries().stream() - .filter(entry -> actuallyChangedTypes.contains(entry.getType().toLowerCase(Locale.ENGLISH))).collect(Collectors.toList()); - - // update all affected entries with new type - filtered.forEach(entry -> EntryTypeFactory.getType(entry.getType(), bibDatabaseMode).ifPresent(entry::setType)); - } - } - - // DEFAULT button pressed. Remember that this entry should be reset to default, - // unless changes are made later. - private class DefaultListener implements ActionListener { - - @Override - public void actionPerformed(ActionEvent e) { - if (lastSelected == null) { - return; - } - defaulted.add(lastSelected); - - Optional type = EntryTypeFactory.getStandardType(lastSelected, bibDatabaseMode); - if (type.isPresent()) { - Set of = type.get().getOptionalFields(); - Set req = type.get().getRequiredFields(); - Set opt1 = new HashSet<>(); - Set opt2 = new HashSet<>(); - - if (!(of.isEmpty())) { - if (biblatexMode) { - opt1 = type.get().getPrimaryOptionalFields(); - opt2 = type.get().getSecondaryOptionalFields(); - } else { - opt1 = of; - } - } - - reqComp.setFields(req); - reqComp.setEnabled(true); - optComp.setFields(opt1); - if (biblatexMode) { - optComp2.setFields(opt2); - } - } - } - } - - class DataListener implements ListDataListener { - - @Override - public void intervalAdded(ListDataEvent e) { - record(); - } - - @Override - public void intervalRemoved(ListDataEvent e) { - record(); - } - - @Override - public void contentsChanged(ListDataEvent e) { - record(); - } - - private void record() { - if (lastSelected == null) { - return; - } - defaulted.remove(lastSelected); - changed.add(lastSelected); - typeComp.enable(lastSelected, true); - } - } - - */ -} diff --git a/src/main/java/org/jabref/gui/customentrytypes/FieldViewModel.java b/src/main/java/org/jabref/gui/customentrytypes/FieldViewModel.java new file mode 100644 index 00000000000..b000871aa67 --- /dev/null +++ b/src/main/java/org/jabref/gui/customentrytypes/FieldViewModel.java @@ -0,0 +1,65 @@ +package org.jabref.gui.customentrytypes; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +import org.jabref.gui.customentrytypes.CustomEntryTypeDialogViewModel.FieldType; +import org.jabref.model.entry.BibEntryType; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.FieldPriority; + +public class FieldViewModel { + + private final ObjectProperty fieldType; + private final StringProperty fieldName = new SimpleStringProperty(""); + private final Field field; + private final FieldPriority fieldPriority; + private BibEntryType entryType; + + public FieldViewModel(Field field, FieldType fieldType, FieldPriority fieldPriority, BibEntryType entryType) { + this.field = field; + this.entryType = entryType; + this.fieldName.setValue(field.getDisplayName()); + this.fieldType = new SimpleObjectProperty<>(fieldType); + this.fieldPriority = fieldPriority; + } + + public FieldViewModel(Field field, boolean required, FieldPriority fieldPriority, BibEntryType entryType) { + this(field, required ? FieldType.REQUIRED : FieldType.OPTIONAL, fieldPriority, entryType); + } + + public ObjectProperty fieldType() { + return this.fieldType; + } + + public StringProperty fieldName() { + return this.fieldName; + } + + public Field getField() { + return this.field; + } + + public BibEntryType getEntryType() { + return this.entryType; + } + + public FieldPriority getFieldPriority() { + return this.fieldPriority; + } + + public FieldType getFieldType() { + return this.fieldType.getValue(); + } + + public void setFieldType(FieldType type) { + this.fieldType.setValue(type); + } + + @Override + public String toString() { + return this.field.getDisplayName(); + } +} diff --git a/src/main/java/org/jabref/gui/customizefields/CustomizeGeneralFieldsDialog.fxml b/src/main/java/org/jabref/gui/customizefields/CustomizeGeneralFieldsDialog.fxml index 0e848e4c13a..3bde4ef9104 100644 --- a/src/main/java/org/jabref/gui/customizefields/CustomizeGeneralFieldsDialog.fxml +++ b/src/main/java/org/jabref/gui/customizefields/CustomizeGeneralFieldsDialog.fxml @@ -5,6 +5,7 @@ + diff --git a/src/main/java/org/jabref/gui/preferences/TableColumnsTabView.java b/src/main/java/org/jabref/gui/preferences/TableColumnsTabView.java index 2bf146a50b5..b1f05c01d1e 100644 --- a/src/main/java/org/jabref/gui/preferences/TableColumnsTabView.java +++ b/src/main/java/org/jabref/gui/preferences/TableColumnsTabView.java @@ -37,7 +37,7 @@ public class TableColumnsTabView extends AbstractPreferenceTabView> extends TableCell { + + private final EnumSet enumeration; + + public RadioButtonCell(EnumSet enumeration) { + this.enumeration = enumeration; + } + + @SuppressWarnings("unchecked") + @Override + protected void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + if (empty || (item == null)) { + setGraphic(null); + } else { + // gui setup + HBox hb = new HBox(7); + hb.setAlignment(Pos.CENTER); + final ToggleGroup group = new ToggleGroup(); + + // create a radio button for each 'element' of the enumeration + for (Enum enumElement : enumeration) { + RadioButton radioButton = new RadioButton(enumElement.toString()); + radioButton.setUserData(enumElement); + radioButton.setToggleGroup(group); + radioButton.setMinWidth(USE_PREF_SIZE); + hb.getChildren().add(radioButton); + if (enumElement.equals(item)) { + radioButton.setSelected(true); + } + hb.setMinWidth(USE_PREF_SIZE); + hb.setHgrow(radioButton, Priority.ALWAYS); + } + + // issue events on change of the selected radio button + group.selectedToggleProperty().addListener((oservable, oldValue, newValue) -> { + getTableView().edit(getIndex(), getTableColumn()); + RadioButtonCell.this.commitEdit((T) newValue.getUserData()); + }); + + setGraphic(hb); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/jabref/model/entry/BibEntryTypesManager.java b/src/main/java/org/jabref/model/entry/BibEntryTypesManager.java index f278b8d5a50..97f40a15a01 100644 --- a/src/main/java/org/jabref/model/entry/BibEntryTypesManager.java +++ b/src/main/java/org/jabref/model/entry/BibEntryTypesManager.java @@ -112,6 +112,14 @@ public void addCustomOrModifiedType(BibEntryType entryType, BibDatabaseMode mode } } + public void removeCustomOrModifiedEntryType(BibEntryType entryType, BibDatabaseMode mode) { + if (BibDatabaseMode.BIBLATEX == mode) { + BIBLATEX.removeCustomOrModifiedEntryType(entryType); + } else if (BibDatabaseMode.BIBTEX == mode) { + BIBTEX.removeCustomOrModifiedEntryType(entryType); + } + } + public Collection getAllTypes(BibDatabaseMode type) { return type == BibDatabaseMode.BIBLATEX ? BIBLATEX.getAllTypes() : BIBTEX.getAllTypes(); } @@ -170,6 +178,10 @@ private void addCustomOrModifiedType(BibEntryType type) { customOrModifiedType.remove(type); customOrModifiedType.add(type); } + + private void removeCustomOrModifiedEntryType(BibEntryType type) { + customOrModifiedType.remove(type); + } public SortedSet getAllTypes() { SortedSet allTypes = new TreeSet<>(customOrModifiedType); diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 8170d2881bb..08037697918 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -1250,6 +1250,7 @@ public void storeBibEntryTypes(List BibEntryTypes, BibDatabaseMode } } + @Override public List loadBibEntryTypes(BibDatabaseMode bibDatabaseMode) { List storedEntryTypes = new ArrayList<>(); Preferences prefsNode = getPrefsNodeForCustomizedEntryTypes(bibDatabaseMode); @@ -2096,8 +2097,7 @@ public void saveCustomEntryTypes() { } private void saveCustomEntryTypes(BibDatabaseMode bibDatabaseMode) { - List customBiblatexBibTexTypes = Globals.entryTypesManager.getAllTypes(bibDatabaseMode).stream() - .filter(type -> type instanceof BibEntryType) + List customBiblatexBibTexTypes = Globals.entryTypesManager.getAllTypes(bibDatabaseMode).stream() .map(entryType -> entryType).collect(Collectors.toList()); storeBibEntryTypes(customBiblatexBibTexTypes, bibDatabaseMode); diff --git a/src/main/java/org/jabref/preferences/PreferencesService.java b/src/main/java/org/jabref/preferences/PreferencesService.java index 19b88c79d26..b8456acc59b 100644 --- a/src/main/java/org/jabref/preferences/PreferencesService.java +++ b/src/main/java/org/jabref/preferences/PreferencesService.java @@ -19,6 +19,8 @@ import org.jabref.logic.util.UpdateFieldPreferences; import org.jabref.logic.util.io.AutoLinkPreferences; import org.jabref.logic.xmp.XmpPreferences; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.field.Field; import org.jabref.model.metadata.FilePreferences; import org.jabref.model.metadata.SaveOrderConfig; @@ -104,4 +106,6 @@ public interface PreferencesService { boolean getAllowIntegerEdition(); EntryEditorPreferences getEntryEditorPreferences(); + + List loadBibEntryTypes(BibDatabaseMode mode); } diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 7b0b9e6ec57..19746dff860 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -51,7 +51,6 @@ Added\ string=Added string Advanced=Advanced All\ entries=All entries -All\ entries\ of\ this\ type\ will\ be\ declared\ typeless.\ Continue?=All entries of this type will be declared typeless. Continue? Always\ reformat\ BIB\ file\ on\ save\ and\ export=Always reformat BIB file on save and export @@ -219,8 +218,6 @@ Execute\ default\ action\ in\ dialog=Execute default action in dialog Delete=Delete -Delete\ custom\ format=Delete custom format - Delete\ entry=Delete entry Delete\ multiple\ entries=Delete multiple entries @@ -2081,9 +2078,10 @@ Select\ all\ changes\ on\ the\ right=Select all changes on the right Dismiss=Dismiss Mark\ all\ changes\ as\ accepted=Mark all changes as accepted Unmark\ all\ changes=Unmark all changes + Normalize\ newline\ characters=Normalize newline characters Normalizes\ all\ newline\ characters\ in\ the\ field\ content.=Normalizes all newline characters in the field content. -Index=Index + Independent=Independent Intersection=Intersection Union=Union @@ -2098,3 +2096,17 @@ Invalid\ regular\ expression.=Invalid regular expression. Keyword\ delimiter=Keyword delimiter Hierarchical\ keyword\ delimiter=Hierarchical keyword delimiter Escape\ ampersands=Escape ampersands + + +Add\ new\ Field=Add new Field +Add\ new\ entry\ type=Add new entry type +Field\ type=Field type +Required\ and\ optional\ fields=Required and optional fields +Index=Index +Remove\ entry\ type=Remove entry type +Remove\ field\ %0\ from\ currently\ selected\ entry\ type=Remove field %0 from currently selected entry type + +Optional=Optional +Required=Required +Entry\ type\ cannot\ be\ empty.\ Please\ enter\ a\ name.=Entry type cannot be empty. Please enter a name. +Field\ cannot\ be\ empty.\ Please\ enter\ a\ name.=Field cannot be empty. Please enter a name. From b18b4d30e59db91f654728b4efcbac72746b1aa8 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Wed, 19 Feb 2020 10:09:47 +0100 Subject: [PATCH 06/11] Added MenuButtons to IntegrityCheckDialog (#5955) --- CHANGELOG.md | 16 ++--- .../gui/integrity/IntegrityCheckDialog.fxml | 36 ++++++---- .../gui/integrity/IntegrityCheckDialog.java | 72 ++++++++++++++++++- src/main/resources/l10n/JabRef_en.properties | 4 ++ 4 files changed, 103 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcbcb4953df..ae9e8816e5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,13 +12,13 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# ## [Unreleased] ### Changed -- we changed the open office panel to show buttons in rows of three instead of going straight down to save space as the button expanded out to take up unnecessary horizontal space [#5479] (https://github.com/JabRef/jabref/issues/5479) +- We changed the open office panel to show buttons in rows of three instead of going straight down to save space as the button expanded out to take up unnecessary horizontal space. [#5479](https://github.com/JabRef/jabref/issues/5479) - We cleaned up the group add/edit dialog. [#5826](https://github.com/JabRef/jabref/pull/5826) - We reintroduced the index column. [#5844](https://github.com/JabRef/jabref/pull/5844) +- Filenames of external files can no longer contain curly braces. [#5926](https://github.com/JabRef/jabref/pull/5926) +- We made the filters more easily accessible in the integrity check dialog. [#5955](https://github.com/JabRef/jabref/pull/5955) - We reimplemented and improved the dialog "Customize entry types" [#4719](https://github.com/JabRef/jabref/issues/4719) -- Filenames of external files can no longer contain curly braces - ### Fixed @@ -26,7 +26,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - We fixed an issue where the Medline fetcher was only working when JabRef was running from source. [#5645](https://github.com/JabRef/jabref/issues/5645) - We fixed some visual issues in the dark theme. [#5764](https://github.com/JabRef/jabref/pull/5764) [#5753](https://github.com/JabRef/jabref/issues/5753) - We fixed an issue where non-default previews didn't handle unicode characters. [#5779](https://github.com/JabRef/jabref/issues/5779) -- We improved the performance, especially changing field values in the entry should feel smoother now. +- We improved the performance, especially changing field values in the entry should feel smoother now. [#5843](https://github.com/JabRef/jabref/issues/5843) - We fixed an issue where the ampersand character wasn't rendering correctly on previews. [#3840](https://github.com/JabRef/jabref/issues/3840) - We fixed an issue where an erroneous "The library has been modified by another program" message was shown when saving. [#4877](https://github.com/JabRef/jabref/issues/4877) - We fixed an issue where the file extension was missing after downloading a file (we now fall-back to pdf). [#5816](https://github.com/JabRef/jabref/issues/5816) @@ -35,12 +35,12 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - We fixed several issues concerning managing external file types: Now everything is usable and fully functional. Previously, there were problems with the radio buttons, with saving the settings and with loading an input field value. Furthermore, different behavior for Windows and other operating systems was given, which was unified as well. [#5846](https://github.com/JabRef/jabref/issues/5846) - We fixed an issue where entries containing Unicode charaters were not parsed correctly [#5899](https://github.com/JabRef/jabref/issues/5899) - We fixed an issue where an entry containing an external filename with curly braces could not be saved. Curly braces are now longer allowed in filenames. [#5899](https://github.com/JabRef/jabref/issues/5899) -- We fixed an issue where changing the type of an entry did not update the main table [#5906](https://github.com/JabRef/jabref/issues/5906) -- We fixed an issue where opening a library from the recent libraries menu was not possible [#5939](https://github.com/JabRef/jabref/issues/5939) - +- We fixed an issue where changing the type of an entry did not update the main table. [#5906](https://github.com/JabRef/jabref/issues/5906) +- We fixed an issue where opening a library from the recent libraries menu was not possible. [#5939](https://github.com/JabRef/jabref/issues/5939) ### Removed -- Ampersands are no longer escaped by default in the `bib` file. If you want to keep the current behaviour, you can use the new "Escape Ampersands" formatter as a save action. + +- Ampersands are no longer escaped by default in the `bib` file. If you want to keep the current behaviour, you can use the new "Escape Ampersands" formatter as a save action. [#5869](https://github.com/JabRef/jabref/issues/5869) ## [5.0-beta] – 2019-12-15 diff --git a/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.fxml b/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.fxml index e3eb9580ce0..23693196dba 100644 --- a/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.fxml +++ b/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.fxml @@ -1,26 +1,32 @@ + - + - + + + - - - - - - - - - - - + + + + + + + + + + + + +