diff --git a/.springjavaformatconfig b/.springjavaformatconfig new file mode 100644 index 00000000000..881903b211e --- /dev/null +++ b/.springjavaformatconfig @@ -0,0 +1 @@ +indentation-style=spaces diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 5ced74bac45..2f2936ed4b8 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -18,4 +18,5 @@ dependencies { implementation("org.gradlex:java-module-testing:1.7") implementation("org.gradlex:jvm-dependency-conflict-resolution:2.4") implementation("org.gradle.toolchains:foojay-resolver:1.0.0") + implementation("io.spring.javaformat:io.spring.javaformat.gradle.plugin:0.0.47") } diff --git a/build-logic/src/main/kotlin/org.jabref.gradle.check.spring-javaformat.gradle.kts b/build-logic/src/main/kotlin/org.jabref.gradle.check.spring-javaformat.gradle.kts new file mode 100644 index 00000000000..0cde7faef5e --- /dev/null +++ b/build-logic/src/main/kotlin/org.jabref.gradle.check.spring-javaformat.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("io.spring.javaformat") +} diff --git a/build-logic/src/main/kotlin/org.jabref.gradle.module.gradle.kts b/build-logic/src/main/kotlin/org.jabref.gradle.module.gradle.kts index 83216741b67..0402e92be60 100644 --- a/build-logic/src/main/kotlin/org.jabref.gradle.module.gradle.kts +++ b/build-logic/src/main/kotlin/org.jabref.gradle.module.gradle.kts @@ -9,4 +9,5 @@ plugins { id("org.jabref.gradle.feature.test") id("org.jabref.gradle.check.checkstyle") id("org.jabref.gradle.check.modernizer") + id("org.jabref.gradle.check.spring-javaformat") } diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 12425a57f6c..10f04301fca 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -6,6 +6,10 @@ + + + + diff --git a/jabgui/src/main/java/org/jabref/Launcher.java b/jabgui/src/main/java/org/jabref/Launcher.java index 2d8e9c2ba57..936ea2b4ade 100644 --- a/jabgui/src/main/java/org/jabref/Launcher.java +++ b/jabgui/src/main/java/org/jabref/Launcher.java @@ -42,12 +42,13 @@ /// - Handle the command line arguments /// - Start the JavaFX application public class Launcher { + private static Logger LOGGER; public enum MultipleInstanceAction { - CONTINUE, - SHUTDOWN, - FOCUS + + CONTINUE, SHUTDOWN, FOCUS + } public static void main(String[] args) { @@ -57,9 +58,7 @@ public static void main(String[] args) { final JabRefGuiPreferences preferences = JabRefGuiPreferences.getInstance(); - ArgumentProcessor argumentProcessor = new ArgumentProcessor( - args, - ArgumentProcessor.Mode.INITIAL_START, + ArgumentProcessor argumentProcessor = new ArgumentProcessor(args, ArgumentProcessor.Mode.INITIAL_START, preferences); if (!argumentProcessor.getGuiCli().usageHelpRequested) { @@ -67,10 +66,12 @@ public static void main(String[] args) { Injector.setModelOrService(GuiPreferences.class, preferences); // Early exit in case another instance is already running - MultipleInstanceAction instanceAction = handleMultipleAppInstances(args, preferences.getRemotePreferences()); + MultipleInstanceAction instanceAction = handleMultipleAppInstances(args, + preferences.getRemotePreferences()); if (instanceAction == MultipleInstanceAction.SHUTDOWN) { systemExit(); - } else if (instanceAction == MultipleInstanceAction.FOCUS) { + } + else if (instanceAction == MultipleInstanceAction.FOCUS) { // Send focus command to running instance RemotePreferences remotePreferences = preferences.getRemotePreferences(); RemoteClient remoteClient = new RemoteClient(remotePreferences.getPort()); @@ -99,26 +100,27 @@ public static void main(String[] args) { } /** - * This needs to be called as early as possible. After the first log write, it - * is not possible to alter the log configuration programmatically anymore. + * This needs to be called as early as possible. After the first log write, it is not + * possible to alter the log configuration programmatically anymore. */ public static void initLogging(String[] args) { // routeLoggingToSlf4J SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.install(); - // We must configure logging as soon as possible, which is why we cannot wait for the usual + // We must configure logging as soon as possible, which is why we cannot wait for + // the usual // argument parsing workflow to parse logging options e.g. --debug - Level logLevel = Arrays.stream(args).anyMatch("--debug"::equalsIgnoreCase) - ? Level.DEBUG - : Level.INFO; + Level logLevel = Arrays.stream(args).anyMatch("--debug"::equalsIgnoreCase) ? Level.DEBUG : Level.INFO; // addLogToDisk - // We cannot use `Injector.instantiateModelOrService(BuildInfo.class).version` here, because this initializes logging + // We cannot use `Injector.instantiateModelOrService(BuildInfo.class).version` + // here, because this initializes logging Path directory = Directories.getLogDirectory(new BuildInfo().version); try { Files.createDirectories(directory); - } catch (IOException e) { + } + catch (IOException e) { LOGGER = LoggerFactory.getLogger(Launcher.class); LOGGER.error("Could not create log directory {}", directory, e); return; @@ -126,15 +128,12 @@ public static void initLogging(String[] args) { // The "Shared File Writer" is explained at // https://tinylog.org/v2/configuration/#shared-file-writer - Map configuration = Map.of( - "level", logLevel.name().toLowerCase(), - "writerFile", "rolling file", + Map configuration = Map.of("level", logLevel.name().toLowerCase(), "writerFile", "rolling file", "writerFile.level", logLevel.name().toLowerCase(), - // We need to manually join the path, because ".resolve" does not work on Windows, because ":" is not allowed in file names on Windows + // We need to manually join the path, because ".resolve" does not work on + // Windows, because ":" is not allowed in file names on Windows "writerFile.file", directory + File.separator + "log_{date:yyyy-MM-dd_HH-mm-ss}.txt", - "writerFile.charset", "UTF-8", - "writerFile.policies", "startup", - "writerFile.backups", "30"); + "writerFile.charset", "UTF-8", "writerFile.policies", "startup", "writerFile.backups", "30"); configuration.forEach(Configuration::set); LOGGER = LoggerFactory.getLogger(Launcher.class); @@ -148,9 +147,11 @@ private static void systemExit() { } /** - * @return MultipleInstanceAction: CONTINUE if JabRef should continue starting up, SHUTDOWN if it should quit, FOCUS if it should focus the existing instance. + * @return MultipleInstanceAction: CONTINUE if JabRef should continue starting up, + * SHUTDOWN if it should quit, FOCUS if it should focus the existing instance. */ - private static MultipleInstanceAction handleMultipleAppInstances(String[] args, RemotePreferences remotePreferences) { + private static MultipleInstanceAction handleMultipleAppInstances(String[] args, + RemotePreferences remotePreferences) { LOGGER.trace("Checking for remote handling..."); if (remotePreferences.useRemoteServer()) { @@ -159,24 +160,31 @@ private static MultipleInstanceAction handleMultipleAppInstances(String[] args, if (remoteClient.ping()) { LOGGER.debug("Pinging other instance succeeded."); if (args.length == 0) { - // There is already a server out there, avoid showing log "Passing arguments" while no arguments are provided. + // There is already a server out there, avoid showing log "Passing + // arguments" while no arguments are provided. LOGGER.warn("A JabRef instance is already running. Switching to that instance."); return MultipleInstanceAction.FOCUS; - } else { - // We are not alone, there is already a server out there, send command line arguments to other instance + } + else { + // We are not alone, there is already a server out there, send command + // line arguments to other instance LOGGER.debug("Passing arguments passed on to running JabRef..."); if (remoteClient.sendCommandLineArguments(args)) { // So we assume it's all taken care of, and quit. - // Output to both to the log and the screen. Therefore, we do not have an additional System.out.println. + // Output to both to the log and the screen. Therefore, we do not + // have an additional System.out.println. LOGGER.info("Arguments passed on to running JabRef instance. Shutting down."); return MultipleInstanceAction.SHUTDOWN; - } else { + } + else { LOGGER.warn("Could not communicate with other running JabRef instance."); } } - // We do not launch a new instance in presence if there is another instance running + // We do not launch a new instance in presence if there is another + // instance running return MultipleInstanceAction.SHUTDOWN; - } else { + } + else { LOGGER.debug("Could not ping JabRef instance."); } } @@ -193,4 +201,5 @@ private static void configureProxy(ProxyPreferences proxyPreferences) { private static void configureSSL(SSLPreferences sslPreferences) { TrustStoreManager.createTruststoreFileIfNotExist(Path.of(sslPreferences.getTruststorePath())); } + } diff --git a/jabgui/src/main/java/org/jabref/cli/ArgumentProcessor.java b/jabgui/src/main/java/org/jabref/cli/ArgumentProcessor.java index 1ebbfcadc7b..32fe6f7eb8b 100644 --- a/jabgui/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/jabgui/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -16,21 +16,28 @@ import picocli.CommandLine; public class ArgumentProcessor { + private static final Logger LOGGER = LoggerFactory.getLogger(ArgumentProcessor.class); - public enum Mode { INITIAL_START, REMOTE_START } + public enum Mode { + + INITIAL_START, REMOTE_START + + } private final Mode startupMode; + private final GuiPreferences preferences; + private final GuiCommandLine guiCli; + private final CommandLine cli; private final List uiCommands = new ArrayList<>(); + private boolean guiNeeded = true; - public ArgumentProcessor(String[] args, - Mode startupMode, - GuiPreferences preferences) { + public ArgumentProcessor(String[] args, Mode startupMode, GuiPreferences preferences) { this.startupMode = startupMode; this.preferences = preferences; this.guiCli = new GuiCommandLine(); @@ -74,7 +81,8 @@ public List processArguments() { if (guiCli.libraries != null && !guiCli.libraries.isEmpty()) { if (guiCli.append) { uiCommands.add(new UiCommand.AppendToCurrentLibrary(guiCli.libraries)); - } else { + } + else { uiCommands.add(new UiCommand.OpenLibraries(guiCli.libraries)); } } @@ -93,7 +101,8 @@ private void resetPreferences() { System.out.println(Localization.lang("Setting all preferences to default values.")); preferences.clear(); new SharedDatabasePreferences().clear(); - } catch (BackingStoreException e) { + } + catch (BackingStoreException e) { System.err.println(Localization.lang("Unable to clear preferences.")); LOGGER.error("Unable to clear preferences", e); } @@ -106,4 +115,5 @@ public boolean shouldShutDown() { public GuiCommandLine getGuiCli() { return guiCli; } + } diff --git a/jabgui/src/main/java/org/jabref/cli/CliImportHelper.java b/jabgui/src/main/java/org/jabref/cli/CliImportHelper.java index 759a5f2432d..1fbb17f1b54 100644 --- a/jabgui/src/main/java/org/jabref/cli/CliImportHelper.java +++ b/jabgui/src/main/java/org/jabref/cli/CliImportHelper.java @@ -22,16 +22,14 @@ /// @deprecated used by the browser extension only @Deprecated public class CliImportHelper { + private static final Logger LOGGER = LoggerFactory.getLogger(CliImportHelper.class); /** * Reads URIs as input - * * @param location URL or file path to import */ - public static Optional importFile(String location, - CliPreferences cliPreferences, - boolean porcelain) { + public static Optional importFile(String location, CliPreferences cliPreferences, boolean porcelain) { LOGGER.debug("Importing file from locaiton {}", location); String[] data = location.split(","); @@ -41,15 +39,18 @@ public static Optional importFile(String location, // Download web resource to temporary file try { file = new URLDownload(address).toTemporaryFile(); - } catch (FetcherException | - MalformedURLException e) { - System.err.println(Localization.lang("Problem downloading from %0: %1", address, e.getLocalizedMessage())); + } + catch (FetcherException | MalformedURLException e) { + System.err + .println(Localization.lang("Problem downloading from %0: %1", address, e.getLocalizedMessage())); return Optional.empty(); } - } else { + } + else { if (OS.WINDOWS) { file = Path.of(address); - } else { + } + else { file = Path.of(address.replace("~", System.getProperty("user.home"))); } } @@ -63,25 +64,22 @@ public static Optional importFile(String location, return importResult; } - public static Optional importFile(Path file, - CliPreferences cliPreferences, - boolean porcelain) { + public static Optional importFile(Path file, CliPreferences cliPreferences, boolean porcelain) { try { - ImportFormatReader importFormatReader = new ImportFormatReader( - cliPreferences.getImporterPreferences(), - cliPreferences.getImportFormatPreferences(), - cliPreferences.getCitationKeyPatternPreferences(), - new DummyFileUpdateMonitor() - ); + ImportFormatReader importFormatReader = new ImportFormatReader(cliPreferences.getImporterPreferences(), + cliPreferences.getImportFormatPreferences(), cliPreferences.getCitationKeyPatternPreferences(), + new DummyFileUpdateMonitor()); if (!porcelain) { System.out.println(Localization.lang("Importing %0", file)); } ParserResult result = importFormatReader.importFromFile("bibtex", file); return Optional.of(result); - } catch (ImportException ex) { + } + catch (ImportException ex) { LOGGER.error("Error opening file '{}'", file, ex); return Optional.empty(); } } + } diff --git a/jabgui/src/main/java/org/jabref/cli/GuiCommandLine.java b/jabgui/src/main/java/org/jabref/cli/GuiCommandLine.java index f725848d9ee..58119df83e9 100644 --- a/jabgui/src/main/java/org/jabref/cli/GuiCommandLine.java +++ b/jabgui/src/main/java/org/jabref/cli/GuiCommandLine.java @@ -10,37 +10,40 @@ @Command(name = "jabref", mixinStandardHelpOptions = true) public class GuiCommandLine { + @Parameters(paramLabel = "", description = "File(s) to be imported.") public List libraries; - @Option(names = {"-a", "--add"}, description = "Add to currently opened library.") + @Option(names = { "-a", "--add" }, description = "Add to currently opened library.") public boolean append; /// @deprecated used by the browser extension @Deprecated - @Option(names = {"--importBibtex"}, description = "Import bibtex string") + @Option(names = { "--importBibtex" }, description = "Import bibtex string") public String importBibtex; /// @deprecated used by the browser extension @Deprecated - @Option(names = {"-importToOpen", "--importToOpen"}, description = "Same as --import, but will be imported to the opened tab") + @Option(names = { "-importToOpen", "--importToOpen" }, + description = "Same as --import, but will be imported to the opened tab") public String importToOpen; - @Option(names = {"--reset"}, description = "Reset all preferences to default values.") + @Option(names = { "--reset" }, description = "Reset all preferences to default values.") public boolean resetPreferences; - @Option(names = {"-v", "--version"}, versionHelp = true, description = "Display version info.") + @Option(names = { "-v", "--version" }, versionHelp = true, description = "Display version info.") public boolean versionInfoRequested; - @Option(names = {"?", "-h", "--help"}, usageHelp = true, description = "Display this help message.") + @Option(names = { "?", "-h", "--help" }, usageHelp = true, description = "Display this help message.") public boolean usageHelpRequested; - @Option(names = {"--debug"}, description = "Enable debug logging.") + @Option(names = { "--debug" }, description = "Enable debug logging.") public boolean debugLogging; - @Option(names = {"-b", "--blank"}, description = "Start with an empty library.") + @Option(names = { "-b", "--blank" }, description = "Start with an empty library.") public boolean blank; - @Option(names = {"-j", "--jumpToKey"}, description = "Jump to the entry of the given citation key.") + @Option(names = { "-j", "--jumpToKey" }, description = "Jump to the entry of the given citation key.") public String jumpToKey; + } diff --git a/jabgui/src/main/java/org/jabref/gui/AbstractViewModel.java b/jabgui/src/main/java/org/jabref/gui/AbstractViewModel.java index d294a280d44..a9f2dbb3af2 100644 --- a/jabgui/src/main/java/org/jabref/gui/AbstractViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/AbstractViewModel.java @@ -1,5 +1,7 @@ package org.jabref.gui; public class AbstractViewModel { + // empty + } diff --git a/jabgui/src/main/java/org/jabref/gui/ClipBoardManager.java b/jabgui/src/main/java/org/jabref/gui/ClipBoardManager.java index f96510c1e02..ce621c5ab3b 100644 --- a/jabgui/src/main/java/org/jabref/gui/ClipBoardManager.java +++ b/jabgui/src/main/java/org/jabref/gui/ClipBoardManager.java @@ -29,9 +29,11 @@ @AllowedToUseAwt("Requires java.awt.datatransfer.Clipboard") public class ClipBoardManager { + private static final Logger LOGGER = LoggerFactory.getLogger(ClipBoardManager.class); private static Clipboard clipboard; + private static java.awt.datatransfer.Clipboard primary; public ClipBoardManager() { @@ -44,24 +46,29 @@ public ClipBoardManager(Clipboard clipboard, java.awt.datatransfer.Clipboard pri } /** - * Add X11 clipboard support to a text input control. It is necessary to call this method in every input where you - * want to use it: {@code ClipBoardManager.addX11Support(TextInputControl input);}. - * - * @param input the TextInputControl (e.g., TextField, TextArea, and children) where adding this functionality. - * @see Short summary for X11 - * clipboards - * @see Longer + * Add X11 clipboard support to a text input control. It is necessary to call this + * method in every input where you want to use it: + * {@code ClipBoardManager.addX11Support(TextInputControl input);}. + * @param input the TextInputControl (e.g., TextField, TextArea, and children) where + * adding this functionality. + * @see Short + * summary for X11 clipboards + * @see Longer * text over clipboards */ public static void addX11Support(TextInputControl input) { - input.selectedTextProperty().addListener( - // using InvalidationListener because of https://bugs.openjdk.java.net/browse/JDK-8176270 - observable -> Platform.runLater(() -> { - String newValue = input.getSelectedText(); - if (!newValue.isEmpty() && (primary != null)) { - primary.setContents(new StringSelection(newValue), null); - } - })); + input.selectedTextProperty() + .addListener( + // using InvalidationListener because of + // https://bugs.openjdk.java.net/browse/JDK-8176270 + observable -> Platform.runLater(() -> { + String newValue = input.getSelectedText(); + if (!newValue.isEmpty() && (primary != null)) { + primary.setContents(new StringSelection(newValue), null); + } + })); input.setOnMouseClicked(event -> { if (event.getButton() == MouseButton.MIDDLE) { input.insertText(input.getCaretPosition(), getContentsPrimary()); @@ -71,7 +78,6 @@ public static void addX11Support(TextInputControl input) { /** * Get the String residing on the system clipboard. - * * @return any text found on the Clipboard; if none found, return an empty String. */ public static String getContents() { @@ -96,8 +102,8 @@ public static boolean hasHtml() { /** * Get the String residing on the primary clipboard (if it exists). - * - * @return any text found on the primary Clipboard; if none found, try with the system clipboard. + * @return any text found on the primary Clipboard; if none found, try with the system + * clipboard. */ public static String getContentsPrimary() { if (primary != null) { @@ -105,7 +111,8 @@ public static String getContentsPrimary() { if ((contents != null) && contents.isDataFlavorSupported(DataFlavor.stringFlavor)) { try { return (String) contents.getTransferData(DataFlavor.stringFlavor); - } catch (UnsupportedFlavorException | IOException e) { + } + catch (UnsupportedFlavorException | IOException e) { LOGGER.warn("", e); } } @@ -115,8 +122,8 @@ public static String getContentsPrimary() { /** * Puts content onto the system clipboard. - * - * @param content the ClipboardContent to set as current value of the system clipboard. + * @param content the ClipboardContent to set as current value of the system + * clipboard. */ public void setContent(ClipboardContent content) { clipboard.setContent(content); @@ -125,8 +132,8 @@ public void setContent(ClipboardContent content) { /** * Puts content onto the primary clipboard (if it exists). - * - * @param content the ClipboardContent to set as current value of the primary clipboard. + * @param content the ClipboardContent to set as current value of the primary + * clipboard. */ public void setPrimaryClipboardContent(ClipboardContent content) { if (primary != null) { @@ -154,9 +161,11 @@ public void setContent(List entries, BibEntryTypesManager entryTypesMa setContent(serializedEntries); } - public void setContent(List entries, BibEntryTypesManager entryTypesManager, List stringConstants) throws IOException { + public void setContent(List entries, BibEntryTypesManager entryTypesManager, + List stringConstants) throws IOException { StringBuilder builder = new StringBuilder(); - stringConstants.forEach(strConst -> builder.append(strConst.getParsedSerialization() == null ? "" : strConst.getParsedSerialization())); + stringConstants.forEach(strConst -> builder + .append(strConst.getParsedSerialization() == null ? "" : strConst.getParsedSerialization())); String serializedEntries = serializeEntries(entries, entryTypesManager); builder.append(serializedEntries); setContent(builder.toString()); @@ -164,10 +173,15 @@ public void setContent(List entries, BibEntryTypesManager entryTypesMa private String serializeEntries(List entries, BibEntryTypesManager entryTypesManager) throws IOException { CliPreferences preferences = Injector.instantiateModelOrService(CliPreferences.class); - // BibEntry is not Java serializable. Thus, we need to do the serialization manually - // At reading of the clipboard in JabRef, we parse the plain string in all cases, so we don't need to flag we put BibEntries here - // Furthermore, storing a string also enables other applications to work with the data - BibEntryWriter writer = new BibEntryWriter(new FieldWriter(preferences.getFieldPreferences()), entryTypesManager); + // BibEntry is not Java serializable. Thus, we need to do the serialization + // manually + // At reading of the clipboard in JabRef, we parse the plain string in all cases, + // so we don't need to flag we put BibEntries here + // Furthermore, storing a string also enables other applications to work with the + // data + BibEntryWriter writer = new BibEntryWriter(new FieldWriter(preferences.getFieldPreferences()), + entryTypesManager); return writer.serializeAll(entries, BibDatabaseMode.BIBTEX); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/CoreGuiPreferences.java b/jabgui/src/main/java/org/jabref/gui/CoreGuiPreferences.java index 9108f48b5b2..bff838b0013 100644 --- a/jabgui/src/main/java/org/jabref/gui/CoreGuiPreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/CoreGuiPreferences.java @@ -6,23 +6,23 @@ import javafx.beans.property.SimpleDoubleProperty; public class CoreGuiPreferences { + private final DoubleProperty positionX; + private final DoubleProperty positionY; + private final DoubleProperty sizeX; + private final DoubleProperty sizeY; private final BooleanProperty windowMaximised; private final DoubleProperty horizontalDividerPosition; + private final DoubleProperty verticalDividerPosition; - public CoreGuiPreferences(double positionX, - double positionY, - double sizeX, - double sizeY, - boolean windowMaximised, - double horizontalDividerPosition, - double verticalDividerPosition) { + public CoreGuiPreferences(double positionX, double positionY, double sizeX, double sizeY, boolean windowMaximised, + double horizontalDividerPosition, double verticalDividerPosition) { this.positionX = new SimpleDoubleProperty(positionX); this.positionY = new SimpleDoubleProperty(positionY); this.sizeX = new SimpleDoubleProperty(sizeX); @@ -115,4 +115,5 @@ public DoubleProperty getVerticalDividerPositionProperty() { public void setVerticalDividerPosition(double dividerPosition) { this.verticalDividerPosition.set(dividerPosition); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/DialogService.java b/jabgui/src/main/java/org/jabref/gui/DialogService.java index 6ac9708018a..1ffd76017c7 100644 --- a/jabgui/src/main/java/org/jabref/gui/DialogService.java +++ b/jabgui/src/main/java/org/jabref/gui/DialogService.java @@ -35,70 +35,78 @@ public interface DialogService extends NotificationService { /** - * This will create and display new {@link ChoiceDialog} of type T with a default choice and a collection of possible choices + * This will create and display new {@link ChoiceDialog} of type T with a default + * choice and a collection of possible choices * - * @implNote The implementation should accept {@code null} for {@code defaultChoice}, but callers should use {@link #showChoiceDialogAndWait(String, String, String, Collection)}. + * @implNote The implementation should accept {@code null} for {@code defaultChoice}, + * but callers should use + * {@link #showChoiceDialogAndWait(String, String, String, Collection)}. */ - Optional showChoiceDialogAndWait(String title, String content, String okButtonLabel, T defaultChoice, Collection choices); + Optional showChoiceDialogAndWait(String title, String content, String okButtonLabel, T defaultChoice, + Collection choices); /** - * This will create and display new {@link ChoiceDialog} of type T with a collection of possible choices + * This will create and display new {@link ChoiceDialog} of type T with a collection + * of possible choices */ - default Optional showChoiceDialogAndWait(String title, String content, String okButtonLabel, Collection choices) { + default Optional showChoiceDialogAndWait(String title, String content, String okButtonLabel, + Collection choices) { return showChoiceDialogAndWait(title, content, okButtonLabel, null, choices); } - Optional showEditableChoiceDialogAndWait(String title, String content, String okButtonLabel, T defaultChoice, Collection choices, StringConverter converter); + Optional showEditableChoiceDialogAndWait(String title, String content, String okButtonLabel, T defaultChoice, + Collection choices, StringConverter converter); - default Optional showEditableChoiceDialogAndWait(String title, String content, String okButtonLabel, Collection choices, StringConverter converter) { + default Optional showEditableChoiceDialogAndWait(String title, String content, String okButtonLabel, + Collection choices, StringConverter converter) { return showEditableChoiceDialogAndWait(title, content, okButtonLabel, null, choices, converter); } /** - * This will create and display new {@link TextInputDialog} with a text fields to enter data + * This will create and display new {@link TextInputDialog} with a text fields to + * enter data */ Optional showInputDialogAndWait(String title, String content); /** - * This will create and display new {@link TextInputDialog} with a text field with a default value to enter data + * This will create and display new {@link TextInputDialog} with a text field with a + * default value to enter data */ Optional showInputDialogWithDefaultAndWait(String title, String content, String defaultValue); /** - * This will create and display a new information dialog. - * It will include a blue information icon on the left and - * a single OK Button. To create an information dialog with custom - * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)} + * This will create and display a new information dialog. It will include a blue + * information icon on the left and a single OK Button. To create an information + * dialog with custom buttons see also + * {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)} */ void showInformationDialogAndWait(String title, String content); /** - * This will create and display a new information dialog. - * It will include a yellow warning icon on the left and - * a single OK Button. To create a warning dialog with custom - * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)} + * This will create and display a new information dialog. It will include a yellow + * warning icon on the left and a single OK Button. To create a warning dialog with + * custom buttons see also + * {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)} */ void showWarningDialogAndWait(String title, String content); /** - * This will create and display a new error dialog. - * It will include a red error icon on the left and - * a single OK Button. To create a error dialog with custom - * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)} + * This will create and display a new error dialog. It will include a red error icon + * on the left and a single OK Button. To create a error dialog with custom buttons + * see also + * {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)} */ void showErrorDialogAndWait(String title, String content); /** * Create and display error dialog displaying the given exception. - * - * @param message the error message + * @param message the error message * @param exception the exception causing the error */ void showErrorDialogAndWait(String message, Throwable exception); /** * Create and display error dialog displaying the given exception. - * * @param exception the exception causing the error */ void showErrorDialogAndWait(Exception exception); @@ -107,225 +115,205 @@ default Optional showEditableChoiceDialogAndWait(String title, String con /** * Create and display error dialog displaying the given exception. - * * @param exception the exception causing the error */ void showErrorDialogAndWait(String title, String content, Throwable exception); /** * Create and display error dialog displaying the given message. - * * @param message the error message */ void showErrorDialogAndWait(String message); /** - * This will create and display a new confirmation dialog. - * It will include a blue question icon on the left and - * a OK and Cancel button. To create a confirmation dialog with custom - * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)} - * + * This will create and display a new confirmation dialog. It will include a blue + * question icon on the left and a OK and Cancel button. To create a confirmation + * dialog with custom buttons see also + * {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)} * @return true if the use clicked "OK" otherwise false */ boolean showConfirmationDialogAndWait(String title, String content); /** - * Create and display a new confirmation dialog. - * It will include a blue question icon on the left and - * a OK (with given label) and Cancel button. To create a confirmation dialog with custom - * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)}. - * + * Create and display a new confirmation dialog. It will include a blue question icon + * on the left and a OK (with given label) and Cancel button. To create a confirmation + * dialog with custom buttons see also + * {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)}. * @return true if the use clicked "OK" otherwise false */ boolean showConfirmationDialogAndWait(String title, String content, String okButtonLabel); /** - * Create and display a new confirmation dialog. - * It will include a blue question icon on the left and - * a OK (with given label) and Cancel (also with given label) button. To create a confirmation dialog with custom - * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)}. - * + * Create and display a new confirmation dialog. It will include a blue question icon + * on the left and a OK (with given label) and Cancel (also with given label) button. + * To create a confirmation dialog with custom buttons see also + * {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)}. * @return true if the use clicked "OK" otherwise false */ boolean showConfirmationDialogAndWait(String title, String content, String okButtonLabel, String cancelButtonLabel); /** - * Create and display a new confirmation dialog. - * It will include a blue question icon on the left and - * a YES (with given label) and Cancel (also with given label) button. To create a confirmation dialog with custom - * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)}. - * Moreover, the dialog contains a opt-out checkbox with the given text to support "Do not ask again"-behaviour. - * + * Create and display a new confirmation dialog. It will include a blue question icon + * on the left and a YES (with given label) and Cancel (also with given label) button. + * To create a confirmation dialog with custom buttons see also + * {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)}. + * Moreover, the dialog contains a opt-out checkbox with the given text to support "Do + * not ask again"-behaviour. * @return true if the use clicked "YES" otherwise false */ - boolean showConfirmationDialogWithOptOutAndWait(String title, String content, - String optOutMessage, Consumer optOutAction); + boolean showConfirmationDialogWithOptOutAndWait(String title, String content, String optOutMessage, + Consumer optOutAction); /** - * Create and display a new confirmation dialog. - * It will include a blue question icon on the left and - * a YES (with given label) and Cancel (also with given label) button. To create a confirmation dialog with custom - * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)}. - * Moreover, the dialog contains a opt-out checkbox with the given text to support "Do not ask again"-behaviour. - * + * Create and display a new confirmation dialog. It will include a blue question icon + * on the left and a YES (with given label) and Cancel (also with given label) button. + * To create a confirmation dialog with custom buttons see also + * {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)}. + * Moreover, the dialog contains a opt-out checkbox with the given text to support "Do + * not ask again"-behaviour. * @return true if the use clicked "YES" otherwise false */ - boolean showConfirmationDialogWithOptOutAndWait(String title, String content, - String okButtonLabel, String cancelButtonLabel, - String optOutMessage, Consumer optOutAction); + boolean showConfirmationDialogWithOptOutAndWait(String title, String content, String okButtonLabel, + String cancelButtonLabel, String optOutMessage, Consumer optOutAction); /** - * This will create and display new {@link CustomPasswordField} that doesn't show the text, and two buttons - * one cancel and one ok. - * + * This will create and display new {@link CustomPasswordField} that doesn't show the + * text, and two buttons one cancel and one ok. * @return the entered password if pressed "OK", null otherwise */ Optional showPasswordDialogAndWait(String title, String header, String content); /** * Shows a custom dialog without returning any results. - * * @param dialog dialog to show */ void showCustomDialog(BaseDialog dialog); /** * Shows a custom window. - * * @param window window to show */ void showCustomWindow(BaseWindow window); /** - * This will create and display a new dialog of the specified - * {@link Alert.AlertType} but with user defined buttons as optional - * {@link ButtonType}s. - * + * This will create and display a new dialog of the specified {@link Alert.AlertType} + * but with user defined buttons as optional {@link ButtonType}s. * @return Optional with the pressed Button as ButtonType */ Optional showCustomButtonDialogAndWait(Alert.AlertType type, String title, String content, - ButtonType... buttonTypes); + ButtonType... buttonTypes); /** - * This will create and display a new dialog showing a custom {@link DialogPane} - * using custom {@link ButtonType}s, and attaches optional {@link Tooltip}s to the buttons. - * Useful for providing additional context to the user through tooltips - * without cluttering the main dialog content. - * + * This will create and display a new dialog showing a custom {@link DialogPane} using + * custom {@link ButtonType}s, and attaches optional {@link Tooltip}s to the buttons. + * Useful for providing additional context to the user through tooltips without + * cluttering the main dialog content. * @return Optional with the pressed Button as ButtonType */ - Optional showCustomButtonDialogWithTooltipsAndWait(Alert.AlertType type, - String title, - String content, - Map tooltips, - ButtonType... buttonTypes); + Optional showCustomButtonDialogWithTooltipsAndWait(Alert.AlertType type, String title, String content, + Map tooltips, ButtonType... buttonTypes); /** - * This will create and display a new dialog showing a custom {@link DialogPane} - * and using custom {@link ButtonType}s. - * + * This will create and display a new dialog showing a custom {@link DialogPane} and + * using custom {@link ButtonType}s. * @return Optional with the pressed Button as ButtonType */ Optional showCustomDialogAndWait(String title, DialogPane contentPane, ButtonType... buttonTypes); /** * Shows a custom dialog and returns the result. - * * @param dialog dialog to show - * @param type of result + * @param type of result */ Optional showCustomDialogAndWait(Dialog dialog); /** - * Constructs and shows a cancelable {@link ProgressDialog}. - * Clicking cancel will cancel the underlying service and close the dialog - * - * @param title title of the dialog + * Constructs and shows a cancelable {@link ProgressDialog}. Clicking cancel will + * cancel the underlying service and close the dialog + * @param title title of the dialog * @param content message to show above the progress bar - * @param task The {@link Task} which executes the work and for which to show the dialog + * @param task The {@link Task} which executes the work and for which to show the + * dialog */ void showProgressDialog(String title, String content, Task task); /** - * Constructs and shows a cancelable {@link ProgressDialog}. - * Clicking cancel will cancel the underlying service and close the dialog, - * otherwise will wait for the task to finish. - * - * @param title title of the dialog + * Constructs and shows a cancelable {@link ProgressDialog}. Clicking cancel will + * cancel the underlying service and close the dialog, otherwise will wait for the + * task to finish. + * @param title title of the dialog * @param content message to show above the progress bar - * @param task The {@link Task} which executes the work and for which to show the dialog + * @param task The {@link Task} which executes the work and for which to show the + * dialog */ void showProgressDialogAndWait(String title, String content, Task task); /** * Constructs and shows a dialog showing the progress of running background tasks. - * Clicking cancel will cancel the underlying service and close the dialog. - * The dialog will exit as soon as none of the background tasks are running - * - * @param title title of the dialog - * @param content message to show below the list of background tasks + * Clicking cancel will cancel the underlying service and close the dialog. The dialog + * will exit as soon as none of the background tasks are running + * @param title title of the dialog + * @param content message to show below the list of background tasks * @param stateManager The {@link StateManager} which contains the background tasks */ - Optional showBackgroundProgressDialogAndWait(String title, String content, StateManager stateManager); + Optional showBackgroundProgressDialogAndWait(String title, String content, + StateManager stateManager); /** - * Shows a new file save dialog. The method doesn't return until the - * displayed file save dialog is dismissed. The return value specifies the - * file chosen by the user or an empty {@link Optional} if no selection has been made. - * After a file was selected, the given file dialog configuration is updated with the selected extension type (if any). - * + * Shows a new file save dialog. The method doesn't return until the displayed file + * save dialog is dismissed. The return value specifies the file chosen by the user or + * an empty {@link Optional} if no selection has been made. After a file was selected, + * the given file dialog configuration is updated with the selected extension type (if + * any). * @return the selected file or an empty {@link Optional} if no file has been selected */ Optional showFileSaveDialog(FileDialogConfiguration fileDialogConfiguration); /** - * Shows a new file open dialog. The method doesn't return until the - * displayed open dialog is dismissed. The return value specifies - * the file chosen by the user or an empty {@link Optional} if no selection has been - * made. - * After a file was selected, the given file dialog configuration is updated with the selected extension type (if any). - * + * Shows a new file open dialog. The method doesn't return until the displayed open + * dialog is dismissed. The return value specifies the file chosen by the user or an + * empty {@link Optional} if no selection has been made. After a file was selected, + * the given file dialog configuration is updated with the selected extension type (if + * any). * @return the selected file or an empty {@link Optional} if no file has been selected */ Optional showFileOpenDialog(FileDialogConfiguration fileDialogConfiguration); /** - * Shows a new file open dialog. The method doesn't return until the - * displayed open dialog is dismissed. The return value specifies - * the files chosen by the user or an empty {@link List} if no selection has been - * made. - * + * Shows a new file open dialog. The method doesn't return until the displayed open + * dialog is dismissed. The return value specifies the files chosen by the user or an + * empty {@link List} if no selection has been made. * @return the selected files or an empty {@link List} if no file has been selected */ List showFileOpenDialogAndGetMultipleFiles(FileDialogConfiguration fileDialogConfiguration); /** * Shows a new directory selection dialog. The method doesn't return until the - * displayed open dialog is dismissed. The return value specifies - * the file chosen by the user or an empty {@link Optional} if no selection has been - * made. - * - * @return the selected directory or an empty {@link Optional} if no directory has been selected + * displayed open dialog is dismissed. The return value specifies the file chosen by + * the user or an empty {@link Optional} if no selection has been made. + * @return the selected directory or an empty {@link Optional} if no directory has + * been selected */ Optional showDirectorySelectionDialog(DirectoryDialogConfiguration directoryDialogConfiguration); /** - * Displays a Print Dialog. Allow the user to update job state such as printer and settings. These changes will be - * available in the appropriate properties after the print dialog has returned. The print dialog is also used to - * confirm the user wants to proceed with printing. - * + * Displays a Print Dialog. Allow the user to update job state such as printer and + * settings. These changes will be available in the appropriate properties after the + * print dialog has returned. The print dialog is also used to confirm the user wants + * to proceed with printing. * @param job the print job to customize * @return false if the user opts to cancel printing */ boolean showPrintDialog(PrinterJob job); /** - * Shows a new dialog that list all files contained in the given archive and which lets the user select one of these - * files. The method doesn't return until the displayed open dialog is dismissed. The return value specifies the - * file chosen by the user or an empty {@link Optional} if no selection has been made. - * + * Shows a new dialog that list all files contained in the given archive and which + * lets the user select one of these files. The method doesn't return until the + * displayed open dialog is dismissed. The return value specifies the file chosen by + * the user or an empty {@link Optional} if no selection has been made. * @return the selected file or an empty {@link Optional} if no file has been selected */ Optional showFileOpenFromArchiveDialog(Path archivePath) throws IOException; + } diff --git a/jabgui/src/main/java/org/jabref/gui/DragAndDropDataFormats.java b/jabgui/src/main/java/org/jabref/gui/DragAndDropDataFormats.java index 4ab27396efa..4a1d60b20bd 100644 --- a/jabgui/src/main/java/org/jabref/gui/DragAndDropDataFormats.java +++ b/jabgui/src/main/java/org/jabref/gui/DragAndDropDataFormats.java @@ -12,9 +12,16 @@ public class DragAndDropDataFormats { public static final DataFormat FIELD = new DataFormat("dnd/org.jabref.model.entry.field.Field"); + public static final DataFormat GROUP = new DataFormat("dnd/org.jabref.model.groups.GroupTreeNode"); + public static final DataFormat LINKED_FILE = new DataFormat("dnd/org.jabref.model.entry.LinkedFile"); + public static final DataFormat ENTRIES = new DataFormat("dnd/org.jabref.model.entry.BibEntries"); + public static final DataFormat PREVIEWLAYOUTS = new DataFormat("dnd/org.jabref.logic.citationstyle.PreviewLayouts"); - @SuppressWarnings("unchecked") public static final Class> PREVIEWLAYOUT_LIST_CLASS = (Class>) (Class) List.class; + + @SuppressWarnings("unchecked") + public static final Class> PREVIEWLAYOUT_LIST_CLASS = (Class>) (Class) List.class; + } diff --git a/jabgui/src/main/java/org/jabref/gui/FXDialog.java b/jabgui/src/main/java/org/jabref/gui/FXDialog.java index 891eae589f7..5aa788a6bb8 100644 --- a/jabgui/src/main/java/org/jabref/gui/FXDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/FXDialog.java @@ -17,9 +17,9 @@ /** * This class provides a super class for all dialogs implemented in JavaFX. *

- * To create a custom JavaFX dialog one should create an instance of this class and set a dialog - * pane through the inherited {@link Dialog#setDialogPane(DialogPane)} method. - * The dialog can be shown via {@link Dialog#show()} or {@link Dialog#showAndWait()}. + * To create a custom JavaFX dialog one should create an instance of this class and set a + * dialog pane through the inherited {@link Dialog#setDialogPane(DialogPane)} method. The + * dialog can be shown via {@link Dialog#show()} or {@link Dialog#showAndWait()}. *

* The layout of the pane should be defined in an external fxml file and loaded it via the * {@link FXMLLoader}. @@ -55,7 +55,8 @@ public FXDialog(AlertType type, boolean isModal) { dialogWindow.setOnCloseRequest(evt -> this.close()); if (isModal) { initModality(Modality.APPLICATION_MODAL); - } else { + } + else { initModality(Modality.NONE); } @@ -79,4 +80,5 @@ private void setDialogIcon(Image image) { private Stage getDialogWindow() { return (Stage) getDialogPane().getScene().getWindow(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/JabRefDialogService.java b/jabgui/src/main/java/org/jabref/gui/JabRefDialogService.java index 82167f34bc0..36095f27064 100644 --- a/jabgui/src/main/java/org/jabref/gui/JabRefDialogService.java +++ b/jabgui/src/main/java/org/jabref/gui/JabRefDialogService.java @@ -65,19 +65,19 @@ import org.slf4j.LoggerFactory; /** - * This class provides methods to create default - * JavaFX dialogs which will also work on top of Swing - * windows. The created dialogs are instances of the - * {@link FXDialog} class. The available dialogs in this class - * are useful for displaying small information graphic dialogs - * rather than complex windows. For more complex dialogs it is - * advised to rather create a new sub class of {@link FXDialog}. + * This class provides methods to create default JavaFX dialogs which will also work on + * top of Swing windows. The created dialogs are instances of the {@link FXDialog} class. + * The available dialogs in this class are useful for displaying small information graphic + * dialogs rather than complex windows. For more complex dialogs it is advised to rather + * create a new sub class of {@link FXDialog}. */ public class JabRefDialogService implements DialogService { + // Snackbar dialog maximum size public static final int DIALOG_SIZE_LIMIT = 300; private static final Duration TOAST_MESSAGE_DISPLAY_TIME = Duration.millis(3000); + private static final Logger LOGGER = LoggerFactory.getLogger(JabRefDialogService.class); private final Window mainWindow; @@ -100,14 +100,16 @@ private FXDialog createDialog(AlertType type, String title, String content) { return alert; } - private FXDialog createDialogWithOptOut(String title, String content, - String optOutMessage, Consumer optOutAction) { + private FXDialog createDialogWithOptOut(String title, String content, String optOutMessage, + Consumer optOutAction) { FXDialog alert = new FXDialog(AlertType.CONFIRMATION, title, true); - // Need to force the alert to layout in order to grab the graphic as we are replacing the dialog pane with a custom pane + // Need to force the alert to layout in order to grab the graphic as we are + // replacing the dialog pane with a custom pane alert.getDialogPane().applyCss(); Node graphic = alert.getDialogPane().getGraphic(); - // Create a new dialog pane that has a checkbox instead of the hide/show details button + // Create a new dialog pane that has a checkbox instead of the hide/show details + // button // Use the supplied callback for the action of the checkbox alert.setDialogPane(new DialogPane() { @Override @@ -119,7 +121,8 @@ protected Node createDetailsButton() { } }); - // Fool the dialog into thinking there is some expandable content; a group won't take up any space if it has no children + // Fool the dialog into thinking there is some expandable content; a group won't + // take up any space if it has no children alert.getDialogPane().setExpandableContent(new Group()); alert.getDialogPane().setExpanded(true); @@ -139,7 +142,8 @@ public static String shortenDialogMessage(String dialogMessage) { return (dialogMessage.substring(0, JabRefDialogService.DIALOG_SIZE_LIMIT) + "...").trim(); } - private ChoiceDialog createChoiceDialog(String title, String content, String okButtonLabel, T defaultChoice, Collection choices) { + private ChoiceDialog createChoiceDialog(String title, String content, String okButtonLabel, T defaultChoice, + Collection choices) { ChoiceDialog choiceDialog = new ChoiceDialog<>(defaultChoice, choices); ((Stage) choiceDialog.getDialogPane().getScene().getWindow()).getIcons().add(IconTheme.getJabRefImage()); ButtonType okButtonType = new ButtonType(okButtonLabel, ButtonBar.ButtonData.OK_DONE); @@ -152,12 +156,14 @@ private ChoiceDialog createChoiceDialog(String title, String content, Str } @Override - public Optional showChoiceDialogAndWait(String title, String content, String okButtonLabel, T defaultChoice, Collection choices) { + public Optional showChoiceDialogAndWait(String title, String content, String okButtonLabel, T defaultChoice, + Collection choices) { return createChoiceDialog(title, content, okButtonLabel, defaultChoice, choices).showAndWait(); } @Override - public Optional showEditableChoiceDialogAndWait(String title, String content, String okButtonLabel, T defaultChoice, Collection choices, StringConverter converter) { + public Optional showEditableChoiceDialogAndWait(String title, String content, String okButtonLabel, + T defaultChoice, Collection choices, StringConverter converter) { ChoiceDialog choiceDialog = createChoiceDialog(title, content, okButtonLabel, defaultChoice, choices); ComboBox comboBox = (ComboBox) choiceDialog.getDialogPane().lookup(".combo-box"); comboBox.setEditable(true); @@ -218,7 +224,8 @@ public void showErrorDialogAndWait(Exception exception) { if (exception instanceof FetcherException fetcherException) { // Somehow, Java does not route correctly to the other method showErrorDialogAndWait(fetcherException); - } else { + } + else { showErrorDialogAndWait(Localization.lang("Unhandled exception occurred."), exception); } } @@ -229,13 +236,20 @@ public void showErrorDialogAndWait(FetcherException fetcherException) { String localizedMessage = fetcherException.getLocalizedMessage(); Optional httpResponse = fetcherException.getHttpResponse(); if (httpResponse.isPresent()) { - this.showInformationDialogAndWait(failedTitle, getContentByCode(httpResponse.get().statusCode()) + "\n\n" + localizedMessage); - } else if (fetcherException instanceof FetcherClientException) { - this.showErrorDialogAndWait(failedTitle, Localization.lang("Something is wrong on JabRef side. Please check the URL and try again.") + "\n\n" + localizedMessage); - } else if (fetcherException instanceof FetcherServerException) { this.showInformationDialogAndWait(failedTitle, - Localization.lang("Error downloading from URL. Cause is likely the server side.\nPlease try again later or contact the server administrator.") + "\n\n" + localizedMessage); - } else { + getContentByCode(httpResponse.get().statusCode()) + "\n\n" + localizedMessage); + } + else if (fetcherException instanceof FetcherClientException) { + this.showErrorDialogAndWait(failedTitle, + Localization.lang("Something is wrong on JabRef side. Please check the URL and try again.") + "\n\n" + + localizedMessage); + } + else if (fetcherException instanceof FetcherServerException) { + this.showInformationDialogAndWait(failedTitle, Localization.lang( + "Error downloading from URL. Cause is likely the server side.\nPlease try again later or contact the server administrator.") + + "\n\n" + localizedMessage); + } + else { this.showErrorDialogAndWait(failedTitle, localizedMessage); } } @@ -270,8 +284,8 @@ public boolean showConfirmationDialogAndWait(String title, String content, Strin } @Override - public boolean showConfirmationDialogAndWait(String title, String content, - String okButtonLabel, String cancelButtonLabel) { + public boolean showConfirmationDialogAndWait(String title, String content, String okButtonLabel, + String cancelButtonLabel) { FXDialog alert = createDialog(AlertType.CONFIRMATION, title, content); ButtonType okButtonType = new ButtonType(okButtonLabel, ButtonBar.ButtonData.OK_DONE); ButtonType cancelButtonType = new ButtonType(cancelButtonLabel, ButtonBar.ButtonData.NO); @@ -280,17 +294,16 @@ public boolean showConfirmationDialogAndWait(String title, String content, } @Override - public boolean showConfirmationDialogWithOptOutAndWait(String title, String content, - String optOutMessage, Consumer optOutAction) { + public boolean showConfirmationDialogWithOptOutAndWait(String title, String content, String optOutMessage, + Consumer optOutAction) { FXDialog alert = createDialogWithOptOut(title, content, optOutMessage, optOutAction); alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO); return alert.showAndWait().filter(buttonType -> buttonType == ButtonType.YES).isPresent(); } @Override - public boolean showConfirmationDialogWithOptOutAndWait(String title, String content, - String okButtonLabel, String cancelButtonLabel, - String optOutMessage, Consumer optOutAction) { + public boolean showConfirmationDialogWithOptOutAndWait(String title, String content, String okButtonLabel, + String cancelButtonLabel, String optOutMessage, Consumer optOutAction) { FXDialog alert = createDialogWithOptOut(title, content, optOutMessage, optOutAction); ButtonType okButtonType = new ButtonType(okButtonLabel, ButtonBar.ButtonData.YES); ButtonType cancelButtonType = new ButtonType(cancelButtonLabel, ButtonBar.ButtonData.NO); @@ -300,18 +313,15 @@ public boolean showConfirmationDialogWithOptOutAndWait(String title, String cont @Override public Optional showCustomButtonDialogAndWait(AlertType type, String title, String content, - ButtonType... buttonTypes) { + ButtonType... buttonTypes) { FXDialog alert = createDialog(type, title, content); alert.getButtonTypes().setAll(buttonTypes); return alert.showAndWait(); } @Override - public Optional showCustomButtonDialogWithTooltipsAndWait(AlertType type, - String title, - String content, - Map tooltips, - ButtonType... buttonTypes) { + public Optional showCustomButtonDialogWithTooltipsAndWait(AlertType type, String title, String content, + Map tooltips, ButtonType... buttonTypes) { // Use a non-editable Label instead of raw text to prevent editing FXDialog alert = createDialog(type, title, ""); Label contentLabel = new Label(content); @@ -335,7 +345,7 @@ public Optional showCustomButtonDialogWithTooltipsAndWait(AlertType @Override public Optional showCustomDialogAndWait(String title, DialogPane contentPane, - ButtonType... buttonTypes) { + ButtonType... buttonTypes) { FXDialog alert = new FXDialog(AlertType.NONE, title); alert.setDialogPane(contentPane); alert.getButtonTypes().setAll(buttonTypes); @@ -409,11 +419,13 @@ public void showProgressDialogAndWait(String title, String content, Task } @Override - public Optional showBackgroundProgressDialogAndWait(String title, String content, StateManager stateManager) { + public Optional showBackgroundProgressDialogAndWait(String title, String content, + StateManager stateManager) { TaskProgressView> taskProgressView = new TaskProgressView<>(); EasyBind.bindContent(taskProgressView.getTasks(), stateManager.getRunningBackgroundTasks()); taskProgressView.setRetainTasks(false); - taskProgressView.setGraphicFactory(task -> ThemeManager.getDownloadIconTitleMap.getOrDefault(task.getTitle(), null)); + taskProgressView + .setGraphicFactory(task -> ThemeManager.getDownloadIconTitleMap.getOrDefault(task.getTitle(), null)); Label message = new Label(content); @@ -441,35 +453,33 @@ public Optional showBackgroundProgressDialogAndWait(String title, St @Override public void notify(String message) { - // TODO: Change to a notification overview instead of event log when that is available. - // The event log is not that user friendly (different purpose). + // TODO: Change to a notification overview instead of event log when that is + // available. + // The event log is not that user friendly (different purpose). LOGGER.info(message); - UiTaskExecutor.runInJavaFXThread(() -> - Notifications.create() - .text(message) - .position(Pos.BOTTOM_CENTER) - .hideAfter(TOAST_MESSAGE_DISPLAY_TIME) - .owner(mainWindow) - .threshold(5, - Notifications.create() - .title(Localization.lang("Last notification")) - .text( - "(" + Localization.lang("Check the event log to see all notifications") + ")" - + "\n\n" + message) - .onAction(e -> { - ErrorConsoleAction ec = new ErrorConsoleAction(); - ec.execute(); - })) - .hideCloseButton() - .show()); + UiTaskExecutor.runInJavaFXThread(() -> Notifications.create() + .text(message) + .position(Pos.BOTTOM_CENTER) + .hideAfter(TOAST_MESSAGE_DISPLAY_TIME) + .owner(mainWindow) + .threshold(5, Notifications.create() + .title(Localization.lang("Last notification")) + .text("(" + Localization.lang("Check the event log to see all notifications") + ")" + "\n\n" + message) + .onAction(e -> { + ErrorConsoleAction ec = new ErrorConsoleAction(); + ec.execute(); + })) + .hideCloseButton() + .show()); } @Override public Optional showFileSaveDialog(FileDialogConfiguration fileDialogConfiguration) { FileChooser chooser = getConfiguredFileChooser(fileDialogConfiguration); File file = chooser.showSaveDialog(mainWindow); - Optional.ofNullable(chooser.getSelectedExtensionFilter()).ifPresent(fileDialogConfiguration::setSelectedExtensionFilter); + Optional.ofNullable(chooser.getSelectedExtensionFilter()) + .ifPresent(fileDialogConfiguration::setSelectedExtensionFilter); return Optional.ofNullable(file).map(File::toPath); } @@ -477,7 +487,8 @@ public Optional showFileSaveDialog(FileDialogConfiguration fileDialogConfi public Optional showFileOpenDialog(FileDialogConfiguration fileDialogConfiguration) { FileChooser chooser = getConfiguredFileChooser(fileDialogConfiguration); File file = chooser.showOpenDialog(mainWindow); - Optional.ofNullable(chooser.getSelectedExtensionFilter()).ifPresent(fileDialogConfiguration::setSelectedExtensionFilter); + Optional.ofNullable(chooser.getSelectedExtensionFilter()) + .ifPresent(fileDialogConfiguration::setSelectedExtensionFilter); return Optional.ofNullable(file).map(File::toPath); } @@ -492,7 +503,8 @@ public Optional showDirectorySelectionDialog(DirectoryDialogConfiguration public List showFileOpenDialogAndGetMultipleFiles(FileDialogConfiguration fileDialogConfiguration) { FileChooser chooser = getConfiguredFileChooser(fileDialogConfiguration); List files = chooser.showOpenMultipleDialog(mainWindow); - Optional.ofNullable(chooser.getSelectedExtensionFilter()).ifPresent(fileDialogConfiguration::setSelectedExtensionFilter); + Optional.ofNullable(chooser.getSelectedExtensionFilter()) + .ifPresent(fileDialogConfiguration::setSelectedExtensionFilter); return files != null ? files.stream().map(File::toPath).toList() : List.of(); } @@ -520,7 +532,8 @@ public boolean showPrintDialog(PrinterJob job) { public Optional showFileOpenFromArchiveDialog(Path archivePath) throws IOException { try (FileSystem zipFile = FileSystems.newFileSystem(archivePath, (ClassLoader) null)) { return new ZipFileChooser(zipFile).showAndWait(); - } catch (NoClassDefFoundError exc) { + } + catch (NoClassDefFoundError exc) { throw new IOException("Could not instantiate ZIP-archive reader.", exc); } } @@ -544,14 +557,14 @@ public void showCustomWindow(BaseWindow window) { private String getContentByCode(int statusCode) { return switch (statusCode) { - case 401 -> - Localization.lang("Access denied. You are not authorized to access this resource. Please check your credentials and try again. If you believe you should have access, please contact the administrator for assistance."); - case 403 -> - Localization.lang("Access denied. You do not have permission to access this resource. Please contact the administrator for assistance or try a different action."); - case 404 -> - Localization.lang("The requested resource could not be found. It seems that the file you are trying to download is not available or has been moved. Please verify the URL and try again. If you believe this is an error, please contact the administrator for further assistance."); - default -> - Localization.lang("Something is wrong on JabRef side. Please check the URL and try again."); + case 401 -> Localization.lang( + "Access denied. You are not authorized to access this resource. Please check your credentials and try again. If you believe you should have access, please contact the administrator for assistance."); + case 403 -> Localization.lang( + "Access denied. You do not have permission to access this resource. Please contact the administrator for assistance or try a different action."); + case 404 -> Localization.lang( + "The requested resource could not be found. It seems that the file you are trying to download is not available or has been moved. Please verify the URL and try again. If you believe this is an error, please contact the administrator for further assistance."); + default -> Localization.lang("Something is wrong on JabRef side. Please check the URL and try again."); }; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/JabRefGUI.java b/jabgui/src/main/java/org/jabref/gui/JabRefGUI.java index 9e3448c2ac3..146c0afc95c 100644 --- a/jabgui/src/main/java/org/jabref/gui/JabRefGUI.java +++ b/jabgui/src/main/java/org/jabref/gui/JabRefGUI.java @@ -68,29 +68,39 @@ public class JabRefGUI extends Application { private static final Logger LOGGER = LoggerFactory.getLogger(JabRefGUI.class); private static List uiCommands; + private static GuiPreferences preferences; // AI Service handles chat messages etc. Therefore, it is tightly coupled to the GUI. private static AiService aiService; - // CitationsAndRelationsSearchService is here configured for a local machine and so to the GUI. + + // CitationsAndRelationsSearchService is here configured for a local machine and so to + // the GUI. private static SearchCitationsRelationsService citationsAndRelationsSearchService; private static FileUpdateMonitor fileUpdateMonitor; + private static StateManager stateManager; + private static ThemeManager themeManager; + private static CountingUndoManager countingUndoManager; + private static TaskExecutor taskExecutor; + private static ClipBoardManager clipBoardManager; + private static DialogService dialogService; + private static JabRefFrame mainFrame; private static RemoteListenerServerManager remoteListenerServerManager; + private static HttpServerManager httpServerManager; private Stage mainStage; - public static void setup(List uiCommands, - GuiPreferences preferences) { + public static void setup(List uiCommands, GuiPreferences preferences) { JabRefGUI.uiCommands = uiCommands; JabRefGUI.preferences = preferences; } @@ -107,37 +117,24 @@ public void start(Stage stage) { initialize(); - JabRefGUI.mainFrame = new JabRefFrame( - mainStage, - dialogService, - fileUpdateMonitor, - preferences, - aiService, - stateManager, - countingUndoManager, - Injector.instantiateModelOrService(BibEntryTypesManager.class), - clipBoardManager, - taskExecutor); + JabRefGUI.mainFrame = new JabRefFrame(mainStage, dialogService, fileUpdateMonitor, preferences, aiService, + stateManager, countingUndoManager, Injector.instantiateModelOrService(BibEntryTypesManager.class), + clipBoardManager, taskExecutor); openWindow(); startBackgroundTasks(); if (!fileUpdateMonitor.isActive()) { - dialogService.showErrorDialogAndWait( - Localization.lang("Unable to monitor file changes. Please close files " + - "and processes and restart. You may encounter errors if you continue " + - "with this session.")); + dialogService.showErrorDialogAndWait(Localization.lang("Unable to monitor file changes. Please close files " + + "and processes and restart. You may encounter errors if you continue " + "with this session.")); } BuildInfo buildInfo = Injector.instantiateModelOrService(BuildInfo.class); EasyBind.subscribe(preferences.getInternalPreferences().versionCheckEnabledProperty(), enabled -> { if (enabled) { - new VersionWorker(buildInfo.version, - dialogService, - taskExecutor, - preferences) - .checkForNewVersionDelayed(); + new VersionWorker(buildInfo.version, dialogService, taskExecutor, preferences) + .checkForNewVersionDelayed(); } }); @@ -157,8 +154,10 @@ public void initialize() { BibEntryTypesManager entryTypesManager = preferences.getCustomEntryTypesRepository(); Injector.setModelOrService(BibEntryTypesManager.class, entryTypesManager); - Injector.setModelOrService(JournalAbbreviationRepository.class, JournalAbbreviationLoader.loadRepository(preferences.getJournalAbbreviationPreferences())); - Injector.setModelOrService(ProtectedTermsLoader.class, new ProtectedTermsLoader(preferences.getProtectedTermsPreferences())); + Injector.setModelOrService(JournalAbbreviationRepository.class, + JournalAbbreviationLoader.loadRepository(preferences.getJournalAbbreviationPreferences())); + Injector.setModelOrService(ProtectedTermsLoader.class, + new ProtectedTermsLoader(preferences.getProtectedTermsPreferences())); IndexManager.clearOldSearchIndices(); @@ -173,9 +172,7 @@ public void initialize() { Injector.setModelOrService(KeyBindingRepository.class, preferences.getKeyBindingRepository()); - JabRefGUI.themeManager = new ThemeManager( - preferences.getWorkspacePreferences(), - fileUpdateMonitor, + JabRefGUI.themeManager = new ThemeManager(preferences.getWorkspacePreferences(), fileUpdateMonitor, Runnable::run); Injector.setModelOrService(ThemeManager.class, themeManager); @@ -193,20 +190,13 @@ public void initialize() { JabRefGUI.clipBoardManager = new ClipBoardManager(); Injector.setModelOrService(ClipBoardManager.class, clipBoardManager); - JabRefGUI.aiService = new AiService( - preferences.getAiPreferences(), - preferences.getFilePreferences(), - preferences.getCitationKeyPatternPreferences(), - dialogService, - taskExecutor); + JabRefGUI.aiService = new AiService(preferences.getAiPreferences(), preferences.getFilePreferences(), + preferences.getCitationKeyPatternPreferences(), dialogService, taskExecutor); Injector.setModelOrService(AiService.class, aiService); JabRefGUI.citationsAndRelationsSearchService = new SearchCitationsRelationsService( - preferences.getImporterPreferences(), - preferences.getImportFormatPreferences(), - preferences.getFieldPreferences(), - entryTypesManager - ); + preferences.getImporterPreferences(), preferences.getImportFormatPreferences(), + preferences.getFieldPreferences(), entryTypesManager); Injector.setModelOrService(SearchCitationsRelationsService.class, citationsAndRelationsSearchService); } @@ -228,15 +218,14 @@ private void setupProxy() { return; } - Optional password = dialogService.showPasswordDialogAndWait( - Localization.lang("Proxy configuration"), - Localization.lang("Proxy requires password"), - Localization.lang("Password")); + Optional password = dialogService.showPasswordDialogAndWait(Localization.lang("Proxy configuration"), + Localization.lang("Proxy requires password"), Localization.lang("Password")); if (password.isPresent()) { preferences.getProxyPreferences().setPassword(password.get()); ProxyRegisterer.register(preferences.getProxyPreferences()); - } else { + } + else { LOGGER.warn("No proxy password specified"); } } @@ -250,7 +239,8 @@ private void openWindow() { mainStage.setMinWidth(580); mainStage.setMinHeight(330); - // maximized target state is stored, because "saveWindowState" saves x and y only if not maximized + // maximized target state is stored, because "saveWindowState" saves x and y only + // if not maximized boolean windowMaximised = coreGuiPreferences.isWindowMaximised(); LOGGER.debug("Screens: {}", Screen.getScreens()); @@ -263,8 +253,10 @@ private void openWindow() { mainStage.setWidth(coreGuiPreferences.getSizeX()); mainStage.setHeight(coreGuiPreferences.getSizeY()); LOGGER.debug("NOT saving window positions"); - } else { - LOGGER.info("The JabRef window is outside of screen bounds. Position and size will be corrected to 1024x768. Primary screen will be used."); + } + else { + LOGGER.info( + "The JabRef window is outside of screen bounds. Position and size will be corrected to 1024x768. Primary screen will be used."); Rectangle2D bounds = Screen.getPrimary().getBounds(); mainStage.setX(bounds.getMinX()); mainStage.setY(bounds.getMinY()); @@ -314,12 +306,14 @@ public void onShowing(WindowEvent event) { // Open last edited databases if (uiCommands.stream().noneMatch(UiCommand.BlankWorkspace.class::isInstance) - && preferences.getWorkspacePreferences().shouldOpenLastEdited()) { + && preferences.getWorkspacePreferences().shouldOpenLastEdited()) { mainFrame.openLastEditedDatabases(); } Platform.runLater(() -> { - // We need to check at this point, because here, all libraries are loaded (e.g., load previously opened libraries) and all UI commands (e.g., load libraries, blank workspace, ...) are handled. + // We need to check at this point, because here, all libraries are loaded + // (e.g., load previously opened libraries) and all UI commands (e.g., load + // libraries, blank workspace, ...) are handled. if (stateManager.getOpenDatabases().isEmpty()) { mainFrame.showWelcomeTab(); } @@ -347,10 +341,12 @@ private void saveWindowState() { preferences.setSizeX(mainStage.getWidth()); preferences.setSizeY(mainStage.getHeight()); } - // maximize does not correctly work on OSX, reports true, although the window was resized! + // maximize does not correctly work on OSX, reports true, although the window was + // resized! if (OS.OS_X) { preferences.setWindowMaximised(false); - } else { + } + else { preferences.setWindowMaximised(mainStage.isMaximized()); } debugLogWindowState(mainStage); @@ -358,19 +354,18 @@ private void saveWindowState() { /** * prints the data from the screen (only in debug mode) - * * @param mainStage JabRef's stage */ private void debugLogWindowState(Stage mainStage) { LOGGER.debug(""" - screen data: - mainStage.WINDOW_MAXIMISED: {} - mainStage.POS_X: {} - mainStage.POS_Y: {} - mainStage.SIZE_X: {} - mainStage.SIZE_Y: {} - """, - mainStage.isMaximized(), mainStage.getX(), mainStage.getY(), mainStage.getWidth(), mainStage.getHeight()); + screen data: + mainStage.WINDOW_MAXIMISED: {} + mainStage.POS_X: {} + mainStage.POS_Y: {} + mainStage.SIZE_X: {} + mainStage.SIZE_Y: {} + """, mainStage.isMaximized(), mainStage.getX(), mainStage.getY(), mainStage.getWidth(), + mainStage.getHeight()); } /** @@ -398,8 +393,10 @@ private boolean lowerLeftIsInBounds(CoreGuiPreferences coreGuiPreferences) { } private boolean upperRightIsInBounds(CoreGuiPreferences coreGuiPreferences) { - // The upper right corner is checked as there are most probably the window controls. - // Windows/PowerToys somehow adds 10 pixels to the right and top of the screen, they are removed + // The upper right corner is checked as there are most probably the window + // controls. + // Windows/PowerToys somehow adds 10 pixels to the right and top of the screen, + // they are removed double rightX = coreGuiPreferences.getPositionX() + coreGuiPreferences.getSizeX() - 10.0; double topY = coreGuiPreferences.getPositionY(); LOGGER.debug("right x: {}, top y: {}", rightX, topY); @@ -414,10 +411,7 @@ public void startBackgroundTasks() { RemotePreferences remotePreferences = preferences.getRemotePreferences(); if (remotePreferences.useRemoteServer()) { - remoteListenerServerManager.openAndStart( - new CLIMessageHandler( - mainFrame, - preferences), + remoteListenerServerManager.openAndStart(new CLIMessageHandler(mainFrame, preferences), remotePreferences.getPort()); } @@ -432,7 +426,8 @@ public void stop() { try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { LOGGER.trace("Stopping JabRef GUI using a virtual thread executor"); - // Shutdown everything in parallel to prevent causing non-shutdown of something in case of issues + // Shutdown everything in parallel to prevent causing non-shutdown of + // something in case of issues executor.submit(() -> { LOGGER.trace("Closing citations and relations search service"); citationsAndRelationsSearchService.close(); @@ -443,7 +438,8 @@ public void stop() { LOGGER.trace("Closing AI service"); try { aiService.close(); - } catch (Exception e) { + } + catch (Exception e) { LOGGER.error("Unable to close AI service", e); } LOGGER.trace("AI service closed"); @@ -517,4 +513,5 @@ public void stop() { // Just to be sure that we do not leave any threads running System.exit(0); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/JabRefGuiStateManager.java b/jabgui/src/main/java/org/jabref/gui/JabRefGuiStateManager.java index c66beb82f7e..b397e1a8ffa 100644 --- a/jabgui/src/main/java/org/jabref/gui/JabRefGuiStateManager.java +++ b/jabgui/src/main/java/org/jabref/gui/JabRefGuiStateManager.java @@ -52,43 +52,80 @@ * This class manages the GUI-state of JabRef, including: * *

    - *
  • currently selected database
  • - *
  • currently selected group
  • - *
  • active search
  • - *
  • active number of search results
  • - *
  • focus owner
  • - *
  • dialog window sizes/positions
  • - *
  • opened AI chat window (controlled by {@link org.jabref.logic.ai.AiService})
  • + *
  • currently selected database
  • + *
  • currently selected group
  • + *
  • active search
  • + *
  • active number of search results
  • + *
  • focus owner
  • + *
  • dialog window sizes/positions
  • + *
  • opened AI chat window (controlled by {@link org.jabref.logic.ai.AiService})
  • *
*/ public class JabRefGuiStateManager implements StateManager { private static final Logger LOGGER = LoggerFactory.getLogger(JabRefGuiStateManager.class); + private final CustomLocalDragboard localDragboard = new CustomLocalDragboard(); + private final ObservableList openDatabases = FXCollections.observableArrayList(); + private final OptionalObjectProperty activeDatabase = OptionalObjectProperty.empty(); + private final OptionalObjectProperty activeTab = OptionalObjectProperty.empty(); + private final ObservableList selectedEntries = FXCollections.observableArrayList(); - private final ObservableMap> selectedGroups = FXCollections.observableHashMap(); + + private final ObservableMap> selectedGroups = FXCollections + .observableHashMap(); + private final ObservableMap indexManagers = FXCollections.observableHashMap(); + private final OptionalObjectProperty activeSearchQuery = OptionalObjectProperty.empty(); + private final OptionalObjectProperty activeGlobalSearchQuery = OptionalObjectProperty.empty(); + private final StringProperty searchQueryProperty = new SimpleStringProperty(); + private final IntegerProperty searchResultSize = new SimpleIntegerProperty(0); + private final IntegerProperty globalSearchResultSize = new SimpleIntegerProperty(0); + private final OptionalObjectProperty focusOwner = OptionalObjectProperty.empty(); - private final ObservableList, Task>> backgroundTasksPairs = FXCollections.observableArrayList(task -> new Observable[] {task.getValue().progressProperty(), task.getValue().runningProperty()}); + + private final ObservableList, Task>> backgroundTasksPairs = FXCollections + .observableArrayList( + task -> new Observable[] { task.getValue().progressProperty(), task.getValue().runningProperty() }); + private final ObservableList> backgroundTasks = EasyBind.map(backgroundTasksPairs, Pair::getValue); + private final FilteredList> runningBackgroundTasks = new FilteredList<>(backgroundTasks, Task::isRunning); - private final BooleanBinding anyTaskRunning = Bindings.createBooleanBinding(() -> !runningBackgroundTasks.isEmpty(), runningBackgroundTasks); - private final EasyBinding anyTasksThatWillNotBeRecoveredRunning = EasyBind.reduce(backgroundTasksPairs, tasks -> tasks.anyMatch(task -> !task.getKey().willBeRecoveredAutomatically() && task.getValue().isRunning())); - private final EasyBinding tasksProgress = EasyBind.reduce(backgroundTasksPairs, tasks -> tasks.map(Pair::getValue).filter(Task::isRunning).mapToDouble(Task::getProgress).average().orElse(1)); + + private final BooleanBinding anyTaskRunning = Bindings.createBooleanBinding(() -> !runningBackgroundTasks.isEmpty(), + runningBackgroundTasks); + + private final EasyBinding anyTasksThatWillNotBeRecoveredRunning = EasyBind.reduce(backgroundTasksPairs, + tasks -> tasks + .anyMatch(task -> !task.getKey().willBeRecoveredAutomatically() && task.getValue().isRunning())); + + private final EasyBinding tasksProgress = EasyBind.reduce(backgroundTasksPairs, + tasks -> tasks.map(Pair::getValue) + .filter(Task::isRunning) + .mapToDouble(Task::getProgress) + .average() + .orElse(1)); + private final ObservableMap dialogWindowStates = FXCollections.observableHashMap(); + private final ObservableList visibleSidePanes = FXCollections.observableArrayList(); + private final ObjectProperty lastAutomaticFieldEditorEdit = new SimpleObjectProperty<>(); + private final ObservableList searchHistory = FXCollections.observableArrayList(); + private final List aiChatWindows = new ArrayList<>(); + private final BooleanProperty editorShowing = new SimpleBooleanProperty(false); + private final OptionalObjectProperty activeWalkthrough = OptionalObjectProperty.empty(); @Override @@ -144,7 +181,8 @@ public void setSelectedEntries(List newSelectedEntries) { @Override public void setSelectedGroups(BibDatabaseContext context, List newSelectedGroups) { Objects.requireNonNull(newSelectedGroups); - selectedGroups.computeIfAbsent(context.getUid(), k -> FXCollections.observableArrayList()).setAll(newSelectedGroups); + selectedGroups.computeIfAbsent(context.getUid(), k -> FXCollections.observableArrayList()) + .setAll(newSelectedGroups); } @Override @@ -177,7 +215,8 @@ public void setActiveDatabase(BibDatabaseContext database) { if (database == null) { LOGGER.info("No open database detected"); activeDatabaseProperty().set(Optional.empty()); - } else { + } + else { activeDatabaseProperty().set(Optional.of(database)); } } @@ -246,10 +285,9 @@ public void setLastAutomaticFieldEditorEdit(LastAutomaticFieldEditorEdit automat public List getAllDatabasePaths() { List list = new ArrayList<>(); getOpenDatabases().stream() - .map(BibDatabaseContext::getDatabasePath) - .forEachOrdered(pathOptional -> pathOptional.ifPresentOrElse( - path -> list.add(path.toAbsolutePath().toString()), - () -> list.add(""))); + .map(BibDatabaseContext::getDatabasePath) + .forEachOrdered(pathOptional -> pathOptional + .ifPresentOrElse(path -> list.add(path.toAbsolutePath().toString()), () -> list.add(""))); return list; } @@ -307,4 +345,5 @@ public void setActiveWalkthrough(Walkthrough walkthrough) { public Optional getActiveWalkthrough() { return activeWalkthrough.get(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java index b4216b2bda2..d7156e5b6f3 100644 --- a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java +++ b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java @@ -104,18 +104,29 @@ import org.slf4j.LoggerFactory; /** - * Represents the ui area where the notifier pane, the library table and the entry editor are shown. + * Represents the ui area where the notifier pane, the library table and the entry editor + * are shown. */ public class LibraryTab extends Tab implements CommandSelectionTab { + private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); + private final LibraryTabContainer tabContainer; + private final CountingUndoManager undoManager; + private final DialogService dialogService; + private final GuiPreferences preferences; + private final FileUpdateMonitor fileUpdateMonitor; + private final StateManager stateManager; + private final BibEntryTypesManager entryTypesManager; + private final BooleanProperty changedProperty = new SimpleBooleanProperty(false); + private final BooleanProperty nonUndoableChangeProperty = new SimpleBooleanProperty(false); private BibDatabaseContext bibDatabaseContext; @@ -125,8 +136,11 @@ public class LibraryTab extends Tab implements CommandSelectionTab { private CoarseChangeFilter coarseChangeFilter; private MainTableDataModel tableModel; + private FileAnnotationCache annotationCache; + private MainTable mainTable; + private DatabaseNotification databaseNotificationPane; // Indicates whether the tab is loading data using a dataloading task @@ -140,11 +154,13 @@ public class LibraryTab extends Tab implements CommandSelectionTab { private SuggestionProviders suggestionProviders; - @SuppressWarnings({"FieldCanBeLocal"}) + @SuppressWarnings({ "FieldCanBeLocal" }) private Subscription dividerPositionSubscription; private ListProperty selectedGroupsProperty; + private final OptionalObjectProperty searchQueryProperty = OptionalObjectProperty.empty(); + private final IntegerProperty resultSize = new SimpleIntegerProperty(0); private Optional changeMonitor = Optional.empty(); @@ -152,31 +168,30 @@ public class LibraryTab extends Tab implements CommandSelectionTab { private BackgroundTask dataLoadingTask; private final ClipBoardManager clipBoardManager; + private final TaskExecutor taskExecutor; private ImportHandler importHandler; + private IndexManager indexManager; private final AiService aiService; /** - * @param isDummyContext Indicates whether the database context is a dummy. A dummy context is used to display a progress indicator while parsing the database. - * If the context is a dummy, the Lucene index should not be created, as both the dummy context and the actual context share the same index path {@link BibDatabaseContext#getFulltextIndexPath()}. - * If the index is created for the dummy context, the actual context will not be able to open the index until it is closed by the dummy context. - * Closing the index takes time and will slow down opening the library. + * @param isDummyContext Indicates whether the database context is a dummy. A dummy + * context is used to display a progress indicator while parsing the database. If the + * context is a dummy, the Lucene index should not be created, as both the dummy + * context and the actual context share the same index path + * {@link BibDatabaseContext#getFulltextIndexPath()}. If the index is created for the + * dummy context, the actual context will not be able to open the index until it is + * closed by the dummy context. Closing the index takes time and will slow down + * opening the library. */ - private LibraryTab(@NonNull BibDatabaseContext bibDatabaseContext, - @NonNull LibraryTabContainer tabContainer, - DialogService dialogService, - AiService aiService, - GuiPreferences preferences, - StateManager stateManager, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager, - CountingUndoManager undoManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor, - boolean isDummyContext) { + private LibraryTab(@NonNull BibDatabaseContext bibDatabaseContext, @NonNull LibraryTabContainer tabContainer, + DialogService dialogService, AiService aiService, GuiPreferences preferences, StateManager stateManager, + FileUpdateMonitor fileUpdateMonitor, BibEntryTypesManager entryTypesManager, + CountingUndoManager undoManager, ClipBoardManager clipBoardManager, TaskExecutor taskExecutor, + boolean isDummyContext) { this.bibDatabaseContext = bibDatabaseContext; this.tabContainer = tabContainer; this.undoManager = undoManager; @@ -201,8 +216,8 @@ private LibraryTab(@NonNull BibDatabaseContext bibDatabaseContext, stateManager.activeDatabaseProperty().addListener((_, _, _) -> { if (preferences.getSearchPreferences().isFulltext()) { - mainTable.getTableModel().refreshSearchMatches(); - } + mainTable.getTableModel().refreshSearchMatches(); + } }); } @@ -216,18 +231,13 @@ private void initializeComponentsAndListeners(boolean isDummyContext) { } this.selectedGroupsProperty = new SimpleListProperty<>(stateManager.getSelectedGroups(bibDatabaseContext)); - this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferences, taskExecutor, getIndexManager(), selectedGroupsProperty(), searchQueryProperty, resultSizeProperty()); + this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferences, taskExecutor, getIndexManager(), + selectedGroupsProperty(), searchQueryProperty, resultSizeProperty()); new CitationStyleCache(bibDatabaseContext); annotationCache = new FileAnnotationCache(bibDatabaseContext, preferences.getFilePreferences()); - importHandler = new ImportHandler( - bibDatabaseContext, - preferences, - fileUpdateMonitor, - undoManager, - stateManager, - dialogService, - taskExecutor); + importHandler = new ImportHandler(bibDatabaseContext, preferences, fileUpdateMonitor, undoManager, stateManager, + dialogService, taskExecutor); setupMainPanel(); setupAutoCompletion(); @@ -236,7 +246,8 @@ private void initializeComponentsAndListeners(boolean isDummyContext) { this.getDatabase().registerListener(new IndexUpdateListener()); - // ensure that at each addition of a new entry, the entry is added to the groups interface + // ensure that at each addition of a new entry, the entry is added to the groups + // interface this.bibDatabaseContext.getDatabase().registerListener(new GroupTreeListener()); // ensure that all entry changes mark the panel as changed this.bibDatabaseContext.getDatabase().registerListener(this); @@ -248,8 +259,8 @@ private void initializeComponentsAndListeners(boolean isDummyContext) { Platform.runLater(() -> { EasyBind.subscribe(changedProperty, this::updateTabTitle); - stateManager.getOpenDatabases().addListener((ListChangeListener) c -> - updateTabTitle(changedProperty.getValue())); + stateManager.getOpenDatabases() + .addListener((ListChangeListener) c -> updateTabTitle(changedProperty.getValue())); }); } @@ -305,7 +316,8 @@ private void onDatabaseLoadingSucceed(ParserResult result) { } public void createIndexManager() { - indexManager = new IndexManager(bibDatabaseContext, taskExecutor, preferences, Injector.instantiateModelOrService(PostgreServer.class)); + indexManager = new IndexManager(bibDatabaseContext, taskExecutor, preferences, + Injector.instantiateModelOrService(PostgreServer.class)); stateManager.setIndexManager(bibDatabaseContext, indexManager); } @@ -339,8 +351,13 @@ private void setDatabaseContext(@NonNull BibDatabaseContext bibDatabaseContext) stateManager.activeTabProperty().set(Optional.of(this)); } - // Remove existing dummy BibDatabaseContext and add correct BibDatabaseContext from ParserResult to trigger changes in the openDatabases list in the stateManager - Optional foundExistingBibDatabase = stateManager.getOpenDatabases().stream().filter(databaseContext -> databaseContext.equals(this.bibDatabaseContext)).findFirst(); + // Remove existing dummy BibDatabaseContext and add correct BibDatabaseContext + // from ParserResult to trigger changes in the openDatabases list in the + // stateManager + Optional foundExistingBibDatabase = stateManager.getOpenDatabases() + .stream() + .filter(databaseContext -> databaseContext.equals(this.bibDatabaseContext)) + .findFirst(); foundExistingBibDatabase.ifPresent(databaseContext -> stateManager.getOpenDatabases().remove(databaseContext)); this.bibDatabaseContext = bibDatabaseContext; @@ -354,18 +371,18 @@ private void setDatabaseContext(@NonNull BibDatabaseContext bibDatabaseContext) public void installAutosaveManagerAndBackupManager() { if (isDatabaseReadyForAutoSave(bibDatabaseContext)) { AutosaveManager autosaveManager = AutosaveManager.start(bibDatabaseContext, coarseChangeFilter); - autosaveManager.registerListener(new AutosaveUiManager(this, dialogService, preferences, entryTypesManager, stateManager)); + autosaveManager.registerListener( + new AutosaveUiManager(this, dialogService, preferences, entryTypesManager, stateManager)); } if (isDatabaseReadyForBackup(bibDatabaseContext) && preferences.getFilePreferences().shouldCreateBackup()) { - BackupManager.start(this, bibDatabaseContext, coarseChangeFilter, Injector.instantiateModelOrService(BibEntryTypesManager.class), preferences); + BackupManager.start(this, bibDatabaseContext, coarseChangeFilter, + Injector.instantiateModelOrService(BibEntryTypesManager.class), preferences); } } private boolean isDatabaseReadyForAutoSave(BibDatabaseContext context) { - return ((context.getLocation() == DatabaseLocation.SHARED) - || ((context.getLocation() == DatabaseLocation.LOCAL) - && preferences.getLibraryPreferences().shouldAutoSave())) - && context.getDatabasePath().isPresent(); + return ((context.getLocation() == DatabaseLocation.SHARED) || ((context.getLocation() == DatabaseLocation.LOCAL) + && preferences.getLibraryPreferences().shouldAutoSave())) && context.getDatabasePath().isPresent(); } private boolean isDatabaseReadyForBackup(BibDatabaseContext context) { @@ -375,7 +392,8 @@ private boolean isDatabaseReadyForBackup(BibDatabaseContext context) { /** * Sets the title of the tab modification-asterisk filename – path-fragment *

- * The modification-asterisk (*) is shown if the file was modified since last save (path-fragment is only shown if filename is not (globally) unique) + * The modification-asterisk (*) is shown if the file was modified since last save + * (path-fragment is only shown if filename is not (globally) unique) *

* Example: *jabref-authors.bib – testbib */ @@ -416,13 +434,16 @@ public void updateTabTitle(boolean isChanged) { } // Unique path fragment - Optional uniquePathPart = FileUtil.getUniquePathDirectory(stateManager.getAllDatabasePaths(), databasePath); + Optional uniquePathPart = FileUtil.getUniquePathDirectory(stateManager.getAllDatabasePaths(), + databasePath); uniquePathPart.ifPresent(part -> tabTitle.append(" \u2013 ").append(part)); - } else { + } + else { if (databaseLocation == DatabaseLocation.LOCAL) { tabTitle.append('*'); tabTitle.append(Localization.lang("untitled")); - } else { + } + else { addSharedDbInformation(tabTitle, bibDatabaseContext); addSharedDbInformation(toolTipText, bibDatabaseContext); } @@ -444,7 +465,8 @@ public void listen(BibDatabaseContextChangedEvent event) { } /** - * Returns a collection of suggestion providers, which are populated from the current library. + * Returns a collection of suggestion providers, which are populated from the current + * library. */ public SuggestionProviders getSuggestionProviders() { return suggestionProviders; @@ -462,21 +484,15 @@ public void registerUndoableChanges(List changes) { } private void createMainTable() { - mainTable = new MainTable(tableModel, - this, - tabContainer, - bibDatabaseContext, - preferences, - dialogService, - stateManager, - preferences.getKeyBindingRepository(), - clipBoardManager, - entryTypesManager, - taskExecutor, + mainTable = new MainTable(tableModel, this, tabContainer, bibDatabaseContext, preferences, dialogService, + stateManager, preferences.getKeyBindingRepository(), clipBoardManager, entryTypesManager, taskExecutor, importHandler); - // Add the listener that binds selection to state manager (TODO: should be replaced by proper JavaFX binding as soon as table is implemented in JavaFX) - // content binding between StateManager#getselectedEntries and mainTable#getSelectedEntries does not work here as it does not trigger the ActionHelper#needsEntriesSelected checker for the menubar + // Add the listener that binds selection to state manager (TODO: should be + // replaced by proper JavaFX binding as soon as table is implemented in JavaFX) + // content binding between StateManager#getselectedEntries and + // mainTable#getSelectedEntries does not work here as it does not trigger the + // ActionHelper#needsEntriesSelected checker for the menubar mainTable.addSelectionListener(event -> { List entries = event.getList().stream().map(BibEntryTableViewModel::getEntry).toList(); stateManager.setSelectedEntries(entries); @@ -489,11 +505,13 @@ public void setupMainPanel() { databaseNotificationPane = new DatabaseNotification(mainTable); setContent(databaseNotificationPane); - // Add changePane in case a file is present - otherwise just add the splitPane to the panel + // Add changePane in case a file is present - otherwise just add the splitPane to + // the panel Optional file = bibDatabaseContext.getDatabasePath(); if (file.isPresent()) { resetChangeMonitor(); - } else { + } + else { if (bibDatabaseContext.getDatabase().hasEntries()) { // if the database is not empty and no file is assigned, // the database came from an import and has to be treated somehow @@ -509,11 +527,10 @@ public void setupMainPanel() { private void setupAutoCompletion() { AutoCompletePreferences autoCompletePreferences = preferences.getAutoCompletePreferences(); if (autoCompletePreferences.shouldAutoComplete()) { - suggestionProviders = new SuggestionProviders( - getDatabase(), - Injector.instantiateModelOrService(JournalAbbreviationRepository.class), - autoCompletePreferences); - } else { + suggestionProviders = new SuggestionProviders(getDatabase(), + Injector.instantiateModelOrService(JournalAbbreviationRepository.class), autoCompletePreferences); + } + else { // Create empty suggestion providers if auto-completion is deactivated suggestionProviders = new SuggestionProviders(); } @@ -530,7 +547,8 @@ public void showAndEdit(BibEntry entry) { } /** - * This method selects the given entry, and scrolls it into view in the table. If an entryEditor is shown, it is given focus afterwards. + * This method selects the given entry, and scrolls it into view in the table. If an + * entryEditor is shown, it is given focus afterwards. */ public void clearAndSelect(final BibEntry bibEntry) { mainTable.clearAndSelect(bibEntry); @@ -555,7 +573,8 @@ public void selectNextEntry() { public synchronized void markChangedOrUnChanged() { if (undoManager.hasChanged()) { this.changedProperty.setValue(true); - } else if (changedProperty.getValue() && !nonUndoableChangeProperty.getValue()) { + } + else if (changedProperty.getValue() && !nonUndoableChangeProperty.getValue()) { this.changedProperty.setValue(false); } } @@ -565,10 +584,9 @@ public BibDatabase getDatabase() { } /** - * Initializes a pop-up dialog box to confirm whether the user wants to delete the selected entry - * Keep track of user preference: - * if the user prefers not to ask before deleting, delete the selected entry without displaying the dialog box - * + * Initializes a pop-up dialog box to confirm whether the user wants to delete the + * selected entry Keep track of user preference: if the user prefers not to ask before + * deleting, delete the selected entry without displaying the dialog box * @param numberOfEntries number of entries user is selecting * @return true if user confirm to delete entry */ @@ -580,19 +598,17 @@ private boolean showDeleteConfirmationDialog(int numberOfEntries) { String cancelButton = Localization.lang("Keep entry"); if (numberOfEntries > 1) { title = Localization.lang("Delete multiple entries"); - message = Localization.lang("Really delete the %0 selected entries?", Integer.toString(numberOfEntries)); + message = Localization.lang("Really delete the %0 selected entries?", + Integer.toString(numberOfEntries)); okButton = Localization.lang("Delete entries"); cancelButton = Localization.lang("Keep entries"); } - return dialogService.showConfirmationDialogWithOptOutAndWait( - title, - message, - okButton, - cancelButton, + return dialogService.showConfirmationDialogWithOptOutAndWait(title, message, okButton, cancelButton, Localization.lang("Do not ask again"), optOut -> preferences.getWorkspacePreferences().setConfirmDelete(!optOut)); - } else { + } + else { return true; } } @@ -607,9 +623,8 @@ public boolean requestClose() { } /** - * Ask if the user really wants to close the given database. - * Offers to save or discard the changes -- or return to the library - * + * Ask if the user really wants to close the given database. Offers to save or discard + * the changes -- or return to the library * @return true if the user choose to close the database */ private boolean confirmClose() { @@ -620,19 +635,18 @@ private boolean confirmClose() { return true; } - String filename = getBibDatabaseContext() - .getDatabasePath() - .map(Path::toAbsolutePath) - .map(Path::toString) - .orElse(Localization.lang("untitled")); + String filename = getBibDatabaseContext().getDatabasePath() + .map(Path::toAbsolutePath) + .map(Path::toString) + .orElse(Localization.lang("untitled")); ButtonType saveChanges = new ButtonType(Localization.lang("Save changes"), ButtonBar.ButtonData.YES); ButtonType discardChanges = new ButtonType(Localization.lang("Discard changes"), ButtonBar.ButtonData.NO); - ButtonType returnToLibrary = new ButtonType(Localization.lang("Return to library"), ButtonBar.ButtonData.CANCEL_CLOSE); + ButtonType returnToLibrary = new ButtonType(Localization.lang("Return to library"), + ButtonBar.ButtonData.CANCEL_CLOSE); Optional response = dialogService.showCustomButtonDialogAndWait(Alert.AlertType.CONFIRMATION, - Localization.lang("Save before closing"), - Localization.lang("Library '%0' has changed.", filename), + Localization.lang("Save before closing"), Localization.lang("Library '%0' has changed.", filename), saveChanges, discardChanges, returnToLibrary); if (response.isEmpty()) { @@ -647,15 +661,18 @@ private boolean confirmClose() { if (buttonType.equals(saveChanges)) { try { - SaveDatabaseAction saveAction = new SaveDatabaseAction(this, dialogService, preferences, Injector.instantiateModelOrService(BibEntryTypesManager.class), stateManager); + SaveDatabaseAction saveAction = new SaveDatabaseAction(this, dialogService, preferences, + Injector.instantiateModelOrService(BibEntryTypesManager.class), stateManager); if (saveAction.save()) { return true; } // The action was either canceled or unsuccessful. dialogService.notify(Localization.lang("Unable to save library")); - } catch (Throwable ex) { + } + catch (Throwable ex) { LOGGER.error("A problem occurred when trying to save the file", ex); - dialogService.showErrorDialogAndWait(Localization.lang("Save library"), Localization.lang("Could not save file."), ex); + dialogService.showErrorDialogAndWait(Localization.lang("Save library"), + Localization.lang("Could not save file."), ex); } // Save was cancelled or an error occurred. return false; @@ -689,27 +706,30 @@ private void onClosed(Event event) { } try { changeMonitor.ifPresent(DatabaseChangeMonitor::unregister); - } catch (RuntimeException e) { + } + catch (RuntimeException e) { LOGGER.error("Problem when closing change monitor", e); } try { if (indexManager != null) { indexManager.close(); } - } catch (RuntimeException e) { + } + catch (RuntimeException e) { LOGGER.error("Problem when closing index manager", e); } try { AutosaveManager.shutdown(bibDatabaseContext); - } catch (RuntimeException e) { + } + catch (RuntimeException e) { LOGGER.error("Problem when shutting down autosave manager", e); } try { - BackupManager.shutdown(bibDatabaseContext, - preferences.getFilePreferences().getBackupDirectory(), + BackupManager.shutdown(bibDatabaseContext, preferences.getFilePreferences().getBackupDirectory(), preferences.getFilePreferences().shouldCreateBackup()); - } catch (RuntimeException e) { + } + catch (RuntimeException e) { LOGGER.error("Problem when shutting down backup manager", e); } @@ -722,8 +742,8 @@ private void onClosed(Event event) { } /** - * Get an array containing the currently selected entries. The array is stable and not changed if the selection changes - * + * Get an array containing the currently selected entries. The array is stable and not + * changed if the selection changes * @return A list containing the selected entries. Is never null. */ public List getSelectedEntries() { @@ -774,14 +794,8 @@ public FileAnnotationCache getAnnotationCache() { public void resetChangeMonitor() { changeMonitor.ifPresent(DatabaseChangeMonitor::unregister); assert bibDatabaseContext.getDatabasePath().isEmpty() || fileUpdateMonitor != null; - changeMonitor = Optional.of(new DatabaseChangeMonitor(bibDatabaseContext, - fileUpdateMonitor, - taskExecutor, - dialogService, - preferences, - databaseNotificationPane, - undoManager, - stateManager)); + changeMonitor = Optional.of(new DatabaseChangeMonitor(bibDatabaseContext, fileUpdateMonitor, taskExecutor, + dialogService, preferences, databaseNotificationPane, undoManager, stateManager)); } public void insertEntry(final BibEntry bibEntry) { @@ -799,7 +813,8 @@ public void insertEntries(final List entries) { stateManager.setSelectedEntries(entries); if (preferences.getEntryEditorPreferences().shouldOpenOnNewEntry()) { showAndEdit(entries.getFirst()); - } else { + } + else { clearAndSelect(entries.getFirst()); } } @@ -808,7 +823,8 @@ public void copyEntry() { int entriesCopied = doCopyEntry(getSelectedEntries()); if (entriesCopied >= 0) { dialogService.notify(Localization.lang("Copied %0 entry(s)", entriesCopied)); - } else { + } + else { dialogService.notify(Localization.lang("Copy failed", entriesCopied)); } } @@ -822,11 +838,13 @@ private int doCopyEntry(List selectedEntries) { try { if (stringConstants.isEmpty()) { clipBoardManager.setContent(selectedEntries, entryTypesManager); - } else { + } + else { clipBoardManager.setContent(selectedEntries, entryTypesManager, stringConstants); } return selectedEntries.size(); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Error while copying selected entries to clipboard.", e); return -1; } @@ -849,12 +867,17 @@ public void pasteEntry() { private List handleNonBibTeXStringData(String data) { try { return this.importHandler.handleStringData(data); - } catch (FetcherException exception) { + } + catch (FetcherException exception) { if (exception instanceof FetcherClientException) { - dialogService.showInformationDialogAndWait(Localization.lang("Look up identifier"), Localization.lang("No data was found for the identifier")); - } else if (exception instanceof FetcherServerException) { - dialogService.showInformationDialogAndWait(Localization.lang("Look up identifier"), Localization.lang("Server not available")); - } else { + dialogService.showInformationDialogAndWait(Localization.lang("Look up identifier"), + Localization.lang("No data was found for the identifier")); + } + else if (exception instanceof FetcherServerException) { + dialogService.showInformationDialogAndWait(Localization.lang("Look up identifier"), + Localization.lang("Server not available")); + } + else { dialogService.showErrorDialogAndWait(exception); } return List.of(); @@ -871,7 +894,8 @@ public void cutEntry() { if (entriesCopied == entriesDeleted) { dialogService.notify(Localization.lang("Cut %0 entry(s)", entriesCopied)); - } else { + } + else { dialogService.notify(Localization.lang("Cut failed", entriesCopied)); undoManager.undo(); clipBoardManager.setContent(""); @@ -894,8 +918,9 @@ public void deleteEntry(BibEntry entry) { /** * Removes the selected entries and files linked to selected entries from the database - * - * @param mode If DELETE_ENTRY the user will get asked if he really wants to delete the entries, and it will be localized as "deleted". If true the action will be localized as "cut" + * @param mode If DELETE_ENTRY the user will get asked if he really wants to delete + * the entries, and it will be localized as "deleted". If true the action will be + * localized as "cut" */ private int doDeleteEntry(StandardActions mode, List entries) { if (entries.isEmpty()) { @@ -906,21 +931,24 @@ private int doDeleteEntry(StandardActions mode, List entries) { } // Delete selected entries - getUndoManager().addEdit(new UndoableRemoveEntries(bibDatabaseContext.getDatabase(), entries, mode == StandardActions.CUT)); + getUndoManager() + .addEdit(new UndoableRemoveEntries(bibDatabaseContext.getDatabase(), entries, mode == StandardActions.CUT)); bibDatabaseContext.getDatabase().removeEntries(entries); if (mode != StandardActions.CUT) { List linkedFileList = entries.stream() - .flatMap(entry -> entry.getFiles().stream()) - .distinct() - .toList(); + .flatMap(entry -> entry.getFiles().stream()) + .distinct() + .toList(); if (!linkedFileList.isEmpty()) { List viewModels = linkedFileList.stream() - .map(linkedFile -> LinkedFileViewModel.fromLinkedFile(linkedFile, null, bibDatabaseContext, null, null, preferences)) - .collect(Collectors.toList()); + .map(linkedFile -> LinkedFileViewModel.fromLinkedFile(linkedFile, null, bibDatabaseContext, null, + null, preferences)) + .collect(Collectors.toList()); - new DeleteFileAction(dialogService, preferences.getFilePreferences(), bibDatabaseContext, viewModels).execute(); + new DeleteFileAction(dialogService, preferences.getFilePreferences(), bibDatabaseContext, viewModels) + .execute(); } } @@ -951,72 +979,38 @@ public void resetChangedProperties() { } /** - * Creates a new library tab. Contents are loaded by the {@code dataLoadingTask}. Most of the other parameters are required by {@code resetChangeMonitor()}. - * + * Creates a new library tab. Contents are loaded by the {@code dataLoadingTask}. Most + * of the other parameters are required by {@code resetChangeMonitor()}. * @param dataLoadingTask The task to execute to load the data asynchronously. * @param file the path to the file (loaded by the dataLoadingTask) */ - public static LibraryTab createLibraryTab(BackgroundTask dataLoadingTask, - Path file, - DialogService dialogService, - AiService aiService, - GuiPreferences preferences, - StateManager stateManager, - LibraryTabContainer tabContainer, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager, - CountingUndoManager undoManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor) { + public static LibraryTab createLibraryTab(BackgroundTask dataLoadingTask, Path file, + DialogService dialogService, AiService aiService, GuiPreferences preferences, StateManager stateManager, + LibraryTabContainer tabContainer, FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager, CountingUndoManager undoManager, ClipBoardManager clipBoardManager, + TaskExecutor taskExecutor) { BibDatabaseContext context = new BibDatabaseContext(); context.setDatabasePath(file); - LibraryTab newTab = new LibraryTab( - context, - tabContainer, - dialogService, - aiService, - preferences, - stateManager, - fileUpdateMonitor, - entryTypesManager, - undoManager, - clipBoardManager, - taskExecutor, - true); + LibraryTab newTab = new LibraryTab(context, tabContainer, dialogService, aiService, preferences, stateManager, + fileUpdateMonitor, entryTypesManager, undoManager, clipBoardManager, taskExecutor, true); newTab.setDataLoadingTask(dataLoadingTask); dataLoadingTask.onRunning(newTab::onDatabaseLoadingStarted) - .onSuccess(newTab::onDatabaseLoadingSucceed) - .onFailure(newTab::onDatabaseLoadingFailed) - .executeWith(taskExecutor); + .onSuccess(newTab::onDatabaseLoadingSucceed) + .onFailure(newTab::onDatabaseLoadingFailed) + .executeWith(taskExecutor); return newTab; } public static LibraryTab createLibraryTab(@NonNull BibDatabaseContext databaseContext, - LibraryTabContainer tabContainer, - DialogService dialogService, - AiService aiService, - GuiPreferences preferences, - StateManager stateManager, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager, - UndoManager undoManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor) { - return new LibraryTab( - databaseContext, - tabContainer, - dialogService, - aiService, - preferences, - stateManager, - fileUpdateMonitor, - entryTypesManager, - (CountingUndoManager) undoManager, - clipBoardManager, - taskExecutor, + LibraryTabContainer tabContainer, DialogService dialogService, AiService aiService, + GuiPreferences preferences, StateManager stateManager, FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager, UndoManager undoManager, ClipBoardManager clipBoardManager, + TaskExecutor taskExecutor) { + return new LibraryTab(databaseContext, tabContainer, dialogService, aiService, preferences, stateManager, + fileUpdateMonitor, entryTypesManager, (CountingUndoManager) undoManager, clipBoardManager, taskExecutor, false); } @@ -1031,10 +1025,11 @@ public void listen(EntriesAddedEvent addedEntriesEvent) { // Automatically add new entries to the selected group (or set of groups) if (preferences.getGroupsPreferences().shouldAutoAssignGroup()) { - stateManager.getSelectedGroups(bibDatabaseContext).forEach( - selectedGroup -> selectedGroup.addEntriesToGroup(addedEntriesEvent.getBibEntries())); + stateManager.getSelectedGroups(bibDatabaseContext) + .forEach(selectedGroup -> selectedGroup.addEntriesToGroup(addedEntriesEvent.getBibEntries())); } } + } private class IndexUpdateListener { @@ -1053,9 +1048,11 @@ public void listen(EntriesRemovedEvent removedEntriesEvent) { public void listen(FieldChangedEvent fieldChangedEvent) { indexManager.updateEntry(fieldChangedEvent); } + } public static class DatabaseNotification extends NotificationPane { + public DatabaseNotification(Node content) { super(content); } @@ -1075,6 +1072,7 @@ public void notify(Node graphic, String text, List actions, Duration dur private void handle(ActionEvent e) { this.hide(); } + } public DatabaseNotification getNotificationPane() { @@ -1083,8 +1081,7 @@ public DatabaseNotification getNotificationPane() { @Override public String toString() { - return "LibraryTab{" + - "bibDatabaseContext=" + bibDatabaseContext + - '}'; + return "LibraryTab{" + "bibDatabaseContext=" + bibDatabaseContext + '}'; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/LibraryTabContainer.java b/jabgui/src/main/java/org/jabref/gui/LibraryTabContainer.java index b572d190e6d..903ae2f1c70 100644 --- a/jabgui/src/main/java/org/jabref/gui/LibraryTabContainer.java +++ b/jabgui/src/main/java/org/jabref/gui/LibraryTabContainer.java @@ -12,10 +12,10 @@ @NullMarked public interface LibraryTabContainer { + ObservableList getLibraryTabs(); - @Nullable - LibraryTab getCurrentLibraryTab(); + @Nullable LibraryTab getCurrentLibraryTab(); void showLibraryTab(LibraryTab libraryTab); @@ -25,7 +25,6 @@ public interface LibraryTabContainer { /** * Closes a designated libraryTab - * * @param tab to be closed. * @return true if closing the tab was successful */ @@ -37,4 +36,5 @@ public interface LibraryTabContainer { * Refreshes the ui after changes to the preferences */ void refresh(); + } diff --git a/jabgui/src/main/java/org/jabref/gui/StateManager.java b/jabgui/src/main/java/org/jabref/gui/StateManager.java index 23caafc512b..d91b171aa96 100644 --- a/jabgui/src/main/java/org/jabref/gui/StateManager.java +++ b/jabgui/src/main/java/org/jabref/gui/StateManager.java @@ -34,13 +34,13 @@ * This class manages the GUI-state of JabRef, including: * *

    - *
  • currently selected database
  • - *
  • currently selected group
  • - *
  • active search
  • - *
  • active number of search results
  • - *
  • focus owner
  • - *
  • dialog window sizes/positions
  • - *
  • opened AI chat window (controlled by {@link org.jabref.logic.ai.AiService})
  • + *
  • currently selected database
  • + *
  • currently selected group
  • + *
  • active search
  • + *
  • active number of search results
  • + *
  • focus owner
  • + *
  • dialog window sizes/positions
  • + *
  • opened AI chat window (controlled by {@link org.jabref.logic.ai.AiService})
  • *
*/ public interface StateManager extends SrvStateManager { @@ -108,4 +108,5 @@ public interface StateManager extends SrvStateManager { void setActiveWalkthrough(Walkthrough walkthrough); Optional getActiveWalkthrough(); + } diff --git a/jabgui/src/main/java/org/jabref/gui/UpdateTimestampListener.java b/jabgui/src/main/java/org/jabref/gui/UpdateTimestampListener.java index 4702d2407ce..2c6dd899271 100644 --- a/jabgui/src/main/java/org/jabref/gui/UpdateTimestampListener.java +++ b/jabgui/src/main/java/org/jabref/gui/UpdateTimestampListener.java @@ -11,6 +11,7 @@ * Updates the timestamp of changed entries if the feature is enabled */ class UpdateTimestampListener { + private final CliPreferences preferences; UpdateTimestampListener(CliPreferences preferences) { @@ -19,11 +20,14 @@ class UpdateTimestampListener { @Subscribe public void listen(EntryChangedEvent event) { - // The event source needs to be checked, since the timestamp is always updated on every change. The cleanup formatter is an exception to that behavior, - // since it just should move the contents from the timestamp field to modificationdate or creationdate. - if (preferences.getTimestampPreferences().shouldAddModificationDate() && event.getEntriesEventSource() != EntriesEventSource.CLEANUP_TIMESTAMP) { - event.getBibEntry().setField(StandardField.MODIFICATIONDATE, - preferences.getTimestampPreferences().now()); + // The event source needs to be checked, since the timestamp is always updated on + // every change. The cleanup formatter is an exception to that behavior, + // since it just should move the contents from the timestamp field to + // modificationdate or creationdate. + if (preferences.getTimestampPreferences().shouldAddModificationDate() + && event.getEntriesEventSource() != EntriesEventSource.CLEANUP_TIMESTAMP) { + event.getBibEntry().setField(StandardField.MODIFICATIONDATE, preferences.getTimestampPreferences().now()); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/WorkspacePreferences.java b/jabgui/src/main/java/org/jabref/gui/WorkspacePreferences.java index 60af19990b0..6331c4f709f 100644 --- a/jabgui/src/main/java/org/jabref/gui/WorkspacePreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/WorkspacePreferences.java @@ -15,31 +15,35 @@ import org.jabref.logic.l10n.Language; public class WorkspacePreferences { + private final ObjectProperty language; + private final BooleanProperty shouldOverrideDefaultFontSize; + private final IntegerProperty mainFontSize; + private final IntegerProperty defaultFontSize; + private final ObjectProperty theme; + private final BooleanProperty themeSyncOs; + private final BooleanProperty shouldOpenLastEdited; + private final BooleanProperty showAdvancedHints; + private final BooleanProperty warnAboutDuplicatesInInspection; + private final BooleanProperty confirmDelete; + private final BooleanProperty confirmHideTabBar; + private final ObservableList selectedSlrCatalogs; - public WorkspacePreferences(Language language, - boolean shouldOverrideDefaultFontSize, - int mainFontSize, - int defaultFontSize, - Theme theme, - boolean themeSyncOs, - boolean shouldOpenLastEdited, - boolean showAdvancedHints, - boolean warnAboutDuplicatesInInspection, - boolean confirmDelete, - boolean confirmHideTabBar, - List selectedSlrCatalogs) { + public WorkspacePreferences(Language language, boolean shouldOverrideDefaultFontSize, int mainFontSize, + int defaultFontSize, Theme theme, boolean themeSyncOs, boolean shouldOpenLastEdited, + boolean showAdvancedHints, boolean warnAboutDuplicatesInInspection, boolean confirmDelete, + boolean confirmHideTabBar, List selectedSlrCatalogs) { this.language = new SimpleObjectProperty<>(language); this.shouldOverrideDefaultFontSize = new SimpleBooleanProperty(shouldOverrideDefaultFontSize); this.mainFontSize = new SimpleIntegerProperty(mainFontSize); @@ -185,4 +189,5 @@ public ObservableList getSelectedSlrCatalogs() { public void setSelectedSlrCatalogs(List catalogs) { selectedSlrCatalogs.setAll(catalogs); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/actions/Action.java b/jabgui/src/main/java/org/jabref/gui/actions/Action.java index e6ccfe487b2..1b2fad0b5e1 100644 --- a/jabgui/src/main/java/org/jabref/gui/actions/Action.java +++ b/jabgui/src/main/java/org/jabref/gui/actions/Action.java @@ -6,6 +6,7 @@ import org.jabref.gui.keyboard.KeyBinding; public interface Action { + default Optional getIcon() { return Optional.empty(); } @@ -19,4 +20,5 @@ default Optional getKeyBinding() { default String getDescription() { return ""; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/actions/ActionFactory.java b/jabgui/src/main/java/org/jabref/gui/actions/ActionFactory.java index 66e3fb9a0b1..d00e8d0dc1e 100644 --- a/jabgui/src/main/java/org/jabref/gui/actions/ActionFactory.java +++ b/jabgui/src/main/java/org/jabref/gui/actions/ActionFactory.java @@ -38,7 +38,8 @@ public ActionFactory() { } /** - * For some reason the graphic is not set correctly by the {@link ActionUtils} class, so we have to fix this by hand + * For some reason the graphic is not set correctly by the {@link ActionUtils} class, + * so we have to fix this by hand */ private static void setGraphic(MenuItem node, Action action) { node.graphicProperty().unbind(); @@ -46,29 +47,31 @@ private static void setGraphic(MenuItem node, Action action) { } /* - * Returns MenuItemContainer node associated with this menu item - * which can contain: - * 1. label node of type Label for displaying menu item text, - * 2. right node of type Label for displaying accelerator text, - * or an arrow if it's a Menu, - * 3. graphic node for displaying menu item icon, and - * 4. left node for displaying either radio button or check box. + * Returns MenuItemContainer node associated with this menu item which can contain: 1. + * label node of type Label for displaying menu item text, 2. right node of type Label + * for displaying accelerator text, or an arrow if it's a Menu, 3. graphic node for + * displaying menu item icon, and 4. left node for displaying either radio button or + * check box. * - * This is basically rewritten impl_styleableGetNode() which - * should not be used since it's marked as deprecated. + * This is basically rewritten impl_styleableGetNode() which should not be used since + * it's marked as deprecated. */ private static Label getAssociatedNode(MenuItem menuItem) { - ContextMenuContent.MenuItemContainer container = (ContextMenuContent.MenuItemContainer) menuItem.getStyleableNode(); + ContextMenuContent.MenuItemContainer container = (ContextMenuContent.MenuItemContainer) menuItem + .getStyleableNode(); if (container == null) { return null; - } else { + } + else { // We have to use reflection to get the associated label try { Method getLabel = ContextMenuContent.MenuItemContainer.class.getDeclaredMethod("getLabel"); getLabel.setAccessible(true); return (Label) getLabel.invoke(container); - } catch (InaccessibleObjectException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + } + catch (InaccessibleObjectException | IllegalAccessException | InvocationTargetException + | NoSuchMethodException e) { LOGGER.warn("Could not get label of menu item", e); } } @@ -84,20 +87,18 @@ public MenuItem configureMenuItem(Action action, Command command, MenuItem menuI private static void enableTooltips(Command command, MenuItem menuItem) { if (command instanceof SimpleCommand simpleCommand) { - EasyBind.subscribe( - simpleCommand.statusMessageProperty(), - message -> { - Label label = getAssociatedNode(menuItem); - if (label != null) { - label.setMouseTransparent(false); - if (StringUtil.isBlank(message)) { - label.setTooltip(null); - } else { - label.setTooltip(new Tooltip(message)); - } - } + EasyBind.subscribe(simpleCommand.statusMessageProperty(), message -> { + Label label = getAssociatedNode(menuItem); + if (label != null) { + label.setMouseTransparent(false); + if (StringUtil.isBlank(message)) { + label.setTooltip(null); + } + else { + label.setTooltip(new Tooltip(message)); } - ); + } + }); } } @@ -117,7 +118,8 @@ public MenuItem createCustomMenuItem(Action action, Command command, String text } public CheckMenuItem createCheckMenuItem(Action action, Command command, boolean selected) { - CheckMenuItem checkMenuItem = ActionUtils.createCheckMenuItem(new JabRefAction(action, command, keyBindingRepository)); + CheckMenuItem checkMenuItem = ActionUtils + .createCheckMenuItem(new JabRefAction(action, command, keyBindingRepository)); checkMenuItem.setSelected(selected); setGraphic(checkMenuItem, action); @@ -125,7 +127,8 @@ public CheckMenuItem createCheckMenuItem(Action action, Command command, boolean } public CheckMenuItem createCheckMenuItem(Action action, Command command, BooleanExpression selectedBinding) { - CheckMenuItem checkMenuItem = ActionUtils.createCheckMenuItem(new JabRefAction(action, command, keyBindingRepository)); + CheckMenuItem checkMenuItem = ActionUtils + .createCheckMenuItem(new JabRefAction(action, command, keyBindingRepository)); EasyBind.subscribe(selectedBinding, checkMenuItem::setSelected); setGraphic(checkMenuItem, action); @@ -147,7 +150,8 @@ public Menu createSubMenu(Action action, MenuItem... children) { } public Button createIconButton(Action action, Command command) { - Button button = ActionUtils.createButton(new JabRefAction(action, command, keyBindingRepository), ActionUtils.ActionTextBehavior.HIDE); + Button button = ActionUtils.createButton(new JabRefAction(action, command, keyBindingRepository), + ActionUtils.ActionTextBehavior.HIDE); button.getStyleClass().setAll("icon-button"); @@ -163,9 +167,7 @@ public Button createIconButton(Action action, Command command) { public ButtonBase configureIconButton(Action action, Command command, ButtonBase button) { ActionUtils.unconfigureButton(button); - ActionUtils.configureButton( - new JabRefAction(action, command, keyBindingRepository), - button, + ActionUtils.configureButton(new JabRefAction(action, command, keyBindingRepository), button, ActionUtils.ActionTextBehavior.HIDE); button.getStyleClass().add("icon-button"); @@ -177,4 +179,5 @@ public ButtonBase configureIconButton(Action action, Command command, ButtonBase return button; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/actions/ActionHelper.java b/jabgui/src/main/java/org/jabref/gui/actions/ActionHelper.java index 68a336b8bc4..cd2e4922e94 100644 --- a/jabgui/src/main/java/org/jabref/gui/actions/ActionHelper.java +++ b/jabgui/src/main/java/org/jabref/gui/actions/ActionHelper.java @@ -29,12 +29,16 @@ public static BooleanExpression needsDatabase(StateManager stateManager) { } public static BooleanExpression needsSavedLocalDatabase(StateManager stateManager) { - EasyBinding binding = EasyBind.map(stateManager.activeDatabaseProperty(), context -> context.filter(c -> c.getLocation() == DatabaseLocation.LOCAL && c.getDatabasePath().isPresent()).isPresent()); + EasyBinding binding = EasyBind.map(stateManager.activeDatabaseProperty(), + context -> context + .filter(c -> c.getLocation() == DatabaseLocation.LOCAL && c.getDatabasePath().isPresent()) + .isPresent()); return BooleanExpression.booleanExpression(binding); } public static BooleanExpression needsSharedDatabase(StateManager stateManager) { - EasyBinding binding = EasyBind.map(stateManager.activeDatabaseProperty(), context -> context.filter(c -> c.getLocation() == DatabaseLocation.SHARED).isPresent()); + EasyBinding binding = EasyBind.map(stateManager.activeDatabaseProperty(), + context -> context.filter(c -> c.getLocation() == DatabaseLocation.SHARED).isPresent()); return BooleanExpression.booleanExpression(binding); } @@ -43,7 +47,8 @@ public static BooleanExpression needsMultipleDatabases(TabPane tabbedPane) { } public static BooleanExpression needsStudyDatabase(StateManager stateManager) { - EasyBinding binding = EasyBind.map(stateManager.activeDatabaseProperty(), context -> context.filter(BibDatabaseContext::isStudy).isPresent()); + EasyBinding binding = EasyBind.map(stateManager.activeDatabaseProperty(), + context -> context.filter(BibDatabaseContext::isStudy).isPresent()); return BooleanExpression.booleanExpression(binding); } @@ -63,14 +68,14 @@ public static BooleanExpression isFieldSetForSelectedEntry(Field field, StateMan public static BooleanExpression isAnyFieldSetForSelectedEntry(List fields, StateManager stateManager) { ObservableList selectedEntries = stateManager.getSelectedEntries(); Binding fieldsAreSet = EasyBind.valueAt(selectedEntries, 0) - .mapObservable(entry -> Bindings.createBooleanBinding( - () -> entry.getFields().stream().anyMatch(fields::contains), - entry.getFieldsObservable())) - .orElseOpt(false); + .mapObservable(entry -> Bindings.createBooleanBinding( + () -> entry.getFields().stream().anyMatch(fields::contains), entry.getFieldsObservable())) + .orElseOpt(false); return BooleanExpression.booleanExpression(fieldsAreSet); } - public static BooleanExpression isFilePresentForSelectedEntry(StateManager stateManager, CliPreferences preferences) { + public static BooleanExpression isFilePresentForSelectedEntry(StateManager stateManager, + CliPreferences preferences) { ObservableList selectedEntries = stateManager.getSelectedEntries(); Binding fileIsPresent = EasyBind.valueAt(selectedEntries, 0).mapOpt(entry -> { List files = entry.getFiles(); @@ -80,12 +85,11 @@ public static BooleanExpression isFilePresentForSelectedEntry(StateManager state return true; } - Optional filename = FileUtil.find( - stateManager.getActiveDatabase().get(), - files.getFirst().getLink(), - preferences.getFilePreferences()); + Optional filename = FileUtil.find(stateManager.getActiveDatabase().get(), + files.getFirst().getLink(), preferences.getFilePreferences()); return filename.isPresent(); - } else { + } + else { return false; } }).orElseOpt(false); @@ -94,10 +98,9 @@ public static BooleanExpression isFilePresentForSelectedEntry(StateManager state } /** - * Check if at least one of the selected entries has linked files - *
- * Used in {@link org.jabref.gui.maintable.OpenSelectedEntriesFilesAction} when multiple entries selected - * + * Check if at least one of the selected entries has linked files
+ * Used in {@link org.jabref.gui.maintable.OpenSelectedEntriesFilesAction} when + * multiple entries selected * @param stateManager manager for the state of the GUI * @return a boolean binding */ @@ -105,4 +108,5 @@ public static BooleanExpression hasLinkedFileForSelectedEntries(StateManager sta return BooleanExpression.booleanExpression(EasyBind.reduce(stateManager.getSelectedEntries(), entries -> entries.anyMatch(entry -> !entry.getFiles().isEmpty()))); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/actions/JabRefAction.java b/jabgui/src/main/java/org/jabref/gui/actions/JabRefAction.java index 50d6be4d3b2..d0e8c308052 100644 --- a/jabgui/src/main/java/org/jabref/gui/actions/JabRefAction.java +++ b/jabgui/src/main/java/org/jabref/gui/actions/JabRefAction.java @@ -7,7 +7,8 @@ import de.saxsys.mvvmfx.utils.commands.Command; /** - * Wrapper around one of our actions from {@link Action} to convert them to controlsfx {@link org.controlsfx.control.action.Action}. + * Wrapper around one of our actions from {@link Action} to convert them to controlsfx + * {@link org.controlsfx.control.action.Action}. */ class JabRefAction extends org.controlsfx.control.action.Action { @@ -30,4 +31,5 @@ public JabRefAction(Action action, Command command, KeyBindingRepository keyBind longTextProperty().bind(Bindings.concat(action.getDescription(), simpleCommand.statusMessageProperty())); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/actions/SimpleCommand.java b/jabgui/src/main/java/org/jabref/gui/actions/SimpleCommand.java index 89ef7ad100a..97ceafa5ce3 100644 --- a/jabgui/src/main/java/org/jabref/gui/actions/SimpleCommand.java +++ b/jabgui/src/main/java/org/jabref/gui/actions/SimpleCommand.java @@ -32,4 +32,5 @@ public ReadOnlyDoubleProperty progressProperty() { public void setExecutable(boolean executable) { this.executable.bind(BindingsHelper.constantOf(executable)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java b/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java index 5cc73d43666..cb64b08e6c7 100644 --- a/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -10,11 +10,11 @@ public enum StandardActions implements Action { - COPY_TO(Localization.lang("Copy to")), - COPY_MORE(Localization.lang("Copy") + "..."), + COPY_TO(Localization.lang("Copy to")), COPY_MORE(Localization.lang("Copy") + "..."), COPY_TITLE(Localization.lang("Copy title"), KeyBinding.COPY_TITLE), COPY_KEY(Localization.lang("Copy citation key"), KeyBinding.COPY_CITATION_KEY), - COPY_CITE_KEY(Localization.lang("Copy citation key with configured cite command"), KeyBinding.COPY_CITE_CITATION_KEY), + COPY_CITE_KEY(Localization.lang("Copy citation key with configured cite command"), + KeyBinding.COPY_CITE_CITATION_KEY), COPY_KEY_AND_TITLE(Localization.lang("Copy citation key and title"), KeyBinding.COPY_CITATION_KEY_AND_TITLE), COPY_KEY_AND_LINK(Localization.lang("Copy citation key and link"), KeyBinding.COPY_CITATION_KEY_AND_LINK), COPY_CITATION_HTML(Localization.lang("Copy citation (html)"), KeyBinding.COPY_PREVIEW), @@ -22,24 +22,28 @@ public enum StandardActions implements Action { COPY_CITATION_MARKDOWN(Localization.lang("Copy citation (markdown)")), COPY_CITATION_PREVIEW(Localization.lang("Copy preview"), KeyBinding.COPY_PREVIEW), EXPORT_TO_CLIPBOARD(Localization.lang("Export to clipboard"), IconTheme.JabRefIcons.EXPORT_TO_CLIPBOARD), - EXPORT_SELECTED_TO_CLIPBOARD(Localization.lang("Export selected entries to clipboard"), IconTheme.JabRefIcons.EXPORT_TO_CLIPBOARD), + EXPORT_SELECTED_TO_CLIPBOARD(Localization.lang("Export selected entries to clipboard"), + IconTheme.JabRefIcons.EXPORT_TO_CLIPBOARD), COPY(Localization.lang("Copy"), IconTheme.JabRefIcons.COPY, KeyBinding.COPY), PASTE(Localization.lang("Paste"), IconTheme.JabRefIcons.PASTE, KeyBinding.PASTE), CUT(Localization.lang("Cut"), IconTheme.JabRefIcons.CUT, KeyBinding.CUT), DELETE(Localization.lang("Delete"), IconTheme.JabRefIcons.DELETE_ENTRY), DELETE_ENTRY(Localization.lang("Delete entry"), IconTheme.JabRefIcons.DELETE_ENTRY, KeyBinding.DELETE_ENTRY), - SEND(Localization.lang("Send"), IconTheme.JabRefIcons.EMAIL), - SEND_AS_EMAIL(Localization.lang("As Email")), + SEND(Localization.lang("Send"), IconTheme.JabRefIcons.EMAIL), SEND_AS_EMAIL(Localization.lang("As Email")), SEND_TO_KINDLE(Localization.lang("To Kindle")), REBUILD_FULLTEXT_SEARCH_INDEX(Localization.lang("Rebuild fulltext search index"), IconTheme.JabRefIcons.FILE), REDOWNLOAD_MISSING_FILES(Localization.lang("Redownload missing files"), IconTheme.JabRefIcons.DOWNLOAD), OPEN_EXTERNAL_FILE(Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE), - EXTRACT_FILE_REFERENCES_ONLINE(Localization.lang("Extract references from file (online)"), IconTheme.JabRefIcons.FILE_STAR), - EXTRACT_FILE_REFERENCES_OFFLINE(Localization.lang("Extract references from file (offline)"), IconTheme.JabRefIcons.FILE_STAR), + EXTRACT_FILE_REFERENCES_ONLINE(Localization.lang("Extract references from file (online)"), + IconTheme.JabRefIcons.FILE_STAR), + EXTRACT_FILE_REFERENCES_OFFLINE(Localization.lang("Extract references from file (offline)"), + IconTheme.JabRefIcons.FILE_STAR), OPEN_URL(Localization.lang("Open URL or DOI"), IconTheme.JabRefIcons.WWW, KeyBinding.OPEN_URL_OR_DOI), SEARCH_SHORTSCIENCE(Localization.lang("Search ShortScience")), - MERGE_WITH_FETCHED_ENTRY(Localization.lang("Get bibliographic data from %0", "DOI/ISBN/..."), KeyBinding.MERGE_WITH_FETCHED_ENTRY), - BATCH_MERGE_WITH_FETCHED_ENTRY(Localization.lang("Get bibliographic data from %0 (fully automated)", "DOI/ISBN/...")), + MERGE_WITH_FETCHED_ENTRY(Localization.lang("Get bibliographic data from %0", "DOI/ISBN/..."), + KeyBinding.MERGE_WITH_FETCHED_ENTRY), + BATCH_MERGE_WITH_FETCHED_ENTRY( + Localization.lang("Get bibliographic data from %0 (fully automated)", "DOI/ISBN/...")), ATTACH_FILE(Localization.lang("Attach file"), IconTheme.JabRefIcons.ATTACH_FILE), ATTACH_FILE_FROM_URL(Localization.lang("Attach file from URL"), IconTheme.JabRefIcons.DOWNLOAD_FILE), PRIORITY(Localization.lang("Priority"), IconTheme.JabRefIcons.PRIORITY), @@ -49,8 +53,7 @@ public enum StandardActions implements Action { PRIORITY_LOW(Localization.lang("Set priority to low"), IconTheme.JabRefIcons.PRIORITY_LOW), QUALITY(Localization.lang("Quality"), IconTheme.JabRefIcons.QUALITY), QUALITY_ASSURED(Localization.lang("Toggle quality assured"), IconTheme.JabRefIcons.QUALITY_ASSURED), - RANKING(Localization.lang("Rank"), IconTheme.JabRefIcons.RANKING), - CLEAR_RANK(Localization.lang("Clear rank")), + RANKING(Localization.lang("Rank"), IconTheme.JabRefIcons.RANKING), CLEAR_RANK(Localization.lang("Clear rank")), RANK_1(Localization.lang("Set rank to one"), IconTheme.JabRefIcons.RANK1), RANK_2(Localization.lang("Set rank to two"), IconTheme.JabRefIcons.RANK2), RANK_3(Localization.lang("Set rank to three"), IconTheme.JabRefIcons.RANK3), @@ -61,7 +64,8 @@ public enum StandardActions implements Action { READ_STATUS(Localization.lang("Read status"), IconTheme.JabRefIcons.READ_STATUS), CLEAR_READ_STATUS(Localization.lang("Clear read status"), KeyBinding.CLEAR_READ_STATUS), READ(Localization.lang("Set read status to read"), IconTheme.JabRefIcons.READ_STATUS_READ, KeyBinding.READ), - SKIMMED(Localization.lang("Set read status to skimmed"), IconTheme.JabRefIcons.READ_STATUS_SKIMMED, KeyBinding.SKIMMED), + SKIMMED(Localization.lang("Set read status to skimmed"), IconTheme.JabRefIcons.READ_STATUS_SKIMMED, + KeyBinding.SKIMMED), RELEVANCE(Localization.lang("Relevance"), IconTheme.JabRefIcons.RELEVANCE), RELEVANT(Localization.lang("Toggle relevance"), IconTheme.JabRefIcons.RELEVANCE), NEW_LIBRARY(Localization.lang("New empty library"), IconTheme.JabRefIcons.NEW), @@ -71,18 +75,25 @@ public enum StandardActions implements Action { SAVE_LIBRARY(Localization.lang("Save library"), IconTheme.JabRefIcons.SAVE, KeyBinding.SAVE_DATABASE), SAVE_LIBRARY_AS(Localization.lang("Save library as..."), KeyBinding.SAVE_DATABASE_AS), SAVE_SELECTED_AS_PLAIN_BIBTEX(Localization.lang("Save selected as plain BibTeX...")), - SAVE_ALL(Localization.lang("Save all"), Localization.lang("Save all open libraries"), IconTheme.JabRefIcons.SAVE_ALL, KeyBinding.SAVE_ALL), + SAVE_ALL(Localization.lang("Save all"), Localization.lang("Save all open libraries"), + IconTheme.JabRefIcons.SAVE_ALL, KeyBinding.SAVE_ALL), IMPORT_INTO_NEW_LIBRARY(Localization.lang("Import into new library"), KeyBinding.IMPORT_INTO_NEW_DATABASE), - IMPORT_INTO_CURRENT_LIBRARY(Localization.lang("Import into current library"), KeyBinding.IMPORT_INTO_CURRENT_DATABASE), + IMPORT_INTO_CURRENT_LIBRARY(Localization.lang("Import into current library"), + KeyBinding.IMPORT_INTO_CURRENT_DATABASE), EXPORT_ALL(Localization.lang("Export all entries")), REMOTE_DB(Localization.lang("Shared database"), IconTheme.JabRefIcons.REMOTE_DATABASE), EXPORT_SELECTED(Localization.lang("Export selected entries"), KeyBinding.EXPORT_SELECTED), CONNECT_TO_SHARED_DB(Localization.lang("Connect to shared database"), IconTheme.JabRefIcons.CONNECT_DB), - PULL_CHANGES_FROM_SHARED_DB(Localization.lang("Pull changes from shared database"), KeyBinding.PULL_CHANGES_FROM_SHARED_DATABASE), - CLOSE_LIBRARY(Localization.lang("Close library"), Localization.lang("Close the current library"), IconTheme.JabRefIcons.CLOSE, KeyBinding.CLOSE_DATABASE), - CLOSE_OTHER_LIBRARIES(Localization.lang("Close others"), Localization.lang("Close other libraries"), IconTheme.JabRefIcons.CLOSE), - CLOSE_ALL_LIBRARIES(Localization.lang("Close all"), Localization.lang("Close all libraries"), IconTheme.JabRefIcons.CLOSE), - QUIT(Localization.lang("Quit"), Localization.lang("Quit JabRef"), IconTheme.JabRefIcons.CLOSE_JABREF, KeyBinding.QUIT_JABREF), + PULL_CHANGES_FROM_SHARED_DB(Localization.lang("Pull changes from shared database"), + KeyBinding.PULL_CHANGES_FROM_SHARED_DATABASE), + CLOSE_LIBRARY(Localization.lang("Close library"), Localization.lang("Close the current library"), + IconTheme.JabRefIcons.CLOSE, KeyBinding.CLOSE_DATABASE), + CLOSE_OTHER_LIBRARIES(Localization.lang("Close others"), Localization.lang("Close other libraries"), + IconTheme.JabRefIcons.CLOSE), + CLOSE_ALL_LIBRARIES(Localization.lang("Close all"), Localization.lang("Close all libraries"), + IconTheme.JabRefIcons.CLOSE), + QUIT(Localization.lang("Quit"), Localization.lang("Quit JabRef"), IconTheme.JabRefIcons.CLOSE_JABREF, + KeyBinding.QUIT_JABREF), UNDO(Localization.lang("Undo"), IconTheme.JabRefIcons.UNDO, KeyBinding.UNDO), REDO(Localization.lang("Redo"), IconTheme.JabRefIcons.REDO, KeyBinding.REDO), REPLACE_ALL(Localization.lang("Find and replace"), KeyBinding.REPLACE_STRING), @@ -91,32 +102,52 @@ public enum StandardActions implements Action { AUTOMATIC_FIELD_EDITOR(Localization.lang("Automatic field editor")), TOGGLE_GROUPS(Localization.lang("Groups"), IconTheme.JabRefIcons.TOGGLE_GROUPS, KeyBinding.TOGGLE_GROUPS_INTERFACE), - TOGGLE_OO(Localization.lang("OpenOffice/LibreOffice"), IconTheme.JabRefIcons.FILE_OPENOFFICE, KeyBinding.OPEN_OPEN_OFFICE_LIBRE_OFFICE_CONNECTION), - TOGGLE_WEB_SEARCH(Localization.lang("Web search"), Localization.lang("Toggle web search interface"), IconTheme.JabRefIcons.WWW, KeyBinding.WEB_SEARCH), + TOGGLE_OO(Localization.lang("OpenOffice/LibreOffice"), IconTheme.JabRefIcons.FILE_OPENOFFICE, + KeyBinding.OPEN_OPEN_OFFICE_LIBRE_OFFICE_CONNECTION), + TOGGLE_WEB_SEARCH(Localization.lang("Web search"), Localization.lang("Toggle web search interface"), + IconTheme.JabRefIcons.WWW, KeyBinding.WEB_SEARCH), PARSE_LATEX(Localization.lang("Search for citations in LaTeX files..."), IconTheme.JabRefIcons.LATEX_CITATIONS), - NEW_SUB_LIBRARY_FROM_AUX(Localization.lang("New sublibrary based on AUX file") + "...", Localization.lang("New BibTeX sublibrary") + Localization.lang("This feature generates a new library based on which entries are needed in an existing LaTeX document."), IconTheme.JabRefIcons.NEW), - NEW_LIBRARY_FROM_PDF_ONLINE(Localization.lang("New library based on references in PDF file... (online)"), Localization.lang("This feature generates a new library based on the list of references in a PDF file. Thereby, it uses Grobid's functionality."), IconTheme.JabRefIcons.NEW), - NEW_LIBRARY_FROM_PDF_OFFLINE(Localization.lang("New library based on references in PDF file... (offline)"), Localization.lang("This feature generates a new library based on the list of references in a PDF file. Thereby, it uses JabRef's built-in functionality."), IconTheme.JabRefIcons.NEW), - WRITE_METADATA_TO_PDF(Localization.lang("Write metadata to PDF files"), Localization.lang("Will write metadata to the PDFs linked from selected entries."), KeyBinding.WRITE_METADATA_TO_PDF), + NEW_SUB_LIBRARY_FROM_AUX(Localization.lang("New sublibrary based on AUX file") + "...", + Localization.lang("New BibTeX sublibrary") + Localization.lang( + "This feature generates a new library based on which entries are needed in an existing LaTeX document."), + IconTheme.JabRefIcons.NEW), + NEW_LIBRARY_FROM_PDF_ONLINE(Localization.lang("New library based on references in PDF file... (online)"), + Localization.lang( + "This feature generates a new library based on the list of references in a PDF file. Thereby, it uses Grobid's functionality."), + IconTheme.JabRefIcons.NEW), + NEW_LIBRARY_FROM_PDF_OFFLINE(Localization.lang("New library based on references in PDF file... (offline)"), + Localization.lang( + "This feature generates a new library based on the list of references in a PDF file. Thereby, it uses JabRef's built-in functionality."), + IconTheme.JabRefIcons.NEW), + WRITE_METADATA_TO_PDF(Localization.lang("Write metadata to PDF files"), + Localization.lang("Will write metadata to the PDFs linked from selected entries."), + KeyBinding.WRITE_METADATA_TO_PDF), START_NEW_STUDY(Localization.lang("Start new systematic literature review")), UPDATE_SEARCH_RESULTS_OF_STUDY(Localization.lang("Update study search results")), EDIT_EXISTING_STUDY(Localization.lang("Manage study definition")), OPEN_DATABASE_FOLDER(Localization.lang("Reveal in file explorer")), - OPEN_FOLDER(Localization.lang("Open folder"), Localization.lang("Open folder"), IconTheme.JabRefIcons.FOLDER, KeyBinding.OPEN_FOLDER), - OPEN_FILE(Localization.lang("Open file"), Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE), - OPEN_CONSOLE(Localization.lang("Open terminal here"), Localization.lang("Open terminal here"), IconTheme.JabRefIcons.CONSOLE, KeyBinding.OPEN_CONSOLE), - COPY_LINKED_FILES(Localization.lang("Copy linked files to folder...")), - COPY_DOI(Localization.lang("Copy DOI")), - COPY_DOI_URL(Localization.lang("Copy DOI url")), - ABBREVIATE(Localization.lang("Abbreviate journal names")), - ABBREVIATE_DEFAULT(Localization.lang("default"), Localization.lang("Abbreviate journal names of the selected entries (DEFAULT abbreviation)"), KeyBinding.ABBREVIATE), - ABBREVIATE_DOTLESS(Localization.lang("dotless"), Localization.lang("Abbreviate journal names of the selected entries (DOTLESS abbreviation)")), - ABBREVIATE_SHORTEST_UNIQUE(Localization.lang("shortest unique"), Localization.lang("Abbreviate journal names of the selected entries (SHORTEST UNIQUE abbreviation)")), - ABBREVIATE_LTWA(Localization.lang("LTWA"), Localization.lang("Abbreviate journal names of the selected entries (LTWA)")), - UNABBREVIATE(Localization.lang("Unabbreviate journal names"), Localization.lang("Unabbreviate journal names of the selected entries"), KeyBinding.UNABBREVIATE), + OPEN_FOLDER(Localization.lang("Open folder"), Localization.lang("Open folder"), IconTheme.JabRefIcons.FOLDER, + KeyBinding.OPEN_FOLDER), + OPEN_FILE(Localization.lang("Open file"), Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, + KeyBinding.OPEN_FILE), + OPEN_CONSOLE(Localization.lang("Open terminal here"), Localization.lang("Open terminal here"), + IconTheme.JabRefIcons.CONSOLE, KeyBinding.OPEN_CONSOLE), + COPY_LINKED_FILES(Localization.lang("Copy linked files to folder...")), COPY_DOI(Localization.lang("Copy DOI")), + COPY_DOI_URL(Localization.lang("Copy DOI url")), ABBREVIATE(Localization.lang("Abbreviate journal names")), + ABBREVIATE_DEFAULT(Localization.lang("default"), + Localization.lang("Abbreviate journal names of the selected entries (DEFAULT abbreviation)"), + KeyBinding.ABBREVIATE), + ABBREVIATE_DOTLESS(Localization.lang("dotless"), + Localization.lang("Abbreviate journal names of the selected entries (DOTLESS abbreviation)")), + ABBREVIATE_SHORTEST_UNIQUE(Localization.lang("shortest unique"), + Localization.lang("Abbreviate journal names of the selected entries (SHORTEST UNIQUE abbreviation)")), + ABBREVIATE_LTWA(Localization.lang("LTWA"), + Localization.lang("Abbreviate journal names of the selected entries (LTWA)")), + UNABBREVIATE(Localization.lang("Unabbreviate journal names"), + Localization.lang("Unabbreviate journal names of the selected entries"), KeyBinding.UNABBREVIATE), MANAGE_CUSTOM_EXPORTS(Localization.lang("Manage custom exports")), MANAGE_CUSTOM_IMPORTS(Localization.lang("Manage custom imports")), @@ -127,34 +158,42 @@ public enum StandardActions implements Action { SHOW_PREFS(Localization.lang("Preferences"), IconTheme.JabRefIcons.PREFERENCES, KeyBinding.SHOW_PREFS), MANAGE_JOURNALS(Localization.lang("Manage journal abbreviations")), CUSTOMIZE_KEYBINDING(Localization.lang("Customize keyboard shortcuts"), IconTheme.JabRefIcons.KEY_BINDINGS), - EDIT_ENTRY(Localization.lang("Open entry editor"), IconTheme.JabRefIcons.EDIT_ENTRY, KeyBinding.OPEN_CLOSE_ENTRY_EDITOR), + EDIT_ENTRY(Localization.lang("Open entry editor"), IconTheme.JabRefIcons.EDIT_ENTRY, + KeyBinding.OPEN_CLOSE_ENTRY_EDITOR), SHOW_PDF_VIEWER(Localization.lang("Open document viewer"), IconTheme.JabRefIcons.PDF_FILE), NEXT_PREVIEW_STYLE(Localization.lang("Next preview style"), KeyBinding.NEXT_PREVIEW_LAYOUT), PREVIOUS_PREVIEW_STYLE(Localization.lang("Previous preview style"), KeyBinding.PREVIOUS_PREVIEW_LAYOUT), - SELECT_ALL(Localization.lang("Select all"), KeyBinding.SELECT_ALL), - UNSELECT_ALL(Localization.lang("Unselect all")), + SELECT_ALL(Localization.lang("Select all"), KeyBinding.SELECT_ALL), UNSELECT_ALL(Localization.lang("Unselect all")), - EXPAND_ALL(Localization.lang("Expand all")), - COLLAPSE_ALL(Localization.lang("Collapse all")), + EXPAND_ALL(Localization.lang("Expand all")), COLLAPSE_ALL(Localization.lang("Collapse all")), ADD_ENTRY_IMMEDIATE(Localization.lang("Add entry"), IconTheme.JabRefIcons.ADD_ENTRY_IMMEDIATE), ADD_ENTRY(Localization.lang("Add entry using..."), IconTheme.JabRefIcons.ADD_ENTRY, KeyBinding.ADD_ENTRY), - ADD_ENTRY_IDENTIFIER(Localization.lang("Enter identifier..."), IconTheme.JabRefIcons.ADD_ENTRY_IDENTIFIER, KeyBinding.ADD_ENTRY_IDENTIFIER), - ADD_ENTRY_PLAINTEXT(Localization.lang("Interpret citations..."), IconTheme.JabRefIcons.ADD_ENTRY_PLAINTEXT, KeyBinding.ADD_ENTRY_PLAINTEXT), + ADD_ENTRY_IDENTIFIER(Localization.lang("Enter identifier..."), IconTheme.JabRefIcons.ADD_ENTRY_IDENTIFIER, + KeyBinding.ADD_ENTRY_IDENTIFIER), + ADD_ENTRY_PLAINTEXT(Localization.lang("Interpret citations..."), IconTheme.JabRefIcons.ADD_ENTRY_PLAINTEXT, + KeyBinding.ADD_ENTRY_PLAINTEXT), LIBRARY_PROPERTIES(Localization.lang("Library properties")), FIND_DUPLICATES(Localization.lang("Find duplicates"), IconTheme.JabRefIcons.FIND_DUPLICATES), MERGE_ENTRIES(Localization.lang("Merge entries"), IconTheme.JabRefIcons.MERGE_ENTRIES, KeyBinding.MERGE_ENTRIES), - RESOLVE_DUPLICATE_KEYS(Localization.lang("Resolve duplicate citation keys"), Localization.lang("Find and remove duplicate citation keys"), KeyBinding.RESOLVE_DUPLICATE_CITATION_KEYS), + RESOLVE_DUPLICATE_KEYS(Localization.lang("Resolve duplicate citation keys"), + Localization.lang("Find and remove duplicate citation keys"), KeyBinding.RESOLVE_DUPLICATE_CITATION_KEYS), CHECK_INTEGRITY(Localization.lang("Check integrity"), KeyBinding.CHECK_INTEGRITY), CHECK_CONSISTENCY(Localization.lang("Check consistency"), KeyBinding.CHECK_CONSISTENCY), - FIND_UNLINKED_FILES(Localization.lang("Search for unlinked local files"), IconTheme.JabRefIcons.SEARCH, KeyBinding.FIND_UNLINKED_FILES), - AUTO_LINK_FILES(Localization.lang("Automatically set file links"), IconTheme.JabRefIcons.AUTO_FILE_LINK, KeyBinding.AUTOMATICALLY_LINK_FILES), + FIND_UNLINKED_FILES(Localization.lang("Search for unlinked local files"), IconTheme.JabRefIcons.SEARCH, + KeyBinding.FIND_UNLINKED_FILES), + AUTO_LINK_FILES(Localization.lang("Automatically set file links"), IconTheme.JabRefIcons.AUTO_FILE_LINK, + KeyBinding.AUTOMATICALLY_LINK_FILES), LOOKUP_DOC_IDENTIFIER(Localization.lang("Search document identifier online"), KeyBinding.LOOKUP_DOC_IDENTIFIER), - LOOKUP_FULLTEXT(Localization.lang("Search full text documents online"), IconTheme.JabRefIcons.FILE_SEARCH, KeyBinding.DOWNLOAD_FULL_TEXT), - GENERATE_CITE_KEY(Localization.lang("Generate citation key"), IconTheme.JabRefIcons.MAKE_KEY, KeyBinding.AUTOGENERATE_CITATION_KEYS), - GENERATE_CITE_KEYS(Localization.lang("Generate citation keys"), IconTheme.JabRefIcons.MAKE_KEY, KeyBinding.AUTOGENERATE_CITATION_KEYS), - DOWNLOAD_FULL_TEXT(Localization.lang("Search full text documents online"), IconTheme.JabRefIcons.FILE_SEARCH, KeyBinding.DOWNLOAD_FULL_TEXT), + LOOKUP_FULLTEXT(Localization.lang("Search full text documents online"), IconTheme.JabRefIcons.FILE_SEARCH, + KeyBinding.DOWNLOAD_FULL_TEXT), + GENERATE_CITE_KEY(Localization.lang("Generate citation key"), IconTheme.JabRefIcons.MAKE_KEY, + KeyBinding.AUTOGENERATE_CITATION_KEYS), + GENERATE_CITE_KEYS(Localization.lang("Generate citation keys"), IconTheme.JabRefIcons.MAKE_KEY, + KeyBinding.AUTOGENERATE_CITATION_KEYS), + DOWNLOAD_FULL_TEXT(Localization.lang("Search full text documents online"), IconTheme.JabRefIcons.FILE_SEARCH, + KeyBinding.DOWNLOAD_FULL_TEXT), CLEANUP_ENTRIES(Localization.lang("Clean up entries"), IconTheme.JabRefIcons.CLEANUP_ENTRIES, KeyBinding.CLEANUP), SET_FILE_LINKS(Localization.lang("Automatically set file links"), KeyBinding.AUTOMATICALLY_LINK_FILES), @@ -162,30 +201,38 @@ public enum StandardActions implements Action { DOWNLOAD_FILE(Localization.lang("Download file"), IconTheme.JabRefIcons.DOWNLOAD_FILE), REDOWNLOAD_FILE(Localization.lang("Redownload file"), IconTheme.JabRefIcons.DOWNLOAD_FILE), RENAME_FILE_TO_PATTERN(Localization.lang("Rename file to defined pattern"), IconTheme.JabRefIcons.AUTO_RENAME), - RENAME_FILE_TO_NAME(Localization.lang("Rename files to configured filename format pattern"), IconTheme.JabRefIcons.RENAME, KeyBinding.REPLACE_STRING), + RENAME_FILE_TO_NAME(Localization.lang("Rename files to configured filename format pattern"), + IconTheme.JabRefIcons.RENAME, KeyBinding.REPLACE_STRING), MOVE_FILE_TO_FOLDER(Localization.lang("Move file to file directory"), IconTheme.JabRefIcons.MOVE_TO_FOLDER), MOVE_FILE_TO_FOLDER_AND_RENAME(Localization.lang("Move file to file directory and rename file")), - COPY_FILE_TO_FOLDER(Localization.lang("Copy linked file to folder..."), IconTheme.JabRefIcons.COPY_TO_FOLDER, KeyBinding.COPY), + COPY_FILE_TO_FOLDER(Localization.lang("Copy linked file to folder..."), IconTheme.JabRefIcons.COPY_TO_FOLDER, + KeyBinding.COPY), REMOVE_LINK(Localization.lang("Remove link"), IconTheme.JabRefIcons.REMOVE_LINK), REMOVE_LINKS(Localization.lang("Remove links"), IconTheme.JabRefIcons.REMOVE_LINK), - DELETE_FILE(Localization.lang("Permanently delete local file"), IconTheme.JabRefIcons.DELETE_FILE, KeyBinding.DELETE_ENTRY), + DELETE_FILE(Localization.lang("Permanently delete local file"), IconTheme.JabRefIcons.DELETE_FILE, + KeyBinding.DELETE_ENTRY), HELP(Localization.lang("Online help"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), HELP_GROUPS(Localization.lang("Open Help page"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), HELP_KEY_PATTERNS(Localization.lang("Help on key patterns"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), - HELP_REGEX_SEARCH(Localization.lang("Help on regular expression search"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), + HELP_REGEX_SEARCH(Localization.lang("Help on regular expression search"), IconTheme.JabRefIcons.HELP, + KeyBinding.HELP), HELP_NAME_FORMATTER(Localization.lang("Help on Name Formatting"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), HELP_SPECIAL_FIELDS(Localization.lang("Help on special fields"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), - HELP_PUSH_TO_APPLICATION(Localization.lang("Help on external applications"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), + HELP_PUSH_TO_APPLICATION(Localization.lang("Help on external applications"), IconTheme.JabRefIcons.HELP, + KeyBinding.HELP), WEB_MENU(Localization.lang("JabRef resources")), OPEN_WEBPAGE(Localization.lang("Website"), Localization.lang("Opens JabRef's website"), IconTheme.JabRefIcons.HOME), OPEN_FACEBOOK("Facebook", Localization.lang("Opens JabRef's Facebook page"), IconTheme.JabRefIcons.FACEBOOK), OPEN_LINKEDIN("LinkedIn", Localization.lang("Opens JabRef's LinkedIn page"), IconTheme.JabRefIcons.LINKEDIN), OPEN_MASTODON("Mastodon", Localization.lang("Opens JabRef's Mastodon page"), IconTheme.JabRefIcons.MASTODON), - OPEN_PRIVACY_POLICY("Privacy policy", Localization.lang("Opens JabRef's privacy policy"), IconTheme.JabRefIcons.BOOK), + OPEN_PRIVACY_POLICY("Privacy policy", Localization.lang("Opens JabRef's privacy policy"), + IconTheme.JabRefIcons.BOOK), OPEN_BLOG(Localization.lang("Blog"), Localization.lang("Opens JabRef's blog"), IconTheme.JabRefIcons.BLOG), - OPEN_DEV_VERSION_LINK(Localization.lang("Development version"), Localization.lang("Opens a link where the current development version can be downloaded")), - OPEN_CHANGELOG(Localization.lang("View change log"), Localization.lang("See what has been changed in the JabRef versions")), + OPEN_DEV_VERSION_LINK(Localization.lang("Development version"), + Localization.lang("Opens a link where the current development version can be downloaded")), + OPEN_CHANGELOG(Localization.lang("View change log"), + Localization.lang("See what has been changed in the JabRef versions")), OPEN_GITHUB("GitHub", Localization.lang("Opens JabRef's GitHub page"), IconTheme.JabRefIcons.GITHUB), DONATE(Localization.lang("Donate to JabRef"), Localization.lang("Donate to JabRef"), IconTheme.JabRefIcons.DONATE), OPEN_FORUM(Localization.lang("Community forum"), Localization.lang("Community forum"), IconTheme.JabRefIcons.FORUM), @@ -199,16 +246,13 @@ public enum StandardActions implements Action { REMOVE_LIST(Localization.lang("Remove"), IconTheme.JabRefIcons.REMOVE), RELOAD_LIST(Localization.lang("Reload"), IconTheme.JabRefIcons.REFRESH), - GROUP_REMOVE(Localization.lang("Remove group")), - GROUP_REMOVE_KEEP_SUBGROUPS(Localization.lang("Keep subgroups")), + GROUP_REMOVE(Localization.lang("Remove group")), GROUP_REMOVE_KEEP_SUBGROUPS(Localization.lang("Keep subgroups")), GROUP_REMOVE_WITH_SUBGROUPS(Localization.lang("Also remove subgroups")), - GROUP_CHAT(Localization.lang("Chat with group")), - GROUP_EDIT(Localization.lang("Edit group")), + GROUP_CHAT(Localization.lang("Chat with group")), GROUP_EDIT(Localization.lang("Edit group")), GROUP_GENERATE_SUMMARIES(Localization.lang("Generate summaries for entries in the group")), GROUP_GENERATE_EMBEDDINGS(Localization.lang("Generate embeddings for linked files in the group")), GROUP_SUGGESTED_GROUPS_ADD(Localization.lang("Add JabRef suggested groups")), - GROUP_SUBGROUP_ADD(Localization.lang("Add subgroup")), - GROUP_SUBGROUP_REMOVE(Localization.lang("Remove subgroups")), + GROUP_SUBGROUP_ADD(Localization.lang("Add subgroup")), GROUP_SUBGROUP_REMOVE(Localization.lang("Remove subgroups")), GROUP_SUBGROUP_SORT(Localization.lang("Sort subgroups A-Z")), GROUP_SUBGROUP_SORT_REVERSE(Localization.lang("Sort subgroups Z-A")), GROUP_SUBGROUP_SORT_ENTRIES(Localization.lang("Sort subgroups by # of entries (Descending)")), @@ -219,15 +263,16 @@ public enum StandardActions implements Action { CLEAR_EMBEDDINGS_CACHE(Localization.lang("Clear embeddings cache")), - GIT(Localization.lang("Git"), IconTheme.JabRefIcons.GIT_SYNC), - GIT_PULL(Localization.lang("Pull")), - GIT_PUSH(Localization.lang("Push")), - GIT_COMMIT(Localization.lang("Commit")), + GIT(Localization.lang("Git"), IconTheme.JabRefIcons.GIT_SYNC), GIT_PULL(Localization.lang("Pull")), + GIT_PUSH(Localization.lang("Push")), GIT_COMMIT(Localization.lang("Commit")), GIT_SHARE(Localization.lang("Share this library to GitHub")); private String text; + private final String description; + private final Optional icon; + private final Optional keyBinding; StandardActions(String text) { @@ -307,4 +352,5 @@ public Action withText(String text) { this.text = Objects.requireNonNull(text); return this; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/ai/ClearEmbeddingsAction.java b/jabgui/src/main/java/org/jabref/gui/ai/ClearEmbeddingsAction.java index 91b3e926269..1745423da1d 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/ClearEmbeddingsAction.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/ClearEmbeddingsAction.java @@ -14,15 +14,17 @@ import static org.jabref.gui.actions.ActionHelper.needsDatabase; public class ClearEmbeddingsAction extends SimpleCommand { + private final StateManager stateManager; + private final DialogService dialogService; + private final AiService aiService; + private final TaskExecutor taskExecutor; - public ClearEmbeddingsAction(StateManager stateManager, - DialogService dialogService, - AiService aiService, - TaskExecutor taskExecutor) { + public ClearEmbeddingsAction(StateManager stateManager, DialogService dialogService, AiService aiService, + TaskExecutor taskExecutor) { this.stateManager = stateManager; this.dialogService = dialogService; this.taskExecutor = taskExecutor; @@ -36,8 +38,7 @@ public void execute() { return; } - boolean confirmed = dialogService.showConfirmationDialogAndWait( - Localization.lang("Clear embeddings cache"), + boolean confirmed = dialogService.showConfirmationDialogAndWait(Localization.lang("Clear embeddings cache"), Localization.lang("Clear embeddings cache for current library?")); if (!confirmed) { @@ -46,16 +47,16 @@ public void execute() { dialogService.notify(Localization.lang("Clearing embeddings cache...")); - List linkedFiles = stateManager - .getActiveDatabase() - .get() - .getDatabase() - .getEntries() - .stream() - .flatMap(entry -> entry.getFiles().stream()) - .toList(); + List linkedFiles = stateManager.getActiveDatabase() + .get() + .getDatabase() + .getEntries() + .stream() + .flatMap(entry -> entry.getFiles().stream()) + .toList(); BackgroundTask.wrap(() -> aiService.getIngestionService().clearEmbeddingsFor(linkedFiles)) - .executeWith(taskExecutor); + .executeWith(taskExecutor); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java index 8a4b2db488d..f9963b4f667 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java @@ -46,43 +46,62 @@ import org.slf4j.LoggerFactory; public class AiChatComponent extends VBox { + private static final Logger LOGGER = LoggerFactory.getLogger(AiChatComponent.class); // Example Questions private static final String EXAMPLE_QUESTION_1 = Localization.lang("What is the goal of the paper?"); + private static final String EXAMPLE_QUESTION_2 = Localization.lang("Which methods were used in the research?"); + private static final String EXAMPLE_QUESTION_3 = Localization.lang("What are the key findings?"); private final AiService aiService; + private final ObservableList entries; + private final BibDatabaseContext bibDatabaseContext; + private final AiPreferences aiPreferences; + private final DialogService dialogService; + private final TaskExecutor taskExecutor; private final AiChatLogic aiChatLogic; private final ObservableList notifications = FXCollections.observableArrayList(); - @FXML private Loadable uiLoadableChatHistory; - @FXML private ChatHistoryComponent uiChatHistory; - @FXML private Button notificationsButton; - @FXML private ChatPromptComponent chatPrompt; - @FXML private Label noticeText; - @FXML private Hyperlink exQuestion1; - @FXML private Hyperlink exQuestion2; - @FXML private Hyperlink exQuestion3; - @FXML private HBox exQuestionBox; - - public AiChatComponent(AiService aiService, - StringProperty name, - ObservableList chatHistory, - ObservableList entries, - BibDatabaseContext bibDatabaseContext, - AiPreferences aiPreferences, - DialogService dialogService, - TaskExecutor taskExecutor - ) { + @FXML + private Loadable uiLoadableChatHistory; + + @FXML + private ChatHistoryComponent uiChatHistory; + + @FXML + private Button notificationsButton; + + @FXML + private ChatPromptComponent chatPrompt; + + @FXML + private Label noticeText; + + @FXML + private Hyperlink exQuestion1; + + @FXML + private Hyperlink exQuestion2; + + @FXML + private Hyperlink exQuestion3; + + @FXML + private HBox exQuestionBox; + + public AiChatComponent(AiService aiService, StringProperty name, ObservableList chatHistory, + ObservableList entries, BibDatabaseContext bibDatabaseContext, AiPreferences aiPreferences, + DialogService dialogService, TaskExecutor taskExecutor) { this.aiService = aiService; this.entries = entries; this.bibDatabaseContext = bibDatabaseContext; @@ -94,9 +113,7 @@ public AiChatComponent(AiService aiService, aiService.getIngestionService().ingest(name, ListUtil.getLinkedFiles(entries).toList(), bibDatabaseContext); - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @FXML @@ -110,16 +127,18 @@ public void initialize() { } private void initializeNotifications() { - ListUtil.getLinkedFiles(entries).forEach(file -> - aiService.getIngestionService().ingest(file, bibDatabaseContext).stateProperty().addListener(obs -> updateNotifications())); + ListUtil.getLinkedFiles(entries) + .forEach(file -> aiService.getIngestionService() + .ingest(file, bibDatabaseContext) + .stateProperty() + .addListener(obs -> updateNotifications())); updateNotifications(); } private void initializeNotice() { - String newNotice = noticeText - .getText() - .replaceAll("%0", aiPreferences.getAiProvider().getLabel() + " " + aiPreferences.getSelectedChatModel()); + String newNotice = noticeText.getText() + .replaceAll("%0", aiPreferences.getAiProvider().getLabel() + " " + aiPreferences.getSelectedChatModel()); noticeText.setText(newNotice); } @@ -154,10 +173,8 @@ private void addExampleQuestionAction(Hyperlink hyperlink) { } private void initializeChatPrompt() { - notificationsButton.setOnAction(event -> - new PopOver(new NotificationsComponent(notifications)) - .show(notificationsButton) - ); + notificationsButton + .setOnAction(event -> new PopOver(new NotificationsComponent(notifications)).show(notificationsButton)); chatPrompt.setSendCallback(this::onSendMessage); @@ -183,7 +200,8 @@ private void updateNotifications() { notificationsButton.setManaged(!notifications.isEmpty()); if (!notifications.isEmpty()) { - UiTaskExecutor.runInJavaFXThread(() -> notificationsButton.setGraphic(IconTheme.JabRefIcons.WARNING.withColor(Color.YELLOW).getGraphicNode())); + UiTaskExecutor.runInJavaFXThread(() -> notificationsButton + .setGraphic(IconTheme.JabRefIcons.WARNING.withColor(Color.YELLOW).getGraphicNode())); } } @@ -192,46 +210,53 @@ private List updateNotificationsForEntry(BibEntry entry) { if (entries.size() == 1) { if (entry.getCitationKey().isEmpty()) { + notifications + .add(new Notification(Localization.lang("No citation key for %0", entry.getAuthorTitleYear()), + Localization.lang("The chat history will not be stored in next sessions"))); + } + else if (!CitationKeyCheck.citationKeyIsPresentAndUnique(bibDatabaseContext, entry)) { notifications.add(new Notification( - Localization.lang("No citation key for %0", entry.getAuthorTitleYear()), - Localization.lang("The chat history will not be stored in next sessions") - )); - } else if (!CitationKeyCheck.citationKeyIsPresentAndUnique(bibDatabaseContext, entry)) { - notifications.add(new Notification( - Localization.lang("Invalid citation key for %0 (%1)", entry.getCitationKey().get(), entry.getAuthorTitleYear()), - Localization.lang("The chat history will not be stored in next sessions") - )); + Localization.lang("Invalid citation key for %0 (%1)", entry.getCitationKey().get(), + entry.getAuthorTitleYear()), + Localization.lang("The chat history will not be stored in next sessions"))); } } entry.getFiles().forEach(file -> { if (!FileUtil.isPDFFile(Path.of(file.getLink()))) { - notifications.add(new Notification( - Localization.lang("File %0 is not a PDF file", file.getLink()), - Localization.lang("Only PDF files can be used for chatting") - )); + notifications.add(new Notification(Localization.lang("File %0 is not a PDF file", file.getLink()), + Localization.lang("Only PDF files can be used for chatting"))); } }); - entry.getFiles().stream().map(file -> aiService.getIngestionService().ingest(file, bibDatabaseContext)).forEach(ingestionStatus -> { - switch (ingestionStatus.getState()) { - case PROCESSING -> notifications.add(new Notification( - Localization.lang("File %0 is currently being processed", ingestionStatus.getObject().getLink()), - Localization.lang("After the file is ingested, you will be able to chat with it.") - )); - - case ERROR -> { - assert ingestionStatus.getException().isPresent(); // When the state is ERROR, the exception must be present. - - notifications.add(new Notification( - Localization.lang("File %0 could not be ingested", ingestionStatus.getObject().getLink()), - ingestionStatus.getException().get().getLocalizedMessage() - )); + entry.getFiles() + .stream() + .map(file -> aiService.getIngestionService().ingest(file, bibDatabaseContext)) + .forEach(ingestionStatus -> { + switch (ingestionStatus.getState()) { + case PROCESSING -> notifications.add(new Notification( + Localization.lang("File %0 is currently being processed", + ingestionStatus.getObject().getLink()), + Localization.lang("After the file is ingested, you will be able to chat with it."))); + + case ERROR -> { + assert ingestionStatus.getException().isPresent(); // When the + // state is + // ERROR, the + // exception + // must be + // present. + + notifications.add(new Notification( + Localization.lang("File %0 could not be ingested", + ingestionStatus.getObject().getLink()), + ingestionStatus.getException().get().getLocalizedMessage())); + } + + case SUCCESS -> { + } } - - case SUCCESS -> { } - } - }); + }); return notifications; } @@ -241,28 +266,30 @@ private void onSendMessage(String userPrompt) { updatePromptHistory(); setLoading(true); - BackgroundTask task = - BackgroundTask - .wrap(() -> aiChatLogic.execute(userMessage)) - .showToUser(true) - .onSuccess(aiMessage -> { - setLoading(false); - chatPrompt.requestPromptFocus(); - }) - .onFailure(e -> { - LOGGER.error("Got an error while sending a message to AI", e); - setLoading(false); - - // Typically, if user has entered an invalid API base URL, we get either "401 - null" or "404 - null" strings. - // Since there might be other strings returned from other API endpoints, we use startsWith() here. - if (e.getMessage().startsWith("404") || e.getMessage().startsWith("401")) { - addError(Localization.lang("API base URL setting appears to be incorrect. Please check it in AI expert settings.")); - } else { - addError(e.getMessage()); - } - - chatPrompt.switchToErrorState(userPrompt); - }); + BackgroundTask task = BackgroundTask.wrap(() -> aiChatLogic.execute(userMessage)) + .showToUser(true) + .onSuccess(aiMessage -> { + setLoading(false); + chatPrompt.requestPromptFocus(); + }) + .onFailure(e -> { + LOGGER.error("Got an error while sending a message to AI", e); + setLoading(false); + + // Typically, if user has entered an invalid API base URL, we get either + // "401 - null" or "404 - null" strings. + // Since there might be other strings returned from other API endpoints, + // we use startsWith() here. + if (e.getMessage().startsWith("404") || e.getMessage().startsWith("401")) { + addError(Localization + .lang("API base URL setting appears to be incorrect. Please check it in AI expert settings.")); + } + else { + addError(e.getMessage()); + } + + chatPrompt.switchToErrorState(userPrompt); + }); task.titleProperty().set(Localization.lang("Waiting for AI reply...")); @@ -280,12 +307,11 @@ private void updatePromptHistory() { } private Stream getReversedUserMessagesStream() { - return aiChatLogic - .getChatHistory() - .reversed() - .stream() - .filter(message -> message instanceof UserMessage) - .map(UserMessage.class::cast); + return aiChatLogic.getChatHistory() + .reversed() + .stream() + .filter(message -> message instanceof UserMessage) + .map(UserMessage.class::cast); } private void setLoading(boolean loading) { @@ -295,10 +321,8 @@ private void setLoading(boolean loading) { @FXML private void onClearChatHistory() { - boolean agreed = dialogService.showConfirmationDialogAndWait( - Localization.lang("Clear chat history"), - Localization.lang("Are you sure you want to clear the chat history of this entry?") - ); + boolean agreed = dialogService.showConfirmationDialogAndWait(Localization.lang("Clear chat history"), + Localization.lang("Are you sure you want to clear the chat history of this entry?")); if (agreed) { aiChatLogic.getChatHistory().clear(); @@ -311,4 +335,5 @@ private void deleteLastMessage() { aiChatLogic.getChatHistory().remove(index); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatGuardedComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatGuardedComponent.java index 4a22e6d1519..a22f435a90f 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatGuardedComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatGuardedComponent.java @@ -17,35 +17,38 @@ import dev.langchain4j.data.message.ChatMessage; /** - * Main class for AI chatting. It checks if the AI features are enabled and if the embedding model is properly set up. + * Main class for AI chatting. It checks if the AI features are enabled and if the + * embedding model is properly set up. */ public class AiChatGuardedComponent extends EmbeddingModelGuardedComponent { + /// This field is used for two purposes: /// 1. Logging /// 2. Title of group chat window - /// Thus, if you use {@link AiChatGuardedComponent} for one entry in {@link org.jabref.gui.entryeditor.EntryEditor}, then you may not localize + /// Thus, if you use {@link AiChatGuardedComponent} for one entry in {@link + /// org.jabref.gui.entryeditor.EntryEditor}, then you may not localize /// this parameter. However, for group chat window, you should. private final StringProperty name; private final ObservableList chatHistory; + private final BibDatabaseContext bibDatabaseContext; + private final ObservableList entries; + private final AiService aiService; + private final DialogService dialogService; + private final AiPreferences aiPreferences; + private final TaskExecutor taskExecutor; - public AiChatGuardedComponent(StringProperty name, - ObservableList chatHistory, - BibDatabaseContext bibDatabaseContext, - ObservableList entries, - AiService aiService, - DialogService dialogService, - AiPreferences aiPreferences, - ExternalApplicationsPreferences externalApplicationsPreferences, - AdaptVisibleTabs adaptVisibleTabs, - TaskExecutor taskExecutor - ) { + public AiChatGuardedComponent(StringProperty name, ObservableList chatHistory, + BibDatabaseContext bibDatabaseContext, ObservableList entries, AiService aiService, + DialogService dialogService, AiPreferences aiPreferences, + ExternalApplicationsPreferences externalApplicationsPreferences, AdaptVisibleTabs adaptVisibleTabs, + TaskExecutor taskExecutor) { super(aiService, aiPreferences, externalApplicationsPreferences, dialogService, adaptVisibleTabs); this.name = name; @@ -62,15 +65,8 @@ public AiChatGuardedComponent(StringProperty name, @Override protected Node showEmbeddingModelGuardedContent() { - return new AiChatComponent( - aiService, - name, - chatHistory, - entries, - bibDatabaseContext, - aiPreferences, - dialogService, - taskExecutor - ); + return new AiChatComponent(aiService, name, chatHistory, entries, bibDatabaseContext, aiPreferences, + dialogService, taskExecutor); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatWindow.java b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatWindow.java index 8e1fd36bea7..b20367fba2a 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatWindow.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatWindow.java @@ -18,23 +18,26 @@ import dev.langchain4j.data.message.ChatMessage; public class AiChatWindow extends BaseWindow { + private final AiService aiService; + private final DialogService dialogService; + private final AiPreferences aiPreferences; + private final ExternalApplicationsPreferences externalApplicationsPreferences; + private final TaskExecutor taskExecutor; + private final AdaptVisibleTabs adaptVisibleTabs; - // This field is used for finding an existing AI chat window when user wants to chat with the same group again. + // This field is used for finding an existing AI chat window when user wants to chat + // with the same group again. private String chatName; - public AiChatWindow(AiService aiService, - DialogService dialogService, - AiPreferences aiPreferences, - ExternalApplicationsPreferences externalApplicationsPreferences, - AdaptVisibleTabs adaptVisibleTabs, - TaskExecutor taskExecutor - ) { + public AiChatWindow(AiService aiService, DialogService dialogService, AiPreferences aiPreferences, + ExternalApplicationsPreferences externalApplicationsPreferences, AdaptVisibleTabs adaptVisibleTabs, + TaskExecutor taskExecutor) { this.aiService = aiService; this.dialogService = dialogService; this.aiPreferences = aiPreferences; @@ -43,30 +46,17 @@ public AiChatWindow(AiService aiService, this.taskExecutor = taskExecutor; } - public void setChat(StringProperty name, ObservableList chatHistory, BibDatabaseContext bibDatabaseContext, ObservableList entries) { + public void setChat(StringProperty name, ObservableList chatHistory, + BibDatabaseContext bibDatabaseContext, ObservableList entries) { setTitle(Localization.lang("AI chat with %0", name.getValue())); chatName = name.getValue(); - setScene( - new Scene( - new AiChatGuardedComponent( - name, - chatHistory, - bibDatabaseContext, - entries, - aiService, - dialogService, - aiPreferences, - externalApplicationsPreferences, - adaptVisibleTabs, - taskExecutor - ), - 800, - 600 - ) - ); + setScene(new Scene(new AiChatGuardedComponent(name, chatHistory, bibDatabaseContext, entries, aiService, + dialogService, aiPreferences, externalApplicationsPreferences, adaptVisibleTabs, taskExecutor), 800, + 600)); } public String getChatName() { return chatName; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java index c99e125b8e7..2bb3671bea3 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java @@ -13,12 +13,12 @@ import dev.langchain4j.data.message.ChatMessage; public class ChatHistoryComponent extends ScrollPane { - @FXML private VBox vBox; + + @FXML + private VBox vBox; public ChatHistoryComponent() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); this.needsLayoutProperty().addListener((obs, oldValue, newValue) -> { if (newValue) { @@ -38,11 +38,11 @@ public void setItems(ObservableList items) { private void fill(ObservableList items) { UiTaskExecutor.runInJavaFXThread(() -> { vBox.getChildren().clear(); - items.forEach(chatMessage -> - vBox.getChildren().add(new ChatMessageComponent(chatMessage, chatMessageComponent -> { - int index = vBox.getChildren().indexOf(chatMessageComponent); - items.remove(index); - }))); + items.forEach(chatMessage -> vBox.getChildren() + .add(new ChatMessageComponent(chatMessage, chatMessageComponent -> { + int index = vBox.getChildren().indexOf(chatMessageComponent); + items.remove(index); + }))); }); } @@ -50,4 +50,5 @@ public void scrollDown() { this.layout(); this.setVvalue(this.getVmax()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java index f620938dea3..73f6c18b53d 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java @@ -25,23 +25,32 @@ import org.slf4j.LoggerFactory; public class ChatMessageComponent extends HBox { + private static final Logger LOGGER = LoggerFactory.getLogger(ChatMessageComponent.class); private final ObjectProperty chatMessage = new SimpleObjectProperty<>(); + private final ObjectProperty> onDelete = new SimpleObjectProperty<>(); - @FXML private HBox wrapperHBox; - @FXML private VBox vBox; - @FXML private Label sourceLabel; - @FXML private Pane markdownContentPane; - @FXML private VBox buttonsVBox; + @FXML + private HBox wrapperHBox; + + @FXML + private VBox vBox; + + @FXML + private Label sourceLabel; + + @FXML + private Pane markdownContentPane; + + @FXML + private VBox buttonsVBox; private final MarkdownTextFlow markdownTextFlow; public ChatMessageComponent() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); chatMessage.addListener((_, _, newValue) -> { if (newValue != null) { @@ -98,8 +107,9 @@ private void loadChatMessage() { markdownTextFlow.setMarkdown(errorMessage.getText()); } - default -> - LOGGER.error("ChatMessageComponent supports only user, AI, or error messages, but other type was passed: {}", chatMessage.get().type().name()); + default -> LOGGER.error( + "ChatMessageComponent supports only user, AI, or error messages, but other type was passed: {}", + chatMessage.get().type().name()); } } @@ -117,6 +127,9 @@ private void onDeleteClick() { } private void setColor(String fillColor, String borderColor) { - vBox.setStyle("-fx-background-color: " + fillColor + "; -fx-border-radius: 10; -fx-background-radius: 10; -fx-border-color: " + borderColor + "; -fx-border-width: 3;"); + vBox.setStyle("-fx-background-color: " + fillColor + + "; -fx-border-radius: 10; -fx-background-radius: 10; -fx-border-color: " + borderColor + + "; -fx-border-width: 3;"); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.java index 8bb590103ac..9d332da35ea 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.java @@ -23,12 +23,16 @@ import com.dlsc.gemsfx.ExpandingTextArea; public class ChatPromptComponent extends HBox { - // If current message that user is typing in prompt is non-existent, new, or empty, then we use + + // If current message that user is typing in prompt is non-existent, new, or empty, + // then we use // this value in currentUserMessageScroll. private static final int NEW_NON_EXISTENT_MESSAGE = -1; private final ObjectProperty> sendCallback = new SimpleObjectProperty<>(); + private final ObjectProperty> retryCallback = new SimpleObjectProperty<>(); + private final ObjectProperty cancelCallback = new SimpleObjectProperty<>(); private final ListProperty history = new SimpleListProperty<>(FXCollections.observableArrayList()); @@ -38,17 +42,19 @@ public class ChatPromptComponent extends HBox { // Whenever user edits the prompt, this value is reset to NEW_NON_EXISTENT_MESSAGE. private final IntegerProperty currentUserMessageScroll = new SimpleIntegerProperty(NEW_NON_EXISTENT_MESSAGE); - // If the current content of the prompt is a history message, then this property is true. + // If the current content of the prompt is a history message, then this property is + // true. // If user begins to edit or type a new text, then this property is false. private final BooleanProperty showingHistoryMessage = new SimpleBooleanProperty(false); - @FXML private ExpandingTextArea userPromptTextArea; - @FXML private Button submitButton; + @FXML + private ExpandingTextArea userPromptTextArea; + + @FXML + private Button submitButton; public ChatPromptComponent() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); history.addListener((observable, oldValue, newValue) -> { currentUserMessageScroll.set(NEW_NON_EXISTENT_MESSAGE); @@ -82,26 +88,34 @@ private void initialize() { currentUserMessageScroll.set(currentUserMessageScroll.get() - 1); // There could be two effects after setting the properties: - // 1) User scrolls to a recent message, then we should properly update the prompt text. - // 2) Scroll is set to -1 (which is NEW_NON_EXISTENT_MESSAGE) and we should clear the prompt text. - // On the second event currentUserMessageScroll will be set to -1 and showingHistoryMessage + // 1) User scrolls to a recent message, then we should properly update + // the prompt text. + // 2) Scroll is set to -1 (which is NEW_NON_EXISTENT_MESSAGE) and we + // should clear the prompt text. + // On the second event currentUserMessageScroll will be set to -1 and + // showingHistoryMessage // will be true (this is important). } - } else if (keyEvent.getCode() == KeyCode.UP) { + } + else if (keyEvent.getCode() == KeyCode.UP) { // [impl->req~ai.chat.new-message-based-on-previous~1] - if ((currentUserMessageScroll.get() < history.get().size() - 1) && (userPromptTextArea.getText().isEmpty() || showingHistoryMessage.get())) { + if ((currentUserMessageScroll.get() < history.get().size() - 1) + && (userPromptTextArea.getText().isEmpty() || showingHistoryMessage.get())) { // 1. We should not go up the maximum number of user messages. // 2. We can scroll history only on two conditions: - // 1) The prompt is empty. - // 2) User has already been scrolling the history. + // 1) The prompt is empty. + // 2) User has already been scrolling the history. showingHistoryMessage.set(true); currentUserMessageScroll.set(currentUserMessageScroll.get() + 1); } - } else { + } + else { // Cursor left/right should not stop history scrolling if (keyEvent.getCode() != KeyCode.RIGHT && keyEvent.getCode() != KeyCode.LEFT) { - // It is okay to go back and forth in the prompt while showing a history message. - // But if user begins doing something else, we should not track the history and reset + // It is okay to go back and forth in the prompt while showing a + // history message. + // But if user begins doing something else, we should not track the + // history and reset // all the properties. showingHistoryMessage.set(false); currentUserMessageScroll.set(NEW_NON_EXISTENT_MESSAGE); @@ -110,7 +124,8 @@ private void initialize() { if (keyEvent.getCode() == KeyCode.ENTER) { if (keyEvent.isControlDown()) { userPromptTextArea.appendText("\n"); - } else { + } + else { onSendMessage(); } } @@ -123,19 +138,28 @@ private void initialize() { // 2) or to a new history entry. if (newValue.intValue() != NEW_NON_EXISTENT_MESSAGE && showingHistoryMessage.get()) { if (userPromptTextArea.getCaretPosition() == 0 || !userPromptTextArea.getText().contains("\n")) { - // If there are new lines in the prompt, then it is ambiguous whether the user tries to scroll up or down in history or editing lines in the current prompt. - // The easy way to get rid of this ambiguity is to disallow scrolling when there are new lines in the prompt. - // But the exception to this situation is when the caret position is at the beginning of the prompt. - history.get().stream() - .skip(newValue.intValue()) - .findFirst() - .ifPresent(message -> userPromptTextArea.setText(message)); + // If there are new lines in the prompt, then it is ambiguous whether + // the user tries to scroll up or down in history or editing lines in + // the current prompt. + // The easy way to get rid of this ambiguity is to disallow scrolling + // when there are new lines in the prompt. + // But the exception to this situation is when the caret position is + // at the beginning of the prompt. + history.get() + .stream() + .skip(newValue.intValue()) + .findFirst() + .ifPresent(message -> userPromptTextArea.setText(message)); } - } else { - // When currentUserMessageScroll is set to NEW_NON_EXISTENT_MESSAGE, then we should: - // 1) either clear the prompt, if user scrolls down the most recent history entry. + } + else { + // When currentUserMessageScroll is set to NEW_NON_EXISTENT_MESSAGE, then + // we should: + // 1) either clear the prompt, if user scrolls down the most recent + // history entry. // 2) do nothing, if user starts to edit the history entry. - // We distinguish these two cases by checking showingHistoryMessage, which is true for -1 message, and false for others. + // We distinguish these two cases by checking showingHistoryMessage, which + // is true for -1 message, and false for others. if (showingHistoryMessage.get()) { userPromptTextArea.setText(""); } @@ -178,7 +202,8 @@ public void switchToNormalState() { } public void requestPromptFocus() { - // TODO: Check what would happen when programmer calls requestPromptFocus() while the component is in error state. + // TODO: Check what would happen when programmer calls requestPromptFocus() while + // the component is in error state. Platform.runLater(() -> userPromptTextArea.requestFocus()); } @@ -191,4 +216,5 @@ private void onSendMessage() { sendCallback.get().accept(userPrompt); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/privacynotice/AiPrivacyNoticeGuardedComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/privacynotice/AiPrivacyNoticeGuardedComponent.java index bb5d2124350..aab2dfb8d50 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/privacynotice/AiPrivacyNoticeGuardedComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/privacynotice/AiPrivacyNoticeGuardedComponent.java @@ -10,16 +10,23 @@ import org.jabref.logic.ai.AiPreferences; /** - * A class that guards a component, before AI privacy policy is accepted. - * Remember to call rebuildUi() method after initializing the guarded component. See {@link AiChatGuardedComponent} to look how it works. + * A class that guards a component, before AI privacy policy is accepted. Remember to call + * rebuildUi() method after initializing the guarded component. See + * {@link AiChatGuardedComponent} to look how it works. */ public abstract class AiPrivacyNoticeGuardedComponent extends DynamicallyChangeableNode { + private final AiPreferences aiPreferences; + private final ExternalApplicationsPreferences externalApplicationsPreferences; + private final DialogService dialogService; + private final AdaptVisibleTabs adaptVisibleTabs; - public AiPrivacyNoticeGuardedComponent(AiPreferences aiPreferences, ExternalApplicationsPreferences externalApplicationsPreferences, DialogService dialogService, AdaptVisibleTabs adaptVisibleTabs) { + public AiPrivacyNoticeGuardedComponent(AiPreferences aiPreferences, + ExternalApplicationsPreferences externalApplicationsPreferences, DialogService dialogService, + AdaptVisibleTabs adaptVisibleTabs) { this.aiPreferences = aiPreferences; this.externalApplicationsPreferences = externalApplicationsPreferences; this.dialogService = dialogService; @@ -31,18 +38,13 @@ public AiPrivacyNoticeGuardedComponent(AiPreferences aiPreferences, ExternalAppl public final void rebuildUi() { if (aiPreferences.getEnableAi()) { setContent(showPrivacyPolicyGuardedContent()); - } else { - setContent( - new PrivacyNoticeComponent( - aiPreferences, - this::rebuildUi, - externalApplicationsPreferences, - dialogService, - adaptVisibleTabs - ) - ); + } + else { + setContent(new PrivacyNoticeComponent(aiPreferences, this::rebuildUi, externalApplicationsPreferences, + dialogService, adaptVisibleTabs)); } } protected abstract Node showPrivacyPolicyGuardedContent(); + } diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java index 015f1b4a088..5cd1ec10c75 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java @@ -27,30 +27,41 @@ import org.slf4j.LoggerFactory; public class PrivacyNoticeComponent extends ScrollPane { + private final Logger LOGGER = LoggerFactory.getLogger(PrivacyNoticeComponent.class); - @FXML private VBox text; - @FXML private GridPane aiPolicies; - @FXML private Text embeddingModelText; + @FXML + private VBox text; + + @FXML + private GridPane aiPolicies; + + @FXML + private Text embeddingModelText; private final AiPreferences aiPreferences; + private final Runnable onIAgreeButtonClickCallback; + private final DialogService dialogService; + private final AdaptVisibleTabs adaptVisibleTabs; + private final ExternalApplicationsPreferences externalApplicationsPreferences; - @Inject private GuiPreferences preferences; + @Inject + private GuiPreferences preferences; - public PrivacyNoticeComponent(AiPreferences aiPreferences, Runnable onIAgreeButtonClickCallback, ExternalApplicationsPreferences externalApplicationsPreferences, DialogService dialogService, AdaptVisibleTabs adaptVisibleTabs) { + public PrivacyNoticeComponent(AiPreferences aiPreferences, Runnable onIAgreeButtonClickCallback, + ExternalApplicationsPreferences externalApplicationsPreferences, DialogService dialogService, + AdaptVisibleTabs adaptVisibleTabs) { this.aiPreferences = aiPreferences; this.onIAgreeButtonClickCallback = onIAgreeButtonClickCallback; this.externalApplicationsPreferences = externalApplicationsPreferences; this.dialogService = dialogService; this.adaptVisibleTabs = adaptVisibleTabs; - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @FXML @@ -61,10 +72,12 @@ private void initialize() { addPrivacyHyperlink(aiPolicies, AiProvider.HUGGING_FACE); addPrivacyHyperlink(aiPolicies, AiProvider.GPT4ALL); - String newEmbeddingModelText = embeddingModelText.getText().replaceAll("%0", aiPreferences.getEmbeddingModel().sizeInfo()); + String newEmbeddingModelText = embeddingModelText.getText() + .replaceAll("%0", aiPreferences.getEmbeddingModel().sizeInfo()); embeddingModelText.setText(newEmbeddingModelText); - // Because of the https://bugs.openjdk.org/browse/JDK-8090400 bug, the text in the privacy policy cannot be + // Because of the https://bugs.openjdk.org/browse/JDK-8090400 bug, the text in the + // privacy policy cannot be // fully wrapped. DoubleBinding textWidth = Bindings.subtract(this.widthProperty(), 88d); @@ -103,7 +116,8 @@ private void onDjlPrivacyPolicyClick() { private void openBrowser(String link) { try { NativeDesktop.openBrowser(link, externalApplicationsPreferences); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Error opening the browser to the Privacy Policy page of the AI provider.", e); dialogService.showErrorDialogAndWait(e); } @@ -116,4 +130,5 @@ private void hideAITabs() { entryEditorPreferences.setShouldShowAiChatTab(false); adaptVisibleTabs.adaptVisibleTabs(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/summary/SummaryComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/summary/SummaryComponent.java index 8e1c6089ca1..a97ea4c75f7 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/summary/SummaryComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/summary/SummaryComponent.java @@ -26,23 +26,23 @@ import org.slf4j.LoggerFactory; public class SummaryComponent extends AiPrivacyNoticeGuardedComponent { + private static final Logger LOGGER = LoggerFactory.getLogger(SummaryComponent.class); private final BibDatabaseContext bibDatabaseContext; + private final BibEntry entry; + private final CitationKeyGenerator citationKeyGenerator; + private final AiService aiService; + private final AiPreferences aiPreferences; - public SummaryComponent(BibDatabaseContext bibDatabaseContext, - BibEntry entry, - AiService aiService, - AiPreferences aiPreferences, - ExternalApplicationsPreferences externalApplicationsPreferences, - CitationKeyPatternPreferences citationKeyPatternPreferences, - DialogService dialogService, - AdaptVisibleTabs adaptVisibleTabs - ) { + public SummaryComponent(BibDatabaseContext bibDatabaseContext, BibEntry entry, AiService aiService, + AiPreferences aiPreferences, ExternalApplicationsPreferences externalApplicationsPreferences, + CitationKeyPatternPreferences citationKeyPatternPreferences, DialogService dialogService, + AdaptVisibleTabs adaptVisibleTabs) { super(aiPreferences, externalApplicationsPreferences, dialogService, adaptVisibleTabs); this.bibDatabaseContext = bibDatabaseContext; @@ -51,7 +51,10 @@ public SummaryComponent(BibDatabaseContext bibDatabaseContext, this.aiService = aiService; this.aiPreferences = aiPreferences; - aiService.getSummariesService().summarize(entry, bibDatabaseContext).stateProperty().addListener(o -> rebuildUi()); + aiService.getSummariesService() + .summarize(entry, bibDatabaseContext) + .stateProperty() + .addListener(o -> rebuildUi()); rebuildUi(); } @@ -60,100 +63,98 @@ public SummaryComponent(BibDatabaseContext bibDatabaseContext, protected Node showPrivacyPolicyGuardedContent() { if (bibDatabaseContext.getDatabasePath().isEmpty()) { return showErrorNoDatabasePath(); - } else if (entry.getFiles().isEmpty()) { + } + else if (entry.getFiles().isEmpty()) { return showErrorNoFiles(); - } else if (entry.getFiles().stream().map(LinkedFile::getLink).map(Path::of).noneMatch(FileUtil::isPDFFile)) { + } + else if (entry.getFiles().stream().map(LinkedFile::getLink).map(Path::of).noneMatch(FileUtil::isPDFFile)) { return showErrorNotPdfs(); - } else if (!CitationKeyCheck.citationKeyIsPresentAndUnique(bibDatabaseContext, entry)) { + } + else if (!CitationKeyCheck.citationKeyIsPresentAndUnique(bibDatabaseContext, entry)) { return tryToGenerateCitationKeyThenBind(entry); - } else { + } + else { return tryToShowSummary(); } } private Node showErrorNoDatabasePath() { - return new ErrorStateComponent( - Localization.lang("Unable to generate summary"), - Localization.lang("The path of the current library is not set, but it is required for summarization") - ); + return new ErrorStateComponent(Localization.lang("Unable to generate summary"), + Localization.lang("The path of the current library is not set, but it is required for summarization")); } private Node showErrorNotPdfs() { - return new ErrorStateComponent( - Localization.lang("Unable to generate summary"), - Localization.lang("Only PDF files are supported.") - ); + return new ErrorStateComponent(Localization.lang("Unable to generate summary"), + Localization.lang("Only PDF files are supported.")); } private Node showErrorNoFiles() { - return new ErrorStateComponent( - Localization.lang("Unable to generate summary"), - Localization.lang("Please attach at least one PDF file to enable summarization of PDF file(s).") - ); + return new ErrorStateComponent(Localization.lang("Unable to generate summary"), + Localization.lang("Please attach at least one PDF file to enable summarization of PDF file(s).")); } private Node tryToGenerateCitationKeyThenBind(BibEntry entry) { if (citationKeyGenerator.generateAndSetKey(entry).isEmpty()) { - return new ErrorStateComponent( - Localization.lang("Unable to generate summary"), - Localization.lang("Please provide a non-empty and unique citation key for this entry.") - ); - } else { + return new ErrorStateComponent(Localization.lang("Unable to generate summary"), + Localization.lang("Please provide a non-empty and unique citation key for this entry.")); + } + else { return showPrivacyPolicyGuardedContent(); } } private Node tryToShowSummary() { - ProcessingInfo processingInfo = aiService.getSummariesService().summarize(entry, bibDatabaseContext); + ProcessingInfo processingInfo = aiService.getSummariesService() + .summarize(entry, bibDatabaseContext); return switch (processingInfo.getState()) { case SUCCESS -> { - assert processingInfo.getData().isPresent(); // When the state is SUCCESS, the data must be present. + assert processingInfo.getData().isPresent(); // When the state is SUCCESS, + // the data must be present. yield showSummary(processingInfo.getData().get()); } - case ERROR -> - showErrorWhileSummarizing(processingInfo); - case PROCESSING, - STOPPED -> - showErrorNotSummarized(); + case ERROR -> showErrorWhileSummarizing(processingInfo); + case PROCESSING, STOPPED -> showErrorNotSummarized(); }; } private Node showErrorWhileSummarizing(ProcessingInfo processingInfo) { - assert processingInfo.getException().isPresent(); // When the state is ERROR, the exception must be present. + assert processingInfo.getException().isPresent(); // When the state is ERROR, the + // exception must be present. - LOGGER.error("Got an error while generating a summary for entry {}", entry.getCitationKey().orElse(""), processingInfo.getException().get()); + LOGGER.error("Got an error while generating a summary for entry {}", + entry.getCitationKey().orElse(""), processingInfo.getException().get()); - return ErrorStateComponent.withTextAreaAndButton( - Localization.lang("Unable to chat"), + return ErrorStateComponent.withTextAreaAndButton(Localization.lang("Unable to chat"), Localization.lang("Got error while processing the file:"), - processingInfo.getException().get().getLocalizedMessage(), - Localization.lang("Regenerate"), - () -> aiService.getSummariesService().regenerateSummary(entry, bibDatabaseContext) - ); + processingInfo.getException().get().getLocalizedMessage(), Localization.lang("Regenerate"), + () -> aiService.getSummariesService().regenerateSummary(entry, bibDatabaseContext)); } private Node showErrorNotSummarized() { - return ErrorStateComponent.withSpinner( - Localization.lang("Processing..."), - Localization.lang("The attached file(s) are currently being processed by %0. Once completed, you will be able to see the summary.", aiPreferences.getSelectedChatModel()) - ); + return ErrorStateComponent.withSpinner(Localization.lang("Processing..."), Localization.lang( + "The attached file(s) are currently being processed by %0. Once completed, you will be able to see the summary.", + aiPreferences.getSelectedChatModel())); } private Node showSummary(Summary summary) { return new SummaryShowingComponent(summary, () -> { if (bibDatabaseContext.getDatabasePath().isEmpty()) { - LOGGER.error("Bib database path is not set, but it was expected to be present. Unable to regenerate summary"); + LOGGER.error( + "Bib database path is not set, but it was expected to be present. Unable to regenerate summary"); return; } if (entry.getCitationKey().isEmpty()) { - LOGGER.error("Citation key is not set, but it was expected to be present. Unable to regenerate summary"); + LOGGER + .error("Citation key is not set, but it was expected to be present. Unable to regenerate summary"); return; } aiService.getSummariesService().regenerateSummary(entry, bibDatabaseContext); - // No need to rebuildUi(), because this class listens to the state of ProcessingInfo of the summary. + // No need to rebuildUi(), because this class listens to the state of + // ProcessingInfo of the summary. }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/summary/SummaryShowingComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/summary/SummaryShowingComponent.java index 74386d50cd1..524788ac207 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/summary/SummaryShowingComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/summary/SummaryShowingComponent.java @@ -19,21 +19,26 @@ import com.airhacks.afterburner.views.ViewLoader; public class SummaryShowingComponent extends VBox { + private static final MarkdownFormatter MARKDOWN_FORMATTER = new MarkdownFormatter(); - @FXML private Text summaryInfoText; - @FXML private CheckBox markdownCheckbox; + + @FXML + private Text summaryInfoText; + + @FXML + private CheckBox markdownCheckbox; private WebView contentWebView; + private final Summary summary; + private final Runnable regenerateCallback; public SummaryShowingComponent(Summary summary, Runnable regenerateCallback) { this.summary = summary; this.regenerateCallback = regenerateCallback; - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @FXML @@ -54,26 +59,25 @@ private void updateContent(boolean isMarkdown) { String content = summary.content(); if (isMarkdown) { contentWebView.getEngine().loadContent(MARKDOWN_FORMATTER.format(content)); - } else { - contentWebView.getEngine().loadContent( - "" + - "
" + - content + - "
" - ); + } + else { + contentWebView.getEngine() + .loadContent("" + + "
" + content + + "
"); } } private void updateInfoText() { - String newInfo = summaryInfoText - .getText() - .replaceAll("%0", formatTimestamp(summary.timestamp())) - .replaceAll("%1", summary.aiProvider().getLabel() + " " + summary.model()); + String newInfo = summaryInfoText.getText() + .replaceAll("%0", formatTimestamp(summary.timestamp())) + .replaceAll("%1", summary.aiProvider().getLabel() + " " + summary.model()); summaryInfoText.setText(newInfo); } private static String formatTimestamp(LocalDateTime timestamp) { - return timestamp.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault())); + return timestamp + .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault())); } @FXML @@ -85,4 +89,5 @@ private void onMarkdownToggle() { private void onRegenerateButtonClick() { regenerateCallback.run(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/util/EmbeddingModelGuardedComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/util/EmbeddingModelGuardedComponent.java index 89426aabeb2..6c800959ec4 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/util/EmbeddingModelGuardedComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/util/EmbeddingModelGuardedComponent.java @@ -16,18 +16,17 @@ import com.google.common.eventbus.Subscribe; /** - * Class that has similar logic to {@link AiPrivacyNoticeGuardedComponent}. It extends from it, so that means, - * if a component needs embedding model, then it should also be guarded with accepting AI privacy policy. + * Class that has similar logic to {@link AiPrivacyNoticeGuardedComponent}. It extends + * from it, so that means, if a component needs embedding model, then it should also be + * guarded with accepting AI privacy policy. */ public abstract class EmbeddingModelGuardedComponent extends AiPrivacyNoticeGuardedComponent { + private final AiService aiService; - public EmbeddingModelGuardedComponent(AiService aiService, - AiPreferences aiPreferences, - ExternalApplicationsPreferences externalApplicationsPreferences, - DialogService dialogService, - AdaptVisibleTabs adaptVisibleTabs - ) { + public EmbeddingModelGuardedComponent(AiService aiService, AiPreferences aiPreferences, + ExternalApplicationsPreferences externalApplicationsPreferences, DialogService dialogService, + AdaptVisibleTabs adaptVisibleTabs) { super(aiPreferences, externalApplicationsPreferences, dialogService, adaptVisibleTabs); this.aiService = aiService; @@ -42,29 +41,26 @@ protected final Node showPrivacyPolicyGuardedContent() { if (!aiService.getEmbeddingModel().isPresent()) { if (aiService.getEmbeddingModel().hadErrorWhileBuildingModel()) { return showErrorWhileBuildingEmbeddingModel(); - } else { + } + else { return showBuildingEmbeddingModel(); } - } else { + } + else { return showEmbeddingModelGuardedContent(); } } private Node showErrorWhileBuildingEmbeddingModel() { - return ErrorStateComponent.withTextAreaAndButton( - Localization.lang("Unable to chat"), + return ErrorStateComponent.withTextAreaAndButton(Localization.lang("Unable to chat"), Localization.lang("An error occurred while building the embedding model"), - aiService.getEmbeddingModel().getErrorWhileBuildingModel(), - Localization.lang("Rebuild"), - () -> aiService.getEmbeddingModel().startRebuildingTask() - ); + aiService.getEmbeddingModel().getErrorWhileBuildingModel(), Localization.lang("Rebuild"), + () -> aiService.getEmbeddingModel().startRebuildingTask()); } public Node showBuildingEmbeddingModel() { - return ErrorStateComponent.withSpinner( - Localization.lang("Downloading..."), - Localization.lang("Downloading embedding model... Afterward, you will be able to chat with your files.") - ); + return ErrorStateComponent.withSpinner(Localization.lang("Downloading..."), Localization + .lang("Downloading embedding model... Afterward, you will be able to chat with your files.")); } @Subscribe @@ -76,4 +72,5 @@ public void listen(JabRefEmbeddingModel.EmbeddingModelBuiltEvent event) { public void listen(JabRefEmbeddingModel.EmbeddingModelBuildingErrorEvent event) { UiTaskExecutor.runInJavaFXThread(EmbeddingModelGuardedComponent.this::rebuildUi); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/util/Loadable.java b/jabgui/src/main/java/org/jabref/gui/ai/components/util/Loadable.java index e92b67ad240..a6efab8762d 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/util/Loadable.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/util/Loadable.java @@ -5,6 +5,7 @@ import javafx.scene.layout.StackPane; public class Loadable extends StackPane { + private boolean isInLoadingState = false; public void setLoading(boolean loading) { @@ -14,10 +15,12 @@ public void setLoading(boolean loading) { if (loading) { getChildren().add(new BorderPane(new ProgressIndicator())); - } else { + } + else { getChildren().removeLast(); } isInLoadingState = loading; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/util/errorstate/ErrorStateComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/util/errorstate/ErrorStateComponent.java index da640261ef7..549b55de96a 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/util/errorstate/ErrorStateComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/util/errorstate/ErrorStateComponent.java @@ -11,14 +11,18 @@ import com.airhacks.afterburner.views.ViewLoader; public class ErrorStateComponent extends BorderPane { - @FXML private Label titleText; - @FXML private Label contentText; - @FXML private VBox contentsVBox; + + @FXML + private Label titleText; + + @FXML + private Label contentText; + + @FXML + private VBox contentsVBox; public ErrorStateComponent(String title, String content) { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); setTitle(title); setContent(content); @@ -44,7 +48,8 @@ public static ErrorStateComponent withTextArea(String title, String content, Str return errorStateComponent; } - public static ErrorStateComponent withTextAreaAndButton(String title, String content, String textAreaContent, String buttonText, Runnable onClick) { + public static ErrorStateComponent withTextAreaAndButton(String title, String content, String textAreaContent, + String buttonText, Runnable onClick) { ErrorStateComponent errorStateComponent = ErrorStateComponent.withTextArea(title, content, textAreaContent); Button button = new Button(buttonText); @@ -70,4 +75,5 @@ public String getContent() { public void setContent(String content) { contentText.setText(content); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/util/notifications/Notification.java b/jabgui/src/main/java/org/jabref/gui/ai/components/util/notifications/Notification.java index c8123974a16..317c343d7e7 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/util/notifications/Notification.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/util/notifications/Notification.java @@ -1,12 +1,15 @@ package org.jabref.gui.ai.components.util.notifications; /** - * Record that is used to display errors and warnings in the AI chat. If you need global notifications, - * see {@link org.jabref.gui.DialogService#notify(String)}. + * Record that is used to display errors and warnings in the AI chat. If you need global + * notifications, see {@link org.jabref.gui.DialogService#notify(String)}. *

- * This type is used to represent errors for: no files in {@link org.jabref.model.entry.BibEntry}, files are processing, - * etc. This is made via notifications to support chat with groups: on one hand we need to be able to notify users - * about possible problems with entries (because that will affect LLM output), but on the other hand the user would - * like to chat with all available entries in the group, even if some of them are not valid. + * This type is used to represent errors for: no files in + * {@link org.jabref.model.entry.BibEntry}, files are processing, etc. This is made via + * notifications to support chat with groups: on one hand we need to be able to notify + * users about possible problems with entries (because that will affect LLM output), but + * on the other hand the user would like to chat with all available entries in the group, + * even if some of them are not valid. */ -public record Notification(String title, String message) { } +public record Notification(String title, String message) { +} diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/util/notifications/NotificationComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/util/notifications/NotificationComponent.java index a711641f949..8a2693ba0e9 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/util/notifications/NotificationComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/util/notifications/NotificationComponent.java @@ -6,11 +6,13 @@ import javafx.scene.text.Font; /** - * Component used to display {@link Notification} in AI chat. See the documentation of {@link Notification} for more - * details. + * Component used to display {@link Notification} in AI chat. See the documentation of + * {@link Notification} for more details. */ public class NotificationComponent extends VBox { + private final Label title = new Label("Title"); + private final Label message = new Label("Message"); public NotificationComponent() { @@ -30,4 +32,5 @@ public void setNotification(Notification notification) { title.setText(notification.title()); message.setText(notification.message()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/ai/components/util/notifications/NotificationsComponent.java b/jabgui/src/main/java/org/jabref/gui/ai/components/util/notifications/NotificationsComponent.java index 72ec5f271ce..5458109f153 100644 --- a/jabgui/src/main/java/org/jabref/gui/ai/components/util/notifications/NotificationsComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/ai/components/util/notifications/NotificationsComponent.java @@ -8,10 +8,11 @@ import javafx.scene.layout.VBox; /** - * A {@link ScrollPane} for displaying AI chat {@link Notification}s. See the documentation of {@link Notification} for - * more details. + * A {@link ScrollPane} for displaying AI chat {@link Notification}s. See the + * documentation of {@link Notification} for more details. */ public class NotificationsComponent extends ScrollPane { + private static final double SCROLL_PANE_MAX_HEIGHT = 300; private final VBox vBox = new VBox(10); @@ -28,4 +29,5 @@ private void fill(List notifications) { vBox.getChildren().clear(); notifications.stream().map(NotificationComponent::new).forEach(vBox.getChildren()::add); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/AppendPersonNamesStrategy.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/AppendPersonNamesStrategy.java index bae94851ca4..f9ef03b2af6 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/AppendPersonNamesStrategy.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/AppendPersonNamesStrategy.java @@ -3,8 +3,8 @@ public class AppendPersonNamesStrategy extends AppendWordsStrategy { /** - * true if the input should be split at a single white space instead of the usual delimiter " and " for names. - * Useful if the input consists of a list of last names. + * true if the input should be split at a single white space instead of the usual + * delimiter " and " for names. Useful if the input consists of a list of last names. */ private final boolean separationBySpace; @@ -20,8 +20,10 @@ public AppendPersonNamesStrategy(boolean separationBySpace) { public String getDelimiter() { if (this.separationBySpace) { return " "; - } else { + } + else { return " and "; } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/AppendWordsStrategy.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/AppendWordsStrategy.java index 1cc0563be0b..9294929ddc0 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/AppendWordsStrategy.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/AppendWordsStrategy.java @@ -19,8 +19,10 @@ private AutoCompletionInput determinePrefixAndReturnRemainder(String input, Stri String prefix = input.substring(0, index + delimiter.length()); String rest = input.substring(index + delimiter.length()); return new AutoCompletionInput(prefix, rest); - } else { + } + else { return new AutoCompletionInput("", input); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/AutoCompletePreferences.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/AutoCompletePreferences.java index 46265c15369..02ef90e1ccb 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/AutoCompletePreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/AutoCompletePreferences.java @@ -15,18 +15,21 @@ public class AutoCompletePreferences { public enum NameFormat { + LAST_FIRST, FIRST_LAST, BOTH + } private final BooleanProperty shouldAutoComplete; + private final ObjectProperty firstNameMode; + private final ObjectProperty nameFormat; + private final ObservableSet completeFields; - public AutoCompletePreferences(boolean shouldAutoComplete, - AutoCompleteFirstNameMode firstNameMode, - NameFormat nameFormat, - Set completeFields) { + public AutoCompletePreferences(boolean shouldAutoComplete, AutoCompleteFirstNameMode firstNameMode, + NameFormat nameFormat, Set completeFields) { this.shouldAutoComplete = new SimpleBooleanProperty(shouldAutoComplete); this.firstNameMode = new SimpleObjectProperty<>(firstNameMode); this.nameFormat = new SimpleObjectProperty<>(nameFormat); @@ -74,10 +77,10 @@ public void setNameFormat(NameFormat nameFormat) { /** * Returns the list of fields for which autocomplete is enabled - * * @return List of field names */ public ObservableSet getCompleteFields() { return completeFields; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/AutoCompletionInput.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/AutoCompletionInput.java index e5ebbdbd34e..e20e82a9fb3 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/AutoCompletionInput.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/AutoCompletionInput.java @@ -1,7 +1,9 @@ package org.jabref.gui.autocompleter; public class AutoCompletionInput { + private String unfinishedPart; + private String prefix; public AutoCompletionInput(String prefix, String unfinishedPart) { @@ -16,4 +18,5 @@ public String getUnfinishedPart() { public String getPrefix() { return prefix; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/AutoCompletionStrategy.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/AutoCompletionStrategy.java index efde39c6561..4db0aae4a19 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/AutoCompletionStrategy.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/AutoCompletionStrategy.java @@ -1,5 +1,7 @@ package org.jabref.gui.autocompleter; public interface AutoCompletionStrategy { + AutoCompletionInput analyze(String input); + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/AutoCompletionTextInputBinding.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/AutoCompletionTextInputBinding.java index 89b7b520a62..8ab553c5cc7 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/AutoCompletionTextInputBinding.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/AutoCompletionTextInputBinding.java @@ -38,9 +38,10 @@ import org.controlsfx.control.textfield.AutoCompletionBinding; /** - * Represents a binding between a text input control and an auto-completion popup - * This class is a slightly modified version of {@link impl.org.controlsfx.autocompletion.AutoCompletionTextFieldBinding} - * that works with general text input controls instead of just text fields. + * Represents a binding between a text input control and an auto-completion popup This + * class is a slightly modified version of + * {@link impl.org.controlsfx.autocompletion.AutoCompletionTextFieldBinding} that works + * with general text input controls instead of just text fields. */ public class AutoCompletionTextInputBinding extends AutoCompletionBinding { @@ -48,46 +49,47 @@ public class AutoCompletionTextInputBinding extends AutoCompletionBinding * String converter to be used to convert suggestions to strings. */ private StringConverter converter; + private AutoCompletionStrategy inputAnalyzer; + private final ChangeListener textChangeListener = (obs, oldText, newText) -> { if (getCompletionTarget().isFocused()) { setUserInputText(newText); } }; + private boolean showOnFocus; + private final ChangeListener focusChangedListener = (obs, oldFocused, newFocused) -> { if (newFocused) { if (showOnFocus) { setUserInputText(getCompletionTarget().getText()); } - } else { + } + else { hidePopup(); } }; /** - * Creates a new auto-completion binding between the given textInputControl - * and the given suggestion provider. + * Creates a new auto-completion binding between the given textInputControl and the + * given suggestion provider. */ private AutoCompletionTextInputBinding(final TextInputControl textInputControl, - Callback> suggestionProvider) { + Callback> suggestionProvider) { - this(textInputControl, - suggestionProvider, - AutoCompletionTextInputBinding.defaultStringConverter(), + this(textInputControl, suggestionProvider, AutoCompletionTextInputBinding.defaultStringConverter(), new ReplaceStrategy()); } private AutoCompletionTextInputBinding(final TextInputControl textInputControl, - final Callback> suggestionProvider, - final StringConverter converter) { + final Callback> suggestionProvider, final StringConverter converter) { this(textInputControl, suggestionProvider, converter, new ReplaceStrategy()); } private AutoCompletionTextInputBinding(final TextInputControl textInputControl, - final Callback> suggestionProvider, - final StringConverter converter, - final AutoCompletionStrategy inputAnalyzer) { + final Callback> suggestionProvider, final StringConverter converter, + final AutoCompletionStrategy inputAnalyzer) { super(textInputControl, suggestionProvider, converter); this.converter = converter; @@ -112,20 +114,26 @@ public T fromString(String string) { }; } - public static void autoComplete(TextInputControl textArea, Callback> suggestionProvider) { + public static void autoComplete(TextInputControl textArea, + Callback> suggestionProvider) { new AutoCompletionTextInputBinding<>(textArea, suggestionProvider); } - public static void autoComplete(TextInputControl textArea, Callback> suggestionProvider, StringConverter converter) { + public static void autoComplete(TextInputControl textArea, + Callback> suggestionProvider, StringConverter converter) { new AutoCompletionTextInputBinding<>(textArea, suggestionProvider, converter); } - public static AutoCompletionTextInputBinding autoComplete(TextInputControl textArea, Callback> suggestionProvider, StringConverter converter, AutoCompletionStrategy inputAnalyzer) { + public static AutoCompletionTextInputBinding autoComplete(TextInputControl textArea, + Callback> suggestionProvider, StringConverter converter, + AutoCompletionStrategy inputAnalyzer) { return new AutoCompletionTextInputBinding<>(textArea, suggestionProvider, converter, inputAnalyzer); } - public static AutoCompletionTextInputBinding autoComplete(TextInputControl textArea, Callback> suggestionProvider, AutoCompletionStrategy inputAnalyzer) { - return autoComplete(textArea, suggestionProvider, AutoCompletionTextInputBinding.defaultStringConverter(), inputAnalyzer); + public static AutoCompletionTextInputBinding autoComplete(TextInputControl textArea, + Callback> suggestionProvider, AutoCompletionStrategy inputAnalyzer) { + return autoComplete(textArea, suggestionProvider, AutoCompletionTextInputBinding.defaultStringConverter(), + inputAnalyzer); } private void setUserInputText(String newText) { @@ -163,4 +171,5 @@ protected void completeUserInput(T completion) { public void setShowOnFocus(boolean showOnFocus) { this.showOnFocus = showOnFocus; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java index a69b8a3b1c1..9efe72910ec 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java @@ -13,7 +13,8 @@ import org.controlsfx.control.textfield.AutoCompletionBinding; /** - * Delivers possible completions as a list of {@link BibEntry} based on their citation key. + * Delivers possible completions as a list of {@link BibEntry} based on their citation + * key. */ public class BibEntrySuggestionProvider extends SuggestionProvider { @@ -36,13 +37,12 @@ protected Comparator getComparator() { @Override protected boolean isMatch(BibEntry entry, AutoCompletionBinding.ISuggestionRequest request) { String userText = request.getUserText(); - return entry.getCitationKey() - .map(key -> StringUtil.containsIgnoreCase(key, userText)) - .orElse(false); + return entry.getCitationKey().map(key -> StringUtil.containsIgnoreCase(key, userText)).orElse(false); } @Override public Stream getSource() { return database.getEntries().parallelStream(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/ContentSelectorSuggestionProvider.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/ContentSelectorSuggestionProvider.java index ebb6ec04837..4932e0c1384 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/ContentSelectorSuggestionProvider.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/ContentSelectorSuggestionProvider.java @@ -10,10 +10,11 @@ public class ContentSelectorSuggestionProvider extends StringSuggestionProvider { private final SuggestionProvider suggestionProvider; + private final List contentSelectorValues; public ContentSelectorSuggestionProvider(SuggestionProvider suggestionProvider, - List contentSelectorValues) { + List contentSelectorValues) { this.suggestionProvider = suggestionProvider; this.contentSelectorValues = contentSelectorValues; @@ -33,4 +34,5 @@ public List getPossibleSuggestions() { suggestions.addAll(contentSelectorValues); return suggestions; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/EmptySuggestionProvider.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/EmptySuggestionProvider.java index ab7df191fb5..b1b62569d3a 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/EmptySuggestionProvider.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/EmptySuggestionProvider.java @@ -7,6 +7,7 @@ import org.controlsfx.control.textfield.AutoCompletionBinding; public class EmptySuggestionProvider extends SuggestionProvider { + @Override protected Equivalence getEquivalence() { return Equivalence.equals().onResultOf(value -> value); @@ -26,4 +27,5 @@ protected boolean isMatch(String candidate, AutoCompletionBinding.ISuggestionReq public Stream getSource() { return Stream.empty(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/FieldValueSuggestionProvider.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/FieldValueSuggestionProvider.java index 1fae672e250..a788de06fe9 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/FieldValueSuggestionProvider.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/FieldValueSuggestionProvider.java @@ -12,6 +12,7 @@ class FieldValueSuggestionProvider extends StringSuggestionProvider { private final Field field; + private final BibDatabase database; FieldValueSuggestionProvider(Field field, BibDatabase database) { @@ -23,4 +24,5 @@ class FieldValueSuggestionProvider extends StringSuggestionProvider { public Stream getSource() { return database.getEntries().parallelStream().flatMap(entry -> entry.getField(field).stream()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/JournalsSuggestionProvider.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/JournalsSuggestionProvider.java index c0ece685519..aedc2c35e5f 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/JournalsSuggestionProvider.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/JournalsSuggestionProvider.java @@ -22,4 +22,5 @@ public class JournalsSuggestionProvider extends FieldValueSuggestionProvider { public Stream getSource() { return Streams.concat(super.getSource(), repository.getFullNames().stream()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/PersonNameStringConverter.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/PersonNameStringConverter.java index c5fc8cf8fcc..de7bff019e9 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/PersonNameStringConverter.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/PersonNameStringConverter.java @@ -9,10 +9,13 @@ public class PersonNameStringConverter extends StringConverter { private final boolean autoCompFF; + private final boolean autoCompLF; + private final AutoCompleteFirstNameMode autoCompleteFirstNameMode; - public PersonNameStringConverter(boolean autoCompFF, boolean autoCompLF, AutoCompleteFirstNameMode autoCompleteFirstNameMode) { + public PersonNameStringConverter(boolean autoCompFF, boolean autoCompLF, + AutoCompleteFirstNameMode autoCompleteFirstNameMode) { this.autoCompFF = autoCompFF; this.autoCompLF = autoCompLF; this.autoCompleteFirstNameMode = autoCompleteFirstNameMode; @@ -66,4 +69,5 @@ public String toString(Author author) { public Author fromString(String string) { return AuthorList.parse(string).getAuthor(0); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/PersonNameSuggestionProvider.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/PersonNameSuggestionProvider.java index bb8a49159e1..66e95257186 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/PersonNameSuggestionProvider.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/PersonNameSuggestionProvider.java @@ -23,6 +23,7 @@ public class PersonNameSuggestionProvider extends SuggestionProvider { private final Collection fields; + private final BibDatabase database; PersonNameSuggestionProvider(Field field, BibDatabase database) { @@ -38,12 +39,12 @@ public PersonNameSuggestionProvider(Collection fields, BibDatabase databa public Stream getAuthors(BibEntry entry) { return entry.getFieldMap() - .entrySet() - .stream() - .filter(fieldValuePair -> fields.contains(fieldValuePair.getKey())) - .map(Map.Entry::getValue) - .map(AuthorList::parse) - .flatMap(authors -> authors.getAuthors().stream()); + .entrySet() + .stream() + .filter(fieldValuePair -> fields.contains(fieldValuePair.getKey())) + .map(Map.Entry::getValue) + .map(AuthorList::parse) + .flatMap(authors -> authors.getAuthors().stream()); } @Override @@ -63,8 +64,7 @@ protected boolean isMatch(Author candidate, AutoCompletionBinding.ISuggestionReq @Override public Stream getSource() { - return database.getEntries() - .parallelStream() - .flatMap(this::getAuthors); + return database.getEntries().parallelStream().flatMap(this::getAuthors); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/ReplaceStrategy.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/ReplaceStrategy.java index 1ba900201b2..d79f47182e5 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/ReplaceStrategy.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/ReplaceStrategy.java @@ -6,4 +6,5 @@ public class ReplaceStrategy implements AutoCompletionStrategy { public AutoCompletionInput analyze(String input) { return new AutoCompletionInput("", input); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/StringSuggestionProvider.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/StringSuggestionProvider.java index 01ba19ef39e..829d575b494 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/StringSuggestionProvider.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/StringSuggestionProvider.java @@ -27,4 +27,5 @@ protected boolean isMatch(String candidate, AutoCompletionBinding.ISuggestionReq @Override public abstract Stream getSource(); + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/SuggestionProvider.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/SuggestionProvider.java index 14755e3a9db..e87f9201132 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/SuggestionProvider.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/SuggestionProvider.java @@ -47,13 +47,15 @@ public final Collection provideSuggestions(ISuggestionRequest request) { Comparator comparator = getComparator(); Equivalence equivalence = getEquivalence(); return getSource().filter(candidate -> isMatch(candidate, request)) - .map(equivalence::wrap) // Need to do a bit of acrobatic as there is no distinctBy method - .distinct() - .limit(10) - .map(Equivalence.Wrapper::get) - .sorted(comparator) - .collect(Collectors.toList()); - } else { + .map(equivalence::wrap) // Need to do a bit of acrobatic as there is no + // distinctBy method + .distinct() + .limit(10) + .map(Equivalence.Wrapper::get) + .sorted(comparator) + .collect(Collectors.toList()); + } + else { return List.of(); } } @@ -63,11 +65,12 @@ public final Collection provideSuggestions(ISuggestionRequest request) { public List getPossibleSuggestions() { Comparator comparator = getComparator().reversed(); Equivalence equivalence = getEquivalence(); - return getSource().map(equivalence::wrap) // Need to do a bit of acrobatic as there is no distinctBy method - .distinct() - .map(Equivalence.Wrapper::get) - .sorted(comparator) - .collect(Collectors.toList()); + return getSource().map(equivalence::wrap) // Need to do a bit of acrobatic as + // there is no distinctBy method + .distinct() + .map(Equivalence.Wrapper::get) + .sorted(comparator) + .collect(Collectors.toList()); } /** @@ -81,4 +84,5 @@ public List getPossibleSuggestions() { protected abstract boolean isMatch(T candidate, ISuggestionRequest request); public abstract Stream getSource(); + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java index 06d2abef8a8..f3c5cdcadf4 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java @@ -11,11 +11,15 @@ public class SuggestionProviders { private final boolean isEmpty; + private BibDatabase database; + private JournalAbbreviationRepository abbreviationRepository; + private AutoCompletePreferences autoCompletePreferences; - public SuggestionProviders(BibDatabase database, JournalAbbreviationRepository abbreviationRepository, AutoCompletePreferences autoCompletePreferences) { + public SuggestionProviders(BibDatabase database, JournalAbbreviationRepository abbreviationRepository, + AutoCompletePreferences autoCompletePreferences) { this.database = database; this.abbreviationRepository = abbreviationRepository; this.autoCompletePreferences = autoCompletePreferences; @@ -34,12 +38,17 @@ public SuggestionProvider getForField(Field field) { Set fieldProperties = field.getProperties(); if (fieldProperties.contains(FieldProperty.PERSON_NAMES)) { return new PersonNameSuggestionProvider(field, database); - } else if (fieldProperties.contains(FieldProperty.SINGLE_ENTRY_LINK) || fieldProperties.contains(FieldProperty.MULTIPLE_ENTRY_LINK)) { + } + else if (fieldProperties.contains(FieldProperty.SINGLE_ENTRY_LINK) + || fieldProperties.contains(FieldProperty.MULTIPLE_ENTRY_LINK)) { return new BibEntrySuggestionProvider(database); - } else if (fieldProperties.contains(FieldProperty.JOURNAL_NAME) || StandardField.PUBLISHER == field) { + } + else if (fieldProperties.contains(FieldProperty.JOURNAL_NAME) || StandardField.PUBLISHER == field) { return new JournalsSuggestionProvider(field, database, abbreviationRepository); - } else { + } + else { return new WordSuggestionProvider(field, database); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/autocompleter/WordSuggestionProvider.java b/jabgui/src/main/java/org/jabref/gui/autocompleter/WordSuggestionProvider.java index f83d047b0f7..3180591dca8 100644 --- a/jabgui/src/main/java/org/jabref/gui/autocompleter/WordSuggestionProvider.java +++ b/jabgui/src/main/java/org/jabref/gui/autocompleter/WordSuggestionProvider.java @@ -12,6 +12,7 @@ public class WordSuggestionProvider extends StringSuggestionProvider { private final Field field; + private final BibDatabase database; public WordSuggestionProvider(Field field, BibDatabase database) { @@ -21,8 +22,7 @@ public WordSuggestionProvider(Field field, BibDatabase database) { @Override public Stream getSource() { - return database.getEntries() - .parallelStream() - .flatMap(entry -> entry.getFieldAsWords(field).stream()); + return database.getEntries().parallelStream().flatMap(entry -> entry.getFieldAsWords(field).stream()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java b/jabgui/src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java index 95b7c9b3ffe..9e0cbca6787 100644 --- a/jabgui/src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java +++ b/jabgui/src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java @@ -16,9 +16,11 @@ import org.slf4j.LoggerFactory; /** - * Saves the given {@link BibDatabaseContext} on every {@link BibDatabaseContextChangedEvent} by posting a new {@link AutosaveEvent}. - * An intelligent {@link ScheduledThreadPoolExecutor} prevents a high load while saving and rejects all redundant save tasks. - * The scheduled action is stored and canceled if a newer save action is proposed. + * Saves the given {@link BibDatabaseContext} on every + * {@link BibDatabaseContextChangedEvent} by posting a new {@link AutosaveEvent}. An + * intelligent {@link ScheduledThreadPoolExecutor} prevents a high load while saving and + * rejects all redundant save tasks. The scheduled action is stored and canceled if a + * newer save action is proposed. */ public class AutosaveManager { @@ -31,8 +33,11 @@ public class AutosaveManager { private final BibDatabaseContext bibDatabaseContext; private final EventBus eventBus; + private final ScheduledThreadPoolExecutor executor; + private final CoarseChangeFilter coarseChangeFilter; + private boolean needsSave = false; private AutosaveManager(BibDatabaseContext bibDatabaseContext, CoarseChangeFilter coarseChangeFilter) { @@ -41,16 +46,12 @@ private AutosaveManager(BibDatabaseContext bibDatabaseContext, CoarseChangeFilte this.eventBus = new EventBus(); this.executor = new ScheduledThreadPoolExecutor(2); - this.executor.scheduleAtFixedRate( - () -> { - if (needsSave) { - eventBus.post(new AutosaveEvent()); - needsSave = false; - } - }, - DELAY_BETWEEN_AUTOSAVE_ATTEMPTS_IN_SECONDS, - DELAY_BETWEEN_AUTOSAVE_ATTEMPTS_IN_SECONDS, - TimeUnit.SECONDS); + this.executor.scheduleAtFixedRate(() -> { + if (needsSave) { + eventBus.post(new AutosaveEvent()); + needsSave = false; + } + }, DELAY_BETWEEN_AUTOSAVE_ATTEMPTS_IN_SECONDS, DELAY_BETWEEN_AUTOSAVE_ATTEMPTS_IN_SECONDS, TimeUnit.SECONDS); } @Subscribe @@ -67,7 +68,6 @@ private void shutdown() { /** * Starts the Autosaver which is associated with the given {@link BibDatabaseContext}. - * * @param bibDatabaseContext Associated {@link BibDatabaseContext} */ public static AutosaveManager start(BibDatabaseContext bibDatabaseContext, CoarseChangeFilter coarseChangeFilter) { @@ -77,13 +77,15 @@ public static AutosaveManager start(BibDatabaseContext bibDatabaseContext, Coars } /** - * Shuts down the Autosaver which is associated with the given {@link BibDatabaseContext}. - * + * Shuts down the Autosaver which is associated with the given + * {@link BibDatabaseContext}. * @param bibDatabaseContext Associated {@link BibDatabaseContext} */ public static void shutdown(BibDatabaseContext bibDatabaseContext) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).findAny() - .ifPresent(instance -> instance.shutdown()); + runningInstances.stream() + .filter(instance -> instance.bibDatabaseContext == bibDatabaseContext) + .findAny() + .ifPresent(instance -> instance.shutdown()); } public void registerListener(Object listener) { @@ -93,9 +95,12 @@ public void registerListener(Object listener) { public void unregisterListener(Object listener) { try { eventBus.unregister(listener); - } catch (IllegalArgumentException e) { - // occurs if the event source has not been registered, should not prevent shutdown + } + catch (IllegalArgumentException e) { + // occurs if the event source has not been registered, should not prevent + // shutdown LOGGER.error("Problem unregistering", e); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java b/jabgui/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java index 6477c85aca2..4aa31fcb992 100644 --- a/jabgui/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java +++ b/jabgui/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java @@ -47,10 +47,11 @@ import org.slf4j.LoggerFactory; /** - * Backups the given bib database file from {@link BibDatabaseContext} on every {@link BibDatabaseContextChangedEvent}. - * An intelligent {@link ExecutorService} with a {@link BlockingQueue} prevents a high load while making backups and - * rejects all redundant backup tasks. This class does not manage the .bak file which is created when opening a - * database. + * Backups the given bib database file from {@link BibDatabaseContext} on every + * {@link BibDatabaseContextChangedEvent}. An intelligent {@link ExecutorService} with a + * {@link BlockingQueue} prevents a high load while making backups and rejects all + * redundant backup tasks. This class does not manage the .bak file which is created when + * opening a database. */ public class BackupManager { @@ -63,18 +64,25 @@ public class BackupManager { private static final Set RUNNING_INSTANCES = new HashSet<>(); private final BibDatabaseContext bibDatabaseContext; + private final CoarseChangeFilter coarseChangeFilter; + private final CliPreferences preferences; + private final ScheduledThreadPoolExecutor executor; + private final BibEntryTypesManager entryTypesManager; + private final LibraryTab libraryTab; // Contains a list of all backup paths // During writing, the less recent backup file is deleted private final Queue backupFilesQueue = new LinkedBlockingQueue<>(); + private boolean needsBackup = false; - BackupManager(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, CoarseChangeFilter coarseChangeFilter, BibEntryTypesManager entryTypesManager, CliPreferences preferences) { + BackupManager(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, CoarseChangeFilter coarseChangeFilter, + BibEntryTypesManager entryTypesManager, CliPreferences preferences) { this.bibDatabaseContext = bibDatabaseContext; this.coarseChangeFilter = coarseChangeFilter; this.entryTypesManager = entryTypesManager; @@ -98,15 +106,18 @@ static Optional getLatestBackupPath(Path originalPath, Path backupDir) { } /** - * Starts the BackupManager which is associated with the given {@link BibDatabaseContext}. As long as no database - * file is present in {@link BibDatabaseContext}, the {@link BackupManager} will do nothing. + * Starts the BackupManager which is associated with the given + * {@link BibDatabaseContext}. As long as no database file is present in + * {@link BibDatabaseContext}, the {@link BackupManager} will do nothing. *

- * This method is not thread-safe. The caller has to ensure that this method is not called in parallel. - * + * This method is not thread-safe. The caller has to ensure that this method is not + * called in parallel. * @param bibDatabaseContext Associated {@link BibDatabaseContext} */ - public static BackupManager start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, CoarseChangeFilter coarseChangeFilter, BibEntryTypesManager entryTypesManager, CliPreferences preferences) { - BackupManager backupManager = new BackupManager(libraryTab, bibDatabaseContext, coarseChangeFilter, entryTypesManager, preferences); + public static BackupManager start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, + CoarseChangeFilter coarseChangeFilter, BibEntryTypesManager entryTypesManager, CliPreferences preferences) { + BackupManager backupManager = new BackupManager(libraryTab, bibDatabaseContext, coarseChangeFilter, + entryTypesManager, preferences); backupManager.startBackupTask(preferences.getFilePreferences().getBackupDirectory()); coarseChangeFilter.registerListener(backupManager); RUNNING_INSTANCES.add(backupManager); @@ -114,44 +125,50 @@ public static BackupManager start(LibraryTab libraryTab, BibDatabaseContext bibD } /** - * Marks the backup as discarded at the library which is associated with the given {@link BibDatabaseContext}. - * + * Marks the backup as discarded at the library which is associated with the given + * {@link BibDatabaseContext}. * @param bibDatabaseContext Associated {@link BibDatabaseContext} */ public static void discardBackup(BibDatabaseContext bibDatabaseContext, Path backupDir) { - RUNNING_INSTANCES.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.discardBackup(backupDir)); + RUNNING_INSTANCES.stream() + .filter(instance -> instance.bibDatabaseContext == bibDatabaseContext) + .forEach(backupManager -> backupManager.discardBackup(backupDir)); } /** - * Shuts down the BackupManager which is associated with the given {@link BibDatabaseContext}. - * + * Shuts down the BackupManager which is associated with the given + * {@link BibDatabaseContext}. * @param bibDatabaseContext Associated {@link BibDatabaseContext} - * @param backupDir The path to the backup directory - * @param createBackup True, if a backup should be created + * @param backupDir The path to the backup directory + * @param createBackup True, if a backup should be created */ public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup) { - RUNNING_INSTANCES.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdown(backupDir, createBackup)); + RUNNING_INSTANCES.stream() + .filter(instance -> instance.bibDatabaseContext == bibDatabaseContext) + .forEach(backupManager -> backupManager.shutdown(backupDir, createBackup)); RUNNING_INSTANCES.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); } /** - * Checks whether a backup file exists for the given database file. If it exists, it is checked whether it is - * newer and different from the original. - * - * In case a discarded file is present, the method also returns false, See also {@link #discardBackup(Path)}. + * Checks whether a backup file exists for the given database file. If it exists, it + * is checked whether it is newer and different from the original. * - * @param originalPath Path to the file a backup should be checked for. Example: jabref.bib. - * - * @return true if backup file exists AND differs from originalPath. false is the - * "default" return value in the good case. In case a discarded file exists, false is returned, too. - * In the case of an exception true is returned to ensure that the user checks the output. + * In case a discarded file is present, the method also returns false, + * See also {@link #discardBackup(Path)}. + * @param originalPath Path to the file a backup should be checked for. Example: + * jabref.bib. + * @return true if backup file exists AND differs from originalPath. + * false is the "default" return value in the good case. In case a + * discarded file exists, false is returned, too. In the case of an + * exception true is returned to ensure that the user checks the output. */ public static boolean backupFileDiffers(Path originalPath, Path backupDir) { Path discardedFile = determineDiscardedFile(originalPath, backupDir); if (Files.exists(discardedFile)) { try { Files.delete(discardedFile); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Could not remove discarded file {}", discardedFile, e); return true; } @@ -161,7 +178,8 @@ public static boolean backupFileDiffers(Path originalPath, Path backupDir) { FileTime latestBackupFileLastModifiedTime; try { latestBackupFileLastModifiedTime = Files.getLastModifiedTime(latestBackupPath); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.debug("Could not get timestamp of backup file {}", latestBackupPath, e); // If we cannot get the timestamp, we do show any warning return false; @@ -169,7 +187,8 @@ public static boolean backupFileDiffers(Path originalPath, Path backupDir) { FileTime currentFileLastModifiedTime; try { currentFileLastModifiedTime = Files.getLastModifiedTime(originalPath); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.debug("Could not get timestamp of current file file {}", originalPath, e); // If we cannot get the timestamp, we do show any warning return false; @@ -185,7 +204,8 @@ public static boolean backupFileDiffers(Path originalPath, Path backupDir) { LOGGER.info("Backup file {} differs from current file {}", latestBackupPath, originalPath); } return result; - } catch (IOException e) { + } + catch (IOException e) { LOGGER.debug("Could not compare original file and backup file.", e); // User has to investigate in this case return true; @@ -195,7 +215,6 @@ public static boolean backupFileDiffers(Path originalPath, Path backupDir) { /** * Restores the backup file by copying and overwriting the original one. - * * @param originalPath Path to the file which should be equalized to the backup file. */ public static void restoreBackup(Path originalPath, Path backupDir) { @@ -206,21 +225,23 @@ public static void restoreBackup(Path originalPath, Path backupDir) { } try { Files.copy(backupPath.get(), originalPath, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Error while restoring the backup file.", e); } } Optional determineBackupPathForNewBackup(Path backupDir) { - return bibDatabaseContext.getDatabasePath().map(path -> BackupManager.getBackupPathForNewBackup(path, backupDir)); + return bibDatabaseContext.getDatabasePath() + .map(path -> BackupManager.getBackupPathForNewBackup(path, backupDir)); } /** * This method is called as soon as the scheduler says: "Do the backup" * * SIDE EFFECT: Deletes oldest backup file - * - * @param backupPath the full path to the file where the library should be backed up to + * @param backupPath the full path to the file where the library should be backed up + * to */ void performBackup(Path backupPath) { if (!needsBackup) { @@ -232,67 +253,69 @@ void performBackup(Path backupPath) { Path oldestBackupFile = backupFilesQueue.poll(); try { Files.delete(oldestBackupFile); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Could not delete backup file {}", oldestBackupFile, e); } } // code similar to org.jabref.gui.exporter.SaveDatabaseAction.saveDatabase - SelfContainedSaveOrder saveOrder = bibDatabaseContext - .getMetaData().getSaveOrder() - .map(so -> { - if (so.getOrderType() == SaveOrder.OrderType.TABLE) { - // We need to "flatten out" SaveOrder.OrderType.TABLE as BibWriter does not have access to preferences - List> sortOrder = libraryTab.getMainTable().getSortOrder(); - return new SelfContainedSaveOrder( - SaveOrder.OrderType.SPECIFIED, - sortOrder.stream() - .filter(col -> col instanceof MainTableColumn) - .map(column -> ((MainTableColumn) column).getModel()) - .flatMap(model -> model.getSortCriteria().stream()) - .toList()); - } else { - return SelfContainedSaveOrder.of(so); - } - }) - .orElse(SaveOrder.getDefaultSaveOrder()); + SelfContainedSaveOrder saveOrder = bibDatabaseContext.getMetaData().getSaveOrder().map(so -> { + if (so.getOrderType() == SaveOrder.OrderType.TABLE) { + // We need to "flatten out" SaveOrder.OrderType.TABLE as BibWriter does + // not have access to preferences + List> sortOrder = libraryTab.getMainTable().getSortOrder(); + return new SelfContainedSaveOrder(SaveOrder.OrderType.SPECIFIED, + sortOrder.stream() + .filter(col -> col instanceof MainTableColumn) + .map(column -> ((MainTableColumn) column).getModel()) + .flatMap(model -> model.getSortCriteria().stream()) + .toList()); + } + else { + return SelfContainedSaveOrder.of(so); + } + }).orElse(SaveOrder.getDefaultSaveOrder()); SelfContainedSaveConfiguration saveConfiguration = (SelfContainedSaveConfiguration) new SelfContainedSaveConfiguration() - .withMakeBackup(false) - .withSaveOrder(saveOrder) - .withReformatOnSave(preferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); + .withMakeBackup(false) + .withSaveOrder(saveOrder) + .withReformatOnSave(preferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); // "Clone" the database context - // We "know" that "only" the BibEntries might be changed during writing (see [org.jabref.logic.exporter.BibDatabaseWriter.savePartOfDatabase]) - List list = bibDatabaseContext.getDatabase().getEntries().stream() - .map(BibEntry::new) - .toList(); + // We "know" that "only" the BibEntries might be changed during writing (see + // [org.jabref.logic.exporter.BibDatabaseWriter.savePartOfDatabase]) + List list = bibDatabaseContext.getDatabase().getEntries().stream().map(BibEntry::new).toList(); BibDatabase bibDatabaseClone = new BibDatabase(list); - bibDatabaseContext.getDatabase().getStringValues().stream().map(BibtexString::clone) - .map(BibtexString.class::cast) - .forEach(bibDatabaseClone::addString); - BibDatabaseContext bibDatabaseContextClone = new BibDatabaseContext(bibDatabaseClone, bibDatabaseContext.getMetaData()); + bibDatabaseContext.getDatabase() + .getStringValues() + .stream() + .map(BibtexString::clone) + .map(BibtexString.class::cast) + .forEach(bibDatabaseClone::addString); + BibDatabaseContext bibDatabaseContextClone = new BibDatabaseContext(bibDatabaseClone, + bibDatabaseContext.getMetaData()); Charset encoding = bibDatabaseContext.getMetaData().getEncoding().orElse(StandardCharsets.UTF_8); // We want to have successful backups only // Thus, we do not use a plain "FileWriter", but the "AtomicFileWriter" - // Example: What happens if one hard powers off the machine (or kills the jabref process) during writing of the backup? - // This MUST NOT create a broken backup file that then jabref wants to "restore" from? + // Example: What happens if one hard powers off the machine (or kills the jabref + // process) during writing of the backup? + // This MUST NOT create a broken backup file that then jabref wants to "restore" + // from? try (Writer writer = new AtomicFileWriter(backupPath, encoding, false)) { BibWriter bibWriter = new BibWriter(writer, bibDatabaseContext.getDatabase().getNewLineSeparator()); - new BibDatabaseWriter( - bibWriter, - saveConfiguration, - preferences.getFieldPreferences(), - preferences.getCitationKeyPatternPreferences(), - entryTypesManager) - // we save the clone to prevent the original database (and thus the UI) from being changed - .saveDatabase(bibDatabaseContextClone); + new BibDatabaseWriter(bibWriter, saveConfiguration, preferences.getFieldPreferences(), + preferences.getCitationKeyPatternPreferences(), entryTypesManager) + // we save the clone to prevent the original database (and thus the UI) + // from being changed + .saveDatabase(bibDatabaseContextClone); backupFilesQueue.add(backupPath); // We wrote the file successfully // Thus, we currently do not need any new backup this.needsBackup = false; - } catch (IOException e) { + } + catch (IOException e) { logIfCritical(backupPath, e); } } @@ -304,14 +327,15 @@ private static Path determineDiscardedFile(Path file, Path backupDir) { /** * Marks the backups as discarded. * - * We do not delete any files, because the user might want to recover old backup files. - * Therefore, we mark discarded backups by a --discarded file. + * We do not delete any files, because the user might want to recover old backup + * files. Therefore, we mark discarded backups by a --discarded file. */ public void discardBackup(Path backupDir) { Path path = determineDiscardedFile(bibDatabaseContext.getDatabasePath().get(), backupDir); try { Files.createFile(path); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.info("Could not create backup file {}", path, e); } } @@ -340,11 +364,10 @@ private void startBackupTask(Path backupDir) { fillQueue(backupDir); executor.scheduleAtFixedRate( - // We need to determine the backup path on each action, because we use the timestamp in the filename - () -> determineBackupPathForNewBackup(backupDir).ifPresent(this::performBackup), - DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, - DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, - TimeUnit.SECONDS); + // We need to determine the backup path on each action, because we use the + // timestamp in the filename + () -> determineBackupPathForNewBackup(backupDir).ifPresent(this::performBackup), + DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, TimeUnit.SECONDS); } private void fillQueue(Path backupDir) { @@ -352,24 +375,26 @@ private void fillQueue(Path backupDir) { return; } bibDatabaseContext.getDatabasePath().ifPresent(databasePath -> { - // code similar to {@link org.jabref.logic.util.io.BackupFileUtil.getPathOfLatestExisingBackupFile} + // code similar to {@link + // org.jabref.logic.util.io.BackupFileUtil.getPathOfLatestExisingBackupFile} final String prefix = BackupFileUtil.getUniqueFilePrefix(databasePath) + "--" + databasePath.getFileName(); try { List allSavFiles = Files.list(backupDir) - // just list the .sav belonging to the given targetFile - .filter(p -> p.getFileName().toString().startsWith(prefix)) - .sorted().toList(); + // just list the .sav belonging to the given targetFile + .filter(p -> p.getFileName().toString().startsWith(prefix)) + .sorted() + .toList(); backupFilesQueue.addAll(allSavFiles); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Could not determine most recent file", e); } }); } /** - * Unregisters the BackupManager from the eventBus of {@link BibDatabaseContext}. - * This method should only be used when closing a database/JabRef in a normal way. - * + * Unregisters the BackupManager from the eventBus of {@link BibDatabaseContext}. This + * method should only be used when closing a database/JabRef in a normal way. * @param backupDir The backup directory * @param createBackup If the backup manager should still perform a backup */ @@ -382,4 +407,5 @@ private void shutdown(Path backupDir, boolean createBackup) { determineBackupPathForNewBackup(backupDir).ifPresent(this::performBackup); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java b/jabgui/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java index f60123a2b94..a72f465ed7a 100644 --- a/jabgui/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java @@ -26,27 +26,43 @@ * A wizard dialog for generating a new sub database from existing TeX AUX file */ public class FromAuxDialog extends BaseDialog { - @FXML private ButtonType generateButtonType; - @FXML private TextField auxFileField; - @FXML private ListView notFoundList; - @FXML private TextArea statusInfos; - @FXML private ComboBox libraryListView; - @Inject private CliPreferences preferences; - @Inject private DialogService dialogService; - @Inject private ThemeManager themeManager; - @Inject private StateManager stateManager; + @FXML + private ButtonType generateButtonType; + + @FXML + private TextField auxFileField; + + @FXML + private ListView notFoundList; + + @FXML + private TextArea statusInfos; + + @FXML + private ComboBox libraryListView; + + @Inject + private CliPreferences preferences; + + @Inject + private DialogService dialogService; + + @Inject + private ThemeManager themeManager; + + @Inject + private StateManager stateManager; private final LibraryTabContainer tabContainer; + private FromAuxDialogViewModel viewModel; public FromAuxDialog(LibraryTabContainer tabContainer) { this.tabContainer = tabContainer; this.setTitle(Localization.lang("AUX file import")); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); Button generateButton = (Button) this.getDialogPane().lookupButton(generateButtonType); generateButton.disableProperty().bind(viewModel.parseFailedProperty()); @@ -72,10 +88,10 @@ private void initialize() { libraryListView.setEditable(false); libraryListView.itemsProperty().bind(viewModel.librariesProperty()); libraryListView.valueProperty().bindBidirectional(viewModel.selectedLibraryProperty()); - new ViewModelListCellFactory() - .withText(viewModel::getDatabaseName) - .install(libraryListView); - EasyBind.listen(libraryListView.getSelectionModel().selectedItemProperty(), (obs, oldValue, newValue) -> parseActionPerformed()); + new ViewModelListCellFactory().withText(viewModel::getDatabaseName) + .install(libraryListView); + EasyBind.listen(libraryListView.getSelectionModel().selectedItemProperty(), + (obs, oldValue, newValue) -> parseActionPerformed()); } @FXML @@ -87,4 +103,5 @@ private void parseActionPerformed() { private void browseButtonClicked() { viewModel.browse(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/auximport/FromAuxDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/auximport/FromAuxDialogViewModel.java index 27bf4831bcf..1ff5f396e29 100644 --- a/jabgui/src/main/java/org/jabref/gui/auximport/FromAuxDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/auximport/FromAuxDialogViewModel.java @@ -35,23 +35,30 @@ public class FromAuxDialogViewModel { private final BooleanProperty parseFailedProperty = new SimpleBooleanProperty(false); + private final StringProperty auxFileProperty = new SimpleStringProperty(); + private final StringProperty statusTextProperty = new SimpleStringProperty(); + private final ListProperty notFoundList = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ListProperty librariesProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + + private final ListProperty librariesProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); + private final ObjectProperty selectedLibraryProperty = new SimpleObjectProperty<>(); private final LibraryTabContainer tabContainer; + private final DialogService dialogService; + private final CliPreferences preferences; + private final StateManager stateManager; private AuxParserResult auxParserResult; - public FromAuxDialogViewModel(LibraryTabContainer tabContainer, - DialogService dialogService, - CliPreferences preferences, - StateManager stateManager) { + public FromAuxDialogViewModel(LibraryTabContainer tabContainer, DialogService dialogService, + CliPreferences preferences, StateManager stateManager) { this.tabContainer = tabContainer; this.dialogService = dialogService; this.preferences = preferences; @@ -69,7 +76,8 @@ public FromAuxDialogViewModel(LibraryTabContainer tabContainer, public String getDatabaseName(BibDatabaseContext databaseContext) { Optional dbOpt = Optional.empty(); if (databaseContext.getDatabasePath().isPresent()) { - dbOpt = FileUtil.getUniquePathFragment(stateManager.getAllDatabasePaths(), databaseContext.getDatabasePath().get()); + dbOpt = FileUtil.getUniquePathFragment(stateManager.getAllDatabasePaths(), + databaseContext.getDatabasePath().get()); } if (databaseContext.getLocation() == DatabaseLocation.SHARED) { return databaseContext.getDBMSSynchronizer().getDBName() + " [" + Localization.lang("shared") + "]"; @@ -80,10 +88,12 @@ public String getDatabaseName(BibDatabaseContext databaseContext) { public void browse() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.AUX) - .withDefaultExtension(StandardFileType.AUX) - .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()).build(); - dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(file -> auxFileProperty.setValue(file.toAbsolutePath().toString())); + .addExtensionFilter(StandardFileType.AUX) + .withDefaultExtension(StandardFileType.AUX) + .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) + .build(); + dialogService.showFileOpenDialog(fileDialogConfiguration) + .ifPresent(file -> auxFileProperty.setValue(file.toAbsolutePath().toString())); } public void parse() { @@ -104,7 +114,8 @@ public void parse() { statusTextProperty.set(statusTextProperty.get() + "\n" + Localization.lang("empty library")); parseFailedProperty.set(true); } - } else { + } + else { parseFailedProperty.set(true); } } @@ -137,4 +148,5 @@ public ReadOnlyListProperty librariesProperty() { public ObjectProperty selectedLibraryProperty() { return selectedLibraryProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/auximport/NewSubLibraryAction.java b/jabgui/src/main/java/org/jabref/gui/auximport/NewSubLibraryAction.java index 2af0171b5da..eab88809bf8 100644 --- a/jabgui/src/main/java/org/jabref/gui/auximport/NewSubLibraryAction.java +++ b/jabgui/src/main/java/org/jabref/gui/auximport/NewSubLibraryAction.java @@ -15,9 +15,11 @@ public class NewSubLibraryAction extends SimpleCommand { private final LibraryTabContainer tabContainer; + private final DialogService dialogService; - public NewSubLibraryAction(LibraryTabContainer tabContainer, StateManager stateManager, DialogService dialogService) { + public NewSubLibraryAction(LibraryTabContainer tabContainer, StateManager stateManager, + DialogService dialogService) { this.tabContainer = tabContainer; this.dialogService = dialogService; @@ -28,4 +30,5 @@ public NewSubLibraryAction(LibraryTabContainer tabContainer, StateManager stateM public void execute() { dialogService.showCustomDialogAndWait(new FromAuxDialog(tabContainer)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java b/jabgui/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java index cf35336765d..7397397b980 100644 --- a/jabgui/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java @@ -20,23 +20,36 @@ import org.slf4j.LoggerFactory; public class BackupResolverDialog extends FXDialog { - public static final ButtonType RESTORE_FROM_BACKUP = new ButtonType(Localization.lang("Restore from backup"), ButtonBar.ButtonData.OK_DONE); - public static final ButtonType REVIEW_BACKUP = new ButtonType(Localization.lang("Review backup"), ButtonBar.ButtonData.LEFT); - public static final ButtonType IGNORE_BACKUP = new ButtonType(Localization.lang("Ignore backup"), ButtonBar.ButtonData.CANCEL_CLOSE); + + public static final ButtonType RESTORE_FROM_BACKUP = new ButtonType(Localization.lang("Restore from backup"), + ButtonBar.ButtonData.OK_DONE); + + public static final ButtonType REVIEW_BACKUP = new ButtonType(Localization.lang("Review backup"), + ButtonBar.ButtonData.LEFT); + + public static final ButtonType IGNORE_BACKUP = new ButtonType(Localization.lang("Ignore backup"), + ButtonBar.ButtonData.CANCEL_CLOSE); private static final Logger LOGGER = LoggerFactory.getLogger(BackupResolverDialog.class); - public BackupResolverDialog(Path originalPath, Path backupDir, ExternalApplicationsPreferences externalApplicationsPreferences) { + public BackupResolverDialog(Path originalPath, Path backupDir, + ExternalApplicationsPreferences externalApplicationsPreferences) { super(AlertType.CONFIRMATION, Localization.lang("Backup found"), true); setHeaderText(null); getDialogPane().setMinHeight(180); getDialogPane().getButtonTypes().setAll(RESTORE_FROM_BACKUP, REVIEW_BACKUP, IGNORE_BACKUP); - Optional backupPathOpt = BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, backupDir); - String backupFilename = backupPathOpt.map(Path::getFileName).map(Path::toString).orElse(Localization.lang("File not found")); - String content = Localization.lang("A backup file for '%0' was found at [%1]", originalPath.getFileName().toString(), backupFilename) + "\n" + - Localization.lang("This could indicate that JabRef did not shut down cleanly last time the file was used.") + "\n\n" + - Localization.lang("Do you want to recover the library from the backup file?"); + Optional backupPathOpt = BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, + BackupFileType.BACKUP, backupDir); + String backupFilename = backupPathOpt.map(Path::getFileName) + .map(Path::toString) + .orElse(Localization.lang("File not found")); + String content = Localization.lang("A backup file for '%0' was found at [%1]", + originalPath.getFileName().toString(), backupFilename) + + "\n" + + Localization + .lang("This could indicate that JabRef did not shut down cleanly last time the file was used.") + + "\n\n" + Localization.lang("Do you want to recover the library from the backup file?"); setContentText(content); HyperlinkLabel contentLabel = new HyperlinkLabel(content); @@ -49,8 +62,10 @@ public BackupResolverDialog(Path originalPath, Path backupDir, ExternalApplicati String clickedLinkText = ((Hyperlink) (e.getSource())).getText(); if (backupFilename.equals(clickedLinkText)) { try { - NativeDesktop.openFolderAndSelectFile(backupPathOpt.get(), externalApplicationsPreferences, null); - } catch (IOException ex) { + NativeDesktop.openFolderAndSelectFile(backupPathOpt.get(), externalApplicationsPreferences, + null); + } + catch (IOException ex) { LOGGER.error("Could not open backup folder", ex); } } @@ -58,4 +73,5 @@ public BackupResolverDialog(Path originalPath, Path backupDir, ExternalApplicati }); getDialogPane().setContent(contentLabel); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java b/jabgui/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java index c204060d7d2..324f793b391 100644 --- a/jabgui/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java +++ b/jabgui/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java @@ -24,22 +24,23 @@ public class GenerateCitationKeyAction extends SimpleCommand { private final Supplier tabSupplier; + private final DialogService dialogService; + private final StateManager stateManager; private List entries; + private boolean isCanceled; private final TaskExecutor taskExecutor; + private final CliPreferences preferences; + private final UndoManager undoManager; - public GenerateCitationKeyAction(Supplier tabSupplier, - DialogService dialogService, - StateManager stateManager, - TaskExecutor taskExecutor, - CliPreferences preferences, - UndoManager undoManager) { + public GenerateCitationKeyAction(Supplier tabSupplier, DialogService dialogService, + StateManager stateManager, TaskExecutor taskExecutor, CliPreferences preferences, UndoManager undoManager) { this.tabSupplier = tabSupplier; this.dialogService = dialogService; this.stateManager = stateManager; @@ -75,25 +76,26 @@ public void execute() { public static boolean confirmOverwriteKeys(DialogService dialogService, CliPreferences preferences) { if (preferences.getCitationKeyPatternPreferences().shouldWarnBeforeOverwriteCiteKey()) { - return dialogService.showConfirmationDialogWithOptOutAndWait( - Localization.lang("Overwrite keys"), + 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("Overwrite keys"), Localization.lang("Cancel"), Localization.lang("Do not ask again"), optOut -> preferences.getCitationKeyPatternPreferences().setWarnBeforeOverwriteCiteKey(!optOut)); - } else { + } + else { // Always overwrite keys by default return true; } } private void checkOverwriteKeysChosen() { - // We don't want to generate keys for entries which already have one thus remove the entries + // We don't want to generate keys for entries which already have one thus remove + // the entries if (this.preferences.getCitationKeyPatternPreferences().shouldAvoidOverwriteCiteKey()) { entries.removeIf(BibEntry::hasCitationKey); // if we're going to override some citation keys warn the user about it - } else if (entries.parallelStream().anyMatch(BibEntry::hasCitationKey)) { + } + else if (entries.parallelStream().anyMatch(BibEntry::hasCitationKey)) { boolean overwriteKeys = confirmOverwriteKeys(dialogService, this.preferences); // The user doesn't want to override citation keys @@ -109,32 +111,32 @@ private BackgroundTask generateKeysInBackground() { @Override public Void call() { - if (isCanceled) { - return null; - } - UiTaskExecutor.runInJavaFXThread(() -> { - updateProgress(0, entries.size()); - messageProperty().set(Localization.lang("%0/%1 entries", 0, entries.size())); - }); - stateManager.getActiveDatabase().ifPresent(databaseContext -> { - // generate the new citation keys for each entry - compound = new NamedCompound(Localization.lang("Autogenerate citation keys")); - CitationKeyGenerator keyGenerator = - new CitationKeyGenerator(databaseContext, preferences.getCitationKeyPatternPreferences()); - int entriesDone = 0; - for (BibEntry entry : entries) { - keyGenerator.generateAndSetKey(entry) - .ifPresent(fieldChange -> compound.addEdit(new UndoableKeyChange(fieldChange))); - entriesDone++; - int finalEntriesDone = entriesDone; - UiTaskExecutor.runInJavaFXThread(() -> { - updateProgress(finalEntriesDone, entries.size()); - messageProperty().set(Localization.lang("%0/%1 entries", finalEntriesDone, entries.size())); - }); - } - compound.end(); - }); + if (isCanceled) { return null; + } + UiTaskExecutor.runInJavaFXThread(() -> { + updateProgress(0, entries.size()); + messageProperty().set(Localization.lang("%0/%1 entries", 0, entries.size())); + }); + stateManager.getActiveDatabase().ifPresent(databaseContext -> { + // generate the new citation keys for each entry + compound = new NamedCompound(Localization.lang("Autogenerate citation keys")); + CitationKeyGenerator keyGenerator = new CitationKeyGenerator(databaseContext, + preferences.getCitationKeyPatternPreferences()); + int entriesDone = 0; + for (BibEntry entry : entries) { + keyGenerator.generateAndSetKey(entry) + .ifPresent(fieldChange -> compound.addEdit(new UndoableKeyChange(fieldChange))); + entriesDone++; + int finalEntriesDone = entriesDone; + UiTaskExecutor.runInJavaFXThread(() -> { + updateProgress(finalEntriesDone, entries.size()); + messageProperty().set(Localization.lang("%0/%1 entries", finalEntriesDone, entries.size())); + }); + } + compound.end(); + }); + return null; } @Override @@ -145,7 +147,8 @@ public BackgroundTask onSuccess(Consumer onSuccess) { } tabSupplier.get().markBaseChanged(); - dialogService.notify(formatOutputMessage(Localization.lang("Generated citation key for"), entries.size())); + dialogService + .notify(formatOutputMessage(Localization.lang("Generated citation key for"), entries.size())); return super.onSuccess(onSuccess); } }; @@ -155,4 +158,5 @@ private String formatOutputMessage(String start, int count) { return "%s %d %s.".formatted(start, count, count > 1 ? Localization.lang("entries") : Localization.lang("entry")); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeySingleAction.java b/jabgui/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeySingleAction.java index 3b99157d6aa..7615755efcb 100644 --- a/jabgui/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeySingleAction.java +++ b/jabgui/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeySingleAction.java @@ -13,12 +13,17 @@ public class GenerateCitationKeySingleAction extends SimpleCommand { private final DialogService dialogService; + private final BibDatabaseContext databaseContext; + private final CliPreferences preferences; + private final BibEntry entry; + private final UndoManager undoManager; - public GenerateCitationKeySingleAction(BibEntry entry, BibDatabaseContext databaseContext, DialogService dialogService, CliPreferences preferences, UndoManager undoManager) { + public GenerateCitationKeySingleAction(BibEntry entry, BibDatabaseContext databaseContext, + DialogService dialogService, CliPreferences preferences, UndoManager undoManager) { this.entry = entry; this.databaseContext = databaseContext; this.dialogService = dialogService; @@ -34,8 +39,9 @@ public GenerateCitationKeySingleAction(BibEntry entry, BibDatabaseContext databa public void execute() { if (!entry.hasCitationKey() || GenerateCitationKeyAction.confirmOverwriteKeys(dialogService, preferences)) { new CitationKeyGenerator(databaseContext, preferences.getCitationKeyPatternPreferences()) - .generateAndSetKey(entry) - .ifPresent(change -> undoManager.addEdit(new UndoableKeyChange(change))); + .generateAndSetKey(entry) + .ifPresent(change -> undoManager.addEdit(new UndoableKeyChange(change))); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupAction.java b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupAction.java index 1a59a432559..13039785df7 100644 --- a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupAction.java +++ b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupAction.java @@ -31,22 +31,25 @@ public class CleanupAction extends SimpleCommand { private final Supplier tabSupplier; + private final CliPreferences preferences; + private final DialogService dialogService; + private final StateManager stateManager; + private final TaskExecutor taskExecutor; + private final UndoManager undoManager; + private final List failures; private boolean isCanceled; + private int modifiedEntriesCount; - public CleanupAction(Supplier tabSupplier, - CliPreferences preferences, - DialogService dialogService, - StateManager stateManager, - TaskExecutor taskExecutor, - UndoManager undoManager) { + public CleanupAction(Supplier tabSupplier, CliPreferences preferences, DialogService dialogService, + StateManager stateManager, TaskExecutor taskExecutor, UndoManager undoManager) { this.tabSupplier = tabSupplier; this.preferences = preferences; this.dialogService = dialogService; @@ -64,28 +67,29 @@ public void execute() { return; } - if (stateManager.getSelectedEntries().isEmpty()) { // None selected. Inform the user to select entries first. - dialogService.showInformationDialogAndWait(Localization.lang("Cleanup entry"), Localization.lang("First select entries to clean up.")); + if (stateManager.getSelectedEntries().isEmpty()) { // None selected. Inform the + // user to select entries + // first. + dialogService.showInformationDialogAndWait(Localization.lang("Cleanup entry"), + Localization.lang("First select entries to clean up.")); return; } isCanceled = false; modifiedEntriesCount = 0; - CleanupDialog cleanupDialog = new CleanupDialog( - stateManager.getActiveDatabase().get(), - preferences.getCleanupPreferences(), - preferences.getFilePreferences() - ); + CleanupDialog cleanupDialog = new CleanupDialog(stateManager.getActiveDatabase().get(), + preferences.getCleanupPreferences(), preferences.getFilePreferences()); Optional chosenPreset = dialogService.showCustomDialogAndWait(cleanupDialog); chosenPreset.ifPresent(preset -> { - if (preset.isActive(CleanupPreferences.CleanupStep.RENAME_PDF) && preferences.getAutoLinkPreferences().shouldAskAutoNamingPdfs()) { - boolean confirmed = dialogService.showConfirmationDialogWithOptOutAndWait(Localization.lang("Autogenerate PDF Names"), - Localization.lang("Auto-generating PDF-Names does not support undo. Continue?"), + if (preset.isActive(CleanupPreferences.CleanupStep.RENAME_PDF) + && preferences.getAutoLinkPreferences().shouldAskAutoNamingPdfs()) { + boolean confirmed = dialogService.showConfirmationDialogWithOptOutAndWait( Localization.lang("Autogenerate PDF Names"), - Localization.lang("Cancel"), + Localization.lang("Auto-generating PDF-Names does not support undo. Continue?"), + Localization.lang("Autogenerate PDF Names"), Localization.lang("Cancel"), Localization.lang("Do not ask again"), optOut -> preferences.getAutoLinkPreferences().setAskAutoNamingPdfs(!optOut)); if (!confirmed) { @@ -98,24 +102,21 @@ public void execute() { preferences.getCleanupPreferences().setFieldFormatterCleanups(preset.getFieldFormatterCleanups()); BackgroundTask.wrap(() -> cleanup(stateManager.getActiveDatabase().get(), preset)) - .onSuccess(result -> showResults()) - .onFailure(dialogService::showErrorDialogAndWait) - .executeWith(taskExecutor); + .onSuccess(result -> showResults()) + .onFailure(dialogService::showErrorDialogAndWait) + .executeWith(taskExecutor); }); } /** * Runs the cleanup on the entry and records the change. - * * @return true iff entry was modified */ - private boolean doCleanup(BibDatabaseContext databaseContext, CleanupPreferences preset, BibEntry entry, NamedCompound ce) { + private boolean doCleanup(BibDatabaseContext databaseContext, CleanupPreferences preset, BibEntry entry, + NamedCompound ce) { // Create and run cleaner - CleanupWorker cleaner = new CleanupWorker( - databaseContext, - preferences.getFilePreferences(), - preferences.getTimestampPreferences() - ); + CleanupWorker cleaner = new CleanupWorker(databaseContext, preferences.getFilePreferences(), + preferences.getTimestampPreferences()); List changes = cleaner.cleanup(preset, entry); @@ -140,10 +141,13 @@ private void showResults() { if (modifiedEntriesCount == 0) { dialogService.notify(Localization.lang("No entry needed a clean up")); - } else if (modifiedEntriesCount == 1) { + } + else if (modifiedEntriesCount == 1) { dialogService.notify(Localization.lang("One entry needed a clean up")); - } else { - dialogService.notify(Localization.lang("%0 entries needed a clean up", Integer.toString(modifiedEntriesCount))); + } + else { + dialogService + .notify(Localization.lang("%0 entries needed a clean up", Integer.toString(modifiedEntriesCount))); } } @@ -172,11 +176,10 @@ private void cleanup(BibDatabaseContext databaseContext, CleanupPreferences clea private void showFailures(List failures) { String message = failures.stream() - .map(exception -> "- " + exception.getLocalizedMessage()) - .collect(Collectors.joining("\n")); + .map(exception -> "- " + exception.getLocalizedMessage()) + .collect(Collectors.joining("\n")); - Platform.runLater(() -> - dialogService.showErrorDialogAndWait(Localization.lang("File Move Errors"), message) - ); + Platform.runLater(() -> dialogService.showErrorDialogAndWait(Localization.lang("File Move Errors"), message)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupDialog.java b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupDialog.java index 752b9309dc6..f0e7e27cb2f 100644 --- a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupDialog.java @@ -10,7 +10,9 @@ import org.jabref.model.database.BibDatabaseContext; public class CleanupDialog extends BaseDialog { - public CleanupDialog(BibDatabaseContext databaseContext, CleanupPreferences initialPreset, FilePreferences filePreferences) { + + public CleanupDialog(BibDatabaseContext databaseContext, CleanupPreferences initialPreset, + FilePreferences filePreferences) { setTitle(Localization.lang("Clean up entries")); getDialogPane().setPrefSize(600, 650); getDialogPane().getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL); @@ -27,9 +29,11 @@ public CleanupDialog(BibDatabaseContext databaseContext, CleanupPreferences init setResultConverter(button -> { if (button == ButtonType.OK) { return presetPanel.getCleanupPreset(); - } else { + } + else { return null; } }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java index 20d818d9b04..e970de82a9f 100644 --- a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java +++ b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java @@ -24,30 +24,61 @@ public class CleanupPresetPanel extends VBox { private final BibDatabaseContext databaseContext; - @FXML private Label cleanupRenamePDFLabel; - @FXML private CheckBox cleanUpDOI; - @FXML private CheckBox cleanUpEprint; - @FXML private CheckBox cleanUpURL; - @FXML private CheckBox cleanUpISSN; - @FXML private CheckBox cleanUpMovePDF; - @FXML private CheckBox cleanUpMakePathsRelative; - @FXML private CheckBox cleanUpRenamePDF; - @FXML private CheckBox cleanUpRenamePDFonlyRelativePaths; - @FXML private CheckBox cleanUpDeletedFiles; - @FXML private CheckBox cleanUpUpgradeExternalLinks; - @FXML private CheckBox cleanUpBiblatex; - @FXML private CheckBox cleanUpBibtex; - @FXML private CheckBox cleanUpTimestampToCreationDate; - @FXML private CheckBox cleanUpTimestampToModificationDate; - @FXML private FieldFormatterCleanupsPanel formatterCleanupsPanel; - - public CleanupPresetPanel(BibDatabaseContext databaseContext, CleanupPreferences cleanupPreferences, FilePreferences filePreferences) { + + @FXML + private Label cleanupRenamePDFLabel; + + @FXML + private CheckBox cleanUpDOI; + + @FXML + private CheckBox cleanUpEprint; + + @FXML + private CheckBox cleanUpURL; + + @FXML + private CheckBox cleanUpISSN; + + @FXML + private CheckBox cleanUpMovePDF; + + @FXML + private CheckBox cleanUpMakePathsRelative; + + @FXML + private CheckBox cleanUpRenamePDF; + + @FXML + private CheckBox cleanUpRenamePDFonlyRelativePaths; + + @FXML + private CheckBox cleanUpDeletedFiles; + + @FXML + private CheckBox cleanUpUpgradeExternalLinks; + + @FXML + private CheckBox cleanUpBiblatex; + + @FXML + private CheckBox cleanUpBibtex; + + @FXML + private CheckBox cleanUpTimestampToCreationDate; + + @FXML + private CheckBox cleanUpTimestampToModificationDate; + + @FXML + private FieldFormatterCleanupsPanel formatterCleanupsPanel; + + public CleanupPresetPanel(BibDatabaseContext databaseContext, CleanupPreferences cleanupPreferences, + FilePreferences filePreferences) { this.databaseContext = Objects.requireNonNull(databaseContext); // Load FXML - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); init(cleanupPreferences, filePreferences); } @@ -55,48 +86,49 @@ public CleanupPresetPanel(BibDatabaseContext databaseContext, CleanupPreferences private void init(CleanupPreferences cleanupPreferences, FilePreferences filePreferences) { Optional firstExistingDir = databaseContext.getFirstExistingFileDir(filePreferences); if (firstExistingDir.isPresent()) { - cleanUpMovePDF.setText(Localization.lang("Move linked files to default file directory %0", firstExistingDir.get().toString())); - } else { + cleanUpMovePDF.setText(Localization.lang("Move linked files to default file directory %0", + firstExistingDir.get().toString())); + } + else { cleanUpMovePDF.setText(Localization.lang("Move linked files to default file directory %0", "...")); - // 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. + // 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.setDisable(true); cleanUpMovePDF.setSelected(false); } cleanUpRenamePDFonlyRelativePaths.disableProperty().bind(cleanUpRenamePDF.selectedProperty().not()); - cleanUpUpgradeExternalLinks.setText(Localization.lang("Upgrade external PDF/PS links to use the '%0' field.", StandardField.FILE.getDisplayName())); + cleanUpUpgradeExternalLinks.setText(Localization.lang("Upgrade external PDF/PS links to use the '%0' field.", + StandardField.FILE.getDisplayName())); String currentPattern = Localization.lang("Filename format pattern (from preferences)") - .concat(filePreferences.getFileNamePattern()); + .concat(filePreferences.getFileNamePattern()); cleanupRenamePDFLabel.setText(currentPattern); - cleanUpBibtex.selectedProperty().addListener( - (observable, oldValue, newValue) -> { - if (newValue) { - cleanUpBiblatex.selectedProperty().setValue(false); - } - }); - cleanUpBiblatex.selectedProperty().addListener( - (observable, oldValue, newValue) -> { - if (newValue) { - cleanUpBibtex.selectedProperty().setValue(false); - } - }); - - cleanUpTimestampToCreationDate.selectedProperty().addListener( - (observable, oldValue, newValue) -> { - if (newValue) { - cleanUpTimestampToModificationDate.selectedProperty().setValue(false); - } - }); - cleanUpTimestampToModificationDate.selectedProperty().addListener( - (observable, oldValue, newValue) -> { - if (newValue) { - cleanUpTimestampToCreationDate.selectedProperty().setValue(false); - } - }); + cleanUpBibtex.selectedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + cleanUpBiblatex.selectedProperty().setValue(false); + } + }); + cleanUpBiblatex.selectedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + cleanUpBibtex.selectedProperty().setValue(false); + } + }); + + cleanUpTimestampToCreationDate.selectedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + cleanUpTimestampToModificationDate.selectedProperty().setValue(false); + } + }); + cleanUpTimestampToModificationDate.selectedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + cleanUpTimestampToCreationDate.selectedProperty().setValue(false); + } + }); updateDisplay(cleanupPreferences); } @@ -109,17 +141,23 @@ private void updateDisplay(CleanupPreferences preset) { } cleanUpMakePathsRelative.setSelected(preset.isActive(CleanupPreferences.CleanupStep.MAKE_PATHS_RELATIVE)); cleanUpRenamePDF.setSelected(preset.isActive(CleanupPreferences.CleanupStep.RENAME_PDF)); - cleanUpRenamePDFonlyRelativePaths.setSelected(preset.isActive(CleanupPreferences.CleanupStep.RENAME_PDF_ONLY_RELATIVE_PATHS)); - cleanUpUpgradeExternalLinks.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CLEAN_UP_UPGRADE_EXTERNAL_LINKS)); + cleanUpRenamePDFonlyRelativePaths + .setSelected(preset.isActive(CleanupPreferences.CleanupStep.RENAME_PDF_ONLY_RELATIVE_PATHS)); + cleanUpUpgradeExternalLinks + .setSelected(preset.isActive(CleanupPreferences.CleanupStep.CLEAN_UP_UPGRADE_EXTERNAL_LINKS)); cleanUpDeletedFiles.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CLEAN_UP_DELETED_LINKED_FILES)); cleanUpBiblatex.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CONVERT_TO_BIBLATEX)); cleanUpBibtex.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CONVERT_TO_BIBTEX)); - cleanUpTimestampToCreationDate.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CONVERT_TIMESTAMP_TO_CREATIONDATE)); - cleanUpTimestampToModificationDate.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CONVERT_TIMESTAMP_TO_MODIFICATIONDATE)); - cleanUpTimestampToModificationDate.setSelected(preset.isActive(CleanupPreferences.CleanupStep.DO_NOT_CONVERT_TIMESTAMP)); + cleanUpTimestampToCreationDate + .setSelected(preset.isActive(CleanupPreferences.CleanupStep.CONVERT_TIMESTAMP_TO_CREATIONDATE)); + cleanUpTimestampToModificationDate + .setSelected(preset.isActive(CleanupPreferences.CleanupStep.CONVERT_TIMESTAMP_TO_MODIFICATIONDATE)); + cleanUpTimestampToModificationDate + .setSelected(preset.isActive(CleanupPreferences.CleanupStep.DO_NOT_CONVERT_TIMESTAMP)); cleanUpISSN.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CLEAN_UP_ISSN)); formatterCleanupsPanel.cleanupsDisableProperty().setValue(!preset.getFieldFormatterCleanups().isEnabled()); - formatterCleanupsPanel.cleanupsProperty().setValue(FXCollections.observableArrayList(preset.getFieldFormatterCleanups().getConfiguredActions())); + formatterCleanupsPanel.cleanupsProperty() + .setValue(FXCollections.observableArrayList(preset.getFieldFormatterCleanups().getConfiguredActions())); } public CleanupPreferences getCleanupPreset() { @@ -146,7 +184,8 @@ public CleanupPreferences getCleanupPreset() { if (cleanUpRenamePDF.isSelected()) { if (cleanUpRenamePDFonlyRelativePaths.isSelected()) { activeJobs.add(CleanupPreferences.CleanupStep.RENAME_PDF_ONLY_RELATIVE_PATHS); - } else { + } + else { activeJobs.add(CleanupPreferences.CleanupStep.RENAME_PDF); } } @@ -171,8 +210,9 @@ public CleanupPreferences getCleanupPreset() { activeJobs.add(CleanupPreferences.CleanupStep.FIX_FILE_LINKS); - return new CleanupPreferences(activeJobs, new FieldFormatterCleanups( - !formatterCleanupsPanel.cleanupsDisableProperty().getValue(), - formatterCleanupsPanel.cleanupsProperty())); + return new CleanupPreferences(activeJobs, + new FieldFormatterCleanups(!formatterCleanupsPanel.cleanupsDisableProperty().getValue(), + formatterCleanupsPanel.cleanupsProperty())); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupSingleAction.java b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupSingleAction.java index 6f9ffc08892..ad0ce7ed28c 100644 --- a/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupSingleAction.java +++ b/jabgui/src/main/java/org/jabref/gui/cleanup/CleanupSingleAction.java @@ -25,15 +25,21 @@ public class CleanupSingleAction extends SimpleCommand { private final CliPreferences preferences; + private final DialogService dialogService; + private final StateManager stateManager; + private final BibEntry entry; + private final UndoManager undoManager; private boolean isCanceled; + private int modifiedEntriesCount; - public CleanupSingleAction(BibEntry entry, CliPreferences preferences, DialogService dialogService, StateManager stateManager, UndoManager undoManager) { + public CleanupSingleAction(BibEntry entry, CliPreferences preferences, DialogService dialogService, + StateManager stateManager, UndoManager undoManager) { this.entry = entry; this.preferences = preferences; this.dialogService = dialogService; @@ -47,20 +53,18 @@ public CleanupSingleAction(BibEntry entry, CliPreferences preferences, DialogSer public void execute() { isCanceled = false; - CleanupDialog cleanupDialog = new CleanupDialog( - stateManager.getActiveDatabase().get(), - preferences.getCleanupPreferences(), - preferences.getFilePreferences() - ); + CleanupDialog cleanupDialog = new CleanupDialog(stateManager.getActiveDatabase().get(), + preferences.getCleanupPreferences(), preferences.getFilePreferences()); Optional chosenPreset = dialogService.showCustomDialogAndWait(cleanupDialog); chosenPreset.ifPresent(preset -> { - if (preset.isActive(CleanupPreferences.CleanupStep.RENAME_PDF) && preferences.getAutoLinkPreferences().shouldAskAutoNamingPdfs()) { - boolean confirmed = dialogService.showConfirmationDialogWithOptOutAndWait(Localization.lang("Autogenerate PDF Names"), - Localization.lang("Auto-generating PDF-Names does not support undo. Continue?"), + if (preset.isActive(CleanupPreferences.CleanupStep.RENAME_PDF) + && preferences.getAutoLinkPreferences().shouldAskAutoNamingPdfs()) { + boolean confirmed = dialogService.showConfirmationDialogWithOptOutAndWait( Localization.lang("Autogenerate PDF Names"), - Localization.lang("Cancel"), + Localization.lang("Auto-generating PDF-Names does not support undo. Continue?"), + Localization.lang("Autogenerate PDF Names"), Localization.lang("Cancel"), Localization.lang("Do not ask again"), optOut -> preferences.getAutoLinkPreferences().setAskAutoNamingPdfs(!optOut)); if (!confirmed) { @@ -79,13 +83,11 @@ public void execute() { /** * Runs the cleanup on the entry and records the change. */ - private void doCleanup(BibDatabaseContext databaseContext, CleanupPreferences preset, BibEntry entry, NamedCompound ce) { + private void doCleanup(BibDatabaseContext databaseContext, CleanupPreferences preset, BibEntry entry, + NamedCompound ce) { // Create and run cleaner - CleanupWorker cleaner = new CleanupWorker( - databaseContext, - preferences.getFilePreferences(), - preferences.getTimestampPreferences() - ); + CleanupWorker cleaner = new CleanupWorker(databaseContext, preferences.getFilePreferences(), + preferences.getTimestampPreferences()); List changes = cleaner.cleanup(preset, entry); @@ -100,15 +102,15 @@ private void doCleanup(BibDatabaseContext databaseContext, CleanupPreferences pr } private void cleanup(BibDatabaseContext databaseContext, CleanupPreferences cleanupPreferences) { - // undo granularity is on entry level - NamedCompound ce = new NamedCompound(Localization.lang("Cleanup entry")); + // undo granularity is on entry level + NamedCompound ce = new NamedCompound(Localization.lang("Cleanup entry")); - doCleanup(databaseContext, cleanupPreferences, entry, ce); + doCleanup(databaseContext, cleanupPreferences, entry, ce); - ce.end(); - if (ce.hasEdits()) { - undoManager.addEdit(ce); - } + ce.end(); + if (ce.hasEdits()) { + undoManager.addEdit(ce); + } } private void showFailures(List failures) { @@ -116,8 +118,8 @@ private void showFailures(List failures) { for (JabRefException exception : failures) { sb.append("- ").append(exception.getLocalizedMessage()).append("\n"); } - Platform.runLater(() -> - dialogService.showErrorDialogAndWait(Localization.lang("File Move Errors"), sb.toString()) - ); + Platform + .runLater(() -> dialogService.showErrorDialogAndWait(Localization.lang("File Move Errors"), sb.toString())); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/ChangeScanner.java b/jabgui/src/main/java/org/jabref/gui/collab/ChangeScanner.java index 5e460892922..7cc4028a1b1 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/ChangeScanner.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/ChangeScanner.java @@ -17,14 +17,14 @@ public class ChangeScanner { private static final Logger LOGGER = LoggerFactory.getLogger(ChangeScanner.class); + private final BibDatabaseContext database; + private final GuiPreferences preferences; private final DatabaseChangeResolverFactory databaseChangeResolverFactory; - public ChangeScanner(BibDatabaseContext database, - DialogService dialogService, - GuiPreferences preferences) { + public ChangeScanner(BibDatabaseContext database, DialogService dialogService, GuiPreferences preferences) { this.database = database; this.preferences = preferences; this.databaseChangeResolverFactory = new DatabaseChangeResolverFactory(dialogService, database, preferences); @@ -39,13 +39,16 @@ public List scanForChanges() { // Parse the modified file // Important: apply all post-load actions ImportFormatPreferences importFormatPreferences = preferences.getImportFormatPreferences(); - ParserResult result = OpenDatabase.loadDatabase(database.getDatabasePath().get(), importFormatPreferences, new DummyFileUpdateMonitor()); + ParserResult result = OpenDatabase.loadDatabase(database.getDatabasePath().get(), importFormatPreferences, + new DummyFileUpdateMonitor()); BibDatabaseContext databaseOnDisk = result.getDatabaseContext(); return DatabaseChangeList.compareAndGetChanges(database, databaseOnDisk, databaseChangeResolverFactory); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.warn("Error while parsing changed file.", e); return List.of(); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChange.java b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChange.java index 2cd4105df0f..7b1c8f93a66 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChange.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChange.java @@ -21,13 +21,20 @@ import org.jabref.logic.util.OptionalObjectProperty; import org.jabref.model.database.BibDatabaseContext; -public sealed abstract class DatabaseChange permits EntryAdd, EntryChange, EntryDelete, GroupChange, MetadataChange, PreambleChange, BibTexStringAdd, BibTexStringChange, BibTexStringDelete, BibTexStringRename { +public sealed abstract class DatabaseChange permits EntryAdd, EntryChange, EntryDelete, GroupChange, MetadataChange, + PreambleChange, BibTexStringAdd, BibTexStringChange, BibTexStringDelete, BibTexStringRename { + protected final BibDatabaseContext databaseContext; - protected final OptionalObjectProperty externalChangeResolver = OptionalObjectProperty.empty(); + + protected final OptionalObjectProperty externalChangeResolver = OptionalObjectProperty + .empty(); + private final BooleanProperty accepted = new SimpleBooleanProperty(); + private final StringProperty name = new SimpleStringProperty(); - protected DatabaseChange(BibDatabaseContext databaseContext, DatabaseChangeResolverFactory databaseChangeResolverFactory) { + protected DatabaseChange(BibDatabaseContext databaseContext, + DatabaseChangeResolverFactory databaseChangeResolverFactory) { this.databaseContext = databaseContext; setChangeName("Unnamed Change!"); @@ -68,4 +75,5 @@ public Optional getExternalChangeResolver() { } public abstract void applyChange(NamedCompound undoEdit); + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeDetailsView.java b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeDetailsView.java index b37b7c7f59b..547832fca26 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeDetailsView.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeDetailsView.java @@ -13,21 +13,14 @@ import org.jabref.gui.collab.stringdelete.BibTexStringDeleteDetailsView; import org.jabref.gui.collab.stringrename.BibTexStringRenameDetailsView; -public sealed abstract class DatabaseChangeDetailsView extends AnchorPane permits - EntryWithPreviewAndSourceDetailsView, - GroupChangeDetailsView, - MetadataChangeDetailsView, - PreambleChangeDetailsView, - BibTexStringAddDetailsView, - BibTexStringChangeDetailsView, - BibTexStringDeleteDetailsView, - BibTexStringRenameDetailsView, - EntryChangeDetailsView { +public sealed abstract class DatabaseChangeDetailsView extends AnchorPane + permits EntryWithPreviewAndSourceDetailsView, GroupChangeDetailsView, MetadataChangeDetailsView, + PreambleChangeDetailsView, BibTexStringAddDetailsView, BibTexStringChangeDetailsView, + BibTexStringDeleteDetailsView, BibTexStringRenameDetailsView, EntryChangeDetailsView { /** - * Set left, top, right, bottom anchors based on common offset parameter for the given child - * and attach it to children. - * + * Set left, top, right, bottom anchors based on common offset parameter for the given + * child and attach it to children. * @param child the child node of the implementation * @see AnchorPane#getChildren() * @see javafx.collections.ObservableList#setAll(Object[]) @@ -40,4 +33,5 @@ protected void setAllAnchorsAndAttachChild(Node child) { setBottomAnchor(child, ANCHOR_PANE_OFFSET); this.getChildren().setAll(child); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeDetailsViewFactory.java b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeDetailsViewFactory.java index 39939bce263..b5d1af0f661 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeDetailsViewFactory.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeDetailsViewFactory.java @@ -28,21 +28,24 @@ import org.jabref.model.entry.BibEntryTypesManager; public class DatabaseChangeDetailsViewFactory { + private final BibDatabaseContext databaseContext; + private final DialogService dialogService; + private final ThemeManager themeManager; + private final GuiPreferences preferences; + private final BibEntryTypesManager entryTypesManager; + private final PreviewViewer previewViewer; + private final TaskExecutor taskExecutor; - public DatabaseChangeDetailsViewFactory(BibDatabaseContext databaseContext, - DialogService dialogService, - ThemeManager themeManager, - GuiPreferences preferences, - BibEntryTypesManager entryTypesManager, - PreviewViewer previewViewer, - TaskExecutor taskExecutor) { + public DatabaseChangeDetailsViewFactory(BibDatabaseContext databaseContext, DialogService dialogService, + ThemeManager themeManager, GuiPreferences preferences, BibEntryTypesManager entryTypesManager, + PreviewViewer previewViewer, TaskExecutor taskExecutor) { this.databaseContext = databaseContext; this.dialogService = dialogService; this.themeManager = themeManager; @@ -54,41 +57,22 @@ public DatabaseChangeDetailsViewFactory(BibDatabaseContext databaseContext, public DatabaseChangeDetailsView create(DatabaseChange databaseChange) { return switch (databaseChange) { - case EntryChange entryChange -> new EntryChangeDetailsView( - entryChange.getOldEntry(), - entryChange.getNewEntry(), - databaseContext, - dialogService, - themeManager, - preferences, - entryTypesManager, - previewViewer, - taskExecutor - ); - case EntryAdd entryAdd -> new EntryWithPreviewAndSourceDetailsView( - entryAdd.getAddedEntry(), - databaseContext, - preferences, - entryTypesManager, - previewViewer - ); - case EntryDelete entryDelete -> new EntryWithPreviewAndSourceDetailsView( - entryDelete.getDeletedEntry(), - databaseContext, - preferences, - entryTypesManager, - previewViewer - ); + case EntryChange entryChange -> + new EntryChangeDetailsView(entryChange.getOldEntry(), entryChange.getNewEntry(), databaseContext, + dialogService, themeManager, preferences, entryTypesManager, previewViewer, taskExecutor); + case EntryAdd entryAdd -> new EntryWithPreviewAndSourceDetailsView(entryAdd.getAddedEntry(), + databaseContext, preferences, entryTypesManager, previewViewer); + case EntryDelete entryDelete -> new EntryWithPreviewAndSourceDetailsView(entryDelete.getDeletedEntry(), + databaseContext, preferences, entryTypesManager, previewViewer); case BibTexStringAdd stringAdd -> new BibTexStringAddDetailsView(stringAdd); case BibTexStringDelete stringDelete -> new BibTexStringDeleteDetailsView(stringDelete); case BibTexStringChange stringChange -> new BibTexStringChangeDetailsView(stringChange); case BibTexStringRename stringRename -> new BibTexStringRenameDetailsView(stringRename); - case MetadataChange metadataChange -> new MetadataChangeDetailsView( - metadataChange, - preferences.getCitationKeyPatternPreferences().getKeyPatterns() - ); + case MetadataChange metadataChange -> new MetadataChangeDetailsView(metadataChange, + preferences.getCitationKeyPatternPreferences().getKeyPatterns()); case GroupChange groupChange -> new GroupChangeDetailsView(groupChange); case PreambleChange preambleChange -> new PreambleChangeDetailsView(preambleChange); }; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeList.java b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeList.java index be4969d1e52..6bbb06712f3 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeList.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeList.java @@ -20,35 +20,42 @@ import org.jabref.model.database.BibDatabaseContext; public class DatabaseChangeList { + private DatabaseChangeList() { } /** - * Compares the given two databases, and returns the list of changes required to change the {@code originalDatabase} into the {@code otherDatabase} - * + * Compares the given two databases, and returns the list of changes required to + * change the {@code originalDatabase} into the {@code otherDatabase} * @param originalDatabase This is the original database - * @param otherDatabase This is the other database. - * @return an unmodifiable list of {@code DatabaseChange} required to change {@code originalDatabase} into {@code otherDatabase} + * @param otherDatabase This is the other database. + * @return an unmodifiable list of {@code DatabaseChange} required to change + * {@code originalDatabase} into {@code otherDatabase} */ - public static List compareAndGetChanges(BibDatabaseContext originalDatabase, BibDatabaseContext otherDatabase, DatabaseChangeResolverFactory databaseChangeResolverFactory) { + public static List compareAndGetChanges(BibDatabaseContext originalDatabase, + BibDatabaseContext otherDatabase, DatabaseChangeResolverFactory databaseChangeResolverFactory) { List changes = new ArrayList<>(); BibDatabaseDiff differences = BibDatabaseDiff.compare(originalDatabase, otherDatabase); differences.getMetaDataDifferences().ifPresent(diff -> { changes.add(new MetadataChange(diff, originalDatabase, databaseChangeResolverFactory)); - diff.getGroupDifferences().ifPresent(groupDiff -> changes.add(new GroupChange( - groupDiff, originalDatabase, databaseChangeResolverFactory - ))); + diff.getGroupDifferences() + .ifPresent(groupDiff -> changes + .add(new GroupChange(groupDiff, originalDatabase, databaseChangeResolverFactory))); }); - differences.getPreambleDifferences().ifPresent(diff -> changes.add(new PreambleChange(diff, originalDatabase, databaseChangeResolverFactory))); - differences.getBibStringDifferences().forEach(diff -> changes.add(createBibStringDiff(originalDatabase, databaseChangeResolverFactory, diff))); - differences.getEntryDifferences().forEach(diff -> changes.add(createBibEntryDiff(originalDatabase, databaseChangeResolverFactory, diff))); + differences.getPreambleDifferences() + .ifPresent(diff -> changes.add(new PreambleChange(diff, originalDatabase, databaseChangeResolverFactory))); + differences.getBibStringDifferences() + .forEach(diff -> changes.add(createBibStringDiff(originalDatabase, databaseChangeResolverFactory, diff))); + differences.getEntryDifferences() + .forEach(diff -> changes.add(createBibEntryDiff(originalDatabase, databaseChangeResolverFactory, diff))); return Collections.unmodifiableList(changes); } - private static DatabaseChange createBibStringDiff(BibDatabaseContext originalDatabase, DatabaseChangeResolverFactory databaseChangeResolverFactory, BibStringDiff diff) { + private static DatabaseChange createBibStringDiff(BibDatabaseContext originalDatabase, + DatabaseChangeResolverFactory databaseChangeResolverFactory, BibStringDiff diff) { if (diff.getOriginalString() == null) { return new BibTexStringAdd(diff.getNewString(), originalDatabase, databaseChangeResolverFactory); } @@ -58,13 +65,16 @@ private static DatabaseChange createBibStringDiff(BibDatabaseContext originalDat } if (diff.getOriginalString().getName().equals(diff.getNewString().getName())) { - return new BibTexStringChange(diff.getOriginalString(), diff.getNewString(), originalDatabase, databaseChangeResolverFactory); + return new BibTexStringChange(diff.getOriginalString(), diff.getNewString(), originalDatabase, + databaseChangeResolverFactory); } - return new BibTexStringRename(diff.getOriginalString(), diff.getNewString(), originalDatabase, databaseChangeResolverFactory); + return new BibTexStringRename(diff.getOriginalString(), diff.getNewString(), originalDatabase, + databaseChangeResolverFactory); } - private static DatabaseChange createBibEntryDiff(BibDatabaseContext originalDatabase, DatabaseChangeResolverFactory databaseChangeResolverFactory, BibEntryDiff diff) { + private static DatabaseChange createBibEntryDiff(BibDatabaseContext originalDatabase, + DatabaseChangeResolverFactory databaseChangeResolverFactory, BibEntryDiff diff) { if (diff.originalEntry() == null) { return new EntryAdd(diff.newEntry(), originalDatabase, databaseChangeResolverFactory); } @@ -75,4 +85,5 @@ private static DatabaseChange createBibEntryDiff(BibDatabaseContext originalData return new EntryChange(diff.originalEntry(), diff.newEntry(), originalDatabase, databaseChangeResolverFactory); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeListener.java b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeListener.java index 4ae14899da9..2c049763478 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeListener.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeListener.java @@ -3,5 +3,7 @@ import java.util.List; public interface DatabaseChangeListener { + void databaseChanged(List changes); + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java index 3d965621b9d..e0314730032 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java @@ -31,24 +31,28 @@ public class DatabaseChangeMonitor implements FileUpdateListener { private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseChangeMonitor.class); private final BibDatabaseContext database; + private final FileUpdateMonitor fileMonitor; + private final List listeners; + private final TaskExecutor taskExecutor; + private final DialogService dialogService; + private final GuiPreferences preferences; + private final LibraryTab.DatabaseNotification notificationPane; + private final UndoManager undoManager; + private final StateManager stateManager; + private LibraryTab saveState; - public DatabaseChangeMonitor(BibDatabaseContext database, - FileUpdateMonitor fileMonitor, - TaskExecutor taskExecutor, - DialogService dialogService, - GuiPreferences preferences, - LibraryTab.DatabaseNotification notificationPane, - UndoManager undoManager, - StateManager stateManager) { + public DatabaseChangeMonitor(BibDatabaseContext database, FileUpdateMonitor fileMonitor, TaskExecutor taskExecutor, + DialogService dialogService, GuiPreferences preferences, LibraryTab.DatabaseNotification notificationPane, + UndoManager undoManager, StateManager stateManager) { this.database = database; this.fileMonitor = fileMonitor; this.taskExecutor = taskExecutor; @@ -63,7 +67,8 @@ public DatabaseChangeMonitor(BibDatabaseContext database, this.database.getDatabasePath().ifPresent(path -> { try { fileMonitor.addListenerForFile(path, this); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Error while trying to monitor {}", path, e); } }); @@ -72,24 +77,31 @@ public DatabaseChangeMonitor(BibDatabaseContext database, } private void notifyOnChange(List changes) { - // The changes come from {@link org.jabref.gui.collab.DatabaseChangeList.compareAndGetChanges} - notificationPane.notify( - IconTheme.JabRefIcons.SAVE.getGraphicNode(), + // The changes come from {@link + // org.jabref.gui.collab.DatabaseChangeList.compareAndGetChanges} + notificationPane.notify(IconTheme.JabRefIcons.SAVE.getGraphicNode(), Localization.lang("The library has been modified by another program."), List.of(new Action(Localization.lang("Dismiss changes"), _ -> notificationPane.hide()), new Action(Localization.lang("Review changes"), _ -> { - DatabaseChangesResolverDialog databaseChangesResolverDialog = new DatabaseChangesResolverDialog(changes, database, Localization.lang("External Changes Resolver")); - Optional areAllChangesResolved = dialogService.showCustomDialogAndWait(databaseChangesResolverDialog); + DatabaseChangesResolverDialog databaseChangesResolverDialog = new DatabaseChangesResolverDialog( + changes, database, Localization.lang("External Changes Resolver")); + Optional areAllChangesResolved = dialogService + .showCustomDialogAndWait(databaseChangesResolverDialog); saveState = stateManager.activeTabProperty().get().get(); final NamedCompound ce = new NamedCompound(Localization.lang("Merged external changes")); - changes.stream().filter(DatabaseChange::isAccepted).forEach(change -> change.applyChange(ce)); + changes.stream() + .filter(DatabaseChange::isAccepted) + .forEach(change -> change.applyChange(ce)); ce.end(); undoManager.addEdit(ce); if (areAllChangesResolved.get()) { if (databaseChangesResolverDialog.areAllChangesAccepted()) { - // In case all changes of the file on disk are merged into the current in-memory file, the file on disk does not differ from the in-memory file + // In case all changes of the file on disk are merged + // into the current in-memory file, the file on disk + // does not differ from the in-memory file saveState.resetChangedProperties(); - } else { + } + else { saveState.markBaseChanged(); } } @@ -101,16 +113,14 @@ private void notifyOnChange(List changes) { @Override public void fileUpdated() { synchronized (database) { - // File on disk has changed, thus look for notable changes and notify listeners in case there are such changes + // File on disk has changed, thus look for notable changes and notify + // listeners in case there are such changes ChangeScanner scanner = new ChangeScanner(database, dialogService, preferences); - BackgroundTask.wrap(scanner::scanForChanges) - .onSuccess(changes -> { - if (!changes.isEmpty()) { - listeners.forEach(listener -> listener.databaseChanged(changes)); - } - }) - .onFailure(e -> LOGGER.error("Error while watching for changes", e)) - .executeWith(taskExecutor); + BackgroundTask.wrap(scanner::scanForChanges).onSuccess(changes -> { + if (!changes.isEmpty()) { + listeners.forEach(listener -> listener.databaseChanged(changes)); + } + }).onFailure(e -> LOGGER.error("Error while watching for changes", e)).executeWith(taskExecutor); } } @@ -121,4 +131,5 @@ public void addListener(DatabaseChangeListener listener) { public void unregister() { database.getDatabasePath().ifPresent(file -> fileMonitor.removeListener(file, this)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeResolver.java b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeResolver.java index 83c318d4965..54f322bafc9 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeResolver.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeResolver.java @@ -6,6 +6,7 @@ import org.jabref.gui.collab.entrychange.EntryChangeResolver; public sealed abstract class DatabaseChangeResolver permits EntryChangeResolver { + protected final DialogService dialogService; protected DatabaseChangeResolver(DialogService dialogService) { @@ -13,4 +14,5 @@ protected DatabaseChangeResolver(DialogService dialogService) { } public abstract Optional askUserToResolveChange(); + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeResolverFactory.java b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeResolverFactory.java index 401d119d8ea..80100810310 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeResolverFactory.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangeResolverFactory.java @@ -9,11 +9,15 @@ import org.jabref.model.database.BibDatabaseContext; public class DatabaseChangeResolverFactory { + private final DialogService dialogService; + private final BibDatabaseContext databaseContext; + private final GuiPreferences preferences; - public DatabaseChangeResolverFactory(DialogService dialogService, BibDatabaseContext databaseContext, GuiPreferences preferences) { + public DatabaseChangeResolverFactory(DialogService dialogService, BibDatabaseContext databaseContext, + GuiPreferences preferences) { this.dialogService = dialogService; this.databaseContext = databaseContext; this.preferences = preferences; @@ -26,4 +30,5 @@ public Optional create(DatabaseChange change) { return Optional.empty(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangesResolverDialog.java b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangesResolverDialog.java index 3c0be42e6ed..ff549eb0dbb 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangesResolverDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/DatabaseChangesResolverDialog.java @@ -30,59 +30,79 @@ import org.slf4j.LoggerFactory; public class DatabaseChangesResolverDialog extends BaseDialog { + private final static Logger LOGGER = LoggerFactory.getLogger(DatabaseChangesResolverDialog.class); + /** - * Reconstructing the details view to preview an {@link DatabaseChange} every time it's selected is a heavy operation. - * It is also useless because changes are static and if the change data is static then the view doesn't have to change - * either. This cache is used to ensure that we only create the detail view instance once for each {@link DatabaseChange}. + * Reconstructing the details view to preview an {@link DatabaseChange} every time + * it's selected is a heavy operation. It is also useless because changes are static + * and if the change data is static then the view doesn't have to change either. This + * cache is used to ensure that we only create the detail view instance once for each + * {@link DatabaseChange}. */ private final Map DETAILS_VIEW_CACHE = new HashMap<>(); @FXML private TableView changesTableView; + @FXML private TableColumn changeName; + @FXML private Button askUserToResolveChangeButton; + @FXML private BorderPane changeInfoPane; private final List changes; + private final BibDatabaseContext database; private ExternalChangesResolverViewModel viewModel; private boolean areAllChangesAccepted; + private boolean areAllChangesDenied; - @Inject private UndoManager undoManager; - @Inject private DialogService dialogService; - @Inject private GuiPreferences preferences; - @Inject private ThemeManager themeManager; - @Inject private BibEntryTypesManager entryTypesManager; - @Inject private TaskExecutor taskExecutor; + @Inject + private UndoManager undoManager; + + @Inject + private DialogService dialogService; + + @Inject + private GuiPreferences preferences; + + @Inject + private ThemeManager themeManager; + + @Inject + private BibEntryTypesManager entryTypesManager; + + @Inject + private TaskExecutor taskExecutor; /** - * A dialog going through given changes, which are diffs to the provided database. - * Each accepted change is written to the provided database. - * + * A dialog going through given changes, which are diffs to the provided + * database. Each accepted change is written to the provided + * database. * @param changes The list of changes * @param database The database to apply the changes to */ - public DatabaseChangesResolverDialog(List changes, BibDatabaseContext database, String dialogTitle) { + public DatabaseChangesResolverDialog(List changes, BibDatabaseContext database, + String dialogTitle) { this.changes = changes; this.database = database; this.setTitle(dialogTitle); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); this.setResultConverter(button -> { if (viewModel.areAllChangesResolved()) { LOGGER.info("External changes are resolved successfully"); return true; - } else { + } + else { LOGGER.info("External changes aren't resolved"); return false; } @@ -101,7 +121,8 @@ public boolean areAllChangesDenied() { private void initialize() { PreviewViewer previewViewer = new PreviewViewer(dialogService, preferences, themeManager, taskExecutor); previewViewer.setDatabaseContext(database); - DatabaseChangeDetailsViewFactory databaseChangeDetailsViewFactory = new DatabaseChangeDetailsViewFactory(database, dialogService, themeManager, preferences, entryTypesManager, previewViewer, taskExecutor); + DatabaseChangeDetailsViewFactory databaseChangeDetailsViewFactory = new DatabaseChangeDetailsViewFactory( + database, dialogService, themeManager, preferences, entryTypesManager, previewViewer, taskExecutor); viewModel = new ExternalChangesResolverViewModel(changes, undoManager); @@ -116,7 +137,8 @@ private void initialize() { viewModel.selectedChangeProperty().bind(changesTableView.getSelectionModel().selectedItemProperty()); EasyBind.subscribe(viewModel.selectedChangeProperty(), selectedChange -> { if (selectedChange != null) { - DatabaseChangeDetailsView detailsView = DETAILS_VIEW_CACHE.computeIfAbsent(selectedChange, databaseChangeDetailsViewFactory::create); + DatabaseChangeDetailsView detailsView = DETAILS_VIEW_CACHE.computeIfAbsent(selectedChange, + databaseChangeDetailsViewFactory::create); changeInfoPane.setCenter(detailsView); } }); @@ -142,7 +164,10 @@ public void acceptChanges() { @FXML public void askUserToResolveChange() { - viewModel.getSelectedChange().flatMap(DatabaseChange::getExternalChangeResolver) - .flatMap(DatabaseChangeResolver::askUserToResolveChange).ifPresent(viewModel::acceptMergedChange); + viewModel.getSelectedChange() + .flatMap(DatabaseChange::getExternalChangeResolver) + .flatMap(DatabaseChangeResolver::askUserToResolveChange) + .ifPresent(viewModel::acceptMergedChange); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java b/jabgui/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java index 1970921b3dd..6ceae45a738 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java @@ -25,8 +25,9 @@ public class ExternalChangesResolverViewModel extends AbstractViewModel { private final ObservableList visibleChanges = FXCollections.observableArrayList(); /** - * Because visible changes list will be bound to the UI, certain changes can be removed. This list is used to keep - * track of changes even when they're removed from the UI. + * Because visible changes list will be bound to the UI, certain changes can be + * removed. This list is used to keep track of changes even when they're removed from + * the UI. */ private final ObservableList changes = FXCollections.observableArrayList(); @@ -51,9 +52,13 @@ public ExternalChangesResolverViewModel(List externalChanges, Un this.undoManager = undoManager; areAllChangesResolved = Bindings.createBooleanBinding(visibleChanges::isEmpty, visibleChanges); - areAllChangesAccepted = Bindings.createBooleanBinding(() -> changes.stream().allMatch(DatabaseChange::isAccepted)); - areAllChangesDenied = Bindings.createBooleanBinding(() -> changes.stream().noneMatch(DatabaseChange::isAccepted)); - canAskUserToResolveChange = Bindings.createBooleanBinding(() -> selectedChange.isNotNull().get() && selectedChange.get().getExternalChangeResolver().isPresent(), selectedChange); + areAllChangesAccepted = Bindings + .createBooleanBinding(() -> changes.stream().allMatch(DatabaseChange::isAccepted)); + areAllChangesDenied = Bindings + .createBooleanBinding(() -> changes.stream().noneMatch(DatabaseChange::isAccepted)); + canAskUserToResolveChange = Bindings.createBooleanBinding( + () -> selectedChange.isNotNull().get() && selectedChange.get().getExternalChangeResolver().isPresent(), + selectedChange); } public ObservableList getVisibleChanges() { @@ -117,4 +122,5 @@ public void acceptMergedChange(DatabaseChange databaseChange) { getVisibleChanges().remove(oldChange); }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/entryadd/EntryAdd.java b/jabgui/src/main/java/org/jabref/gui/collab/entryadd/EntryAdd.java index abf8ae633bf..635235744e1 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/entryadd/EntryAdd.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/entryadd/EntryAdd.java @@ -9,14 +9,16 @@ import org.jabref.model.entry.BibEntry; public final class EntryAdd extends DatabaseChange { + private final BibEntry addedEntry; - public EntryAdd(BibEntry addedEntry, BibDatabaseContext databaseContext, DatabaseChangeResolverFactory databaseChangeResolverFactory) { + public EntryAdd(BibEntry addedEntry, BibDatabaseContext databaseContext, + DatabaseChangeResolverFactory databaseChangeResolverFactory) { super(databaseContext, databaseChangeResolverFactory); this.addedEntry = addedEntry; setChangeName(addedEntry.getCitationKey() - .map(key -> Localization.lang("Added entry '%0'", key)) - .orElse(Localization.lang("Added entry"))); + .map(key -> Localization.lang("Added entry '%0'", key)) + .orElse(Localization.lang("Added entry"))); } @Override @@ -28,4 +30,5 @@ public void applyChange(NamedCompound undoEdit) { public BibEntry getAddedEntry() { return addedEntry; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/entrychange/EntryChange.java b/jabgui/src/main/java/org/jabref/gui/collab/entrychange/EntryChange.java index 44189beef93..ee282c47ddf 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/entrychange/EntryChange.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/entrychange/EntryChange.java @@ -12,15 +12,19 @@ import org.jabref.model.entry.BibEntry; public final class EntryChange extends DatabaseChange { + private final BibEntry oldEntry; + private final BibEntry newEntry; - public EntryChange(BibEntry oldEntry, BibEntry newEntry, BibDatabaseContext databaseContext, DatabaseChangeResolverFactory databaseChangeResolverFactory) { + public EntryChange(BibEntry oldEntry, BibEntry newEntry, BibDatabaseContext databaseContext, + DatabaseChangeResolverFactory databaseChangeResolverFactory) { super(databaseContext, databaseChangeResolverFactory); this.oldEntry = oldEntry; this.newEntry = newEntry; - setChangeName(oldEntry.getCitationKey().map(key -> Localization.lang("Modified entry '%0'", key)) - .orElse(Localization.lang("Modified entry"))); + setChangeName(oldEntry.getCitationKey() + .map(key -> Localization.lang("Modified entry '%0'", key)) + .orElse(Localization.lang("Modified entry"))); } public EntryChange(BibEntry oldEntry, BibEntry newEntry, BibDatabaseContext databaseContext) { @@ -46,4 +50,5 @@ public void applyChange(NamedCompound undoEdit) { undoEdit.addEdit(changeEntryEdit); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeDetailsView.java b/jabgui/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeDetailsView.java index 552e2e7bdb3..dde0fc0744c 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeDetailsView.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeDetailsView.java @@ -23,17 +23,12 @@ import com.tobiasdiez.easybind.EasyBind; public final class EntryChangeDetailsView extends DatabaseChangeDetailsView { + private boolean scrolling = false; - public EntryChangeDetailsView(BibEntry oldEntry, - BibEntry newEntry, - BibDatabaseContext databaseContext, - DialogService dialogService, - ThemeManager themeManager, - GuiPreferences preferences, - BibEntryTypesManager entryTypesManager, - PreviewViewer previewViewer, - TaskExecutor taskExecutor) { + public EntryChangeDetailsView(BibEntry oldEntry, BibEntry newEntry, BibDatabaseContext databaseContext, + DialogService dialogService, ThemeManager themeManager, GuiPreferences preferences, + BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer, TaskExecutor taskExecutor) { Label inJabRef = new Label(Localization.lang("In JabRef")); inJabRef.getStyleClass().add("lib-change-header"); Label onDisk = new Label(Localization.lang("On disk")); @@ -51,13 +46,14 @@ public EntryChangeDetailsView(BibEntry oldEntry, // TODO: Also sync scrolling for BibTeX view. PreviewWithSourceTab oldPreviewWithSourcesTab = new PreviewWithSourceTab(); - TabPane oldEntryTabPane = oldPreviewWithSourcesTab.getPreviewWithSourceTab(oldEntry, databaseContext, preferences, entryTypesManager, previewClone, Localization.lang("Entry Preview")); + TabPane oldEntryTabPane = oldPreviewWithSourcesTab.getPreviewWithSourceTab(oldEntry, databaseContext, + preferences, entryTypesManager, previewClone, Localization.lang("Entry Preview")); PreviewWithSourceTab newPreviewWithSourcesTab = new PreviewWithSourceTab(); - TabPane newEntryTabPane = newPreviewWithSourcesTab.getPreviewWithSourceTab(newEntry, databaseContext, preferences, entryTypesManager, previewViewer, Localization.lang("Entry Preview")); + TabPane newEntryTabPane = newPreviewWithSourcesTab.getPreviewWithSourceTab(newEntry, databaseContext, + preferences, entryTypesManager, previewViewer, Localization.lang("Entry Preview")); - EasyBind.subscribe( - oldEntryTabPane.getSelectionModel().selectedIndexProperty(), - selectedIndex -> newEntryTabPane.getSelectionModel().select(selectedIndex.intValue())); + EasyBind.subscribe(oldEntryTabPane.getSelectionModel().selectedIndexProperty(), + selectedIndex -> newEntryTabPane.getSelectionModel().select(selectedIndex.intValue())); EasyBind.subscribe(newEntryTabPane.getSelectionModel().selectedIndexProperty(), selectedIndex -> { if (oldEntryTabPane.getSelectionModel().getSelectedIndex() != selectedIndex.intValue()) { @@ -86,11 +82,13 @@ private void synchronizeScrolling(WebView webView, WebView otherWebView) { int value = (Integer) webView.getEngine().executeScript("window.scrollY"); otherWebView.getEngine().executeScript("window.scrollTo(0, " + value + ")"); } - } else { + } + else { otherWebView.fireEvent(event.copyFor(otherWebView, otherWebView)); } scrolling = false; } }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeResolver.java b/jabgui/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeResolver.java index fd5ea51eb11..21947327a8d 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeResolver.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeResolver.java @@ -15,12 +15,15 @@ import org.jabref.model.database.BibDatabaseContext; public final class EntryChangeResolver extends DatabaseChangeResolver { + private final EntryChange entryChange; + private final BibDatabaseContext databaseContext; private final GuiPreferences preferences; - public EntryChangeResolver(EntryChange entryChange, DialogService dialogService, BibDatabaseContext databaseContext, GuiPreferences preferences) { + public EntryChangeResolver(EntryChange entryChange, DialogService dialogService, BibDatabaseContext databaseContext, + GuiPreferences preferences) { super(dialogService); this.entryChange = entryChange; this.databaseContext = databaseContext; @@ -29,20 +32,18 @@ public EntryChangeResolver(EntryChange entryChange, DialogService dialogService, @Override public Optional askUserToResolveChange() { - MergeEntriesDialog mergeEntriesDialog = new MergeEntriesDialog(entryChange.getOldEntry(), entryChange.getNewEntry(), preferences); + MergeEntriesDialog mergeEntriesDialog = new MergeEntriesDialog(entryChange.getOldEntry(), + entryChange.getNewEntry(), preferences); mergeEntriesDialog.setLeftHeaderText(Localization.lang("In JabRef")); mergeEntriesDialog.setRightHeaderText(Localization.lang("On disk")); - mergeEntriesDialog.configureDiff(new ShowDiffConfig(ThreeWayMergeToolbar.DiffView.SPLIT, BasicDiffMethod.WORDS)); + mergeEntriesDialog + .configureDiff(new ShowDiffConfig(ThreeWayMergeToolbar.DiffView.SPLIT, BasicDiffMethod.WORDS)); - return dialogService.showCustomDialogAndWait(mergeEntriesDialog) - .map(this::mapMergeResultToExternalChange); + return dialogService.showCustomDialogAndWait(mergeEntriesDialog).map(this::mapMergeResultToExternalChange); } private EntryChange mapMergeResultToExternalChange(EntriesMergeResult entriesMergeResult) { - return new EntryChange( - entryChange.getOldEntry(), - entriesMergeResult.mergedEntry(), - databaseContext - ); + return new EntryChange(entryChange.getOldEntry(), entriesMergeResult.mergedEntry(), databaseContext); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/entrychange/EntryWithPreviewAndSourceDetailsView.java b/jabgui/src/main/java/org/jabref/gui/collab/entrychange/EntryWithPreviewAndSourceDetailsView.java index a4720a5f29b..d7c8b1465fb 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/entrychange/EntryWithPreviewAndSourceDetailsView.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/entrychange/EntryWithPreviewAndSourceDetailsView.java @@ -13,8 +13,10 @@ public final class EntryWithPreviewAndSourceDetailsView extends DatabaseChangeDe private final PreviewWithSourceTab previewWithSourceTab = new PreviewWithSourceTab(); - public EntryWithPreviewAndSourceDetailsView(BibEntry entry, BibDatabaseContext bibDatabaseContext, GuiPreferences preferences, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer) { - TabPane tabPanePreviewCode = previewWithSourceTab.getPreviewWithSourceTab(entry, bibDatabaseContext, preferences, entryTypesManager, previewViewer); + public EntryWithPreviewAndSourceDetailsView(BibEntry entry, BibDatabaseContext bibDatabaseContext, + GuiPreferences preferences, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer) { + TabPane tabPanePreviewCode = previewWithSourceTab.getPreviewWithSourceTab(entry, bibDatabaseContext, + preferences, entryTypesManager, previewViewer); setLeftAnchor(tabPanePreviewCode, 8d); setTopAnchor(tabPanePreviewCode, 8d); setRightAnchor(tabPanePreviewCode, 8d); @@ -22,4 +24,5 @@ public EntryWithPreviewAndSourceDetailsView(BibEntry entry, BibDatabaseContext b getChildren().setAll(tabPanePreviewCode); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/entrychange/PreviewWithSourceTab.java b/jabgui/src/main/java/org/jabref/gui/collab/entrychange/PreviewWithSourceTab.java index 385d9d73ddd..fc3903bcd6d 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/entrychange/PreviewWithSourceTab.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/entrychange/PreviewWithSourceTab.java @@ -28,11 +28,14 @@ public class PreviewWithSourceTab { private static final Logger LOGGER = LoggerFactory.getLogger(PreviewWithSourceTab.class); - public TabPane getPreviewWithSourceTab(BibEntry entry, BibDatabaseContext bibDatabaseContext, GuiPreferences preferences, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer) { + public TabPane getPreviewWithSourceTab(BibEntry entry, BibDatabaseContext bibDatabaseContext, + GuiPreferences preferences, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer) { return getPreviewWithSourceTab(entry, bibDatabaseContext, preferences, entryTypesManager, previewViewer, ""); } - public TabPane getPreviewWithSourceTab(BibEntry entry, BibDatabaseContext bibDatabaseContext, GuiPreferences preferences, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer, String label) { + public TabPane getPreviewWithSourceTab(BibEntry entry, BibDatabaseContext bibDatabaseContext, + GuiPreferences preferences, BibEntryTypesManager entryTypesManager, PreviewViewer previewViewer, + String label) { previewViewer.setLayout(preferences.getPreviewPreferences().getSelectedPreviewLayout()); previewViewer.setEntry(entry); @@ -47,27 +50,33 @@ public TabPane getPreviewWithSourceTab(BibEntry entry, BibDatabaseContext bibDat if (StringUtil.isNullOrEmpty(label)) { previewTab.setText(Localization.lang("Entry preview")); - } else { + } + else { previewTab.setText(label); } try { - codeArea.appendText(getSourceString(entry, bibDatabaseContext.getMode(), preferences.getFieldPreferences(), entryTypesManager)); - } catch (IOException e) { + codeArea.appendText(getSourceString(entry, bibDatabaseContext.getMode(), preferences.getFieldPreferences(), + entryTypesManager)); + } + catch (IOException e) { LOGGER.error("Error getting Bibtex: {}", entry); } codeArea.setEditable(false); - Tab codeTab = new Tab(Localization.lang("%0 source", bibDatabaseContext.getMode().getFormattedName()), codeArea); + Tab codeTab = new Tab(Localization.lang("%0 source", bibDatabaseContext.getMode().getFormattedName()), + codeArea); tabPanePreviewCode.getTabs().addAll(previewTab, codeTab); return tabPanePreviewCode; } - private String getSourceString(BibEntry entry, BibDatabaseMode type, FieldPreferences fieldPreferences, BibEntryTypesManager entryTypesManager) throws IOException { + private String getSourceString(BibEntry entry, BibDatabaseMode type, FieldPreferences fieldPreferences, + BibEntryTypesManager entryTypesManager) throws IOException { StringWriter writer = new StringWriter(); BibWriter bibWriter = new BibWriter(writer, OS.NEWLINE); FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldPreferences); new BibEntryWriter(fieldWriter, entryTypesManager).write(entry, bibWriter, type); return writer.toString(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/entrydelete/EntryDelete.java b/jabgui/src/main/java/org/jabref/gui/collab/entrydelete/EntryDelete.java index 36116bb5845..b14b1e3d0d3 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/entrydelete/EntryDelete.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/entrydelete/EntryDelete.java @@ -9,14 +9,16 @@ import org.jabref.model.entry.BibEntry; public final class EntryDelete extends DatabaseChange { + private final BibEntry deletedEntry; - public EntryDelete(BibEntry deletedEntry, BibDatabaseContext databaseContext, DatabaseChangeResolverFactory databaseChangeResolverFactory) { + public EntryDelete(BibEntry deletedEntry, BibDatabaseContext databaseContext, + DatabaseChangeResolverFactory databaseChangeResolverFactory) { super(databaseContext, databaseChangeResolverFactory); this.deletedEntry = deletedEntry; setChangeName(deletedEntry.getCitationKey() - .map(key -> Localization.lang("Deleted entry '%0'", key)) - .orElse(Localization.lang("Deleted entry"))); + .map(key -> Localization.lang("Deleted entry '%0'", key)) + .orElse(Localization.lang("Deleted entry"))); } @Override @@ -28,4 +30,5 @@ public void applyChange(NamedCompound undoEdit) { public BibEntry getDeletedEntry() { return deletedEntry; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/groupchange/GroupChange.java b/jabgui/src/main/java/org/jabref/gui/collab/groupchange/GroupChange.java index 4444d0f79c3..0d9e3d1fa78 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/groupchange/GroupChange.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/groupchange/GroupChange.java @@ -12,13 +12,15 @@ import org.jabref.model.groups.GroupTreeNode; public final class GroupChange extends DatabaseChange { + private final GroupDiff groupDiff; - public GroupChange(GroupDiff groupDiff, BibDatabaseContext databaseContext, DatabaseChangeResolverFactory databaseChangeResolverFactory) { + public GroupChange(GroupDiff groupDiff, BibDatabaseContext databaseContext, + DatabaseChangeResolverFactory databaseChangeResolverFactory) { super(databaseContext, databaseChangeResolverFactory); this.groupDiff = groupDiff; - setChangeName(groupDiff.getOriginalGroupRoot() == null ? Localization.lang("Removed all groups") : Localization - .lang("Modified groups tree")); + setChangeName(groupDiff.getOriginalGroupRoot() == null ? Localization.lang("Removed all groups") + : Localization.lang("Modified groups tree")); } @Override @@ -39,7 +41,8 @@ public void applyChange(NamedCompound undoEdit) { if (newRoot == null) { // I think setting root to null is not possible root.setGroup(DefaultGroupsFactory.getAllEntriesGroup(), false, false, null); - } else { + } + else { // change root group, even though it'll be AllEntries anyway root.setGroup(newRoot.getGroup(), false, false, null); for (GroupTreeNode child : newRoot.getChildren()) { @@ -53,4 +56,5 @@ public void applyChange(NamedCompound undoEdit) { public GroupDiff getGroupDiff() { return groupDiff; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/groupchange/GroupChangeDetailsView.java b/jabgui/src/main/java/org/jabref/gui/collab/groupchange/GroupChangeDetailsView.java index f29b929ad43..9b8db580fa8 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/groupchange/GroupChangeDetailsView.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/groupchange/GroupChangeDetailsView.java @@ -11,12 +11,16 @@ public GroupChangeDetailsView(GroupChange groupChange) { String labelValue; if (groupChange.getGroupDiff().getNewGroupRoot() == null) { labelValue = groupChange.getName() + '.'; - } else { - labelValue = Localization.lang("%0. Accepting the change replaces the complete groups tree with the externally modified groups tree.", groupChange.getName()); + } + else { + labelValue = Localization.lang( + "%0. Accepting the change replaces the complete groups tree with the externally modified groups tree.", + groupChange.getName()); } Label label = new Label(labelValue); label.setWrapText(true); this.setAllAnchorsAndAttachChild(label); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChange.java b/jabgui/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChange.java index 571fe74b752..d9e75d0fa5d 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChange.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChange.java @@ -8,9 +8,11 @@ import org.jabref.model.database.BibDatabaseContext; public final class MetadataChange extends DatabaseChange { + private final MetaDataDiff metaDataDiff; - public MetadataChange(MetaDataDiff metaDataDiff, BibDatabaseContext databaseContext, DatabaseChangeResolverFactory databaseChangeResolverFactory) { + public MetadataChange(MetaDataDiff metaDataDiff, BibDatabaseContext databaseContext, + DatabaseChangeResolverFactory databaseChangeResolverFactory) { super(databaseContext, databaseChangeResolverFactory); this.metaDataDiff = metaDataDiff; setChangeName(Localization.lang("Metadata change")); @@ -20,13 +22,15 @@ public MetadataChange(MetaDataDiff metaDataDiff, BibDatabaseContext databaseCont public void applyChange(NamedCompound undoEdit) { // TODO: Metadata edit should be undoable databaseContext.setMetaData(metaDataDiff.getNewMetaData()); - // group change is handled by GroupChange, so we set the groups root to the original value + // group change is handled by GroupChange, so we set the groups root to the + // original value // to prevent any inconsistency metaDataDiff.getGroupDifferences() - .ifPresent(groupDiff -> databaseContext.getMetaData().setGroups(groupDiff.getOriginalGroupRoot())); + .ifPresent(groupDiff -> databaseContext.getMetaData().setGroups(groupDiff.getOriginalGroupRoot())); } public MetaDataDiff getMetaDataDiff() { return metaDataDiff; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChangeDetailsView.java b/jabgui/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChangeDetailsView.java index 09600d4c756..474ec0cb57e 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChangeDetailsView.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChangeDetailsView.java @@ -19,7 +19,8 @@ public final class MetadataChangeDetailsView extends DatabaseChangeDetailsView { - public MetadataChangeDetailsView(MetadataChange metadataChange, GlobalCitationKeyPatterns globalCitationKeyPatterns) { + public MetadataChangeDetailsView(MetadataChange metadataChange, + GlobalCitationKeyPatterns globalCitationKeyPatterns) { VBox container = new VBox(15); Label header = new Label(Localization.lang("The following metadata changed:")); @@ -27,7 +28,8 @@ public MetadataChangeDetailsView(MetadataChange metadataChange, GlobalCitationKe container.getChildren().add(header); // Add views for each detected difference - for (MetaDataDiff.Difference diff : metadataChange.getMetaDataDiff().getDifferences(globalCitationKeyPatterns)) { + for (MetaDataDiff.Difference diff : metadataChange.getMetaDataDiff() + .getDifferences(globalCitationKeyPatterns)) { addDifferenceView(container, diff, metadataChange); } @@ -35,9 +37,8 @@ public MetadataChangeDetailsView(MetadataChange metadataChange, GlobalCitationKe } /** - * Adds a view for a specific metadata difference to the container. - * Default view if not a group diff. - * + * Adds a view for a specific metadata difference to the container. Default view if + * not a group diff. * @param container The parent container to add the difference view to * @param diff The metadata difference to display * @param metadataChange The metadata change object containing all changes @@ -50,14 +51,14 @@ private void addDifferenceView(VBox container, MetaDataDiff.Difference diff, Met // Show appropriate view based on difference type if (diff.differenceType() == MetaDataDiff.DifferenceType.GROUPS) { container.getChildren().add(createGroupDiffSplitPane(metadataChange)); - } else { + } + else { container.getChildren().add(createDefaultDiffScrollPane(diff)); } } /** * Creates a scroll pane showing simple text differences. - * * @param diff The difference to display * @return Configured ScrollPane showing the difference */ @@ -75,7 +76,6 @@ private ScrollPane createDefaultDiffScrollPane(MetaDataDiff.Difference diff) { /** * Creates a split pane showing differences in groups tree structure. - * * @param metadataChange The metadata change containing groups differences * @return Configured SplitPane showing groups differences */ @@ -89,11 +89,8 @@ private SplitPane createGroupDiffSplitPane(MetadataChange metadataChange) { jabrefTextArea.replaceText(jabRefContent); diskTextArea.replaceText(diskContent); - SplitDiffHighlighter highlighter = new SplitDiffHighlighter( - jabrefTextArea, - diskTextArea, - DiffHighlighter.BasicDiffMethod.CHARS - ); + SplitDiffHighlighter highlighter = new SplitDiffHighlighter(jabrefTextArea, diskTextArea, + DiffHighlighter.BasicDiffMethod.CHARS); highlighter.highlight(); ScrollPane leftScrollPane = createScrollPane(jabrefTextArea); @@ -122,7 +119,6 @@ private SplitPane createGroupDiffSplitPane(MetadataChange metadataChange) { /** * Creates a configured scroll pane for a text area. - * * @param textArea The text area to wrap in a scroll pane * @return Configured ScrollPane */ @@ -138,7 +134,6 @@ private ScrollPane createScrollPane(StyleClassedTextArea textArea) { /** * Creates a configured text area for displaying diff content. - * * @return Configured StyleClassedTextArea */ private StyleClassedTextArea createConfiguredTextArea() { @@ -151,19 +146,15 @@ private StyleClassedTextArea createConfiguredTextArea() { /** * Extracts the groups tree content from metadata as a string. - * * @param metadata The metadata containing groups * @return String representation of groups tree, or empty string if no groups */ private String getMetadataGroupsContent(MetaData metadata) { - return metadata.getGroups() - .map(this::convertGroupTreeToString) - .orElse(""); + return metadata.getGroups().map(this::convertGroupTreeToString).orElse(""); } /** * Converts a group tree to a string representation with indentation. - * * @param node The root node of the group tree * @return String representation of the group tree */ @@ -175,15 +166,12 @@ private String convertGroupTreeToString(GroupTreeNode node) { /** * Recursively appends a group tree node to the string builder. - * * @param node The current node to append * @param builder The string builder to append to * @param level The current depth level in the tree (for indentation) */ private void appendGroupTreeNode(GroupTreeNode node, StringBuilder builder, int level) { - builder.append("| ".repeat(level)) - .append(node.getName()) - .append("\n"); + builder.append("| ".repeat(level)).append(node.getName()).append("\n"); for (GroupTreeNode child : node.getChildren()) { appendGroupTreeNode(child, builder, level + 1); @@ -192,30 +180,19 @@ private void appendGroupTreeNode(GroupTreeNode node, StringBuilder builder, int private String getDifferenceString(MetaDataDiff.DifferenceType changeType) { return switch (changeType) { - case PROTECTED -> - Localization.lang("Library protection"); - case GROUPS -> - Localization.lang("Modified groups tree"); - case ENCODING -> - Localization.lang("Library encoding"); - case SAVE_SORT_ORDER -> - Localization.lang("Save sort order"); - case KEY_PATTERNS -> - Localization.lang("Key patterns"); - case USER_FILE_DIRECTORY -> - Localization.lang("User-specific file directory"); - case LATEX_FILE_DIRECTORY -> - Localization.lang("LaTeX file directory"); - case DEFAULT_KEY_PATTERN -> - Localization.lang("Default pattern"); - case SAVE_ACTIONS -> - Localization.lang("Save actions"); - case MODE -> - Localization.lang("Library mode"); - case LIBRARY_SPECIFIC_FILE_DIRECTORY -> - Localization.lang("Library-specific file directory"); - case CONTENT_SELECTOR -> - Localization.lang("Content selectors"); + case PROTECTED -> Localization.lang("Library protection"); + case GROUPS -> Localization.lang("Modified groups tree"); + case ENCODING -> Localization.lang("Library encoding"); + case SAVE_SORT_ORDER -> Localization.lang("Save sort order"); + case KEY_PATTERNS -> Localization.lang("Key patterns"); + case USER_FILE_DIRECTORY -> Localization.lang("User-specific file directory"); + case LATEX_FILE_DIRECTORY -> Localization.lang("LaTeX file directory"); + case DEFAULT_KEY_PATTERN -> Localization.lang("Default pattern"); + case SAVE_ACTIONS -> Localization.lang("Save actions"); + case MODE -> Localization.lang("Library mode"); + case LIBRARY_SPECIFIC_FILE_DIRECTORY -> Localization.lang("Library-specific file directory"); + case CONTENT_SELECTOR -> Localization.lang("Content selectors"); }; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/preamblechange/PreambleChange.java b/jabgui/src/main/java/org/jabref/gui/collab/preamblechange/PreambleChange.java index 5789d2bbbcc..4d5a08ca0c2 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/preamblechange/PreambleChange.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/preamblechange/PreambleChange.java @@ -12,11 +12,13 @@ import org.slf4j.LoggerFactory; public final class PreambleChange extends DatabaseChange { + private static final Logger LOGGER = LoggerFactory.getLogger(PreambleChange.class); private final PreambleDiff preambleDiff; - public PreambleChange(PreambleDiff preambleDiff, BibDatabaseContext databaseContext, DatabaseChangeResolverFactory databaseChangeResolverFactory) { + public PreambleChange(PreambleDiff preambleDiff, BibDatabaseContext databaseContext, + DatabaseChangeResolverFactory databaseChangeResolverFactory) { super(databaseContext, databaseChangeResolverFactory); this.preambleDiff = preambleDiff; @@ -26,10 +28,12 @@ public PreambleChange(PreambleDiff preambleDiff, BibDatabaseContext databaseCont @Override public void applyChange(NamedCompound undoEdit) { databaseContext.getDatabase().setPreamble(preambleDiff.getNewPreamble()); - undoEdit.addEdit(new UndoablePreambleChange(databaseContext.getDatabase(), preambleDiff.getOriginalPreamble(), preambleDiff.getNewPreamble())); + undoEdit.addEdit(new UndoablePreambleChange(databaseContext.getDatabase(), preambleDiff.getOriginalPreamble(), + preambleDiff.getNewPreamble())); } public PreambleDiff getPreambleDiff() { return preambleDiff; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/preamblechange/PreambleChangeDetailsView.java b/jabgui/src/main/java/org/jabref/gui/collab/preamblechange/PreambleChangeDetailsView.java index c49182f1d2e..55d2f3b4360 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/preamblechange/PreambleChangeDetailsView.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/preamblechange/PreambleChangeDetailsView.java @@ -19,15 +19,19 @@ public PreambleChangeDetailsView(PreambleChange preambleChange) { container.getChildren().add(header); if (StringUtil.isNotBlank(preambleDiff.getOriginalPreamble())) { - container.getChildren().add(new Label(Localization.lang("Current value: %0", preambleDiff.getOriginalPreamble()))); + container.getChildren() + .add(new Label(Localization.lang("Current value: %0", preambleDiff.getOriginalPreamble()))); } if (StringUtil.isNotBlank(preambleDiff.getNewPreamble())) { - container.getChildren().add(new Label(Localization.lang("Value set externally: %0", preambleDiff.getNewPreamble()))); - } else { + container.getChildren() + .add(new Label(Localization.lang("Value set externally: %0", preambleDiff.getNewPreamble()))); + } + else { container.getChildren().add(new Label(Localization.lang("Value cleared externally"))); } this.setAllAnchorsAndAttachChild(container); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/stringadd/BibTexStringAdd.java b/jabgui/src/main/java/org/jabref/gui/collab/stringadd/BibTexStringAdd.java index 98c311ca6dc..4b05ce9b7ac 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/stringadd/BibTexStringAdd.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/stringadd/BibTexStringAdd.java @@ -13,11 +13,13 @@ import org.slf4j.LoggerFactory; public final class BibTexStringAdd extends DatabaseChange { + private static final Logger LOGGER = LoggerFactory.getLogger(BibTexStringAdd.class); private final BibtexString addedString; - public BibTexStringAdd(BibtexString addedString, BibDatabaseContext databaseContext, DatabaseChangeResolverFactory databaseChangeResolverFactory) { + public BibTexStringAdd(BibtexString addedString, BibDatabaseContext databaseContext, + DatabaseChangeResolverFactory databaseChangeResolverFactory) { super(databaseContext, databaseChangeResolverFactory); this.addedString = addedString; setChangeName(Localization.lang("Added string: '%0'", addedString.getName())); @@ -28,7 +30,8 @@ public void applyChange(NamedCompound undoEdit) { try { databaseContext.getDatabase().addString(addedString); undoEdit.addEdit(new UndoableInsertString(databaseContext.getDatabase(), addedString)); - } catch (KeyCollisionException ex) { + } + catch (KeyCollisionException ex) { LOGGER.warn("Error: could not add string '{}': {}", addedString.getName(), ex.getMessage(), ex); } } @@ -36,4 +39,5 @@ public void applyChange(NamedCompound undoEdit) { public BibtexString getAddedString() { return addedString; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/stringadd/BibTexStringAddDetailsView.java b/jabgui/src/main/java/org/jabref/gui/collab/stringadd/BibTexStringAddDetailsView.java index ffca7b4d7ba..5e7133631d5 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/stringadd/BibTexStringAddDetailsView.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/stringadd/BibTexStringAddDetailsView.java @@ -12,12 +12,11 @@ public BibTexStringAddDetailsView(BibTexStringAdd stringAdd) { VBox container = new VBox(); Label header = new Label(Localization.lang("Added string")); header.getStyleClass().add("sectionHeader"); - container.getChildren().addAll( - header, - new Label(Localization.lang("Label: %0", stringAdd.getAddedString().getName())), - new Label(Localization.lang("Content: %0", stringAdd.getAddedString().getContent())) - ); + container.getChildren() + .addAll(header, new Label(Localization.lang("Label: %0", stringAdd.getAddedString().getName())), + new Label(Localization.lang("Content: %0", stringAdd.getAddedString().getContent()))); this.setAllAnchorsAndAttachChild(container); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/stringchange/BibTexStringChange.java b/jabgui/src/main/java/org/jabref/gui/collab/stringchange/BibTexStringChange.java index 8cb58e99d46..ac7688f65e4 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/stringchange/BibTexStringChange.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/stringchange/BibTexStringChange.java @@ -12,12 +12,15 @@ import org.slf4j.LoggerFactory; public final class BibTexStringChange extends DatabaseChange { + private static final Logger LOGGER = LoggerFactory.getLogger(BibTexStringChange.class); private final BibtexString oldString; + private final BibtexString newString; - public BibTexStringChange(BibtexString oldString, BibtexString newString, BibDatabaseContext databaseContext, DatabaseChangeResolverFactory databaseChangeResolverFactory) { + public BibTexStringChange(BibtexString oldString, BibtexString newString, BibDatabaseContext databaseContext, + DatabaseChangeResolverFactory databaseChangeResolverFactory) { super(databaseContext, databaseChangeResolverFactory); this.oldString = oldString; this.newString = newString; @@ -40,4 +43,5 @@ public BibtexString getOldString() { public BibtexString getNewString() { return newString; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/stringchange/BibTexStringChangeDetailsView.java b/jabgui/src/main/java/org/jabref/gui/collab/stringchange/BibTexStringChangeDetailsView.java index 880f5661ad8..4ae35e00f8c 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/stringchange/BibTexStringChangeDetailsView.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/stringchange/BibTexStringChangeDetailsView.java @@ -12,14 +12,14 @@ public BibTexStringChangeDetailsView(BibTexStringChange stringChange) { VBox container = new VBox(); Label header = new Label(Localization.lang("Modified string")); header.getStyleClass().add("sectionHeader"); - container.getChildren().addAll( - header, - new Label(Localization.lang("Label: %0", stringChange.getOldString().getName())), - new Label(Localization.lang("Content: %0", stringChange.getNewString().getContent())) - ); + container.getChildren() + .addAll(header, new Label(Localization.lang("Label: %0", stringChange.getOldString().getName())), + new Label(Localization.lang("Content: %0", stringChange.getNewString().getContent()))); - container.getChildren().add(new Label(Localization.lang("Current content: %0", stringChange.getOldString().getContent()))); + container.getChildren() + .add(new Label(Localization.lang("Current content: %0", stringChange.getOldString().getContent()))); this.setAllAnchorsAndAttachChild(container); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/stringdelete/BibTexStringDelete.java b/jabgui/src/main/java/org/jabref/gui/collab/stringdelete/BibTexStringDelete.java index 2f55cf7d331..e9b5e882c77 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/stringdelete/BibTexStringDelete.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/stringdelete/BibTexStringDelete.java @@ -12,11 +12,13 @@ import org.slf4j.LoggerFactory; public final class BibTexStringDelete extends DatabaseChange { + private static final Logger LOGGER = LoggerFactory.getLogger(BibTexStringDelete.class); private final BibtexString deletedString; - public BibTexStringDelete(BibtexString deletedString, BibDatabaseContext databaseContext, DatabaseChangeResolverFactory databaseChangeResolverFactory) { + public BibTexStringDelete(BibtexString deletedString, BibDatabaseContext databaseContext, + DatabaseChangeResolverFactory databaseChangeResolverFactory) { super(databaseContext, databaseChangeResolverFactory); this.deletedString = deletedString; setChangeName(Localization.lang("Deleted string: '%0'", deletedString.getName())); @@ -27,7 +29,8 @@ public void applyChange(NamedCompound undoEdit) { try { databaseContext.getDatabase().removeString(deletedString.getId()); undoEdit.addEdit(new UndoableRemoveString(databaseContext.getDatabase(), deletedString)); - } catch (Exception ex) { + } + catch (Exception ex) { LOGGER.warn("Error: could not remove string '{}': {}", deletedString.getName(), ex.getMessage(), ex); } } @@ -35,4 +38,5 @@ public void applyChange(NamedCompound undoEdit) { public BibtexString getDeletedString() { return deletedString; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/stringdelete/BibTexStringDeleteDetailsView.java b/jabgui/src/main/java/org/jabref/gui/collab/stringdelete/BibTexStringDeleteDetailsView.java index 31a63e5504c..9cd2eb9da9c 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/stringdelete/BibTexStringDeleteDetailsView.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/stringdelete/BibTexStringDeleteDetailsView.java @@ -12,12 +12,11 @@ public BibTexStringDeleteDetailsView(BibTexStringDelete stringDelete) { VBox container = new VBox(); Label header = new Label(Localization.lang("Deleted string")); header.getStyleClass().add("sectionHeader"); - container.getChildren().addAll( - header, - new Label(Localization.lang("Label: %0", stringDelete.getDeletedString().getName())), - new Label(Localization.lang("Content: %0", stringDelete.getDeletedString().getContent())) - ); + container.getChildren() + .addAll(header, new Label(Localization.lang("Label: %0", stringDelete.getDeletedString().getName())), + new Label(Localization.lang("Content: %0", stringDelete.getDeletedString().getContent()))); this.setAllAnchorsAndAttachChild(container); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/stringrename/BibTexStringRename.java b/jabgui/src/main/java/org/jabref/gui/collab/stringrename/BibTexStringRename.java index 0236ebda467..4eaa2debe37 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/stringrename/BibTexStringRename.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/stringrename/BibTexStringRename.java @@ -12,12 +12,15 @@ import org.slf4j.LoggerFactory; public final class BibTexStringRename extends DatabaseChange { + private static final Logger LOGGER = LoggerFactory.getLogger(BibTexStringRename.class); private final BibtexString oldString; + private final BibtexString newString; - public BibTexStringRename(BibtexString oldString, BibtexString newString, BibDatabaseContext databaseContext, DatabaseChangeResolverFactory databaseChangeResolverFactory) { + public BibTexStringRename(BibtexString oldString, BibtexString newString, BibDatabaseContext databaseContext, + DatabaseChangeResolverFactory databaseChangeResolverFactory) { super(databaseContext, databaseChangeResolverFactory); this.oldString = oldString; this.newString = newString; @@ -29,7 +32,8 @@ public BibTexStringRename(BibtexString oldString, BibtexString newString, BibDat public void applyChange(NamedCompound undoEdit) { if (databaseContext.getDatabase().hasStringByName(newString.getName())) { // The name to change to is already in the database, so we can't comply. - LOGGER.info("Cannot rename string '{}' to '{}' because the name is already in use", oldString.getName(), newString.getName()); + LOGGER.info("Cannot rename string '{}' to '{}' because the name is already in use", oldString.getName(), + newString.getName()); } String currentName = oldString.getName(); @@ -45,4 +49,5 @@ public BibtexString getOldString() { public BibtexString getNewString() { return newString; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/collab/stringrename/BibTexStringRenameDetailsView.java b/jabgui/src/main/java/org/jabref/gui/collab/stringrename/BibTexStringRenameDetailsView.java index fa826b08e8f..c114689c8cd 100644 --- a/jabgui/src/main/java/org/jabref/gui/collab/stringrename/BibTexStringRenameDetailsView.java +++ b/jabgui/src/main/java/org/jabref/gui/collab/stringrename/BibTexStringRenameDetailsView.java @@ -7,8 +7,10 @@ public final class BibTexStringRenameDetailsView extends DatabaseChangeDetailsView { public BibTexStringRenameDetailsView(BibTexStringRename stringRename) { - Label label = new Label(stringRename.getNewString().getName() + " : " + stringRename.getOldString().getContent()); + Label label = new Label( + stringRename.getNewString().getName() + " : " + stringRename.getOldString().getContent()); this.setAllAnchorsAndAttachChild(label); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternSuggestionCell.java b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternSuggestionCell.java index 950b9beade3..52aca9ceb20 100644 --- a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternSuggestionCell.java +++ b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternSuggestionCell.java @@ -20,6 +20,7 @@ import org.jabref.logic.l10n.Localization; public class CitationKeyPatternSuggestionCell extends TextFieldTableCell { + private final CitationKeyPatternSuggestionTextField searchField; public CitationKeyPatternSuggestionCell(List citationKeyPatterns) { @@ -54,20 +55,25 @@ public void updateItem(String item, boolean empty) { if (empty || item == null) { setGraphic(null); setText(null); - } else { + } + else { setText(item); } } static class CitationKeyPatternSuggestionTextField extends TextField { + private final List citationKeyPatterns; + private final ContextMenu suggestionsList; + private int heightOfMenuItem; public CitationKeyPatternSuggestionTextField(List citationKeyPatterns) { this.citationKeyPatterns = new ArrayList<>(citationKeyPatterns); this.suggestionsList = new ContextMenu(); - // Initial reasonable estimate before the menu items are populated. We overwrite this dynamically + // Initial reasonable estimate before the menu items are populated. We + // overwrite this dynamically this.heightOfMenuItem = 30; setListener(); @@ -78,10 +84,11 @@ private void setListener() { String enteredText = getText(); if (enteredText == null || enteredText.isEmpty()) { suggestionsList.hide(); - } else { + } + else { List filteredEntries = citationKeyPatterns.stream() - .filter(e -> e.toLowerCase().contains(enteredText.toLowerCase())) - .toList(); + .filter(e -> e.toLowerCase().contains(enteredText.toLowerCase())) + .toList(); if (!filteredEntries.isEmpty()) { populatePopup(filteredEntries); @@ -90,7 +97,8 @@ private void setListener() { double screenY = localToScreen(0, 0).getY() + getHeight(); suggestionsList.show(this, screenX, screenY); } - } else { + } + else { suggestionsList.hide(); } } @@ -125,7 +133,10 @@ private void populatePopup(List searchResult) { } if (!menuItems.isEmpty()) { - Platform.runLater(() -> heightOfMenuItem = (int) menuItems.getFirst().getContent().getBoundsInLocal().getHeight()); + Platform.runLater(() -> heightOfMenuItem = (int) menuItems.getFirst() + .getContent() + .getBoundsInLocal() + .getHeight()); } suggestionsList.getItems().clear(); @@ -157,17 +168,17 @@ public static double getAvailableSpaceBelow(TextField textField) { private Menu createPatternsSubMenu() { Menu patternsSubMenu = new Menu(Localization.lang("All patterns")); - Map> categorizedPatterns = - CitationKeyPattern.getAllPatterns().stream() - .collect(Collectors.groupingBy(CitationKeyPattern::getCategory)); + Map> categorizedPatterns = CitationKeyPattern + .getAllPatterns() + .stream() + .collect(Collectors.groupingBy(CitationKeyPattern::getCategory)); - Map categoryNames = Map.of( - CitationKeyPattern.Category.AUTHOR_RELATED, Localization.lang("Author related"), - CitationKeyPattern.Category.EDITOR_RELATED, Localization.lang("Editor related"), - CitationKeyPattern.Category.TITLE_RELATED, Localization.lang("Title related"), - CitationKeyPattern.Category.OTHER_FIELDS, Localization.lang("Other fields"), - CitationKeyPattern.Category.BIBENTRY_FIELDS, Localization.lang("Entry fields") - ); + Map categoryNames = Map.of(CitationKeyPattern.Category.AUTHOR_RELATED, + Localization.lang("Author related"), CitationKeyPattern.Category.EDITOR_RELATED, + Localization.lang("Editor related"), CitationKeyPattern.Category.TITLE_RELATED, + Localization.lang("Title related"), CitationKeyPattern.Category.OTHER_FIELDS, + Localization.lang("Other fields"), CitationKeyPattern.Category.BIBENTRY_FIELDS, + Localization.lang("Entry fields")); for (Map.Entry entry : categoryNames.entrySet()) { CitationKeyPattern.Category category = entry.getKey(); @@ -189,5 +200,7 @@ private Menu createPatternsSubMenu() { } return patternsSubMenu; } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanel.java b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanel.java index 2b044661236..d18305a9d34 100644 --- a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanel.java +++ b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanel.java @@ -26,29 +26,32 @@ public class CitationKeyPatternsPanel extends TableView { - @FXML public TableColumn entryTypeColumn; - @FXML public TableColumn patternColumn; - @FXML public TableColumn actionsColumn; + @FXML + public TableColumn entryTypeColumn; + + @FXML + public TableColumn patternColumn; + + @FXML + public TableColumn actionsColumn; - @Inject private CliPreferences preferences; + @Inject + private CliPreferences preferences; private CitationKeyPatternsPanelViewModel viewModel; private long lastKeyPressTime; + private String tableSearchTerm; + private final ObservableList patterns; public CitationKeyPatternsPanel() { super(); this.patterns = FXCollections.observableArrayList( - CitationKeyPattern.getAllPatterns().stream() - .map(CitationKeyPattern::stringRepresentation) - .toList() - ); - - ViewLoader.view(this) - .root(this) - .load(); + CitationKeyPattern.getAllPatterns().stream().map(CitationKeyPattern::stringRepresentation).toList()); + + ViewLoader.view(this).root(this).load(); } @FXML @@ -60,11 +63,10 @@ private void initialize() { entryTypeColumn.setSortable(true); entryTypeColumn.setReorderable(false); entryTypeColumn.setCellValueFactory(cellData -> cellData.getValue().entryType()); - new ValueTableCellFactory() - .withText(EntryType::getDisplayName) - .install(entryTypeColumn); - this.setOnSort(event -> - viewModel.patternListProperty().sort(CitationKeyPatternsPanelViewModel.defaultOnTopComparator)); + new ValueTableCellFactory().withText(EntryType::getDisplayName) + .install(entryTypeColumn); + this.setOnSort(event -> viewModel.patternListProperty() + .sort(CitationKeyPatternsPanelViewModel.defaultOnTopComparator)); patternColumn.setSortable(true); patternColumn.setReorderable(false); @@ -72,19 +74,19 @@ private void initialize() { patternColumn.setCellFactory(_ -> new CitationKeyPatternSuggestionCell(patterns)); patternColumn.setEditable(true); patternColumn.setOnEditCommit( - (TableColumn.CellEditEvent event) -> - event.getRowValue().setPattern(event.getNewValue())); + (TableColumn.CellEditEvent event) -> event.getRowValue() + .setPattern(event.getNewValue())); actionsColumn.setSortable(false); actionsColumn.setReorderable(false); actionsColumn.setCellValueFactory(cellData -> cellData.getValue().entryType()); new ValueTableCellFactory() - .withGraphic(entryType -> IconTheme.JabRefIcons.REFRESH.getGraphicNode()) - .withTooltip(entryType -> - Localization.lang("Reset %s to default value").formatted(entryType.getDisplayName())) - .withOnMouseClickedEvent(item -> evt -> - viewModel.setItemToDefaultPattern(this.getFocusModel().getFocusedItem())) - .install(actionsColumn); + .withGraphic(entryType -> IconTheme.JabRefIcons.REFRESH.getGraphicNode()) + .withTooltip( + entryType -> Localization.lang("Reset %s to default value").formatted(entryType.getDisplayName())) + .withOnMouseClickedEvent( + item -> evt -> viewModel.setItemToDefaultPattern(this.getFocusModel().getFocusedItem())) + .install(actionsColumn); this.setRowFactory(item -> new HighlightTableRow()); this.setOnKeyTyped(this::jumpToSearchKey); @@ -114,29 +116,39 @@ private void jumpToSearchKey(KeyEvent keypressed) { if (System.currentTimeMillis() - lastKeyPressTime < 1000) { tableSearchTerm += keypressed.getCharacter().toLowerCase(); - } else { + } + else { tableSearchTerm = keypressed.getCharacter().toLowerCase(); } lastKeyPressTime = System.currentTimeMillis(); - this.getItems().stream().filter(item -> item.getEntryType().getName().toLowerCase().startsWith(tableSearchTerm)) - .findFirst().ifPresent(this::scrollTo); + this.getItems() + .stream() + .filter(item -> item.getEntryType().getName().toLowerCase().startsWith(tableSearchTerm)) + .findFirst() + .ifPresent(this::scrollTo); } private static class HighlightTableRow extends TableRow { + @Override public void updateItem(CitationKeyPatternsPanelItemModel item, boolean empty) { super.updateItem(item, empty); if (item == null || item.getEntryType() == null) { setStyle(""); - } else if (isSelected()) { + } + else if (isSelected()) { setStyle("-fx-background-color: -fx-selection-bar"); - } else if (CitationKeyPatternsPanelViewModel.ENTRY_TYPE_DEFAULT_NAME.equals(item.getEntryType().getName())) { + } + else if (CitationKeyPatternsPanelViewModel.ENTRY_TYPE_DEFAULT_NAME.equals(item.getEntryType().getName())) { setStyle("-fx-background-color: -fx-default-button"); - } else { + } + else { setStyle(""); } } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanelItemModel.java b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanelItemModel.java index 86cd170d4ea..c46f43df2bd 100644 --- a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanelItemModel.java +++ b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanelItemModel.java @@ -10,7 +10,9 @@ import org.jabref.model.entry.types.EntryType; public class CitationKeyPatternsPanelItemModel { + private final ObjectProperty entryType = new SimpleObjectProperty<>(); + private final StringProperty pattern = new SimpleStringProperty(""); public CitationKeyPatternsPanelItemModel(EntryType entryType, String pattern) { @@ -44,4 +46,5 @@ public StringProperty pattern() { public String toString() { return "[" + entryType.getValue().getName() + "," + pattern.getValue() + "]"; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanelViewModel.java b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanelViewModel.java index 78df89b5ba7..f9d9f53f5ac 100644 --- a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanelViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanelViewModel.java @@ -26,9 +26,11 @@ public class CitationKeyPatternsPanelViewModel { if (itemOneName.equals(itemTwoName)) { return 0; - } else if (ENTRY_TYPE_DEFAULT_NAME.equals(itemOneName)) { + } + else if (ENTRY_TYPE_DEFAULT_NAME.equals(itemOneName)) { return -1; - } else if (ENTRY_TYPE_DEFAULT_NAME.equals(itemTwoName)) { + } + else if (ENTRY_TYPE_DEFAULT_NAME.equals(itemTwoName)) { return 1; } @@ -36,6 +38,7 @@ public class CitationKeyPatternsPanelViewModel { }; private final ListProperty patternListProperty = new SimpleListProperty<>(); + private final ObjectProperty defaultItemProperty = new SimpleObjectProperty<>(); private final CitationKeyPatternPreferences keyPatternPreferences; @@ -46,9 +49,11 @@ public CitationKeyPatternsPanelViewModel(CitationKeyPatternPreferences keyPatter public void setValues(Collection entryTypeList, AbstractCitationKeyPatterns initialKeyPattern) { String defaultPattern; - if ((initialKeyPattern.getDefaultValue() == null) || initialKeyPattern.getDefaultValue().equals(CitationKeyPattern.NULL_CITATION_KEY_PATTERN)) { + if ((initialKeyPattern.getDefaultValue() == null) + || initialKeyPattern.getDefaultValue().equals(CitationKeyPattern.NULL_CITATION_KEY_PATTERN)) { defaultPattern = ""; - } else { + } + else { defaultPattern = initialKeyPattern.getDefaultValue().stringRepresentation(); } @@ -56,17 +61,16 @@ public void setValues(Collection entryTypeList, AbstractCitationKe patternListProperty.setValue(FXCollections.observableArrayList()); patternListProperty.add(defaultItemProperty.getValue()); - entryTypeList.stream() - .map(BibEntryType::getType) - .forEach(entryType -> { - String pattern; - if (initialKeyPattern.isDefaultValue(entryType)) { - pattern = ""; - } else { - pattern = initialKeyPattern.getPatterns().get(entryType).stringRepresentation(); - } - patternListProperty.add(new CitationKeyPatternsPanelItemModel(entryType, pattern)); - }); + entryTypeList.stream().map(BibEntryType::getType).forEach(entryType -> { + String pattern; + if (initialKeyPattern.isDefaultValue(entryType)) { + pattern = ""; + } + else { + pattern = initialKeyPattern.getPatterns().get(entryType).stringRepresentation(); + } + patternListProperty.add(new CitationKeyPatternsPanelItemModel(entryType, pattern)); + }); } public void setItemToDefaultPattern(CitationKeyPatternsPanelItemModel item) { @@ -87,6 +91,7 @@ public ObjectProperty defaultKeyPatternProper } public static class DefaultEntryType implements EntryType { + @Override public String getName() { return ENTRY_TYPE_DEFAULT_NAME; @@ -96,5 +101,7 @@ public String getName() { public String getDisplayName() { return Localization.lang("Default pattern"); } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/FieldFormatterCleanupsPanel.java b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/FieldFormatterCleanupsPanel.java index 287b2918aac..fd2bc54b028 100644 --- a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/FieldFormatterCleanupsPanel.java +++ b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/FieldFormatterCleanupsPanel.java @@ -29,22 +29,34 @@ public class FieldFormatterCleanupsPanel extends VBox { - @FXML private CheckBox cleanupsEnabled; - @FXML private TableView cleanupsList; - @FXML private TableColumn fieldColumn; - @FXML private TableColumn formatterColumn; - @FXML private TableColumn actionsColumn; - @FXML private ComboBox addableFields; - @FXML private ComboBox addableFormatters; + @FXML + private CheckBox cleanupsEnabled; + + @FXML + private TableView cleanupsList; + + @FXML + private TableColumn fieldColumn; + + @FXML + private TableColumn formatterColumn; - @Inject private StateManager stateManager; + @FXML + private TableColumn actionsColumn; + + @FXML + private ComboBox addableFields; + + @FXML + private ComboBox addableFormatters; + + @Inject + private StateManager stateManager; private FieldFormatterCleanupsPanelViewModel viewModel; public FieldFormatterCleanupsPanel() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @FXML @@ -59,24 +71,24 @@ private void initialize() { private void setupTable() { cleanupsList.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); - // ToDo: To be editable the list needs a view model wrapper for FieldFormatterCleanup + // ToDo: To be editable the list needs a view model wrapper for + // FieldFormatterCleanup fieldColumn.setCellValueFactory(cellData -> new ReadOnlyObjectWrapper<>(cellData.getValue().getField())); - new ValueTableCellFactory() - .withText(Field::getDisplayName) - .install(fieldColumn); + new ValueTableCellFactory().withText(Field::getDisplayName).install(fieldColumn); - formatterColumn.setCellValueFactory(cellData -> new ReadOnlyObjectWrapper<>(cellData.getValue().getFormatter())); - new ValueTableCellFactory() - .withText(Formatter::getName) - .install(formatterColumn); + formatterColumn + .setCellValueFactory(cellData -> new ReadOnlyObjectWrapper<>(cellData.getValue().getFormatter())); + new ValueTableCellFactory().withText(Formatter::getName) + .install(formatterColumn); actionsColumn.setCellValueFactory(cellData -> new ReadOnlyObjectWrapper<>(cellData.getValue().getField())); new ValueTableCellFactory() - .withGraphic(field -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) - .withTooltip(field -> Localization.lang("Remove formatter for %0", field.getDisplayName())) - .withOnMouseClickedEvent(item -> event -> viewModel.removeCleanup(cleanupsList.getSelectionModel().getSelectedItem())) - .install(actionsColumn); + .withGraphic(field -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) + .withTooltip(field -> Localization.lang("Remove formatter for %0", field.getDisplayName())) + .withOnMouseClickedEvent( + item -> event -> viewModel.removeCleanup(cleanupsList.getSelectionModel().getSelectedItem())) + .install(actionsColumn); viewModel.selectedCleanupProperty().setValue(cleanupsList.getSelectionModel()); @@ -88,9 +100,7 @@ private void setupTable() { } private void setupCombos() { - new ViewModelListCellFactory() - .withText(Field::getDisplayName) - .install(addableFields); + new ViewModelListCellFactory().withText(Field::getDisplayName).install(addableFields); addableFields.setConverter(FieldsUtil.FIELD_STRING_CONVERTER); addableFields.setOnKeyPressed(event -> { if (event.getCode() == KeyCode.TAB || event.getCode() == KeyCode.ENTER) { @@ -99,10 +109,9 @@ private void setupCombos() { } }); - new ViewModelListCellFactory() - .withText(Formatter::getName) - .withStringTooltip(Formatter::getDescription) - .install(addableFormatters); + new ViewModelListCellFactory().withText(Formatter::getName) + .withStringTooltip(Formatter::getDescription) + .install(addableFormatters); addableFormatters.setOnKeyPressed(event -> { if (event.getCode() == KeyCode.ENTER) { viewModel.addCleanup(); @@ -113,8 +122,7 @@ private void setupCombos() { private void setupBindings() { BindingsHelper.bindBidirectional((ObservableValue) cleanupsEnabled.selectedProperty(), - viewModel.cleanupsDisableProperty(), - disabled -> cleanupsEnabled.selectedProperty().setValue(!disabled), + viewModel.cleanupsDisableProperty(), disabled -> cleanupsEnabled.selectedProperty().setValue(!disabled), selected -> viewModel.cleanupsDisableProperty().setValue(!selected)); cleanupsList.itemsProperty().bind(viewModel.cleanupsListProperty()); @@ -146,4 +154,5 @@ public BooleanProperty cleanupsDisableProperty() { public ListProperty cleanupsProperty() { return viewModel.cleanupsListProperty(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/FieldFormatterCleanupsPanelViewModel.java b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/FieldFormatterCleanupsPanelViewModel.java index d7a926291e1..7c02e0f70de 100644 --- a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/FieldFormatterCleanupsPanelViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/FieldFormatterCleanupsPanelViewModel.java @@ -24,11 +24,22 @@ public class FieldFormatterCleanupsPanelViewModel { private final BooleanProperty cleanupsDisableProperty = new SimpleBooleanProperty(); - private final ListProperty cleanupsListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ObjectProperty> selectedCleanupProperty = new SimpleObjectProperty<>(new NoSelectionModel<>()); - private final ListProperty availableFieldsProperty = new SimpleListProperty<>(new SortedList<>(FXCollections.observableArrayList(FieldFactory.getCommonFields()), Comparator.comparing(Field::getDisplayName))); + + private final ListProperty cleanupsListProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); + + private final ObjectProperty> selectedCleanupProperty = new SimpleObjectProperty<>( + new NoSelectionModel<>()); + + private final ListProperty availableFieldsProperty = new SimpleListProperty<>( + new SortedList<>(FXCollections.observableArrayList(FieldFactory.getCommonFields()), + Comparator.comparing(Field::getDisplayName))); + private final ObjectProperty selectedFieldProperty = new SimpleObjectProperty<>(); - private final ListProperty availableFormattersProperty = new SimpleListProperty<>(new SortedList<>(FXCollections.observableArrayList(Formatters.getAll()), Comparator.comparing(Formatter::getName))); + + private final ListProperty availableFormattersProperty = new SimpleListProperty<>(new SortedList<>( + FXCollections.observableArrayList(Formatters.getAll()), Comparator.comparing(Formatter::getName))); + private final ObjectProperty selectedFormatterProperty = new SimpleObjectProperty<>(); private final StateManager stateManager; @@ -41,7 +52,8 @@ public void resetToRecommended() { stateManager.getActiveDatabase().ifPresent(databaseContext -> { if (databaseContext.isBiblatexMode()) { cleanupsListProperty.setAll(FieldFormatterCleanups.RECOMMEND_BIBLATEX_ACTIONS); - } else { + } + else { cleanupsListProperty.setAll(FieldFormatterCleanups.RECOMMEND_BIBTEX_ACTIONS); } }); @@ -56,8 +68,7 @@ public void addCleanup() { return; } - FieldFormatterCleanup cleanup = new FieldFormatterCleanup( - selectedFieldProperty.getValue(), + FieldFormatterCleanup cleanup = new FieldFormatterCleanup(selectedFieldProperty.getValue(), selectedFormatterProperty.getValue()); if (cleanupsListProperty.stream().noneMatch(item -> item.equals(cleanup))) { @@ -96,4 +107,5 @@ public ListProperty availableFormattersProperty() { public ObjectProperty selectedFormatterProperty() { return selectedFormatterProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanel.java b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanel.java index 12dcd20777f..356c49aabe2 100644 --- a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanel.java +++ b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanel.java @@ -33,21 +33,31 @@ public class SaveOrderConfigPanel extends VBox { - @FXML private RadioButton exportInSpecifiedOrder; - @FXML private RadioButton exportInTableOrder; - @FXML private RadioButton exportInOriginalOrder; - @FXML private GridPane sortCriterionList; - @FXML private Button addButton; + @FXML + private RadioButton exportInSpecifiedOrder; + + @FXML + private RadioButton exportInTableOrder; + + @FXML + private RadioButton exportInOriginalOrder; + + @FXML + private GridPane sortCriterionList; + + @FXML + private Button addButton; - @Inject private CliPreferences preferences; - @Inject private UndoManager undoManager; + @Inject + private CliPreferences preferences; + + @Inject + private UndoManager undoManager; private SaveOrderConfigPanelViewModel viewModel; public SaveOrderConfigPanel() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @FXML @@ -61,14 +71,16 @@ private void initialize() { viewModel.sortCriteriaProperty().addListener((ListChangeListener) change -> { while (change.next()) { if (change.wasReplaced()) { - clearCriterionRow(change.getFrom()); - createCriterionRow(change.getAddedSubList().getFirst(), change.getFrom()); - } else if (change.wasAdded()) { + clearCriterionRow(change.getFrom()); + createCriterionRow(change.getAddedSubList().getFirst(), change.getFrom()); + } + else if (change.wasAdded()) { for (SortCriterionViewModel criterionViewModel : change.getAddedSubList()) { int row = change.getFrom() + change.getAddedSubList().indexOf(criterionViewModel); createCriterionRow(criterionViewModel, row); } - } else if (change.wasRemoved()) { + } + else if (change.wasRemoved()) { for (SortCriterionViewModel criterionViewModel : change.getRemoved()) { clearCriterionRow(change.getFrom()); } @@ -78,14 +90,12 @@ private void initialize() { } private void createCriterionRow(SortCriterionViewModel criterionViewModel, int row) { - sortCriterionList.getChildren().stream() - .filter(item -> GridPane.getRowIndex(item) >= row) - .forEach(item -> { - GridPane.setRowIndex(item, GridPane.getRowIndex(item) + 1); - if (item instanceof Label label) { - label.setText(String.valueOf(GridPane.getRowIndex(item) + 1)); - } - }); + sortCriterionList.getChildren().stream().filter(item -> GridPane.getRowIndex(item) >= row).forEach(item -> { + GridPane.setRowIndex(item, GridPane.getRowIndex(item) + 1); + if (item instanceof Label label) { + label.setText(String.valueOf(GridPane.getRowIndex(item) + 1)); + } + }); Label label = new Label(String.valueOf(row + 1)); sortCriterionList.add(label, 0, row); @@ -94,8 +104,8 @@ private void createCriterionRow(SortCriterionViewModel criterionViewModel, int r field.setMaxWidth(Double.MAX_VALUE); new ViewModelListCellFactory() - .withText(item -> FieldsUtil.getNameWithType(item, preferences, undoManager)) - .install(field); + .withText(item -> FieldsUtil.getNameWithType(item, preferences, undoManager)) + .install(field); field.setConverter(FieldsUtil.FIELD_STRING_CONVERTER); field.itemsProperty().bindBidirectional(viewModel.sortableFieldsProperty()); field.valueProperty().bindBidirectional(criterionViewModel.fieldProperty()); @@ -134,28 +144,27 @@ private List createRowButtons(SortCriterionViewModel criterionViewModel) { } private void clearCriterionRow(int row) { - List criterionRow = sortCriterionList.getChildren().stream() - .filter(item -> GridPane.getRowIndex(item) == row) - .collect(Collectors.toList()); + List criterionRow = sortCriterionList.getChildren() + .stream() + .filter(item -> GridPane.getRowIndex(item) == row) + .collect(Collectors.toList()); sortCriterionList.getChildren().removeAll(criterionRow); - sortCriterionList.getChildren().stream() - .filter(item -> GridPane.getRowIndex(item) > row) - .forEach(item -> { - GridPane.setRowIndex(item, GridPane.getRowIndex(item) - 1); - if (item instanceof Label label) { - label.setText(String.valueOf(GridPane.getRowIndex(item) + 1)); - } - }); + sortCriterionList.getChildren().stream().filter(item -> GridPane.getRowIndex(item) > row).forEach(item -> { + GridPane.setRowIndex(item, GridPane.getRowIndex(item) - 1); + if (item instanceof Label label) { + label.setText(String.valueOf(GridPane.getRowIndex(item) + 1)); + } + }); } public void setCriteriaLimit(int limit) { addButton.disableProperty().unbind(); - addButton.disableProperty().bind( - Bindings.createBooleanBinding( - () -> viewModel.sortCriteriaProperty().size() >= limit || !exportInSpecifiedOrder.selectedProperty().get(), - viewModel.sortCriteriaProperty().sizeProperty(), - exportInSpecifiedOrder.selectedProperty())); + addButton.disableProperty() + .bind(Bindings.createBooleanBinding( + () -> viewModel.sortCriteriaProperty().size() >= limit + || !exportInSpecifiedOrder.selectedProperty().get(), + viewModel.sortCriteriaProperty().sizeProperty(), exportInSpecifiedOrder.selectedProperty())); } @FXML @@ -197,4 +206,5 @@ public ListProperty sortableFieldsProperty() { public ListProperty sortCriteriaProperty() { return viewModel.sortCriteriaProperty(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanelViewModel.java b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanelViewModel.java index 6776df54e47..ab4386769ef 100644 --- a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanelViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanelViewModel.java @@ -13,11 +13,16 @@ public class SaveOrderConfigPanelViewModel { private final BooleanProperty saveInOriginalProperty = new SimpleBooleanProperty(); + private final BooleanProperty saveInTableOrderProperty = new SimpleBooleanProperty(); + private final BooleanProperty saveInSpecifiedOrderProperty = new SimpleBooleanProperty(); - private final ListProperty sortableFieldsProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ListProperty selectedSortCriteriaProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty sortableFieldsProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); + + private final ListProperty selectedSortCriteriaProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); public SaveOrderConfigPanelViewModel() { } @@ -67,4 +72,5 @@ public ListProperty sortableFieldsProperty() { public ListProperty sortCriteriaProperty() { return selectedSortCriteriaProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/SortCriterionViewModel.java b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/SortCriterionViewModel.java index 19edca1f9d9..ca62c52db77 100644 --- a/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/SortCriterionViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/commonfxcontrols/SortCriterionViewModel.java @@ -12,6 +12,7 @@ public class SortCriterionViewModel { private final ObjectProperty fieldProperty = new SimpleObjectProperty<>(); + private final BooleanProperty descendingProperty = new SimpleBooleanProperty(); public SortCriterionViewModel(SaveOrder.SortCriterion criterion) { @@ -35,4 +36,5 @@ public BooleanProperty descendingProperty() { public SaveOrder.SortCriterion getCriterion() { return new SaveOrder.SortCriterion(fieldProperty.getValue(), descendingProperty.getValue()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencyCheckAction.java b/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencyCheckAction.java index 67b357a1700..7ee9eadf819 100644 --- a/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencyCheckAction.java +++ b/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencyCheckAction.java @@ -23,18 +23,20 @@ public class ConsistencyCheckAction extends SimpleCommand { Supplier tabSupplier; + private final DialogService dialogService; + private final StateManager stateManager; + private final GuiPreferences preferences; + private final BibEntryTypesManager entryTypesManager; + private final UiTaskExecutor taskExecutor; - public ConsistencyCheckAction(Supplier tabSupplier, - DialogService dialogService, - StateManager stateManager, - GuiPreferences preferences, - BibEntryTypesManager entryTypesManager, - UiTaskExecutor taskExecutor) { + public ConsistencyCheckAction(Supplier tabSupplier, DialogService dialogService, + StateManager stateManager, GuiPreferences preferences, BibEntryTypesManager entryTypesManager, + UiTaskExecutor taskExecutor) { this.tabSupplier = tabSupplier; this.dialogService = dialogService; this.stateManager = stateManager; @@ -58,28 +60,28 @@ public BibliographyConsistencyCheck.Result call() throws Exception { List entries = databaseContext.get().getEntries(); BibliographyConsistencyCheck consistencyCheck = new BibliographyConsistencyCheck(); - return consistencyCheck.check(entries, (count, total) -> - UiTaskExecutor.runInJavaFXThread(() -> { - updateProgress(count, total); - updateMessage(Localization.lang("%0/%1 entry types", count + 1, total)); - })); + return consistencyCheck.check(entries, (count, total) -> UiTaskExecutor.runInJavaFXThread(() -> { + updateProgress(count, total); + updateMessage(Localization.lang("%0/%1 entry types", count + 1, total)); + })); } }; - task.setOnFailed(_ -> dialogService.showErrorDialogAndWait(Localization.lang("Consistency check failed."), task.getException())); + task.setOnFailed(_ -> dialogService.showErrorDialogAndWait(Localization.lang("Consistency check failed."), + task.getException())); task.setOnSucceeded(_ -> { if (task.getValue().entryTypeToResultMap().isEmpty()) { dialogService.notify(Localization.lang("No problems found.")); - } else { - dialogService.showCustomDialogAndWait( - new ConsistencyCheckDialog(tabSupplier.get(), dialogService, preferences, entryTypesManager, task.getValue())); + } + else { + dialogService.showCustomDialogAndWait(new ConsistencyCheckDialog(tabSupplier.get(), dialogService, + preferences, entryTypesManager, task.getValue())); } }); taskExecutor.execute(task); - dialogService.showProgressDialogAndWait( - Localization.lang("Check consistency"), - Localization.lang("Checking consistency..."), - task); + dialogService.showProgressDialogAndWait(Localization.lang("Check consistency"), + Localization.lang("Checking consistency..."), task); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencyCheckDialog.java b/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencyCheckDialog.java index 7924781dc3b..785353945cc 100644 --- a/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencyCheckDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencyCheckDialog.java @@ -37,25 +37,32 @@ public class ConsistencyCheckDialog extends BaseDialog { - @FXML private TableView tableView; - @FXML private ComboBox entryTypeCombo; + @FXML + private TableView tableView; + + @FXML + private ComboBox entryTypeCombo; - @Inject private StateManager stateManager; - @Inject private EntryEditor entryEditor; + @Inject + private StateManager stateManager; + + @Inject + private EntryEditor entryEditor; private final LibraryTab libraryTab; + private final DialogService dialogService; + private final GuiPreferences preferences; + private final BibEntryTypesManager entryTypesManager; + private final BibliographyConsistencyCheck.Result result; private ConsistencyCheckDialogViewModel viewModel; - public ConsistencyCheckDialog(LibraryTab libraryTab, - DialogService dialogService, - GuiPreferences preferences, - BibEntryTypesManager entryTypesManager, - BibliographyConsistencyCheck.Result result) { + public ConsistencyCheckDialog(LibraryTab libraryTab, DialogService dialogService, GuiPreferences preferences, + BibEntryTypesManager entryTypesManager, BibliographyConsistencyCheck.Result result) { this.libraryTab = libraryTab; this.dialogService = dialogService; this.preferences = preferences; @@ -65,9 +72,7 @@ public ConsistencyCheckDialog(LibraryTab libraryTab, this.setTitle(Localization.lang("Check consistency")); this.initModality(Modality.NONE); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); } public ConsistencyCheckDialogViewModel getViewModel() { @@ -83,14 +88,12 @@ public void initialize() { EasyBind.listen(entryTypeCombo.getEditor().textProperty(), observable -> entryTypeCombo.commitValue()); entryTypeCombo.getSelectionModel().selectFirst(); - FilteredList filteredData = new FilteredList<>(viewModel.getTableData(), message -> - message.message().getFirst().equals(viewModel.selectedEntryTypeProperty().get()) - ); + FilteredList filteredData = new FilteredList<>(viewModel.getTableData(), + message -> message.message().getFirst().equals(viewModel.selectedEntryTypeProperty().get())); - viewModel.selectedEntryTypeProperty().addListener((_, _, newValue) -> - filteredData.setPredicate(message -> - message.message().getFirst().equals(newValue) - )); + viewModel.selectedEntryTypeProperty() + .addListener((_, _, newValue) -> filteredData + .setPredicate(message -> message.message().getFirst().equals(newValue))); tableView.setItems(filteredData); @@ -120,13 +123,10 @@ protected void updateItem(String item, boolean empty) { } ConsistencySymbol.fromText(item) - .ifPresentOrElse( - symbol -> setGraphic(symbol.getIcon().getGraphicNode()), - () -> { - setGraphic(null); - setText(item); - } - ); + .ifPresentOrElse(symbol -> setGraphic(symbol.getIcon().getGraphicNode()), () -> { + setGraphic(null); + setText(item); + }); this.setOnMouseClicked(_ -> { if (!isEmpty()) { @@ -135,7 +135,8 @@ protected void updateItem(String item, boolean empty) { ConsistencyMessage message = getTableRow().getItem(); String cellValue = getTableColumn().getCellObservableValue(getIndex()).getValue(); Field field = FieldFactory.parseField(clickedColumn.getText()); - boolean isUnsetField = cellValue.equals(ConsistencySymbol.UNSET_FIELD_AT_ENTRY_TYPE_CELL_ENTRY.getText()); + boolean isUnsetField = cellValue + .equals(ConsistencySymbol.UNSET_FIELD_AT_ENTRY_TYPE_CELL_ENTRY.getText()); if (field.isStandardField()) { stateManager.getEditorShowing().setValue(true); @@ -143,9 +144,11 @@ protected void updateItem(String item, boolean empty) { libraryTab.clearAndSelect(message.bibEntry()); entryEditor.setFocusToField(field); }); - } else if (!message.bibEntry().hasField(field) && isUnsetField) { + } + else if (!message.bibEntry().hasField(field) && isUnsetField) { libraryTab.showAndEdit(message.bibEntry()); - } else { + } + else { stateManager.getEditorShowing().setValue(true); Platform.runLater(() -> { libraryTab.clearAndSelect(message.bibEntry()); @@ -160,31 +163,26 @@ protected void updateItem(String item, boolean empty) { tableView.getColumns().add(tableColumn); } - EnumSet targetSymbols = EnumSet.of( - ConsistencySymbol.OPTIONAL_FIELD_AT_ENTRY_TYPE_CELL_ENTRY, + EnumSet targetSymbols = EnumSet.of(ConsistencySymbol.OPTIONAL_FIELD_AT_ENTRY_TYPE_CELL_ENTRY, ConsistencySymbol.REQUIRED_FIELD_AT_ENTRY_TYPE_CELL_ENTRY, ConsistencySymbol.UNKNOWN_FIELD_AT_ENTRY_TYPE_CELL_ENTRY, - ConsistencySymbol.UNSET_FIELD_AT_ENTRY_TYPE_CELL_ENTRY - ); + ConsistencySymbol.UNSET_FIELD_AT_ENTRY_TYPE_CELL_ENTRY); - targetSymbols.stream() - .map(ConsistencySymbol::getText) - .forEach(this::removeColumnWithUniformValue); + targetSymbols.stream().map(ConsistencySymbol::getText).forEach(this::removeColumnWithUniformValue); - Arrays.stream(SpecialField.values()) - .map(SpecialField::getDisplayName) - .forEach(this::removeColumnByTitle); + Arrays.stream(SpecialField.values()).map(SpecialField::getDisplayName).forEach(this::removeColumnByTitle); } private void removeColumnWithUniformValue(String symbol) { - List> columnToRemove = tableView.getColumns().stream() - .filter(column -> { - Set values = tableView.getItems().stream() - .map(item -> Optional.ofNullable(column.getCellObservableValue(item).getValue()).map(Object::toString).orElse("")) - .collect(Collectors.toSet()); - return values.size() == 1 && values.contains(symbol); - }) - .toList(); + List> columnToRemove = tableView.getColumns().stream().filter(column -> { + Set values = tableView.getItems() + .stream() + .map(item -> Optional.ofNullable(column.getCellObservableValue(item).getValue()) + .map(Object::toString) + .orElse("")) + .collect(Collectors.toSet()); + return values.size() == 1 && values.contains(symbol); + }).toList(); tableView.getColumns().removeAll(columnToRemove); } @@ -207,4 +205,5 @@ private void showInfo() { ConsistencySymbolsDialog consistencySymbolsDialog = new ConsistencySymbolsDialog(); dialogService.showCustomDialog(consistencySymbolsDialog); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencyCheckDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencyCheckDialogViewModel.java index 5feea960485..5eadb5a54da 100644 --- a/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencyCheckDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencyCheckDialogViewModel.java @@ -46,37 +46,46 @@ public class ConsistencyCheckDialogViewModel extends AbstractViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(ConsistencyCheckDialogViewModel.class); + private static final int EXTRA_COLUMNS_COUNT = 2; private final BibliographyConsistencyCheck.Result result; + private final DialogService dialogService; + private final GuiPreferences preferences; + private final BibEntryTypesManager entryTypesManager; private final List allReportedFields; + private final int columnCount; + private final ObservableList tableData = FXCollections.observableArrayList(); + private final StringProperty selectedEntryType = new SimpleStringProperty(); - public ConsistencyCheckDialogViewModel(DialogService dialogService, - GuiPreferences preferences, - BibEntryTypesManager entryTypesManager, - BibliographyConsistencyCheck.Result result) { + public ConsistencyCheckDialogViewModel(DialogService dialogService, GuiPreferences preferences, + BibEntryTypesManager entryTypesManager, BibliographyConsistencyCheck.Result result) { this.dialogService = dialogService; this.preferences = preferences; this.entryTypesManager = entryTypesManager; this.result = result; - this.allReportedFields = result.entryTypeToResultMap().values().stream() - .flatMap(entryTypeResult -> entryTypeResult.fields().stream()) - .sorted(Comparator.comparing(Field::getName)) - .distinct() - .toList(); + this.allReportedFields = result.entryTypeToResultMap() + .values() + .stream() + .flatMap(entryTypeResult -> entryTypeResult.fields().stream()) + .sorted(Comparator.comparing(Field::getName)) + .distinct() + .toList(); this.columnCount = getColumnNames().size(); - result.entryTypeToResultMap().entrySet().stream() - .sorted(Comparator.comparing(entry -> entry.getKey().getName())) - .forEach(Unchecked.consumer(this::writeMapEntry)); + result.entryTypeToResultMap() + .entrySet() + .stream() + .sorted(Comparator.comparing(entry -> entry.getKey().getName())) + .forEach(Unchecked.consumer(this::writeMapEntry)); } public StringProperty selectedEntryTypeProperty() { @@ -97,7 +106,7 @@ public Set getColumnNames() { Set result = LinkedHashSet.newLinkedHashSet(columnCount + EXTRA_COLUMNS_COUNT); result.add("Entry Type"); result.add("CitationKey"); - allReportedFields.forEach(field-> result.add(field.getDisplayName().trim())); + allReportedFields.forEach(field -> result.add(field.getDisplayName().trim())); return result; } @@ -106,61 +115,60 @@ private void writeMapEntry(Map.Entry bibEntryType = this.entryTypesManager.enrich(mapEntry.getKey(), bibDatabaseMode); - Set requiredFields = bibEntryType - .map(BibEntryType::getRequiredFields) - .stream() - .flatMap(Collection::stream) - .flatMap(orFields -> orFields.getFields().stream()) - .collect(Collectors.toSet()); - Set optionalFields = bibEntryType - .map(BibEntryType::getOptionalFields) - .stream() - .flatMap(Collection::stream) - .map(BibField::field) - .collect(Collectors.toSet()); + Set requiredFields = bibEntryType.map(BibEntryType::getRequiredFields) + .stream() + .flatMap(Collection::stream) + .flatMap(orFields -> orFields.getFields().stream()) + .collect(Collectors.toSet()); + Set optionalFields = bibEntryType.map(BibEntryType::getOptionalFields) + .stream() + .flatMap(Collection::stream) + .map(BibField::field) + .collect(Collectors.toSet()); BibliographyConsistencyCheck.EntryTypeResult entries = mapEntry.getValue(); SequencedCollection bibEntries = entries.sortedEntries(); - bibEntries.forEach(Unchecked.consumer(bibEntry -> - writeBibEntry(bibEntry, entryType, requiredFields, optionalFields) - )); + bibEntries.forEach( + Unchecked.consumer(bibEntry -> writeBibEntry(bibEntry, entryType, requiredFields, optionalFields))); } - private void writeBibEntry(BibEntry bibEntry, String entryType, Set requiredFields, Set optionalFields) { + private void writeBibEntry(BibEntry bibEntry, String entryType, Set requiredFields, + Set optionalFields) { List theRecord = getFindingsAsList(bibEntry, entryType, requiredFields, optionalFields); List message = new ArrayList<>(); - for (String s: theRecord) { + for (String s : theRecord) { String modifiedString = s.replaceAll("\\s+", " "); message.add(modifiedString); } tableData.add(new ConsistencyMessage(message, bibEntry)); } - private List getFindingsAsList(BibEntry bibEntry, String entryType, Set requiredFields, Set optionalFields) { + private List getFindingsAsList(BibEntry bibEntry, String entryType, Set requiredFields, + Set optionalFields) { List result = new ArrayList<>(columnCount + EXTRA_COLUMNS_COUNT); result.add(entryType); result.add(bibEntry.getCitationKey().orElse("")); - allReportedFields.forEach(field -> - result.add(bibEntry.getField(field).map(_ -> { - if (requiredFields.contains(field)) { - return ConsistencySymbol.REQUIRED_FIELD_AT_ENTRY_TYPE_CELL_ENTRY.getText(); - } else if (optionalFields.contains(field)) { - return ConsistencySymbol.OPTIONAL_FIELD_AT_ENTRY_TYPE_CELL_ENTRY.getText(); - } else { - return ConsistencySymbol.UNKNOWN_FIELD_AT_ENTRY_TYPE_CELL_ENTRY.getText(); - } - }).orElse(ConsistencySymbol.UNSET_FIELD_AT_ENTRY_TYPE_CELL_ENTRY.getText())) - ); + allReportedFields.forEach(field -> result.add(bibEntry.getField(field).map(_ -> { + if (requiredFields.contains(field)) { + return ConsistencySymbol.REQUIRED_FIELD_AT_ENTRY_TYPE_CELL_ENTRY.getText(); + } + else if (optionalFields.contains(field)) { + return ConsistencySymbol.OPTIONAL_FIELD_AT_ENTRY_TYPE_CELL_ENTRY.getText(); + } + else { + return ConsistencySymbol.UNKNOWN_FIELD_AT_ENTRY_TYPE_CELL_ENTRY.getText(); + } + }).orElse(ConsistencySymbol.UNSET_FIELD_AT_ENTRY_TYPE_CELL_ENTRY.getText()))); return result; } protected void startExportAsTxt() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) - .addExtensionFilter(StandardFileType.TXT) - .withDefaultExtension(StandardFileType.TXT) - .build(); + .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) + .addExtensionFilter(StandardFileType.TXT) + .withDefaultExtension(StandardFileType.TXT) + .build(); Optional exportPath = dialogService.showFileSaveDialog(fileDialogConfiguration); if (exportPath.isEmpty()) { @@ -168,9 +176,11 @@ protected void startExportAsTxt() { } try (Writer writer = new OutputStreamWriter(Files.newOutputStream(exportPath.get())); - BibliographyConsistencyCheckResultTxtWriter bibliographyConsistencyCheckResultTxtWriter = new BibliographyConsistencyCheckResultTxtWriter(result, writer, true)) { + BibliographyConsistencyCheckResultTxtWriter bibliographyConsistencyCheckResultTxtWriter = new BibliographyConsistencyCheckResultTxtWriter( + result, writer, true)) { bibliographyConsistencyCheckResultTxtWriter.writeFindings(); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error(Localization.lang("Problem when exporting file"), e); dialogService.showErrorDialogAndWait(Localization.lang("Failed to export file.")); } @@ -178,10 +188,10 @@ protected void startExportAsTxt() { protected void startExportAsCsv() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) - .addExtensionFilter(StandardFileType.CSV) - .withDefaultExtension(StandardFileType.CSV) - .build(); + .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) + .addExtensionFilter(StandardFileType.CSV) + .withDefaultExtension(StandardFileType.CSV) + .build(); Optional exportPath = dialogService.showFileSaveDialog(fileDialogConfiguration); if (exportPath.isEmpty()) { @@ -189,11 +199,14 @@ protected void startExportAsCsv() { } try (Writer writer = new OutputStreamWriter(Files.newOutputStream(exportPath.get())); - BibliographyConsistencyCheckResultCsvWriter bibliographyConsistencyCheckResultCsvWriter = new BibliographyConsistencyCheckResultCsvWriter(result, writer, true)) { + BibliographyConsistencyCheckResultCsvWriter bibliographyConsistencyCheckResultCsvWriter = new BibliographyConsistencyCheckResultCsvWriter( + result, writer, true)) { bibliographyConsistencyCheckResultCsvWriter.writeFindings(); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error(Localization.lang("Problem when exporting file"), e); dialogService.showErrorDialogAndWait(Localization.lang("Failed to export file.")); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencySymbol.java b/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencySymbol.java index 89dc33d2a60..2c164f36b25 100644 --- a/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencySymbol.java +++ b/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencySymbol.java @@ -6,12 +6,14 @@ import org.jabref.gui.icon.IconTheme; public enum ConsistencySymbol { + REQUIRED_FIELD_AT_ENTRY_TYPE_CELL_ENTRY("x", IconTheme.JabRefIcons.CONSISTENCY_REQUIRED_FIELD), OPTIONAL_FIELD_AT_ENTRY_TYPE_CELL_ENTRY("o", IconTheme.JabRefIcons.CONSISTENCY_OPTIONAL_FIELD), UNKNOWN_FIELD_AT_ENTRY_TYPE_CELL_ENTRY("?", IconTheme.JabRefIcons.CONSISTENCY_UNKNOWN_FIELD), UNSET_FIELD_AT_ENTRY_TYPE_CELL_ENTRY("-", IconTheme.JabRefIcons.CONSISTENCY_UNSET_FIELD); private final String text; + private final IconTheme.JabRefIcons icon; ConsistencySymbol(String text, IconTheme.JabRefIcons icon) { @@ -28,8 +30,7 @@ public IconTheme.JabRefIcons getIcon() { } public static Optional fromText(String text) { - return Arrays.stream(ConsistencySymbol.values()) - .filter(symbol -> symbol.getText().equals(text)) - .findFirst(); + return Arrays.stream(ConsistencySymbol.values()).filter(symbol -> symbol.getText().equals(text)).findFirst(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencySymbolsDialog.java b/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencySymbolsDialog.java index 73a7e41ce22..2c724a03ea9 100644 --- a/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencySymbolsDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/consistency/ConsistencySymbolsDialog.java @@ -14,8 +14,7 @@ public ConsistencySymbolsDialog() { this.initModality(Modality.NONE); this.setResizable(false); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java b/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java index 582971b6cc7..6e96e83f316 100644 --- a/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java +++ b/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java @@ -22,14 +22,15 @@ public class CopyFilesAction extends SimpleCommand { private final DialogService dialogService; + private final CliPreferences preferences; + private final StateManager stateManager; + private final UiTaskExecutor uiTaskExecutor; - public CopyFilesAction(DialogService dialogService, - CliPreferences preferences, - StateManager stateManager, - UiTaskExecutor taskExecutor) { + public CopyFilesAction(DialogService dialogService, CliPreferences preferences, StateManager stateManager, + UiTaskExecutor taskExecutor) { this.dialogService = dialogService; this.preferences = preferences; this.stateManager = stateManager; @@ -40,7 +41,8 @@ public CopyFilesAction(DialogService dialogService, private void showDialog(List data) { if (data.isEmpty()) { - dialogService.showInformationDialogAndWait(Localization.lang("Copy linked files to folder..."), Localization.lang("No linked files found for export.")); + dialogService.showInformationDialogAndWait(Localization.lang("Copy linked files to folder..."), + Localization.lang("No linked files found for export.")); return; } dialogService.showCustomDialogAndWait(new CopyFilesDialogView(new CopyFilesResultListDependency(data))); @@ -48,23 +50,24 @@ private void showDialog(List data) { @Override public void execute() { - BibDatabaseContext database = stateManager.getActiveDatabase().orElseThrow(() -> new NullPointerException("Database null")); + BibDatabaseContext database = stateManager.getActiveDatabase() + .orElseThrow(() -> new NullPointerException("Database null")); List entries = stateManager.getSelectedEntries(); DirectoryDialogConfiguration dirDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(preferences.getExportPreferences().getExportWorkingDirectory()) - .build(); + .withInitialDirectory(preferences.getExportPreferences().getExportWorkingDirectory()) + .build(); Optional exportPath = dialogService.showDirectorySelectionDialog(dirDialogConfiguration); exportPath.ifPresent(path -> { - Task> exportTask = new CopyFilesTask(database, entries, path, preferences); + Task> exportTask = new CopyFilesTask(database, entries, path, + preferences); - dialogService.showProgressDialog( - Localization.lang("Copy linked files to folder..."), - Localization.lang("Copy linked files to folder..."), - exportTask); + dialogService.showProgressDialog(Localization.lang("Copy linked files to folder..."), + Localization.lang("Copy linked files to folder..."), exportTask); uiTaskExecutor.execute(exportTask); exportTask.setOnSucceeded(e -> showDialog(exportTask.getValue())); }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogView.java b/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogView.java index 7b1bfcc2f58..e73538f308d 100644 --- a/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogView.java +++ b/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogView.java @@ -16,10 +16,18 @@ public class CopyFilesDialogView extends BaseDialog { - @FXML private TableView tvResult; - @FXML private TableColumn colStatus; - @FXML private TableColumn colMessage; - @FXML private TableColumn colFile; + @FXML + private TableView tvResult; + + @FXML + private TableColumn colStatus; + + @FXML + private TableColumn colMessage; + + @FXML + private TableColumn colFile; + private final CopyFilesDialogViewModel viewModel; public CopyFilesDialogView(CopyFilesResultListDependency results) { @@ -29,9 +37,7 @@ public CopyFilesDialogView(CopyFilesResultListDependency results) { viewModel = new CopyFilesDialogViewModel(results); - ViewLoader.view(this) - .load() - .setAsContent(this.getDialogPane()); + ViewLoader.view(this).load().setAsContent(this.getDialogPane()); } @FXML @@ -44,18 +50,21 @@ private void setupTable() { colMessage.setCellValueFactory(cellData -> cellData.getValue().getMessage()); colStatus.setCellValueFactory(cellData -> cellData.getValue().getIcon()); - colFile.setCellFactory(new ValueTableCellFactory().withText(item -> item).withTooltip(item -> item)); - colStatus.setCellFactory(new ValueTableCellFactory().withGraphic(item -> { - if (item == IconTheme.JabRefIcons.CHECK) { - item = item.withColor(Color.GREEN); - } - if (item == IconTheme.JabRefIcons.WARNING) { - item = item.withColor(Color.RED); - } - return item.getGraphicNode(); - })); + colFile.setCellFactory(new ValueTableCellFactory().withText(item -> item) + .withTooltip(item -> item)); + colStatus + .setCellFactory(new ValueTableCellFactory().withGraphic(item -> { + if (item == IconTheme.JabRefIcons.CHECK) { + item = item.withColor(Color.GREEN); + } + if (item == IconTheme.JabRefIcons.WARNING) { + item = item.withColor(Color.RED); + } + return item.getGraphicNode(); + })); tvResult.setItems(viewModel.copyFilesResultListProperty()); tvResult.setColumnResizePolicy(param -> true); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogViewModel.java index e30563b4fa9..912391ea6b5 100644 --- a/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogViewModel.java @@ -17,4 +17,5 @@ public CopyFilesDialogViewModel(CopyFilesResultListDependency results) { public SimpleListProperty copyFilesResultListProperty() { return this.copyFilesResultItems; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultItemViewModel.java b/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultItemViewModel.java index e48f0685112..43226812f8a 100644 --- a/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultItemViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultItemViewModel.java @@ -13,7 +13,9 @@ public class CopyFilesResultItemViewModel { private final StringProperty file = new SimpleStringProperty(""); + private final ObjectProperty icon = new SimpleObjectProperty<>(IconTheme.JabRefIcons.WARNING); + private final StringProperty message = new SimpleStringProperty(""); public CopyFilesResultItemViewModel(Path file, boolean success, String message) { @@ -40,4 +42,5 @@ public ObjectProperty getIcon() { public String toString() { return "CopyFilesResultItemViewModel [file=" + file.get() + ", message=" + message.get() + "]"; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultListDependency.java b/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultListDependency.java index 71aef284159..8c0d17f1a3e 100644 --- a/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultListDependency.java +++ b/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultListDependency.java @@ -4,7 +4,8 @@ import java.util.List; /** - * This class is a wrapper class for the containing list as it is currently not possible to inject complex object types into FXML controller + * This class is a wrapper class for the containing list as it is currently not possible + * to inject complex object types into FXML controller */ public class CopyFilesResultListDependency { @@ -26,4 +27,5 @@ public List getResults() { public String toString() { return "CopyFilesResultListDependency [results=" + results + "]"; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesTask.java b/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesTask.java index b92991a7a65..d8efbb7535b 100644 --- a/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesTask.java +++ b/jabgui/src/main/java/org/jabref/gui/copyfiles/CopyFilesTask.java @@ -29,23 +29,38 @@ public class CopyFilesTask extends Task> { private static final Logger LOGGER = LoggerFactory.getLogger(CopyFilesTask.class); + private static final String LOGFILE_PREFIX = "copyFileslog_"; + private static final String LOGFILE_EXT = ".log"; + private final BibDatabaseContext databaseContext; + private final CliPreferences preferences; + private final Path exportPath; + private final String localizedSuccessMessage = Localization.lang("Copied file successfully"); - private final String localizedErrorMessage = Localization.lang("Could not copy file") + ": " + Localization.lang("File exists"); + + private final String localizedErrorMessage = Localization.lang("Could not copy file") + ": " + + Localization.lang("File exists"); + private final long totalFilesCount; + private final List entries; + private final List results = new ArrayList<>(); + private Optional newPath = Optional.empty(); + private int numberSuccessful; + private int totalFilesCounter; private final BiFunction resolvePathFilename = (path, file) -> path.resolve(file.getFileName()); - public CopyFilesTask(BibDatabaseContext databaseContext, List entries, Path path, CliPreferences preferences) { + public CopyFilesTask(BibDatabaseContext databaseContext, List entries, Path path, + CliPreferences preferences) { this.databaseContext = databaseContext; this.preferences = preferences; this.entries = entries; @@ -62,7 +77,8 @@ protected List call() throws InterruptedException, LocalDateTime currentTime = LocalDateTime.now(); String currentDate = currentTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")); - try (BufferedWriter bw = Files.newBufferedWriter(exportPath.resolve(LOGFILE_PREFIX + currentDate + LOGFILE_EXT), StandardCharsets.UTF_8)) { + try (BufferedWriter bw = Files.newBufferedWriter(exportPath.resolve(LOGFILE_PREFIX + currentDate + LOGFILE_EXT), + StandardCharsets.UTF_8)) { for (int i = 0; i < entries.size(); i++) { if (isCancelled()) { break; @@ -75,7 +91,8 @@ protected List call() throws InterruptedException, break; } - updateMessage(Localization.lang("Copying file %0 of entry %1", Integer.toString(j + 1), Integer.toString(i + 1))); + updateMessage(Localization.lang("Copying file %0 of entry %1", Integer.toString(j + 1), + Integer.toString(i + 1))); LinkedFile fileName = files.get(j); @@ -89,7 +106,8 @@ protected List call() throws InterruptedException, updateProgress(totalFilesCounter++, totalFilesCount); try { Thread.sleep(300); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { if (isCancelled()) { updateMessage("Cancelled"); break; @@ -100,7 +118,8 @@ protected List call() throws InterruptedException, numberSuccessful++; writeLogMessage(newFile, bw, localizedSuccessMessage); addResultToList(newFile, success, localizedSuccessMessage); - } else { + } + else { updateMessage(localizedErrorMessage); writeLogMessage(newFile, bw, localizedErrorMessage); addResultToList(newFile, success, localizedErrorMessage); @@ -111,8 +130,7 @@ protected List call() throws InterruptedException, updateMessage(Localization.lang("Finished copying")); String successMessage = Localization.lang("Copied %0 files of %1 successfully to %2", - Integer.toString(numberSuccessful), - Integer.toString(totalFilesCounter), + Integer.toString(numberSuccessful), Integer.toString(totalFilesCounter), newPath.map(Path::getParent).map(Path::toString).orElse("")); updateMessage(successMessage); bw.write(successMessage); @@ -124,7 +142,8 @@ private void writeLogMessage(Path newFile, BufferedWriter bw, String logMessage) try { bw.write(logMessage + ": " + newFile); bw.write(OS.NEWLINE); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("error writing log file", e); } } @@ -133,4 +152,5 @@ private void addResultToList(Path newFile, boolean success, String logMessage) { CopyFilesResultItemViewModel result = new CopyFilesResultItemViewModel(newFile, success, logMessage); results.add(result); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/copyfiles/CopySingleFileAction.java b/jabgui/src/main/java/org/jabref/gui/copyfiles/CopySingleFileAction.java index 22ae70d77cd..88b674b9c36 100644 --- a/jabgui/src/main/java/org/jabref/gui/copyfiles/CopySingleFileAction.java +++ b/jabgui/src/main/java/org/jabref/gui/copyfiles/CopySingleFileAction.java @@ -19,13 +19,17 @@ public class CopySingleFileAction extends SimpleCommand { private final LinkedFile linkedFile; + private final DialogService dialogService; + private final BibDatabaseContext databaseContext; + private final FilePreferences filePreferences; private final BiFunction resolvePathFilename = (path, file) -> path.resolve(file.getFileName()); - public CopySingleFileAction(LinkedFile linkedFile, DialogService dialogService, BibDatabaseContext databaseContext, FilePreferences filePreferences) { + public CopySingleFileAction(LinkedFile linkedFile, DialogService dialogService, BibDatabaseContext databaseContext, + FilePreferences filePreferences) { this.linkedFile = linkedFile; this.dialogService = dialogService; this.databaseContext = databaseContext; @@ -40,8 +44,8 @@ public CopySingleFileAction(LinkedFile linkedFile, DialogService dialogService, @Override public void execute() { DirectoryDialogConfiguration dirDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(filePreferences.getWorkingDirectory()) - .build(); + .withInitialDirectory(filePreferences.getWorkingDirectory()) + .build(); Optional exportPath = dialogService.showDirectorySelectionDialog(dirDialogConfiguration); exportPath.ifPresent(this::copyFileToDestination); } @@ -54,12 +58,20 @@ private void copyFileToDestination(Path exportPath) { Path newFile = newPath.get(); boolean success = fileToExport.isPresent() && FileUtil.copyFile(fileToExport.get(), newFile, false); if (success) { - dialogService.showInformationDialogAndWait(Localization.lang("Copy linked file"), Localization.lang("Successfully copied file to %0.", newPath.map(Path::getParent).map(Path::toString).orElse(""))); - } else { - dialogService.showErrorDialogAndWait(Localization.lang("Copy linked file"), Localization.lang("Could not copy file to %0, maybe the file is already existing?", newPath.map(Path::getParent).map(Path::toString).orElse(""))); + dialogService.showInformationDialogAndWait(Localization.lang("Copy linked file"), + Localization.lang("Successfully copied file to %0.", + newPath.map(Path::getParent).map(Path::toString).orElse(""))); } - } else { - dialogService.showErrorDialogAndWait(Localization.lang("Could not resolve the file %0", fileToExport.map(Path::getParent).map(Path::toString).orElse(""))); + else { + dialogService.showErrorDialogAndWait(Localization.lang("Copy linked file"), + Localization.lang("Could not copy file to %0, maybe the file is already existing?", + newPath.map(Path::getParent).map(Path::toString).orElse(""))); + } + } + else { + dialogService.showErrorDialogAndWait(Localization.lang("Could not resolve the file %0", + fileToExport.map(Path::getParent).map(Path::toString).orElse(""))); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/desktop/os/DefaultDesktop.java b/jabgui/src/main/java/org/jabref/gui/desktop/os/DefaultDesktop.java index 59b4d030a17..e2b8640d2d0 100644 --- a/jabgui/src/main/java/org/jabref/gui/desktop/os/DefaultDesktop.java +++ b/jabgui/src/main/java/org/jabref/gui/desktop/os/DefaultDesktop.java @@ -13,17 +13,20 @@ import org.slf4j.LoggerFactory; /** - * This class contains some default implementations (if OS is neither linux, windows or osx) file directories and file/application open handling methods. + * This class contains some default implementations (if OS is neither linux, windows or + * osx) file directories and file/application open handling methods. *

- * We cannot use a static logger instance here in this class as the Logger first needs to be configured in the {@link JabKit#initLogging}. - * The configuration of tinylog will become immutable as soon as the first log entry is issued. + * We cannot use a static logger instance here in this class as the Logger first needs to + * be configured in the {@link JabKit#initLogging}. The configuration of tinylog will + * become immutable as soon as the first log entry is issued. * https://tinylog.org/v2/configuration/ */ @AllowedToUseAwt("Requires AWT to open a file") public class DefaultDesktop extends NativeDesktop { @Override - public void openFile(String filePath, String fileType, ExternalApplicationsPreferences externalApplicationsPreferences) throws IOException { + public void openFile(String filePath, String fileType, + ExternalApplicationsPreferences externalApplicationsPreferences) throws IOException { Desktop.getDesktop().open(Path.of(filePath).toFile()); } @@ -47,4 +50,5 @@ public void openConsole(String absolutePath, DialogService dialogService) throws public Path getApplicationDirectory() { return Directories.getUserDirectory(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/desktop/os/Linux.java b/jabgui/src/main/java/org/jabref/gui/desktop/os/Linux.java index aae1d826d3e..e1e4e32944f 100644 --- a/jabgui/src/main/java/org/jabref/gui/desktop/os/Linux.java +++ b/jabgui/src/main/java/org/jabref/gui/desktop/os/Linux.java @@ -25,10 +25,12 @@ import org.slf4j.LoggerFactory; /** - * This class contains Linux specific implementations for file directories and file/application open handling methods. + * This class contains Linux specific implementations for file directories and + * file/application open handling methods. *

- * We cannot use a static logger instance here in this class as the Logger first needs to be configured in the {@link JabKit#initLogging}. - * The configuration of tinylog will become immutable as soon as the first log entry is issued. + * We cannot use a static logger instance here in this class as the Logger first needs to + * be configured in the {@link JabKit#initLogging}. The configuration of tinylog will + * become immutable as soon as the first log entry is issued. * https://tinylog.org/v2/configuration */ @AllowedToUseAwt("Requires AWT to open a file with the native method") @@ -41,35 +43,43 @@ private void nativeOpenFile(String filePath) { try { Desktop.getDesktop().open(Path.of(filePath).toFile()); LoggerFactory.getLogger(Linux.class).debug("Open file in default application with Desktop integration"); - } catch (IllegalArgumentException e) { + } + catch (IllegalArgumentException e) { LoggerFactory.getLogger(Linux.class).debug("Fail back to xdg-open"); try { - String[] cmd = {"xdg-open", filePath}; + String[] cmd = { "xdg-open", filePath }; Runtime.getRuntime().exec(cmd); - } catch (Exception e2) { + } + catch (Exception e2) { LoggerFactory.getLogger(Linux.class).warn("Open operation not successful: ", e2); } - } catch (IOException e) { + } + catch (IOException e) { LoggerFactory.getLogger(Linux.class).warn("Native open operation not successful: ", e); } }); } @Override - public void openFile(String filePath, String fileType, ExternalApplicationsPreferences externalApplicationsPreferences) throws IOException { - Optional type = ExternalFileTypes.getExternalFileTypeByExt(fileType, externalApplicationsPreferences); + public void openFile(String filePath, String fileType, + ExternalApplicationsPreferences externalApplicationsPreferences) throws IOException { + Optional type = ExternalFileTypes.getExternalFileTypeByExt(fileType, + externalApplicationsPreferences); String viewer; if (type.isPresent() && !type.get().getOpenWithApplication().isEmpty()) { viewer = type.get().getOpenWithApplication(); ProcessBuilder processBuilder = new ProcessBuilder(viewer, filePath); Process process = processBuilder.start(); - StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), LoggerFactory.getLogger(Linux.class)::debug); - StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), LoggerFactory.getLogger(Linux.class)::debug); + StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), + LoggerFactory.getLogger(Linux.class)::debug); + StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), + LoggerFactory.getLogger(Linux.class)::debug); HeadlessExecutorService.INSTANCE.execute(streamGobblerInput); HeadlessExecutorService.INSTANCE.execute(streamGobblerError); - } else { + } + else { nativeOpenFile(filePath); } } @@ -87,12 +97,15 @@ public void openFileWithApplication(String filePath, String application) throws ProcessBuilder processBuilder = new ProcessBuilder(cmdArray); Process process = processBuilder.start(); - StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), LoggerFactory.getLogger(Linux.class)::debug); - StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), LoggerFactory.getLogger(Linux.class)::debug); + StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), + LoggerFactory.getLogger(Linux.class)::debug); + StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), + LoggerFactory.getLogger(Linux.class)::debug); HeadlessExecutorService.INSTANCE.execute(streamGobblerInput); HeadlessExecutorService.INSTANCE.execute(streamGobblerError); - } else { + } + else { nativeOpenFile(filePath); } } @@ -102,28 +115,40 @@ public void openFolderAndSelectFile(Path filePath) throws IOException { String desktopSession = System.getenv("DESKTOP_SESSION"); String absoluteFilePath = filePath.toAbsolutePath().toString(); - String[] cmd = {"xdg-open", filePath.getParent().toString()}; // default is the folder of the file + String[] cmd = { "xdg-open", filePath.getParent().toString() }; // default is the + // folder of the + // file if (desktopSession != null) { desktopSession = desktopSession.toLowerCase(Locale.ROOT); if (desktopSession.contains("gnome")) { - cmd = new String[] {"nautilus", "--select", absoluteFilePath}; - } else if (desktopSession.contains("kde") || desktopSession.contains("plasma")) { - cmd = new String[] {"dolphin", "--select", absoluteFilePath}; - } else if (desktopSession.contains("mate")) { - cmd = new String[] {"caja", "--select", absoluteFilePath}; - } else if (desktopSession.contains("cinnamon")) { - cmd = new String[] {"nemo", absoluteFilePath}; // Although nemo is based on nautilus it does not support --select, it directly highlights the file - } else if (desktopSession.contains("xfce")) { - cmd = new String[] {"thunar", absoluteFilePath}; + cmd = new String[] { "nautilus", "--select", absoluteFilePath }; + } + else if (desktopSession.contains("kde") || desktopSession.contains("plasma")) { + cmd = new String[] { "dolphin", "--select", absoluteFilePath }; + } + else if (desktopSession.contains("mate")) { + cmd = new String[] { "caja", "--select", absoluteFilePath }; + } + else if (desktopSession.contains("cinnamon")) { + cmd = new String[] { "nemo", absoluteFilePath }; // Although nemo is based + // on nautilus it does + // not support --select, + // it directly highlights + // the file + } + else if (desktopSession.contains("xfce")) { + cmd = new String[] { "thunar", absoluteFilePath }; } } LoggerFactory.getLogger(Linux.class).debug("Opening folder and selecting file using {}", String.join(" ", cmd)); ProcessBuilder processBuilder = new ProcessBuilder(cmd); Process process = processBuilder.start(); - StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), LoggerFactory.getLogger(Linux.class)::debug); - StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), LoggerFactory.getLogger(Linux.class)::debug); + StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), + LoggerFactory.getLogger(Linux.class)::debug); + StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), + LoggerFactory.getLogger(Linux.class)::debug); HeadlessExecutorService.INSTANCE.execute(streamGobblerInput); HeadlessExecutorService.INSTANCE.execute(streamGobblerError); @@ -133,7 +158,9 @@ public void openFolderAndSelectFile(Path filePath) throws IOException { public void openConsole(String absolutePath, DialogService dialogService) throws IOException { if (!Files.exists(Path.of(ETC_ALTERNATIVES_X_TERMINAL_EMULATOR))) { - dialogService.showErrorDialogAndWait(Localization.lang("Could not detect terminal automatically using '%0'. Please define a custom terminal in the preferences.", ETC_ALTERNATIVES_X_TERMINAL_EMULATOR)); + dialogService.showErrorDialogAndWait(Localization.lang( + "Could not detect terminal automatically using '%0'. Please define a custom terminal in the preferences.", + ETC_ALTERNATIVES_X_TERMINAL_EMULATOR)); return; } @@ -147,14 +174,18 @@ public void openConsole(String absolutePath, DialogService dialogService) throws String[] cmd; if (emulatorName.contains("gnome")) { - cmd = new String[] {"gnome-terminal", "--working-directory", absolutePath}; - } else if (emulatorName.contains("xfce4")) { - // xfce4-terminal requires "--working-directory=" format (one arg) - cmd = new String[] {"xfce4-terminal", "--working-directory=" + absolutePath}; - } else if (emulatorName.contains("konsole")) { - cmd = new String[] {"konsole", "--workdir", absolutePath}; - } else { - cmd = new String[] {emulatorName, absolutePath}; + cmd = new String[] { "gnome-terminal", "--working-directory", absolutePath }; + } + else if (emulatorName.contains("xfce4")) { + // xfce4-terminal requires "--working-directory=" format + // (one arg) + cmd = new String[] { "xfce4-terminal", "--working-directory=" + absolutePath }; + } + else if (emulatorName.contains("konsole")) { + cmd = new String[] { "konsole", "--workdir", absolutePath }; + } + else { + cmd = new String[] { emulatorName, absolutePath }; } LoggerFactory.getLogger(Linux.class).debug("Opening terminal using {}", String.join(" ", cmd)); @@ -163,8 +194,10 @@ public void openConsole(String absolutePath, DialogService dialogService) throws builder.directory(Path.of(absolutePath).toFile()); Process processTerminal = builder.start(); - StreamGobbler streamGobblerInput = new StreamGobbler(processTerminal.getInputStream(), LoggerFactory.getLogger(Linux.class)::debug); - StreamGobbler streamGobblerError = new StreamGobbler(processTerminal.getErrorStream(), LoggerFactory.getLogger(Linux.class)::debug); + StreamGobbler streamGobblerInput = new StreamGobbler(processTerminal.getInputStream(), + LoggerFactory.getLogger(Linux.class)::debug); + StreamGobbler streamGobblerError = new StreamGobbler(processTerminal.getErrorStream(), + LoggerFactory.getLogger(Linux.class)::debug); HeadlessExecutorService.INSTANCE.execute(streamGobblerInput); HeadlessExecutorService.INSTANCE.execute(streamGobblerError); @@ -187,9 +220,16 @@ public Path getDefaultFileChooserDirectory() { // Make use of xdg-user-dirs // See https://www.freedesktop.org/wiki/Software/xdg-user-dirs/ for details try { - Process process = new ProcessBuilder("xdg-user-dir", "DOCUMENTS").start(); // Package name with 's', command without - List strings = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)) - .lines().toList(); + Process process = new ProcessBuilder("xdg-user-dir", "DOCUMENTS").start(); // Package + // name + // with + // 's', + // command + // without + List strings = new BufferedReader( + new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)) + .lines() + .toList(); if (strings.isEmpty()) { LoggerFactory.getLogger(Linux.class).error("xdg-user-dir returned nothing"); return Directories.getUserDirectory(); @@ -197,16 +237,19 @@ public Path getDefaultFileChooserDirectory() { String documentsDirectory = strings.getFirst(); Path documentsPath = Path.of(documentsDirectory); if (!Files.exists(documentsPath)) { - LoggerFactory.getLogger(Linux.class).error("xdg-user-dir returned non-existant directory {}", documentsDirectory); + LoggerFactory.getLogger(Linux.class) + .error("xdg-user-dir returned non-existant directory {}", documentsDirectory); return Directories.getUserDirectory(); } LoggerFactory.getLogger(Linux.class).debug("Got documents path {}", documentsPath); return documentsPath; - } catch (IOException e) { + } + catch (IOException e) { LoggerFactory.getLogger(Linux.class).error("Error while executing xdg-user-dir", e); } // Fallback return Directories.getUserDirectory(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java b/jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java index 39539032415..bde36acc058 100644 --- a/jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java +++ b/jabgui/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java @@ -40,37 +40,40 @@ import static org.jabref.model.entry.field.StandardField.URL; /** - * This class contains bundles OS specific implementations for file directories and file/application open handling methods. - * In case the default does not work, subclasses provide the correct behavior. + * This class contains bundles OS specific implementations for file directories and + * file/application open handling methods. In case the default does not work, subclasses + * provide the correct behavior. *

- * We cannot use a static logger instance here in this class as the Logger first needs to be configured in the {@link JabKit#initLogging}. - * The configuration of tinylog will become immutable as soon as the first log entry is issued. + * We cannot use a static logger instance here in this class as the Logger first needs to + * be configured in the {@link JabKit#initLogging}. The configuration of tinylog will + * become immutable as soon as the first log entry is issued. * https://tinylog.org/v2/configuration/ *

- * See https://stackoverflow.com/questions/18004150/desktop-api-is-not-supported-on-the-current-platform for more implementation hints. - * https://docs.oracle.com/javase/7/docs/api/java/awt/Desktop.html cannot be used as we don't want to rely on AWT. + * See + * https://stackoverflow.com/questions/18004150/desktop-api-is-not-supported-on-the-current-platform + * for more implementation hints. + * https://docs.oracle.com/javase/7/docs/api/java/awt/Desktop.html cannot be used as we + * don't want to rely on AWT. * * For non-GUI things, see {@link org.jabref.logic.os.OS}. */ @AllowedToUseAwt("Because of moveToTrash() is not available elsewhere.") public abstract class NativeDesktop { + // No LOGGER may be initialized directly - // Otherwise, org.jabref.Launcher.addLogToDisk will fail, because tinylog's properties are frozen + // Otherwise, org.jabref.Launcher.addLogToDisk will fail, because tinylog's properties + // are frozen private static final Pattern REMOTE_LINK_PATTERN = Pattern.compile("[a-z]+://.*"); /** * Open a http/pdf/ps viewer for the given link string. *

- * Opening a PDF file at the file field is done at {@link org.jabref.gui.fieldeditors.LinkedFileViewModel#open} + * Opening a PDF file at the file field is done at + * {@link org.jabref.gui.fieldeditors.LinkedFileViewModel#open} */ - public static void openExternalViewer(BibDatabaseContext databaseContext, - GuiPreferences preferences, - String initialLink, - Field initialField, - DialogService dialogService, - BibEntry entry) - throws IOException { + public static void openExternalViewer(BibDatabaseContext databaseContext, GuiPreferences preferences, + String initialLink, Field initialField, DialogService dialogService, BibEntry entry) throws IOException { String link = initialLink; Field field = initialField; if ((PS == field) || (PDF == field)) { @@ -90,32 +93,40 @@ public static void openExternalViewer(BibDatabaseContext databaseContext, if (split.length >= 2) { if ("pdf".equalsIgnoreCase(split[split.length - 1])) { field = PDF; - } else if ("ps".equalsIgnoreCase(split[split.length - 1]) + } + else if ("ps".equalsIgnoreCase(split[split.length - 1]) || ((split.length >= 3) && "ps".equalsIgnoreCase(split[split.length - 2]))) { field = PS; } } - } else if (StandardField.DOI == field) { + } + else if (StandardField.DOI == field) { openDoi(link, preferences); return; - } else if (StandardField.ISBN == field) { + } + else if (StandardField.ISBN == field) { openIsbn(link, preferences); return; - } else if (StandardField.EPRINT == field) { + } + else if (StandardField.EPRINT == field) { IdentifierParser identifierParser = new IdentifierParser(entry); link = identifierParser.parse(StandardField.EPRINT) - .flatMap(Identifier::getExternalURI) - .map(URI::toASCIIString) - .orElse(link); + .flatMap(Identifier::getExternalURI) + .map(URI::toASCIIString) + .orElse(link); if (Objects.equals(link, initialLink)) { Optional eprintTypeOpt = entry.getField(StandardField.EPRINTTYPE); Optional archivePrefixOpt = entry.getField(StandardField.ARCHIVEPREFIX); if (eprintTypeOpt.isEmpty() && archivePrefixOpt.isEmpty()) { - dialogService.showErrorDialogAndWait(Localization.lang("Unable to open linked eprint. Please set the eprinttype field")); - } else { - dialogService.showErrorDialogAndWait(Localization.lang("Unable to open linked eprint. Please verify that the eprint field has a valid '%0' id", link)); + dialogService.showErrorDialogAndWait( + Localization.lang("Unable to open linked eprint. Please set the eprinttype field")); + } + else { + dialogService.showErrorDialogAndWait(Localization.lang( + "Unable to open linked eprint. Please verify that the eprint field has a valid '%0' id", + link)); } } // should be opened in browser @@ -123,24 +134,25 @@ public static void openExternalViewer(BibDatabaseContext databaseContext, } switch (field) { - case URL -> - openBrowser(link, preferences.getExternalApplicationsPreferences()); + case URL -> openBrowser(link, preferences.getExternalApplicationsPreferences()); case PS -> { try { get().openFile(link, PS.getName(), preferences.getExternalApplicationsPreferences()); - } catch (IOException e) { + } + catch (IOException e) { LoggerFactory.getLogger(NativeDesktop.class).error("An error occurred on the command: {}", link, e); } } case PDF -> { try { get().openFile(link, PDF.getName(), preferences.getExternalApplicationsPreferences()); - } catch (IOException e) { + } + catch (IOException e) { LoggerFactory.getLogger(NativeDesktop.class).error("An error occurred on the command: {}", link, e); } } - case null, default -> - LoggerFactory.getLogger(NativeDesktop.class).info("Message: currently only PDF, PS and HTML files can be opened by double clicking"); + case null, default -> LoggerFactory.getLogger(NativeDesktop.class) + .info("Message: currently only PDF, PS and HTML files can be opened by double clicking"); } } @@ -151,14 +163,15 @@ private static void openDoi(String doi, GuiPreferences preferences) throws IOExc public static void openCustomDoi(String link, GuiPreferences preferences, DialogService dialogService) { DOI.parse(link) - .flatMap(doi -> doi.getExternalURIWithCustomBase(preferences.getDOIPreferences().getDefaultBaseURI())) - .ifPresent(uri -> { - try { - openBrowser(uri, preferences.getExternalApplicationsPreferences()); - } catch (IOException e) { - dialogService.showErrorDialogAndWait(Localization.lang("Unable to open link."), e); - } - }); + .flatMap(doi -> doi.getExternalURIWithCustomBase(preferences.getDOIPreferences().getDefaultBaseURI())) + .ifPresent(uri -> { + try { + openBrowser(uri, preferences.getExternalApplicationsPreferences()); + } + catch (IOException e) { + dialogService.showErrorDialogAndWait(Localization.lang("Unable to open link."), e); + } + }); } private static void openIsbn(String isbn, GuiPreferences preferences) throws IOException { @@ -167,18 +180,15 @@ private static void openIsbn(String isbn, GuiPreferences preferences) throws IOE } /** - * Open an external file, attempting to use the correct viewer for it. - * If the "file" is an online link, instead open it with the browser - * + * Open an external file, attempting to use the correct viewer for it. If the "file" + * is an online link, instead open it with the browser * @param databaseContext The database this file belongs to. - * @param link The filename. + * @param link The filename. * @return false if the link couldn't be resolved, true otherwise. */ public static boolean openExternalFileAnyFormat(final BibDatabaseContext databaseContext, - ExternalApplicationsPreferences externalApplicationsPreferences, - FilePreferences filePreferences, - String link, - final Optional type) throws IOException { + ExternalApplicationsPreferences externalApplicationsPreferences, FilePreferences filePreferences, + String link, final Optional type) throws IOException { if (REMOTE_LINK_PATTERN.matcher(link.toLowerCase(Locale.ROOT)).matches()) { openBrowser(link, externalApplicationsPreferences); return true; @@ -193,19 +203,19 @@ public static boolean openExternalFileAnyFormat(final BibDatabaseContext databas return true; } - private static void openExternalFilePlatformIndependent(Optional fileType, - String filePath, - ExternalApplicationsPreferences externalApplicationsPreferences) - throws IOException { + private static void openExternalFilePlatformIndependent(Optional fileType, String filePath, + ExternalApplicationsPreferences externalApplicationsPreferences) throws IOException { if (fileType.isPresent()) { String application = fileType.get().getOpenWithApplication(); if (application.isEmpty()) { get().openFile(filePath, fileType.get().getExtension(), externalApplicationsPreferences); - } else { + } + else { get().openFileWithApplication(filePath, application); } - } else { + } + else { // File type is not given and therefore no application specified // Let the OS handle the opening of the file get().openFile(filePath, "", externalApplicationsPreferences); @@ -213,14 +223,14 @@ private static void openExternalFilePlatformIndependent(Optional fileType = ExternalFileTypes.getExternalFileTypeByExt("html", externalApplicationsPreferences); + public static void openBrowser(String url, ExternalApplicationsPreferences externalApplicationsPreferences) + throws IOException { + Optional fileType = ExternalFileTypes.getExternalFileTypeByExt("html", + externalApplicationsPreferences); openExternalFilePlatformIndependent(fileType, url, externalApplicationsPreferences); } - public static void openBrowser(URI url, ExternalApplicationsPreferences externalApplicationsPreferences) throws IOException { + public static void openBrowser(URI url, ExternalApplicationsPreferences externalApplicationsPreferences) + throws IOException { openBrowser(url.toASCIIString(), externalApplicationsPreferences); } /** - * Opens the url with the users standard Browser. If that fails a popup will be shown to instruct the user to open the link manually and the link gets copied to the clipboard - * + * Opens the url with the users standard Browser. If that fails a popup will be shown + * to instruct the user to open the link manually and the link gets copied to the + * clipboard * @param url the URL to open */ - public static void openBrowserShowPopup(String url, DialogService dialogService, ExternalApplicationsPreferences externalApplicationsPreferences) { + public static void openBrowserShowPopup(String url, DialogService dialogService, + ExternalApplicationsPreferences externalApplicationsPreferences) { try { openBrowser(url, externalApplicationsPreferences); - } catch (IOException exception) { + } + catch (IOException exception) { ClipBoardManager clipBoardManager = Injector.instantiateModelOrService(ClipBoardManager.class); clipBoardManager.setContent(url); LoggerFactory.getLogger(NativeDesktop.class).error("Could not open browser", exception); @@ -316,27 +333,30 @@ public static void openBrowserShowPopup(String url, DialogService dialogService, String openManually = Localization.lang("Please open %0 manually.", url); String copiedToClipboard = Localization.lang("The link has been copied to the clipboard."); dialogService.notify(couldNotOpenBrowser); - dialogService.showErrorDialogAndWait(couldNotOpenBrowser, couldNotOpenBrowser + "\n" + openManually + "\n" + copiedToClipboard); + dialogService.showErrorDialogAndWait(couldNotOpenBrowser, + couldNotOpenBrowser + "\n" + openManually + "\n" + copiedToClipboard); } } public static NativeDesktop get() { if (OS.WINDOWS) { return new Windows(); - } else if (OS.OS_X) { + } + else if (OS.OS_X) { return new OSX(); - } else if (OS.LINUX) { + } + else if (OS.LINUX) { return new Linux(); } return new DefaultDesktop(); } - public abstract void openFile(String filePath, String fileType, ExternalApplicationsPreferences externalApplicationsPreferences) throws IOException; + public abstract void openFile(String filePath, String fileType, + ExternalApplicationsPreferences externalApplicationsPreferences) throws IOException; /** * Opens a file on an Operating System, using the given application. - * - * @param filePath The filename. + * @param filePath The filename. * @param application Link to the app that opens the file. */ public abstract void openFileWithApplication(String filePath, String application) throws IOException; @@ -347,29 +367,27 @@ public static NativeDesktop get() { /** * Returns the path to the system's applications folder. - * * @return the path */ public abstract Path getApplicationDirectory(); /** * Get the user's default file chooser directory - * * @return the path */ public Path getDefaultFileChooserDirectory() { - Path userDirectory = Directories.getUserDirectory(); - Path documents = userDirectory.resolve("Documents"); - if (!Files.exists(documents)) { - return userDirectory; - } - return documents; - } + Path userDirectory = Directories.getUserDirectory(); + Path documents = userDirectory.resolve("Documents"); + if (!Files.exists(documents)) { + return userDirectory; + } + return documents; + } /** * Moves the given file to the trash. - * - * @throws UnsupportedOperationException if the current platform does not support the {@link Desktop.Action#MOVE_TO_TRASH} action + * @throws UnsupportedOperationException if the current platform does not support the + * {@link Desktop.Action#MOVE_TO_TRASH} action * @see Desktop#moveToTrash(File) */ public void moveToTrash(Path path) { @@ -382,4 +400,5 @@ public void moveToTrash(Path path) { public boolean moveToTrashSupported() { return Desktop.getDesktop().isSupported(Desktop.Action.MOVE_TO_TRASH); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/desktop/os/OSX.java b/jabgui/src/main/java/org/jabref/gui/desktop/os/OSX.java index 98feb0f4230..03a5f4ed2ba 100644 --- a/jabgui/src/main/java/org/jabref/gui/desktop/os/OSX.java +++ b/jabgui/src/main/java/org/jabref/gui/desktop/os/OSX.java @@ -11,47 +11,55 @@ import org.jabref.gui.frame.ExternalApplicationsPreferences; /** - * This class contains macOS (OSX) specific implementations for file directories and file/application open handling methods. + * This class contains macOS (OSX) specific implementations for file directories and + * file/application open handling methods. *

- * We cannot use a static logger instance here in this class as the Logger first needs to be configured in the {@link JabKit#initLogging}. - * The configuration of tinylog will become immutable as soon as the first log entry is issued. + * We cannot use a static logger instance here in this class as the Logger first needs to + * be configured in the {@link JabKit#initLogging}. The configuration of tinylog will + * become immutable as soon as the first log entry is issued. * https://tinylog.org/v2/configuration/ */ @AllowedToUseAwt("Requires AWT to open a file") public class OSX extends NativeDesktop { @Override - public void openFile(String filePath, String fileType, ExternalApplicationsPreferences externalApplicationsPreferences) throws IOException { - Optional type = ExternalFileTypes.getExternalFileTypeByExt(fileType, externalApplicationsPreferences); + public void openFile(String filePath, String fileType, + ExternalApplicationsPreferences externalApplicationsPreferences) throws IOException { + Optional type = ExternalFileTypes.getExternalFileTypeByExt(fileType, + externalApplicationsPreferences); if (type.isPresent() && !type.get().getOpenWithApplication().isEmpty()) { openFileWithApplication(filePath, type.get().getOpenWithApplication()); - } else { - String[] cmd = {"/usr/bin/open", filePath}; + } + else { + String[] cmd = { "/usr/bin/open", filePath }; Runtime.getRuntime().exec(cmd); } } @Override public void openFileWithApplication(String filePath, String application) throws IOException { - // Use "-a " if the app is specified, and just "open " otherwise: - String[] cmd = (application != null) && !application.isEmpty() ? new String[] {"/usr/bin/open", "-a", - application, filePath} : new String[] {"/usr/bin/open", filePath}; + // Use "-a " if the app is specified, and just "open " + // otherwise: + String[] cmd = (application != null) && !application.isEmpty() + ? new String[] { "/usr/bin/open", "-a", application, filePath } + : new String[] { "/usr/bin/open", filePath }; new ProcessBuilder(cmd).start(); } @Override public void openFolderAndSelectFile(Path file) throws IOException { - String[] cmd = {"/usr/bin/open", "-R", file.toString()}; + String[] cmd = { "/usr/bin/open", "-R", file.toString() }; Runtime.getRuntime().exec(cmd); } @Override public void openConsole(String absolutePath, DialogService dialogService) throws IOException { - new ProcessBuilder("open", "-a", "Terminal", absolutePath).start(); + new ProcessBuilder("open", "-a", "Terminal", absolutePath).start(); } @Override public Path getApplicationDirectory() { return Path.of("/Applications"); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/desktop/os/Windows.java b/jabgui/src/main/java/org/jabref/gui/desktop/os/Windows.java index 3d2d1213d5b..6e4b30e1362 100644 --- a/jabgui/src/main/java/org/jabref/gui/desktop/os/Windows.java +++ b/jabgui/src/main/java/org/jabref/gui/desktop/os/Windows.java @@ -17,21 +17,26 @@ import org.slf4j.LoggerFactory; /** - * This class contains Windows specific implementations for file directories and file/application open handling methods. + * This class contains Windows specific implementations for file directories and + * file/application open handling methods. *

- * We cannot use a static logger instance here in this class as the Logger first needs to be configured in the {@link JabKit#initLogging}. - * The configuration of tinylog will become immutable as soon as the first log entry is issued. + * We cannot use a static logger instance here in this class as the Logger first needs to + * be configured in the {@link JabKit#initLogging}. The configuration of tinylog will + * become immutable as soon as the first log entry is issued. * https://tinylog.org/v2/configuration/ */ public class Windows extends NativeDesktop { @Override - public void openFile(String filePath, String fileType, ExternalApplicationsPreferences externalApplicationsPreferences) throws IOException { - Optional type = ExternalFileTypes.getExternalFileTypeByExt(fileType, externalApplicationsPreferences); + public void openFile(String filePath, String fileType, + ExternalApplicationsPreferences externalApplicationsPreferences) throws IOException { + Optional type = ExternalFileTypes.getExternalFileTypeByExt(fileType, + externalApplicationsPreferences); if (type.isPresent() && !type.get().getOpenWithApplication().isEmpty()) { openFileWithApplication(filePath, type.get().getOpenWithApplication()); - } else { + } + else { // quote String so explorer handles URL query strings correctly String quotePath = "\"" + filePath + "\""; new ProcessBuilder("explorer.exe", quotePath).start(); @@ -53,11 +58,13 @@ public Path getDefaultFileChooserDirectory() { try { try { return Path.of(Shell32Util.getKnownFolderPath(KnownFolders.FOLDERID_Documents)); - } catch (UnsatisfiedLinkError e) { + } + catch (UnsatisfiedLinkError e) { // Windows Vista or earlier return Path.of(Shell32Util.getFolderPath(ShlObj.CSIDL_MYDOCUMENTS)); } - } catch (Win32Exception e) { + } + catch (Win32Exception e) { // needs to be non-static because of org.jabref.Launcher.addLogToDisk LoggerFactory.getLogger(Windows.class).error("Error accessing folder", e); return Path.of(System.getProperty("user.home")); @@ -80,4 +87,5 @@ public void openConsole(String absolutePath, DialogService dialogService) throws process.directory(Path.of(absolutePath).toFile()); process.start(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java b/jabgui/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java index 7d5ff4dc9e5..efd7d662788 100644 --- a/jabgui/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java +++ b/jabgui/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java @@ -13,24 +13,29 @@ import org.slf4j.LoggerFactory; /** - * This class has an abstract UI role as it listens for an {@link AutosaveEvent} and saves the bib file associated with - * the given {@link LibraryTab}. + * This class has an abstract UI role as it listens for an {@link AutosaveEvent} and saves + * the bib file associated with the given {@link LibraryTab}. */ public class AutosaveUiManager { + private static final Logger LOGGER = LoggerFactory.getLogger(AutosaveUiManager.class); private final SaveDatabaseAction saveDatabaseAction; - public AutosaveUiManager(LibraryTab libraryTab, DialogService dialogService, GuiPreferences preferences, BibEntryTypesManager entryTypesManager, StateManager stateManager) { - this.saveDatabaseAction = new SaveDatabaseAction(libraryTab, dialogService, preferences, entryTypesManager, stateManager); + public AutosaveUiManager(LibraryTab libraryTab, DialogService dialogService, GuiPreferences preferences, + BibEntryTypesManager entryTypesManager, StateManager stateManager) { + this.saveDatabaseAction = new SaveDatabaseAction(libraryTab, dialogService, preferences, entryTypesManager, + stateManager); } @Subscribe public void listen(AutosaveEvent event) { try { this.saveDatabaseAction.save(SaveDatabaseAction.SaveDatabaseMode.SILENT); - } catch (Throwable e) { + } + catch (Throwable e) { LOGGER.error("Problem occurred while saving.", e); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/jabgui/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index 0df7d1c12e1..5a3c0b8643e 100644 --- a/jabgui/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/jabgui/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -39,67 +39,65 @@ * Stores all user dialogs related to {@link BackupManager}. */ public class BackupUIManager { + private static final Logger LOGGER = LoggerFactory.getLogger(BackupUIManager.class); private BackupUIManager() { } - public static Optional showRestoreBackupDialog(DialogService dialogService, - Path originalPath, - GuiPreferences preferences, - FileUpdateMonitor fileUpdateMonitor, - UndoManager undoManager, - StateManager stateManager) { - Optional actionOpt = showBackupResolverDialog( - dialogService, - preferences.getExternalApplicationsPreferences(), - originalPath, + public static Optional showRestoreBackupDialog(DialogService dialogService, Path originalPath, + GuiPreferences preferences, FileUpdateMonitor fileUpdateMonitor, UndoManager undoManager, + StateManager stateManager) { + Optional actionOpt = showBackupResolverDialog(dialogService, + preferences.getExternalApplicationsPreferences(), originalPath, preferences.getFilePreferences().getBackupDirectory()); return actionOpt.flatMap(action -> { if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { BackupManager.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory()); return Optional.empty(); - } else if (action == BackupResolverDialog.REVIEW_BACKUP) { - return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager); + } + else if (action == BackupResolverDialog.REVIEW_BACKUP) { + return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, + stateManager); } return Optional.empty(); }); } private static Optional showBackupResolverDialog(DialogService dialogService, - ExternalApplicationsPreferences externalApplicationsPreferences, - Path originalPath, - Path backupDir) { - return UiTaskExecutor.runInJavaFXThread( - () -> dialogService.showCustomDialogAndWait(new BackupResolverDialog(originalPath, backupDir, externalApplicationsPreferences))); + ExternalApplicationsPreferences externalApplicationsPreferences, Path originalPath, Path backupDir) { + return UiTaskExecutor.runInJavaFXThread(() -> dialogService.showCustomDialogAndWait( + new BackupResolverDialog(originalPath, backupDir, externalApplicationsPreferences))); } - private static Optional showReviewBackupDialog( - DialogService dialogService, - Path originalPath, - GuiPreferences preferences, - FileUpdateMonitor fileUpdateMonitor, - UndoManager undoManager, + private static Optional showReviewBackupDialog(DialogService dialogService, Path originalPath, + GuiPreferences preferences, FileUpdateMonitor fileUpdateMonitor, UndoManager undoManager, StateManager stateManager) { try { ImportFormatPreferences importFormatPreferences = preferences.getImportFormatPreferences(); // The database of the originalParserResult will be modified - ParserResult originalParserResult = OpenDatabase.loadDatabase(originalPath, importFormatPreferences, fileUpdateMonitor); + ParserResult originalParserResult = OpenDatabase.loadDatabase(originalPath, importFormatPreferences, + fileUpdateMonitor); // This will be modified by using the `DatabaseChangesResolverDialog`. BibDatabaseContext originalDatabase = originalParserResult.getDatabaseContext(); - Path backupPath = BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, preferences.getFilePreferences().getBackupDirectory()).orElseThrow(); - BibDatabaseContext backupDatabase = OpenDatabase.loadDatabase(backupPath, importFormatPreferences, new DummyFileUpdateMonitor()).getDatabaseContext(); + Path backupPath = BackupFileUtil + .getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, + preferences.getFilePreferences().getBackupDirectory()) + .orElseThrow(); + BibDatabaseContext backupDatabase = OpenDatabase + .loadDatabase(backupPath, importFormatPreferences, new DummyFileUpdateMonitor()) + .getDatabaseContext(); - DatabaseChangeResolverFactory changeResolverFactory = new DatabaseChangeResolverFactory(dialogService, originalDatabase, preferences); + DatabaseChangeResolverFactory changeResolverFactory = new DatabaseChangeResolverFactory(dialogService, + originalDatabase, preferences); return UiTaskExecutor.runInJavaFXThread(() -> { - List changes = DatabaseChangeList.compareAndGetChanges(originalDatabase, backupDatabase, changeResolverFactory); - DatabaseChangesResolverDialog reviewBackupDialog = new DatabaseChangesResolverDialog( - changes, - originalDatabase, "Review Backup" - ); + List changes = DatabaseChangeList.compareAndGetChanges(originalDatabase, backupDatabase, + changeResolverFactory); + DatabaseChangesResolverDialog reviewBackupDialog = new DatabaseChangesResolverDialog(changes, + originalDatabase, "Review Backup"); Optional allChangesResolved = dialogService.showCustomDialogAndWait(reviewBackupDialog); LibraryTab saveState = stateManager.activeTabProperty().get().get(); final NamedCompound CE = new NamedCompound(Localization.lang("Merged external changes")); @@ -108,22 +106,30 @@ private static Optional showReviewBackupDialog( undoManager.addEdit(CE); if (allChangesResolved.get()) { if (reviewBackupDialog.areAllChangesDenied()) { - // Here the case of a backup file is handled: If no changes of the backup are merged in, the file stays the same + // Here the case of a backup file is handled: If no changes of the + // backup are merged in, the file stays the same saveState.resetChangeMonitor(); - } else { - // In case any change of the backup is accepted, this means, the in-memory file differs from the file on disk (which is not the backup file) + } + else { + // In case any change of the backup is accepted, this means, the + // in-memory file differs from the file on disk (which is not the + // backup file) saveState.markBaseChanged(); } - // This does NOT return the original ParserResult, but a modified version with all changes accepted or rejected + // This does NOT return the original ParserResult, but a modified + // version with all changes accepted or rejected return Optional.of(originalParserResult); } // In case not all changes are resolved, start from scratch - return showRestoreBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager); + return showRestoreBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, + stateManager); }); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Error while loading backup or current database", e); return Optional.empty(); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/documentviewer/DocumentViewerView.java b/jabgui/src/main/java/org/jabref/gui/documentviewer/DocumentViewerView.java index 9544366d836..5576c418f0c 100644 --- a/jabgui/src/main/java/org/jabref/gui/documentviewer/DocumentViewerView.java +++ b/jabgui/src/main/java/org/jabref/gui/documentviewer/DocumentViewerView.java @@ -23,27 +23,39 @@ public class DocumentViewerView extends BaseDialog { - @FXML private ComboBox fileChoice; - @FXML private BorderPane mainPane; - @FXML private ToggleGroup toggleGroupMode; - @FXML private ToggleButton modeLive; - @FXML private ToggleButton modeLock; + @FXML + private ComboBox fileChoice; + + @FXML + private BorderPane mainPane; + + @FXML + private ToggleGroup toggleGroupMode; + + @FXML + private ToggleButton modeLive; - @Inject private StateManager stateManager; - @Inject private CliPreferences preferences; + @FXML + private ToggleButton modeLock; + + @Inject + private StateManager stateManager; + + @Inject + private CliPreferences preferences; private final PdfDocumentViewer viewer = new PdfDocumentViewer(); + private DocumentViewerViewModel viewModel; public DocumentViewerView() { this.setTitle(Localization.lang("Document viewer")); this.initModality(Modality.NONE); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); - // Remove button bar at bottom, but add close button to keep the dialog closable by clicking the "x" window symbol + // Remove button bar at bottom, but add close button to keep the dialog closable + // by clicking the "x" window symbol getDialogPane().getButtonTypes().add(ButtonType.CLOSE); getDialogPane().getChildren().removeIf(ButtonBar.class::isInstance); } @@ -81,15 +93,14 @@ private void setupModeButtons() { private void setupFileChoice() { ViewModelListCellFactory cellFactory = new ViewModelListCellFactory() - .withText(LinkedFile::getLink); + .withText(LinkedFile::getLink); fileChoice.setButtonCell(cellFactory.call(null)); fileChoice.setCellFactory(cellFactory); - fileChoice.getSelectionModel().selectedItemProperty().addListener( - (_, _, newValue) -> { - if (newValue != null && !fileChoice.getItems().isEmpty()) { - viewModel.switchToFile(newValue); - } - }); + fileChoice.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> { + if (newValue != null && !fileChoice.getItems().isEmpty()) { + viewModel.switchToFile(newValue); + } + }); fileChoice.itemsProperty().addListener((_, _, newValue) -> { if (newValue != null && !newValue.isEmpty()) { @@ -103,7 +114,7 @@ private void setupFileChoice() { private void setupViewer() { viewModel.currentDocumentProperty().addListener((_, _, newDocument) -> { - viewer.show(newDocument); + viewer.show(newDocument); }); viewModel.currentPageProperty().bindBidirectional(viewer.currentPageProperty()); viewModel.highlightTextProperty().bindBidirectional(viewer.highlightTextProperty()); @@ -125,4 +136,5 @@ public void gotoPage(int pageNumber) { public void highlightText(String text) { viewModel.highlightText(text); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java b/jabgui/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java index 776444c1330..829727c8c99 100644 --- a/jabgui/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java @@ -33,15 +33,23 @@ import org.slf4j.LoggerFactory; public class DocumentViewerViewModel extends AbstractViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(DocumentViewerViewModel.class); private final StateManager stateManager; + private final CliPreferences preferences; + private final ObjectProperty currentDocument = new SimpleObjectProperty<>(); + private final ListProperty files = new SimpleListProperty<>(); + private final BooleanProperty liveMode = new SimpleBooleanProperty(true); + private final IntegerProperty currentPage = new SimpleIntegerProperty(); + private final StringProperty highlightText = new SimpleStringProperty(); + private final DialogService dialogService; public DocumentViewerViewModel(StateManager stateManager, CliPreferences preferences, DialogService dialogService) { @@ -86,18 +94,20 @@ private void setCurrentEntries(List entries) { if (entries.isEmpty()) { files.clear(); currentDocument.set(null); - } else { + } + else { Set pdfFiles = entries.stream() - .map(BibEntry::getFiles) - .flatMap(List::stream) - .filter(this::isPdfFile) - .collect(Collectors.toSet()); + .map(BibEntry::getFiles) + .flatMap(List::stream) + .filter(this::isPdfFile) + .collect(Collectors.toSet()); if (pdfFiles.isEmpty()) { files.clear(); currentDocument.set(null); dialogService.notify(Localization.lang("No PDF files available")); - } else { + } + else { files.setValue(FXCollections.observableArrayList(pdfFiles)); } } @@ -117,7 +127,8 @@ private boolean isPdfFile(LinkedFile file) { try { Path filePath = Path.of(file.getLink()); return FileUtil.isPDFFile(filePath); - } catch (InvalidPathException | SecurityException e) { + } + catch (InvalidPathException | SecurityException e) { return false; } } @@ -125,15 +136,13 @@ private boolean isPdfFile(LinkedFile file) { public void switchToFile(LinkedFile file) { if (file != null) { stateManager.getActiveDatabase() - .flatMap(database -> file.findIn(database, preferences.getFilePreferences())) - .ifPresentOrElse( - this::setCurrentDocument, - () -> { - currentDocument.set(null); - LOGGER.warn("Could not find or access file: {}", file.getLink()); - } - ); - } else { + .flatMap(database -> file.findIn(database, preferences.getFilePreferences())) + .ifPresentOrElse(this::setCurrentDocument, () -> { + currentDocument.set(null); + LOGGER.warn("Could not find or access file: {}", file.getLink()); + }); + } + else { currentDocument.set(null); } } @@ -149,4 +158,5 @@ public void setLiveMode(boolean value) { public void highlightText(String text) { this.highlightText.set(text); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewer.java b/jabgui/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewer.java index 073032d6e8b..e414bdbf47e 100644 --- a/jabgui/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewer.java +++ b/jabgui/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewer.java @@ -27,8 +27,11 @@ public class PdfDocumentViewer extends StackPane { private static final Logger LOGGER = LoggerFactory.getLogger(PdfDocumentViewer.class); private final PDFView pdfView; + private final IntegerProperty currentPage = new SimpleIntegerProperty(0); + private final StringProperty highlightText = new SimpleStringProperty(""); + private final Label placeholderLabel; public PdfDocumentViewer() { @@ -67,17 +70,21 @@ public void show(Path document) { pdfView.setVisible(true); placeholderLabel.setVisible(false); LOGGER.debug("Successfully loaded PDF document: {}", document); - } catch (IOException | PDFView.Document.DocumentProcessingException e) { + } + catch (IOException | PDFView.Document.DocumentProcessingException e) { LOGGER.error("Could not load PDF document {}", document, e); pdfView.setVisible(false); - placeholderLabel.setText(Localization.lang("Could not load PDF: %0", document.getFileName().toString())); + placeholderLabel + .setText(Localization.lang("Could not load PDF: %0", document.getFileName().toString())); placeholderLabel.setVisible(true); } - } else { + } + else { LOGGER.debug("No document provided to viewer, showing placeholder"); pdfView.setVisible(false); placeholderLabel.setText(Localization.lang("No PDF available for preview")); placeholderLabel.setVisible(true); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/documentviewer/ShowDocumentViewerAction.java b/jabgui/src/main/java/org/jabref/gui/documentviewer/ShowDocumentViewerAction.java index cdb19a3512a..7b58834fcca 100644 --- a/jabgui/src/main/java/org/jabref/gui/documentviewer/ShowDocumentViewerAction.java +++ b/jabgui/src/main/java/org/jabref/gui/documentviewer/ShowDocumentViewerAction.java @@ -11,11 +11,14 @@ import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; public class ShowDocumentViewerAction extends SimpleCommand { + private final DialogService dialogService = Injector.instantiateModelOrService(DialogService.class); + private DocumentViewerView documentViewerView; public ShowDocumentViewerAction(StateManager stateManager, CliPreferences preferences) { - this.executable.bind(needsEntriesSelected(stateManager).and(ActionHelper.isFilePresentForSelectedEntry(stateManager, preferences))); + this.executable.bind(needsEntriesSelected(stateManager) + .and(ActionHelper.isFilePresentForSelectedEntry(stateManager, preferences))); } @Override @@ -25,5 +28,5 @@ public void execute() { } dialogService.showCustomDialog(documentViewerView); } -} +} diff --git a/jabgui/src/main/java/org/jabref/gui/duplicationFinder/DuplicateResolverDialog.java b/jabgui/src/main/java/org/jabref/gui/duplicationFinder/DuplicateResolverDialog.java index 5629a41cc9c..d4558aab049 100644 --- a/jabgui/src/main/java/org/jabref/gui/duplicationFinder/DuplicateResolverDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/duplicationFinder/DuplicateResolverDialog.java @@ -25,18 +25,17 @@ public class DuplicateResolverDialog extends BaseDialog private final StateManager stateManager; public enum DuplicateResolverType { - DUPLICATE_SEARCH, - IMPORT_CHECK, - DUPLICATE_SEARCH_WITH_EXACT + + DUPLICATE_SEARCH, IMPORT_CHECK, DUPLICATE_SEARCH_WITH_EXACT + } public enum DuplicateResolverResult { - KEEP_BOTH(Localization.lang("Keep both")), - KEEP_LEFT(Localization.lang("Keep existing entry")), + + KEEP_BOTH(Localization.lang("Keep both")), KEEP_LEFT(Localization.lang("Keep existing entry")), KEEP_RIGHT(Localization.lang("Keep from import")), AUTOREMOVE_EXACT(Localization.lang("Automatically remove exact duplicates")), - KEEP_MERGE(Localization.lang("Keep merged")), - BREAK(Localization.lang("Ask every time")); + KEEP_MERGE(Localization.lang("Keep merged")), BREAK(Localization.lang("Ask every time")); final String defaultTranslationForImport; @@ -51,23 +50,24 @@ public String getDefaultTranslationForImport() { public static DuplicateResolverResult parse(String name) { try { return DuplicateResolverResult.valueOf(name); - } catch (IllegalArgumentException e) { + } + catch (IllegalArgumentException e) { return BREAK; // default } } + } private ThreeWayMergeView threeWayMerge; + private final DialogService dialogService; + private final ActionFactory actionFactory; + private final GuiPreferences preferences; - public DuplicateResolverDialog(BibEntry one, - BibEntry two, - DuplicateResolverType type, - StateManager stateManager, - DialogService dialogService, - GuiPreferences preferences) { + public DuplicateResolverDialog(BibEntry one, BibEntry two, DuplicateResolverType type, StateManager stateManager, + DialogService dialogService, GuiPreferences preferences) { this.setTitle(Localization.lang("Possible duplicate entries")); this.stateManager = stateManager; this.dialogService = dialogService; @@ -83,7 +83,8 @@ private void init(BibEntry one, BibEntry two, DuplicateResolverType type) { ButtonType both; ButtonType second; ButtonType first; - ButtonType removeExact = new ButtonType(Localization.lang("Automatically remove exact duplicates"), ButtonData.LEFT); + ButtonType removeExact = new ButtonType(Localization.lang("Automatically remove exact duplicates"), + ButtonData.LEFT); boolean removeExactVisible = false; switch (type) { @@ -117,13 +118,16 @@ private void init(BibEntry one, BibEntry two, DuplicateResolverType type) { this.getDialogPane().getButtonTypes().add(removeExact); // This will prevent all dialog buttons from having the same size - // Read more: https://stackoverflow.com/questions/45866249/javafx-8-alert-different-button-sizes - getDialogPane().getButtonTypes().stream() - .map(getDialogPane()::lookupButton) - .forEach(btn -> ButtonBar.setButtonUniformSize(btn, false)); + // Read more: + // https://stackoverflow.com/questions/45866249/javafx-8-alert-different-button-sizes + getDialogPane().getButtonTypes() + .stream() + .map(getDialogPane()::lookupButton) + .forEach(btn -> ButtonBar.setButtonUniformSize(btn, false)); } - // Retrieves the previous window state and sets the new dialog window size and position to match it + // Retrieves the previous window state and sets the new dialog window size and + // position to match it DialogWindowState state = stateManager.getDialogWindowState(getClass().getSimpleName()); if (state != null) { this.getDialogPane().setPrefSize(state.getWidth(), state.getHeight()); @@ -135,26 +139,33 @@ private void init(BibEntry one, BibEntry two, DuplicateResolverType type) { this.setResultConverter(button -> { // Updates the window state on button press - stateManager.setDialogWindowState(getClass().getSimpleName(), new DialogWindowState(this.getX(), this.getY(), this.getDialogPane().getHeight(), this.getDialogPane().getWidth())); + stateManager.setDialogWindowState(getClass().getSimpleName(), new DialogWindowState(this.getX(), + this.getY(), this.getDialogPane().getHeight(), this.getDialogPane().getWidth())); threeWayMerge.saveConfiguration(); if (button.equals(first)) { return DuplicateResolverResult.KEEP_LEFT; - } else if (button.equals(second)) { + } + else if (button.equals(second)) { return DuplicateResolverResult.KEEP_RIGHT; - } else if (button.equals(both)) { + } + else if (button.equals(both)) { return DuplicateResolverResult.KEEP_BOTH; - } else if (button.equals(merge)) { + } + else if (button.equals(merge)) { return DuplicateResolverResult.KEEP_MERGE; - } else if (button.equals(removeExact)) { + } + else if (button.equals(removeExact)) { return DuplicateResolverResult.AUTOREMOVE_EXACT; - } else if (button.equals(cancel)) { + } + else if (button.equals(cancel)) { return DuplicateResolverResult.KEEP_LEFT; } return null; }); - HelpAction helpCommand = new HelpAction(HelpFile.FIND_DUPLICATES, dialogService, preferences.getExternalApplicationsPreferences()); + HelpAction helpCommand = new HelpAction(HelpFile.FIND_DUPLICATES, dialogService, + preferences.getExternalApplicationsPreferences()); Button helpButton = actionFactory.createIconButton(StandardActions.HELP, helpCommand); borderPane.setRight(helpButton); @@ -172,4 +183,5 @@ public BibEntry getNewLeftEntry() { public BibEntry getNewRightEntry() { return threeWayMerge.getRightEntry(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java b/jabgui/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java index f2e5ed1a0c8..72cc0c05d5b 100644 --- a/jabgui/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java +++ b/jabgui/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java @@ -42,27 +42,33 @@ public class DuplicateSearch extends SimpleCommand { private final Supplier tabSupplier; + private final BlockingQueue> duplicates = new LinkedBlockingQueue<>(); private final AtomicBoolean libraryAnalyzed = new AtomicBoolean(); + private final AtomicBoolean autoRemoveExactDuplicates = new AtomicBoolean(); + private final AtomicInteger duplicateCount = new AtomicInteger(); + private final SimpleStringProperty duplicateCountObservable = new SimpleStringProperty(); + private final SimpleStringProperty duplicateTotal = new SimpleStringProperty(); + private final SimpleIntegerProperty duplicateProgress = new SimpleIntegerProperty(0); + private final DialogService dialogService; + private final StateManager stateManager; private final GuiPreferences preferences; + private final BibEntryTypesManager entryTypesManager; + private final TaskExecutor taskExecutor; - public DuplicateSearch(Supplier tabSupplier, - DialogService dialogService, - StateManager stateManager, - GuiPreferences preferences, - BibEntryTypesManager entryTypesManager, - TaskExecutor taskExecutor) { + public DuplicateSearch(Supplier tabSupplier, DialogService dialogService, StateManager stateManager, + GuiPreferences preferences, BibEntryTypesManager entryTypesManager, TaskExecutor taskExecutor) { this.tabSupplier = tabSupplier; this.dialogService = dialogService; this.stateManager = stateManager; @@ -75,7 +81,8 @@ public DuplicateSearch(Supplier tabSupplier, @Override public void execute() { - BibDatabaseContext database = stateManager.getActiveDatabase().orElseThrow(() -> new NullPointerException("Database null")); + BibDatabaseContext database = stateManager.getActiveDatabase() + .orElseThrow(() -> new NullPointerException("Database null")); dialogService.notify(Localization.lang("Searching for duplicates...")); List entries = database.getEntries(); @@ -88,12 +95,12 @@ public void execute() { return; } - duplicateCountObservable.addListener((obj, oldValue, newValue) -> UiTaskExecutor.runAndWaitInJavaFXThread(() -> duplicateTotal.set(newValue))); + duplicateCountObservable.addListener((obj, oldValue, newValue) -> UiTaskExecutor + .runAndWaitInJavaFXThread(() -> duplicateTotal.set(newValue))); - HeadlessExecutorService.INSTANCE.executeInterruptableTask(() -> searchPossibleDuplicates(entries, database.getMode()), "DuplicateSearcher"); - BackgroundTask.wrap(this::verifyDuplicates) - .onSuccess(this::handleDuplicates) - .executeWith(taskExecutor); + HeadlessExecutorService.INSTANCE + .executeInterruptableTask(() -> searchPossibleDuplicates(entries, database.getMode()), "DuplicateSearcher"); + BackgroundTask.wrap(this::verifyDuplicates).onSuccess(this::handleDuplicates).executeWith(taskExecutor); } private void searchPossibleDuplicates(List entries, BibDatabaseMode databaseMode) { @@ -123,12 +130,14 @@ private DuplicateSearchResult verifyDuplicates() { List dups; try { - // poll with timeout in case the library is not analyzed completely, but contains no more duplicates + // poll with timeout in case the library is not analyzed completely, but + // contains no more duplicates dups = this.duplicates.poll(100, TimeUnit.MILLISECONDS); if (dups == null) { continue; } - } catch (InterruptedException e) { + } + catch (InterruptedException e) { return null; } @@ -146,7 +155,8 @@ private DuplicateSearchResult verifyDuplicates() { askAboutExact = true; } - DuplicateResolverType resolverType = askAboutExact ? DuplicateResolverType.DUPLICATE_SEARCH_WITH_EXACT : DuplicateResolverType.DUPLICATE_SEARCH; + DuplicateResolverType resolverType = askAboutExact ? DuplicateResolverType.DUPLICATE_SEARCH_WITH_EXACT + : DuplicateResolverType.DUPLICATE_SEARCH; UiTaskExecutor.runAndWaitInJavaFXThread(() -> askResolveStrategy(result, first, second, resolverType)); } @@ -155,13 +165,21 @@ private DuplicateSearchResult verifyDuplicates() { return result; } - private void askResolveStrategy(DuplicateSearchResult result, BibEntry first, BibEntry second, DuplicateResolverType resolverType) { - DuplicateResolverDialog dialog = new DuplicateResolverDialog(first, second, resolverType, stateManager, dialogService, preferences); + private void askResolveStrategy(DuplicateSearchResult result, BibEntry first, BibEntry second, + DuplicateResolverType resolverType) { + DuplicateResolverDialog dialog = new DuplicateResolverDialog(first, second, resolverType, stateManager, + dialogService, preferences); - dialog.titleProperty().bind(Bindings.concat(dialog.getTitle()).concat(" (").concat(duplicateProgress.getValue()).concat("/").concat(duplicateTotal).concat(")")); + dialog.titleProperty() + .bind(Bindings.concat(dialog.getTitle()) + .concat(" (") + .concat(duplicateProgress.getValue()) + .concat("/") + .concat(duplicateTotal) + .concat(")")); DuplicateResolverResult resolverResult = dialogService.showCustomDialogAndWait(dialog) - .orElse(DuplicateResolverResult.BREAK); + .orElse(DuplicateResolverResult.BREAK); if ((resolverResult == DuplicateResolverResult.KEEP_LEFT) || (resolverResult == DuplicateResolverResult.AUTOREMOVE_EXACT)) { @@ -170,15 +188,19 @@ private void askResolveStrategy(DuplicateSearchResult result, BibEntry first, Bi if (resolverResult == DuplicateResolverResult.AUTOREMOVE_EXACT) { autoRemoveExactDuplicates.set(true); // Remember choice } - } else if (resolverResult == DuplicateResolverResult.KEEP_RIGHT) { + } + else if (resolverResult == DuplicateResolverResult.KEEP_RIGHT) { result.remove(first); result.replace(second, dialog.getNewRightEntry()); - } else if (resolverResult == DuplicateResolverResult.BREAK) { + } + else if (resolverResult == DuplicateResolverResult.BREAK) { libraryAnalyzed.set(true); duplicates.clear(); - } else if (resolverResult == DuplicateResolverResult.KEEP_MERGE) { + } + else if (resolverResult == DuplicateResolverResult.KEEP_MERGE) { result.replace(first, second, dialog.getMergedEntry()); - } else if (resolverResult == DuplicateResolverResult.KEEP_BOTH) { + } + else if (resolverResult == DuplicateResolverResult.KEEP_BOTH) { result.replace(first, dialog.getNewLeftEntry()); result.replace(second, dialog.getNewRightEntry()); } @@ -213,13 +235,14 @@ private void handleDuplicates(DuplicateSearchResult result) { } /** - * Result of a duplicate search. - * Uses {@link System#identityHashCode(Object)} for identifying objects for removal, as completely identical - * {@link BibEntry BibEntries} are equal to each other. + * Result of a duplicate search. Uses {@link System#identityHashCode(Object)} for + * identifying objects for removal, as completely identical {@link BibEntry + * BibEntries} are equal to each other. */ static class DuplicateSearchResult { private final Map toRemove = new HashMap<>(); + private final List toAdd = new ArrayList<>(); private int duplicates = 0; @@ -258,5 +281,7 @@ public synchronized boolean isToRemove(BibEntry entry) { public synchronized int getDuplicateCount() { return duplicates; } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/CopyDoiUrlAction.java b/jabgui/src/main/java/org/jabref/gui/edit/CopyDoiUrlAction.java index 7f9c93b70af..38069ad2274 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/CopyDoiUrlAction.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/CopyDoiUrlAction.java @@ -17,11 +17,15 @@ public class CopyDoiUrlAction extends SimpleCommand { private final TextField component; + private final StandardActions action; + private final DialogService dialogService; + private final ClipBoardManager clipBoardManager; - public CopyDoiUrlAction(TextField component, StandardActions action, DialogService dialogService, ClipBoardManager clipBoardManager) { + public CopyDoiUrlAction(TextField component, StandardActions action, DialogService dialogService, + ClipBoardManager clipBoardManager) { this.component = component; this.action = action; this.dialogService = dialogService; @@ -34,7 +38,8 @@ public void execute() { if (action == StandardActions.COPY_DOI_URL) { copy(DOI.parse(identifier).map(DOI::getURIAsASCIIString), identifier); - } else { + } + else { copy(DOI.parse(identifier).map(DOI::asString), identifier); } } @@ -43,8 +48,10 @@ private void copy(Optional urlOptional, String identifier) { if (urlOptional.isPresent()) { clipBoardManager.setContent(urlOptional.get()); dialogService.notify(Localization.lang("The link has been copied to the clipboard.")); - } else { + } + else { dialogService.notify(Localization.lang("Invalid DOI: '%0'.", identifier)); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java b/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java index 193a00a5bab..709330dacac 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java @@ -30,19 +30,22 @@ public class CopyMoreAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(CopyMoreAction.class); + private final StandardActions action; + private final DialogService dialogService; + private final StateManager stateManager; + private final ClipBoardManager clipBoardManager; + private final GuiPreferences preferences; + private final JournalAbbreviationRepository abbreviationRepository; - public CopyMoreAction(StandardActions action, - DialogService dialogService, - StateManager stateManager, - ClipBoardManager clipBoardManager, - GuiPreferences preferences, - JournalAbbreviationRepository abbreviationRepository) { + public CopyMoreAction(StandardActions action, DialogService dialogService, StateManager stateManager, + ClipBoardManager clipBoardManager, GuiPreferences preferences, + JournalAbbreviationRepository abbreviationRepository) { this.action = action; this.dialogService = dialogService; this.stateManager = stateManager; @@ -60,20 +63,13 @@ public void execute() { } switch (action) { - case COPY_TITLE -> - copyTitle(); - case COPY_KEY -> - copyKey(); - case COPY_CITE_KEY -> - copyCiteKey(); - case COPY_KEY_AND_TITLE -> - copyKeyAndTitle(); - case COPY_KEY_AND_LINK -> - copyKeyAndLink(); - case COPY_DOI, COPY_DOI_URL -> - copyDoi(); - default -> - LOGGER.info("Unknown copy command."); + case COPY_TITLE -> copyTitle(); + case COPY_KEY -> copyKey(); + case COPY_CITE_KEY -> copyCiteKey(); + case COPY_KEY_AND_TITLE -> copyKeyAndTitle(); + case COPY_KEY_AND_LINK -> copyKeyAndLink(); + case COPY_DOI, COPY_DOI_URL -> copyDoi(); + default -> LOGGER.info("Unknown copy command."); } } @@ -81,9 +77,9 @@ private void copyTitle() { List selectedBibEntries = stateManager.getSelectedEntries(); List titles = selectedBibEntries.stream() - .filter(bibEntry -> bibEntry.getTitle().isPresent()) - .map(bibEntry -> bibEntry.getTitle().get()) - .collect(Collectors.toList()); + .filter(bibEntry -> bibEntry.getTitle().isPresent()) + .map(bibEntry -> bibEntry.getTitle().get()) + .collect(Collectors.toList()); if (titles.isEmpty()) { dialogService.notify(Localization.lang("None of the selected entries have titles.")); @@ -97,9 +93,11 @@ private void copyTitle() { // All entries had titles. dialogService.notify(Localization.lang("Copied '%0' to clipboard.", JabRefDialogService.shortenDialogMessage(copiedTitles))); - } else { + } + else { dialogService.notify(Localization.lang("Warning: %0 out of %1 entries have undefined title.", - Integer.toString(selectedBibEntries.size() - titles.size()), Integer.toString(selectedBibEntries.size()))); + Integer.toString(selectedBibEntries.size() - titles.size()), + Integer.toString(selectedBibEntries.size()))); } } @@ -109,14 +107,15 @@ private void copyDoi() { // Collect all non-null DOI or DOI urls if (action == StandardActions.COPY_DOI_URL) { copyDoiList(entries.stream() - .filter(entry -> entry.getDOI().isPresent()) - .map(entry -> entry.getDOI().get().getURIAsASCIIString()) - .collect(Collectors.toList()), entries.size()); - } else { + .filter(entry -> entry.getDOI().isPresent()) + .map(entry -> entry.getDOI().get().getURIAsASCIIString()) + .collect(Collectors.toList()), entries.size()); + } + else { copyDoiList(entries.stream() - .filter(entry -> entry.getDOI().isPresent()) - .map(entry -> entry.getDOI().get().asString()) - .collect(Collectors.toList()), entries.size()); + .filter(entry -> entry.getDOI().isPresent()) + .map(entry -> entry.getDOI().get().asString()) + .collect(Collectors.toList()), entries.size()); } } @@ -133,7 +132,8 @@ private void copyDoiList(List dois, int size) { // All entries had DOIs. dialogService.notify(Localization.lang("Copied '%0' to clipboard.", JabRefDialogService.shortenDialogMessage(copiedDois))); - } else { + } + else { dialogService.notify(Localization.lang("Warning: %0 out of %1 entries have undefined DOIs.", Integer.toString(size - dois.size()), Integer.toString(size))); } @@ -144,9 +144,9 @@ private void doCopyKey(Function, String> mapKeyList) { // Collect all non-null keys. List keys = entries.stream() - .filter(entry -> entry.getCitationKey().isPresent()) - .map(entry -> entry.getCitationKey().get()) - .collect(Collectors.toList()); + .filter(entry -> entry.getCitationKey().isPresent()) + .map(entry -> entry.getCitationKey().get()) + .collect(Collectors.toList()); if (keys.isEmpty()) { dialogService.notify(Localization.lang("None of the selected entries have citation keys.")); @@ -161,7 +161,8 @@ private void doCopyKey(Function, String> mapKeyList) { // All entries had keys. dialogService.notify(Localization.lang("Copied '%0' to clipboard.", JabRefDialogService.shortenDialogMessage(clipBoardContent))); - } else { + } + else { dialogService.notify(Localization.lang("Warning: %0 out of %1 entries have undefined citation key.", Integer.toString(entries.size() - keys.size()), Integer.toString(entries.size()))); } @@ -175,18 +176,22 @@ private void copyCiteKey() { } private void copyKey() { - doCopyKey(keys -> String.join(preferences.getPushToApplicationPreferences().getCiteCommand().delimiter(), keys)); + doCopyKey( + keys -> String.join(preferences.getPushToApplicationPreferences().getCiteCommand().delimiter(), keys)); } private void copyKeyAndTitle() { List entries = stateManager.getSelectedEntries(); // ToDo: this string should be configurable to allow arbitrary exports - Reader layoutString = Reader.of("\\citationkey - \\begin{title}\\format[RemoveBrackets]{\\title}\\end{title}\n"); + Reader layoutString = Reader + .of("\\citationkey - \\begin{title}\\format[RemoveBrackets]{\\title}\\end{title}\n"); Layout layout; try { - layout = new LayoutHelper(layoutString, preferences.getLayoutFormatterPreferences(), abbreviationRepository).getLayoutFromText(); - } catch (IOException e) { + layout = new LayoutHelper(layoutString, preferences.getLayoutFormatterPreferences(), abbreviationRepository) + .getLayoutFromText(); + } + catch (IOException e) { LOGGER.info("Could not get layout.", e); return; } @@ -199,8 +204,8 @@ private void copyKeyAndTitle() { if (entry.hasCitationKey()) { entriesWithKeys++; stateManager.getActiveDatabase() - .map(BibDatabaseContext::getDatabase) - .ifPresent(bibDatabase -> keyAndTitle.append(layout.doLayout(entry, bibDatabase))); + .map(BibDatabaseContext::getDatabase) + .ifPresent(bibDatabase -> keyAndTitle.append(layout.doLayout(entry, bibDatabase))); } } @@ -215,16 +220,17 @@ private void copyKeyAndTitle() { // All entries had keys. dialogService.notify(Localization.lang("Copied '%0' to clipboard.", JabRefDialogService.shortenDialogMessage(keyAndTitle.toString()))); - } else { + } + else { dialogService.notify(Localization.lang("Warning: %0 out of %1 entries have undefined citation key.", Integer.toString(entries.size() - entriesWithKeys), Integer.toString(entries.size()))); } } /** - * This method will copy each selected entry's citation key as a hyperlink to its url to the clipboard. In case an - * entry doesn't have a citation key it will not be copied. In case an entry doesn't have an url this will only copy - * the citation key. + * This method will copy each selected entry's citation key as a hyperlink to its url + * to the clipboard. In case an entry doesn't have a citation key it will not be + * copied. In case an entry doesn't have an url this will only copy the citation key. */ private void copyKeyAndLink() { List entries = stateManager.getSelectedEntries(); @@ -232,9 +238,7 @@ private void copyKeyAndLink() { StringBuilder keyAndLink = new StringBuilder(); StringBuilder fallbackString = new StringBuilder(); - List entriesWithKey = entries.stream() - .filter(BibEntry::hasCitationKey) - .toList(); + List entriesWithKey = entries.stream().filter(BibEntry::hasCitationKey).toList(); if (entriesWithKey.isEmpty()) { dialogService.notify(Localization.lang("None of the selected entries have citation keys.")); @@ -259,9 +263,11 @@ private void copyKeyAndLink() { // All entries had keys. dialogService.notify(Localization.lang("Copied '%0' to clipboard.", JabRefDialogService.shortenDialogMessage(keyAndLink.toString()))); - } else { + } + else { dialogService.notify(Localization.lang("Warning: %0 out of %1 entries have undefined citation key.", Long.toString(entries.size() - entriesWithKey.size()), Integer.toString(entries.size()))); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java b/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java index 2164994660c..a57faadb2d1 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java @@ -23,18 +23,20 @@ public class CopyTo extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(CopyTo.class); private final DialogService dialogService; + private final StateManager stateManager; + private final CopyToPreferences copyToPreferences; + private final ImportHandler importHandler; + private final BibDatabaseContext sourceDatabaseContext; + private final BibDatabaseContext targetDatabaseContext; - public CopyTo(DialogService dialogService, - StateManager stateManager, - CopyToPreferences copyToPreferences, - ImportHandler importHandler, - BibDatabaseContext sourceDatabaseContext, - BibDatabaseContext targetDatabaseContext) { + public CopyTo(DialogService dialogService, StateManager stateManager, CopyToPreferences copyToPreferences, + ImportHandler importHandler, BibDatabaseContext sourceDatabaseContext, + BibDatabaseContext targetDatabaseContext) { this.dialogService = dialogService; this.stateManager = stateManager; this.copyToPreferences = copyToPreferences; @@ -47,7 +49,8 @@ public CopyTo(DialogService dialogService, @Override public void execute() { - // we need to operate on a copy otherwise we might get ConcurrentModification issues + // we need to operate on a copy otherwise we might get ConcurrentModification + // issues List selectedEntries = stateManager.getSelectedEntries().stream().toList(); boolean includeCrossReferences = copyToPreferences.getShouldIncludeCrossReferences(); @@ -63,7 +66,8 @@ public void execute() { if (includeCrossReferences) { copyEntriesWithCrossRef(selectedEntries, targetDatabaseContext); - } else { + } + else { copyEntriesWithoutCrossRef(selectedEntries, targetDatabaseContext); } } @@ -71,8 +75,10 @@ public void execute() { public void copyEntriesWithCrossRef(List selectedEntries, BibDatabaseContext targetDatabaseContext) { List entriesToAdd = new ArrayList<>(selectedEntries); - List entriesWithCrossRef = selectedEntries.stream().filter(bibEntry -> bibEntry.hasField(StandardField.CROSSREF)) - .flatMap(entry -> getCrossRefEntry(entry, sourceDatabaseContext).stream()).toList(); + List entriesWithCrossRef = selectedEntries.stream() + .filter(bibEntry -> bibEntry.hasField(StandardField.CROSSREF)) + .flatMap(entry -> getCrossRefEntry(entry, sourceDatabaseContext).stream()) + .toList(); entriesToAdd.addAll(entriesWithCrossRef); copyEntriesWithFeedback(entriesToAdd, targetDatabaseContext, @@ -86,22 +92,26 @@ public void copyEntriesWithoutCrossRef(List selectedEntries, BibDataba Localization.lang("Copied %0 entry(s) to %1. %2 were skipped without cross-references")); } - private void copyEntriesWithFeedback(List entriesToAdd, BibDatabaseContext targetDatabaseContext, String successMessage, String partialMessage) { + private void copyEntriesWithFeedback(List entriesToAdd, BibDatabaseContext targetDatabaseContext, + String successMessage, String partialMessage) { EntryImportHandlerTracker tracker = new EntryImportHandlerTracker(entriesToAdd.size()); tracker.setOnFinish(() -> { int importedCount = tracker.getImportedCount(); int skippedCount = tracker.getSkippedCount(); String targetName = targetDatabaseContext.getDatabasePath() - .map(path -> path.getFileName().toString()) - .orElse(Localization.lang("target library")); + .map(path -> path.getFileName().toString()) + .orElse(Localization.lang("target library")); if (importedCount == entriesToAdd.size()) { dialogService.notify(Localization.lang(successMessage, String.valueOf(importedCount), targetName)); - } else if (importedCount == 0) { + } + else if (importedCount == 0) { dialogService.notify(Localization.lang("No entry was copied to %0", targetName)); - } else { - dialogService.notify(Localization.lang(partialMessage, String.valueOf(importedCount), targetName, String.valueOf(skippedCount))); + } + else { + dialogService.notify(Localization.lang(partialMessage, String.valueOf(importedCount), targetName, + String.valueOf(skippedCount))); } }); @@ -109,7 +119,10 @@ private void copyEntriesWithFeedback(List entriesToAdd, BibDatabaseCon } public Optional getCrossRefEntry(BibEntry bibEntryToCheck, BibDatabaseContext sourceDatabaseContext) { - return sourceDatabaseContext.getEntries().stream().filter(entry -> bibEntryToCheck.getField(StandardField.CROSSREF).equals(entry.getCitationKey())).findFirst(); + return sourceDatabaseContext.getEntries() + .stream() + .filter(entry -> bibEntryToCheck.getField(StandardField.CROSSREF).equals(entry.getCitationKey())) + .findFirst(); } private boolean askForCrossReferencedEntries() { @@ -117,13 +130,12 @@ private boolean askForCrossReferencedEntries() { return dialogService.showConfirmationDialogWithOptOutAndWait( Localization.lang("Include or exclude cross-referenced entries"), Localization.lang("Would you like to include cross-reference entries in the current operation?"), - Localization.lang("Include"), - Localization.lang("Exclude"), - Localization.lang("Do not ask again"), - optOut -> copyToPreferences.setShouldAskForIncludingCrossReferences(!optOut) - ); - } else { + Localization.lang("Include"), Localization.lang("Exclude"), Localization.lang("Do not ask again"), + optOut -> copyToPreferences.setShouldAskForIncludingCrossReferences(!optOut)); + } + else { return copyToPreferences.getShouldIncludeCrossReferences(); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/CopyToPreferences.java b/jabgui/src/main/java/org/jabref/gui/edit/CopyToPreferences.java index c0d57b45c09..0e8df674e8e 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/CopyToPreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/CopyToPreferences.java @@ -4,7 +4,9 @@ import javafx.beans.property.SimpleBooleanProperty; public class CopyToPreferences { + private final BooleanProperty shouldIncludeCrossReferences = new SimpleBooleanProperty(); + private final BooleanProperty shouldAskForIncludingCrossReferences = new SimpleBooleanProperty(); public CopyToPreferences(boolean shouldAskForIncludingCrossReferences, boolean shouldIncludeCrossReferences) { @@ -35,4 +37,5 @@ public void setShouldAskForIncludingCrossReferences(boolean decision) { public BooleanProperty shouldAskForIncludingCrossReferencesProperty() { return shouldAskForIncludingCrossReferences; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/EditAction.java b/jabgui/src/main/java/org/jabref/gui/edit/EditAction.java index 48133bb10da..70ed19c01cc 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/EditAction.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/EditAction.java @@ -18,19 +18,24 @@ import org.slf4j.LoggerFactory; /** - * Class for handling general actions; cut, copy and paste. The focused component is kept track of by - * Globals.focusListener, and we call the action stored under the relevant name in its action map. + * Class for handling general actions; cut, copy and paste. The focused component is kept + * track of by Globals.focusListener, and we call the action stored under the relevant + * name in its action map. */ public class EditAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(EditAction.class); private final Supplier tabSupplier; + private final StandardActions action; + private final StateManager stateManager; + private final UndoManager undoManager; - public EditAction(StandardActions action, Supplier tabSupplier, StateManager stateManager, UndoManager undoManager) { + public EditAction(StandardActions action, Supplier tabSupplier, StateManager stateManager, + UndoManager undoManager) { this.action = action; this.tabSupplier = tabSupplier; this.stateManager = stateManager; @@ -38,7 +43,8 @@ public EditAction(StandardActions action, Supplier tabSupplier, Stat if (action == StandardActions.PASTE) { this.executable.bind(ActionHelper.needsDatabase(stateManager)); - } else { + } + else { this.executable.bind(ActionHelper.needsEntriesSelected(stateManager)); } } @@ -70,11 +76,14 @@ public void execute() { throw new IllegalStateException(message); } } - } else if ((focusOwner instanceof CodeArea) || (focusOwner instanceof WebView)) { + } + else if ((focusOwner instanceof CodeArea) || (focusOwner instanceof WebView)) { LOGGER.debug("Ignoring request in CodeArea or WebView"); - } else { + } + else { LOGGER.debug("Else: {}", focusOwner.getClass().getSimpleName()); - // Not sure what is selected -> copy/paste/cut selected entries except for Preview and CodeArea + // Not sure what is selected -> copy/paste/cut selected entries except for + // Preview and CodeArea switch (action) { case COPY -> tabSupplier.get().copyEntry(); @@ -91,9 +100,11 @@ public void execute() { undoManager.redo(); } } - default -> LOGGER.debug("Only cut/copy/paste/deleteEntry supported but got: {} and focus owner {}", action, focusOwner); + default -> LOGGER.debug("Only cut/copy/paste/deleteEntry supported but got: {} and focus owner {}", + action, focusOwner); } } }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/ManageKeywordsAction.java b/jabgui/src/main/java/org/jabref/gui/edit/ManageKeywordsAction.java index 5ea7a6c9565..8c2b1aa9af4 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/ManageKeywordsAction.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/ManageKeywordsAction.java @@ -22,7 +22,8 @@ public ManageKeywordsAction(StateManager stateManager) { this.stateManager = stateManager; this.executable.bind(needsDatabase(stateManager).and(needsEntriesSelected(stateManager))); - this.statusMessage.bind(BindingsHelper.ifThenElse(this.executable, "", Localization.lang("Select at least one entry to manage keywords."))); + this.statusMessage.bind(BindingsHelper.ifThenElse(this.executable, "", + Localization.lang("Select at least one entry to manage keywords."))); } @Override @@ -30,4 +31,5 @@ public void execute() { DialogService dialogService = Injector.instantiateModelOrService(DialogService.class); dialogService.showCustomDialogAndWait(new ManageKeywordsDialog(stateManager.getSelectedEntries())); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/ManageKeywordsDialog.java b/jabgui/src/main/java/org/jabref/gui/edit/ManageKeywordsDialog.java index 00d42f25e31..8dc8a406ed5 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/ManageKeywordsDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/ManageKeywordsDialog.java @@ -22,22 +22,34 @@ import jakarta.inject.Inject; public class ManageKeywordsDialog extends BaseDialog { + private final List entries; - @FXML private TableColumn keywordsTableMainColumn; - @FXML private TableColumn keywordsTableEditColumn; - @FXML private TableColumn keywordsTableDeleteColumn; - @FXML private TableView keywordsTable; - @FXML private ToggleGroup displayType; - @Inject private CliPreferences preferences; + + @FXML + private TableColumn keywordsTableMainColumn; + + @FXML + private TableColumn keywordsTableEditColumn; + + @FXML + private TableColumn keywordsTableDeleteColumn; + + @FXML + private TableView keywordsTable; + + @FXML + private ToggleGroup displayType; + + @Inject + private CliPreferences preferences; + private ManageKeywordsViewModel viewModel; public ManageKeywordsDialog(List entries) { this.entries = entries; this.setTitle(Localization.lang("Manage keywords")); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); setResultConverter(button -> { if (button == ButtonType.APPLY) { @@ -51,32 +63,32 @@ public ManageKeywordsDialog(List entries) { public void initialize() { viewModel = new ManageKeywordsViewModel(preferences.getBibEntryPreferences(), entries); - viewModel.displayTypeProperty().bind( - EasyBind.map(displayType.selectedToggleProperty(), toggle -> { - if (toggle != null) { - return (ManageKeywordsDisplayType) toggle.getUserData(); - } else { - return ManageKeywordsDisplayType.CONTAINED_IN_ALL_ENTRIES; - } - }) - ); + viewModel.displayTypeProperty().bind(EasyBind.map(displayType.selectedToggleProperty(), toggle -> { + if (toggle != null) { + return (ManageKeywordsDisplayType) toggle.getUserData(); + } + else { + return ManageKeywordsDisplayType.CONTAINED_IN_ALL_ENTRIES; + } + })); keywordsTable.setItems(viewModel.getKeywords()); keywordsTableMainColumn.setCellValueFactory(data -> BindingsHelper.constantOf(data.getValue())); keywordsTableMainColumn.setOnEditCommit(event -> { - // Poor mans reverse databinding (necessary because we use a constant value above) + // Poor mans reverse databinding (necessary because we use a constant value + // above) viewModel.getKeywords().set(event.getTablePosition().getRow(), event.getNewValue()); }); keywordsTableMainColumn.setCellFactory(TextFieldTableCell.forTableColumn()); keywordsTableEditColumn.setCellValueFactory(data -> BindingsHelper.constantOf(true)); keywordsTableDeleteColumn.setCellValueFactory(data -> BindingsHelper.constantOf(true)); - new ValueTableCellFactory() - .withGraphic(none -> IconTheme.JabRefIcons.EDIT.getGraphicNode()) - .withOnMouseClickedEvent(none -> event -> keywordsTable.edit(keywordsTable.getFocusModel().getFocusedIndex(), keywordsTableMainColumn)) - .install(keywordsTableEditColumn); - new ValueTableCellFactory() - .withGraphic(none -> IconTheme.JabRefIcons.REMOVE.getGraphicNode()) - .withOnMouseClickedEvent((keyword, none) -> event -> viewModel.removeKeyword(keyword)) - .install(keywordsTableDeleteColumn); + new ValueTableCellFactory().withGraphic(none -> IconTheme.JabRefIcons.EDIT.getGraphicNode()) + .withOnMouseClickedEvent(none -> event -> keywordsTable + .edit(keywordsTable.getFocusModel().getFocusedIndex(), keywordsTableMainColumn)) + .install(keywordsTableEditColumn); + new ValueTableCellFactory().withGraphic(none -> IconTheme.JabRefIcons.REMOVE.getGraphicNode()) + .withOnMouseClickedEvent((keyword, none) -> event -> viewModel.removeKeyword(keyword)) + .install(keywordsTableDeleteColumn); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/ManageKeywordsDisplayType.java b/jabgui/src/main/java/org/jabref/gui/edit/ManageKeywordsDisplayType.java index 0af51af0734..d05ed96ac72 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/ManageKeywordsDisplayType.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/ManageKeywordsDisplayType.java @@ -1,6 +1,7 @@ package org.jabref.gui.edit; public enum ManageKeywordsDisplayType { - CONTAINED_IN_ALL_ENTRIES, - CONTAINED_IN_ANY_ENTRY, + + CONTAINED_IN_ALL_ENTRIES, CONTAINED_IN_ANY_ENTRY, + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/ManageKeywordsViewModel.java b/jabgui/src/main/java/org/jabref/gui/edit/ManageKeywordsViewModel.java index c4a5f8773a0..03959549d72 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/ManageKeywordsViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/ManageKeywordsViewModel.java @@ -22,9 +22,14 @@ public class ManageKeywordsViewModel { private final List entries; + private final KeywordList sortedKeywordsOfAllEntriesBeforeUpdateByUser = new KeywordList(); + private final BibEntryPreferences bibEntryPreferences; - private final ObjectProperty displayType = new SimpleObjectProperty<>(ManageKeywordsDisplayType.CONTAINED_IN_ALL_ENTRIES); + + private final ObjectProperty displayType = new SimpleObjectProperty<>( + ManageKeywordsDisplayType.CONTAINED_IN_ALL_ENTRIES); + private final ObservableList keywords; public ManageKeywordsViewModel(BibEntryPreferences bibEntryPreferences, List entries) { @@ -53,19 +58,22 @@ private void fillKeywordsList(ManageKeywordsDisplayType type) { KeywordList separatedKeywords = entry.getKeywords(keywordSeparator); sortedKeywordsOfAllEntriesBeforeUpdateByUser.addAll(separatedKeywords); } - } else if (type == ManageKeywordsDisplayType.CONTAINED_IN_ANY_ENTRY) { + } + else if (type == ManageKeywordsDisplayType.CONTAINED_IN_ANY_ENTRY) { // all keywords from first entry have to be added BibEntry firstEntry = entries.getFirst(); KeywordList separatedKeywords = firstEntry.getKeywords(keywordSeparator); sortedKeywordsOfAllEntriesBeforeUpdateByUser.addAll(separatedKeywords); // for the remaining entries, intersection has to be used - // this approach ensures that one empty keyword list leads to an empty set of common keywords + // this approach ensures that one empty keyword list leads to an empty set of + // common keywords for (BibEntry entry : entries) { separatedKeywords = entry.getKeywords(keywordSeparator); sortedKeywordsOfAllEntriesBeforeUpdateByUser.retainAll(separatedKeywords); } - } else { + } + else { throw new IllegalStateException("DisplayType " + type + " not handled"); } for (Keyword keyword : sortedKeywordsOfAllEntriesBeforeUpdateByUser) { @@ -109,7 +117,7 @@ public void saveChanges() { } private NamedCompound updateKeywords(List entries, KeywordList keywordsToAdd, - KeywordList keywordsToRemove) { + KeywordList keywordsToRemove) { Character keywordSeparator = bibEntryPreferences.getKeywordSeparator(); NamedCompound ce = new NamedCompound(Localization.lang("Update keywords")); @@ -127,4 +135,5 @@ private NamedCompound updateKeywords(List entries, KeywordList keyword ce.end(); return ce; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/OpenBrowserAction.java b/jabgui/src/main/java/org/jabref/gui/edit/OpenBrowserAction.java index 9711dd5478e..1ce7d791fe6 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/OpenBrowserAction.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/OpenBrowserAction.java @@ -8,10 +8,13 @@ public class OpenBrowserAction extends SimpleCommand { private final String urlToOpen; + private final DialogService dialogService; + private final ExternalApplicationsPreferences externalApplicationsPreferences; - public OpenBrowserAction(String urlToOpen, DialogService dialogService, ExternalApplicationsPreferences externalApplicationsPreferences) { + public OpenBrowserAction(String urlToOpen, DialogService dialogService, + ExternalApplicationsPreferences externalApplicationsPreferences) { this.urlToOpen = urlToOpen; this.dialogService = dialogService; this.externalApplicationsPreferences = externalApplicationsPreferences; @@ -21,4 +24,5 @@ public OpenBrowserAction(String urlToOpen, DialogService dialogService, External public void execute() { NativeDesktop.openBrowserShowPopup(urlToOpen, dialogService, externalApplicationsPreferences); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/ReplaceStringAction.java b/jabgui/src/main/java/org/jabref/gui/edit/ReplaceStringAction.java index 3a7ba00795c..30b870bd703 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/ReplaceStringAction.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/ReplaceStringAction.java @@ -9,10 +9,13 @@ import org.jabref.gui.actions.SimpleCommand; public class ReplaceStringAction extends SimpleCommand { + private final Supplier tabSupplier; + private final DialogService dialogService; - public ReplaceStringAction(Supplier tabSupplier, StateManager stateManager, DialogService dialogService) { + public ReplaceStringAction(Supplier tabSupplier, StateManager stateManager, + DialogService dialogService) { this.tabSupplier = tabSupplier; this.dialogService = dialogService; this.executable.bind(ActionHelper.needsDatabase(stateManager)); @@ -22,4 +25,5 @@ public ReplaceStringAction(Supplier tabSupplier, StateManager stateM public void execute() { dialogService.showCustomDialogAndWait(new ReplaceStringView(tabSupplier.get())); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/ReplaceStringView.java b/jabgui/src/main/java/org/jabref/gui/edit/ReplaceStringView.java index a931406c99c..87b52344076 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/ReplaceStringView.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/ReplaceStringView.java @@ -17,12 +17,23 @@ public class ReplaceStringView extends BaseDialog { - @FXML private RadioButton allReplace; - @FXML private CheckBox selectFieldOnly; - @FXML private ButtonType replaceButton; - @FXML private TextField limitFieldInput; - @FXML private TextField findField; - @FXML private TextField replaceField; + @FXML + private RadioButton allReplace; + + @FXML + private CheckBox selectFieldOnly; + + @FXML + private ButtonType replaceButton; + + @FXML + private TextField limitFieldInput; + + @FXML + private TextField findField; + + @FXML + private TextField replaceField; private ReplaceStringViewModel viewModel; @@ -33,9 +44,7 @@ public ReplaceStringView(LibraryTab libraryTab) { viewModel = new ReplaceStringViewModel(libraryTab); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); ControlHelper.setAction(replaceButton, getDialogPane(), event -> buttonReplace()); } @@ -61,4 +70,5 @@ private void buttonReplace() { viewModel.replace(); this.close(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java b/jabgui/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java index 04db4bf9147..0e5bac3eddb 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java @@ -18,16 +18,25 @@ import org.jabref.model.entry.field.FieldFactory; public class ReplaceStringViewModel extends AbstractViewModel { + private boolean allFieldReplace; + private String findString; + private String replaceString; + private Set fields; + private LibraryTab panel; private StringProperty findStringProperty = new SimpleStringProperty(); + private StringProperty replaceStringProperty = new SimpleStringProperty(); + private StringProperty fieldStringProperty = new SimpleStringProperty(); + private BooleanProperty allFieldReplaceProperty = new SimpleBooleanProperty(); + private BooleanProperty selectOnlyProperty = new SimpleBooleanProperty(); public ReplaceStringViewModel(LibraryTab libraryTab) { @@ -48,7 +57,8 @@ public int replace() { for (BibEntry bibEntry : this.panel.getSelectedEntries()) { counter += replaceItem(bibEntry, compound); } - } else { + } + else { for (BibEntry bibEntry : this.panel.getDatabase().getEntries()) { counter += replaceItem(bibEntry, compound); } @@ -57,8 +67,8 @@ public int replace() { } /** - * Does the actual operation on a Bibtex entry based on the settings specified in this same dialog. Returns the - * number of occurrences replaced. + * Does the actual operation on a Bibtex entry based on the settings specified in this + * same dialog. Returns the number of occurrences replaced. */ private int replaceItem(BibEntry entry, NamedCompound compound) { int counter = 0; @@ -66,7 +76,8 @@ private int replaceItem(BibEntry entry, NamedCompound compound) { for (Field field : entry.getFields()) { counter += replaceField(entry, field, compound); } - } else { + } + else { for (Field espField : fields) { counter += replaceField(entry, espField, compound); } @@ -116,4 +127,5 @@ public StringProperty findStringProperty() { public StringProperty replaceStringProperty() { return replaceStringProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabView.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabView.java index 2954b013f68..e61c8cb70b4 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabView.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabView.java @@ -8,4 +8,5 @@ public abstract class AbstractAutomaticFieldEditorTabView extends AnchorPane imp public AnchorPane getContent() { return this; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabViewModel.java index c7c435d4a83..f0ac61638b4 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabViewModel.java @@ -17,6 +17,7 @@ import org.jabref.model.entry.field.StandardField; public abstract class AbstractAutomaticFieldEditorTabViewModel extends AbstractViewModel { + protected final StateManager stateManager; private final ObservableList allFields = FXCollections.observableArrayList(); @@ -40,4 +41,5 @@ private void addFields(Collection fields) { fieldsSet.addAll(fields); allFields.setAll(fieldsSet); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorAction.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorAction.java index afd8adc29b9..068caeffbb3 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorAction.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorAction.java @@ -10,8 +10,11 @@ import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; public class AutomaticFieldEditorAction extends SimpleCommand { + private final StateManager stateManager; + private final DialogService dialogService; + private final UndoManager undoManager; public AutomaticFieldEditorAction(StateManager stateManager, DialogService dialogService, UndoManager undoManager) { @@ -26,4 +29,5 @@ public AutomaticFieldEditorAction(StateManager stateManager, DialogService dialo public void execute() { dialogService.showCustomDialogAndWait(new AutomaticFieldEditorDialog(stateManager, undoManager)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java index d2d0893a488..9adcc16f604 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java @@ -32,6 +32,7 @@ public class AutomaticFieldEditorDialog extends BaseDialog { private final UndoManager undoManager; private final BibDatabase database; + private final List selectedEntries; private final StateManager stateManager; @@ -48,22 +49,23 @@ public AutomaticFieldEditorDialog(StateManager stateManager, UndoManager undoMan this.setTitle(Localization.lang("Automatic field editor")); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); setResultConverter(buttonType -> { if (buttonType != null && buttonType.getButtonData() == ButtonBar.ButtonData.OK_DONE) { saveChanges(); - } else { + } + else { cancelChanges(); } return ""; }); // This will prevent all dialog buttons from having the same size - // Read more: https://stackoverflow.com/questions/45866249/javafx-8-alert-different-button-sizes - getDialogPane().getButtonTypes().stream() + // Read more: + // https://stackoverflow.com/questions/45866249/javafx-8-alert-different-button-sizes + getDialogPane().getButtonTypes() + .stream() .map(getDialogPane()::lookupButton) .forEach(btn -> ButtonBar.setButtonUniformSize(btn, false)); } @@ -80,8 +82,7 @@ public void initialize() { EasyBind.listen(stateManager.lastAutomaticFieldEditorEditProperty(), (obs, old, lastEdit) -> { viewModel.getDialogEdits().addEdit(lastEdit.getEdit()); - notificationPanes.get(lastEdit.getTabIndex()) - .notify(lastEdit.getAffectedEntries(), selectedEntries.size()); + notificationPanes.get(lastEdit.getTabIndex()).notify(lastEdit.getAffectedEntries(), selectedEntries.size()); }); } @@ -92,8 +93,10 @@ private void saveChanges() { private void cancelChanges() { try { viewModel.cancelChanges(); - } catch (CannotUndoException e) { + } + catch (CannotUndoException e) { LOGGER.info("Could not undo", e); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorTab.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorTab.java index 7863422deff..ffb11232ae6 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorTab.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorTab.java @@ -3,7 +3,9 @@ import javafx.scene.layout.Pane; public interface AutomaticFieldEditorTab { + Pane getContent(); String getTabName(); + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java index 3f517a1dabd..77bd647d6de 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java @@ -17,19 +17,21 @@ import org.jabref.model.entry.BibEntry; public class AutomaticFieldEditorViewModel extends AbstractViewModel { + public static final String NAMED_COMPOUND_EDITS = "EDIT_FIELDS"; + private final ObservableList fieldEditorTabs = FXCollections.observableArrayList(); + private final NamedCompound dialogEdits = new NamedCompound(NAMED_COMPOUND_EDITS); private final UndoManager undoManager; - public AutomaticFieldEditorViewModel(List selectedEntries, BibDatabase database, UndoManager undoManager, StateManager stateManager) { + public AutomaticFieldEditorViewModel(List selectedEntries, BibDatabase database, UndoManager undoManager, + StateManager stateManager) { this.undoManager = undoManager; - fieldEditorTabs.addAll( - new EditFieldContentTabView(database, stateManager), + fieldEditorTabs.addAll(new EditFieldContentTabView(database, stateManager), new CopyOrMoveFieldContentTabView(database, stateManager), - new RenameFieldTabView(database, stateManager) - ); + new RenameFieldTabView(database, stateManager)); } public NamedCompound getDialogEdits() { @@ -49,4 +51,5 @@ public void cancelChanges() { dialogEdits.end(); dialogEdits.undo(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/LastAutomaticFieldEditorEdit.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/LastAutomaticFieldEditorEdit.java index a1263576608..288d2953596 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/LastAutomaticFieldEditorEdit.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/LastAutomaticFieldEditorEdit.java @@ -7,7 +7,9 @@ import org.jabref.gui.undo.NamedCompound; public class LastAutomaticFieldEditorEdit extends AbstractUndoableEdit { + private final Integer affectedEntries; + private final NamedCompound edit; private final Integer tabIndex; @@ -41,4 +43,5 @@ public void redo() throws CannotRedoException { super.redo(); edit.redo(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/MoveFieldValueAction.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/MoveFieldValueAction.java index 660c2f8454b..b75d65d9524 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/MoveFieldValueAction.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/MoveFieldValueAction.java @@ -10,8 +10,11 @@ import org.jabref.model.strings.StringUtil; public class MoveFieldValueAction extends SimpleCommand { + private final Field fromField; + private final Field toField; + private final List entries; private final NamedCompound edits; @@ -20,7 +23,8 @@ public class MoveFieldValueAction extends SimpleCommand { private final boolean overwriteToFieldContent; - public MoveFieldValueAction(Field fromField, Field toField, List entries, NamedCompound edits, boolean overwriteToFieldContent) { + public MoveFieldValueAction(Field fromField, Field toField, List entries, NamedCompound edits, + boolean overwriteToFieldContent) { this.fromField = fromField; this.toField = toField; this.entries = entries; @@ -55,9 +59,10 @@ public void execute() { /** * @return the number of affected entries - * */ + */ public int executeAndGetAffectedEntriesCount() { execute(); return affectedEntriesCount; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/NotificationPaneAdapter.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/NotificationPaneAdapter.java index d650271d9d1..8ad3297246b 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/NotificationPaneAdapter.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/NotificationPaneAdapter.java @@ -20,4 +20,5 @@ public void notify(int affectedEntries, int totalEntries) { notify(notificationGraphic, notificationMessage, List.of(), Duration.seconds(2)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabView.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabView.java index f36cee707b3..59702eba5ac 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabView.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabView.java @@ -23,8 +23,11 @@ import static org.jabref.gui.util.FieldsUtil.FIELD_STRING_CONVERTER; -public class CopyOrMoveFieldContentTabView extends AbstractAutomaticFieldEditorTabView implements AutomaticFieldEditorTab { +public class CopyOrMoveFieldContentTabView extends AbstractAutomaticFieldEditorTabView + implements AutomaticFieldEditorTab { + public Button copyContentButton; + @FXML private Button moveContentButton; @@ -33,6 +36,7 @@ public class CopyOrMoveFieldContentTabView extends AbstractAutomaticFieldEditorT @FXML private ComboBox fromFieldComboBox; + @FXML private ComboBox toFieldComboBox; @@ -40,8 +44,11 @@ public class CopyOrMoveFieldContentTabView extends AbstractAutomaticFieldEditorT private CheckBox overwriteFieldContentCheckBox; private CopyOrMoveFieldContentTabViewModel viewModel; + private final List selectedEntries; + private final BibDatabase database; + private final StateManager stateManager; private final ControlsFxVisualizer visualizer = new ControlsFxVisualizer(); @@ -51,9 +58,7 @@ public CopyOrMoveFieldContentTabView(BibDatabase database, StateManager stateMan this.database = database; this.stateManager = stateManager; - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } public void initialize() { @@ -67,7 +72,8 @@ public void initialize() { copyContentButton.disableProperty().bind(viewModel.toFieldValidationStatus().validProperty().not()); overwriteFieldContentCheckBox.disableProperty().bind(viewModel.toFieldValidationStatus().validProperty().not()); - Platform.runLater(() -> visualizer.initVisualization(viewModel.toFieldValidationStatus(), toFieldComboBox, true)); + Platform + .runLater(() -> visualizer.initVisualization(viewModel.toFieldValidationStatus(), toFieldComboBox, true)); } private void initializeFromAndToComboBox() { @@ -104,4 +110,5 @@ void moveContent() { void swapContent() { viewModel.swapValues(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabViewModel.java index 3ed3a1b38ee..e7d47770273 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabViewModel.java @@ -27,12 +27,15 @@ import de.saxsys.mvvmfx.utils.validation.Validator; public class CopyOrMoveFieldContentTabViewModel extends AbstractAutomaticFieldEditorTabViewModel { + public static final int TAB_INDEX = 1; + private final ObjectProperty fromField = new SimpleObjectProperty<>(StandardField.ABSTRACT); private final ObjectProperty toField = new SimpleObjectProperty<>(StandardField.AUTHOR); private final BooleanProperty overwriteFieldContent = new SimpleBooleanProperty(Boolean.FALSE); + private final List selectedEntries; private final Validator toFieldValidator; @@ -41,24 +44,26 @@ public class CopyOrMoveFieldContentTabViewModel extends AbstractAutomaticFieldEd private final BooleanBinding canSwap; - public CopyOrMoveFieldContentTabViewModel(List selectedEntries, BibDatabase bibDatabase, StateManager stateManager) { + public CopyOrMoveFieldContentTabViewModel(List selectedEntries, BibDatabase bibDatabase, + StateManager stateManager) { super(bibDatabase, stateManager); this.selectedEntries = new ArrayList<>(selectedEntries); toFieldValidator = new FunctionBasedValidator<>(toField, field -> { if (StringUtil.isBlank(field.getName())) { return ValidationMessage.error("Field name cannot be empty"); - } else if (StringUtil.containsWhitespace(field.getName())) { + } + else if (StringUtil.containsWhitespace(field.getName())) { return ValidationMessage.error("Field name cannot have whitespace characters"); } return null; }); canMove = BooleanBinding.booleanExpression(toFieldValidationStatus().validProperty()) - .and(overwriteFieldContentProperty()); + .and(overwriteFieldContentProperty()); canSwap = BooleanBinding.booleanExpression(toFieldValidationStatus().validProperty()) - .and(overwriteFieldContentProperty()); + .and(overwriteFieldContentProperty()); } public ValidationStatus toFieldValidationStatus() { @@ -107,10 +112,8 @@ public void copyValue() { if (overwriteFieldContent.get() || StringUtil.isBlank(toFieldValue)) { if (StringUtil.isNotBlank(fromFieldValue)) { entry.setField(toField.get(), fromFieldValue); - copyFieldValueEdit.addEdit(new UndoableFieldChange(entry, - toField.get(), - toFieldValue, - fromFieldValue)); + copyFieldValueEdit + .addEdit(new UndoableFieldChange(entry, toField.get(), toFieldValue, fromFieldValue)); affectedEntriesCount++; } } @@ -118,27 +121,23 @@ public void copyValue() { if (copyFieldValueEdit.hasEdits()) { copyFieldValueEdit.end(); } - stateManager.setLastAutomaticFieldEditorEdit(new LastAutomaticFieldEditorEdit( - affectedEntriesCount, TAB_INDEX, copyFieldValueEdit - )); + stateManager.setLastAutomaticFieldEditorEdit( + new LastAutomaticFieldEditorEdit(affectedEntriesCount, TAB_INDEX, copyFieldValueEdit)); } public void moveValue() { NamedCompound moveEdit = new NamedCompound("MOVE_EDIT"); int affectedEntriesCount = 0; if (overwriteFieldContent.get()) { - affectedEntriesCount = new MoveFieldValueAction(fromField.get(), - toField.get(), - selectedEntries, - moveEdit).executeAndGetAffectedEntriesCount(); + affectedEntriesCount = new MoveFieldValueAction(fromField.get(), toField.get(), selectedEntries, moveEdit) + .executeAndGetAffectedEntriesCount(); if (moveEdit.hasEdits()) { moveEdit.end(); } } - stateManager.setLastAutomaticFieldEditorEdit(new LastAutomaticFieldEditorEdit( - affectedEntriesCount, TAB_INDEX, moveEdit - )); + stateManager.setLastAutomaticFieldEditorEdit( + new LastAutomaticFieldEditorEdit(affectedEntriesCount, TAB_INDEX, moveEdit)); } public void swapValues() { @@ -148,23 +147,16 @@ public void swapValues() { String fromFieldValue = entry.getField(fromField.get()).orElse(""); String toFieldValue = entry.getField(toField.get()).orElse(""); - if (overwriteFieldContent.get() && StringUtil.isNotBlank(fromFieldValue) && StringUtil.isNotBlank(toFieldValue)) { + if (overwriteFieldContent.get() && StringUtil.isNotBlank(fromFieldValue) + && StringUtil.isNotBlank(toFieldValue)) { entry.setField(toField.get(), fromFieldValue); entry.setField(fromField.get(), toFieldValue); - swapFieldValuesEdit.addEdit(new UndoableFieldChange( - entry, - toField.get(), - toFieldValue, - fromFieldValue - )); - - swapFieldValuesEdit.addEdit(new UndoableFieldChange( - entry, - fromField.get(), - fromFieldValue, - toFieldValue - )); + swapFieldValuesEdit + .addEdit(new UndoableFieldChange(entry, toField.get(), toFieldValue, fromFieldValue)); + + swapFieldValuesEdit + .addEdit(new UndoableFieldChange(entry, fromField.get(), fromFieldValue, toFieldValue)); affectedEntriesCount++; } } @@ -172,12 +164,12 @@ public void swapValues() { if (swapFieldValuesEdit.hasEdits()) { swapFieldValuesEdit.end(); } - stateManager.setLastAutomaticFieldEditorEdit(new LastAutomaticFieldEditorEdit( - affectedEntriesCount, TAB_INDEX, swapFieldValuesEdit - )); + stateManager.setLastAutomaticFieldEditorEdit( + new LastAutomaticFieldEditorEdit(affectedEntriesCount, TAB_INDEX, swapFieldValuesEdit)); } public List getSelectedEntries() { return selectedEntries; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentTabView.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentTabView.java index 0c97b3f81e6..87c642709d4 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentTabView.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentTabView.java @@ -23,9 +23,13 @@ import static org.jabref.gui.util.FieldsUtil.FIELD_STRING_CONVERTER; public class EditFieldContentTabView extends AbstractAutomaticFieldEditorTabView { + public Button appendValueButton; + public Button clearFieldButton; + public Button setValueButton; + @FXML private ComboBox fieldComboBox; @@ -36,6 +40,7 @@ public class EditFieldContentTabView extends AbstractAutomaticFieldEditorTabView private CheckBox overwriteFieldContentCheckBox; private final List selectedEntries; + private final BibDatabase database; private EditFieldContentViewModel viewModel; @@ -49,9 +54,7 @@ public EditFieldContentTabView(BibDatabase database, StateManager stateManager) this.database = database; this.stateManager = stateManager; - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @FXML @@ -97,4 +100,5 @@ void clearField() { void setFieldValue() { viewModel.setFieldValue(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentViewModel.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentViewModel.java index 933e1d4f87b..56106d26ffa 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentViewModel.java @@ -30,6 +30,7 @@ import de.saxsys.mvvmfx.utils.validation.Validator; public class EditFieldContentViewModel extends AbstractAutomaticFieldEditorTabViewModel { + public static final int TAB_INDEX = 0; private final List selectedEntries; @@ -41,6 +42,7 @@ public class EditFieldContentViewModel extends AbstractAutomaticFieldEditorTabVi private final BooleanProperty overwriteFieldContent = new SimpleBooleanProperty(Boolean.FALSE); private final Validator fieldValidator; + private final BooleanBinding canAppend; public EditFieldContentViewModel(BibDatabase database, List selectedEntries, StateManager stateManager) { @@ -50,7 +52,8 @@ public EditFieldContentViewModel(BibDatabase database, List selectedEn fieldValidator = new FunctionBasedValidator<>(selectedField, field -> { if (StringUtil.isBlank(field.getName())) { return ValidationMessage.error("Field name cannot be empty"); - } else if (StringUtil.containsWhitespace(field.getName())) { + } + else if (StringUtil.containsWhitespace(field.getName())) { return ValidationMessage.error("Field name cannot have whitespace characters"); } return null; @@ -74,7 +77,7 @@ public void clearSelectedField() { Optional oldFieldValue = entry.getField(selectedField.get()); if (oldFieldValue.isPresent()) { entry.clearField(selectedField.get()) - .ifPresent(fieldChange -> clearFieldEdit.addEdit(new UndoableFieldChange(fieldChange))); + .ifPresent(fieldChange -> clearFieldEdit.addEdit(new UndoableFieldChange(fieldChange))); affectedEntriesCount++; } } @@ -82,11 +85,8 @@ public void clearSelectedField() { if (clearFieldEdit.hasEdits()) { clearFieldEdit.end(); } - stateManager.setLastAutomaticFieldEditorEdit(new LastAutomaticFieldEditorEdit( - affectedEntriesCount, - TAB_INDEX, - clearFieldEdit - )); + stateManager.setLastAutomaticFieldEditorEdit( + new LastAutomaticFieldEditorEdit(affectedEntriesCount, TAB_INDEX, clearFieldEdit)); } public void setFieldValue() { @@ -97,9 +97,10 @@ public void setFieldValue() { Optional oldFieldValue = entry.getField(selectedField.get()); if (oldFieldValue.isEmpty() || overwriteFieldContent.get()) { entry.setField(selectedField.get(), toSetFieldValue) - .ifPresent(fieldChange -> setFieldEdit.addEdit(new UndoableFieldChange(fieldChange))); + .ifPresent(fieldChange -> setFieldEdit.addEdit(new UndoableFieldChange(fieldChange))); fieldValue.set(""); - // TODO: increment affected entries only when UndoableFieldChange.isPresent() + // TODO: increment affected entries only when + // UndoableFieldChange.isPresent() affectedEntriesCount++; } } @@ -107,11 +108,8 @@ public void setFieldValue() { if (setFieldEdit.hasEdits()) { setFieldEdit.end(); } - stateManager.setLastAutomaticFieldEditorEdit(new LastAutomaticFieldEditorEdit( - affectedEntriesCount, - TAB_INDEX, - setFieldEdit - )); + stateManager.setLastAutomaticFieldEditorEdit( + new LastAutomaticFieldEditorEdit(affectedEntriesCount, TAB_INDEX, setFieldEdit)); } public void appendToFieldValue() { @@ -125,7 +123,7 @@ public void appendToFieldValue() { String newFieldValue = oldFieldValue.orElse("").concat(toAppendFieldValue); entry.setField(selectedField.get(), newFieldValue) - .ifPresent(fieldChange -> appendToFieldEdit.addEdit(new UndoableFieldChange(fieldChange))); + .ifPresent(fieldChange -> appendToFieldEdit.addEdit(new UndoableFieldChange(fieldChange))); fieldValue.set(""); affectedEntriesCount++; @@ -135,11 +133,8 @@ public void appendToFieldValue() { if (appendToFieldEdit.hasEdits()) { appendToFieldEdit.end(); } - stateManager.setLastAutomaticFieldEditorEdit(new LastAutomaticFieldEditorEdit( - affectedEntriesCount, - TAB_INDEX, - appendToFieldEdit - )); + stateManager.setLastAutomaticFieldEditorEdit( + new LastAutomaticFieldEditorEdit(affectedEntriesCount, TAB_INDEX, appendToFieldEdit)); } public ObjectProperty selectedFieldProperty() { @@ -161,4 +156,5 @@ public StringProperty fieldValueProperty() { public BooleanProperty overwriteFieldContentProperty() { return overwriteFieldContent; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTabView.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTabView.java index 18140387cbe..8b01a78ced9 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTabView.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTabView.java @@ -23,15 +23,22 @@ import static org.jabref.gui.util.FieldsUtil.FIELD_STRING_CONVERTER; public class RenameFieldTabView extends AbstractAutomaticFieldEditorTabView implements AutomaticFieldEditorTab { + @FXML private Button renameButton; + @FXML private ComboBox fieldComboBox; + @FXML private TextField newFieldNameTextField; + private final List selectedEntries; + private final BibDatabase database; + private final StateManager stateManager; + private RenameFieldViewModel viewModel; private final ControlsFxVisualizer visualizer = new ControlsFxVisualizer(); @@ -41,9 +48,7 @@ public RenameFieldTabView(BibDatabase database, StateManager stateManager) { this.database = database; this.stateManager = stateManager; - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @FXML @@ -62,7 +67,8 @@ public void initialize() { newFieldNameTextField.textProperty().bindBidirectional(viewModel.newFieldNameProperty()); - Platform.runLater(() -> visualizer.initVisualization(viewModel.fieldNameValidationStatus(), newFieldNameTextField, true)); + Platform.runLater( + () -> visualizer.initVisualization(viewModel.fieldNameValidationStatus(), newFieldNameTextField, true)); } @Override @@ -74,4 +80,5 @@ public String getTabName() { void renameField() { viewModel.renameField(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldViewModel.java b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldViewModel.java index 6f22623d466..c029e9735ab 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldViewModel.java @@ -27,9 +27,13 @@ import de.saxsys.mvvmfx.utils.validation.Validator; public class RenameFieldViewModel extends AbstractAutomaticFieldEditorTabViewModel { + public static final int TAB_INDEX = 2; + private final StringProperty newFieldName = new SimpleStringProperty(""); + private final ObjectProperty selectedField = new SimpleObjectProperty<>(StandardField.AUTHOR); + private final List selectedEntries; private final Validator fieldValidator; @@ -47,7 +51,8 @@ public RenameFieldViewModel(List selectedEntries, BibDatabase database fieldNameValidator = new FunctionBasedValidator<>(newFieldName, fieldName -> { if (StringUtil.isBlank(fieldName)) { return ValidationMessage.error("Field name cannot be empty"); - } else if (StringUtil.containsWhitespace(fieldName)) { + } + else if (StringUtil.containsWhitespace(fieldName)) { return ValidationMessage.error("Field name cannot have whitespace characters"); } return null; @@ -97,18 +102,16 @@ public void renameField() { int affectedEntriesCount = 0; if (fieldNameValidationStatus().isValid()) { affectedEntriesCount = new MoveFieldValueAction(selectedField.get(), - FieldFactory.parseField(newFieldName.get()), - selectedEntries, - renameEdit, - false).executeAndGetAffectedEntriesCount(); + FieldFactory.parseField(newFieldName.get()), selectedEntries, renameEdit, false) + .executeAndGetAffectedEntriesCount(); if (renameEdit.hasEdits()) { renameEdit.end(); } } - stateManager.setLastAutomaticFieldEditorEdit(new LastAutomaticFieldEditorEdit( - affectedEntriesCount, TAB_INDEX, renameEdit - )); + stateManager.setLastAutomaticFieldEditorEdit( + new LastAutomaticFieldEditorEdit(affectedEntriesCount, TAB_INDEX, renameEdit)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/AdaptVisibleTabs.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/AdaptVisibleTabs.java index 193350ad5dd..f69a0be54b6 100644 --- a/jabgui/src/main/java/org/jabref/gui/entryeditor/AdaptVisibleTabs.java +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/AdaptVisibleTabs.java @@ -4,8 +4,10 @@ /// /// TODO: A better solution would be use an observable list of tabs in the state manager, but in April 2025, this was too much effort. public interface AdaptVisibleTabs { + /** * Adapt the visible tabs to the current entry type. */ void adaptVisibleTabs(); + } diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/AiChatTab.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/AiChatTab.java index 07df4aa9422..40f0b8e1b45 100644 --- a/jabgui/src/main/java/org/jabref/gui/entryeditor/AiChatTab.java +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/AiChatTab.java @@ -30,24 +30,29 @@ import org.jabref.model.entry.LinkedFile; public class AiChatTab extends EntryEditorTab { + private final AiService aiService; + private final DialogService dialogService; + private final AiPreferences aiPreferences; + private final ExternalApplicationsPreferences externalApplicationsPreferences; + private final EntryEditorPreferences entryEditorPreferences; + private final StateManager stateManager; + private final TaskExecutor taskExecutor; + private final AdaptVisibleTabs adaptVisibleTabs; + private final CitationKeyPatternPreferences citationKeyPatternPreferences; private Optional previousBibEntry = Optional.empty(); - public AiChatTab(AiService aiService, - DialogService dialogService, - GuiPreferences preferences, - StateManager stateManager, - AdaptVisibleTabs adaptVisibleTabs, - TaskExecutor taskExecutor) { + public AiChatTab(AiService aiService, DialogService dialogService, GuiPreferences preferences, + StateManager stateManager, AdaptVisibleTabs adaptVisibleTabs, TaskExecutor taskExecutor) { this.aiService = aiService; this.dialogService = dialogService; @@ -74,76 +79,68 @@ public boolean shouldShow(BibEntry entry) { */ @Override protected void bindToEntry(BibEntry entry) { - previousBibEntry.ifPresent(previousBibEntry -> aiService.getChatHistoryService().closeChatHistoryForEntry(previousBibEntry)); + previousBibEntry.ifPresent( + previousBibEntry -> aiService.getChatHistoryService().closeChatHistoryForEntry(previousBibEntry)); previousBibEntry = Optional.of(entry); BibDatabaseContext bibDatabaseContext = stateManager.getActiveDatabase().orElse(new BibDatabaseContext()); if (!aiPreferences.getEnableAi()) { showPrivacyNotice(entry); - } else if (entry.getFiles().isEmpty()) { + } + else if (entry.getFiles().isEmpty()) { showErrorNoFiles(); - } else if (entry.getFiles().stream().map(LinkedFile::getLink).map(Path::of).noneMatch(FileUtil::isPDFFile)) { + } + else if (entry.getFiles().stream().map(LinkedFile::getLink).map(Path::of).noneMatch(FileUtil::isPDFFile)) { showErrorNotPdfs(); - } else if (!CitationKeyCheck.citationKeyIsPresentAndUnique(bibDatabaseContext, entry)) { + } + else if (!CitationKeyCheck.citationKeyIsPresentAndUnique(bibDatabaseContext, entry)) { tryToGenerateCitationKeyThenBind(bibDatabaseContext, entry); - } else { + } + else { showChatPanel(bibDatabaseContext, entry); } } private void showPrivacyNotice(BibEntry entry) { - setContent(new PrivacyNoticeComponent(aiPreferences, () -> bindToEntry(entry), externalApplicationsPreferences, dialogService, adaptVisibleTabs)); + setContent(new PrivacyNoticeComponent(aiPreferences, () -> bindToEntry(entry), externalApplicationsPreferences, + dialogService, adaptVisibleTabs)); } private void showErrorNotPdfs() { - setContent( - new ErrorStateComponent( - Localization.lang("Unable to chat"), - Localization.lang("Only PDF files are supported.") - ) - ); + setContent(new ErrorStateComponent(Localization.lang("Unable to chat"), + Localization.lang("Only PDF files are supported."))); } private void showErrorNoFiles() { - setContent( - new ErrorStateComponent( - Localization.lang("Unable to chat"), - Localization.lang("Please attach at least one PDF file to enable chatting with PDF file(s).") - ) - ); + setContent(new ErrorStateComponent(Localization.lang("Unable to chat"), + Localization.lang("Please attach at least one PDF file to enable chatting with PDF file(s)."))); } private void tryToGenerateCitationKeyThenBind(BibDatabaseContext bibDatabaseContext, BibEntry entry) { - CitationKeyGenerator citationKeyGenerator = new CitationKeyGenerator(bibDatabaseContext, citationKeyPatternPreferences); + CitationKeyGenerator citationKeyGenerator = new CitationKeyGenerator(bibDatabaseContext, + citationKeyPatternPreferences); if (citationKeyGenerator.generateAndSetKey(entry).isEmpty()) { - setContent( - new ErrorStateComponent( - Localization.lang("Unable to chat"), - Localization.lang("Please provide a non-empty and unique citation key for this entry.") - ) - ); - } else { + setContent(new ErrorStateComponent(Localization.lang("Unable to chat"), + Localization.lang("Please provide a non-empty and unique citation key for this entry."))); + } + else { bindToEntry(entry); } } private void showChatPanel(BibDatabaseContext bibDatabaseContext, BibEntry entry) { - // We omit the localization here, because it is only a chat with one entry in the {@link EntryEditor}. + // We omit the localization here, because it is only a chat with one entry in the + // {@link EntryEditor}. // See documentation for {@link AiChatGuardedComponent#name}. - StringProperty chatName = new SimpleStringProperty("entry " + entry.getCitationKey().orElse("")); - entry.getCiteKeyBinding().addListener((observable, oldValue, newValue) -> chatName.setValue("entry " + newValue)); - - setContent(new AiChatGuardedComponent( - chatName, - aiService.getChatHistoryService().getChatHistoryForEntry(bibDatabaseContext, entry), - bibDatabaseContext, - FXCollections.observableArrayList(new ArrayList<>(List.of(entry))), - aiService, - dialogService, - aiPreferences, - externalApplicationsPreferences, - adaptVisibleTabs, - taskExecutor - )); + StringProperty chatName = new SimpleStringProperty( + "entry " + entry.getCitationKey().orElse("")); + entry.getCiteKeyBinding() + .addListener((observable, oldValue, newValue) -> chatName.setValue("entry " + newValue)); + + setContent(new AiChatGuardedComponent(chatName, + aiService.getChatHistoryService().getChatHistoryForEntry(bibDatabaseContext, entry), bibDatabaseContext, + FXCollections.observableArrayList(new ArrayList<>(List.of(entry))), aiService, dialogService, + aiPreferences, externalApplicationsPreferences, adaptVisibleTabs, taskExecutor)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/AiSummaryTab.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/AiSummaryTab.java index c47f91fb073..156d838e960 100644 --- a/jabgui/src/main/java/org/jabref/gui/entryeditor/AiSummaryTab.java +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/AiSummaryTab.java @@ -15,20 +15,25 @@ import org.jabref.model.entry.BibEntry; public class AiSummaryTab extends EntryEditorTab { + private final AiService aiService; + private final DialogService dialogService; + private final StateManager stateManager; + private final AdaptVisibleTabs adaptVisibleTabs; + private final AiPreferences aiPreferences; + private final ExternalApplicationsPreferences externalApplicationsPreferences; + private final CitationKeyPatternPreferences citationKeyPatternPreferences; + private final EntryEditorPreferences entryEditorPreferences; - public AiSummaryTab(AiService aiService, - DialogService dialogService, - StateManager stateManager, - AdaptVisibleTabs adaptVisibleTabs, - GuiPreferences preferences) { + public AiSummaryTab(AiService aiService, DialogService dialogService, StateManager stateManager, + AdaptVisibleTabs adaptVisibleTabs, GuiPreferences preferences) { this.aiService = aiService; this.dialogService = dialogService; this.stateManager = stateManager; @@ -54,15 +59,8 @@ public boolean shouldShow(BibEntry entry) { @Override protected void bindToEntry(BibEntry entry) { BibDatabaseContext bibDatabaseContext = stateManager.getActiveDatabase().orElse(new BibDatabaseContext()); - setContent(new SummaryComponent( - bibDatabaseContext, - entry, - aiService, - aiPreferences, - externalApplicationsPreferences, - citationKeyPatternPreferences, - dialogService, - adaptVisibleTabs - )); + setContent(new SummaryComponent(bibDatabaseContext, entry, aiService, aiPreferences, + externalApplicationsPreferences, citationKeyPatternPreferences, dialogService, adaptVisibleTabs)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java index c1bbee669bc..6be750f7688 100644 --- a/jabgui/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java @@ -34,30 +34,28 @@ import org.jabref.model.entry.field.UserSpecificCommentField; public class CommentsTab extends FieldsEditorTab { + public static final String NAME = "Comments"; private final String defaultOwner; + private final UserSpecificCommentField userSpecificCommentField; + private final EntryEditorPreferences entryEditorPreferences; + private boolean isFieldCurrentlyVisible; + private boolean shouldShowHideButton; - public CommentsTab(GuiPreferences preferences, - UndoManager undoManager, - UndoAction undoAction, - RedoAction redoAction, - JournalAbbreviationRepository journalAbbreviationRepository, - StateManager stateManager, - PreviewPanel previewPanel) { - super(false, - undoManager, - undoAction, - redoAction, - preferences, - journalAbbreviationRepository, - stateManager, + public CommentsTab(GuiPreferences preferences, UndoManager undoManager, UndoAction undoAction, + RedoAction redoAction, JournalAbbreviationRepository journalAbbreviationRepository, + StateManager stateManager, PreviewPanel previewPanel) { + super(false, undoManager, undoAction, redoAction, preferences, journalAbbreviationRepository, stateManager, previewPanel); - this.defaultOwner = preferences.getOwnerPreferences().getDefaultOwner().toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]", "-"); + this.defaultOwner = preferences.getOwnerPreferences() + .getDefaultOwner() + .toLowerCase(Locale.ROOT) + .replaceAll("[^a-z0-9]", "-"); setText(Localization.lang("Comments")); setGraphic(IconTheme.JabRefIcons.COMMENT.getGraphicNode()); @@ -79,11 +77,12 @@ protected SequencedSet determineFieldsToShow(BibEntry entry) { } // Show all non-empty comment fields (otherwise, they are completely hidden) - comments.addAll(entry.getFields().stream() - .filter(field -> (field instanceof UserSpecificCommentField && !field.equals(userSpecificCommentField)) - || field.getName().toLowerCase().contains("comment")) - .sorted(Comparator.comparing(Field::getName)) - .collect(Collectors.toCollection(LinkedHashSet::new))); + comments.addAll(entry.getFields() + .stream() + .filter(field -> (field instanceof UserSpecificCommentField && !field.equals(userSpecificCommentField)) + || field.getName().toLowerCase().contains("comment")) + .sorted(Comparator.comparing(Field::getName)) + .collect(Collectors.toCollection(LinkedHashSet::new))); return comments; } @@ -119,7 +118,11 @@ private void setCompressedRowLayout() { protected void setupPanel(BibDatabaseContext bibDatabaseContext, BibEntry entry, boolean compressed) { super.setupPanel(bibDatabaseContext, entry, compressed); - Optional fieldEditorForUserDefinedComment = editors.entrySet().stream().filter(f -> f.getKey().getName().contains(defaultOwner)).map(Map.Entry::getValue).findFirst(); + Optional fieldEditorForUserDefinedComment = editors.entrySet() + .stream() + .filter(f -> f.getKey().getName().contains(defaultOwner)) + .map(Map.Entry::getValue) + .findFirst(); for (Map.Entry fieldEditorEntry : editors.entrySet()) { Field field = fieldEditorEntry.getKey(); @@ -135,11 +138,12 @@ protected void setupPanel(BibDatabaseContext bibDatabaseContext, BibEntry entry, // Show "Hide" button only if user-specific comment field is empty if (!entry.hasField(userSpecificCommentField)) { if (shouldShowHideButton) { - Button hideDefaultOwnerCommentButton = new Button(Localization.lang("Hide user-specific comments field")); + Button hideDefaultOwnerCommentButton = new Button( + Localization.lang("Hide user-specific comments field")); hideDefaultOwnerCommentButton.setOnAction(e -> { - gridPane.getChildren().removeIf(node -> - (node instanceof FieldNameLabel fieldNameLabel && fieldNameLabel.getText().equals(userSpecificCommentField.getName())) - ); + gridPane.getChildren() + .removeIf(node -> (node instanceof FieldNameLabel fieldNameLabel + && fieldNameLabel.getText().equals(userSpecificCommentField.getName()))); fieldEditorForUserDefinedComment.ifPresent(f -> gridPane.getChildren().remove(f.getNode())); editors.remove(userSpecificCommentField); entry.clearField(userSpecificCommentField); @@ -149,9 +153,11 @@ protected void setupPanel(BibDatabaseContext bibDatabaseContext, BibEntry entry, }); gridPane.add(hideDefaultOwnerCommentButton, 1, gridPane.getRowCount(), 2, 1); setCompressedRowLayout(); - } else { + } + else { // Show "Show" button when user comments field is hidden - Button showDefaultOwnerCommentButton = new Button(Localization.lang("Show user-specific comments field")); + Button showDefaultOwnerCommentButton = new Button( + Localization.lang("Show user-specific comments field")); showDefaultOwnerCommentButton.setOnAction(e -> { shouldShowHideButton = true; setupPanel(bibDatabaseContext, entry, false); @@ -162,4 +168,5 @@ protected void setupPanel(BibDatabaseContext bibDatabaseContext, BibEntry entry, } } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java index 70b1655a991..67e3dcdb0c7 100644 --- a/jabgui/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java @@ -29,33 +29,24 @@ public class DeprecatedFieldsTab extends FieldsEditorTab { public static final String NAME = "Deprecated fields"; + private final BibEntryTypesManager entryTypesManager; - public DeprecatedFieldsTab(UndoManager undoManager, - UndoAction undoAction, - RedoAction redoAction, - GuiPreferences preferences, - BibEntryTypesManager entryTypesManager, - JournalAbbreviationRepository journalAbbreviationRepository, - StateManager stateManager, - PreviewPanel previewPanel) { - super( - false, - undoManager, - undoAction, - redoAction, - preferences, - journalAbbreviationRepository, - stateManager, - previewPanel - ); + public DeprecatedFieldsTab(UndoManager undoManager, UndoAction undoAction, RedoAction redoAction, + GuiPreferences preferences, BibEntryTypesManager entryTypesManager, + JournalAbbreviationRepository journalAbbreviationRepository, StateManager stateManager, + PreviewPanel previewPanel) { + super(false, undoManager, undoAction, redoAction, preferences, journalAbbreviationRepository, stateManager, + previewPanel); this.entryTypesManager = entryTypesManager; setText(Localization.lang("Deprecated fields")); EasyBind.subscribe(preferences.getWorkspacePreferences().showAdvancedHintsProperty(), advancedHints -> { if (advancedHints) { - setTooltip(new Tooltip(Localization.lang("Shows fields having a successor in biblatex.\nFor instance, the publication month should be part of the date field.\nUse the Clean up Entries functionality to convert the entry to biblatex."))); - } else { + setTooltip(new Tooltip(Localization.lang( + "Shows fields having a successor in biblatex.\nFor instance, the publication month should be part of the date field.\nUse the Clean up Entries functionality to convert the entry to biblatex."))); + } + else { setTooltip(new Tooltip(Localization.lang("Shows fields having a successor in biblatex."))); } }); @@ -64,14 +55,22 @@ public DeprecatedFieldsTab(UndoManager undoManager, @Override protected SequencedSet determineFieldsToShow(BibEntry entry) { - BibDatabaseMode mode = stateManager.getActiveDatabase().map(BibDatabaseContext::getMode) - .orElse(BibDatabaseMode.BIBLATEX); + BibDatabaseMode mode = stateManager.getActiveDatabase() + .map(BibDatabaseContext::getMode) + .orElse(BibDatabaseMode.BIBLATEX); Optional entryType = entryTypesManager.enrich(entry.getType(), mode); if (entryType.isPresent()) { - return entryType.get().getDeprecatedFields(mode).stream().filter(field -> entry.getField(field).isPresent()).collect(Collectors.toCollection(LinkedHashSet::new)); - } else { - // Entry type unknown -> treat all fields as required (thus no optional fields) + return entryType.get() + .getDeprecatedFields(mode) + .stream() + .filter(field -> entry.getField(field).isPresent()) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + else { + // Entry type unknown -> treat all fields as required (thus no optional + // fields) return new LinkedHashSet<>(); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/DetailOptionalFieldsTab.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/DetailOptionalFieldsTab.java index 5ea115fe41e..bdab5bc9023 100644 --- a/jabgui/src/main/java/org/jabref/gui/entryeditor/DetailOptionalFieldsTab.java +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/DetailOptionalFieldsTab.java @@ -15,25 +15,12 @@ public class DetailOptionalFieldsTab extends OptionalFieldsTabBase { public static final String NAME = "Optional fields 2"; - public DetailOptionalFieldsTab(UndoManager undoManager, - UndoAction undoAction, - RedoAction redoAction, - GuiPreferences preferences, - BibEntryTypesManager entryTypesManager, - JournalAbbreviationRepository journalAbbreviationRepository, - StateManager stateManager, - PreviewPanel previewPanel) { - super( - Localization.lang("Optional fields 2"), - false, - undoManager, - undoAction, - redoAction, - preferences, - entryTypesManager, - journalAbbreviationRepository, - stateManager, - previewPanel - ); + public DetailOptionalFieldsTab(UndoManager undoManager, UndoAction undoAction, RedoAction redoAction, + GuiPreferences preferences, BibEntryTypesManager entryTypesManager, + JournalAbbreviationRepository journalAbbreviationRepository, StateManager stateManager, + PreviewPanel previewPanel) { + super(Localization.lang("Optional fields 2"), false, undoManager, undoAction, redoAction, preferences, + entryTypesManager, journalAbbreviationRepository, stateManager, previewPanel); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index 4c72ecca4f7..87a81c51c84 100644 --- a/jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -77,21 +77,28 @@ import org.jspecify.annotations.NonNull; /** - * GUI component that allows editing of the fields of a BibEntry (i.e. the one that shows up, when you double click on - * an entry in the table) + * GUI component that allows editing of the fields of a BibEntry (i.e. the one that shows + * up, when you double click on an entry in the table) *

* It hosts the tabs (required, general, optional) and the buttons to the left. *

- * EntryEditor also registers itself to the event bus, receiving events whenever a field of the entry changes, enabling - * the text fields to update themselves if the change is made from somewhere else. + * EntryEditor also registers itself to the event bus, receiving events whenever a field + * of the entry changes, enabling the text fields to update themselves if the change is + * made from somewhere else. *

- * The editors for fields are created via {@link org.jabref.gui.fieldeditors.FieldEditors}. + * The editors for fields are created via + * {@link org.jabref.gui.fieldeditors.FieldEditors}. */ public class EntryEditor extends BorderPane implements PreviewControls, AdaptVisibleTabs { + private final Supplier tabSupplier; + private final ExternalFilesEntryLinker fileLinker; + private final PreviewPanel previewPanel; + private final UndoAction undoAction; + private final RedoAction redoAction; private Subscription typeSubscription; @@ -100,26 +107,59 @@ public class EntryEditor extends BorderPane implements PreviewControls, AdaptVis private SourceTab sourceTab; - @FXML private TabPane tabbed; - - @FXML private Button typeChangeButton; - @FXML private Button fetcherButton; - @FXML private Label typeLabel; - - @Inject private BuildInfo buildInfo; - @Inject private DialogService dialogService; - @Inject private TaskExecutor taskExecutor; - @Inject private GuiPreferences preferences; - @Inject private StateManager stateManager; - @Inject private ThemeManager themeManager; - @Inject private FileUpdateMonitor fileMonitor; - @Inject private DirectoryMonitor directoryMonitor; - @Inject private CountingUndoManager undoManager; - @Inject private BibEntryTypesManager bibEntryTypesManager; - @Inject private KeyBindingRepository keyBindingRepository; - @Inject private JournalAbbreviationRepository journalAbbreviationRepository; - @Inject private AiService aiService; - @Inject private SearchCitationsRelationsService searchCitationsRelationsService; + @FXML + private TabPane tabbed; + + @FXML + private Button typeChangeButton; + + @FXML + private Button fetcherButton; + + @FXML + private Label typeLabel; + + @Inject + private BuildInfo buildInfo; + + @Inject + private DialogService dialogService; + + @Inject + private TaskExecutor taskExecutor; + + @Inject + private GuiPreferences preferences; + + @Inject + private StateManager stateManager; + + @Inject + private ThemeManager themeManager; + + @Inject + private FileUpdateMonitor fileMonitor; + + @Inject + private DirectoryMonitor directoryMonitor; + + @Inject + private CountingUndoManager undoManager; + + @Inject + private BibEntryTypesManager bibEntryTypesManager; + + @Inject + private KeyBindingRepository keyBindingRepository; + + @Inject + private JournalAbbreviationRepository journalAbbreviationRepository; + + @Inject + private AiService aiService; + + @Inject + private SearchCitationsRelationsService searchCitationsRelationsService; private final List allPossibleTabs = new ArrayList<>(); @@ -128,23 +168,13 @@ public EntryEditor(Supplier tabSupplier, UndoAction undoAction, Redo this.undoAction = undoAction; this.redoAction = redoAction; - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); - this.fileLinker = new ExternalFilesEntryLinker( - preferences.getExternalApplicationsPreferences(), - preferences.getFilePreferences(), - dialogService, - stateManager); + this.fileLinker = new ExternalFilesEntryLinker(preferences.getExternalApplicationsPreferences(), + preferences.getFilePreferences(), dialogService, stateManager); - this.previewPanel = new PreviewPanel( - dialogService, - preferences.getKeyBindingRepository(), - preferences, - themeManager, - taskExecutor, - stateManager); + this.previewPanel = new PreviewPanel(dialogService, preferences.getKeyBindingRepository(), preferences, + themeManager, taskExecutor, stateManager); setupKeyBindings(); @@ -156,7 +186,8 @@ public EntryEditor(Supplier tabSupplier, UndoAction undoAction, Redo this.allPossibleTabs.addAll(createTabs()); adaptVisibleTabs(); - } else { + } + else { this.allPossibleTabs.clear(); } }); @@ -171,29 +202,28 @@ public EntryEditor(Supplier tabSupplier, UndoAction undoAction, Redo }); stateManager.getSelectedEntries().addListener((InvalidationListener) _ -> { - if (stateManager.getSelectedEntries().isEmpty()) { - // [impl->req~entry-editor.keep-showing~1] - // No change in the entry editor - // We allow users to edit the "old" entry - } else { - setCurrentlyEditedEntry(stateManager.getSelectedEntries().getFirst()); - } + if (stateManager.getSelectedEntries().isEmpty()) { + // [impl->req~entry-editor.keep-showing~1] + // No change in the entry editor + // We allow users to edit the "old" entry + } + else { + setCurrentlyEditedEntry(stateManager.getSelectedEntries().getFirst()); + } + }); + + EasyBind.listen(preferences.getPreviewPreferences().showPreviewAsExtraTabProperty(), (_, _, newValue) -> { + if (currentlyEditedEntry != null) { + adaptVisibleTabs(); + Tab tab = tabbed.getSelectionModel().selectedItemProperty().get(); + if (newValue && tab instanceof FieldsEditorTab fieldsEditorTab) { + fieldsEditorTab.removePreviewPanelFromThisTab(); } - ); - - EasyBind.listen(preferences.getPreviewPreferences().showPreviewAsExtraTabProperty(), - (_, _, newValue) -> { - if (currentlyEditedEntry != null) { - adaptVisibleTabs(); - Tab tab = tabbed.getSelectionModel().selectedItemProperty().get(); - if (newValue && tab instanceof FieldsEditorTab fieldsEditorTab) { - fieldsEditorTab.removePreviewPanelFromThisTab(); - } - if (tab instanceof TabWithPreviewPanel previewTab) { - previewTab.handleFocus(); - } - } - }); + if (tab instanceof TabWithPreviewPanel previewTab) { + previewTab.handleFocus(); + } + } + }); } private void setupDragAndDrop() { @@ -210,9 +240,15 @@ private void setupDragAndDrop() { if (event.getDragboard().hasContent(DataFormat.FILES)) { TransferMode transferMode = event.getTransferMode(); - List files = event.getDragboard().getFiles().stream().map(File::toPath).collect(Collectors.toList()); - // Modifiers do not work on macOS: https://bugs.openjdk.org/browse/JDK-8264172 - // Similar code as org.jabref.gui.externalfiles.ImportHandler.importFilesInBackground + List files = event.getDragboard() + .getFiles() + .stream() + .map(File::toPath) + .collect(Collectors.toList()); + // Modifiers do not work on macOS: + // https://bugs.openjdk.org/browse/JDK-8264172 + // Similar code as + // org.jabref.gui.externalfiles.ImportHandler.importFilesInBackground DragDrop.handleDropOfFiles(files, transferMode, fileLinker, entry); success = true; } @@ -249,7 +285,9 @@ private void setupKeyBindings() { event.consume(); break; case HELP: - new HelpAction(HelpFile.ENTRY_EDITOR, dialogService, preferences.getExternalApplicationsPreferences()).execute(); + new HelpAction(HelpFile.ENTRY_EDITOR, dialogService, + preferences.getExternalApplicationsPreferences()) + .execute(); event.consume(); break; case CLOSE: @@ -280,14 +318,15 @@ private void deleteEntry() { @FXML private void generateCiteKeyButton() { - GenerateCitationKeySingleAction action = new GenerateCitationKeySingleAction(getCurrentlyEditedEntry(), tabSupplier.get().getBibDatabaseContext(), - dialogService, preferences, undoManager); + GenerateCitationKeySingleAction action = new GenerateCitationKeySingleAction(getCurrentlyEditedEntry(), + tabSupplier.get().getBibDatabaseContext(), dialogService, preferences, undoManager); action.execute(); } @FXML private void generateCleanupButton() { - CleanupSingleAction action = new CleanupSingleAction(getCurrentlyEditedEntry(), preferences, dialogService, stateManager, undoManager); + CleanupSingleAction action = new CleanupSingleAction(getCurrentlyEditedEntry(), preferences, dialogService, + stateManager, undoManager); action.execute(); } @@ -307,44 +346,37 @@ private List createTabs() { tabs.add(new PreviewTab(preferences, stateManager, previewPanel)); // Required, optional (important+detail), deprecated, and "other" fields - tabs.add(new RequiredFieldsTab(undoManager, undoAction, redoAction, preferences, bibEntryTypesManager, journalAbbreviationRepository, stateManager, previewPanel)); - tabs.add(new ImportantOptionalFieldsTab(undoManager, undoAction, redoAction, preferences, bibEntryTypesManager, journalAbbreviationRepository, stateManager, previewPanel)); - tabs.add(new DetailOptionalFieldsTab(undoManager, undoAction, redoAction, preferences, bibEntryTypesManager, journalAbbreviationRepository, stateManager, previewPanel)); - tabs.add(new DeprecatedFieldsTab(undoManager, undoAction, redoAction, preferences, bibEntryTypesManager, journalAbbreviationRepository, stateManager, previewPanel)); - tabs.add(new OtherFieldsTab(undoManager, undoAction, redoAction, preferences, bibEntryTypesManager, journalAbbreviationRepository, stateManager, previewPanel)); + tabs.add(new RequiredFieldsTab(undoManager, undoAction, redoAction, preferences, bibEntryTypesManager, + journalAbbreviationRepository, stateManager, previewPanel)); + tabs.add(new ImportantOptionalFieldsTab(undoManager, undoAction, redoAction, preferences, bibEntryTypesManager, + journalAbbreviationRepository, stateManager, previewPanel)); + tabs.add(new DetailOptionalFieldsTab(undoManager, undoAction, redoAction, preferences, bibEntryTypesManager, + journalAbbreviationRepository, stateManager, previewPanel)); + tabs.add(new DeprecatedFieldsTab(undoManager, undoAction, redoAction, preferences, bibEntryTypesManager, + journalAbbreviationRepository, stateManager, previewPanel)); + tabs.add(new OtherFieldsTab(undoManager, undoAction, redoAction, preferences, bibEntryTypesManager, + journalAbbreviationRepository, stateManager, previewPanel)); // Comment Tab: Tab for general and user-specific comments - tabs.add(new CommentsTab(preferences, undoManager, undoAction, redoAction, journalAbbreviationRepository, stateManager, previewPanel)); + tabs.add(new CommentsTab(preferences, undoManager, undoAction, redoAction, journalAbbreviationRepository, + stateManager, previewPanel)); // ToDo: Needs to be recreated on preferences change Map> entryEditorTabList = getAdditionalUserConfiguredTabs(); for (Map.Entry> tab : entryEditorTabList.entrySet()) { - tabs.add(new UserDefinedFieldsTab(tab.getKey(), tab.getValue(), undoManager, undoAction, redoAction, preferences, journalAbbreviationRepository, stateManager, previewPanel)); + tabs.add(new UserDefinedFieldsTab(tab.getKey(), tab.getValue(), undoManager, undoAction, redoAction, + preferences, journalAbbreviationRepository, stateManager, previewPanel)); } tabs.add(new MathSciNetTab()); tabs.add(new FileAnnotationTab(stateManager, preferences)); tabs.add(new SciteTab(preferences, taskExecutor, dialogService)); - tabs.add(new CitationRelationsTab( - dialogService, - undoManager, - stateManager, - fileMonitor, - preferences, - taskExecutor, - bibEntryTypesManager, - searchCitationsRelationsService - )); + tabs.add(new CitationRelationsTab(dialogService, undoManager, stateManager, fileMonitor, preferences, + taskExecutor, bibEntryTypesManager, searchCitationsRelationsService)); tabs.add(new RelatedArticlesTab(buildInfo, preferences, dialogService, stateManager, taskExecutor)); - sourceTab = new SourceTab( - undoManager, - preferences.getFieldPreferences(), - preferences.getImportFormatPreferences(), - fileMonitor, - dialogService, - bibEntryTypesManager, - keyBindingRepository, - stateManager); + sourceTab = new SourceTab(undoManager, preferences.getFieldPreferences(), + preferences.getImportFormatPreferences(), fileMonitor, dialogService, bibEntryTypesManager, + keyBindingRepository, stateManager); tabs.add(sourceTab); tabs.add(new LatexCitationsTab(preferences, dialogService, stateManager, directoryMonitor)); tabs.add(new FulltextSearchResultsTab(stateManager, preferences, dialogService, taskExecutor, this)); @@ -355,16 +387,17 @@ private List createTabs() { } /** - * The preferences allow to configure tabs to show (e.g.,"General", "Abstract") - * These should be shown. Already hard-coded ones (above and below this code block) should be removed. - * This method does this calculation. - * + * The preferences allow to configure tabs to show (e.g.,"General", "Abstract") These + * should be shown. Already hard-coded ones (above and below this code block) should + * be removed. This method does this calculation. * @return Map of tab names and the fields to show in them. */ private Map> getAdditionalUserConfiguredTabs() { - Map> entryEditorTabList = new HashMap<>(preferences.getEntryEditorPreferences().getEntryEditorTabs()); + Map> entryEditorTabList = new HashMap<>( + preferences.getEntryEditorPreferences().getEntryEditorTabs()); - // Same order as in org.jabref.gui.entryeditor.EntryEditor.createTabs before the call of getAdditionalUserConfiguredTabs + // Same order as in org.jabref.gui.entryeditor.EntryEditor.createTabs before the + // call of getAdditionalUserConfiguredTabs entryEditorTabList.remove(PreviewTab.NAME); entryEditorTabList.remove(RequiredFieldsTab.NAME); entryEditorTabList.remove(ImportantOptionalFieldsTab.NAME); @@ -373,13 +406,15 @@ private Map> getAdditionalUserConfiguredTabs() { entryEditorTabList.remove(OtherFieldsTab.NAME); entryEditorTabList.remove(CommentsTab.NAME); - // Same order as in org.jabref.gui.entryeditor.EntryEditor.createTabs after the call of getAdditionalUserConfiguredTabs + // Same order as in org.jabref.gui.entryeditor.EntryEditor.createTabs after the + // call of getAdditionalUserConfiguredTabs entryEditorTabList.remove(MathSciNetTab.NAME); entryEditorTabList.remove(FileAnnotationTab.NAME); entryEditorTabList.remove(SciteTab.NAME); entryEditorTabList.remove(CitationRelationsTab.NAME); entryEditorTabList.remove(RelatedArticlesTab.NAME); - // SourceTab is not listed, because it has different names for BibTeX and biblatex mode + // SourceTab is not listed, because it has different names for BibTeX and biblatex + // mode entryEditorTabList.remove(LatexCitationsTab.NAME); entryEditorTabList.remove(FulltextSearchResultsTab.NAME); @@ -388,10 +423,14 @@ private Map> getAdditionalUserConfiguredTabs() { @Override public void adaptVisibleTabs() { - // We need to find out, which tabs will be shown (and which not anymore) and remove and re-add the appropriate tabs - // to the editor. We cannot to simply remove all and re-add the complete list of visible tabs, because - // the tabs give an ugly animation the looks like all tabs are shifting in from the right. In other words: - // This hack is required since tabbed.getTabs().setAll(visibleTabs) changes the order of the tabs in the editor + // We need to find out, which tabs will be shown (and which not anymore) and + // remove and re-add the appropriate tabs + // to the editor. We cannot to simply remove all and re-add the complete list of + // visible tabs, because + // the tabs give an ugly animation the looks like all tabs are shifting in from + // the right. In other words: + // This hack is required since tabbed.getTabs().setAll(visibleTabs) changes the + // order of the tabs in the editor if (currentlyEditedEntry == null) { tabbed.getTabs().clear(); @@ -399,11 +438,15 @@ public void adaptVisibleTabs() { } // First, remove tabs that we do not want to show - List toBeRemoved = allPossibleTabs.stream().filter(tab -> !tab.shouldShow(currentlyEditedEntry)).toList(); + List toBeRemoved = allPossibleTabs.stream() + .filter(tab -> !tab.shouldShow(currentlyEditedEntry)) + .toList(); tabbed.getTabs().removeAll(toBeRemoved); // Next add all the visible tabs (if not already present) at the right position - List visibleTabs = allPossibleTabs.stream().filter(tab -> tab.shouldShow(currentlyEditedEntry)).collect(Collectors.toList()); + List visibleTabs = allPossibleTabs.stream() + .filter(tab -> tab.shouldShow(currentlyEditedEntry)) + .collect(Collectors.toList()); for (int i = 0; i < visibleTabs.size(); i++) { Tab toBeAdded = visibleTabs.get(i); Tab shown = null; @@ -436,7 +479,9 @@ public void setCurrentlyEditedEntry(@NonNull BibEntry currentlyEditedEntry) { } typeSubscription = EasyBind.subscribe(this.currentlyEditedEntry.typeProperty(), _ -> { - typeLabel.setText(new TypedBibEntry(currentlyEditedEntry, tabSupplier.get().getBibDatabaseContext().getMode()).getTypeForDisplay()); + typeLabel + .setText(new TypedBibEntry(currentlyEditedEntry, tabSupplier.get().getBibDatabaseContext().getMode()) + .getTypeForDisplay()); adaptVisibleTabs(); setupToolBar(); getSelectedTab().notifyAboutFocus(currentlyEditedEntry); @@ -453,35 +498,36 @@ private EntryEditorTab getSelectedTab() { private void setupToolBar() { // Update type label - TypedBibEntry typedEntry = new TypedBibEntry(currentlyEditedEntry, tabSupplier.get().getBibDatabaseContext().getMode()); + TypedBibEntry typedEntry = new TypedBibEntry(currentlyEditedEntry, + tabSupplier.get().getBibDatabaseContext().getMode()); typeLabel.setText(typedEntry.getTypeForDisplay()); // Add type change menu - ContextMenu typeMenu = new ChangeEntryTypeMenu(List.of(currentlyEditedEntry), tabSupplier.get().getBibDatabaseContext(), undoManager, bibEntryTypesManager).asContextMenu(); + ContextMenu typeMenu = new ChangeEntryTypeMenu(List.of(currentlyEditedEntry), + tabSupplier.get().getBibDatabaseContext(), undoManager, bibEntryTypesManager) + .asContextMenu(); typeLabel.setOnMouseClicked(event -> typeMenu.show(typeLabel, Side.RIGHT, 0, 0)); typeChangeButton.setOnMouseClicked(event -> typeMenu.show(typeChangeButton, Side.RIGHT, 0, 0)); // Add menu for fetching bibliographic information ContextMenu fetcherMenu = new ContextMenu(); SortedSet entryBasedFetchers = WebFetchers.getEntryBasedFetchers( - preferences.getImporterPreferences(), - preferences.getImportFormatPreferences(), - preferences.getFilePreferences(), - tabSupplier.get().getBibDatabaseContext()); + preferences.getImporterPreferences(), preferences.getImportFormatPreferences(), + preferences.getFilePreferences(), tabSupplier.get().getBibDatabaseContext()); for (EntryBasedFetcher fetcher : entryBasedFetchers) { MenuItem fetcherMenuItem = new MenuItem(fetcher.getName()); if (fetcher instanceof PdfMergeMetadataImporter.EntryBasedFetcherWrapper) { // Handle Grobid Opt-In in case of the PdfMergeMetadataImporter fetcherMenuItem.setOnAction(event -> { - GrobidUseDialogHelper.showAndWaitIfUserIsUndecided(dialogService, preferences.getGrobidPreferences()); - PdfMergeMetadataImporter.EntryBasedFetcherWrapper pdfMergeMetadataImporter = - new PdfMergeMetadataImporter.EntryBasedFetcherWrapper( - preferences.getImportFormatPreferences(), - preferences.getFilePreferences(), - tabSupplier.get().getBibDatabaseContext()); + GrobidUseDialogHelper.showAndWaitIfUserIsUndecided(dialogService, + preferences.getGrobidPreferences()); + PdfMergeMetadataImporter.EntryBasedFetcherWrapper pdfMergeMetadataImporter = new PdfMergeMetadataImporter.EntryBasedFetcherWrapper( + preferences.getImportFormatPreferences(), preferences.getFilePreferences(), + tabSupplier.get().getBibDatabaseContext()); fetchAndMerge(pdfMergeMetadataImporter); }); - } else { + } + else { fetcherMenuItem.setOnAction(_ -> fetchAndMerge(fetcher)); } fetcherMenu.getItems().add(fetcherMenuItem); @@ -491,20 +537,23 @@ private void setupToolBar() { } private void fetchAndMerge(EntryBasedFetcher fetcher) { - new FetchAndMergeEntry(tabSupplier.get().getBibDatabaseContext(), taskExecutor, preferences, dialogService, undoManager).fetchAndMerge(currentlyEditedEntry, fetcher); + new FetchAndMergeEntry(tabSupplier.get().getBibDatabaseContext(), taskExecutor, preferences, dialogService, + undoManager) + .fetchAndMerge(currentlyEditedEntry, fetcher); } public void setFocusToField(Field field) { UiTaskExecutor.runInJavaFXThread(() -> { - Field actualField = field; - boolean fieldFound = false; + Field actualField = field; + boolean fieldFound = false; for (Tab tab : tabbed.getTabs()) { tabbed.getSelectionModel().select(tab); if ((tab instanceof FieldsEditorTab fieldsEditorTab) && fieldsEditorTab.getShownFields().contains(actualField)) { tabbed.getSelectionModel().select(tab); Platform.runLater(() -> fieldsEditorTab.requestFocus(actualField)); - // This line explicitly brings focus back to the main window containing the Entry Editor. + // This line explicitly brings focus back to the main window + // containing the Entry Editor. getScene().getWindow().requestFocus(); fieldFound = true; break; @@ -528,4 +577,5 @@ public void nextPreviewStyle() { public void previousPreviewStyle() { this.previewPanel.previousPreviewStyle(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java index 561736fd7a1..ee84869c1ac 100644 --- a/jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java @@ -22,9 +22,9 @@ public class EntryEditorPreferences { * Specifies the different possible enablement states for online services */ public enum JournalPopupEnabled { + FIRST_START, // The first time a user uses this service - ENABLED, - DISABLED; + ENABLED, DISABLED; public static JournalPopupEnabled fromString(String status) { for (JournalPopupEnabled value : JournalPopupEnabled.values()) { @@ -34,44 +34,52 @@ public static JournalPopupEnabled fromString(String status) { } throw new IllegalArgumentException("No enum found with value: " + status); } + } private final MapProperty> entryEditorTabList; + private final MapProperty> defaultEntryEditorTabList; + private final BooleanProperty shouldOpenOnNewEntry; + private final BooleanProperty shouldShowRecommendationsTab; + private final BooleanProperty shouldShowAiSummaryTab; + private final BooleanProperty shouldShowAiChatTab; + private final BooleanProperty shouldShowLatexCitationsTab; + private final BooleanProperty shouldShowFileAnnotationsTab; + private final BooleanProperty showSourceTabByDefault; + private final BooleanProperty enableValidation; + private final BooleanProperty allowIntegerEditionBibtex; + private final BooleanProperty autoLinkFiles; + private final ObjectProperty enablementStatus; + private final BooleanProperty shouldShowSciteTab; + private final BooleanProperty showUserCommentsFields; + private final DoubleProperty previewWidthDividerPosition; public EntryEditorPreferences(Map> entryEditorTabList, - Map> defaultEntryEditorTabList, - boolean shouldOpenOnNewEntry, - boolean shouldShowRecommendationsTab, - boolean shouldShowAiSummaryTab, - boolean shouldShowAiChatTab, - boolean shouldShowLatexCitationsTab, - boolean shouldShowFileAnnotationsTab, - boolean showSourceTabByDefault, - boolean enableValidation, - boolean allowIntegerEditionBibtex, - boolean autolinkFilesEnabled, - JournalPopupEnabled journalPopupEnabled, - boolean showSciteTab, - boolean showUserCommentsFields, - double previewWidthDividerPosition) { + Map> defaultEntryEditorTabList, boolean shouldOpenOnNewEntry, + boolean shouldShowRecommendationsTab, boolean shouldShowAiSummaryTab, boolean shouldShowAiChatTab, + boolean shouldShowLatexCitationsTab, boolean shouldShowFileAnnotationsTab, boolean showSourceTabByDefault, + boolean enableValidation, boolean allowIntegerEditionBibtex, boolean autolinkFilesEnabled, + JournalPopupEnabled journalPopupEnabled, boolean showSciteTab, boolean showUserCommentsFields, + double previewWidthDividerPosition) { this.entryEditorTabList = new SimpleMapProperty<>(FXCollections.observableMap(entryEditorTabList)); - this.defaultEntryEditorTabList = new SimpleMapProperty<>(FXCollections.observableMap(defaultEntryEditorTabList)); + this.defaultEntryEditorTabList = new SimpleMapProperty<>( + FXCollections.observableMap(defaultEntryEditorTabList)); this.shouldOpenOnNewEntry = new SimpleBooleanProperty(shouldOpenOnNewEntry); this.shouldShowRecommendationsTab = new SimpleBooleanProperty(shouldShowRecommendationsTab); this.shouldShowAiSummaryTab = new SimpleBooleanProperty(shouldShowAiSummaryTab); @@ -274,4 +282,5 @@ public DoubleProperty previewWidthDividerPositionProperty() { public Double getPreviewWidthDividerPosition() { return previewWidthDividerPosition.get(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditorTab.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditorTab.java index 61a89bd055b..5f9e3af2932 100644 --- a/jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditorTab.java +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/EntryEditorTab.java @@ -15,7 +15,8 @@ public abstract class EntryEditorTab extends Tab { protected BibEntry currentEntry; /** - * Needed to track for which type of entry this tab was build and to rebuild it if the type changes + * Needed to track for which type of entry this tab was build and to rebuild it if the + * type changes */ private EntryType currentEntryType; @@ -30,8 +31,8 @@ public abstract class EntryEditorTab extends Tab { protected abstract void bindToEntry(BibEntry entry); /** - * The tab just got the focus. Override this method if you want to perform a special action on focus (like selecting - * the first field in the editor) + * The tab just got the focus. Override this method if you want to perform a special + * action on focus (like selecting the first field in the editor) */ protected void handleFocus() { // Do nothing by default @@ -52,4 +53,5 @@ public void notifyAboutFocus(BibEntry entry) { } handleFocus(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java index 7bf9fe8f8ff..2651c38f23c 100644 --- a/jabgui/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java @@ -52,12 +52,19 @@ abstract class FieldsEditorTab extends TabWithPreviewPanel { private static final Logger LOGGER = LoggerFactory.getLogger(FieldsEditorTab.class); protected final Map editors = new LinkedHashMap<>(); + protected GridPane gridPane; + private final boolean isCompressed; + private final UndoAction undoAction; + private final RedoAction redoAction; + private final GuiPreferences preferences; + private final JournalAbbreviationRepository journalAbbreviationRepository; + private final UndoManager undoManager; private Collection fields = new ArrayList<>(); @@ -65,14 +72,9 @@ abstract class FieldsEditorTab extends TabWithPreviewPanel { @SuppressWarnings("FieldCanBeLocal") private Subscription dividerPositionSubscription; - public FieldsEditorTab(boolean compressed, - UndoManager undoManager, - UndoAction undoAction, - RedoAction redoAction, - GuiPreferences preferences, - JournalAbbreviationRepository journalAbbreviationRepository, - StateManager stateManager, - PreviewPanel previewPanel) { + public FieldsEditorTab(boolean compressed, UndoManager undoManager, UndoAction undoAction, RedoAction redoAction, + GuiPreferences preferences, JournalAbbreviationRepository journalAbbreviationRepository, + StateManager stateManager, PreviewPanel previewPanel) { super(stateManager, previewPanel); this.isCompressed = compressed; this.undoManager = Objects.requireNonNull(undoManager); @@ -104,10 +106,9 @@ protected void setupPanel(BibDatabaseContext bibDatabaseContext, BibEntry entry, fields = determineFieldsToShow(entry); - List

* Therefore, some special handling is needed */ - private void importCitedBy(List entries, BibEntry existingEntry, ImportHandler importHandler, CitationKeyGenerator generator, boolean generateNewKeyOnImport) { + private void importCitedBy(List entries, BibEntry existingEntry, ImportHandler importHandler, + CitationKeyGenerator generator, boolean generateNewKeyOnImport) { if (existingEntry.getCitationKey().isEmpty()) { if (!generateNewKeyOnImport) { dialogService.notify(Localization.lang("No citation key for %0", existingEntry.getAuthorTitleYear())); @@ -99,4 +106,5 @@ private void importCitedBy(List entries, BibEntry existingEntry, Impor importHandler.importEntries(entries); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.java index 462618171ae..4e3998690b3 100644 --- a/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.java +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.java @@ -19,10 +19,10 @@ public class FileAnnotationTab extends EntryEditorTab { public static final String NAME = "File annotations"; private final StateManager stateManager; + private final EntryEditorPreferences entryEditorPreferences; - public FileAnnotationTab(StateManager stateManager, - GuiPreferences preferences) { + public FileAnnotationTab(StateManager stateManager, GuiPreferences preferences) { this.stateManager = stateManager; this.entryEditorPreferences = preferences.getEntryEditorPreferences(); @@ -36,26 +36,26 @@ public boolean shouldShow(BibEntry entry) { return entry.getField(StandardField.FILE).isPresent(); } - return entry.getField(StandardField.FILE).isPresent() - && stateManager.activeTabProperty().get() - .map(tab -> tab.getAnnotationCache() - .getFromCache(entry) - .values() - .stream() - .anyMatch(list -> !list.isEmpty())) - .orElse(false); + return entry.getField(StandardField.FILE).isPresent() && stateManager.activeTabProperty() + .get() + .map(tab -> tab.getAnnotationCache() + .getFromCache(entry) + .values() + .stream() + .anyMatch(list -> !list.isEmpty())) + .orElse(false); } @Override protected void bindToEntry(BibEntry entry) { if (stateManager.activeTabProperty().get().isPresent()) { FileAnnotationCache cache = stateManager.activeTabProperty().get().get().getAnnotationCache(); - Parent content = ViewLoader.view(new FileAnnotationTabView(entry, cache)) - .load() - .getView(); + Parent content = ViewLoader.view(new FileAnnotationTabView(entry, cache)).load().getView(); setContent(content); - } else { + } + else { setContent(null); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTabView.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTabView.java index 5316c42eaed..2d8b47d03d1 100644 --- a/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTabView.java +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTabView.java @@ -31,20 +31,41 @@ public class FileAnnotationTabView { - @FXML public ComboBox files; - @FXML public ListView annotationList; - @FXML public Label author; - @FXML public Label page; - @FXML public Label date; - @FXML public TextArea content; - @FXML public TextArea marking; - @FXML public VBox details; + @FXML + public ComboBox files; + + @FXML + public ListView annotationList; + + @FXML + public Label author; + + @FXML + public Label page; + + @FXML + public Label date; + + @FXML + public TextArea content; + + @FXML + public TextArea marking; + + @FXML + public VBox details; + private final BibEntry entry; + private final FileAnnotationCache fileAnnotationCache; + private FileAnnotationTabViewModel viewModel; - @Inject private FileUpdateMonitor fileMonitor; - @Inject private ClipBoardManager clipBoardManager; + @Inject + private FileUpdateMonitor fileMonitor; + + @Inject + private ClipBoardManager clipBoardManager; public FileAnnotationTabView(BibEntry entry, FileAnnotationCache fileAnnotationCache) { this.entry = entry; @@ -57,24 +78,34 @@ public void initialize() { // Set-up files list files.getItems().setAll(viewModel.filesProperty().get()); - files.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> viewModel.notifyNewSelectedFile(newValue)); + files.getSelectionModel() + .selectedItemProperty() + .addListener((observable, oldValue, newValue) -> viewModel.notifyNewSelectedFile(newValue)); files.getSelectionModel().selectFirst(); // Set-up annotation list annotationList.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); - annotationList.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> viewModel.notifyNewSelectedAnnotation(newValue)); + annotationList.getSelectionModel() + .selectedItemProperty() + .addListener((observable, oldValue, newValue) -> viewModel.notifyNewSelectedAnnotation(newValue)); ViewModelListCellFactory cellFactory = new ViewModelListCellFactory() - .withGraphic(this::createFileAnnotationNode); + .withGraphic(this::createFileAnnotationNode); annotationList.setCellFactory(cellFactory); annotationList.setPlaceholder(new Label(Localization.lang("File has no attached annotations"))); Bindings.bindContent(annotationList.itemsProperty().get(), viewModel.annotationsProperty()); annotationList.getSelectionModel().selectFirst(); - annotationList.itemsProperty().get().addListener( - (ListChangeListener) c -> annotationList.getSelectionModel().selectFirst()); + annotationList.itemsProperty() + .get() + .addListener((ListChangeListener) c -> annotationList.getSelectionModel() + .selectFirst()); // Set-up details pane - content.textProperty().bind(EasyBind.select(viewModel.currentAnnotationProperty()).selectObject(FileAnnotationViewModel::contentProperty)); - marking.textProperty().bind(EasyBind.select(viewModel.currentAnnotationProperty()).selectObject(FileAnnotationViewModel::markingProperty)); + content.textProperty() + .bind(EasyBind.select(viewModel.currentAnnotationProperty()) + .selectObject(FileAnnotationViewModel::contentProperty)); + marking.textProperty() + .bind(EasyBind.select(viewModel.currentAnnotationProperty()) + .selectObject(FileAnnotationViewModel::markingProperty)); details.disableProperty().bind(viewModel.isAnnotationsEmpty()); } @@ -125,4 +156,5 @@ private Node createFileAnnotationNode(FileAnnotationViewModel annotation) { public void copy() { viewModel.copyCurrentAnnotation(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTabViewModel.java index 2d98719ac2e..1ff960a5c89 100644 --- a/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTabViewModel.java @@ -31,25 +31,34 @@ import org.slf4j.LoggerFactory; public class FileAnnotationTabViewModel extends AbstractViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(FileAnnotationTabViewModel.class); - private final ListProperty annotations = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty annotations = new SimpleListProperty<>( + FXCollections.observableArrayList()); + private final ListProperty files = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ObjectProperty currentAnnotation = new SimpleObjectProperty<>(); + private final ReadOnlyBooleanProperty annotationEmpty = annotations.emptyProperty(); private final FileAnnotationCache cache; + private final BibEntry entry; + private Map> fileAnnotations; + private Path currentFile; + private final FileUpdateMonitor fileMonitor; + private final ClipBoardManager clipBoardManager; + private final FileUpdateListener fileListener = this::reloadAnnotations; - public FileAnnotationTabViewModel(FileAnnotationCache cache, - BibEntry entry, - FileUpdateMonitor fileMonitor, - ClipBoardManager clipBoardManager) { + public FileAnnotationTabViewModel(FileAnnotationCache cache, BibEntry entry, FileUpdateMonitor fileMonitor, + ClipBoardManager clipBoardManager) { this.cache = cache; this.entry = entry; this.fileMonitor = fileMonitor; @@ -85,31 +94,33 @@ public void notifyNewSelectedFile(Path newFile) { Comparator byPage = Comparator.comparingInt(FileAnnotation::getPage); - List newAnnotations = fileAnnotations - .getOrDefault(currentFile, new ArrayList<>()) - .stream() - .filter(annotation -> (null != annotation.getContent())) - .sorted(byPage) - .map(FileAnnotationViewModel::new) - .collect(Collectors.toList()); + List newAnnotations = fileAnnotations.getOrDefault(currentFile, new ArrayList<>()) + .stream() + .filter(annotation -> (null != annotation.getContent())) + .sorted(byPage) + .map(FileAnnotationViewModel::new) + .collect(Collectors.toList()); annotations.setAll(newAnnotations); try { fileMonitor.addListenerForFile(currentFile, fileListener); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Problem while watching file for changes {}", currentFile, e); } } private void reloadAnnotations() { - // Make sure to always run this in the JavaFX thread as the file monitor (and its notifications) live in a different thread + // Make sure to always run this in the JavaFX thread as the file monitor (and its + // notifications) live in a different thread UiTaskExecutor.runInJavaFXThread(() -> { // Remove annotations for the current entry and reinitialize annotation/cache cache.remove(entry); fileAnnotations = cache.getFromCache(entry); files.setAll(fileAnnotations.keySet()); - // Pretend that we just switched to the current file in order to refresh the display + // Pretend that we just switched to the current file in order to refresh the + // display notifyNewSelectedFile(currentFile); }); } @@ -134,4 +145,5 @@ public void copyCurrentAnnotation() { private FileAnnotationViewModel getCurrentAnnotation() { return currentAnnotation.get(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationViewModel.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationViewModel.java index 92ca042e062..30e660e238a 100644 --- a/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationViewModel.java @@ -11,11 +11,17 @@ public class FileAnnotationViewModel { private static final String NEWLINE = "%n".formatted(); + private final FileAnnotation annotation; + private StringProperty author = new SimpleStringProperty(); + private StringProperty page = new SimpleStringProperty(); + private StringProperty date = new SimpleStringProperty(); + private StringProperty content = new SimpleStringProperty(); + private StringProperty marking = new SimpleStringProperty(); public FileAnnotationViewModel(FileAnnotation annotation) { @@ -33,7 +39,8 @@ private void setupContentProperties(FileAnnotation annotation) { String illegibleTextMessage = Localization.lang("The marked area does not contain any legible text!"); String markingContent = annotationContent.isEmpty() ? illegibleTextMessage : annotationContent; this.marking.set(removePunctuationMark(markingContent)); - } else { + } + else { String content = annotation.getContent(); this.content.set(removePunctuationMark(content)); this.marking.set(""); @@ -110,4 +117,5 @@ public String toString() { public String getMarking() { return marking.get(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java b/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java index ded229a79bd..40c95ded0a0 100644 --- a/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java +++ b/jabgui/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java @@ -44,24 +44,29 @@ public class FulltextSearchResultsTab extends EntryEditorTab { public static final String NAME = "Search results"; + private static final Logger LOGGER = LoggerFactory.getLogger(FulltextSearchResultsTab.class); private final StateManager stateManager; + private final GuiPreferences preferences; + private final DialogService dialogService; + private final ActionFactory actionFactory; + private final TaskExecutor taskExecutor; + private final EntryEditor entryEditor; + private final TextFlow content; private BibEntry entry; + private DocumentViewerView documentViewerView; - public FulltextSearchResultsTab(StateManager stateManager, - GuiPreferences preferences, - DialogService dialogService, - TaskExecutor taskExecutor, - EntryEditor entryEditor) { + public FulltextSearchResultsTab(StateManager stateManager, GuiPreferences preferences, DialogService dialogService, + TaskExecutor taskExecutor, EntryEditor entryEditor) { this.stateManager = stateManager; this.preferences = preferences; this.dialogService = dialogService; @@ -82,9 +87,10 @@ public FulltextSearchResultsTab(StateManager stateManager, @Override public boolean shouldShow(BibEntry entry) { - return stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH).get() - .map(query -> query.isValid() && query.getSearchFlags().contains(SearchFlags.FULLTEXT)) - .orElse(false); + return stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH) + .get() + .map(query -> query.isValid() && query.getSearchFlags().contains(SearchFlags.FULLTEXT)) + .orElse(false); } @Override @@ -100,33 +106,51 @@ private void updateSearch() { stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH).get().ifPresent(searchQuery -> { SearchResults searchResults = searchQuery.getSearchResults(); if (searchResults != null && entry != null) { - Map> searchResultsForEntry = searchResults.getFileSearchResultsForEntry(entry); + Map> searchResultsForEntry = searchResults + .getFileSearchResultsForEntry(entry); content.getChildren().clear(); if (searchResultsForEntry.isEmpty()) { content.getChildren().add(new Text(Localization.lang("No search matches."))); - } else { + } + else { // Iterate through files with search hits for (Map.Entry> iterator : searchResultsForEntry.entrySet()) { - entry.getFiles().stream().filter(file -> file.getLink().equals(iterator.getKey())).findFirst().ifPresent(linkedFile -> { - content.getChildren().addAll(createFileLink(linkedFile), lineSeparator()); - // Iterate through pages (within file) with search hits - for (SearchResult searchResult : iterator.getValue()) { - for (String resultTextHtml : searchResult.getContentResultStringsHtml()) { - content.getChildren().addAll(TooltipTextUtil.createTextsFromHtml(resultTextHtml.replace(" ", " "))); - content.getChildren().addAll(new Text(System.lineSeparator()), lineSeparator(0.8), createPageLink(linkedFile, searchResult.getPageNumber(), searchQuery.getSearchExpression())); - } - if (!searchResult.getAnnotationsResultStringsHtml().isEmpty()) { - Text annotationsText = new Text(System.lineSeparator() + Localization.lang("Found matches in annotations:") + System.lineSeparator() + System.lineSeparator()); - annotationsText.setStyle("-fx-font-style: italic;"); - content.getChildren().add(annotationsText); - - for (String resultTextHtml : searchResult.getAnnotationsResultStringsHtml()) { - content.getChildren().addAll(TooltipTextUtil.createTextsFromHtml(resultTextHtml.replace(" ", " "))); - content.getChildren().addAll(new Text(System.lineSeparator()), lineSeparator(0.8), createPageLink(linkedFile, searchResult.getPageNumber(), searchQuery.getSearchExpression())); + entry.getFiles() + .stream() + .filter(file -> file.getLink().equals(iterator.getKey())) + .findFirst() + .ifPresent(linkedFile -> { + content.getChildren().addAll(createFileLink(linkedFile), lineSeparator()); + // Iterate through pages (within file) with search hits + for (SearchResult searchResult : iterator.getValue()) { + for (String resultTextHtml : searchResult.getContentResultStringsHtml()) { + content.getChildren() + .addAll(TooltipTextUtil + .createTextsFromHtml(resultTextHtml.replace(" ", " "))); + content.getChildren() + .addAll(new Text(System.lineSeparator()), lineSeparator(0.8), + createPageLink(linkedFile, searchResult.getPageNumber(), + searchQuery.getSearchExpression())); + } + if (!searchResult.getAnnotationsResultStringsHtml().isEmpty()) { + Text annotationsText = new Text(System.lineSeparator() + + Localization.lang("Found matches in annotations:") + + System.lineSeparator() + System.lineSeparator()); + annotationsText.setStyle("-fx-font-style: italic;"); + content.getChildren().add(annotationsText); + + for (String resultTextHtml : searchResult.getAnnotationsResultStringsHtml()) { + content.getChildren() + .addAll(TooltipTextUtil + .createTextsFromHtml(resultTextHtml.replace(" ", " "))); + content.getChildren() + .addAll(new Text(System.lineSeparator()), lineSeparator(0.8), + createPageLink(linkedFile, searchResult.getPageNumber(), + searchQuery.getSearchExpression())); + } } } - } - }); + }); } } } @@ -135,22 +159,26 @@ private void updateSearch() { } private Text createFileLink(LinkedFile linkedFile) { - Text fileLinkText = new Text(Localization.lang("Found match in %0", linkedFile.getLink()) + System.lineSeparator() + System.lineSeparator()); + Text fileLinkText = new Text(Localization.lang("Found match in %0", linkedFile.getLink()) + + System.lineSeparator() + System.lineSeparator()); fileLinkText.setStyle("-fx-font-weight: bold;"); ContextMenu fileContextMenu = getFileContextMenu(linkedFile); BibDatabaseContext databaseContext = stateManager.getActiveDatabase().orElse(new BibDatabaseContext()); - Path resolvedPath = linkedFile.findIn(databaseContext, preferences.getFilePreferences()).orElse(Path.of(linkedFile.getLink())); + Path resolvedPath = linkedFile.findIn(databaseContext, preferences.getFilePreferences()) + .orElse(Path.of(linkedFile.getLink())); Tooltip fileLinkTooltip = new Tooltip(resolvedPath.toAbsolutePath().toString()); Tooltip.install(fileLinkText, fileLinkTooltip); fileLinkText.setOnMouseClicked(event -> { if (MouseButton.PRIMARY == event.getButton()) { try { NativeDesktop.openBrowser(resolvedPath.toUri(), preferences.getExternalApplicationsPreferences()); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Cannot open {}.", resolvedPath, e); } - } else { + } + else { fileContextMenu.show(fileLinkText, event.getScreenX(), event.getScreenY()); } }); @@ -158,7 +186,8 @@ private Text createFileLink(LinkedFile linkedFile) { } private Text createPageLink(LinkedFile linkedFile, int pageNumber, String searchExpression) { - Text pageLink = new Text(Localization.lang("On page %0", pageNumber) + System.lineSeparator() + System.lineSeparator()); + Text pageLink = new Text( + Localization.lang("On page %0", pageNumber) + System.lineSeparator() + System.lineSeparator()); pageLink.setStyle("-fx-font-style: italic; -fx-font-weight: bold;"); pageLink.setOnMouseClicked(event -> { @@ -178,10 +207,12 @@ private Text createPageLink(LinkedFile linkedFile, int pageNumber, String search private ContextMenu getFileContextMenu(LinkedFile file) { ContextMenu fileContextMenu = new ContextMenu(); - fileContextMenu.getItems().add(actionFactory.createMenuItem( - StandardActions.OPEN_FOLDER, new OpenFolderAction(dialogService, stateManager, preferences, entry, file, taskExecutor))); - fileContextMenu.getItems().add(actionFactory.createMenuItem( - StandardActions.OPEN_EXTERNAL_FILE, new OpenSingleExternalFileAction(dialogService, preferences, entry, file, taskExecutor, stateManager))); + fileContextMenu.getItems() + .add(actionFactory.createMenuItem(StandardActions.OPEN_FOLDER, + new OpenFolderAction(dialogService, stateManager, preferences, entry, file, taskExecutor))); + fileContextMenu.getItems() + .add(actionFactory.createMenuItem(StandardActions.OPEN_EXTERNAL_FILE, new OpenSingleExternalFileAction( + dialogService, preferences, entry, file, taskExecutor, stateManager))); return fileContextMenu; } @@ -195,4 +226,5 @@ private Separator lineSeparator(double widthMultiplier) { lineSeparator.setPrefHeight(15); return lineSeparator; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/errorconsole/ErrorConsoleView.java b/jabgui/src/main/java/org/jabref/gui/errorconsole/ErrorConsoleView.java index 30a276468b2..38a07c58e93 100644 --- a/jabgui/src/main/java/org/jabref/gui/errorconsole/ErrorConsoleView.java +++ b/jabgui/src/main/java/org/jabref/gui/errorconsole/ErrorConsoleView.java @@ -37,26 +37,44 @@ public class ErrorConsoleView extends BaseDialog { private ErrorConsoleViewModel viewModel; - @FXML private ButtonType copyLogButton; - @FXML private ButtonType clearLogButton; - @FXML private ButtonType createIssueButton; - @FXML private ListView messagesListView; - @FXML private Label descriptionLabel; - - @Inject private DialogService dialogService; - @Inject private GuiPreferences preferences; - @Inject private ClipBoardManager clipBoardManager; - @Inject private BuildInfo buildInfo; - @Inject private KeyBindingRepository keyBindingRepository; - @Inject private ThemeManager themeManager; + @FXML + private ButtonType copyLogButton; + + @FXML + private ButtonType clearLogButton; + + @FXML + private ButtonType createIssueButton; + + @FXML + private ListView messagesListView; + + @FXML + private Label descriptionLabel; + + @Inject + private DialogService dialogService; + + @Inject + private GuiPreferences preferences; + + @Inject + private ClipBoardManager clipBoardManager; + + @Inject + private BuildInfo buildInfo; + + @Inject + private KeyBindingRepository keyBindingRepository; + + @Inject + private ThemeManager themeManager; public ErrorConsoleView() { this.setTitle(Localization.lang("Event log")); this.initModality(Modality.NONE); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); ControlHelper.setAction(copyLogButton, getDialogPane(), event -> copyLog()); ControlHelper.setAction(clearLogButton, getDialogPane(), event -> clearLog()); @@ -84,9 +102,13 @@ private void initialize() { private Callback, ListCell> createCellFactory() { return cell -> new ListCell<>() { private HBox graphic; + private Node icon; + private VBox message; + private Label heading; + private Label stacktrace; { @@ -104,7 +126,8 @@ public void updateItem(LogEventViewModel event, boolean empty) { if ((event == null) || empty) { setGraphic(null); - } else { + } + else { icon = event.getIcon().getGraphicNode(); heading.setText(event.getDisplayText()); heading.getStyleClass().setAll(event.getStyleClass()); @@ -148,4 +171,5 @@ private void clearLog() { private void createIssue() { viewModel.reportIssue(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/errorconsole/ErrorConsoleViewModel.java b/jabgui/src/main/java/org/jabref/gui/errorconsole/ErrorConsoleViewModel.java index 25f4225d2eb..be051c9e2b8 100644 --- a/jabgui/src/main/java/org/jabref/gui/errorconsole/ErrorConsoleViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/errorconsole/ErrorConsoleViewModel.java @@ -31,17 +31,23 @@ public class ErrorConsoleViewModel extends AbstractViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(ErrorConsoleViewModel.class); private final DialogService dialogService; + private final GuiPreferences preferences; + private final ClipBoardManager clipBoardManager; + private final BuildInfo buildInfo; + private final ListProperty allMessagesData; - public ErrorConsoleViewModel(DialogService dialogService, GuiPreferences preferences, ClipBoardManager clipBoardManager, BuildInfo buildInfo) { + public ErrorConsoleViewModel(DialogService dialogService, GuiPreferences preferences, + ClipBoardManager clipBoardManager, BuildInfo buildInfo) { this.dialogService = Objects.requireNonNull(dialogService); this.preferences = Objects.requireNonNull(preferences); this.clipBoardManager = Objects.requireNonNull(clipBoardManager); this.buildInfo = Objects.requireNonNull(buildInfo); - ObservableList eventViewModels = EasyBind.map(BindingsHelper.forUI(LogMessages.getInstance().getMessages()), LogEventViewModel::new); + ObservableList eventViewModels = EasyBind + .map(BindingsHelper.forUI(LogMessages.getInstance().getMessages()), LogEventViewModel::new); allMessagesData = new ReadOnlyListWrapper<>(eventViewModels); } @@ -50,14 +56,12 @@ public ListProperty allMessagesDataProperty() { } /** - * Concatenates the formatted message of the given {@link LogEventViewModel}s by using a new line separator. - * + * Concatenates the formatted message of the given {@link LogEventViewModel}s by using + * a new line separator. * @return all messages as String */ private String getLogMessagesAsString(List messages) { - return messages.stream() - .map(LogEventViewModel::getDetailedText) - .collect(Collectors.joining(OS.NEWLINE)); + return messages.stream().map(LogEventViewModel::getDetailedText).collect(Collectors.joining(OS.NEWLINE)); } /** @@ -111,18 +115,21 @@ public void reportIssue() { dialogService.notify(Localization.lang("Issue on GitHub successfully reported.")); dialogService.showInformationDialogAndWait(Localization.lang("Issue report successful"), - Localization.lang("Your issue was reported in your browser.") + "\n" + - Localization.lang("The log and exception information was copied to your clipboard.") + " " + - Localization.lang("Please paste this information (with Ctrl+V) in the issue description.") + "\n" + - Localization.lang("Please also add all steps to reproduce this issue, if possible.")); - - URIBuilder uriBuilder = new URIBuilder() - .setScheme("https").setHost("github.com") - .setPath("/JabRef/jabref/issues/new") - .setParameter("body", issueBody); + Localization.lang("Your issue was reported in your browser.") + "\n" + + Localization.lang("The log and exception information was copied to your clipboard.") + " " + + Localization.lang("Please paste this information (with Ctrl+V) in the issue description.") + + "\n" + + Localization.lang("Please also add all steps to reproduce this issue, if possible.")); + + URIBuilder uriBuilder = new URIBuilder().setScheme("https") + .setHost("github.com") + .setPath("/JabRef/jabref/issues/new") + .setParameter("body", issueBody); NativeDesktop.openBrowser(uriBuilder.build().toString(), preferences.getExternalApplicationsPreferences()); - } catch (IOException | URISyntaxException e) { + } + catch (IOException | URISyntaxException e) { LOGGER.error("Problem opening url", e); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/errorconsole/LogEventViewModel.java b/jabgui/src/main/java/org/jabref/gui/errorconsole/LogEventViewModel.java index 47ba7ac56ee..f141d46c38d 100644 --- a/jabgui/src/main/java/org/jabref/gui/errorconsole/LogEventViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/errorconsole/LogEventViewModel.java @@ -45,4 +45,5 @@ public Optional getStackTrace() { public String getDetailedText() { return getDisplayText() + getStackTrace().map(stacktrace -> OS.NEWLINE + stacktrace).orElse(""); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogView.java b/jabgui/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogView.java index dc7f27da2c3..5fc4bab5fc3 100644 --- a/jabgui/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogView.java +++ b/jabgui/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogView.java @@ -16,26 +16,38 @@ public class CreateModifyExporterDialogView extends BaseDialog { private final ExporterViewModel exporter; - @FXML private TextField name; - @FXML private TextField fileName; - @FXML private TextField extension; - @FXML private ButtonType saveExporter; - @Inject private DialogService dialogService; - @Inject private CliPreferences preferences; + + @FXML + private TextField name; + + @FXML + private TextField fileName; + + @FXML + private TextField extension; + + @FXML + private ButtonType saveExporter; + + @Inject + private DialogService dialogService; + + @Inject + private CliPreferences preferences; + private CreateModifyExporterDialogViewModel viewModel; public CreateModifyExporterDialogView(ExporterViewModel exporter) { this.setTitle(Localization.lang("Customize Export Formats")); this.exporter = exporter; - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); this.setResultConverter(button -> { if (button == saveExporter) { return viewModel.saveExporter(); - } else { + } + else { return null; } }); @@ -53,4 +65,5 @@ private void initialize() { private void browse(ActionEvent event) { viewModel.browse(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogViewModel.java index 57613bd8af7..6671fd9be31 100644 --- a/jabgui/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogViewModel.java @@ -17,10 +17,11 @@ import org.slf4j.LoggerFactory; /** - * This view model can be used both for "add exporter" and "modify exporter" functionalities. - * It takes an optional exporter which is empty for "add exporter," and takes the selected exporter - * for "modify exporter." It returns an optional exporter which empty if an invalid or no exporter is - * created, and otherwise contains the exporter to be added or that is modified. + * This view model can be used both for "add exporter" and "modify exporter" + * functionalities. It takes an optional exporter which is empty for "add exporter," and + * takes the selected exporter for "modify exporter." It returns an optional exporter + * which empty if an invalid or no exporter is created, and otherwise contains the + * exporter to be added or that is modified. */ public class CreateModifyExporterDialogViewModel extends AbstractViewModel { @@ -28,15 +29,17 @@ public class CreateModifyExporterDialogViewModel extends AbstractViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(CreateModifyExporterDialogViewModel.class); private final DialogService dialogService; + private final CliPreferences preferences; private final StringProperty name = new SimpleStringProperty(""); + private final StringProperty layoutFile = new SimpleStringProperty(""); + private final StringProperty extension = new SimpleStringProperty(""); - public CreateModifyExporterDialogViewModel(ExporterViewModel exporter, - DialogService dialogService, - CliPreferences preferences) { + public CreateModifyExporterDialogViewModel(ExporterViewModel exporter, DialogService dialogService, + CliPreferences preferences) { this.dialogService = dialogService; this.preferences = preferences; @@ -61,11 +64,9 @@ public ExporterViewModel saveExporter() { return null; } - // Create a new exporter to be returned to ExportCustomizationDialogViewModel, which requested it - TemplateExporter format = new TemplateExporter( - name.get(), - layoutFile.get(), - extension.get(), + // Create a new exporter to be returned to ExportCustomizationDialogViewModel, + // which requested it + TemplateExporter format = new TemplateExporter(name.get(), layoutFile.get(), extension.get(), preferences.getLayoutFormatterPreferences(), preferences.getSelfContainedExportConfiguration().getSelfContainedSaveOrder()); format.setCustomExport(true); @@ -74,14 +75,15 @@ public ExporterViewModel saveExporter() { public void browse() { String fileDir = layoutFile.getValue().isEmpty() - ? preferences.getExportPreferences().getExportWorkingDirectory().toString() - : layoutFile.getValue(); + ? preferences.getExportPreferences().getExportWorkingDirectory().toString() : layoutFile.getValue(); FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(Localization.lang("Custom layout file"), StandardFileType.LAYOUT) - .withDefaultExtension(Localization.lang("Custom layout file"), StandardFileType.LAYOUT) - .withInitialDirectory(fileDir).build(); - dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(f -> layoutFile.set(f.toAbsolutePath().toString())); + .addExtensionFilter(Localization.lang("Custom layout file"), StandardFileType.LAYOUT) + .withDefaultExtension(Localization.lang("Custom layout file"), StandardFileType.LAYOUT) + .withInitialDirectory(fileDir) + .build(); + dialogService.showFileOpenDialog(fileDialogConfiguration) + .ifPresent(f -> layoutFile.set(f.toAbsolutePath().toString())); } public StringProperty getName() { @@ -95,4 +97,5 @@ public StringProperty getLayoutFileName() { public StringProperty getExtension() { return extension; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/exporter/ExportCommand.java b/jabgui/src/main/java/org/jabref/gui/exporter/ExportCommand.java index ac54a3953fa..0c990a9a103 100644 --- a/jabgui/src/main/java/org/jabref/gui/exporter/ExportCommand.java +++ b/jabgui/src/main/java/org/jabref/gui/exporter/ExportCommand.java @@ -40,27 +40,33 @@ */ public class ExportCommand extends SimpleCommand { - public enum ExportMethod { EXPORT_ALL, EXPORT_SELECTED } + public enum ExportMethod { + + EXPORT_ALL, EXPORT_SELECTED + + } private static final Logger LOGGER = LoggerFactory.getLogger(ExportCommand.class); private final ExportMethod exportMethod; + private final Supplier tabSupplier; + private final StateManager stateManager; + private final GuiPreferences preferences; + private final DialogService dialogService; + private final BibEntryTypesManager entryTypesManager; + private final JournalAbbreviationRepository abbreviationRepository; + private final TaskExecutor taskExecutor; - public ExportCommand(ExportMethod exportMethod, - Supplier tabSupplier, - StateManager stateManager, - DialogService dialogService, - GuiPreferences preferences, - BibEntryTypesManager entryTypesManager, - JournalAbbreviationRepository abbreviationRepository, - TaskExecutor taskExecutor) { + public ExportCommand(ExportMethod exportMethod, Supplier tabSupplier, StateManager stateManager, + DialogService dialogService, GuiPreferences preferences, BibEntryTypesManager entryTypesManager, + JournalAbbreviationRepository abbreviationRepository, TaskExecutor taskExecutor) { this.exportMethod = exportMethod; this.tabSupplier = tabSupplier; this.stateManager = stateManager; @@ -71,25 +77,25 @@ public ExportCommand(ExportMethod exportMethod, this.taskExecutor = taskExecutor; this.executable.bind(exportMethod == ExportMethod.EXPORT_SELECTED - ? ActionHelper.needsEntriesSelected(stateManager) - : ActionHelper.needsDatabase(stateManager)); + ? ActionHelper.needsEntriesSelected(stateManager) : ActionHelper.needsDatabase(stateManager)); } @Override public void execute() { // Get list of exporters and sort before adding to file dialog ExporterFactory exporterFactory = ExporterFactory.create(preferences); - List exporters = exporterFactory.getExporters().stream() - .sorted(Comparator.comparing(Exporter::getName)) - .collect(Collectors.toList()); + List exporters = exporterFactory.getExporters() + .stream() + .sorted(Comparator.comparing(Exporter::getName)) + .collect(Collectors.toList()); FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(FileFilterConverter.exporterToExtensionFilter(exporters)) - .withDefaultExtension(preferences.getExportPreferences().getLastExportExtension()) - .withInitialDirectory(preferences.getExportPreferences().getExportWorkingDirectory()) - .build(); + .addExtensionFilter(FileFilterConverter.exporterToExtensionFilter(exporters)) + .withDefaultExtension(preferences.getExportPreferences().getLastExportExtension()) + .withInitialDirectory(preferences.getExportPreferences().getExportWorkingDirectory()) + .build(); dialogService.showFileSaveDialog(fileDialogConfiguration) - .ifPresent(path -> export(path, fileDialogConfiguration.getSelectedExtensionFilter(), exporters)); + .ifPresent(path -> export(path, fileDialogConfiguration.getSelectedExtensionFilter(), exporters)); } private void export(Path file, FileChooser.ExtensionFilter selectedExtensionFilter, List exporters) { @@ -99,21 +105,20 @@ private void export(Path file, FileChooser.ExtensionFilter selectedExtensionFilt } final Exporter format = FileFilterConverter.getExporter(selectedExtensionFilter, exporters) - .orElseThrow(() -> new IllegalStateException("User didn't selected a file type for the extension")); + .orElseThrow(() -> new IllegalStateException("User didn't selected a file type for the extension")); List entries; if (exportMethod == ExportMethod.EXPORT_SELECTED) { // Selected entries entries = stateManager.getSelectedEntries(); - } else { + } + else { // All entries - entries = stateManager.getActiveDatabase() - .map(BibDatabaseContext::getEntries) - .orElse(List.of()); + entries = stateManager.getActiveDatabase().map(BibDatabaseContext::getEntries).orElse(List.of()); } List fileDirForDatabase = stateManager.getActiveDatabase() - .map(db -> db.getFileDirectories(preferences.getFilePreferences())) - .orElse(List.of(preferences.getFilePreferences().getWorkingDirectory())); + .map(db -> db.getFileDirectories(preferences.getFilePreferences())) + .orElse(List.of(preferences.getFilePreferences().getWorkingDirectory())); // Make sure we remember which filter was used, to set // the default for next time: @@ -122,38 +127,34 @@ private void export(Path file, FileChooser.ExtensionFilter selectedExtensionFilt final List finEntries = entries; - BackgroundTask - .wrap(() -> { - format.export(stateManager.getActiveDatabase().get(), - file, - finEntries, - fileDirForDatabase, - abbreviationRepository); - return null; // can not use BackgroundTask.wrap(Runnable) because Runnable.run() can't throw Exceptions - }) - .onSuccess(save -> { - LibraryTab.DatabaseNotification notificationPane = tabSupplier.get().getNotificationPane(); - notificationPane.notify( - IconTheme.JabRefIcons.FOLDER.getGraphicNode(), - Localization.lang("Export operation finished successfully."), - List.of(new Action(Localization.lang("Reveal in File Explorer"), event -> { - try { - NativeDesktop.openFolderAndSelectFile(file, preferences.getExternalApplicationsPreferences(), dialogService); - } catch (IOException e) { - LOGGER.error("Could not open export folder.", e); - } - notificationPane.hide(); - })), - Duration.seconds(5)); - }) - .onFailure(this::handleError) - .executeWith(taskExecutor); + BackgroundTask.wrap(() -> { + format.export(stateManager.getActiveDatabase().get(), file, finEntries, fileDirForDatabase, + abbreviationRepository); + return null; // can not use BackgroundTask.wrap(Runnable) because + // Runnable.run() can't throw Exceptions + }).onSuccess(save -> { + LibraryTab.DatabaseNotification notificationPane = tabSupplier.get().getNotificationPane(); + notificationPane.notify(IconTheme.JabRefIcons.FOLDER.getGraphicNode(), + Localization.lang("Export operation finished successfully."), + List.of(new Action(Localization.lang("Reveal in File Explorer"), event -> { + try { + NativeDesktop.openFolderAndSelectFile(file, + preferences.getExternalApplicationsPreferences(), dialogService); + } + catch (IOException e) { + LOGGER.error("Could not open export folder.", e); + } + notificationPane.hide(); + })), Duration.seconds(5)); + }).onFailure(this::handleError).executeWith(taskExecutor); } private void handleError(Exception ex) { LOGGER.warn("Problem exporting", ex); dialogService.notify(Localization.lang("Could not save file.")); // Need to warn the user that saving failed! - dialogService.showErrorDialogAndWait(Localization.lang("Save library"), Localization.lang("Could not save file."), ex); + dialogService.showErrorDialogAndWait(Localization.lang("Save library"), + Localization.lang("Could not save file."), ex); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java b/jabgui/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java index e0bf680413a..31cd6c29b04 100644 --- a/jabgui/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java +++ b/jabgui/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java @@ -41,27 +41,24 @@ public class ExportToClipboardAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(ExportToClipboardAction.class); // Only text based exporters can be used - private static final Set SUPPORTED_FILETYPES = Set.of( - StandardFileType.TXT, - StandardFileType.RTF, - StandardFileType.RDF, - StandardFileType.XML, - StandardFileType.HTML, - StandardFileType.CSV, + private static final Set SUPPORTED_FILETYPES = Set.of(StandardFileType.TXT, StandardFileType.RTF, + StandardFileType.RDF, StandardFileType.XML, StandardFileType.HTML, StandardFileType.CSV, StandardFileType.RIS); private final DialogService dialogService; + private final List entries = new ArrayList<>(); + private final ClipBoardManager clipBoardManager; + private final TaskExecutor taskExecutor; + private final CliPreferences preferences; + private final StateManager stateManager; - public ExportToClipboardAction(DialogService dialogService, - StateManager stateManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor, - CliPreferences preferences) { + public ExportToClipboardAction(DialogService dialogService, StateManager stateManager, + ClipBoardManager clipBoardManager, TaskExecutor taskExecutor, CliPreferences preferences) { this.dialogService = dialogService; this.clipBoardManager = clipBoardManager; this.taskExecutor = taskExecutor; @@ -79,34 +76,35 @@ public void execute() { } ExporterFactory exporterFactory = ExporterFactory.create(preferences); - List exporters = exporterFactory.getExporters().stream() - .sorted(Comparator.comparing(Exporter::getName)) - .filter(exporter -> SUPPORTED_FILETYPES.contains(exporter.getFileType())) - .collect(Collectors.toList()); + List exporters = exporterFactory.getExporters() + .stream() + .sorted(Comparator.comparing(Exporter::getName)) + .filter(exporter -> SUPPORTED_FILETYPES.contains(exporter.getFileType())) + .collect(Collectors.toList()); // Find default choice, if any Exporter defaultChoice = exporters.stream() - .filter(exporter -> exporter.getName().equals(preferences.getExportPreferences().getLastExportExtension())) - .findAny() - .orElse(null); + .filter(exporter -> exporter.getName().equals(preferences.getExportPreferences().getLastExportExtension())) + .findAny() + .orElse(null); - Optional selectedExporter = dialogService.showChoiceDialogAndWait( - Localization.lang("Export"), Localization.lang("Select export format"), - Localization.lang("Export"), defaultChoice, exporters); + Optional selectedExporter = dialogService.showChoiceDialogAndWait(Localization.lang("Export"), + Localization.lang("Select export format"), Localization.lang("Export"), defaultChoice, exporters); selectedExporter.ifPresent(exporter -> BackgroundTask.wrap(() -> exportToClipboard(exporter)) - .onSuccess(this::setContentToClipboard) - .onFailure(ex -> { - LOGGER.error("Error exporting to clipboard", ex); - dialogService.showErrorDialogAndWait("Error exporting to clipboard", ex); - }) - .executeWith(taskExecutor)); + .onSuccess(this::setContentToClipboard) + .onFailure(ex -> { + LOGGER.error("Error exporting to clipboard", ex); + dialogService.showErrorDialogAndWait("Error exporting to clipboard", ex); + }) + .executeWith(taskExecutor)); } - private ExportResult exportToClipboard(Exporter exporter) throws IOException, SaveException, ParserConfigurationException, TransformerException { + private ExportResult exportToClipboard(Exporter exporter) + throws IOException, SaveException, ParserConfigurationException, TransformerException { List fileDirForDatabase = stateManager.getActiveDatabase() - .map(db -> db.getFileDirectories(preferences.getFilePreferences())) - .orElse(List.of(preferences.getFilePreferences().getWorkingDirectory())); + .map(db -> db.getFileDirectories(preferences.getFilePreferences())) + .orElse(List.of(preferences.getFilePreferences().getWorkingDirectory())); // Add chosen export type to last used preference, to become default preferences.getExportPreferences().setLastExportExtension(exporter.getName()); @@ -120,21 +118,19 @@ private ExportResult exportToClipboard(Exporter exporter) throws IOException, Sa entries.addAll(stateManager.getSelectedEntries()); // Write to file: - exporter.export( - stateManager.getActiveDatabase().get(), - tmp, - entries, - fileDirForDatabase, + exporter.export(stateManager.getActiveDatabase().get(), tmp, entries, fileDirForDatabase, Injector.instantiateModelOrService(JournalAbbreviationRepository.class)); // Read the file and put the contents on the clipboard: return new ExportResult(Files.readString(tmp), exporter.getFileType()); - } finally { + } + finally { // Clean up: if ((tmp != null) && Files.exists(tmp)) { try { Files.delete(tmp); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.info("Cannot delete temporary clipboard file", e); } } @@ -146,9 +142,11 @@ private void setContentToClipboard(ExportResult result) { List extensions = result.fileType.getExtensions(); if (extensions.contains("html")) { clipboardContent.putHtml(result.content); - } else if (extensions.contains("rtf")) { + } + else if (extensions.contains("rtf")) { clipboardContent.putRtf(result.content); - } else if (extensions.contains("rdf")) { + } + else if (extensions.contains("rdf")) { clipboardContent.putRtf(result.content); } clipboardContent.putString(result.content); @@ -159,4 +157,5 @@ private void setContentToClipboard(ExportResult result) { private record ExportResult(String content, FileType fileType) { } + } diff --git a/jabgui/src/main/java/org/jabref/gui/exporter/ExporterViewModel.java b/jabgui/src/main/java/org/jabref/gui/exporter/ExporterViewModel.java index 23b92f01083..85f486f2c8d 100644 --- a/jabgui/src/main/java/org/jabref/gui/exporter/ExporterViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/exporter/ExporterViewModel.java @@ -6,21 +6,26 @@ import org.jabref.logic.exporter.TemplateExporter; /** - * ExporterViewModel wraps a TemplateExporter from logic and is used in the exporter customization dialog view and ViewModel. + * ExporterViewModel wraps a TemplateExporter from logic and is used in the exporter + * customization dialog view and ViewModel. */ public class ExporterViewModel { private final TemplateExporter exporter; + private final StringProperty name = new SimpleStringProperty(); + private final StringProperty layoutFileName = new SimpleStringProperty(); + private final StringProperty extension = new SimpleStringProperty(); public ExporterViewModel(TemplateExporter exporter) { this.exporter = exporter; this.name.setValue(exporter.getName()); this.layoutFileName.setValue(exporter.getLayoutFileNameWithExtension()); - // Only the first of the extensions gotten from FileType is saved into the class using get(0) + // Only the first of the extensions gotten from FileType is saved into the class + // using get(0) String extensionString = exporter.getFileType().getExtensions().getFirst(); this.extension.setValue(extensionString); } @@ -40,4 +45,5 @@ public StringProperty layoutFileName() { public StringProperty extension() { return this.extension; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/exporter/SaveAction.java b/jabgui/src/main/java/org/jabref/gui/exporter/SaveAction.java index 83f4011912e..e0ae03d3e3a 100644 --- a/jabgui/src/main/java/org/jabref/gui/exporter/SaveAction.java +++ b/jabgui/src/main/java/org/jabref/gui/exporter/SaveAction.java @@ -17,20 +17,24 @@ */ public class SaveAction extends SimpleCommand { - public enum SaveMethod { SAVE, SAVE_AS, SAVE_SELECTED } + public enum SaveMethod { + + SAVE, SAVE_AS, SAVE_SELECTED + + } private final SaveMethod saveMethod; + private final Supplier tabSupplier; private final DialogService dialogService; + private final GuiPreferences preferences; + private final StateManager stateManager; - public SaveAction(SaveMethod saveMethod, - Supplier tabSupplier, - DialogService dialogService, - GuiPreferences preferences, - StateManager stateManager) { + public SaveAction(SaveMethod saveMethod, Supplier tabSupplier, DialogService dialogService, + GuiPreferences preferences, StateManager stateManager) { this.saveMethod = saveMethod; this.tabSupplier = tabSupplier; this.dialogService = dialogService; @@ -39,19 +43,16 @@ public SaveAction(SaveMethod saveMethod, if (saveMethod == SaveMethod.SAVE_SELECTED) { this.executable.bind(ActionHelper.needsEntriesSelected(stateManager)); - } else { + } + else { this.executable.bind(ActionHelper.needsDatabase(stateManager)); } } @Override public void execute() { - SaveDatabaseAction saveDatabaseAction = new SaveDatabaseAction( - tabSupplier.get(), - dialogService, - preferences, - Injector.instantiateModelOrService(BibEntryTypesManager.class), - stateManager); + SaveDatabaseAction saveDatabaseAction = new SaveDatabaseAction(tabSupplier.get(), dialogService, preferences, + Injector.instantiateModelOrService(BibEntryTypesManager.class), stateManager); switch (saveMethod) { case SAVE -> saveDatabaseAction.save(); @@ -59,4 +60,5 @@ public void execute() { case SAVE_SELECTED -> saveDatabaseAction.saveSelectedAsPlain(); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/exporter/SaveAllAction.java b/jabgui/src/main/java/org/jabref/gui/exporter/SaveAllAction.java index c8b7a51700b..f555227a67b 100644 --- a/jabgui/src/main/java/org/jabref/gui/exporter/SaveAllAction.java +++ b/jabgui/src/main/java/org/jabref/gui/exporter/SaveAllAction.java @@ -18,11 +18,15 @@ public class SaveAllAction extends SimpleCommand { private final Supplier> tabsSupplier; + private final DialogService dialogService; + private final GuiPreferences preferences; + private final StateManager stateManager; - public SaveAllAction(Supplier> tabsSupplier, GuiPreferences preferences, DialogService dialogService, StateManager stateManager) { + public SaveAllAction(Supplier> tabsSupplier, GuiPreferences preferences, + DialogService dialogService, StateManager stateManager) { this.tabsSupplier = tabsSupplier; this.dialogService = dialogService; this.preferences = preferences; @@ -35,7 +39,8 @@ public void execute() { dialogService.notify(Localization.lang("Saving all libraries...")); for (LibraryTab libraryTab : tabsSupplier.get()) { - SaveDatabaseAction saveDatabaseAction = new SaveDatabaseAction(libraryTab, dialogService, preferences, Injector.instantiateModelOrService(BibEntryTypesManager.class), stateManager); + SaveDatabaseAction saveDatabaseAction = new SaveDatabaseAction(libraryTab, dialogService, preferences, + Injector.instantiateModelOrService(BibEntryTypesManager.class), stateManager); boolean saveResult = saveDatabaseAction.save(); if (!saveResult) { dialogService.notify(Localization.lang("Could not save file.")); @@ -44,4 +49,5 @@ public void execute() { dialogService.notify(Localization.lang("Save all finished.")); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/jabgui/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index f9651ed03a2..1388bfd9338 100644 --- a/jabgui/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/jabgui/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -48,30 +48,34 @@ import org.slf4j.LoggerFactory; /** - * Action for the "Save" and "Save as" operations called from BasePanel. This class is also used for save operations - * when closing a database or quitting the applications. + * Action for the "Save" and "Save as" operations called from BasePanel. This class is + * also used for save operations when closing a database or quitting the applications. *

- * The save operation is loaded off of the GUI thread using {@link BackgroundTask}. Callers can query whether the - * operation was canceled, or whether it was successful. + * The save operation is loaded off of the GUI thread using {@link BackgroundTask}. + * Callers can query whether the operation was canceled, or whether it was successful. */ public class SaveDatabaseAction { + private static final Logger LOGGER = LoggerFactory.getLogger(SaveDatabaseAction.class); private final LibraryTab libraryTab; + private final DialogService dialogService; + private final GuiPreferences preferences; + private final BibEntryTypesManager entryTypesManager; + private final StateManager stateManager; public enum SaveDatabaseMode { + SILENT, NORMAL + } - public SaveDatabaseAction(LibraryTab libraryTab, - DialogService dialogService, - GuiPreferences preferences, - BibEntryTypesManager entryTypesManager, - StateManager stateManager) { + public SaveDatabaseAction(LibraryTab libraryTab, DialogService dialogService, GuiPreferences preferences, + BibEntryTypesManager entryTypesManager, StateManager stateManager) { this.libraryTab = libraryTab; this.dialogService = dialogService; this.preferences = preferences; @@ -99,42 +103,43 @@ public boolean saveAs(Path file) { } private SelfContainedSaveOrder getSaveOrder() { - return libraryTab.getBibDatabaseContext() - .getMetaData().getSaveOrder() - .map(so -> { - if (so.getOrderType() == SaveOrder.OrderType.TABLE) { - // We need to "flatten out" SaveOrder.OrderType.TABLE as BibWriter does not have access to preferences - List> sortOrder = libraryTab.getMainTable().getSortOrder(); - return new SelfContainedSaveOrder( - SaveOrder.OrderType.SPECIFIED, - sortOrder.stream() - .filter(col -> col instanceof MainTableColumn) - .map(column -> ((MainTableColumn) column).getModel()) - .flatMap(model -> model.getSortCriteria().stream()) - .toList()); - } else { - return SelfContainedSaveOrder.of(so); - } - }) - .orElse(SaveOrder.getDefaultSaveOrder()); + return libraryTab.getBibDatabaseContext().getMetaData().getSaveOrder().map(so -> { + if (so.getOrderType() == SaveOrder.OrderType.TABLE) { + // We need to "flatten out" SaveOrder.OrderType.TABLE as BibWriter does + // not have access to preferences + List> sortOrder = libraryTab.getMainTable().getSortOrder(); + return new SelfContainedSaveOrder(SaveOrder.OrderType.SPECIFIED, + sortOrder.stream() + .filter(col -> col instanceof MainTableColumn) + .map(column -> ((MainTableColumn) column).getModel()) + .flatMap(model -> model.getSortCriteria().stream()) + .toList()); + } + else { + return SelfContainedSaveOrder.of(so); + } + }).orElse(SaveOrder.getDefaultSaveOrder()); } public void saveSelectedAsPlain() { askForSavePath().ifPresent(path -> { try { - saveDatabase(path, true, StandardCharsets.UTF_8, BibDatabaseWriter.SaveType.PLAIN_BIBTEX, getSaveOrder()); + saveDatabase(path, true, StandardCharsets.UTF_8, BibDatabaseWriter.SaveType.PLAIN_BIBTEX, + getSaveOrder()); preferences.getLastFilesOpenedPreferences().getFileHistory().newFile(path); dialogService.notify(Localization.lang("Saved selected to '%0'.", path.toString())); - } catch (SaveException ex) { + } + catch (SaveException ex) { LOGGER.error("A problem occurred when trying to save the file", ex); - dialogService.showErrorDialogAndWait(Localization.lang("Save library"), Localization.lang("Could not save file."), ex); + dialogService.showErrorDialogAndWait(Localization.lang("Save library"), + Localization.lang("Could not save file."), ex); } }); } /** - * @param file the new file name to save the database to. This is stored in the database context of the panel upon - * successful save. + * @param file the new file name to save the database to. This is stored in the + * database context of the panel upon successful save. * @return true on successful save */ boolean saveAs(Path file, SaveDatabaseMode mode) { @@ -144,15 +149,17 @@ boolean saveAs(Path file, SaveDatabaseMode mode) { if (databasePath.isPresent()) { // Close AutosaveManager, BackupManager, and IndexManager for original library AutosaveManager.shutdown(context); - BackupManager.shutdown(context, this.preferences.getFilePreferences().getBackupDirectory(), preferences.getFilePreferences().shouldCreateBackup()); + BackupManager.shutdown(context, this.preferences.getFilePreferences().getBackupDirectory(), + preferences.getFilePreferences().shouldCreateBackup()); libraryTab.closeIndexManger(); } // Set new location if (context.getLocation() == DatabaseLocation.SHARED) { - // Save all properties dependent on the ID. This makes it possible to restore them. + // Save all properties dependent on the ID. This makes it possible to restore + // them. new SharedDatabasePreferences(context.getDatabase().generateSharedDatabaseID()) - .putAllDBMSConnectionProperties(context.getDBMSSynchronizer().getConnectionProperties()); + .putAllDBMSConnectionProperties(context.getDBMSSynchronizer().getConnectionProperties()); } boolean saveResult = save(file, mode); @@ -164,7 +171,8 @@ boolean saveAs(Path file, SaveDatabaseMode mode) { stateManager.setActiveDatabase(context); libraryTab.updateTabTitle(false); - // Reset (here: uninstall and install again) AutosaveManager, BackupManager and IndexManager for the new file name + // Reset (here: uninstall and install again) AutosaveManager, BackupManager + // and IndexManager for the new file name libraryTab.resetChangeMonitor(); libraryTab.installAutosaveManagerAndBackupManager(); libraryTab.createIndexManager(); @@ -175,17 +183,16 @@ boolean saveAs(Path file, SaveDatabaseMode mode) { } /** - * Asks the user for the path to save to. Stores the directory to the preferences, which is used next time when - * opening the dialog. - * + * Asks the user for the path to save to. Stores the directory to the preferences, + * which is used next time when opening the dialog. * @return the path set by the user */ private Optional askForSavePath() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.BIBTEX_DB) - .withDefaultExtension(StandardFileType.BIBTEX_DB) - .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) - .build(); + .addExtensionFilter(StandardFileType.BIBTEX_DB) + .withDefaultExtension(StandardFileType.BIBTEX_DB) + .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) + .build(); Optional selectedPath = dialogService.showFileSaveDialog(fileDialogConfiguration); selectedPath.ifPresent(path -> preferences.getFilePreferences().setWorkingDirectory(path.getParent())); if (selectedPath.isPresent()) { @@ -193,9 +200,9 @@ private Optional askForSavePath() { // Workaround for linux systems not adding file extension if (!savePath.getFileName().toString().toLowerCase().endsWith(".bib")) { savePath = Path.of(savePath + ".bib"); - if (!Files.notExists(savePath) && !dialogService.showConfirmationDialogAndWait( - Localization.lang("Overwrite file"), - Localization.lang("'%0' exists. Overwrite file?", savePath.getFileName()))) { + if (!Files.notExists(savePath) + && !dialogService.showConfirmationDialogAndWait(Localization.lang("Overwrite file"), + Localization.lang("'%0' exists. Overwrite file?", savePath.getFileName()))) { return Optional.empty(); } @@ -230,14 +237,15 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { try { Charset encoding = libraryTab.getBibDatabaseContext() - .getMetaData() - .getEncoding() - .orElse(StandardCharsets.UTF_8); + .getMetaData() + .getEncoding() + .orElse(StandardCharsets.UTF_8); // Make sure to remember which encoding we used libraryTab.getBibDatabaseContext().getMetaData().setEncoding(encoding, ChangePropagation.DO_NOT_POST_EVENT); - boolean success = saveDatabase(targetPath, false, encoding, BibDatabaseWriter.SaveType.WITH_JABREF_META_DATA, getSaveOrder()); + boolean success = saveDatabase(targetPath, false, encoding, + BibDatabaseWriter.SaveType.WITH_JABREF_META_DATA, getSaveOrder()); if (success) { libraryTab.getUndoManager().markUnchanged(); @@ -245,80 +253,91 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { } dialogService.notify(Localization.lang("Library saved")); return success; - } catch (SaveException ex) { + } + catch (SaveException ex) { LOGGER.error("A problem occurred when trying to save the file {}", targetPath, ex); - dialogService.showErrorDialogAndWait(Localization.lang("Save library"), Localization.lang("Could not save file."), ex); + dialogService.showErrorDialogAndWait(Localization.lang("Save library"), + Localization.lang("Could not save file."), ex); return false; - } finally { + } + finally { // release panel from save status libraryTab.setSaving(false); } } - private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding, BibDatabaseWriter.SaveType saveType, SelfContainedSaveOrder saveOrder) throws SaveException { - // if this code is adapted, please also adapt org.jabref.logic.autosaveandbackup.BackupManager.performBackup - SelfContainedSaveConfiguration saveConfiguration - = new SelfContainedSaveConfiguration(saveOrder, false, saveType, preferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); + private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding, BibDatabaseWriter.SaveType saveType, + SelfContainedSaveOrder saveOrder) throws SaveException { + // if this code is adapted, please also adapt + // org.jabref.logic.autosaveandbackup.BackupManager.performBackup + SelfContainedSaveConfiguration saveConfiguration = new SelfContainedSaveConfiguration(saveOrder, false, + saveType, preferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); BibDatabaseContext bibDatabaseContext = libraryTab.getBibDatabaseContext(); synchronized (bibDatabaseContext) { - try (AtomicFileWriter fileWriter = new AtomicFileWriter(file, encoding, saveConfiguration.shouldMakeBackup())) { + try (AtomicFileWriter fileWriter = new AtomicFileWriter(file, encoding, + saveConfiguration.shouldMakeBackup())) { BibWriter bibWriter = new BibWriter(fileWriter, bibDatabaseContext.getDatabase().getNewLineSeparator()); - BibDatabaseWriter databaseWriter = new BibDatabaseWriter( - bibWriter, - saveConfiguration, - preferences.getFieldPreferences(), - preferences.getCitationKeyPatternPreferences(), + BibDatabaseWriter databaseWriter = new BibDatabaseWriter(bibWriter, saveConfiguration, + preferences.getFieldPreferences(), preferences.getCitationKeyPatternPreferences(), entryTypesManager); if (selectedOnly) { databaseWriter.savePartOfDatabase(bibDatabaseContext, libraryTab.getSelectedEntries()); - } else { + } + else { databaseWriter.saveDatabase(bibDatabaseContext); } libraryTab.registerUndoableChanges(databaseWriter.getSaveActionsFieldChanges()); if (fileWriter.hasEncodingProblems()) { - saveWithDifferentEncoding(file, selectedOnly, encoding, fileWriter.getEncodingProblems(), saveType, saveOrder); + saveWithDifferentEncoding(file, selectedOnly, encoding, fileWriter.getEncodingProblems(), saveType, + saveOrder); } - } catch (UnsupportedCharsetException ex) { - throw new SaveException(Localization.lang("Character encoding '%0' is not supported.", encoding.displayName()), ex); - } catch (IOException ex) { + } + catch (UnsupportedCharsetException ex) { + throw new SaveException( + Localization.lang("Character encoding '%0' is not supported.", encoding.displayName()), ex); + } + catch (IOException ex) { throw new SaveException("Problems saving: " + ex, ex); } return true; } } - private void saveWithDifferentEncoding(Path file, boolean selectedOnly, Charset encoding, Set encodingProblems, BibDatabaseWriter.SaveType saveType, SelfContainedSaveOrder saveOrder) throws SaveException { + private void saveWithDifferentEncoding(Path file, boolean selectedOnly, Charset encoding, + Set encodingProblems, BibDatabaseWriter.SaveType saveType, SelfContainedSaveOrder saveOrder) + throws SaveException { DialogPane pane = new DialogPane(); VBox vbox = new VBox(); - vbox.getChildren().addAll( - new Text(Localization.lang("The chosen encoding '%0' could not encode the following characters:", encoding.displayName())), - new Text(encodingProblems.stream().map(Object::toString).collect(Collectors.joining("."))), - new Text(Localization.lang("What do you want to do?")) - ); + vbox.getChildren() + .addAll(new Text(Localization.lang("The chosen encoding '%0' could not encode the following characters:", + encoding.displayName())), + new Text(encodingProblems.stream().map(Object::toString).collect(Collectors.joining("."))), + new Text(Localization.lang("What do you want to do?"))); pane.setContent(vbox); - ButtonType tryDifferentEncoding = new ButtonType(Localization.lang("Try different encoding"), ButtonBar.ButtonData.OTHER); + ButtonType tryDifferentEncoding = new ButtonType(Localization.lang("Try different encoding"), + ButtonBar.ButtonData.OTHER); ButtonType ignore = new ButtonType(Localization.lang("Ignore"), ButtonBar.ButtonData.APPLY); boolean saveWithDifferentEncoding = dialogService - .showCustomDialogAndWait(Localization.lang("Save library"), pane, ignore, tryDifferentEncoding) - .filter(buttonType -> buttonType.equals(tryDifferentEncoding)) - .isPresent(); + .showCustomDialogAndWait(Localization.lang("Save library"), pane, ignore, tryDifferentEncoding) + .filter(buttonType -> buttonType.equals(tryDifferentEncoding)) + .isPresent(); if (saveWithDifferentEncoding) { - Optional newEncoding = dialogService.showChoiceDialogAndWait( - Localization.lang("Save library"), - Localization.lang("Select new encoding"), - Localization.lang("Save library"), - encoding, + Optional newEncoding = dialogService.showChoiceDialogAndWait(Localization.lang("Save library"), + Localization.lang("Select new encoding"), Localization.lang("Save library"), encoding, OS.ENCODINGS); if (newEncoding.isPresent()) { // Make sure to remember which encoding we used. - libraryTab.getBibDatabaseContext().getMetaData().setEncoding(newEncoding.get(), ChangePropagation.DO_NOT_POST_EVENT); + libraryTab.getBibDatabaseContext() + .getMetaData() + .setEncoding(newEncoding.get(), ChangePropagation.DO_NOT_POST_EVENT); saveDatabase(file, selectedOnly, newEncoding.get(), saveType, saveOrder); } } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/exporter/WriteMetadataToLinkedPdfsAction.java b/jabgui/src/main/java/org/jabref/gui/exporter/WriteMetadataToLinkedPdfsAction.java index 7d33c24caf4..d6dde5399dc 100644 --- a/jabgui/src/main/java/org/jabref/gui/exporter/WriteMetadataToLinkedPdfsAction.java +++ b/jabgui/src/main/java/org/jabref/gui/exporter/WriteMetadataToLinkedPdfsAction.java @@ -33,28 +33,33 @@ import static org.jabref.gui.actions.ActionHelper.needsDatabase; /** - * Writes XMP Metadata to all the linked pdfs of the selected entries according to the linking entry + * Writes XMP Metadata to all the linked pdfs of the selected entries according to the + * linking entry */ public class WriteMetadataToLinkedPdfsAction extends SimpleCommand { + private static final Logger LOGGER = LoggerFactory.getLogger(WriteMetadataToLinkedPdfsAction.class); private final StateManager stateManager; + private final BibEntryTypesManager entryTypesManager; + private final FieldPreferences fieldPreferences; + private final DialogService dialogService; + private final TaskExecutor taskExecutor; + private final FilePreferences filePreferences; + private final XmpPreferences xmpPreferences; + private final JournalAbbreviationRepository abbreviationRepository; - public WriteMetadataToLinkedPdfsAction(DialogService dialogService, - FieldPreferences fieldPreferences, - FilePreferences filePreferences, - XmpPreferences xmpPreferences, - BibEntryTypesManager entryTypesManager, - JournalAbbreviationRepository abbreviationRepository, - TaskExecutor taskExecutor, - StateManager stateManager) { + public WriteMetadataToLinkedPdfsAction(DialogService dialogService, FieldPreferences fieldPreferences, + FilePreferences filePreferences, XmpPreferences xmpPreferences, BibEntryTypesManager entryTypesManager, + JournalAbbreviationRepository abbreviationRepository, TaskExecutor taskExecutor, + StateManager stateManager) { this.stateManager = stateManager; this.entryTypesManager = entryTypesManager; this.fieldPreferences = fieldPreferences; @@ -82,7 +87,8 @@ public void execute() { LOGGER.warn("No entry selected for fulltext download."); dialogService.notify(Localization.lang("This operation requires one or more entries to be selected.")); return; - } else { + } + else { boolean confirm = dialogService.showConfirmationDialogAndWait( Localization.lang("Write metadata to PDF files"), Localization.lang("Write metadata for all PDFs in current library?")); @@ -94,45 +100,43 @@ public void execute() { dialogService.notify(Localization.lang("Writing metadata...")); - new WriteMetaDataTask( - databaseContext, - entries, - abbreviationRepository, - entryTypesManager, - fieldPreferences, - filePreferences, - xmpPreferences, - stateManager, - dialogService) - .executeWith(taskExecutor); + new WriteMetaDataTask(databaseContext, entries, abbreviationRepository, entryTypesManager, fieldPreferences, + filePreferences, xmpPreferences, stateManager, dialogService) + .executeWith(taskExecutor); } private static class WriteMetaDataTask extends BackgroundTask { private final BibDatabaseContext databaseContext; + private final List entries; + private final JournalAbbreviationRepository abbreviationRepository; + private final BibEntryTypesManager entryTypesManager; + private final FieldPreferences fieldPreferences; + private final FilePreferences filePreferences; + private final XmpPreferences xmpPreferences; + private final StateManager stateManager; + private final DialogService dialogService; private final List failedWrittenFiles = new ArrayList<>(); + private int skipped = 0; + private int entriesChanged = 0; + private int errors = 0; - public WriteMetaDataTask(BibDatabaseContext databaseContext, - List entries, - JournalAbbreviationRepository abbreviationRepository, - BibEntryTypesManager entryTypesManager, - FieldPreferences fieldPreferences, - FilePreferences filePreferences, - XmpPreferences xmpPreferences, - StateManager stateManager, - DialogService dialogService) { + public WriteMetaDataTask(BibDatabaseContext databaseContext, List entries, + JournalAbbreviationRepository abbreviationRepository, BibEntryTypesManager entryTypesManager, + FieldPreferences fieldPreferences, FilePreferences filePreferences, XmpPreferences xmpPreferences, + StateManager stateManager, DialogService dialogService) { this.databaseContext = databaseContext; this.entries = entries; this.abbreviationRepository = abbreviationRepository; @@ -157,41 +161,37 @@ public Void call() { updateProgress(i, entries.size()); // Make a list of all PDFs linked from this entry: - List files = entry.getFiles().stream() - .map(file -> file.findIn(stateManager.getActiveDatabase().get(), filePreferences)) - .filter(Optional::isPresent) - .map(Optional::get) - .filter(FileUtil::isPDFFile) - .toList(); + List files = entry.getFiles() + .stream() + .map(file -> file.findIn(stateManager.getActiveDatabase().get(), filePreferences)) + .filter(Optional::isPresent) + .map(Optional::get) + .filter(FileUtil::isPDFFile) + .toList(); if (files.isEmpty()) { LOGGER.debug("Skipped empty entry '{}'", entry.getCitationKey().orElse(entry.getAuthorTitleYear(16))); skipped++; - } else { + } + else { for (Path file : files) { updateMessage(Localization.lang("Writing metadata to %0", file.getFileName())); if (Files.exists(file)) { try { - WriteMetadataToSinglePdfAction.writeMetadataToFile( - file, - entry, - databaseContext, - abbreviationRepository, - entryTypesManager, - fieldPreferences, - filePreferences, + WriteMetadataToSinglePdfAction.writeMetadataToFile(file, entry, databaseContext, + abbreviationRepository, entryTypesManager, fieldPreferences, filePreferences, xmpPreferences); entriesChanged++; - } catch (IOException - | TransformerException - | SaveException - | ParserConfigurationException e) { + } + catch (IOException | TransformerException | SaveException + | ParserConfigurationException e) { LOGGER.error("Error while writing XMP data to pdf '{}'", file, e); failedWrittenFiles.add(file); errors++; } - } else { + } + else { LOGGER.debug("Skipped non existing pdf '{}'", file); skipped++; } @@ -201,9 +201,10 @@ public Void call() { } updateMessage(Localization.lang("Finished")); - dialogService.notify(Localization.lang("Finished writing metadata for library %0 (%1 succeeded, %2 skipped, %3 errors).", - databaseContext.getDatabasePath().map(Path::toString).orElse("undefined"), - String.valueOf(entriesChanged), String.valueOf(skipped), String.valueOf(errors))); + dialogService.notify( + Localization.lang("Finished writing metadata for library %0 (%1 succeeded, %2 skipped, %3 errors).", + databaseContext.getDatabasePath().map(Path::toString).orElse("undefined"), + String.valueOf(entriesChanged), String.valueOf(skipped), String.valueOf(errors))); if (!failedWrittenFiles.isEmpty()) { LOGGER.error("Failed to write XMP data to PDFs:\n{}", failedWrittenFiles); @@ -211,5 +212,7 @@ public Void call() { return null; } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoLinkFilesAction.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoLinkFilesAction.java index 3af24299587..1a98c33e737 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoLinkFilesAction.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoLinkFilesAction.java @@ -26,18 +26,23 @@ import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; /** - * This Action may only be used in a menu or button. - * Never in the entry editor. FileListEditor and EntryEditor have other ways to update the file links + * This Action may only be used in a menu or button. Never in the entry editor. + * FileListEditor and EntryEditor have other ways to update the file links */ public class AutoLinkFilesAction extends SimpleCommand { private final DialogService dialogService; + private final GuiPreferences preferences; + private final StateManager stateManager; + private final UndoManager undoManager; + private final UiTaskExecutor taskExecutor; - public AutoLinkFilesAction(DialogService dialogService, GuiPreferences preferences, StateManager stateManager, UndoManager undoManager, UiTaskExecutor taskExecutor) { + public AutoLinkFilesAction(DialogService dialogService, GuiPreferences preferences, StateManager stateManager, + UndoManager undoManager, UiTaskExecutor taskExecutor) { this.dialogService = dialogService; this.preferences = preferences; this.stateManager = stateManager; @@ -45,24 +50,24 @@ public AutoLinkFilesAction(DialogService dialogService, GuiPreferences preferenc this.taskExecutor = taskExecutor; this.executable.bind(needsDatabase(this.stateManager).and(needsEntriesSelected(stateManager))); - this.statusMessage.bind(BindingsHelper.ifThenElse(executable, "", Localization.lang("This operation requires one or more entries to be selected."))); + this.statusMessage.bind(BindingsHelper.ifThenElse(executable, "", + Localization.lang("This operation requires one or more entries to be selected."))); } @Override public void execute() { - final BibDatabaseContext database = stateManager.getActiveDatabase().orElseThrow(() -> new NullPointerException("Database null")); + final BibDatabaseContext database = stateManager.getActiveDatabase() + .orElseThrow(() -> new NullPointerException("Database null")); final List entries = stateManager.getSelectedEntries(); - AutoSetFileLinksUtil util = new AutoSetFileLinksUtil( - database, - preferences.getExternalApplicationsPreferences(), - preferences.getFilePreferences(), - preferences.getAutoLinkPreferences()); + AutoSetFileLinksUtil util = new AutoSetFileLinksUtil(database, preferences.getExternalApplicationsPreferences(), + preferences.getFilePreferences(), preferences.getAutoLinkPreferences()); final NamedCompound nc = new NamedCompound(Localization.lang("Automatically set file links")); Task linkFilesTask = new Task<>() { final BiConsumer onLinkedFile = (linkedFile, entry) -> { - // lambda for gui actions that are relevant when setting the linked file entry when ui is opened + // lambda for gui actions that are relevant when setting the linked file + // entry when ui is opened String newVal = FileFieldWriter.getStringRepresentation(linkedFile); String oldVal = entry.getField(StandardField.FILE).orElse(null); UndoableFieldChange fieldChange = new UndoableFieldChange(entry, StandardField.FILE, oldVal, newVal); @@ -80,8 +85,7 @@ protected void succeeded() { AutoSetFileLinksUtil.LinkFilesResult result = getValue(); if (!result.getFileExceptions().isEmpty()) { - dialogService.showWarningDialogAndWait( - Localization.lang("Automatically set file links"), + dialogService.showWarningDialogAndWait(Localization.lang("Automatically set file links"), Localization.lang("Problem finding files. See error log for details.")); return; } @@ -103,10 +107,9 @@ protected void succeeded() { } }; - dialogService.showProgressDialog( - Localization.lang("Automatically setting file links"), - Localization.lang("Searching for files"), - linkFilesTask); + dialogService.showProgressDialog(Localization.lang("Automatically setting file links"), + Localization.lang("Searching for files"), linkFilesTask); taskExecutor.execute(linkFilesTask); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java index 1526290334e..e8abfa75ed4 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java @@ -28,7 +28,9 @@ public class AutoSetFileLinksUtil { public static class LinkFilesResult { + private final List changedEntries = new ArrayList<>(); + private final List fileExceptions = new ArrayList<>(); protected void addBibEntry(BibEntry bibEntry) { @@ -46,28 +48,32 @@ public List getChangedEntries() { public List getFileExceptions() { return fileExceptions; } + } private static final Logger LOGGER = LoggerFactory.getLogger(AutoSetFileLinksUtil.class); private final List directories; + private final AutoLinkPreferences autoLinkPreferences; + private final ExternalApplicationsPreferences externalApplicationsPreferences; public AutoSetFileLinksUtil(BibDatabaseContext databaseContext, - ExternalApplicationsPreferences externalApplicationsPreferences, - FilePreferences filePreferences, - AutoLinkPreferences autoLinkPreferences) { + ExternalApplicationsPreferences externalApplicationsPreferences, FilePreferences filePreferences, + AutoLinkPreferences autoLinkPreferences) { this(databaseContext.getFileDirectories(filePreferences), externalApplicationsPreferences, autoLinkPreferences); } - private AutoSetFileLinksUtil(List directories, ExternalApplicationsPreferences externalApplicationsPreferences, AutoLinkPreferences autoLinkPreferences) { + private AutoSetFileLinksUtil(List directories, + ExternalApplicationsPreferences externalApplicationsPreferences, AutoLinkPreferences autoLinkPreferences) { this.directories = directories; this.autoLinkPreferences = autoLinkPreferences; this.externalApplicationsPreferences = externalApplicationsPreferences; } - public LinkFilesResult linkAssociatedFiles(List entries, BiConsumer onAddLinkedFile) { + public LinkFilesResult linkAssociatedFiles(List entries, + BiConsumer onAddLinkedFile) { LinkFilesResult result = new LinkFilesResult(); for (BibEntry entry : entries) { @@ -75,7 +81,8 @@ public LinkFilesResult linkAssociatedFiles(List entries, BiConsumer

  • entries, BiConsumer
  • findAssociatedNotLinkedFiles(BibEntry entry) throws IOException { List linkedFiles = new ArrayList<>(); - List extensions = externalApplicationsPreferences.getExternalFileTypes().stream().map(ExternalFileType::getExtension).toList(); + List extensions = externalApplicationsPreferences.getExternalFileTypes() + .stream() + .map(ExternalFileType::getExtension) + .toList(); LOGGER.debug("Searching for extensions {} in directories {}", extensions, directories); @@ -109,21 +120,24 @@ public List findAssociatedNotLinkedFiles(BibEntry entry) throws IOEx // Collect the found files that are not yet linked for (Path foundFile : result) { - boolean fileAlreadyLinked = entry.getFiles().stream() - .map(file -> file.findIn(directories)) - .anyMatch(linked -> linked.filter(path -> { - try { - return Files.isSameFile(path, foundFile); - } catch (IOException e) { - LOGGER.debug("Unable to check file identity, assuming no identity", e); - return false; - } - }).isPresent()); + boolean fileAlreadyLinked = entry.getFiles() + .stream() + .map(file -> file.findIn(directories)) + .anyMatch(linked -> linked.filter(path -> { + try { + return Files.isSameFile(path, foundFile); + } + catch (IOException e) { + LOGGER.debug("Unable to check file identity, assuming no identity", e); + return false; + } + }).isPresent()); if (!fileAlreadyLinked) { Optional type = FileUtil.getFileExtension(foundFile) - .map(extension -> ExternalFileTypes.getExternalFileTypeByExt(extension, externalApplicationsPreferences)) - .orElse(Optional.of(new UnknownExternalFileType(""))); + .map(extension -> ExternalFileTypes.getExternalFileTypeByExt(extension, + externalApplicationsPreferences)) + .orElse(Optional.of(new UnknownExternalFileType(""))); String strType = type.map(ExternalFileType::getName).orElse(""); Path relativeFilePath = FileUtil.relativize(foundFile, directories); @@ -158,4 +172,5 @@ private List findByBrokenLinkName(BibEntry entry) throws IOException { return matches; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/ChainedFilters.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/ChainedFilters.java index 8e6f2cba15e..0ef1765454e 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/ChainedFilters.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/ChainedFilters.java @@ -18,7 +18,8 @@ public boolean accept(Path entry) throws IOException { return filters.stream().allMatch(filter -> { try { return filter.accept(entry); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Could not apply filter", e); return true; } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java index 48d321b1d81..41178a8dadf 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java @@ -30,18 +30,20 @@ public class DownloadFullTextAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(DownloadFullTextAction.class); + // The minimum number of selected entries to ask the user for confirmation private static final int WARNING_LIMIT = 5; private final DialogService dialogService; + private final StateManager stateManager; + private final GuiPreferences preferences; + private final UiTaskExecutor taskExecutor; - public DownloadFullTextAction(DialogService dialogService, - StateManager stateManager, - GuiPreferences preferences, - UiTaskExecutor taskExecutor) { + public DownloadFullTextAction(DialogService dialogService, StateManager stateManager, GuiPreferences preferences, + UiTaskExecutor taskExecutor) { this.dialogService = dialogService; this.stateManager = stateManager; this.preferences = preferences; @@ -65,14 +67,12 @@ public void execute() { dialogService.notify(Localization.lang("Looking for full text document...")); if (entries.size() >= WARNING_LIMIT) { - boolean confirmDownload = dialogService.showConfirmationDialogAndWait( - Localization.lang("Download full text documents"), - Localization.lang( - "You are attempting to download full text documents for %0 entries.\nJabRef will send at least one request per entry to a publisher.", - String.valueOf(stateManager.getSelectedEntries().size())), - // [impl->req~ui.dialogs.confirmation.naming~1] - Localization.lang("Download full text documents"), - Localization.lang("Cancel")); + boolean confirmDownload = dialogService + .showConfirmationDialogAndWait(Localization.lang("Download full text documents"), Localization.lang( + "You are attempting to download full text documents for %0 entries.\nJabRef will send at least one request per entry to a publisher.", + String.valueOf(stateManager.getSelectedEntries().size())), + // [impl->req~ui.dialogs.confirmation.naming~1] + Localization.lang("Download full text documents"), Localization.lang("Cancel")); if (!confirmDownload) { dialogService.notify(Localization.lang("Operation canceled.")); @@ -86,8 +86,7 @@ protected Map> call() { Map> downloads = new ConcurrentHashMap<>(); int count = 0; for (BibEntry entry : entries) { - FulltextFetchers fetchers = new FulltextFetchers( - preferences.getImportFormatPreferences(), + FulltextFetchers fetchers = new FulltextFetchers(preferences.getImportFormatPreferences(), preferences.getImporterPreferences()); downloads.put(entry, fetchers.findFullTextPDF(entry)); updateProgress(++count, entries.size()); @@ -96,13 +95,11 @@ protected Map> call() { } }; - findFullTextsTask.setOnSucceeded(value -> - downloadFullTexts(findFullTextsTask.getValue(), stateManager.getActiveDatabase().get())); + findFullTextsTask.setOnSucceeded( + value -> downloadFullTexts(findFullTextsTask.getValue(), stateManager.getActiveDatabase().get())); - dialogService.showProgressDialog( - Localization.lang("Download full text documents"), - Localization.lang("Looking for full text document..."), - findFullTextsTask); + dialogService.showProgressDialog(Localization.lang("Download full text documents"), + Localization.lang("Looking for full text document..."), findFullTextsTask); taskExecutor.execute(findFullTextsTask); } @@ -113,7 +110,8 @@ private void downloadFullTexts(Map> downloads, BibDataba Optional result = download.getValue(); if (result.isPresent()) { addLinkedFileFromURL(databaseContext, result.get(), entry); - } else { + } + else { dialogService.notify(Localization.lang("No full text document found for entry %0.", entry.getCitationKey().orElse(Localization.lang("undefined")))); } @@ -121,28 +119,25 @@ private void downloadFullTexts(Map> downloads, BibDataba } /** - * This method attaches a linked file from a URL (if not already linked) to an entry using the key and value pair - * from the findFullTexts map and then downloads the file into the given targetDirectory - * + * This method attaches a linked file from a URL (if not already linked) to an entry + * using the key and value pair from the findFullTexts map and then downloads the file + * into the given targetDirectory * @param databaseContext the active database - * @param url the url "key" - * @param entry the entry "value" + * @param url the url "key" + * @param entry the entry "value" */ private void addLinkedFileFromURL(BibDatabaseContext databaseContext, URL url, BibEntry entry) { LinkedFile newLinkedFile = new LinkedFile(url, ""); if (!entry.getFiles().contains(newLinkedFile)) { - LinkedFileViewModel onlineFile = new LinkedFileViewModel( - newLinkedFile, - entry, - databaseContext, - taskExecutor, - dialogService, - preferences); + LinkedFileViewModel onlineFile = new LinkedFileViewModel(newLinkedFile, entry, databaseContext, + taskExecutor, dialogService, preferences); onlineFile.download(true); - } else { + } + else { dialogService.notify(Localization.lang("Full text document for entry %0 already linked.", entry.getCitationKey().orElse(Localization.lang("undefined")))); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/DuplicateDecisionResult.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/DuplicateDecisionResult.java index 8bfc842297d..8bdc6882268 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/DuplicateDecisionResult.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/DuplicateDecisionResult.java @@ -3,9 +3,5 @@ import org.jabref.gui.duplicationFinder.DuplicateResolverDialog; import org.jabref.model.entry.BibEntry; -public record DuplicateDecisionResult( - DuplicateResolverDialog.DuplicateResolverResult decision, - BibEntry mergedEntry) { +public record DuplicateDecisionResult(DuplicateResolverDialog.DuplicateResolverResult decision, BibEntry mergedEntry) { } - - diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/EntryImportHandlerTracker.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/EntryImportHandlerTracker.java index 3bdf566e9f0..924c5a96e91 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/EntryImportHandlerTracker.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/EntryImportHandlerTracker.java @@ -3,10 +3,13 @@ import java.util.concurrent.atomic.AtomicInteger; public class EntryImportHandlerTracker { + private final AtomicInteger imported = new AtomicInteger(0); + private final AtomicInteger skipped = new AtomicInteger(0); private final int totalEntries; + private Runnable onFinish; public EntryImportHandlerTracker() { @@ -44,4 +47,5 @@ public int getImportedCount() { public int getSkippedCount() { return skipped.get(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java index a156472dcbf..9c85099262b 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java @@ -28,14 +28,18 @@ public class ExternalFilesEntryLinker { private static final Logger LOGGER = LoggerFactory.getLogger(ExternalFilesEntryLinker.class); private final ExternalApplicationsPreferences externalApplicationsPreferences; + private final FilePreferences filePreferences; + private final NotificationService notificationService; + private final Supplier bibDatabaseContextSupplier; /** * @param stateManager required for currently active BibDatabaseContext */ - public ExternalFilesEntryLinker(ExternalApplicationsPreferences externalApplicationsPreferences, FilePreferences filePreferences, NotificationService notificationService, StateManager stateManager) { + public ExternalFilesEntryLinker(ExternalApplicationsPreferences externalApplicationsPreferences, + FilePreferences filePreferences, NotificationService notificationService, StateManager stateManager) { this.externalApplicationsPreferences = externalApplicationsPreferences; this.filePreferences = filePreferences; this.bibDatabaseContextSupplier = () -> stateManager.getActiveDatabase().orElse(new BibDatabaseContext()); @@ -46,17 +50,21 @@ public void linkFilesToEntry(BibEntry entry, List files) { List existingFiles = entry.getFiles(); List linkedFiles = files.stream().flatMap(file -> { String typeName = FileUtil.getFileExtension(file) - .map(ext -> ExternalFileTypes.getExternalFileTypeByExt(ext, externalApplicationsPreferences).orElse(new UnknownExternalFileType(ext)).getName()) - .orElse(""); + .map(ext -> ExternalFileTypes.getExternalFileTypeByExt(ext, externalApplicationsPreferences) + .orElse(new UnknownExternalFileType(ext)) + .getName()) + .orElse(""); Path relativePath = FileUtil.relativize(file, bibDatabaseContextSupplier.get(), filePreferences); LinkedFile linkedFile = new LinkedFile("", relativePath, typeName); String link = linkedFile.getLink(); - boolean alreadyLinked = existingFiles.stream().anyMatch(existingFile -> existingFile.getLink().equals(link)); + boolean alreadyLinked = existingFiles.stream() + .anyMatch(existingFile -> existingFile.getLink().equals(link)); if (alreadyLinked) { notificationService.notify(Localization.lang("File '%0' already linked", link)); return Stream.empty(); - } else { + } + else { return Stream.of(linkedFile); } }).toList(); @@ -65,10 +73,11 @@ public void linkFilesToEntry(BibEntry entry, List files) { /** *
      - *
    • Move files to file directory
    • - *
    • Use configured file directory pattern
    • - *
    • Rename file to configured pattern (and skip renaming if file already exists)
    • - *
    • Avoid overwriting files - by adding " {number}" after the file name
    • + *
    • Move files to file directory
    • + *
    • Use configured file directory pattern
    • + *
    • Rename file to configured pattern (and skip renaming if file already + * exists)
    • + *
    • Avoid overwriting files - by adding " {number}" after the file name
    • *
    */ public void coveOrMoveFilesSteps(BibEntry entry, List files, boolean shouldMove) { @@ -77,24 +86,31 @@ public void coveOrMoveFilesSteps(BibEntry entry, List files, boolean shoul // "old school" loop to enable logging properly for (Path file : files) { String typeName = FileUtil.getFileExtension(file) - .map(ext -> ExternalFileTypes.getExternalFileTypeByExt(ext, externalApplicationsPreferences).orElse(new UnknownExternalFileType(ext)).getName()) - .orElse(""); + .map(ext -> ExternalFileTypes.getExternalFileTypeByExt(ext, externalApplicationsPreferences) + .orElse(new UnknownExternalFileType(ext)) + .getName()) + .orElse(""); LinkedFile linkedFile = new LinkedFile("", file, typeName); - LinkedFileHandler linkedFileHandler = new LinkedFileHandler(linkedFile, entry, bibDatabaseContextSupplier.get(), filePreferences); + LinkedFileHandler linkedFileHandler = new LinkedFileHandler(linkedFile, entry, + bibDatabaseContextSupplier.get(), filePreferences); try { linkedFileHandler.copyOrMoveToDefaultDirectory(shouldMove, true); - } catch (IOException exception) { + } + catch (IOException exception) { LOGGER.error("Error while copying/moving file {}", file, exception); } String link = linkedFile.getLink(); - boolean alreadyLinked = existingFiles.stream().anyMatch(existingFile -> existingFile.getLink().equals(link)); + boolean alreadyLinked = existingFiles.stream() + .anyMatch(existingFile -> existingFile.getLink().equals(link)); if (alreadyLinked) { notificationService.notify(Localization.lang("File '%0' already linked", link)); - } else { + } + else { linkedFiles.add(linkedFile); } } entry.addFiles(linkedFiles); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/FileExtensionViewModel.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/FileExtensionViewModel.java index 28a4f284c9e..ea44e9bc4ac 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/FileExtensionViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/FileExtensionViewModel.java @@ -16,8 +16,11 @@ public class FileExtensionViewModel { private final String name; + private final String description; + private final List extensions; + private final ExternalApplicationsPreferences externalApplicationsPreferences; FileExtensionViewModel(FileType fileType, ExternalApplicationsPreferences externalApplicationsPreferences) { @@ -37,11 +40,12 @@ public String getDescription() { public JabRefIcon getIcon() { return ExternalFileTypes.getExternalFileTypeByExt(extensions.getFirst(), externalApplicationsPreferences) - .map(ExternalFileType::getIcon) - .orElse(null); + .map(ExternalFileType::getIcon) + .orElse(null); } public Filter dirFilter() { return FileFilterConverter.toDirFilter(extensions); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/FileFilterUtils.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/FileFilterUtils.java index 22f2aee57f5..6efec2e0a0c 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/FileFilterUtils.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/FileFilterUtils.java @@ -25,46 +25,50 @@ public static LocalDateTime getFileTime(Path path) { FileTime lastEditedTime; try { lastEditedTime = Files.getLastModifiedTime(path); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Could not retrieve file time", e); return LocalDateTime.now(); } - LocalDateTime localDateTime = lastEditedTime - .toInstant() - .atZone(ZoneId.systemDefault()) - .toLocalDateTime(); + LocalDateTime localDateTime = lastEditedTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); return localDateTime; } - /* Returns true if a file with a specific path - * was edited during the last 24 hours. */ + /* + * Returns true if a file with a specific path was edited during the last 24 hours. + */ public boolean isDuringLastDay(LocalDateTime fileEditTime) { LocalDateTime NOW = LocalDateTime.now(ZoneId.systemDefault()); return fileEditTime.isAfter(NOW.minusHours(24)); } - /* Returns true if a file with a specific path - * was edited during the last 7 days. */ + /* + * Returns true if a file with a specific path was edited during the last 7 days. + */ public boolean isDuringLastWeek(LocalDateTime fileEditTime) { LocalDateTime NOW = LocalDateTime.now(ZoneId.systemDefault()); return fileEditTime.isAfter(NOW.minusDays(7)); } - /* Returns true if a file with a specific path - * was edited during the last 30 days. */ + /* + * Returns true if a file with a specific path was edited during the last 30 days. + */ public boolean isDuringLastMonth(LocalDateTime fileEditTime) { LocalDateTime NOW = LocalDateTime.now(ZoneId.systemDefault()); return fileEditTime.isAfter(NOW.minusDays(30)); } - /* Returns true if a file with a specific path - * was edited during the last 365 days. */ + /* + * Returns true if a file with a specific path was edited during the last 365 days. + */ public boolean isDuringLastYear(LocalDateTime fileEditTime) { LocalDateTime NOW = LocalDateTime.now(ZoneId.systemDefault()); return fileEditTime.isAfter(NOW.minusDays(365)); } - /* Returns true if a file is edited in the time margin specified by the given filter. */ + /* + * Returns true if a file is edited in the time margin specified by the given filter. + */ public static boolean filterByDate(Path path, DateRange filter) { FileFilterUtils fileFilter = new FileFilterUtils(); LocalDateTime fileTime = FileFilterUtils.getFileTime(path); @@ -79,34 +83,34 @@ public static boolean filterByDate(Path path, DateRange filter) { } /** - * Sorts a list of Path objects according to the last edited date - * of their corresponding files, from newest to oldest. + * Sorts a list of Path objects according to the last edited date of their + * corresponding files, from newest to oldest. */ public List sortByDateAscending(List files) { return files.stream() - .sorted(Comparator.comparingLong(file -> FileFilterUtils.getFileTime(file) - .atZone(ZoneId.systemDefault()) - .toInstant() - .toEpochMilli())) - .collect(Collectors.toList()); + .sorted(Comparator.comparingLong(file -> FileFilterUtils.getFileTime(file) + .atZone(ZoneId.systemDefault()) + .toInstant() + .toEpochMilli())) + .collect(Collectors.toList()); } /** - * Sorts a list of Path objects according to the last edited date - * of their corresponding files, from oldest to newest. + * Sorts a list of Path objects according to the last edited date of their + * corresponding files, from oldest to newest. */ public List sortByDateDescending(List files) { return files.stream() - .sorted(Comparator.comparingLong(file -> -FileFilterUtils.getFileTime(file) - .atZone(ZoneId.systemDefault()) - .toInstant() - .toEpochMilli())) - .collect(Collectors.toList()); + .sorted(Comparator.comparingLong(file -> -FileFilterUtils.getFileTime(file) + .atZone(ZoneId.systemDefault()) + .toInstant() + .toEpochMilli())) + .collect(Collectors.toList()); } /** - * Sorts a list of Path objects according to the last edited date - * the order depends on the specified sorter type. + * Sorts a list of Path objects according to the last edited date the order depends on + * the specified sorter type. */ public static List sortByDate(List files, ExternalFileSorter sortType) { FileFilterUtils fileFilter = new FileFilterUtils(); @@ -117,5 +121,5 @@ public static List sortByDate(List files, ExternalFileSorter sortTyp }; return sortedFiles; } -} +} diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/FindUnlinkedFilesAction.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/FindUnlinkedFilesAction.java index e31c5fd1a2f..b65fe487a72 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/FindUnlinkedFilesAction.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/FindUnlinkedFilesAction.java @@ -9,6 +9,7 @@ public class FindUnlinkedFilesAction extends SimpleCommand { private final DialogService dialogService; + private final StateManager stateManager; public FindUnlinkedFilesAction(DialogService dialogService, StateManager stateManager) { @@ -22,4 +23,5 @@ public FindUnlinkedFilesAction(DialogService dialogService, StateManager stateMa public void execute() { dialogService.showCustomDialogAndWait(new UnlinkedFilesDialogView()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/GitIgnoreFileFilter.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/GitIgnoreFileFilter.java index e396c4f883a..e1c57dd4247 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/GitIgnoreFileFilter.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/GitIgnoreFileFilter.java @@ -28,26 +28,30 @@ public GitIgnoreFileFilter(Path path) { } if (currentPath == null) { // we did not find any gitignore, lets use the default - gitIgnorePatterns = Set.of(".git", ".DS_Store", "desktop.ini", "Thumbs.db").stream() - // duplicate code as below - .map(line -> "glob:" + line) - .map(matcherString -> FileSystems.getDefault().getPathMatcher(matcherString)) - .collect(Collectors.toSet()); - } else { + gitIgnorePatterns = Set.of(".git", ".DS_Store", "desktop.ini", "Thumbs.db") + .stream() + // duplicate code as below + .map(line -> "glob:" + line) + .map(matcherString -> FileSystems.getDefault().getPathMatcher(matcherString)) + .collect(Collectors.toSet()); + } + else { Path gitIgnore = currentPath.resolve(".gitignore"); try { - Set plainGitIgnorePatternsFromGitIgnoreFile = Files.readAllLines(gitIgnore).stream() - .map(String::trim) - .filter(not(String::isEmpty)) - .filter(line -> !line.startsWith("#")) - // convert to Java syntax for Glob patterns - .map(line -> "glob:" + line) - .map(matcherString -> FileSystems.getDefault().getPathMatcher(matcherString)) - .collect(Collectors.toSet()); + Set plainGitIgnorePatternsFromGitIgnoreFile = Files.readAllLines(gitIgnore) + .stream() + .map(String::trim) + .filter(not(String::isEmpty)) + .filter(line -> !line.startsWith("#")) + // convert to Java syntax for Glob patterns + .map(line -> "glob:" + line) + .map(matcherString -> FileSystems.getDefault().getPathMatcher(matcherString)) + .collect(Collectors.toSet()); gitIgnorePatterns = new HashSet<>(plainGitIgnorePatternsFromGitIgnoreFile); // we want to ignore ".gitignore" itself gitIgnorePatterns.add(FileSystems.getDefault().getPathMatcher("glob:.gitignore")); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.info("Could not read .gitignore from {}", gitIgnore, e); gitIgnorePatterns = Set.of(); } @@ -56,11 +60,13 @@ public GitIgnoreFileFilter(Path path) { @Override public boolean accept(Path path) throws IOException { - // We assume that git does not stop at a patern, but tries all. We implement that behavior + // We assume that git does not stop at a patern, but tries all. We implement that + // behavior return gitIgnorePatterns.stream().noneMatch(filter -> - // we need this one for "*.png" - filter.matches(path.getFileName()) || - // we need this one for "**/*.png" + // we need this one for "*.png" + filter.matches(path.getFileName()) || + // we need this one for "**/*.png" filter.matches(path)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/ImportFilesResultItemViewModel.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/ImportFilesResultItemViewModel.java index 3677107b427..a79a54504d0 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/ImportFilesResultItemViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/ImportFilesResultItemViewModel.java @@ -14,7 +14,9 @@ public class ImportFilesResultItemViewModel { private final StringProperty file = new SimpleStringProperty(""); + private final ObjectProperty icon = new SimpleObjectProperty<>(IconTheme.JabRefIcons.WARNING); + private final StringProperty message = new SimpleStringProperty(""); public ImportFilesResultItemViewModel(Path file, boolean success, String message) { @@ -22,7 +24,8 @@ public ImportFilesResultItemViewModel(Path file, boolean success, String message this.message.setValue(message); if (success) { this.icon.setValue(IconTheme.JabRefIcons.CHECK.withColor(Color.GREEN)); - } else { + } + else { this.icon.setValue(IconTheme.JabRefIcons.WARNING.withColor(Color.RED)); } } @@ -43,4 +46,5 @@ public StringProperty message() { public String toString() { return "ImportFilesResultItemViewModel [file=" + file.get() + ", message=" + message.get() + "]"; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java index bb201b85f18..be34ff24d8f 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java @@ -70,23 +70,28 @@ public class ImportHandler { private static final Logger LOGGER = LoggerFactory.getLogger(ImportHandler.class); + private final BibDatabaseContext bibDatabaseContext; + private final GuiPreferences preferences; + private final FileUpdateMonitor fileUpdateMonitor; + private final ExternalFilesEntryLinker fileLinker; + private final ExternalFilesContentImporter contentImporter; + private final UndoManager undoManager; + private final StateManager stateManager; + private final DialogService dialogService; + private final TaskExecutor taskExecutor; - public ImportHandler(BibDatabaseContext database, - GuiPreferences preferences, - FileUpdateMonitor fileupdateMonitor, - UndoManager undoManager, - StateManager stateManager, - DialogService dialogService, - TaskExecutor taskExecutor) { + public ImportHandler(BibDatabaseContext database, GuiPreferences preferences, FileUpdateMonitor fileupdateMonitor, + UndoManager undoManager, StateManager stateManager, DialogService dialogService, + TaskExecutor taskExecutor) { this.bibDatabaseContext = database; this.preferences = preferences; @@ -95,7 +100,8 @@ public ImportHandler(BibDatabaseContext database, this.dialogService = dialogService; this.taskExecutor = taskExecutor; - this.fileLinker = new ExternalFilesEntryLinker(preferences.getExternalApplicationsPreferences(), preferences.getFilePreferences(), dialogService, stateManager); + this.fileLinker = new ExternalFilesEntryLinker(preferences.getExternalApplicationsPreferences(), + preferences.getFilePreferences(), dialogService, stateManager); this.contentImporter = new ExternalFilesContentImporter(preferences.getImportFormatPreferences()); this.undoManager = undoManager; } @@ -104,11 +110,16 @@ public ExternalFilesEntryLinker getFileLinker() { return fileLinker; } - public BackgroundTask> importFilesInBackground(final List files, final BibDatabaseContext bibDatabaseContext, final FilePreferences filePreferences, TransferMode transferMode) { - // TODO: Make a utility class out of this. Package: org.jabref.logic.externalfiles. + public BackgroundTask> importFilesInBackground(final List files, + final BibDatabaseContext bibDatabaseContext, final FilePreferences filePreferences, + TransferMode transferMode) { + // TODO: Make a utility class out of this. Package: + // org.jabref.logic.externalfiles. return new BackgroundTask<>() { private int counter; + private final List results = new ArrayList<>(); + private final List allEntriesToAdd = new ArrayList<>(); @Override @@ -123,11 +134,14 @@ public List call() { } UiTaskExecutor.runInJavaFXThread(() -> { - setTitle(Localization.lang("Importing files into %1 | %2 of %0 file(s) processed.", - files.size(), - bibDatabaseContext.getDatabasePath().map(path -> path.getFileName().toString()).orElse(Localization.lang("untitled")), - counter)); - updateMessage(Localization.lang("Processing %0", FileUtil.shortenFileName(file.getFileName().toString(), 68))); + setTitle( + Localization.lang("Importing files into %1 | %2 of %0 file(s) processed.", files.size(), + bibDatabaseContext.getDatabasePath() + .map(path -> path.getFileName().toString()) + .orElse(Localization.lang("untitled")), + counter)); + updateMessage(Localization.lang("Processing %0", + FileUtil.shortenFileName(file.getFileName().toString(), 68))); updateProgress(counter, files.size()); showToUser(true); }); @@ -140,21 +154,27 @@ public List call() { if (files.size() == 1) { pdfEntriesInFile = new ArrayList<>(1); UiTaskExecutor.runAndWaitInJavaFXThread(() -> { - MultiMergeEntriesView dialog = PdfMergeDialog.createMergeDialog(file, preferences, taskExecutor); + MultiMergeEntriesView dialog = PdfMergeDialog.createMergeDialog(file, preferences, + taskExecutor); dialogService.showCustomDialogAndWait(dialog).ifPresent(pdfEntriesInFile::add); }); - } else { - ParserResult pdfImporterResult = contentImporter.importPDFContent(file, bibDatabaseContext, filePreferences); + } + else { + ParserResult pdfImporterResult = contentImporter.importPDFContent(file, + bibDatabaseContext, filePreferences); pdfEntriesInFile = pdfImporterResult.getDatabase().getEntries(); if (pdfImporterResult.hasWarnings()) { - addResultToList(file, false, Localization.lang("Error reading PDF content: %0", pdfImporterResult.getErrorMessage())); + addResultToList(file, false, Localization.lang("Error reading PDF content: %0", + pdfImporterResult.getErrorMessage())); } } if (pdfEntriesInFile.isEmpty()) { entriesToAdd.add(createEmptyEntryWithLink(file)); - addResultToList(file, false, Localization.lang("No BibTeX was found. An empty entry was created with file link.")); - } else { + addResultToList(file, false, Localization + .lang("No BibTeX was found. An empty entry was created with file link.")); + } + else { generateKeys(pdfEntriesInFile); pdfEntriesInFile.forEach(entry -> { if (entry.getFiles().size() > 1) { @@ -162,34 +182,44 @@ public List call() { LOGGER.warn("Entry's files: {}", entry.getFiles()); } entry.clearField(StandardField.FILE); - // Modifiers do not work on macOS: https://bugs.openjdk.org/browse/JDK-8264172 - // Similar code as org.jabref.gui.preview.PreviewPanel.PreviewPanel + // Modifiers do not work on macOS: + // https://bugs.openjdk.org/browse/JDK-8264172 + // Similar code as + // org.jabref.gui.preview.PreviewPanel.PreviewPanel DragDrop.handleDropOfFiles(List.of(file), transferMode, fileLinker, entry); addToImportEntriesGroup(pdfEntriesInFile); entriesToAdd.addAll(pdfEntriesInFile); - addResultToList(file, true, Localization.lang("File was successfully imported as a new entry")); + addResultToList(file, true, + Localization.lang("File was successfully imported as a new entry")); }); } - } else if (FileUtil.isBibFile(file)) { - ParserResult bibtexParserResult = contentImporter.importFromBibFile(file, fileUpdateMonitor); + } + else if (FileUtil.isBibFile(file)) { + ParserResult bibtexParserResult = contentImporter.importFromBibFile(file, + fileUpdateMonitor); List entries = bibtexParserResult.getDatabaseContext().getEntries(); entriesToAdd.addAll(entries); boolean success = !bibtexParserResult.hasWarnings(); String message; if (success) { message = Localization.lang("Bib entry was successfully imported"); - } else { + } + else { message = bibtexParserResult.getErrorMessage(); } addResultToList(file, success, message); - } else { + } + else { BibEntry emptyEntryWithLink = createEmptyEntryWithLink(file); entriesToAdd.add(emptyEntryWithLink); - addResultToList(file, false, Localization.lang("No BibTeX data was found. An empty entry was created with file link.")); + addResultToList(file, false, Localization + .lang("No BibTeX data was found. An empty entry was created with file link.")); } - } catch (IOException ex) { + } + catch (IOException ex) { LOGGER.error("Error importing", ex); - addResultToList(file, false, Localization.lang("Error from import: %0", ex.getLocalizedMessage())); + addResultToList(file, false, + Localization.lang("Error from import: %0", ex.getLocalizedMessage())); UiTaskExecutor.runInJavaFXThread(() -> updateMessage(Localization.lang("Error"))); } @@ -202,14 +232,16 @@ public List call() { counter++; } - // We need to run the actual import on the FX Thread, otherwise we will get some deadlocks with the UIThreadList + // We need to run the actual import on the FX Thread, otherwise we will + // get some deadlocks with the UIThreadList // That method does a clone() on each entry UiTaskExecutor.runInJavaFXThread(() -> importEntries(allEntriesToAdd)); return results; } private void addResultToList(Path newFile, boolean success, String logMessage) { - ImportFilesResultItemViewModel result = new ImportFilesResultItemViewModel(newFile, success, logMessage); + ImportFilesResultItemViewModel result = new ImportFilesResultItemViewModel(newFile, success, + logMessage); results.add(result); } }; @@ -223,11 +255,12 @@ private BibEntry createEmptyEntryWithLink(Path file) { } /** - * Cleans up the given entries and adds them to the library. - * There is no automatic download done. + * Cleans up the given entries and adds them to the library. There is no automatic + * download done. */ public void importEntries(List entries) { - ImportCleanup cleanup = ImportCleanup.targeting(bibDatabaseContext.getMode(), preferences.getFieldPreferences()); + ImportCleanup cleanup = ImportCleanup.targeting(bibDatabaseContext.getMode(), + preferences.getFieldPreferences()); cleanup.doPostCleanup(entries); importCleanedEntries(entries); } @@ -250,45 +283,47 @@ public void importEntryWithDuplicateCheck(BibDatabaseContext bibDatabaseContext, importEntryWithDuplicateCheck(bibDatabaseContext, entry, BREAK, new EntryImportHandlerTracker()); } - private void importEntryWithDuplicateCheck(BibDatabaseContext bibDatabaseContext, BibEntry entry, DuplicateResolverDialog.DuplicateResolverResult decision, EntryImportHandlerTracker tracker) { + private void importEntryWithDuplicateCheck(BibDatabaseContext bibDatabaseContext, BibEntry entry, + DuplicateResolverDialog.DuplicateResolverResult decision, EntryImportHandlerTracker tracker) { BibEntry entryToInsert = cleanUpEntry(bibDatabaseContext, entry); - BackgroundTask.wrap(() -> findDuplicate(bibDatabaseContext, entryToInsert)) - .onFailure(e -> { - tracker.markSkipped(); - LOGGER.error("Error in duplicate search", e); - }) - .onSuccess(existingDuplicateInLibrary -> { - BibEntry finalEntry = entryToInsert; - if (existingDuplicateInLibrary.isPresent()) { - Optional duplicateHandledEntry = handleDuplicates(bibDatabaseContext, entryToInsert, existingDuplicateInLibrary.get(), decision); - if (duplicateHandledEntry.isEmpty()) { - tracker.markSkipped(); - return; - } - finalEntry = duplicateHandledEntry.get(); - } - importCleanedEntries(bibDatabaseContext, List.of(finalEntry)); - addToImportEntriesGroup(List.of(finalEntry)); - downloadLinkedFiles(finalEntry); - BibEntry entryToFocus = finalEntry; - stateManager.activeTabProperty().get().ifPresent(tab -> tab.clearAndSelect(entryToFocus)); - tracker.markImported(); - }).executeWith(taskExecutor); + BackgroundTask.wrap(() -> findDuplicate(bibDatabaseContext, entryToInsert)).onFailure(e -> { + tracker.markSkipped(); + LOGGER.error("Error in duplicate search", e); + }).onSuccess(existingDuplicateInLibrary -> { + BibEntry finalEntry = entryToInsert; + if (existingDuplicateInLibrary.isPresent()) { + Optional duplicateHandledEntry = handleDuplicates(bibDatabaseContext, entryToInsert, + existingDuplicateInLibrary.get(), decision); + if (duplicateHandledEntry.isEmpty()) { + tracker.markSkipped(); + return; + } + finalEntry = duplicateHandledEntry.get(); + } + importCleanedEntries(bibDatabaseContext, List.of(finalEntry)); + addToImportEntriesGroup(List.of(finalEntry)); + downloadLinkedFiles(finalEntry); + BibEntry entryToFocus = finalEntry; + stateManager.activeTabProperty().get().ifPresent(tab -> tab.clearAndSelect(entryToFocus)); + tracker.markImported(); + }).executeWith(taskExecutor); } @VisibleForTesting BibEntry cleanUpEntry(BibDatabaseContext bibDatabaseContext, BibEntry entry) { - ImportCleanup cleanup = ImportCleanup.targeting(bibDatabaseContext.getMode(), preferences.getFieldPreferences()); + ImportCleanup cleanup = ImportCleanup.targeting(bibDatabaseContext.getMode(), + preferences.getFieldPreferences()); return cleanup.doPostCleanup(entry); } public Optional findDuplicate(BibDatabaseContext bibDatabaseContext, BibEntry entryToCheck) { return new DuplicateCheck(Injector.instantiateModelOrService(BibEntryTypesManager.class)) - .containsDuplicate(bibDatabaseContext.getDatabase(), entryToCheck, bibDatabaseContext.getMode()); + .containsDuplicate(bibDatabaseContext.getDatabase(), entryToCheck, bibDatabaseContext.getMode()); } - public Optional handleDuplicates(BibDatabaseContext bibDatabaseContext, BibEntry originalEntry, BibEntry duplicateEntry, DuplicateResolverDialog.DuplicateResolverResult decision) { + public Optional handleDuplicates(BibDatabaseContext bibDatabaseContext, BibEntry originalEntry, + BibEntry duplicateEntry, DuplicateResolverDialog.DuplicateResolverResult decision) { DuplicateDecisionResult decisionResult = getDuplicateDecision(originalEntry, duplicateEntry, decision); switch (decisionResult.decision()) { case KEEP_RIGHT: @@ -308,8 +343,10 @@ public Optional handleDuplicates(BibDatabaseContext bibDatabaseContext return Optional.of(originalEntry); } - public DuplicateDecisionResult getDuplicateDecision(BibEntry originalEntry, BibEntry duplicateEntry, DuplicateResolverDialog.DuplicateResolverResult decision) { - DuplicateResolverDialog dialog = new DuplicateResolverDialog(duplicateEntry, originalEntry, DuplicateResolverDialog.DuplicateResolverType.IMPORT_CHECK, stateManager, dialogService, preferences); + public DuplicateDecisionResult getDuplicateDecision(BibEntry originalEntry, BibEntry duplicateEntry, + DuplicateResolverDialog.DuplicateResolverResult decision) { + DuplicateResolverDialog dialog = new DuplicateResolverDialog(duplicateEntry, originalEntry, + DuplicateResolverDialog.DuplicateResolverType.IMPORT_CHECK, stateManager, dialogService, preferences); if (decision == BREAK) { decision = dialogService.showCustomDialogAndWait(dialog).orElse(BREAK); } @@ -320,27 +357,18 @@ public DuplicateDecisionResult getDuplicateDecision(BibEntry originalEntry, BibE } public void setAutomaticFields(List entries) { - UpdateField.setAutomaticFields( - entries, - preferences.getOwnerPreferences(), - preferences.getTimestampPreferences() - ); + UpdateField.setAutomaticFields(entries, preferences.getOwnerPreferences(), + preferences.getTimestampPreferences()); } public void downloadLinkedFiles(BibEntry entry) { if (preferences.getFilePreferences().shouldDownloadLinkedFiles()) { - entry.getFiles().stream() - .filter(LinkedFile::isOnlineLink) - .forEach(linkedFile -> - new LinkedFileViewModel( - linkedFile, - entry, - bibDatabaseContext, - taskExecutor, - dialogService, - preferences - ).download(false) - ); + entry.getFiles() + .stream() + .filter(LinkedFile::isOnlineLink) + .forEach(linkedFile -> new LinkedFileViewModel(linkedFile, entry, bibDatabaseContext, taskExecutor, + dialogService, preferences) + .download(false)); } } @@ -350,8 +378,9 @@ private void addToGroups(List entries, Collection group List undo = entryChanger.add(entries); // TODO: Add undo // if (!undo.isEmpty()) { - // ce.addEdit(UndoableChangeEntriesOfGroup.getUndoableEdit(new GroupTreeNodeViewModel(node), - // undo)); + // ce.addEdit(UndoableChangeEntriesOfGroup.getUndoableEdit(new + // GroupTreeNodeViewModel(node), + // undo)); // } } } @@ -359,7 +388,6 @@ private void addToGroups(List entries, Collection group /** * Generate keys for given entries. - * * @param entries entries to generate keys for */ private void generateKeys(List entries) { @@ -367,21 +395,22 @@ private void generateKeys(List entries) { return; } CitationKeyGenerator keyGenerator = new CitationKeyGenerator( - bibDatabaseContext.getMetaData().getCiteKeyPatterns(preferences.getCitationKeyPatternPreferences() - .getKeyPatterns()), - bibDatabaseContext.getDatabase(), - preferences.getCitationKeyPatternPreferences()); + bibDatabaseContext.getMetaData() + .getCiteKeyPatterns(preferences.getCitationKeyPatternPreferences().getKeyPatterns()), + bibDatabaseContext.getDatabase(), preferences.getCitationKeyPatternPreferences()); entries.forEach(keyGenerator::generateAndSetKey); } public List handleBibTeXData(String entries) { BibtexParser parser = new BibtexParser(preferences.getImportFormatPreferences(), fileUpdateMonitor); try { - List result = parser.parseEntries(new ByteArrayInputStream(entries.getBytes(StandardCharsets.UTF_8))); + List result = parser + .parseEntries(new ByteArrayInputStream(entries.getBytes(StandardCharsets.UTF_8))); Collection stringConstants = parser.getStringValues(); importStringConstantsWithDuplicateCheck(stringConstants); return result; - } catch (ParseException ex) { + } + catch (ParseException ex) { LOGGER.error("Could not paste", ex); return List.of(); } @@ -392,18 +421,26 @@ public void importStringConstantsWithDuplicateCheck(Collection str for (BibtexString stringConstantToAdd : stringConstants) { try { - ConstantsItemModel checker = new ConstantsItemModel(stringConstantToAdd.getName(), stringConstantToAdd.getContent()); + ConstantsItemModel checker = new ConstantsItemModel(stringConstantToAdd.getName(), + stringConstantToAdd.getContent()); if (checker.combinedValidationValidProperty().get()) { bibDatabaseContext.getDatabase().addString(stringConstantToAdd); - } else { - failures.add(Localization.lang("String constant \"%0\" was not imported because it is not a valid string constant", stringConstantToAdd.getName())); } - } catch (KeyCollisionException ex) { - failures.add(Localization.lang("String constant %0 was not imported because it already exists in this library", stringConstantToAdd.getName())); + else { + failures.add(Localization.lang( + "String constant \"%0\" was not imported because it is not a valid string constant", + stringConstantToAdd.getName())); + } + } + catch (KeyCollisionException ex) { + failures.add(Localization.lang( + "String constant %0 was not imported because it already exists in this library", + stringConstantToAdd.getName())); } } if (!failures.isEmpty()) { - dialogService.showWarningDialogAndWait(Localization.lang("Importing String constants"), Localization.lang("Could not import the following string constants:\n %0", String.join("\n", failures))); + dialogService.showWarningDialogAndWait(Localization.lang("Importing String constants"), Localization + .lang("Could not import the following string constants:\n %0", String.join("\n", failures))); } } @@ -418,7 +455,8 @@ public List handleStringData(String data) throws FetcherException { if (FileUtil.isPDFFile(Path.of(fileName))) { try { return handlePdfUrl(data); - } catch (IOException ex) { + } + catch (IOException ex) { LOGGER.error("Could not handle PDF URL", ex); return List.of(); } @@ -436,15 +474,13 @@ public List handleStringData(String data) throws FetcherException { private List tryImportFormats(String data) { try { - ImportFormatReader importFormatReader = new ImportFormatReader( - preferences.getImporterPreferences(), - preferences.getImportFormatPreferences(), - preferences.getCitationKeyPatternPreferences(), - fileUpdateMonitor - ); + ImportFormatReader importFormatReader = new ImportFormatReader(preferences.getImporterPreferences(), + preferences.getImportFormatPreferences(), preferences.getCitationKeyPatternPreferences(), + fileUpdateMonitor); UnknownFormatImport unknownFormatImport = importFormatReader.importUnknownFormat(data); return unknownFormatImport.parserResult().getDatabase().getEntries(); - } catch (ImportException ex) { // ex is already localized + } + catch (ImportException ex) { // ex is already localized dialogService.showErrorDialogAndWait(Localization.lang("Import error"), ex); return List.of(); } @@ -454,7 +490,8 @@ public void importEntriesWithDuplicateCheck(BibDatabaseContext database, List entriesToAdd, EntryImportHandlerTracker tracker) { + public void importEntriesWithDuplicateCheck(BibDatabaseContext database, List entriesToAdd, + EntryImportHandlerTracker tracker) { boolean firstEntry = true; for (BibEntry entry : entriesToAdd) { if (firstEntry) { @@ -464,10 +501,12 @@ public void importEntriesWithDuplicateCheck(BibDatabaseContext database, List handlePdfUrl(String pdfUrl) throws IOException { Path targetFile = targetDirectory.get().resolve(filename); try { urlDownload.toFile(targetFile); - } catch (FetcherException fe) { + } + catch (FetcherException fe) { LOGGER.error("Error downloading PDF from URL", fe); return List.of(); } try { PdfMergeMetadataImporter importer = new PdfMergeMetadataImporter(preferences.getImportFormatPreferences()); - ParserResult parserResult = importer.importDatabase(targetFile, bibDatabaseContext, preferences.getFilePreferences()); + ParserResult parserResult = importer.importDatabase(targetFile, bibDatabaseContext, + preferences.getFilePreferences()); if (parserResult.hasWarnings()) { LOGGER.warn("PDF import had warnings: {}", parserResult.getErrorMessage()); } @@ -502,13 +543,15 @@ private List handlePdfUrl(String pdfUrl) throws IOException { entry.addFile(new LinkedFile("", targetFile, StandardFileType.PDF.getName())); } }); - } else { + } + else { BibEntry emptyEntry = new BibEntry(); emptyEntry.addFile(new LinkedFile("", targetFile, StandardFileType.PDF.getName())); entries.add(emptyEntry); } return entries; - } catch (IOException ex) { + } + catch (IOException ex) { LOGGER.error("Error importing PDF from URL - IO issue", ex); return List.of(); } @@ -518,12 +561,13 @@ private void addToImportEntriesGroup(List entriesToInsert) { if (preferences.getLibraryPreferences().isAddImportedEntriesEnabled()) { // Only one SmartGroup this.bibDatabaseContext.getMetaData() - .getGroups() - .flatMap(grp -> grp.getChildren() - .stream() - .filter(node -> node.getGroup() instanceof SmartGroup) - .findFirst()) - .ifPresent(smtGrp -> smtGrp.addEntriesToGroup(entriesToInsert)); + .getGroups() + .flatMap(grp -> grp.getChildren() + .stream() + .filter(node -> node.getGroup() instanceof SmartGroup) + .findFirst()) + .ifPresent(smtGrp -> smtGrp.addEntriesToGroup(entriesToInsert)); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/PdfMergeDialog.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/PdfMergeDialog.java index 6ea1b65568c..cb8e9ba6fb4 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/PdfMergeDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/PdfMergeDialog.java @@ -21,19 +21,24 @@ public class PdfMergeDialog { /** - * Constructs a merge dialog for a PDF file. This dialog calls various {@link PdfImporter}s, collects the results, and lets the user choose between them. + * Constructs a merge dialog for a PDF file. This dialog calls various + * {@link PdfImporter}s, collects the results, and lets the user choose between them. *

    - * {@link PdfImporter}s try to extract a {@link BibEntry} out of a PDF file, - * but it does not perform this 100% perfectly, it is only a set of heuristics that in some cases might work, in others not. - * Thus, JabRef provides this merge dialog that collects the results of all {@link PdfImporter}s - * and gives user a choice between field values. - * + * {@link PdfImporter}s try to extract a {@link BibEntry} out of a PDF file, but it + * does not perform this 100% perfectly, it is only a set of heuristics that in some + * cases might work, in others not. Thus, JabRef provides this merge dialog that + * collects the results of all {@link PdfImporter}s and gives user a choice between + * field values. * @param entry the entry to merge with - * @param filePath the path to the PDF file. This PDF is used as the source for the {@link PdfImporter}s. - * @param preferences the preferences to use. Full preference object is required, because of current implementation of {@link MultiMergeEntriesView}. - * @param taskExecutor the task executor to use when the multi merge dialog executes the importers. + * @param filePath the path to the PDF file. This PDF is used as the source for the + * {@link PdfImporter}s. + * @param preferences the preferences to use. Full preference object is required, + * because of current implementation of {@link MultiMergeEntriesView}. + * @param taskExecutor the task executor to use when the multi merge dialog executes + * the importers. */ - public static MultiMergeEntriesView createMergeDialog(BibEntry entry, Path filePath, GuiPreferences preferences, TaskExecutor taskExecutor) { + public static MultiMergeEntriesView createMergeDialog(BibEntry entry, Path filePath, GuiPreferences preferences, + TaskExecutor taskExecutor) { MultiMergeEntriesView dialog = initDialog(preferences, taskExecutor); dialog.addSource(Localization.lang("Entry"), entry); @@ -42,7 +47,8 @@ public static MultiMergeEntriesView createMergeDialog(BibEntry entry, Path fileP return dialog; } - public static MultiMergeEntriesView createMergeDialog(Path filePath, GuiPreferences preferences, TaskExecutor taskExecutor) { + public static MultiMergeEntriesView createMergeDialog(Path filePath, GuiPreferences preferences, + TaskExecutor taskExecutor) { MultiMergeEntriesView dialog = initDialog(preferences, taskExecutor); finishDialog(dialog, filePath, preferences); @@ -57,12 +63,16 @@ private static MultiMergeEntriesView initDialog(GuiPreferences preferences, Task } private static void finishDialog(MultiMergeEntriesView dialog, Path filePath, GuiPreferences preferences) { - dialog.addSource(Localization.lang("Verbatim"), wrapImporterToSupplier(new PdfVerbatimBibtexImporter(preferences.getImportFormatPreferences()), filePath)); - dialog.addSource(Localization.lang("Embedded"), wrapImporterToSupplier(new PdfEmbeddedBibFileImporter(preferences.getImportFormatPreferences()), filePath)); + dialog.addSource(Localization.lang("Verbatim"), wrapImporterToSupplier( + new PdfVerbatimBibtexImporter(preferences.getImportFormatPreferences()), filePath)); + dialog.addSource(Localization.lang("Embedded"), wrapImporterToSupplier( + new PdfEmbeddedBibFileImporter(preferences.getImportFormatPreferences()), filePath)); if (preferences.getGrobidPreferences().isGrobidEnabled()) { - dialog.addSource("Grobid", wrapImporterToSupplier(new PdfGrobidImporter(preferences.getImportFormatPreferences()), filePath)); + dialog.addSource("Grobid", + wrapImporterToSupplier(new PdfGrobidImporter(preferences.getImportFormatPreferences()), filePath)); } - dialog.addSource(Localization.lang("XMP metadata"), wrapImporterToSupplier(new PdfXmpImporter(preferences.getXmpPreferences()), filePath)); + dialog.addSource(Localization.lang("XMP metadata"), + wrapImporterToSupplier(new PdfXmpImporter(preferences.getXmpPreferences()), filePath)); dialog.addSource(Localization.lang("Content"), wrapImporterToSupplier(new PdfContentImporter(), filePath)); } @@ -74,9 +84,11 @@ private static Supplier wrapImporterToSupplier(Importer importer, Path return null; } return parserResult.getDatabase().getEntries().getFirst(); - } catch (IOException e) { + } + catch (IOException e) { return null; } }; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesCrawler.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesCrawler.java index 9e13a0173c6..9547ffc1ba0 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesCrawler.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesCrawler.java @@ -35,13 +35,19 @@ public class UnlinkedFilesCrawler extends BackgroundTask { private static final Logger LOGGER = LoggerFactory.getLogger(UnlinkedFilesCrawler.class); private final Path directory; + private final Filter fileFilter; + private final DateRange dateFilter; + private final ExternalFileSorter sorter; + private final BibDatabaseContext databaseContext; + private final FilePreferences filePreferences; - public UnlinkedFilesCrawler(Path directory, Filter fileFilter, DateRange dateFilter, ExternalFileSorter sorter, BibDatabaseContext databaseContext, FilePreferences filePreferences) { + public UnlinkedFilesCrawler(Path directory, Filter fileFilter, DateRange dateFilter, + ExternalFileSorter sorter, BibDatabaseContext databaseContext, FilePreferences filePreferences) { this.directory = directory; this.fileFilter = fileFilter; this.dateFilter = dateFilter; @@ -52,30 +58,33 @@ public UnlinkedFilesCrawler(Path directory, Filter fileFilter, DateRange d @Override public FileNodeViewModel call() throws IOException { - UnlinkedPDFFileFilter unlinkedPDFFileFilter = new UnlinkedPDFFileFilter(fileFilter, databaseContext, filePreferences); + UnlinkedPDFFileFilter unlinkedPDFFileFilter = new UnlinkedPDFFileFilter(fileFilter, databaseContext, + filePreferences); return searchDirectory(directory, unlinkedPDFFileFilter); } /** * Searches recursively all files in the specified directory.
    *
    - * All files matched by the given {@link UnlinkedPDFFileFilter} are taken into the resulting tree.
    + * All files matched by the given {@link UnlinkedPDFFileFilter} are taken into the + * resulting tree.
    *
    - * The result will be a tree structure of nodes of the type {@link CheckBoxTreeItem}.
    + * The result will be a tree structure of nodes of the type {@link CheckBoxTreeItem}. *
    - * The user objects that are attached to the nodes is the {@link FileNodeViewModel}, which wraps the {@link - * File}-Object.
    *
    - * For ensuring the capability to cancel the work of this recursive method, the first position in the integer array - * 'state' must be set to 1, to keep the recursion running. When the states value changes, the method will resolve - * its recursion and return what it has saved so far. + * The user objects that are attached to the nodes is the {@link FileNodeViewModel}, + * which wraps the {@link File}-Object.
    *
    - * The files are filtered according to the {@link DateRange} filter value - * and then sorted according to the {@link ExternalFileSorter} value. - * - * @param unlinkedPDFFileFilter contains a BibDatabaseContext which is used to determine whether the file is linked - * - * @return FileNodeViewModel containing the data of the current directory and all subdirectories + * For ensuring the capability to cancel the work of this recursive method, the first + * position in the integer array 'state' must be set to 1, to keep the recursion + * running. When the states value changes, the method will resolve its recursion and + * return what it has saved so far.
    + * The files are filtered according to the {@link DateRange} filter value and then + * sorted according to the {@link ExternalFileSorter} value. + * @param unlinkedPDFFileFilter contains a BibDatabaseContext which is used to + * determine whether the file is linked + * @return FileNodeViewModel containing the data of the current directory and all + * subdirectories * @throws IOException if directory is not a directory or empty */ FileNodeViewModel searchDirectory(Path directory, UnlinkedPDFFileFilter unlinkedPDFFileFilter) throws IOException { @@ -87,16 +96,18 @@ FileNodeViewModel searchDirectory(Path directory, UnlinkedPDFFileFilter unlinked FileNodeViewModel fileNodeViewModelForCurrentDirectory = new FileNodeViewModel(directory); // Map from isDirectory (true/false) to full path - // Result: Contains only files not matching the filter (i.e., PDFs not linked and files not ignored) + // Result: Contains only files not matching the filter (i.e., PDFs not linked and + // files not ignored) // Filters: - // 1. UnlinkedPDFFileFilter - // 2. GitIgnoreFilter + // 1. UnlinkedPDFFileFilter + // 2. GitIgnoreFilter ChainedFilters filters = new ChainedFilters(List.of(unlinkedPDFFileFilter, new GitIgnoreFileFilter(directory))); Map> directoryAndFilePartition; try (DirectoryStream dirStream = Files.newDirectoryStream(directory, filters); - Stream filesStream = StreamSupport.stream(dirStream.spliterator(), false)) { + Stream filesStream = StreamSupport.stream(dirStream.spliterator(), false)) { directoryAndFilePartition = filesStream.collect(Collectors.partitioningBy(Files::isDirectory)); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Error while searching files", e); return fileNodeViewModelForCurrentDirectory; } @@ -122,7 +133,8 @@ FileNodeViewModel searchDirectory(Path directory, UnlinkedPDFFileFilter unlinked // now we handle the files in the current directory // filter files according to last edited date. - // Note that we do not use the "StreamSupport.stream" filtering functionality, because refactoring the code to that would lead to more code + // Note that we do not use the "StreamSupport.stream" filtering functionality, + // because refactoring the code to that would lead to more code List resultingFiles = new ArrayList<>(); for (Path path : files) { if (FileFilterUtils.filterByDate(path, dateFilter)) { @@ -133,14 +145,16 @@ FileNodeViewModel searchDirectory(Path directory, UnlinkedPDFFileFilter unlinked // sort files according to last edited date. resultingFiles = FileFilterUtils.sortByDate(resultingFiles, sorter); - // the count of all files is the count of the found files in current directory plus the count of all files in the subdirectories + // the count of all files is the count of the found files in current directory + // plus the count of all files in the subdirectories fileNodeViewModelForCurrentDirectory.setFileCount(resultingFiles.size() + fileCountOfSubdirectories); - // create and add FileNodeViewModel to the FileNodeViewModel for the current directory - fileNodeViewModelForCurrentDirectory.getChildren().addAll(resultingFiles.stream() - .map(FileNodeViewModel::new) - .toList()); + // create and add FileNodeViewModel to the FileNodeViewModel for the current + // directory + fileNodeViewModelForCurrentDirectory.getChildren() + .addAll(resultingFiles.stream().map(FileNodeViewModel::new).toList()); return fileNodeViewModelForCurrentDirectory; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogPreferences.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogPreferences.java index 26324a7e949..8a08589d255 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogPreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogPreferences.java @@ -9,13 +9,15 @@ import org.jabref.logic.externalfiles.ExternalFileSorter; public class UnlinkedFilesDialogPreferences { + private final StringProperty unlinkedFilesSelectedExtension; + private final ObjectProperty unlinkedFilesSelectedDateRange; + private final ObjectProperty unlinkedFilesSelectedSort; public UnlinkedFilesDialogPreferences(String unlinkedFilesSelectedExtension, - DateRange unlinkedFilesSelectedDateRange, - ExternalFileSorter unlinkedFilesSelectedSort) { + DateRange unlinkedFilesSelectedDateRange, ExternalFileSorter unlinkedFilesSelectedSort) { this.unlinkedFilesSelectedExtension = new SimpleStringProperty(unlinkedFilesSelectedExtension); this.unlinkedFilesSelectedDateRange = new SimpleObjectProperty<>(unlinkedFilesSelectedDateRange); this.unlinkedFilesSelectedSort = new SimpleObjectProperty<>(unlinkedFilesSelectedSort); @@ -56,4 +58,5 @@ public ObjectProperty unlinkedFilesSelectedSortProperty() { public void setUnlinkedFilesSelectedSort(ExternalFileSorter unlinkedFilesSelectedSort) { this.unlinkedFilesSelectedSort.set(unlinkedFilesSelectedSort); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogView.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogView.java index be68d21f0d7..45af617468f 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogView.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogView.java @@ -62,36 +62,83 @@ public class UnlinkedFilesDialogView extends BaseDialog { private static final String REFRESH_CLASS = "refresh"; - @FXML private TextField directoryPathField; - @FXML private ComboBox fileTypeCombo; - @FXML private ComboBox fileDateCombo; - @FXML private ComboBox fileSortCombo; - @FXML private CheckTreeView unlinkedFilesList; - @FXML private Button scanButton; - @FXML private Button exportButton; - @FXML private Button importButton; - @FXML private Label progressText; - @FXML private Accordion accordion; - @FXML private ProgressIndicator progressDisplay; - @FXML private VBox progressPane; - - @FXML private TableView importResultTable; - @FXML private TableColumn colStatus; - @FXML private TableColumn colMessage; - @FXML private TableColumn colFile; - - @FXML private TitledPane filePane; - @FXML private TitledPane resultPane; - - @Inject private GuiPreferences preferences; - @Inject private DialogService dialogService; - @Inject private StateManager stateManager; - @Inject private UndoManager undoManager; - @Inject private TaskExecutor taskExecutor; - @Inject private FileUpdateMonitor fileUpdateMonitor; - @Inject private ThemeManager themeManager; + @FXML + private TextField directoryPathField; + + @FXML + private ComboBox fileTypeCombo; + + @FXML + private ComboBox fileDateCombo; + + @FXML + private ComboBox fileSortCombo; + + @FXML + private CheckTreeView unlinkedFilesList; + + @FXML + private Button scanButton; + + @FXML + private Button exportButton; + + @FXML + private Button importButton; + + @FXML + private Label progressText; + + @FXML + private Accordion accordion; + + @FXML + private ProgressIndicator progressDisplay; + + @FXML + private VBox progressPane; + + @FXML + private TableView importResultTable; + + @FXML + private TableColumn colStatus; + + @FXML + private TableColumn colMessage; + + @FXML + private TableColumn colFile; + + @FXML + private TitledPane filePane; + + @FXML + private TitledPane resultPane; + + @Inject + private GuiPreferences preferences; + + @Inject + private DialogService dialogService; + + @Inject + private StateManager stateManager; + + @Inject + private UndoManager undoManager; + + @Inject + private TaskExecutor taskExecutor; + + @Inject + private FileUpdateMonitor fileUpdateMonitor; + + @Inject + private ThemeManager themeManager; private final ControlsFxVisualizer validationVisualizer; + private UnlinkedFilesDialogViewModel viewModel; private BibDatabaseContext bibDatabaseContext; @@ -101,9 +148,7 @@ public UnlinkedFilesDialogView() { this.setTitle(Localization.lang("Search for unlinked local files")); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); setResultConverter(button -> { if (button == ButtonType.CANCEL) { @@ -118,15 +163,11 @@ public UnlinkedFilesDialogView() { @FXML private void initialize() { - viewModel = new UnlinkedFilesDialogViewModel( - dialogService, - undoManager, - fileUpdateMonitor, - preferences, - stateManager, - taskExecutor); + viewModel = new UnlinkedFilesDialogViewModel(dialogService, undoManager, fileUpdateMonitor, preferences, + stateManager, taskExecutor); - this.bibDatabaseContext = stateManager.getActiveDatabase().orElseThrow(() -> new NullPointerException("No active library")); + this.bibDatabaseContext = stateManager.getActiveDatabase() + .orElseThrow(() -> new NullPointerException("No active library")); progressDisplay.progressProperty().bind(viewModel.progressValueProperty()); progressText.textProperty().bind(viewModel.progressTextProperty()); @@ -158,41 +199,40 @@ private void initDirectorySelection() { validationVisualizer.setDecoration(new IconValidationDecorator()); directoryPathField.textProperty().bindBidirectional(viewModel.directoryPathProperty()); - Platform.runLater(() -> validationVisualizer.initVisualization(viewModel.directoryPathValidationStatus(), directoryPathField)); + Platform.runLater(() -> validationVisualizer.initVisualization(viewModel.directoryPathValidationStatus(), + directoryPathField)); - new ViewModelListCellFactory() - .withText(FileExtensionViewModel::getDescription) - .withIcon(FileExtensionViewModel::getIcon) - .install(fileTypeCombo); + new ViewModelListCellFactory().withText(FileExtensionViewModel::getDescription) + .withIcon(FileExtensionViewModel::getIcon) + .install(fileTypeCombo); fileTypeCombo.setItems(viewModel.getFileFilters()); fileTypeCombo.valueProperty().bindBidirectional(viewModel.selectedExtensionProperty()); - new ViewModelListCellFactory() - .withText(DateRange::getDateRange) - .install(fileDateCombo); + new ViewModelListCellFactory().withText(DateRange::getDateRange).install(fileDateCombo); fileDateCombo.setItems(viewModel.getDateFilters()); fileDateCombo.valueProperty().bindBidirectional(viewModel.selectedDateProperty()); - new ViewModelListCellFactory() - .withText(ExternalFileSorter::getSorter) - .install(fileSortCombo); + new ViewModelListCellFactory().withText(ExternalFileSorter::getSorter) + .install(fileSortCombo); fileSortCombo.setItems(viewModel.getSorters()); fileSortCombo.valueProperty().bindBidirectional(viewModel.selectedSortProperty()); - directoryPathField.setText(bibDatabaseContext.getFirstExistingFileDir(preferences.getFilePreferences()).map(Path::toString).orElse("")); + directoryPathField.setText(bibDatabaseContext.getFirstExistingFileDir(preferences.getFilePreferences()) + .map(Path::toString) + .orElse("")); loadSavedConfiguration(); } private void initUnlinkedFilesList() { - new ViewModelTreeCellFactory() - .withText(FileNodeViewModel::getDisplayTextWithEditDate) - .install(unlinkedFilesList); + new ViewModelTreeCellFactory().withText(FileNodeViewModel::getDisplayTextWithEditDate) + .install(unlinkedFilesList); unlinkedFilesList.maxHeightProperty().bind(((Control) filePane.contentProperty().get()).heightProperty()); unlinkedFilesList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); - unlinkedFilesList.rootProperty().bind(EasyBind.map(viewModel.treeRootProperty(), - fileNode -> fileNode.map(fileNodeViewModel -> new RecursiveTreeItem<>(fileNodeViewModel, FileNodeViewModel::getChildren)) - .orElse(null))); + unlinkedFilesList.rootProperty() + .bind(EasyBind.map(viewModel.treeRootProperty(), fileNode -> fileNode + .map(fileNodeViewModel -> new RecursiveTreeItem<>(fileNodeViewModel, FileNodeViewModel::getChildren)) + .orElse(null))); unlinkedFilesList.setContextMenu(createSearchContextMenu()); @@ -200,8 +240,10 @@ private void initUnlinkedFilesList() { if (root != null) { ((CheckBoxTreeItem) root).setSelected(true); root.setExpanded(true); - EasyBind.bindContent(viewModel.checkedFileListProperty(), unlinkedFilesList.getCheckModel().getCheckedItems()); - } else { + EasyBind.bindContent(viewModel.checkedFileListProperty(), + unlinkedFilesList.getCheckModel().getCheckedItems()); + } + else { EasyBind.bindContent(viewModel.checkedFileListProperty(), FXCollections.observableArrayList()); } }); @@ -209,19 +251,18 @@ private void initUnlinkedFilesList() { private void initResultTable() { colFile.setCellValueFactory(cellData -> cellData.getValue().file()); - new ValueTableCellFactory() - .withGraphic(this::createEllipsisLabel) - .withTooltip(item -> item) - .install(colFile); + new ValueTableCellFactory().withGraphic(this::createEllipsisLabel) + .withTooltip(item -> item) + .install(colFile); colMessage.setCellValueFactory(cellData -> cellData.getValue().message()); - new ValueTableCellFactory() - .withGraphic(this::createEllipsisLabel) - .withTooltip(item -> item) - .install(colMessage); + new ValueTableCellFactory().withGraphic(this::createEllipsisLabel) + .withTooltip(item -> item) + .install(colMessage); colStatus.setCellValueFactory(cellData -> cellData.getValue().icon()); - colStatus.setCellFactory(new ValueTableCellFactory().withGraphic(JabRefIcon::getGraphicNode)); + colStatus.setCellFactory(new ValueTableCellFactory() + .withGraphic(JabRefIcon::getGraphicNode)); colFile.setResizable(true); colStatus.setResizable(true); colMessage.setResizable(true); @@ -231,29 +272,33 @@ private void initResultTable() { private void initButtons() { BooleanBinding noItemsChecked = Bindings.isNull(unlinkedFilesList.rootProperty()) - .or(Bindings.isEmpty(viewModel.checkedFileListProperty())); + .or(Bindings.isEmpty(viewModel.checkedFileListProperty())); exportButton.disableProperty().bind(noItemsChecked); importButton.disableProperty().bind(noItemsChecked); scanButton.setDefaultButton(true); - scanButton.disableProperty().bind(viewModel.taskActiveProperty().or(viewModel.directoryPathValidationStatus().validProperty().not())); + scanButton.disableProperty() + .bind(viewModel.taskActiveProperty().or(viewModel.directoryPathValidationStatus().validProperty().not())); } private void loadSavedConfiguration() { UnlinkedFilesDialogPreferences unlinkedFilesDialogPreferences = preferences.getUnlinkedFilesDialogPreferences(); FileExtensionViewModel selectedExtension = fileTypeCombo.getItems() - .stream() - .filter(item -> Objects.equals(item.getName(), unlinkedFilesDialogPreferences.getUnlinkedFilesSelectedExtension())) - .findFirst() - .orElseGet(() -> new FileExtensionViewModel(StandardFileType.ANY_FILE, preferences.getExternalApplicationsPreferences())); + .stream() + .filter(item -> Objects.equals(item.getName(), + unlinkedFilesDialogPreferences.getUnlinkedFilesSelectedExtension())) + .findFirst() + .orElseGet(() -> new FileExtensionViewModel(StandardFileType.ANY_FILE, + preferences.getExternalApplicationsPreferences())); fileTypeCombo.getSelectionModel().select(selectedExtension); fileDateCombo.getSelectionModel().select(unlinkedFilesDialogPreferences.getUnlinkedFilesSelectedDateRange()); fileSortCombo.getSelectionModel().select(unlinkedFilesDialogPreferences.getUnlinkedFilesSelectedSort()); } public void saveConfiguration() { - preferences.getUnlinkedFilesDialogPreferences().setUnlinkedFilesSelectedExtension(fileTypeCombo.getValue().getName()); + preferences.getUnlinkedFilesDialogPreferences() + .setUnlinkedFilesSelectedExtension(fileTypeCombo.getValue().getName()); preferences.getUnlinkedFilesDialogPreferences().setUnlinkedFilesSelectedDateRange(fileDateCombo.getValue()); preferences.getUnlinkedFilesDialogPreferences().setUnlinkedFilesSelectedSort(fileSortCombo.getValue()); } @@ -272,10 +317,14 @@ void scanFiles() { void startImport() { viewModel.startImport(); - // Already imported files should not be re-added at a second click on "Import". Therefore, all imported files are unchecked. + // Already imported files should not be re-added at a second click on "Import". + // Therefore, all imported files are unchecked. unlinkedFilesList.getCheckModel().clearChecks(); - // JavaFX does not re-render everything necessary after the file import, and hence it ends up with some misalignment (see https://github.com/JabRef/jabref/issues/12713). Thus, we remove and add the CSS property to force it to re-render. + // JavaFX does not re-render everything necessary after the file import, and hence + // it ends up with some misalignment (see + // https://github.com/JabRef/jabref/issues/12713). Thus, we remove and add the CSS + // property to force it to re-render. Platform.runLater(() -> { accordion.getStyleClass().remove(REFRESH_CLASS); accordion.getStyleClass().add(REFRESH_CLASS); @@ -288,8 +337,8 @@ void exportSelected() { } /** - * Creates a Label with a maximum width and ellipsis for overflow. - * Truncates text if it exceeds two-thirds of the screen width. + * Creates a Label with a maximum width and ellipsis for overflow. Truncates text if + * it exceeds two-thirds of the screen width. */ private Label createEllipsisLabel(String text) { Label label = new Label(text); @@ -300,7 +349,8 @@ private Label createEllipsisLabel(String text) { } /** - * Expands or collapses the specified tree according to the expand-parameter. + * Expands or collapses the specified tree according to the + * expand-parameter. */ private void expandTree(TreeItem item, boolean expand) { if ((item != null) && !item.isLeaf()) { @@ -315,10 +365,18 @@ private ContextMenu createSearchContextMenu() { ContextMenu contextMenu = new ContextMenu(); ActionFactory factory = new ActionFactory(); - contextMenu.getItems().add(factory.createMenuItem(StandardActions.SELECT_ALL, new SearchContextAction(StandardActions.SELECT_ALL))); - contextMenu.getItems().add(factory.createMenuItem(StandardActions.UNSELECT_ALL, new SearchContextAction(StandardActions.UNSELECT_ALL))); - contextMenu.getItems().add(factory.createMenuItem(StandardActions.EXPAND_ALL, new SearchContextAction(StandardActions.EXPAND_ALL))); - contextMenu.getItems().add(factory.createMenuItem(StandardActions.COLLAPSE_ALL, new SearchContextAction(StandardActions.COLLAPSE_ALL))); + contextMenu.getItems() + .add(factory.createMenuItem(StandardActions.SELECT_ALL, + new SearchContextAction(StandardActions.SELECT_ALL))); + contextMenu.getItems() + .add(factory.createMenuItem(StandardActions.UNSELECT_ALL, + new SearchContextAction(StandardActions.UNSELECT_ALL))); + contextMenu.getItems() + .add(factory.createMenuItem(StandardActions.EXPAND_ALL, + new SearchContextAction(StandardActions.EXPAND_ALL))); + contextMenu.getItems() + .add(factory.createMenuItem(StandardActions.COLLAPSE_ALL, + new SearchContextAction(StandardActions.COLLAPSE_ALL))); return contextMenu; } @@ -342,5 +400,7 @@ public void execute() { case COLLAPSE_ALL -> expandTree(unlinkedFilesList.getRoot(), false); } } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogViewModel.java index 50163b34515..d213825f066 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogViewModel.java @@ -55,51 +55,58 @@ public class UnlinkedFilesDialogViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(UnlinkedFilesDialogViewModel.class); private final ImportHandler importHandler; + private final StringProperty directoryPath = new SimpleStringProperty(""); + private final ObjectProperty selectedExtension = new SimpleObjectProperty<>(); + private final ObjectProperty selectedDate = new SimpleObjectProperty<>(); + private final ObjectProperty selectedSort = new SimpleObjectProperty<>(); private final ObjectProperty> treeRootProperty = new SimpleObjectProperty<>(); - private final SimpleListProperty> checkedFileListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + + private final SimpleListProperty> checkedFileListProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); private final BooleanProperty taskActiveProperty = new SimpleBooleanProperty(false); + private final DoubleProperty progressValueProperty = new SimpleDoubleProperty(0); + private final StringProperty progressTextProperty = new SimpleStringProperty(); private final ObservableList resultList = FXCollections.observableArrayList(); + private final ObservableList fileFilterList; + private final ObservableList dateFilterList; + private final ObservableList fileSortList; private final DialogService dialogService; + private final CliPreferences preferences; + private BackgroundTask findUnlinkedFilesTask; + private BackgroundTask> importFilesBackgroundTask; private final BibDatabaseContext bibDatabase; + private final TaskExecutor taskExecutor; private final FunctionBasedValidator scanDirectoryValidator; - public UnlinkedFilesDialogViewModel(DialogService dialogService, - UndoManager undoManager, - FileUpdateMonitor fileUpdateMonitor, - GuiPreferences preferences, - StateManager stateManager, - TaskExecutor taskExecutor) { + public UnlinkedFilesDialogViewModel(DialogService dialogService, UndoManager undoManager, + FileUpdateMonitor fileUpdateMonitor, GuiPreferences preferences, StateManager stateManager, + TaskExecutor taskExecutor) { this.preferences = preferences; this.dialogService = dialogService; this.taskExecutor = taskExecutor; - this.bibDatabase = stateManager.getActiveDatabase().orElseThrow(() -> new NullPointerException("Database null")); - importHandler = new ImportHandler( - bibDatabase, - preferences, - fileUpdateMonitor, - undoManager, - stateManager, - dialogService, - taskExecutor); + this.bibDatabase = stateManager.getActiveDatabase() + .orElseThrow(() -> new NullPointerException("Database null")); + importHandler = new ImportHandler(bibDatabase, preferences, fileUpdateMonitor, undoManager, stateManager, + dialogService, taskExecutor); this.fileFilterList = FXCollections.observableArrayList( new FileExtensionViewModel(StandardFileType.ANY_FILE, preferences.getExternalApplicationsPreferences()), @@ -126,30 +133,30 @@ public void startSearch() { progressValueProperty.unbind(); progressTextProperty.unbind(); - findUnlinkedFilesTask = new UnlinkedFilesCrawler(directory, selectedFileFilter, selectedDateFilter, selectedSortFilter, bibDatabase, preferences.getFilePreferences()) - .onRunning(() -> { - progressValueProperty.set(ProgressIndicator.INDETERMINATE_PROGRESS); - progressTextProperty.setValue(Localization.lang("Searching file system...")); - progressTextProperty.bind(findUnlinkedFilesTask.messageProperty()); - taskActiveProperty.setValue(true); - treeRootProperty.setValue(Optional.empty()); - }) - .onFinished(() -> { - progressValueProperty.set(0); - taskActiveProperty.setValue(false); - }) - .onSuccess(treeRoot -> treeRootProperty.setValue(Optional.of(treeRoot))); + findUnlinkedFilesTask = new UnlinkedFilesCrawler(directory, selectedFileFilter, selectedDateFilter, + selectedSortFilter, bibDatabase, preferences.getFilePreferences()) + .onRunning(() -> { + progressValueProperty.set(ProgressIndicator.INDETERMINATE_PROGRESS); + progressTextProperty.setValue(Localization.lang("Searching file system...")); + progressTextProperty.bind(findUnlinkedFilesTask.messageProperty()); + taskActiveProperty.setValue(true); + treeRootProperty.setValue(Optional.empty()); + }) + .onFinished(() -> { + progressValueProperty.set(0); + taskActiveProperty.setValue(false); + }) + .onSuccess(treeRoot -> treeRootProperty.setValue(Optional.of(treeRoot))); findUnlinkedFilesTask.executeWith(taskExecutor); } public void startImport() { - List fileList = checkedFileListProperty - .stream() - .map(TreeItem::getValue) - .map(FileNodeViewModel::getPath) - .filter(Files::isRegularFile) - .toList(); + List fileList = checkedFileListProperty.stream() + .map(TreeItem::getValue) + .map(FileNodeViewModel::getPath) + .filter(Files::isRegularFile) + .toList(); if (fileList.isEmpty()) { LOGGER.warn("There are no valid files checked for import"); @@ -158,18 +165,18 @@ public void startImport() { resultList.clear(); importFilesBackgroundTask = importHandler - .importFilesInBackground(fileList, bibDatabase, preferences.getFilePreferences(), TransferMode.LINK) - .onRunning(() -> { - progressValueProperty.bind(importFilesBackgroundTask.workDonePercentageProperty()); - progressTextProperty.bind(importFilesBackgroundTask.messageProperty()); - taskActiveProperty.setValue(true); - }) - .onFinished(() -> { - progressValueProperty.unbind(); - progressTextProperty.unbind(); - taskActiveProperty.setValue(false); - }) - .onSuccess(resultList::addAll); + .importFilesInBackground(fileList, bibDatabase, preferences.getFilePreferences(), TransferMode.LINK) + .onRunning(() -> { + progressValueProperty.bind(importFilesBackgroundTask.workDonePercentageProperty()); + progressTextProperty.bind(importFilesBackgroundTask.messageProperty()); + taskActiveProperty.setValue(true); + }) + .onFinished(() -> { + progressValueProperty.unbind(); + progressTextProperty.unbind(); + taskActiveProperty.setValue(false); + }) + .onSuccess(resultList::addAll); importFilesBackgroundTask.executeWith(taskExecutor); } @@ -177,21 +184,20 @@ public void startImport() { * This starts the export of all files of all selected nodes in the file tree view. */ public void startExport() { - List fileList = checkedFileListProperty - .stream() - .map(item -> item.getValue().getPath()) - .filter(Files::isRegularFile) - .toList(); + List fileList = checkedFileListProperty.stream() + .map(item -> item.getValue().getPath()) + .filter(Files::isRegularFile) + .toList(); if (fileList.isEmpty()) { LOGGER.warn("There are no valid files checked for export"); return; } FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) - .addExtensionFilter(StandardFileType.TXT) - .withDefaultExtension(StandardFileType.TXT) - .build(); + .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) + .addExtensionFilter(StandardFileType.TXT) + .withDefaultExtension(StandardFileType.TXT) + .build(); Optional exportPath = dialogService.showFileSaveDialog(fileDialogConfiguration); if (exportPath.isEmpty()) { @@ -203,7 +209,8 @@ public void startExport() { for (Path file : fileList) { writer.write(file.toString() + "\n"); } - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Error exporting", e); } } @@ -231,13 +238,13 @@ public void cancelTasks() { public void browseFileDirectory() { DirectoryDialogConfiguration directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()).build(); + .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) + .build(); - dialogService.showDirectorySelectionDialog(directoryDialogConfiguration) - .ifPresent(selectedDirectory -> { - directoryPath.setValue(selectedDirectory.toAbsolutePath().toString()); - preferences.getFilePreferences().setWorkingDirectory(selectedDirectory.toAbsolutePath()); - }); + dialogService.showDirectorySelectionDialog(directoryDialogConfiguration).ifPresent(selectedDirectory -> { + directoryPath.setValue(selectedDirectory.toAbsolutePath().toString()); + preferences.getFilePreferences().setWorkingDirectory(selectedDirectory.toAbsolutePath()); + }); } private Path getSearchDirectory() { @@ -296,4 +303,5 @@ public BooleanProperty taskActiveProperty() { public SimpleListProperty> checkedFileListProperty() { return checkedFileListProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedPDFFileFilter.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedPDFFileFilter.java index 6b684596dd7..ee68124c07b 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedPDFFileFilter.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/UnlinkedPDFFileFilter.java @@ -14,18 +14,21 @@ import org.jabref.model.entry.BibEntry; /** - * {@link FileFilter} implementation, that allows only files which are not linked in any of the {@link BibEntry}s of the - * specified {@link BibDatabase}. + * {@link FileFilter} implementation, that allows only files which are not linked in any + * of the {@link BibEntry}s of the specified {@link BibDatabase}. *

    - * This {@link FileFilter} sits on top of another {@link FileFilter} -implementation, which it first consults. Only if - * this major filefilter has accepted a file, this implementation will verify on that file. + * This {@link FileFilter} sits on top of another {@link FileFilter} -implementation, + * which it first consults. Only if this major filefilter has accepted a file, this + * implementation will verify on that file. */ public class UnlinkedPDFFileFilter implements DirectoryStream.Filter { private final DatabaseFileLookup lookup; + private final Filter fileFilter; - public UnlinkedPDFFileFilter(DirectoryStream.Filter fileFilter, BibDatabaseContext databaseContext, FilePreferences filePreferences) { + public UnlinkedPDFFileFilter(DirectoryStream.Filter fileFilter, BibDatabaseContext databaseContext, + FilePreferences filePreferences) { this.fileFilter = fileFilter; this.lookup = new DatabaseFileLookup(databaseContext, filePreferences); } @@ -34,8 +37,11 @@ public UnlinkedPDFFileFilter(DirectoryStream.Filter fileFilter, BibDatabas public boolean accept(Path pathname) throws IOException { if (Files.isDirectory(pathname)) { return true; - } else { - return fileFilter.accept(pathname) && !lookup.lookupDatabase(pathname) && !lookup.getPathOfDatabase().equals(pathname); + } + else { + return fileFilter.accept(pathname) && !lookup.lookupDatabase(pathname) + && !lookup.getPathOfDatabase().equals(pathname); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiletype/CustomExternalFileType.java b/jabgui/src/main/java/org/jabref/gui/externalfiletype/CustomExternalFileType.java index 396e1cacf97..ac2262f259b 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiletype/CustomExternalFileType.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiletype/CustomExternalFileType.java @@ -7,23 +7,29 @@ import org.jabref.logic.FilePreferences; /** - * This class defines a type of external files that can be linked to from JabRef. - * The class contains enough information to provide an icon, a standard extension - * and a link to which application handles files of this type. + * This class defines a type of external files that can be linked to from JabRef. The + * class contains enough information to provide an icon, a standard extension and a link + * to which application handles files of this type. * - * TODO: Move to model (and then adapt {@link org.jabref.gui.fieldeditors.LinkedFilesEditorViewModel#fromFile(java.nio.file.Path, java.util.List, FilePreferences)}). + * TODO: Move to model (and then adapt + * {@link org.jabref.gui.fieldeditors.LinkedFilesEditorViewModel#fromFile(java.nio.file.Path, java.util.List, FilePreferences)}). */ public class CustomExternalFileType implements ExternalFileType { private String name; + private String extension; + private String openWith; + private String iconName; + private String mimeType; + private JabRefIcon icon; - public CustomExternalFileType(String name, String extension, String mimeType, - String openWith, String iconName, JabRefIcon icon) { + public CustomExternalFileType(String name, String extension, String mimeType, String openWith, String iconName, + JabRefIcon icon) { this.name = name; this.extension = extension; this.mimeType = mimeType; @@ -34,20 +40,21 @@ public CustomExternalFileType(String name, String extension, String mimeType, } public CustomExternalFileType(ExternalFileType type) { - this(type.getName(), type.getExtension(), type.getMimeType(), type.getOpenWithApplication(), "", type.getIcon()); + this(type.getName(), type.getExtension(), type.getMimeType(), type.getOpenWithApplication(), "", + type.getIcon()); } /** - * Construct an ExternalFileType from a String array. This is used when - * reading file type definitions from Preferences, where the available data types are - * limited. We assume that the array contains the same values as the main constructor, - * in the same order. - * + * Construct an ExternalFileType from a String array. This is used when reading file + * type definitions from Preferences, where the available data types are limited. We + * assume that the array contains the same values as the main constructor, in the same + * order. * @param val arguments. */ public static ExternalFileType buildFromArgs(String[] val) { if ((val == null) || (val.length < 4) || (val.length > 5)) { - throw new IllegalArgumentException("Cannot construct ExternalFileType without four elements in String[] argument."); + throw new IllegalArgumentException( + "Cannot construct ExternalFileType without four elements in String[] argument."); } String name = val[0]; String extension = val[1]; @@ -60,7 +67,8 @@ public static ExternalFileType buildFromArgs(String[] val) { mimeType = ""; openWith = val[2]; iconName = val[3]; - } else { + } + else { // When mime type is included, the array length should be 5: mimeType = val[2]; openWith = val[3]; @@ -128,7 +136,6 @@ public void setOpenWith(String openWith) { /** * Get the string associated with this file type's icon. - * * @return The icon name. */ public String getIconName() { @@ -137,7 +144,6 @@ public String getIconName() { /** * Set the string associated with this file type's icon. - * * @param name The icon name to use. */ public void setIconName(String name) { @@ -171,7 +177,6 @@ public int hashCode() { /** * We define two file type objects as equal if their name, extension, openWith and * iconName are equal. - * * @param object The file type to compare with. * @return true if the file types are equal. */ @@ -182,9 +187,11 @@ public boolean equals(Object object) { } if (object instanceof CustomExternalFileType other) { - return Objects.equals(name, other.name) && Objects.equals(extension, other.extension) && - Objects.equals(mimeType, other.mimeType) && Objects.equals(openWith, other.openWith) && Objects.equals(iconName, other.iconName); + return Objects.equals(name, other.name) && Objects.equals(extension, other.extension) + && Objects.equals(mimeType, other.mimeType) && Objects.equals(openWith, other.openWith) + && Objects.equals(iconName, other.iconName); } return false; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiletype/ExternalFileType.java b/jabgui/src/main/java/org/jabref/gui/externalfiletype/ExternalFileType.java index df386dbc19c..ec8d45a1f92 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiletype/ExternalFileType.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiletype/ExternalFileType.java @@ -8,6 +8,7 @@ * "Twin" interface: {@link org.jabref.logic.util.FileType} */ public interface ExternalFileType { + String getName(); String getExtension(); @@ -19,8 +20,8 @@ public interface ExternalFileType { JabRefIcon getIcon(); /** - * Get the bibtex field name used for this file type. Currently, we assume that field name equals filename extension. - * + * Get the bibtex field name used for this file type. Currently, we assume that field + * name equals filename extension. * @return The field name. */ default Field getField() { @@ -29,12 +30,12 @@ default Field getField() { /** * Return a String array representing this file type. This is used for storage into - * Preferences, and the same array can be used to construct the file type later, - * using the String[] constructor. - * + * Preferences, and the same array can be used to construct the file type later, using + * the String[] constructor. * @return A String[] containing all information about this file type. */ default String[] toStringArray() { - return new String[]{getName(), getExtension(), getMimeType(), getOpenWithApplication(), getIcon().name()}; + return new String[] { getName(), getExtension(), getMimeType(), getOpenWithApplication(), getIcon().name() }; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypes.java b/jabgui/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypes.java index 6946b5997ee..5e6e97b2eca 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypes.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypes.java @@ -22,6 +22,7 @@ public class ExternalFileTypes { // This String is used in the encoded list in prefs of external file type // modifications, in order to indicate a removed default file type: private static final String FILE_TYPE_REMOVED_FLAG = "REMOVED"; + private static final ExternalFileType HTML_FALLBACK_TYPE = StandardExternalFileType.URL; private ExternalFileTypes() { @@ -33,12 +34,15 @@ public static List getDefaultExternalFileTypes() { /** * Look up the external file type registered with this name, if any. - * * @param name The file type name. * @return The ExternalFileType registered, or null if none. */ - public static Optional getExternalFileTypeByName(String name, ExternalApplicationsPreferences externalApplicationsPreferences) { - Optional externalFileType = externalApplicationsPreferences.getExternalFileTypes().stream().filter(type -> type.getName().equals(name)).findFirst(); + public static Optional getExternalFileTypeByName(String name, + ExternalApplicationsPreferences externalApplicationsPreferences) { + Optional externalFileType = externalApplicationsPreferences.getExternalFileTypes() + .stream() + .filter(type -> type.getName().equals(name)) + .findFirst(); if (externalFileType.isPresent()) { return externalFileType; } @@ -48,36 +52,42 @@ public static Optional getExternalFileTypeByName(String name, /** * Look up the external file type registered for this extension, if any. - * * @param extension The file extension. * @return The ExternalFileType registered, or null if none. */ - public static Optional getExternalFileTypeByExt(String extension, ExternalApplicationsPreferences externalApplicationsPreferences) { + public static Optional getExternalFileTypeByExt(String extension, + ExternalApplicationsPreferences externalApplicationsPreferences) { String extensionCleaned = extension.replace(".", "").replace("*", ""); - return externalApplicationsPreferences.getExternalFileTypes().stream().filter(type -> type.getExtension().equalsIgnoreCase(extensionCleaned)).findFirst(); + return externalApplicationsPreferences.getExternalFileTypes() + .stream() + .filter(type -> type.getExtension().equalsIgnoreCase(extensionCleaned)) + .findFirst(); } /** * Returns true if there is an external file type registered for this extension. - * * @param extension The file extension. * @return true if an ExternalFileType with the extension exists, false otherwise */ - public static boolean isExternalFileTypeByExt(String extension, ExternalApplicationsPreferences externalApplicationsPreferences) { - return externalApplicationsPreferences.getExternalFileTypes().stream().anyMatch(type -> type.getExtension().equalsIgnoreCase(extension)); + public static boolean isExternalFileTypeByExt(String extension, + ExternalApplicationsPreferences externalApplicationsPreferences) { + return externalApplicationsPreferences.getExternalFileTypes() + .stream() + .anyMatch(type -> type.getExtension().equalsIgnoreCase(extension)); } /** * Look up the external file type registered for this filename, if any. - * * @param filename The name of the file whose type to look up. * @return The ExternalFileType registered, or null if none. */ - public static Optional getExternalFileTypeForName(String filename, ExternalApplicationsPreferences externalApplicationsPreferences) { + public static Optional getExternalFileTypeForName(String filename, + ExternalApplicationsPreferences externalApplicationsPreferences) { int longestFound = -1; ExternalFileType foundType = null; for (ExternalFileType type : externalApplicationsPreferences.getExternalFileTypes()) { - if (!type.getExtension().isEmpty() && filename.toLowerCase(Locale.ROOT).endsWith(type.getExtension().toLowerCase(Locale.ROOT)) + if (!type.getExtension().isEmpty() + && filename.toLowerCase(Locale.ROOT).endsWith(type.getExtension().toLowerCase(Locale.ROOT)) && (type.getExtension().length() > longestFound)) { longestFound = type.getExtension().length(); foundType = type; @@ -88,13 +98,14 @@ public static Optional getExternalFileTypeForName(String filen /** * Look up the external file type registered for this MIME type, if any. - * * @param mimeType The MIME type. - * @return The ExternalFileType registered, or null if none. For the mime type "text/html", a valid file type is - * guaranteed to be returned. + * @return The ExternalFileType registered, or null if none. For the mime type + * "text/html", a valid file type is guaranteed to be returned. */ - public static Optional getExternalFileTypeByMimeType(String mimeType, ExternalApplicationsPreferences externalApplicationsPreferences) { - // Ignores parameters according to link: (https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) + public static Optional getExternalFileTypeByMimeType(String mimeType, + ExternalApplicationsPreferences externalApplicationsPreferences) { + // Ignores parameters according to link: + // (https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) if (mimeType.indexOf(';') != -1) { mimeType = mimeType.substring(0, mimeType.indexOf(';')).trim(); } @@ -105,38 +116,46 @@ public static Optional getExternalFileTypeByMimeType(String mi } if ("text/html".equalsIgnoreCase(mimeType)) { return Optional.of(HTML_FALLBACK_TYPE); - } else { + } + else { return Optional.empty(); } } - public static Optional getExternalFileTypeByFile(Path file, ExternalApplicationsPreferences externalApplicationsPreferences) { + public static Optional getExternalFileTypeByFile(Path file, + ExternalApplicationsPreferences externalApplicationsPreferences) { final String filePath = file.toString(); final Optional extension = FileUtil.getFileExtension(filePath); return extension.flatMap(ext -> getExternalFileTypeByExt(ext, externalApplicationsPreferences)); } - public static Optional getExternalFileTypeByLinkedFile(LinkedFile linkedFile, boolean deduceUnknownType, ExternalApplicationsPreferences externalApplicationsPreferences) { - Optional type = getExternalFileTypeByName(linkedFile.getFileType(), externalApplicationsPreferences); + public static Optional getExternalFileTypeByLinkedFile(LinkedFile linkedFile, + boolean deduceUnknownType, ExternalApplicationsPreferences externalApplicationsPreferences) { + Optional type = getExternalFileTypeByName(linkedFile.getFileType(), + externalApplicationsPreferences); boolean isUnknownType = type.isEmpty() || (type.get() instanceof UnknownExternalFileType); if (isUnknownType && deduceUnknownType) { - // No file type was recognized. Try to find a usable file type based on mime type: - Optional mimeType = getExternalFileTypeByMimeType(linkedFile.getFileType(), externalApplicationsPreferences); + // No file type was recognized. Try to find a usable file type based on mime + // type: + Optional mimeType = getExternalFileTypeByMimeType(linkedFile.getFileType(), + externalApplicationsPreferences); if (mimeType.isPresent()) { return mimeType; } // No type could be found from mime type. Try based on the extension: return FileUtil.getFileExtension(linkedFile.getLink()) - .flatMap(extension -> getExternalFileTypeByExt(extension, externalApplicationsPreferences)); - } else { + .flatMap(extension -> getExternalFileTypeByExt(extension, externalApplicationsPreferences)); + } + else { return type; } } /** - * @return A StringList of customized and removed file types compared to the default list of external file types for storing + * @return A StringList of customized and removed file types compared to the default + * list of external file types for storing */ public static String toStringList(Collection fileTypes) { // First find a list of the default types: @@ -160,7 +179,8 @@ public static String toStringList(Collection fileTypes) { // Found it! Check if it is an exact match, or if it has been customized: if (found.equals(type)) { unchanged.add(type); - } else { + } + else { // It was modified. Remove its entry from the defaults list, since // the type hasn't been removed: defTypes.remove(found); @@ -184,14 +204,15 @@ public static String toStringList(Collection fileTypes) { i++; } for (ExternalFileType type : defTypes) { - array[i] = new String[] {type.getName(), FILE_TYPE_REMOVED_FLAG}; + array[i] = new String[] { type.getName(), FILE_TYPE_REMOVED_FLAG }; i++; } return FileFieldWriter.encodeStringArray(array); } /** - * Set up the list of external file types, either from default values, or from values recorded in PreferencesService. + * Set up the list of external file types, either from default values, or from values + * recorded in PreferencesService. */ public static Set fromString(String storedFileTypes) { // First get a list of the default file types as a starting point: @@ -218,10 +239,12 @@ public static Set fromString(String storedFileTypes) { if (toRemove != null) { types.remove(toRemove); } - } else { + } + else { // A new or modified entry type. Construct it from the string array: ExternalFileType type = CustomExternalFileType.buildFromArgs(val); - // Check if there is a default type with the same extension. If so, this is a + // Check if there is a default type with the same extension. If so, this + // is a // modification of that type, so remove the default one: ExternalFileType toRemove = null; for (ExternalFileType defType : types) { @@ -242,4 +265,5 @@ public static Set fromString(String storedFileTypes) { return types; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiletype/StandardExternalFileType.java b/jabgui/src/main/java/org/jabref/gui/externalfiletype/StandardExternalFileType.java index fc12e9670e8..9859cb56b44 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiletype/StandardExternalFileType.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiletype/StandardExternalFileType.java @@ -9,14 +9,24 @@ public enum StandardExternalFileType implements ExternalFileType { PDF("PDF", "pdf", "application/pdf", "evince", "pdfSmall", IconTheme.JabRefIcons.PDF_FILE), PostScript("PostScript", "ps", "application/postscript", "evince", "psSmall", IconTheme.JabRefIcons.FILE), Word("Word", "doc", "application/msword", "oowriter", "openoffice", IconTheme.JabRefIcons.FILE_WORD), - Word_NEW("Word 2007+", "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "oowriter", "openoffice", IconTheme.JabRefIcons.FILE_WORD), - OpenDocument_TEXT(Localization.lang("OpenDocument text"), "odt", "application/vnd.oasis.opendocument.text", "oowriter", "openoffice", IconTheme.JabRefIcons.FILE_OPENOFFICE), + Word_NEW("Word 2007+", "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "oowriter", "openoffice", IconTheme.JabRefIcons.FILE_WORD), + OpenDocument_TEXT(Localization.lang("OpenDocument text"), "odt", "application/vnd.oasis.opendocument.text", + "oowriter", "openoffice", IconTheme.JabRefIcons.FILE_OPENOFFICE), Excel("Excel", "xls", "application/excel", "oocalc", "openoffice", IconTheme.JabRefIcons.FILE_EXCEL), - Excel_NEW("Excel 2007+", "xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "oocalc", "openoffice", IconTheme.JabRefIcons.FILE_EXCEL), - OpenDocumentSpreadsheet(Localization.lang("OpenDocument spreadsheet"), "ods", "application/vnd.oasis.opendocument.spreadsheet", "oocalc", "openoffice", IconTheme.JabRefIcons.FILE_OPENOFFICE), - PowerPoint("PowerPoint", "ppt", "application/vnd.ms-powerpoint", "ooimpress", "openoffice", IconTheme.JabRefIcons.FILE_POWERPOINT), - PowerPoint_NEW("PowerPoint 2007+", "pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "ooimpress", "openoffice", IconTheme.JabRefIcons.FILE_POWERPOINT), - OpenDocumentPresentation(Localization.lang("OpenDocument presentation"), "odp", "application/vnd.oasis.opendocument.presentation", "ooimpress", "openoffice", IconTheme.JabRefIcons.FILE_OPENOFFICE), + Excel_NEW("Excel 2007+", "xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "oocalc", + "openoffice", IconTheme.JabRefIcons.FILE_EXCEL), + OpenDocumentSpreadsheet(Localization.lang("OpenDocument spreadsheet"), "ods", + "application/vnd.oasis.opendocument.spreadsheet", "oocalc", "openoffice", + IconTheme.JabRefIcons.FILE_OPENOFFICE), + PowerPoint("PowerPoint", "ppt", "application/vnd.ms-powerpoint", "ooimpress", "openoffice", + IconTheme.JabRefIcons.FILE_POWERPOINT), + PowerPoint_NEW("PowerPoint 2007+", "pptx", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", "ooimpress", "openoffice", + IconTheme.JabRefIcons.FILE_POWERPOINT), + OpenDocumentPresentation(Localization.lang("OpenDocument presentation"), "odp", + "application/vnd.oasis.opendocument.presentation", "ooimpress", "openoffice", + IconTheme.JabRefIcons.FILE_OPENOFFICE), RTF("Rich Text Format", "rtf", "application/rtf", "oowriter", "openoffice", IconTheme.JabRefIcons.FILE_TEXT), PNG(Localization.lang("%0 image", "PNG"), "png", "image/png", "gimp", "picture", IconTheme.JabRefIcons.PICTURE), GIF(Localization.lang("%0 image", "GIF"), "gif", "image/gif", "gimp", "picture", IconTheme.JabRefIcons.PICTURE), @@ -30,15 +40,21 @@ public enum StandardExternalFileType implements ExternalFileType { MHT("MHT", "mht", "multipart/related", "firefox", "www", IconTheme.JabRefIcons.WWW), ePUB("ePUB", "epub", "application/epub+zip", "firefox", "www", IconTheme.JabRefIcons.WWW), MARKDOWN("Markdown", "md", "text/markdown", "emacs", "emacs", IconTheme.JabRefIcons.FILE_TEXT); + private final String name; + private final String extension; + private final String mimeType; + private final String openWith; + private final String iconName; + private final JabRefIcon icon; - StandardExternalFileType(String name, String extension, String mimeType, - String openWith, String iconName, JabRefIcon icon) { + StandardExternalFileType(String name, String extension, String mimeType, String openWith, String iconName, + JabRefIcon icon) { this.name = name; this.extension = extension; this.mimeType = mimeType; @@ -64,7 +80,8 @@ public String getMimeType() { @Override public String getOpenWithApplication() { - // On all OSes there is a generic application available to handle file opening, so use this one + // On all OSes there is a generic application available to handle file opening, so + // use this one return ""; } @@ -72,4 +89,5 @@ public String getOpenWithApplication() { public JabRefIcon getIcon() { return icon; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiletype/UnknownExternalFileType.java b/jabgui/src/main/java/org/jabref/gui/externalfiletype/UnknownExternalFileType.java index db1a681dd26..099878bd740 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiletype/UnknownExternalFileType.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiletype/UnknownExternalFileType.java @@ -6,12 +6,14 @@ /** * Unknown external file type. *

    - * This instance represents a file type unknown to JabRef. - * This can happen, for example, when a database is loaded which contains links to files of a type that has not been defined on this JabRef instance. + * This instance represents a file type unknown to JabRef. This can happen, for example, + * when a database is loaded which contains links to files of a type that has not been + * defined on this JabRef instance. */ public class UnknownExternalFileType implements ExternalFileType { private final String name; + private final String extension; public UnknownExternalFileType(String name) { @@ -47,4 +49,5 @@ public String getOpenWithApplication() { public JabRefIcon getIcon() { return IconTheme.JabRefIcons.FILE; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/AbstractEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/AbstractEditorViewModel.java index 5fada27175a..a02d99da5f6 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/AbstractEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/AbstractEditorViewModel.java @@ -24,23 +24,31 @@ import org.controlsfx.control.textfield.AutoCompletionBinding; public class AbstractEditorViewModel extends AbstractViewModel { + protected final Field field; + protected StringProperty text = new SimpleStringProperty(""); + protected BibEntry entry; + private final SuggestionProvider suggestionProvider; + private final UndoManager undoManager; + private final CompositeValidator fieldValidator; + private EasyObservableValue fieldBinding; - public AbstractEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, UndoManager undoManager) { + public AbstractEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + UndoManager undoManager) { this.field = field; this.suggestionProvider = suggestionProvider; this.undoManager = undoManager; this.fieldValidator = new CompositeValidator(); for (ValueChecker checker : fieldCheckers.getForField(field)) { - FunctionBasedValidator validator = new FunctionBasedValidator<>(text, value -> - checker.checkValue(value).map(ValidationMessage::warning).orElse(null)); + FunctionBasedValidator validator = new FunctionBasedValidator<>(text, + value -> checker.checkValue(value).map(ValidationMessage::warning).orElse(null)); fieldValidator.addValidators(validator); } } @@ -59,24 +67,24 @@ public void bindToEntry(BibEntry entry) { // We need to keep a reference to the binding since it otherwise gets discarded fieldBinding = entry.getFieldBinding(field).asOrdinary(); - BindingsHelper.bindBidirectional( - this.textProperty(), - fieldBinding, - newValue -> { - if (newValue != null) { - // A file may be loaded using CRLF. ControlsFX uses hardcoded \n for multiline fields. - // Thus, we need to normalize the line endings. - // Note: Normalizing for the .bib file is done during writing of the .bib file (see org.jabref.logic.exporter.BibWriter.BibWriter). - String oldValue = entry.getField(field).map(value -> value.replace("\r\n", "\n")).orElse(null); - if (!newValue.equals(oldValue)) { - entry.setField(field, newValue); - undoManager.addEdit(new UndoableFieldChange(entry, field, oldValue, newValue)); - } - } - }); + BindingsHelper.bindBidirectional(this.textProperty(), fieldBinding, newValue -> { + if (newValue != null) { + // A file may be loaded using CRLF. ControlsFX uses hardcoded \n for + // multiline fields. + // Thus, we need to normalize the line endings. + // Note: Normalizing for the .bib file is done during writing of the .bib + // file (see org.jabref.logic.exporter.BibWriter.BibWriter). + String oldValue = entry.getField(field).map(value -> value.replace("\r\n", "\n")).orElse(null); + if (!newValue.equals(oldValue)) { + entry.setField(field, newValue); + undoManager.addEdit(new UndoableFieldChange(entry, field, oldValue, newValue)); + } + } + }); } public Collection complete(AutoCompletionBinding.ISuggestionRequest request) { return suggestionProvider.provideSuggestions(request); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/CitationCountEditor.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/CitationCountEditor.java index 68544e68f91..0d7cd4b9e76 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/CitationCountEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/CitationCountEditor.java @@ -25,42 +25,47 @@ import jakarta.inject.Inject; public class CitationCountEditor extends HBox implements FieldEditorFX { - @FXML private CitationCountEditorViewModel viewModel; - @FXML private EditorTextField textField; - @FXML private Button fetchCitationCountButton; - - @Inject private DialogService dialogService; - @Inject private GuiPreferences preferences; - @Inject private UndoManager undoManager; - @Inject private TaskExecutor taskExecutor; - @Inject private StateManager stateManager; - @Inject private SearchCitationsRelationsService searchCitationsRelationsService; - - public CitationCountEditor(Field field, - SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers) { + + @FXML + private CitationCountEditorViewModel viewModel; + + @FXML + private EditorTextField textField; + + @FXML + private Button fetchCitationCountButton; + + @Inject + private DialogService dialogService; + + @Inject + private GuiPreferences preferences; + + @Inject + private UndoManager undoManager; + + @Inject + private TaskExecutor taskExecutor; + + @Inject + private StateManager stateManager; + + @Inject + private SearchCitationsRelationsService searchCitationsRelationsService; + + public CitationCountEditor(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers) { Injector.registerExistingAndInject(this); - this.viewModel = new CitationCountEditorViewModel( - field, - suggestionProvider, - fieldCheckers, - taskExecutor, - dialogService, - undoManager, - stateManager, - preferences, - searchCitationsRelationsService); - - ViewLoader.view(this) - .root(this) - .load(); + this.viewModel = new CitationCountEditorViewModel(field, suggestionProvider, fieldCheckers, taskExecutor, + dialogService, undoManager, stateManager, preferences, searchCitationsRelationsService); + + ViewLoader.view(this).root(this).load(); textField.textProperty().bindBidirectional(viewModel.textProperty()); - fetchCitationCountButton.setTooltip( - new Tooltip(Localization.lang("Look up %0", field.getDisplayName()))); + fetchCitationCountButton.setTooltip(new Tooltip(Localization.lang("Look up %0", field.getDisplayName()))); textField.initContextMenu(new DefaultMenu(textField), preferences.getKeyBindingRepository()); - new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textField); + new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), + textField); } @FXML @@ -81,4 +86,5 @@ public void bindToEntry(BibEntry entry) { public Parent getNode() { return this; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/CitationCountEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/CitationCountEditorViewModel.java index 173fc27c078..4bd0d2a5394 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/CitationCountEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/CitationCountEditorViewModel.java @@ -22,24 +22,26 @@ import org.slf4j.LoggerFactory; public class CitationCountEditorViewModel extends AbstractEditorViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(CitationCountEditorViewModel.class); protected final BooleanProperty fetchCitationCountInProgress = new SimpleBooleanProperty(false); + private final TaskExecutor taskExecutor; + private final DialogService dialogService; + private final UndoManager undoManager; + private final StateManager stateManager; + private final GuiPreferences preferences; + private final SearchCitationsRelationsService searchCitationsRelationsService; - public CitationCountEditorViewModel( - Field field, - SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers, - TaskExecutor taskExecutor, - DialogService dialogService, - UndoManager undoManager, - StateManager stateManager, - GuiPreferences preferences, + + public CitationCountEditorViewModel(Field field, SuggestionProvider suggestionProvider, + FieldCheckers fieldCheckers, TaskExecutor taskExecutor, DialogService dialogService, + UndoManager undoManager, StateManager stateManager, GuiPreferences preferences, SearchCitationsRelationsService searchCitationsRelationsService) { super(field, suggestionProvider, fieldCheckers, undoManager); this.taskExecutor = taskExecutor; @@ -61,14 +63,18 @@ public boolean getFetchCitationCountInProgress() { public void getCitationCount() { Optional fieldContent = entry.getField(field); BackgroundTask.wrap(() -> searchCitationsRelationsService.getCitationCount(this.entry, fieldContent)) - .onRunning(() -> fetchCitationCountInProgress.setValue(true)) - .onFinished(() -> fetchCitationCountInProgress.setValue(false)) - .onFailure(e -> { - dialogService.notify(Localization.lang("Error occurred when getting citation count, please try again or check the identifier.\n\n%0", e.getLocalizedMessage())); - LOGGER.error("Error while fetching citation count", e); - }) - .onSuccess(identifier -> { - entry.setField(field, String.valueOf(identifier)); - }).executeWith(taskExecutor); + .onRunning(() -> fetchCitationCountInProgress.setValue(true)) + .onFinished(() -> fetchCitationCountInProgress.setValue(false)) + .onFailure(e -> { + dialogService.notify(Localization.lang( + "Error occurred when getting citation count, please try again or check the identifier.\n\n%0", + e.getLocalizedMessage())); + LOGGER.error("Error while fetching citation count", e); + }) + .onSuccess(identifier -> { + entry.setField(field, String.valueOf(identifier)); + }) + .executeWith(taskExecutor); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditor.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditor.java index 53251ba46a2..393c33d7789 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditor.java @@ -27,38 +27,39 @@ public class CitationKeyEditor extends HBox implements FieldEditorFX { - @FXML private final CitationKeyEditorViewModel viewModel; - @FXML private Button generateCitationKeyButton; - @FXML private EditorTextField textField; - - @Inject private GuiPreferences preferences; - @Inject private KeyBindingRepository keyBindingRepository; - @Inject private DialogService dialogService; - @Inject private UndoManager undoManager; - - public CitationKeyEditor(Field field, - SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers, - BibDatabaseContext databaseContext, - UndoAction undoAction, - RedoAction redoAction) { - - ViewLoader.view(this) - .root(this) - .load(); - - this.viewModel = new CitationKeyEditorViewModel( - field, - suggestionProvider, - fieldCheckers, - preferences, - databaseContext, - undoManager, - dialogService); + @FXML + private final CitationKeyEditorViewModel viewModel; + + @FXML + private Button generateCitationKeyButton; + + @FXML + private EditorTextField textField; + + @Inject + private GuiPreferences preferences; + + @Inject + private KeyBindingRepository keyBindingRepository; + + @Inject + private DialogService dialogService; + + @Inject + private UndoManager undoManager; + + public CitationKeyEditor(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + BibDatabaseContext databaseContext, UndoAction undoAction, RedoAction redoAction) { + + ViewLoader.view(this).root(this).load(); + + this.viewModel = new CitationKeyEditorViewModel(field, suggestionProvider, fieldCheckers, preferences, + databaseContext, undoManager, dialogService); establishBinding(textField, viewModel.textProperty(), keyBindingRepository, undoAction, redoAction); textField.initContextMenu(Collections::emptyList, keyBindingRepository); - new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textField); + new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), + textField); } public CitationKeyEditorViewModel getViewModel() { @@ -70,14 +71,13 @@ public void bindToEntry(BibEntry entry) { viewModel.bindToEntry(entry); // Configure button to generate citation key - new ActionFactory().configureIconButton( - StandardActions.GENERATE_CITE_KEY, - viewModel.getGenerateCiteKeyCommand(), - generateCitationKeyButton); + new ActionFactory().configureIconButton(StandardActions.GENERATE_CITE_KEY, + viewModel.getGenerateCiteKeyCommand(), generateCitationKeyButton); } @Override public Parent getNode() { return this; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditorViewModel.java index 109258ae084..d1718ffd05d 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditorViewModel.java @@ -13,18 +13,18 @@ import de.saxsys.mvvmfx.utils.commands.Command; public class CitationKeyEditorViewModel extends AbstractEditorViewModel { + private final CliPreferences preferences; + private final BibDatabaseContext databaseContext; + private final UndoManager undoManager; + private final DialogService dialogService; - public CitationKeyEditorViewModel(Field field, - SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers, - CliPreferences preferences, - BibDatabaseContext databaseContext, - UndoManager undoManager, - DialogService dialogService) { + public CitationKeyEditorViewModel(Field field, SuggestionProvider suggestionProvider, + FieldCheckers fieldCheckers, CliPreferences preferences, BibDatabaseContext databaseContext, + UndoManager undoManager, DialogService dialogService) { super(field, suggestionProvider, fieldCheckers, undoManager); this.preferences = preferences; this.databaseContext = databaseContext; @@ -35,4 +35,5 @@ public CitationKeyEditorViewModel(Field field, public Command getGenerateCiteKeyCommand() { return new GenerateCitationKeySingleAction(entry, databaseContext, dialogService, preferences, undoManager); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/ContextMenuAddable.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/ContextMenuAddable.java index 86b22ca86e5..29e6988a63e 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/ContextMenuAddable.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/ContextMenuAddable.java @@ -8,10 +8,13 @@ import org.jabref.gui.keyboard.KeyBindingRepository; public interface ContextMenuAddable { + /** - * Adds the given list of menu items to the context menu. The usage of {@link Supplier} prevents that the menus need - * to be instantiated at this point. They are populated when the user needs them which prevents many unnecessary + * Adds the given list of menu items to the context menu. The usage of + * {@link Supplier} prevents that the menus need to be instantiated at this point. + * They are populated when the user needs them which prevents many unnecessary * allocations when the main table is just scrolled with the entry editor open. */ void initContextMenu(final Supplier> items, KeyBindingRepository keyBindingRepository); + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/DateEditor.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/DateEditor.java index d4db223ab9d..21e30d6954a 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/DateEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/DateEditor.java @@ -23,27 +23,31 @@ public class DateEditor extends HBox implements FieldEditorFX { - @FXML private DateEditorViewModel viewModel; - @FXML private TemporalAccessorPicker datePicker; - - @Inject private UndoManager undoManager; - @Inject private GuiPreferences preferences; - @Inject private KeyBindingRepository keyBindingRepository; - - public DateEditor(Field field, - DateTimeFormatter dateFormatter, - SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers, - UndoAction undoAction, - RedoAction redoAction) { - ViewLoader.view(this) - .root(this) - .load(); + @FXML + private DateEditorViewModel viewModel; + + @FXML + private TemporalAccessorPicker datePicker; + + @Inject + private UndoManager undoManager; + + @Inject + private GuiPreferences preferences; + + @Inject + private KeyBindingRepository keyBindingRepository; + + public DateEditor(Field field, DateTimeFormatter dateFormatter, SuggestionProvider suggestionProvider, + FieldCheckers fieldCheckers, UndoAction undoAction, RedoAction redoAction) { + ViewLoader.view(this).root(this).load(); this.viewModel = new DateEditorViewModel(field, suggestionProvider, dateFormatter, fieldCheckers, undoManager); datePicker.setStringConverter(viewModel.getDateToStringConverter()); - establishBinding(datePicker.getEditor(), viewModel.textProperty(), keyBindingRepository, undoAction, redoAction); - new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), datePicker.getEditor()); + establishBinding(datePicker.getEditor(), viewModel.textProperty(), keyBindingRepository, undoAction, + redoAction); + new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), + datePicker.getEditor()); } public DateEditorViewModel getViewModel() { @@ -59,4 +63,5 @@ public void bindToEntry(BibEntry entry) { public Parent getNode() { return this; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/DateEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/DateEditorViewModel.java index d8bc6e37e12..f8c12892ec3 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/DateEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/DateEditorViewModel.java @@ -21,9 +21,11 @@ public class DateEditorViewModel extends AbstractEditorViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(DateEditorViewModel.class); + private final DateTimeFormatter dateFormatter; - public DateEditorViewModel(Field field, SuggestionProvider suggestionProvider, DateTimeFormatter dateFormatter, FieldCheckers fieldCheckers, UndoManager undoManager) { + public DateEditorViewModel(Field field, SuggestionProvider suggestionProvider, DateTimeFormatter dateFormatter, + FieldCheckers fieldCheckers, UndoManager undoManager) { super(field, suggestionProvider, fieldCheckers, undoManager); this.dateFormatter = dateFormatter; } @@ -35,11 +37,13 @@ public String toString(TemporalAccessor date) { if (date != null) { try { return dateFormatter.format(date); - } catch (DateTimeException ex) { + } + catch (DateTimeException ex) { LOGGER.error("Could not format date", ex); return ""; } - } else { + } + else { return ""; } } @@ -49,14 +53,17 @@ public TemporalAccessor fromString(String string) { if (StringUtil.isNotBlank(string)) { try { return dateFormatter.parse(string); - } catch (DateTimeParseException exception) { + } + catch (DateTimeParseException exception) { // We accept all kinds of dates (not just in the format specified) return Date.parse(string).map(Date::toTemporalAccessor).orElse(null); } - } else { + } + else { return null; } } }; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/EditorTextArea.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/EditorTextArea.java index 5c9b64fdb39..c17b3b643ab 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/EditorTextArea.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/EditorTextArea.java @@ -22,6 +22,7 @@ public class EditorTextArea extends TextArea implements Initializable, ContextMenuAddable { private final ContextMenu contextMenu = new ContextMenu(); + /** * Variable that contains user-defined behavior for paste action. */ @@ -61,7 +62,6 @@ public void initialize(URL location, ResourceBundle resources) { /** * Set pasteActionHandler variable to passed handler - * * @param handler an instance of PasteActionHandler that describes paste behavior */ public void setPasteActionHandler(Runnable handler) { @@ -70,7 +70,8 @@ public void setPasteActionHandler(Runnable handler) { } /** - * Override javafx TextArea method applying TextArea.paste() and pasteActionHandler after + * Override javafx TextArea method applying TextArea.paste() and pasteActionHandler + * after */ @Override public void paste() { @@ -80,6 +81,7 @@ public void paste() { // Custom event handler for Tab key presses. private static class FieldTraversalEventHandler implements EventHandler { + @Override public void handle(KeyEvent event) { if (event.getCode() == KeyCode.TAB && !event.isShiftDown() && !event.isControlDown()) { @@ -87,15 +89,14 @@ public void handle(KeyEvent event) { // Get the current text area Node node = (Node) event.getSource(); - KeyEvent newEvent = new KeyEvent(node, - event.getTarget(), event.getEventType(), - event.getCharacter(), event.getText(), - event.getCode(), event.isShiftDown(), - true, event.isAltDown(), + KeyEvent newEvent = new KeyEvent(node, event.getTarget(), event.getEventType(), event.getCharacter(), + event.getText(), event.getCode(), event.isShiftDown(), true, event.isAltDown(), event.isMetaDown()); node.fireEvent(newEvent); } } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/EditorTextField.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/EditorTextField.java index 08cd9dde886..32ad36107c8 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/EditorTextField.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/EditorTextField.java @@ -20,6 +20,7 @@ public class EditorTextField extends TextField implements Initializable, ContextMenuAddable { private final ContextMenu contextMenu = new ContextMenu(); + private Runnable additionalPasteActionHandler = () -> { // No additional paste behavior }; @@ -62,4 +63,5 @@ public void paste() { super.paste(); additionalPasteActionHandler.run(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/EditorValidator.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/EditorValidator.java index 64a5ff7e4cf..1e134f4bc69 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/EditorValidator.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/EditorValidator.java @@ -23,4 +23,5 @@ public void configureValidation(final ValidationStatus status, final TextInputCo validationVisualizer.initVisualization(status, textInput); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/FieldEditorFX.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/FieldEditorFX.java index 8371ccbfa2f..2dddcc1fa74 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/FieldEditorFX.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/FieldEditorFX.java @@ -27,21 +27,30 @@ public interface FieldEditorFX { void bindToEntry(BibEntry entry); /** - * @implNote Decided to add undoAction and redoAction as parameter instead of passing a tabSupplier, {@link org.jabref.gui.DialogService} and {@link org.jabref.gui.StateManager} to the method. + * @implNote Decided to add undoAction and redoAction as parameter instead of passing + * a tabSupplier, {@link org.jabref.gui.DialogService} and + * {@link org.jabref.gui.StateManager} to the method. */ - default void establishBinding(TextInputControl textInputControl, StringProperty viewModelTextProperty, KeyBindingRepository keyBindingRepository, UndoAction undoAction, RedoAction redoAction) { + default void establishBinding(TextInputControl textInputControl, StringProperty viewModelTextProperty, + KeyBindingRepository keyBindingRepository, UndoAction undoAction, RedoAction redoAction) { Logger logger = LoggerFactory.getLogger(FieldEditorFX.class); - // We need to use the "global" UndoManager instead of JavaFX TextInputControls's native undo/redo handling. - // This also prevents NPEs. See https://github.com/JabRef/jabref/issues/11420 for details. + // We need to use the "global" UndoManager instead of JavaFX TextInputControls's + // native undo/redo handling. + // This also prevents NPEs. See https://github.com/JabRef/jabref/issues/11420 for + // details. textInputControl.addEventFilter(KeyEvent.ANY, e -> { // Fix based on https://stackoverflow.com/a/37575818/873282 - if (e.getEventType() == KeyEvent.KEY_PRESSED // if not checked, it will be fired twice: once for key pressed and once for key released + if (e.getEventType() == KeyEvent.KEY_PRESSED // if not checked, it will be + // fired twice: once for key + // pressed and once for key + // released && e.isShortcutDown()) { if (keyBindingRepository.matches(e, KeyBinding.UNDO)) { undoAction.execute(); e.consume(); - } else if (keyBindingRepository.matches(e, KeyBinding.REDO)) { + } + else if (keyBindingRepository.matches(e, KeyBinding.REDO)) { redoAction.execute(); e.consume(); } @@ -52,11 +61,14 @@ default void establishBinding(TextInputControl textInputControl, StringProperty // https://github.com/JabRef/jabref/issues/5904 EasyBind.subscribe(viewModelTextProperty, newText -> { - // This might be triggered by save actions from a background thread, so we need to check if we are in the FX thread + // This might be triggered by save actions from a background thread, so we + // need to check if we are in the FX thread if (Platform.isFxApplicationThread()) { setTextAndUpdateCaretPosition(textInputControl, newText, logger); - } else { - UiTaskExecutor.runInJavaFXThread(() -> setTextAndUpdateCaretPosition(textInputControl, newText, logger)); + } + else { + UiTaskExecutor + .runInJavaFXThread(() -> setTextAndUpdateCaretPosition(textInputControl, newText, logger)); } }); EasyBind.subscribe(textInputControl.textProperty(), viewModelTextProperty::set); @@ -98,7 +110,8 @@ private void setTextAndUpdateCaretPosition(TextInputControl textInputControl, St // Change happened after current caret position // Thus, simply restore the old position textInputControl.positionCaret(lastCaretPosition); - } else { + } + else { logger.trace("Last Delta: {}", lastDelta); logger.trace("Last Delta source: {}", lastDelta.getSource()); logger.trace("Last Delta target: {}", lastDelta.getTarget()); @@ -129,23 +142,20 @@ private void setTextAndUpdateCaretPosition(TextInputControl textInputControl, St Parent getNode(); default void focus() { - getNode().getChildrenUnmodifiable() - .stream() - .findFirst() - .orElse(getNode()) - .requestFocus(); + getNode().getChildrenUnmodifiable().stream().findFirst().orElse(getNode()).requestFocus(); } /** * Returns relative size of the field editor in terms of display space. *

    - * A value of 1 means that the editor gets exactly as much space as all other regular editors. + * A value of 1 means that the editor gets exactly as much space as all other regular + * editors. *

    * A value of 2 means that the editor gets twice as much space as regular editors. - * * @return the relative weight of the editor in terms of display space */ default double getWeight() { return 1; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java index 9583f0e87c2..c05e4d66c08 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java @@ -44,103 +44,146 @@ public class FieldEditors { private static final Logger LOGGER = LoggerFactory.getLogger(FieldEditors.class); public static FieldEditorFX getForField(final Field field, - final JournalAbbreviationRepository journalAbbreviationRepository, - final GuiPreferences preferences, - final BibDatabaseContext databaseContext, - final EntryType entryType, - final SuggestionProviders suggestionProviders, - final UndoManager undoManager, - final UndoAction undoAction, - final RedoAction redoAction) { + final JournalAbbreviationRepository journalAbbreviationRepository, final GuiPreferences preferences, + final BibDatabaseContext databaseContext, final EntryType entryType, + final SuggestionProviders suggestionProviders, final UndoManager undoManager, final UndoAction undoAction, + final RedoAction redoAction) { final Set fieldProperties = field.getProperties(); - final SuggestionProvider suggestionProvider = getSuggestionProvider(field, suggestionProviders, databaseContext.getMetaData()); + final SuggestionProvider suggestionProvider = getSuggestionProvider(field, suggestionProviders, + databaseContext.getMetaData()); - final FieldCheckers fieldCheckers = new FieldCheckers( - databaseContext, - preferences.getFilePreferences(), + final FieldCheckers fieldCheckers = new FieldCheckers(databaseContext, preferences.getFilePreferences(), journalAbbreviationRepository, preferences.getEntryEditorPreferences().shouldAllowIntegerEditionBibtex()); - boolean isMultiLine = FieldFactory.isMultiLineField(field, preferences.getFieldPreferences().getNonWrappableFields()); + boolean isMultiLine = FieldFactory.isMultiLineField(field, + preferences.getFieldPreferences().getNonWrappableFields()); if (preferences.getTimestampPreferences().getTimestampField().equals(field)) { - return new DateEditor(field, DateTimeFormatter.ofPattern(preferences.getTimestampPreferences().getTimestampFormat()), suggestionProvider, fieldCheckers, undoAction, redoAction); - } else if (fieldProperties.contains(FieldProperty.DATE)) { - return new DateEditor(field, DateTimeFormatter.ofPattern("[uuuu][-MM][-dd]"), suggestionProvider, fieldCheckers, undoAction, redoAction); - } else if (fieldProperties.contains(FieldProperty.EXTERNAL)) { + return new DateEditor(field, + DateTimeFormatter.ofPattern(preferences.getTimestampPreferences().getTimestampFormat()), + suggestionProvider, fieldCheckers, undoAction, redoAction); + } + else if (fieldProperties.contains(FieldProperty.DATE)) { + return new DateEditor(field, DateTimeFormatter.ofPattern("[uuuu][-MM][-dd]"), suggestionProvider, + fieldCheckers, undoAction, redoAction); + } + else if (fieldProperties.contains(FieldProperty.EXTERNAL)) { return new UrlEditor(field, suggestionProvider, fieldCheckers, undoAction, redoAction); - } else if (fieldProperties.contains(FieldProperty.JOURNAL_NAME)) { + } + else if (fieldProperties.contains(FieldProperty.JOURNAL_NAME)) { return new JournalEditor(field, suggestionProvider, fieldCheckers, undoAction, redoAction); - } else if (fieldProperties.contains(FieldProperty.IDENTIFIER) && field != StandardField.PMID || field == StandardField.ISBN) { - // Identifier editor does not support PMID, therefore excluded at the condition above + } + else if (fieldProperties.contains(FieldProperty.IDENTIFIER) && field != StandardField.PMID + || field == StandardField.ISBN) { + // Identifier editor does not support PMID, therefore excluded at the + // condition above return new IdentifierEditor(field, suggestionProvider, fieldCheckers); - } else if (field == StandardField.CITATIONCOUNT) { + } + else if (field == StandardField.CITATIONCOUNT) { return new CitationCountEditor(field, suggestionProvider, fieldCheckers); - } else if (field == StandardField.ISSN) { + } + else if (field == StandardField.ISSN) { return new ISSNEditor(field, suggestionProvider, fieldCheckers, undoAction, redoAction); - } else if (field == StandardField.OWNER) { + } + else if (field == StandardField.OWNER) { return new OwnerEditor(field, suggestionProvider, fieldCheckers, undoAction, redoAction); - } else if (field == StandardField.GROUPS) { - return new GroupEditor(field, suggestionProvider, fieldCheckers, preferences, isMultiLine, undoManager, undoAction, redoAction); - } else if (field == StandardField.FILE) { + } + else if (field == StandardField.GROUPS) { + return new GroupEditor(field, suggestionProvider, fieldCheckers, preferences, isMultiLine, undoManager, + undoAction, redoAction); + } + else if (field == StandardField.FILE) { return new LinkedFilesEditor(field, databaseContext, suggestionProvider, fieldCheckers); - } else if (fieldProperties.contains(FieldProperty.YES_NO)) { + } + else if (fieldProperties.contains(FieldProperty.YES_NO)) { return new OptionEditor<>(new YesNoEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager)); - } else if (fieldProperties.contains(FieldProperty.MONTH)) { - return new OptionEditor<>(new - MonthEditorViewModel(field, suggestionProvider, databaseContext.getMode(), fieldCheckers, undoManager)); - } else if (fieldProperties.contains(FieldProperty.LANGUAGE)) { - return new OptionEditor<>(new LanguageEditorViewModel(field, suggestionProvider, databaseContext.getMode(), fieldCheckers, undoManager)); - } else if (field == StandardField.GENDER) { + } + else if (fieldProperties.contains(FieldProperty.MONTH)) { + return new OptionEditor<>(new MonthEditorViewModel(field, suggestionProvider, databaseContext.getMode(), + fieldCheckers, undoManager)); + } + else if (fieldProperties.contains(FieldProperty.LANGUAGE)) { + return new OptionEditor<>(new LanguageEditorViewModel(field, suggestionProvider, databaseContext.getMode(), + fieldCheckers, undoManager)); + } + else if (field == StandardField.GENDER) { return new OptionEditor<>(new GenderEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager)); - } else if (fieldProperties.contains(FieldProperty.EDITOR_TYPE)) { - return new OptionEditor<>(new EditorTypeEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager)); - } else if (fieldProperties.contains(FieldProperty.PAGINATION)) { - return new OptionEditor<>(new PaginationEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager)); - } else if (field == StandardField.TYPE) { + } + else if (fieldProperties.contains(FieldProperty.EDITOR_TYPE)) { + return new OptionEditor<>( + new EditorTypeEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager)); + } + else if (fieldProperties.contains(FieldProperty.PAGINATION)) { + return new OptionEditor<>( + new PaginationEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager)); + } + else if (field == StandardField.TYPE) { if (entryType.equals(IEEETranEntryType.Patent)) { - return new OptionEditor<>(new PatentTypeEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager)); - } else { - return new OptionEditor<>(new TypeEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager)); + return new OptionEditor<>( + new PatentTypeEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager)); } - } else if (fieldProperties.contains(FieldProperty.SINGLE_ENTRY_LINK) || fieldProperties.contains(FieldProperty.MULTIPLE_ENTRY_LINK)) { + else { + return new OptionEditor<>( + new TypeEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager)); + } + } + else if (fieldProperties.contains(FieldProperty.SINGLE_ENTRY_LINK) + || fieldProperties.contains(FieldProperty.MULTIPLE_ENTRY_LINK)) { return new LinkedEntriesEditor(field, databaseContext, suggestionProvider, fieldCheckers); - } else if (fieldProperties.contains(FieldProperty.PERSON_NAMES)) { - return new PersonsEditor(field, suggestionProvider, fieldCheckers, isMultiLine, undoManager, undoAction, redoAction); - } else if (StandardField.KEYWORDS == field) { + } + else if (fieldProperties.contains(FieldProperty.PERSON_NAMES)) { + return new PersonsEditor(field, suggestionProvider, fieldCheckers, isMultiLine, undoManager, undoAction, + redoAction); + } + else if (StandardField.KEYWORDS == field) { return new KeywordsEditor(field, suggestionProvider, fieldCheckers); - } else if (field == InternalField.KEY_FIELD) { - return new CitationKeyEditor(field, suggestionProvider, fieldCheckers, databaseContext, undoAction, redoAction); - } else if (fieldProperties.contains(FieldProperty.MARKDOWN)) { - return new MarkdownEditor(field, suggestionProvider, fieldCheckers, preferences, undoManager, undoAction, redoAction); - } else { + } + else if (field == InternalField.KEY_FIELD) { + return new CitationKeyEditor(field, suggestionProvider, fieldCheckers, databaseContext, undoAction, + redoAction); + } + else if (fieldProperties.contains(FieldProperty.MARKDOWN)) { + return new MarkdownEditor(field, suggestionProvider, fieldCheckers, preferences, undoManager, undoAction, + redoAction); + } + else { // There was no specific editor found // Check whether there are selectors defined for the field at hand List selectorValues = databaseContext.getMetaData().getContentSelectorValuesForField(field); if (!isMultiLine && !selectorValues.isEmpty()) { - return new OptionEditor<>(new CustomFieldEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager, selectorValues)); - } else { - return new SimpleEditor(field, suggestionProvider, fieldCheckers, preferences, isMultiLine, undoManager, undoAction, redoAction); + return new OptionEditor<>(new CustomFieldEditorViewModel(field, suggestionProvider, fieldCheckers, + undoManager, selectorValues)); + } + else { + return new SimpleEditor(field, suggestionProvider, fieldCheckers, preferences, isMultiLine, undoManager, + undoAction, redoAction); } } } - private static SuggestionProvider getSuggestionProvider(Field field, SuggestionProviders suggestionProviders, MetaData metaData) { + private static SuggestionProvider getSuggestionProvider(Field field, SuggestionProviders suggestionProviders, + MetaData metaData) { SuggestionProvider suggestionProvider = suggestionProviders.getForField(field); List contentSelectorValues = metaData.getContentSelectorValuesForField(field); if (!contentSelectorValues.isEmpty()) { // Enrich auto completion by content selector values try { - return new ContentSelectorSuggestionProvider((SuggestionProvider) suggestionProvider, contentSelectorValues); - } catch (ClassCastException exception) { - LOGGER.error("Content selectors are only supported for normal fields with string-based auto completion."); + return new ContentSelectorSuggestionProvider((SuggestionProvider) suggestionProvider, + contentSelectorValues); + } + catch (ClassCastException exception) { + LOGGER + .error("Content selectors are only supported for normal fields with string-based auto completion."); return suggestionProvider; } - } else { + } + else { return suggestionProvider; } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/FieldNameLabel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/FieldNameLabel.java index f607bfa04ac..2d92c9479c6 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/FieldNameLabel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/FieldNameLabel.java @@ -38,14 +38,17 @@ public String getDescription(Field field) { StandardField standardField = (StandardField) field; switch (standardField) { case ABSTRACT: - return Localization.lang("This field is intended for recording abstracts, to be printed by a special bibliography style."); + return Localization.lang( + "This field is intended for recording abstracts, to be printed by a special bibliography style."); case ADDENDUM: - return Localization.lang("Miscellaneous bibliographic data usually printed at the end of the entry."); + return Localization + .lang("Miscellaneous bibliographic data usually printed at the end of the entry."); case AFTERWORD: return Localization.lang("Author(s) of an afterword to the work."); case ANNOTATION: case ANNOTE: - return Localization.lang("This field may be useful when implementing a style for annotated bibliographies."); + return Localization + .lang("This field may be useful when implementing a style for annotated bibliographies."); case ANNOTATOR: return Localization.lang("Author(s) of annotations to the work."); case AUTHOR: @@ -61,8 +64,8 @@ public String getDescription(Field field) { case COMMENT: return Localization.lang("Comment to this entry."); case COMMENTATOR: - return Localization.lang("Author(s) of a commentary to the work.") + "\n" + - Localization.lang("Note that this field is intended for commented editions which have a commentator in addition to the author. If the work is a stand-alone commentary, the commentator should be given in the author field."); + return Localization.lang("Author(s) of a commentary to the work.") + "\n" + Localization.lang( + "Note that this field is intended for commented editions which have a commentator in addition to the author. If the work is a stand-alone commentary, the commentator should be given in the author field."); case DATE: return Localization.lang("Publication date of the work."); case DOI: @@ -70,9 +73,11 @@ public String getDescription(Field field) { case EDITION: return Localization.lang("Edition of a printed publication."); case EDITOR: - return Localization.lang("Editor(s) of the work or the main publication, depending on the type of the entry."); + return Localization + .lang("Editor(s) of the work or the main publication, depending on the type of the entry."); case EDITORA: - return Localization.lang("Secondary editor performing a different editorial role, such as compiling, redacting, etc."); + return Localization.lang( + "Secondary editor performing a different editorial role, such as compiling, redacting, etc."); case EDITORB: return Localization.lang("Another secondary editor performing a different role."); case EDITORC: @@ -86,33 +91,38 @@ public String getDescription(Field field) { case EDITORCTYPE: return Localization.lang("Type of editorial role performed by the \"Editorc\"."); case EID: - return Localization.lang("Electronic identifier of a work.") + "\n" + - Localization.lang("This field may replace the pages field for journals deviating from the classic pagination scheme of printed journals by only enumerating articles or papers and not pages."); + return Localization.lang("Electronic identifier of a work.") + "\n" + Localization.lang( + "This field may replace the pages field for journals deviating from the classic pagination scheme of printed journals by only enumerating articles or papers and not pages."); case EPRINT: - return Localization.lang("Electronic identifier of an online publication.") + "\n" + - Localization.lang("This is roughly comparable to a DOI but specific to a certain archive, repository, service, or system."); + return Localization.lang("Electronic identifier of an online publication.") + "\n" + Localization + .lang("This is roughly comparable to a DOI but specific to a certain archive, repository, service, or system."); case EPRINTCLASS: case PRIMARYCLASS: - return Localization.lang("Additional information related to the resource indicated by the eprint field.") + "\n" + - Localization.lang("This could be a section of an archive, a path indicating a service, a classification of some sort."); + return Localization + .lang("Additional information related to the resource indicated by the eprint field.") + "\n" + + Localization.lang( + "This could be a section of an archive, a path indicating a service, a classification of some sort."); case EPRINTTYPE: case ARCHIVEPREFIX: - return Localization.lang("Type of the eprint identifier, e.g., the name of the archive, repository, service, or system the eprint field refers to."); + return Localization.lang( + "Type of the eprint identifier, e.g., the name of the archive, repository, service, or system the eprint field refers to."); case EVENTDATE: return Localization.lang("Date of a conference, a symposium, or some other event."); case EVENTTITLE: return Localization.lang("Title of a conference, a symposium, or some other event.") + "\n" - + Localization.lang("Note that this field holds the plain title of the event. Things like \"Proceedings of the Fifth XYZ Conference\" go into the titleaddon or booktitleaddon field."); + + Localization.lang( + "Note that this field holds the plain title of the event. Things like \"Proceedings of the Fifth XYZ Conference\" go into the titleaddon or booktitleaddon field."); case EVENTTITLEADDON: - return Localization.lang("Annex to the eventtitle field.") + "\n" + - Localization.lang("Can be used for known event acronyms."); + return Localization.lang("Annex to the eventtitle field.") + "\n" + + Localization.lang("Can be used for known event acronyms."); case FILE: case PDF: return Localization.lang("Link(s) to a local PDF or other document of the work."); case FOREWORD: return Localization.lang("Author(s) of a foreword to the work."); case HOWPUBLISHED: - return Localization.lang("Publication notice for unusual publications which do not fit into any of the common categories."); + return Localization.lang( + "Publication notice for unusual publications which do not fit into any of the common categories."); case INSTITUTION: case SCHOOL: return Localization.lang("Name of a university or some other institution."); @@ -125,8 +135,8 @@ public String getDescription(Field field) { case ISSN: return Localization.lang("International Standard Serial Number of a periodical."); case ISSUE: - return Localization.lang("Issue of a journal.") + "\n" + - Localization.lang("This field is intended for journals whose individual issues are identified by a designation such as \"Spring\" or \"Summer\" rather than the month or a number. Integer ranges and short designators are better written to the number field."); + return Localization.lang("Issue of a journal.") + "\n" + Localization.lang( + "This field is intended for journals whose individual issues are identified by a designation such as \"Spring\" or \"Summer\" rather than the month or a number. Integer ranges and short designators are better written to the number field."); case ISSUESUBTITLE: return Localization.lang("Subtitle of a specific issue of a journal or other periodical."); case ISSUETITLE: @@ -137,14 +147,17 @@ public String getDescription(Field field) { case JOURNAL: return Localization.lang("Name of a journal, a newspaper, or some other periodical."); case LABEL: - return Localization.lang("Designation to be used by the citation style as a substitute for the regular label if any data required to generate the regular label is missing."); + return Localization.lang( + "Designation to be used by the citation style as a substitute for the regular label if any data required to generate the regular label is missing."); case LANGUAGE: - return Localization.lang("Language(s) of the work. Languages may be specified literally or as localisation keys."); + return Localization + .lang("Language(s) of the work. Languages may be specified literally or as localisation keys."); case LIBRARY: return Localization.lang("Information such as a library name and a call number."); case LOCATION: case ADDRESS: - return Localization.lang("Place(s) of publication, i. e., the location of the publisher or institution, depending on the entry type."); + return Localization.lang( + "Place(s) of publication, i. e., the location of the publisher or institution, depending on the entry type."); case MAINSUBTITLE: return Localization.lang("Subtitle related to the \"Maintitle\"."); case MAINTITLE: @@ -154,32 +167,38 @@ public String getDescription(Field field) { case MONTH: return Localization.lang("Publication month."); case NAMEADDON: - return Localization.lang("Addon to be printed immediately after the author name in the bibliography."); + return Localization + .lang("Addon to be printed immediately after the author name in the bibliography."); case NOTE: - return Localization.lang("Miscellaneous bibliographic data which does not fit into any other field."); + return Localization + .lang("Miscellaneous bibliographic data which does not fit into any other field."); case NUMBER: return Localization.lang("Number of a journal or the volume/number of a book in a series."); case ORGANIZATION: - return Localization.lang("Organization(s) that published a manual or an online resource, or sponsored a conference."); + return Localization.lang( + "Organization(s) that published a manual or an online resource, or sponsored a conference."); case ORIGDATE: - return Localization.lang("If the work is a translation, a reprint, or something similar, the publication date of the original edition."); + return Localization.lang( + "If the work is a translation, a reprint, or something similar, the publication date of the original edition."); case ORIGLANGUAGE: return Localization.lang("If the work is a translation, the language(s) of the original work."); case PAGES: - return Localization.lang("One or more page numbers or page ranges.") + "\n" + - Localization.lang("If the work is published as part of another one, such as an article in a journal or a collection, this field holds the relevant page range in that other work. It may also be used to limit the reference to a specific part of a work (a chapter in a book, for example). For papers in electronic journals with anon-classical pagination setup the eid field may be more suitable."); + return Localization.lang("One or more page numbers or page ranges.") + "\n" + Localization.lang( + "If the work is published as part of another one, such as an article in a journal or a collection, this field holds the relevant page range in that other work. It may also be used to limit the reference to a specific part of a work (a chapter in a book, for example). For papers in electronic journals with anon-classical pagination setup the eid field may be more suitable."); case PAGETOTAL: return Localization.lang("Total number of pages of the work."); case PAGINATION: return Localization.lang("Pagination of the work. The key should be given in the singular form."); case PART: - return Localization.lang("Number of a partial volume. This field applies to books only, not to journals. It may be used when a logical volume consists of two or more physical ones."); + return Localization.lang( + "Number of a partial volume. This field applies to books only, not to journals. It may be used when a logical volume consists of two or more physical ones."); case PUBLISHER: return Localization.lang("Name(s) of the publisher(s)."); case PUBSTATE: return Localization.lang("Publication state of the work, e.g., \"in press\"."); case SERIES: - return Localization.lang("Name of a publication series, such as \"Studies in...\", or the number of a journal series."); + return Localization.lang( + "Name of a publication series, such as \"Studies in...\", or the number of a journal series."); case SHORTTITLE: return Localization.lang("Title in an abridged form."); case SUBTITLE: @@ -189,7 +208,8 @@ public String getDescription(Field field) { case TITLEADDON: return Localization.lang("Annex to the \"Title\", to be printed in a different font."); case TRANSLATOR: - return Localization.lang("Translator(s) of the \"Title\" or \"Booktitle\", depending on the entry type. If the translator is identical to the \"Editor\", the standard styles will automatically concatenate these fields in the bibliography."); + return Localization.lang( + "Translator(s) of the \"Title\" or \"Booktitle\", depending on the entry type. If the translator is identical to the \"Editor\", the standard styles will automatically concatenate these fields in the bibliography."); case TYPE: return Localization.lang("Type of a \"Manual\", \"Patent\", \"Report\", or \"Thesis\"."); case URL: @@ -207,7 +227,8 @@ public String getDescription(Field field) { case YEAR: return Localization.lang("Year of publication."); case CROSSREF: - return Localization.lang("This field holds an entry key for the cross-referencing feature. Child entries with a \"Crossref\" field inherit data from the parent entry specified in the \"Crossref\" field."); + return Localization.lang( + "This field holds an entry key for the cross-referencing feature. Child entries with a \"Crossref\" field inherit data from the parent entry specified in the \"Crossref\" field."); case GENDER: return Localization.lang("Gender of the author or gender of the editor, if there is no author."); case KEYWORDS: @@ -215,7 +236,8 @@ public String getDescription(Field field) { case RELATED: return Localization.lang("Citation keys of other entries which have a relationship to this entry."); case XREF: - return Localization.lang("This field is an alternative cross-referencing mechanism. It differs from \"Crossref\" in that the child entry will not inherit any data from the parent entry specified in the \"Xref\" field."); + return Localization.lang( + "This field is an alternative cross-referencing mechanism. It differs from \"Crossref\" in that the child entry will not inherit any data from the parent entry specified in the \"Xref\" field."); case GROUPS: return Localization.lang("Name(s) of the (manual) groups the entry belongs to."); case OWNER: @@ -223,12 +245,14 @@ public String getDescription(Field field) { case TIMESTAMP: return Localization.lang("Timestamp of this entry, when it has been created or last modified."); } - } else if (field instanceof InternalField internalField) { + } + else if (field instanceof InternalField internalField) { switch (internalField) { case KEY_FIELD: return Localization.lang("Key by which the work may be cited."); } - } else if (field instanceof SpecialField specialField) { + } + else if (field instanceof SpecialField specialField) { return switch (specialField) { case PRINTED -> Localization.lang("User-specific printed flag, in case the entry has been printed."); case PRIORITY -> Localization.lang("User-specific priority."); @@ -240,4 +264,5 @@ public String getDescription(Field field) { } return ""; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/GroupEditor.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/GroupEditor.java index 16a50521b65..3aaacfd6894 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/GroupEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/GroupEditor.java @@ -21,14 +21,9 @@ public class GroupEditor extends SimpleEditor { private Optional bibEntry; - public GroupEditor(final Field field, - final SuggestionProvider suggestionProvider, - final FieldCheckers fieldCheckers, - final GuiPreferences preferences, - final boolean isMultiLine, - final UndoManager undoManager, - final UndoAction undoAction, - final RedoAction redoAction) { + public GroupEditor(final Field field, final SuggestionProvider suggestionProvider, + final FieldCheckers fieldCheckers, final GuiPreferences preferences, final boolean isMultiLine, + final UndoManager undoManager, final UndoAction undoAction, final RedoAction redoAction) { super(field, suggestionProvider, fieldCheckers, preferences, isMultiLine, undoManager, undoAction, redoAction); this.setOnDragOver(event -> { @@ -41,12 +36,15 @@ public GroupEditor(final Field field, this.setOnDragDropped(event -> { boolean success = false; if (event.getDragboard().hasContent(DragAndDropDataFormats.GROUP)) { - List draggedGroups = (List) event.getDragboard().getContent(DragAndDropDataFormats.GROUP); + List draggedGroups = (List) event.getDragboard() + .getContent(DragAndDropDataFormats.GROUP); if (bibEntry.isPresent() && draggedGroups.getFirst() != null) { - String newGroup = bibEntry.map(entry -> entry.getField(StandardField.GROUPS) - .map(oldGroups -> oldGroups + (preferences.getBibEntryPreferences().getKeywordSeparator()) + (draggedGroups.getFirst())) - .orElse(draggedGroups.getFirst())) - .orElse(null); + String newGroup = bibEntry + .map(entry -> entry.getField(StandardField.GROUPS) + .map(oldGroups -> oldGroups + (preferences.getBibEntryPreferences().getKeywordSeparator()) + + (draggedGroups.getFirst())) + .orElse(draggedGroups.getFirst())) + .orElse(null); bibEntry.map(entry -> entry.setField(StandardField.GROUPS, newGroup)); success = true; } @@ -61,4 +59,5 @@ public void bindToEntry(BibEntry entry) { super.bindToEntry(entry); this.bibEntry = Optional.of(entry); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/ISSNEditor.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/ISSNEditor.java index d900512fb8f..0c8d4d4dd8b 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/ISSNEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/ISSNEditor.java @@ -28,43 +28,51 @@ // TODO: Document why this is not an IdentifierEditor like ISBN and EPrint // If there is no reason, then integrate it in IdentifierEditor public class ISSNEditor extends HBox implements FieldEditorFX { - @FXML private ISSNEditorViewModel viewModel; - @FXML private EditorTextField textField; - @FXML private Button journalInfoButton; - @FXML private Button fetchInformationByIdentifierButton; - - @Inject private DialogService dialogService; - @Inject private GuiPreferences preferences; - @Inject private KeyBindingRepository keyBindingRepository; - @Inject private UndoManager undoManager; - @Inject private TaskExecutor taskExecutor; - @Inject private StateManager stateManager; + + @FXML + private ISSNEditorViewModel viewModel; + + @FXML + private EditorTextField textField; + + @FXML + private Button journalInfoButton; + + @FXML + private Button fetchInformationByIdentifierButton; + + @Inject + private DialogService dialogService; + + @Inject + private GuiPreferences preferences; + + @Inject + private KeyBindingRepository keyBindingRepository; + + @Inject + private UndoManager undoManager; + + @Inject + private TaskExecutor taskExecutor; + + @Inject + private StateManager stateManager; private Optional entry = Optional.empty(); - public ISSNEditor(Field field, - SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers, - UndoAction undoAction, - RedoAction redoAction) { - - ViewLoader.view(this) - .root(this) - .load(); - - this.viewModel = new ISSNEditorViewModel( - field, - suggestionProvider, - fieldCheckers, - taskExecutor, - dialogService, - undoManager, - stateManager, - preferences); + public ISSNEditor(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + UndoAction undoAction, RedoAction redoAction) { + + ViewLoader.view(this).root(this).load(); + + this.viewModel = new ISSNEditorViewModel(field, suggestionProvider, fieldCheckers, taskExecutor, dialogService, + undoManager, stateManager, preferences); establishBinding(textField, viewModel.textProperty(), keyBindingRepository, undoAction, redoAction); textField.initContextMenu(new DefaultMenu(textField), keyBindingRepository); - new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textField); + new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), + textField); } public ISSNEditorViewModel getViewModel() { @@ -98,4 +106,5 @@ private void showJournalInfo() { viewModel.showJournalInfo(journalInfoButton); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/ISSNEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/ISSNEditorViewModel.java index b260019f2f9..22f89f4c1cd 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/ISSNEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/ISSNEditorViewModel.java @@ -17,20 +17,19 @@ import org.jabref.model.entry.field.StandardField; public class ISSNEditorViewModel extends AbstractEditorViewModel { + private final TaskExecutor taskExecutor; + private final DialogService dialogService; + private final UndoManager undoManager; + private final StateManager stateManager; + private final GuiPreferences preferences; - public ISSNEditorViewModel( - Field field, - SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers, - TaskExecutor taskExecutor, - DialogService dialogService, - UndoManager undoManager, - StateManager stateManager, + public ISSNEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + TaskExecutor taskExecutor, DialogService dialogService, UndoManager undoManager, StateManager stateManager, GuiPreferences preferences) { super(field, suggestionProvider, fieldCheckers, undoManager); this.taskExecutor = taskExecutor; @@ -45,10 +44,12 @@ public void showJournalInfo(Button journalInfoButton) { } public void fetchBibliographyInformation(BibEntry bibEntry) { - stateManager.getActiveDatabase().ifPresentOrElse( - databaseContext -> new FetchAndMergeEntry(databaseContext, taskExecutor, preferences, dialogService, undoManager) + stateManager.getActiveDatabase() + .ifPresentOrElse( + databaseContext -> new FetchAndMergeEntry(databaseContext, taskExecutor, preferences, dialogService, + undoManager) .fetchAndMerge(bibEntry, StandardField.ISSN), - () -> dialogService.notify(Localization.lang("No library selected")) - ); + () -> dialogService.notify(Localization.lang("No library selected"))); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java index 0c964ef9532..099acc78071 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java @@ -26,40 +26,46 @@ public class JournalEditor extends HBox implements FieldEditorFX { - @FXML private JournalEditorViewModel viewModel; - @FXML private EditorTextField textField; - @FXML private Button journalInfoButton; - - @Inject private DialogService dialogService; - @Inject private GuiPreferences preferences; - @Inject private KeyBindingRepository keyBindingRepository; - @Inject private TaskExecutor taskExecutor; - @Inject private JournalAbbreviationRepository abbreviationRepository; - @Inject private UndoManager undoManager; - - public JournalEditor(Field field, - SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers, - UndoAction undoAction, - RedoAction redoAction) { - - ViewLoader.view(this) - .root(this) - .load(); - - this.viewModel = new JournalEditorViewModel( - field, - suggestionProvider, - abbreviationRepository, - fieldCheckers, - taskExecutor, - dialogService, - undoManager); + @FXML + private JournalEditorViewModel viewModel; + + @FXML + private EditorTextField textField; + + @FXML + private Button journalInfoButton; + + @Inject + private DialogService dialogService; + + @Inject + private GuiPreferences preferences; + + @Inject + private KeyBindingRepository keyBindingRepository; + + @Inject + private TaskExecutor taskExecutor; + + @Inject + private JournalAbbreviationRepository abbreviationRepository; + + @Inject + private UndoManager undoManager; + + public JournalEditor(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + UndoAction undoAction, RedoAction redoAction) { + + ViewLoader.view(this).root(this).load(); + + this.viewModel = new JournalEditorViewModel(field, suggestionProvider, abbreviationRepository, fieldCheckers, + taskExecutor, dialogService, undoManager); establishBinding(textField, viewModel.textProperty(), keyBindingRepository, undoAction, redoAction); textField.initContextMenu(new DefaultMenu(textField), keyBindingRepository); AutoCompletionTextInputBinding.autoComplete(textField, viewModel::complete); - new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textField); + new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), + textField); } public JournalEditorViewModel getViewModel() { @@ -87,4 +93,5 @@ private void showJournalInfo() { viewModel.showJournalInfo(journalInfoButton); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/JournalEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/JournalEditorViewModel.java index 4fba9d2635e..160585fc2a8 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/JournalEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/JournalEditorViewModel.java @@ -13,18 +13,16 @@ import org.jabref.model.strings.StringUtil; public class JournalEditorViewModel extends AbstractEditorViewModel { + private final JournalAbbreviationRepository journalAbbreviationRepository; + private final TaskExecutor taskExecutor; + private final DialogService dialogService; - public JournalEditorViewModel( - Field field, - SuggestionProvider suggestionProvider, - JournalAbbreviationRepository journalAbbreviationRepository, - FieldCheckers fieldCheckers, - TaskExecutor taskExecutor, - DialogService dialogService, - UndoManager undoManager) { + public JournalEditorViewModel(Field field, SuggestionProvider suggestionProvider, + JournalAbbreviationRepository journalAbbreviationRepository, FieldCheckers fieldCheckers, + TaskExecutor taskExecutor, DialogService dialogService, UndoManager undoManager) { super(field, suggestionProvider, fieldCheckers, undoManager); this.journalAbbreviationRepository = journalAbbreviationRepository; this.taskExecutor = taskExecutor; @@ -42,11 +40,13 @@ public void toggleAbbreviation() { journalAbbreviationRepository.getNextAbbreviation(name).ifPresent(nextAbbreviation -> { text.set(nextAbbreviation); // TODO: Add undo - // panel.getUndoManager().addEdit(new UndoableFieldChange(entry, editor.getName(), text, nextAbbreviation)); + // panel.getUndoManager().addEdit(new UndoableFieldChange(entry, + // editor.getName(), text, nextAbbreviation)); }); } public void showJournalInfo(Button journalInfoButton) { PopOverUtil.showJournalInfo(journalInfoButton, entry, dialogService, taskExecutor); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/JournalInfoOptInDialogHelper.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/JournalInfoOptInDialogHelper.java index b1209ba95db..7c5ee8010cb 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/JournalInfoOptInDialogHelper.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/JournalInfoOptInDialogHelper.java @@ -7,7 +7,8 @@ public class JournalInfoOptInDialogHelper { /** - * Using the journal information data fetcher service needs to be opt-in for GDPR compliance. + * Using the journal information data fetcher service needs to be opt-in for GDPR + * compliance. */ public static boolean isJournalInfoEnabled(DialogService dialogService, EntryEditorPreferences preferences) { if (preferences.shouldEnableJournalPopup() == EntryEditorPreferences.JournalPopupEnabled.ENABLED) { @@ -15,27 +16,25 @@ public static boolean isJournalInfoEnabled(DialogService dialogService, EntryEdi } if (preferences.shouldEnableJournalPopup() == EntryEditorPreferences.JournalPopupEnabled.DISABLED) { - boolean enableJournalPopup = dialogService.showConfirmationDialogAndWait( - Localization.lang("Enable Journal Information Fetching?"), - Localization.lang("Would you like to enable fetching of journal information? This can be changed later in %0 > %1.", - Localization.lang("Preferences"), - Localization.lang("Entry editor")), Localization.lang("Enable"), Localization.lang("Keep disabled") - ); - - preferences.setEnableJournalPopup(enableJournalPopup - ? EntryEditorPreferences.JournalPopupEnabled.ENABLED + boolean enableJournalPopup = dialogService + .showConfirmationDialogAndWait(Localization.lang("Enable Journal Information Fetching?"), Localization + .lang("Would you like to enable fetching of journal information? This can be changed later in %0 > %1.", + Localization.lang("Preferences"), Localization.lang("Entry editor")), + Localization.lang("Enable"), Localization.lang("Keep disabled")); + + preferences.setEnableJournalPopup(enableJournalPopup ? EntryEditorPreferences.JournalPopupEnabled.ENABLED : EntryEditorPreferences.JournalPopupEnabled.DISABLED); return enableJournalPopup; } - boolean journalInfoEnabled = dialogService.showConfirmationDialogAndWait( - Localization.lang("Remote services"), - Localization.lang("Allow sending ISSN to a JabRef online service (SCimago) for fetching journal information")); + boolean journalInfoEnabled = dialogService.showConfirmationDialogAndWait(Localization.lang("Remote services"), + Localization + .lang("Allow sending ISSN to a JabRef online service (SCimago) for fetching journal information")); - preferences.setEnableJournalPopup(journalInfoEnabled - ? EntryEditorPreferences.JournalPopupEnabled.ENABLED + preferences.setEnableJournalPopup(journalInfoEnabled ? EntryEditorPreferences.JournalPopupEnabled.ENABLED : EntryEditorPreferences.JournalPopupEnabled.DISABLED); return journalInfoEnabled; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/KeywordsEditor.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/KeywordsEditor.java index aadd9f52f3a..ffe93972cf8 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/KeywordsEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/KeywordsEditor.java @@ -52,8 +52,11 @@ import org.slf4j.LoggerFactory; public class KeywordsEditor extends HBox implements FieldEditorFX { + private static final Logger LOGGER = LoggerFactory.getLogger(KeywordsEditor.class); + private static final PseudoClass FOCUSED = PseudoClass.getPseudoClass("focused"); + private static HashBiMap mscmap; static { @@ -69,40 +72,45 @@ public class KeywordsEditor extends HBox implements FieldEditorFX { if (optionalMscCodes.isPresent()) { mscmap = optionalMscCodes.get(); - } else { + } + else { LOGGER.warn("Resource not found msc_codes.json"); mscmap = HashBiMap.create(); } - } catch (MscCodeLoadingException e) { + } + catch (MscCodeLoadingException e) { LOGGER.error("Error loading MSC codes", e); mscmap = HashBiMap.create(); } } - @FXML private KeywordsEditorViewModel viewModel; - @FXML private TagsField keywordTagsField; + @FXML + private KeywordsEditorViewModel viewModel; + + @FXML + private TagsField keywordTagsField; + + @Inject + private CliPreferences preferences; + + @Inject + private DialogService dialogService; + + @Inject + private UndoManager undoManager; - @Inject private CliPreferences preferences; - @Inject private DialogService dialogService; - @Inject private UndoManager undoManager; - @Inject private ClipBoardManager clipBoardManager; + @Inject + private ClipBoardManager clipBoardManager; private boolean isSortedTagsField = false; + private Optional draggedKeyword = Optional.empty(); - public KeywordsEditor(Field field, - SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers) { + public KeywordsEditor(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers) { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); - this.viewModel = new KeywordsEditorViewModel( - field, - suggestionProvider, - fieldCheckers, - preferences, + this.viewModel = new KeywordsEditorViewModel(field, suggestionProvider, fieldCheckers, preferences, undoManager); keywordTagsField.setCellFactory(new ViewModelListCellFactory().withText(Keyword::get)); @@ -110,7 +118,8 @@ public KeywordsEditor(Field field, keywordTagsField.setSuggestionProvider(request -> viewModel.getSuggestions(request.getUserText())); keywordTagsField.setConverter(viewModel.getStringConverter()); - keywordTagsField.setMatcher((keyword, searchText) -> keyword.get().toLowerCase().startsWith(searchText.toLowerCase())); + keywordTagsField + .setMatcher((keyword, searchText) -> keyword.get().toLowerCase().startsWith(searchText.toLowerCase())); keywordTagsField.setComparator(Comparator.comparing(Keyword::get)); keywordTagsField.setNewItemProducer(searchText -> viewModel.getStringConverter().fromString(searchText)); @@ -119,7 +128,10 @@ public KeywordsEditor(Field field, keywordTagsField.setOnMouseClicked(event -> keywordTagsField.getEditor().requestFocus()); keywordTagsField.getEditor().getStyleClass().clear(); keywordTagsField.getEditor().getStyleClass().add("tags-field-editor"); - keywordTagsField.getEditor().focusedProperty().addListener((observable, oldValue, newValue) -> keywordTagsField.pseudoClassStateChanged(FOCUSED, newValue)); + keywordTagsField.getEditor() + .focusedProperty() + .addListener( + (observable, oldValue, newValue) -> keywordTagsField.pseudoClassStateChanged(FOCUSED, newValue)); String keywordSeparator = String.valueOf(viewModel.getKeywordSeparator()); keywordTagsField.getEditor().setOnKeyReleased(event -> { @@ -132,7 +144,9 @@ public KeywordsEditor(Field field, this.viewModel.keywordListProperty().addListener((observable, oldValue, newValue) -> { if (keywordTagsField.getTags().size() < 2) { isSortedTagsField = false; - } else if ((Comparators.isInOrder(keywordTagsField.getTags(), Comparator.comparing(Keyword::get))) || isSortedTagsField) { + } + else if ((Comparators.isInOrder(keywordTagsField.getTags(), Comparator.comparing(Keyword::get))) + || isSortedTagsField) { isSortedTagsField = true; keywordTagsField.getTags().sort(Comparator.comparing(Keyword::get)); } @@ -163,11 +177,13 @@ private Node createTag(Keyword keyword) { tagLabel.setContentDisplay(ContentDisplay.RIGHT); ContextMenu contextMenu = new ContextMenu(); ActionFactory factory = new ActionFactory(); - contextMenu.getItems().addAll( - factory.createMenuItem(StandardActions.COPY, new KeywordsEditor.TagContextAction(StandardActions.COPY, keyword)), - factory.createMenuItem(StandardActions.CUT, new KeywordsEditor.TagContextAction(StandardActions.CUT, keyword)), - factory.createMenuItem(StandardActions.DELETE, new KeywordsEditor.TagContextAction(StandardActions.DELETE, keyword)) - ); + contextMenu.getItems() + .addAll(factory.createMenuItem(StandardActions.COPY, + new KeywordsEditor.TagContextAction(StandardActions.COPY, keyword)), + factory.createMenuItem(StandardActions.CUT, + new KeywordsEditor.TagContextAction(StandardActions.CUT, keyword)), + factory.createMenuItem(StandardActions.DELETE, + new KeywordsEditor.TagContextAction(StandardActions.DELETE, keyword))); tagLabel.setContextMenu(contextMenu); tagLabel.setOnMouseClicked(event -> { if (event.getClickCount() == 2) { @@ -210,7 +226,8 @@ private Node createTag(Keyword keyword) { keywordTagsField.getTags().add(dropIndex, draggedKeyword.get()); } event.setDropCompleted(true); - } else { + } + else { event.setDropCompleted(false); } draggedKeyword = Optional.empty(); @@ -240,7 +257,9 @@ public double getWeight() { } private class TagContextAction extends SimpleCommand { + private final StandardActions command; + private final Keyword keyword; public TagContextAction(StandardActions command, Keyword keyword) { @@ -254,19 +273,19 @@ public void execute() { case COPY -> { clipBoardManager.setContent(keyword.get()); dialogService.notify(Localization.lang("Copied '%0' to clipboard.", - JabRefDialogService.shortenDialogMessage(keyword.get()))); + JabRefDialogService.shortenDialogMessage(keyword.get()))); } case CUT -> { clipBoardManager.setContent(keyword.get()); dialogService.notify(Localization.lang("Copied '%0' to clipboard.", - JabRefDialogService.shortenDialogMessage(keyword.get()))); + JabRefDialogService.shortenDialogMessage(keyword.get()))); keywordTagsField.removeTags(keyword); } - case DELETE -> - keywordTagsField.removeTags(keyword); - default -> - LOGGER.info("Action {} not defined", command.getText()); + case DELETE -> keywordTagsField.removeTags(keyword); + default -> LOGGER.info("Action {} not defined", command.getText()); } } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/KeywordsEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/KeywordsEditorViewModel.java index 78a7dc650c2..a6eaed20d0c 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/KeywordsEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/KeywordsEditorViewModel.java @@ -26,14 +26,13 @@ public class KeywordsEditorViewModel extends AbstractEditorViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(KeywordsEditorViewModel.class); private final ListProperty keywordListProperty; + private final Character keywordSeparator; + private final SuggestionProvider suggestionProvider; - public KeywordsEditorViewModel(Field field, - SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers, - CliPreferences preferences, - UndoManager undoManager) { + public KeywordsEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + CliPreferences preferences, UndoManager undoManager) { super(field, suggestionProvider, fieldCheckers, undoManager); @@ -41,10 +40,7 @@ public KeywordsEditorViewModel(Field field, this.keywordSeparator = preferences.getBibEntryPreferences().getKeywordSeparator(); this.suggestionProvider = suggestionProvider; - BindingsHelper.bindContentBidirectional( - keywordListProperty, - text, - this::serializeKeywords, + BindingsHelper.bindContentBidirectional(keywordListProperty, text, this::serializeKeywords, this::parseKeywords); } @@ -79,12 +75,13 @@ public Keyword fromString(String keywordString) { } public List getSuggestions(String request) { - List suggestions = suggestionProvider.getPossibleSuggestions().stream() - .map(String.class::cast) - .filter(keyword -> keyword.toLowerCase().contains(request.toLowerCase())) - .map(Keyword::new) - .distinct() - .collect(Collectors.toList()); + List suggestions = suggestionProvider.getPossibleSuggestions() + .stream() + .map(String.class::cast) + .filter(keyword -> keyword.toLowerCase().contains(request.toLowerCase())) + .map(Keyword::new) + .distinct() + .collect(Collectors.toList()); Keyword requestedKeyword = new Keyword(request); if (!suggestions.contains(requestedKeyword)) { @@ -97,4 +94,5 @@ public List getSuggestions(String request) { public Character getKeywordSeparator() { return keywordSeparator; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java index 4b65ad78349..444561d1213 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java @@ -40,38 +40,54 @@ import org.slf4j.LoggerFactory; public class LinkedEntriesEditor extends HBox implements FieldEditorFX { + private static final Logger LOGGER = LoggerFactory.getLogger(LinkedEntriesEditor.class); + private static final PseudoClass FOCUSED = PseudoClass.getPseudoClass("focused"); - @FXML private LinkedEntriesEditorViewModel viewModel; - @FXML private TagsField entryLinkField; + @FXML + private LinkedEntriesEditorViewModel viewModel; + + @FXML + private TagsField entryLinkField; + + @Inject + private DialogService dialogService; + + @Inject + private ClipBoardManager clipBoardManager; - @Inject private DialogService dialogService; - @Inject private ClipBoardManager clipBoardManager; - @Inject private UndoManager undoManager; - @Inject private StateManager stateManager; + @Inject + private UndoManager undoManager; - public LinkedEntriesEditor(Field field, BibDatabaseContext databaseContext, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers) { - ViewLoader.view(this) - .root(this) - .load(); + @Inject + private StateManager stateManager; - this.viewModel = new LinkedEntriesEditorViewModel(field, suggestionProvider, databaseContext, fieldCheckers, undoManager, stateManager); + public LinkedEntriesEditor(Field field, BibDatabaseContext databaseContext, + SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers) { + ViewLoader.view(this).root(this).load(); - entryLinkField.setCellFactory(new ViewModelListCellFactory().withText(ParsedEntryLink::getKey)); + this.viewModel = new LinkedEntriesEditorViewModel(field, suggestionProvider, databaseContext, fieldCheckers, + undoManager, stateManager); + + entryLinkField + .setCellFactory(new ViewModelListCellFactory().withText(ParsedEntryLink::getKey)); entryLinkField.setSuggestionProvider(request -> viewModel.getSuggestions(request.getUserText())); entryLinkField.setTagViewFactory(this::createTag); entryLinkField.setConverter(viewModel.getStringConverter()); entryLinkField.setNewItemProducer(searchText -> viewModel.getStringConverter().fromString(searchText)); - entryLinkField.setMatcher((entryLink, searchText) -> entryLink.getKey().toLowerCase().startsWith(searchText.toLowerCase())); + entryLinkField.setMatcher( + (entryLink, searchText) -> entryLink.getKey().toLowerCase().startsWith(searchText.toLowerCase())); entryLinkField.setComparator(Comparator.comparing(ParsedEntryLink::getKey)); entryLinkField.setShowSearchIcon(false); entryLinkField.setOnMouseClicked(event -> entryLinkField.getEditor().requestFocus()); entryLinkField.getEditor().getStyleClass().clear(); entryLinkField.getEditor().getStyleClass().add("tags-field-editor"); - entryLinkField.getEditor().focusedProperty().addListener((observable, oldValue, newValue) -> entryLinkField.pseudoClassStateChanged(FOCUSED, newValue)); + entryLinkField.getEditor() + .focusedProperty() + .addListener((observable, oldValue, newValue) -> entryLinkField.pseudoClassStateChanged(FOCUSED, newValue)); String separator = EntryLinkList.SEPARATOR; entryLinkField.getEditor().setOnKeyReleased(event -> { @@ -98,11 +114,11 @@ private Node createTag(ParsedEntryLink entryLink) { ContextMenu contextMenu = new ContextMenu(); ActionFactory factory = new ActionFactory(); - contextMenu.getItems().addAll( - factory.createMenuItem(StandardActions.COPY, new TagContextAction(StandardActions.COPY, entryLink)), - factory.createMenuItem(StandardActions.CUT, new TagContextAction(StandardActions.CUT, entryLink)), - factory.createMenuItem(StandardActions.DELETE, new TagContextAction(StandardActions.DELETE, entryLink)) - ); + contextMenu.getItems() + .addAll(factory.createMenuItem(StandardActions.COPY, new TagContextAction(StandardActions.COPY, entryLink)), + factory.createMenuItem(StandardActions.CUT, new TagContextAction(StandardActions.CUT, entryLink)), + factory.createMenuItem(StandardActions.DELETE, + new TagContextAction(StandardActions.DELETE, entryLink))); tagLabel.setContextMenu(contextMenu); return tagLabel; } @@ -122,7 +138,9 @@ public Parent getNode() { } private class TagContextAction extends SimpleCommand { + private final StandardActions command; + private final ParsedEntryLink entryLink; public TagContextAction(StandardActions command, ParsedEntryLink entryLink) { @@ -144,11 +162,11 @@ public void execute() { JabRefDialogService.shortenDialogMessage(entryLink.getKey()))); entryLinkField.removeTags(entryLink); } - case DELETE -> - entryLinkField.removeTags(entryLink); - default -> - LOGGER.info("Action {} not defined", command.getText()); + case DELETE -> entryLinkField.removeTags(entryLink); + default -> LOGGER.info("Action {} not defined", command.getText()); } } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java index a61599e9a30..5ae61c27e20 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java @@ -24,16 +24,16 @@ public class LinkedEntriesEditorViewModel extends AbstractEditorViewModel { private final BibDatabaseContext databaseContext; + private final SuggestionProvider suggestionProvider; + private final ListProperty linkedEntries; + private final StateManager stateManager; - public LinkedEntriesEditorViewModel(Field field, - SuggestionProvider suggestionProvider, - BibDatabaseContext databaseContext, - FieldCheckers fieldCheckers, - UndoManager undoManager, - StateManager stateManager) { + public LinkedEntriesEditorViewModel(Field field, SuggestionProvider suggestionProvider, + BibDatabaseContext databaseContext, FieldCheckers fieldCheckers, UndoManager undoManager, + StateManager stateManager) { super(field, suggestionProvider, fieldCheckers, undoManager); this.databaseContext = databaseContext; @@ -41,10 +41,7 @@ public LinkedEntriesEditorViewModel(Field field, this.stateManager = stateManager; linkedEntries = new SimpleListProperty<>(FXCollections.observableArrayList()); - BindingsHelper.bindContentBidirectional( - linkedEntries, - text, - EntryLinkList::serialize, + BindingsHelper.bindContentBidirectional(linkedEntries, text, EntryLinkList::serialize, newText -> EntryLinkList.parse(newText, databaseContext.getDatabase())); } @@ -70,14 +67,14 @@ public ParsedEntryLink fromString(String key) { } public List getSuggestions(String request) { - List suggestions = suggestionProvider - .getPossibleSuggestions() - .stream() - .map(suggestion -> suggestion instanceof BibEntry bibEntry ? bibEntry.getCitationKey().orElse("") : (String) suggestion) - .filter(suggestion -> suggestion.toLowerCase(Locale.ROOT).contains(request.toLowerCase(Locale.ROOT))) - .map(suggestion -> new ParsedEntryLink(suggestion, databaseContext.getDatabase())) - .distinct() - .collect(Collectors.toList()); + List suggestions = suggestionProvider.getPossibleSuggestions() + .stream() + .map(suggestion -> suggestion instanceof BibEntry bibEntry ? bibEntry.getCitationKey().orElse("") + : (String) suggestion) + .filter(suggestion -> suggestion.toLowerCase(Locale.ROOT).contains(request.toLowerCase(Locale.ROOT))) + .map(suggestion -> new ParsedEntryLink(suggestion, databaseContext.getDatabase())) + .distinct() + .collect(Collectors.toList()); ParsedEntryLink requestedLink = new ParsedEntryLink(request, databaseContext.getDatabase()); if (!suggestions.contains(requestedLink)) { @@ -88,7 +85,8 @@ public List getSuggestions(String request) { } public void jumpToEntry(ParsedEntryLink parsedEntryLink) { - parsedEntryLink.getLinkedEntry().ifPresent(entry -> - stateManager.activeTabProperty().get().ifPresent(tab -> tab.clearAndSelect(entry))); + parsedEntryLink.getLinkedEntry() + .ifPresent(entry -> stateManager.activeTabProperty().get().ifPresent(tab -> tab.clearAndSelect(entry))); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java index c262b5e753a..241a0b4695a 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java @@ -62,65 +62,60 @@ public class LinkedFileViewModel extends AbstractViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(LinkedFileViewModel.class); private final LinkedFile linkedFile; + private final BibDatabaseContext databaseContext; + private final DoubleProperty downloadProgress = new SimpleDoubleProperty(-1); + private final BooleanProperty downloadOngoing = new SimpleBooleanProperty(false); + private final BooleanProperty isAutomaticallyFound = new SimpleBooleanProperty(false); + private final BooleanProperty isOfflinePdf = new SimpleBooleanProperty(false); + private final DialogService dialogService; + private final BibEntry entry; + private final TaskExecutor taskExecutor; + private final GuiPreferences preferences; + private final LinkedFileHandler linkedFileHandler; private ObjectBinding linkedFileIconBinding; private final Validator fileExistsValidator; - public LinkedFileViewModel(LinkedFile linkedFile, - BibEntry entry, - BibDatabaseContext databaseContext, - TaskExecutor taskExecutor, - DialogService dialogService, - GuiPreferences preferences) { + public LinkedFileViewModel(LinkedFile linkedFile, BibEntry entry, BibDatabaseContext databaseContext, + TaskExecutor taskExecutor, DialogService dialogService, GuiPreferences preferences) { this.linkedFile = linkedFile; this.preferences = preferences; - this.linkedFileHandler = new LinkedFileHandler(linkedFile, entry, databaseContext, preferences.getFilePreferences()); + this.linkedFileHandler = new LinkedFileHandler(linkedFile, entry, databaseContext, + preferences.getFilePreferences()); this.databaseContext = databaseContext; this.entry = entry; this.dialogService = dialogService; this.taskExecutor = taskExecutor; - fileExistsValidator = new FunctionBasedValidator<>( - linkedFile.linkProperty(), - link -> { - if (linkedFile.isOnlineLink()) { - return true; - } else { - Optional path = FileUtil.find(databaseContext, link, preferences.getFilePreferences()); - return path.isPresent() && Files.exists(path.get()); - } - }, - ValidationMessage.warning(Localization.lang("Could not find file '%0'.", linkedFile.getLink()))); + fileExistsValidator = new FunctionBasedValidator<>(linkedFile.linkProperty(), link -> { + if (linkedFile.isOnlineLink()) { + return true; + } + else { + Optional path = FileUtil.find(databaseContext, link, preferences.getFilePreferences()); + return path.isPresent() && Files.exists(path.get()); + } + }, ValidationMessage.warning(Localization.lang("Could not find file '%0'.", linkedFile.getLink()))); downloadOngoing.bind(downloadProgress.greaterThanOrEqualTo(0).and(downloadProgress.lessThan(1))); isOfflinePdf.setValue(!linkedFile.isOnlineLink() && "pdf".equalsIgnoreCase(linkedFile.getFileType())); } - public static LinkedFileViewModel fromLinkedFile( - LinkedFile linkedFile, - BibEntry entry, - BibDatabaseContext databaseContext, - TaskExecutor taskExecutor, - DialogService dialogService, + public static LinkedFileViewModel fromLinkedFile(LinkedFile linkedFile, BibEntry entry, + BibDatabaseContext databaseContext, TaskExecutor taskExecutor, DialogService dialogService, GuiPreferences preferences) { - return new LinkedFileViewModel( - linkedFile, - entry, - databaseContext, - taskExecutor, - dialogService, - preferences); + return new LinkedFileViewModel(linkedFile, entry, databaseContext, taskExecutor, dialogService, preferences); } public BooleanProperty isOfflinePdfProperty() { @@ -158,20 +153,22 @@ public String getDescription() { public String getDescriptionAndLink() { if (StringUtil.isBlank(linkedFile.getDescription())) { return linkedFile.getLink(); - } else { + } + else { return linkedFile.getDescription() + " (" + linkedFile.getLink() + ")"; } } public String getTruncatedDescriptionAndLink() { if (StringUtil.isBlank(linkedFile.getDescription())) { - return ControlHelper.truncateString(linkedFile.getLink(), -1, "...", - ControlHelper.EllipsisPosition.CENTER); - } else { + return ControlHelper.truncateString(linkedFile.getLink(), -1, "...", ControlHelper.EllipsisPosition.CENTER); + } + else { return ControlHelper.truncateString(linkedFile.getDescription(), -1, "...", - ControlHelper.EllipsisPosition.CENTER) + " (" + - ControlHelper.truncateString(linkedFile.getLink(), -1, "...", - ControlHelper.EllipsisPosition.CENTER) + ")"; + ControlHelper.EllipsisPosition.CENTER) + " (" + + ControlHelper.truncateString(linkedFile.getLink(), -1, "...", + ControlHelper.EllipsisPosition.CENTER) + + ")"; } } @@ -180,14 +177,16 @@ public Optional findIn(List directories) { } public JabRefIcon getTypeIcon() { - return ExternalFileTypes.getExternalFileTypeByLinkedFile(linkedFile, false, preferences.getExternalApplicationsPreferences()) - .map(ExternalFileType::getIcon) - .orElse(IconTheme.JabRefIcons.FILE); + return ExternalFileTypes + .getExternalFileTypeByLinkedFile(linkedFile, false, preferences.getExternalApplicationsPreferences()) + .map(ExternalFileType::getIcon) + .orElse(IconTheme.JabRefIcons.FILE); } public ObjectBinding typeIconProperty() { if (linkedFileIconBinding == null) { - linkedFileIconBinding = Bindings.createObjectBinding(() -> this.getTypeIcon().getGraphicNode(), linkedFile.fileTypeProperty()); + linkedFileIconBinding = Bindings.createObjectBinding(() -> this.getTypeIcon().getGraphicNode(), + linkedFile.fileTypeProperty()); } return linkedFileIconBinding; @@ -211,12 +210,17 @@ public Observable[] getObservables() { public void open() { try { - Optional type = ExternalFileTypes.getExternalFileTypeByLinkedFile(linkedFile, true, preferences.getExternalApplicationsPreferences()); - boolean successful = NativeDesktop.openExternalFileAnyFormat(databaseContext, preferences.getExternalApplicationsPreferences(), preferences.getFilePreferences(), linkedFile.getLink(), type); + Optional type = ExternalFileTypes.getExternalFileTypeByLinkedFile(linkedFile, true, + preferences.getExternalApplicationsPreferences()); + boolean successful = NativeDesktop.openExternalFileAnyFormat(databaseContext, + preferences.getExternalApplicationsPreferences(), preferences.getFilePreferences(), + linkedFile.getLink(), type); if (!successful) { - dialogService.showErrorDialogAndWait(Localization.lang("File not found"), Localization.lang("Could not find file '%0'.", linkedFile.getLink())); + dialogService.showErrorDialogAndWait(Localization.lang("File not found"), + Localization.lang("Could not find file '%0'.", linkedFile.getLink())); } - } catch (IOException e) { + } + catch (IOException e) { dialogService.showErrorDialogAndWait(Localization.lang("Error opening file '%0'", linkedFile.getLink()), e); } } @@ -224,19 +228,22 @@ public void open() { public void openFolder() { try { if (!linkedFile.isOnlineLink()) { - Optional resolvedPath = FileUtil.find( - databaseContext, - linkedFile.getLink(), + Optional resolvedPath = FileUtil.find(databaseContext, linkedFile.getLink(), preferences.getFilePreferences()); if (resolvedPath.isPresent()) { - NativeDesktop.openFolderAndSelectFile(resolvedPath.get(), preferences.getExternalApplicationsPreferences(), dialogService); - } else { + NativeDesktop.openFolderAndSelectFile(resolvedPath.get(), + preferences.getExternalApplicationsPreferences(), dialogService); + } + else { dialogService.showErrorDialogAndWait(Localization.lang("File not found")); } - } else { - dialogService.showErrorDialogAndWait(Localization.lang("Cannot open folder as the file is an online link.")); } - } catch (IOException ex) { + else { + dialogService + .showErrorDialogAndWait(Localization.lang("Cannot open folder as the file is an online link.")); + } + } + catch (IOException ex) { LOGGER.debug("Cannot open folder", ex); } } @@ -249,8 +256,7 @@ public void askForNameAndRename() { String oldFile = this.linkedFile.getLink(); Path oldFilePath = Path.of(oldFile); Optional askedFileName = dialogService.showInputDialogWithDefaultAndWait( - Localization.lang("Rename file"), - Localization.lang("New Filename"), + Localization.lang("Rename file"), Localization.lang("New Filename"), oldFilePath.getFileName().toString()); askedFileName.ifPresent(this::renameFileToName); } @@ -264,8 +270,10 @@ public void renameFileToName(String targetFileName) { Optional file = linkedFile.findIn(databaseContext, preferences.getFilePreferences()); if (file.isPresent()) { performRenameWithConflictCheck(targetFileName); - } else { - dialogService.showErrorDialogAndWait(Localization.lang("File not found"), Localization.lang("Could not find file '%0'.", linkedFile.getLink())); + } + else { + dialogService.showErrorDialogAndWait(Localization.lang("File not found"), + Localization.lang("Could not find file '%0'.", linkedFile.getLink())); } } @@ -285,7 +293,8 @@ private void performRenameWithConflictCheck(String targetFileName) { // Define available dialog options ButtonType replace = new ButtonType(Localization.lang("Replace"), ButtonBar.ButtonData.OTHER); ButtonType keepBoth = new ButtonType(Localization.lang("Keep both"), ButtonBar.ButtonData.OTHER); - ButtonType provideAlternative = new ButtonType(Localization.lang("Provide alternative file name"), ButtonBar.ButtonData.OTHER); + ButtonType provideAlternative = new ButtonType(Localization.lang("Provide alternative file name"), + ButtonBar.ButtonData.OTHER); ButtonType cancel = new ButtonType(Localization.lang("Cancel"), ButtonBar.ButtonData.CANCEL_CLOSE); // Define tooltips for dialog buttons @@ -294,21 +303,21 @@ private void performRenameWithConflictCheck(String targetFileName) { // Use JabRefDialogService to show a custom dialog with tooltips Optional result = dialogService.showCustomButtonDialogWithTooltipsAndWait( - Alert.AlertType.CONFIRMATION, - Localization.lang("File already exists"), - Localization.lang("File name: \n'%0'", targetFileName), - tooltips, - replace, keepBoth, provideAlternative, cancel - ); + Alert.AlertType.CONFIRMATION, Localization.lang("File already exists"), + Localization.lang("File name: \n'%0'", targetFileName), tooltips, replace, keepBoth, + provideAlternative, cancel); // Show dialog and handle user response if (result.isEmpty() || result.get() == cancel) { return; // User canceled - } else if (result.get() == replace) { + } + else if (result.get() == replace) { overwriteFile = true; - } else if (result.get() == keepBoth) { + } + else if (result.get() == keepBoth) { targetFileName = suggestedFileName; - } else if (result.get() == provideAlternative) { + } + else if (result.get() == provideAlternative) { askForNameAndRename(); return; } @@ -317,10 +326,10 @@ private void performRenameWithConflictCheck(String targetFileName) { try { // Attempt the rename operation linkedFileHandler.renameToName(targetFileName, overwriteFile); - } catch (IOException e) { + } + catch (IOException e) { // Display an error dialog if file is locked or inaccessible - dialogService.showErrorDialogAndWait( - Localization.lang("Rename failed"), + dialogService.showErrorDialogAndWait(Localization.lang("Rename failed"), Localization.lang("JabRef cannot access the file because it is being used by another process.")); } } @@ -334,7 +343,8 @@ public void moveToDefaultDirectory() { // Get target folder Optional fileDir = databaseContext.getFirstExistingFileDir(preferences.getFilePreferences()); if (fileDir.isEmpty()) { - dialogService.showErrorDialogAndWait(Localization.lang("Move file"), Localization.lang("File directory is not set or does not exist.")); + dialogService.showErrorDialogAndWait(Localization.lang("Move file"), + Localization.lang("File directory is not set or does not exist.")); return; } @@ -343,21 +353,22 @@ public void moveToDefaultDirectory() { // Found the linked file, so move it try { linkedFileHandler.moveToDefaultDirectory(); - } catch (IOException exception) { - dialogService.showErrorDialogAndWait( - Localization.lang("Move file"), - Localization.lang("Could not move file '%0'.", file.get().toString()), - exception); } - } else { + catch (IOException exception) { + dialogService.showErrorDialogAndWait(Localization.lang("Move file"), + Localization.lang("Could not move file '%0'.", file.get().toString()), exception); + } + } + else { // File doesn't exist, so we can't move it. - dialogService.showErrorDialogAndWait(Localization.lang("File not found"), Localization.lang("Could not find file '%0'.", linkedFile.getLink())); + dialogService.showErrorDialogAndWait(Localization.lang("File not found"), + Localization.lang("Could not find file '%0'.", linkedFile.getLink())); } } /** - * Gets the filename for the current linked file and compares it to the new suggested filename. - * + * Gets the filename for the current linked file and compares it to the new suggested + * filename. * @return true if the suggested filename is same as current filename. */ public boolean isGeneratedNameSameAsOriginal() { @@ -369,8 +380,8 @@ public boolean isGeneratedNameSameAsOriginal() { } /** - * Compares suggested directory of current linkedFile with existing filepath directory. - * + * Compares suggested directory of current linkedFile with existing filepath + * directory. * @return true if suggested filepath is same as existing filepath. */ public boolean isGeneratedPathSameAsOriginal() { @@ -382,14 +393,13 @@ public boolean isGeneratedPathSameAsOriginal() { } // append File directory pattern if exits - String targetDirectoryName = FileUtil.createDirNameFromPattern( - databaseContext.getDatabase(), - entry, + String targetDirectoryName = FileUtil.createDirNameFromPattern(databaseContext.getDatabase(), entry, filePreferences.getFileDirectoryPattern()); Optional targetDir = baseDir.map(dir -> dir.resolve(targetDirectoryName)); - Optional currentDir = linkedFile.findIn(databaseContext, preferences.getFilePreferences()).map(Path::getParent); + Optional currentDir = linkedFile.findIn(databaseContext, preferences.getFilePreferences()) + .map(Path::getParent); if (currentDir.isEmpty()) { // Could not find file return false; @@ -398,7 +408,8 @@ public boolean isGeneratedPathSameAsOriginal() { BiPredicate equality = (fileA, fileB) -> { try { return Files.isSameFile(fileA, fileB); - } catch (IOException e) { + } + catch (IOException e) { return false; } }; @@ -411,20 +422,22 @@ public void moveToDefaultDirectoryAndRename() { } /** - * Asks the user for confirmation that he really wants to the delete the file from disk (or just remove the link) - * and then proceeds accordingly. - * - * @return true if the linked file has been removed afterward from the entry (i.e., because it was deleted - * successfully, does not exist in the first place, or the user choose to remove it) + * Asks the user for confirmation that he really wants to the delete the file from + * disk (or just remove the link) and then proceeds accordingly. + * @return true if the linked file has been removed afterward from the entry (i.e., + * because it was deleted successfully, does not exist in the first place, or the user + * choose to remove it) */ public boolean delete() { - DeleteFileAction deleteFileAction = new DeleteFileAction(dialogService, preferences.getFilePreferences(), databaseContext, null, List.of(this)); + DeleteFileAction deleteFileAction = new DeleteFileAction(dialogService, preferences.getFilePreferences(), + databaseContext, null, List.of(this)); deleteFileAction.execute(); return deleteFileAction.isSuccess(); } public void edit() { - Optional editedFile = dialogService.showCustomDialogAndWait(new LinkedFileEditDialog(this.linkedFile)); + Optional editedFile = dialogService + .showCustomDialogAndWait(new LinkedFileEditDialog(this.linkedFile)); editedFile.ifPresent(file -> { this.linkedFile.setLink(file.getLink()); this.linkedFile.setDescription(file.getDescription()); @@ -434,27 +447,21 @@ public void edit() { } /** - * @implNote Similar method {@link org.jabref.gui.linkedfile.RedownloadMissingFilesAction#redownloadMissing} + * @implNote Similar method + * {@link org.jabref.gui.linkedfile.RedownloadMissingFilesAction#redownloadMissing} */ public void redownload() { LOGGER.info("Redownloading file from {}", linkedFile.getSourceUrl()); if (linkedFile.getSourceUrl().isEmpty() || !LinkedFile.isOnlineLink(linkedFile.getSourceUrl())) { - throw new UnsupportedOperationException("In order to download the file, the source url has to be an online link"); + throw new UnsupportedOperationException( + "In order to download the file, the source url has to be an online link"); } String fileName = Path.of(linkedFile.getLink()).getFileName().toString(); - DownloadLinkedFileAction downloadLinkedFileAction = new DownloadLinkedFileAction( - databaseContext, - entry, - linkedFile, - linkedFile.getSourceUrl(), - dialogService, - preferences.getExternalApplicationsPreferences(), - preferences.getFilePreferences(), - taskExecutor, - fileName, - true); + DownloadLinkedFileAction downloadLinkedFileAction = new DownloadLinkedFileAction(databaseContext, entry, + linkedFile, linkedFile.getSourceUrl(), dialogService, preferences.getExternalApplicationsPreferences(), + preferences.getFilePreferences(), taskExecutor, fileName, true); downloadProgress.bind(downloadLinkedFileAction.downloadProgress()); downloadLinkedFileAction.execute(); } @@ -465,17 +472,9 @@ public void download(boolean keepHtmlLink) { throw new UnsupportedOperationException("In order to download the file it has to be an online link"); } - DownloadLinkedFileAction downloadLinkedFileAction = new DownloadLinkedFileAction( - databaseContext, - entry, - linkedFile, - linkedFile.getLink(), - dialogService, - preferences.getExternalApplicationsPreferences(), - preferences.getFilePreferences(), - taskExecutor, - "", - keepHtmlLink); + DownloadLinkedFileAction downloadLinkedFileAction = new DownloadLinkedFileAction(databaseContext, entry, + linkedFile, linkedFile.getLink(), dialogService, preferences.getExternalApplicationsPreferences(), + preferences.getFilePreferences(), taskExecutor, "", keepHtmlLink); downloadProgress.bind(downloadLinkedFileAction.downloadProgress()); downloadLinkedFileAction.execute(); } @@ -497,4 +496,5 @@ public void parsePdfMetadataAndShowMergeDialog() { }); }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java index ff6992be048..cbd2217ba6d 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java @@ -65,42 +65,59 @@ public class LinkedFilesEditor extends HBox implements FieldEditorFX { - @FXML private ListView listView; - @FXML private JabRefIconView fulltextFetcher; - @FXML private ProgressIndicator progressIndicator; + @FXML + private ListView listView; + + @FXML + private JabRefIconView fulltextFetcher; + + @FXML + private ProgressIndicator progressIndicator; private final Field field; + private final BibDatabaseContext databaseContext; + private final SuggestionProvider suggestionProvider; + private final FieldCheckers fieldCheckers; - @Inject private DialogService dialogService; - @Inject private GuiPreferences preferences; - @Inject private BibEntryTypesManager bibEntryTypesManager; - @Inject private JournalAbbreviationRepository abbreviationRepository; - @Inject private TaskExecutor taskExecutor; - @Inject private UndoManager undoManager; + @Inject + private DialogService dialogService; + + @Inject + private GuiPreferences preferences; + + @Inject + private BibEntryTypesManager bibEntryTypesManager; + + @Inject + private JournalAbbreviationRepository abbreviationRepository; + + @Inject + private TaskExecutor taskExecutor; + + @Inject + private UndoManager undoManager; private LinkedFilesEditorViewModel viewModel; private ObservableOptionalValue bibEntry = EasyBind.wrapNullable(new SimpleObjectProperty<>()); + private final UiThreadObservableList decoratedModelList; private ContextMenu activeContextMenu = null; + private ContextMenuFactory contextMenuFactory; - public LinkedFilesEditor(Field field, - BibDatabaseContext databaseContext, - SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers) { + public LinkedFilesEditor(Field field, BibDatabaseContext databaseContext, SuggestionProvider suggestionProvider, + FieldCheckers fieldCheckers) { this.field = field; this.databaseContext = databaseContext; this.suggestionProvider = suggestionProvider; this.fieldCheckers = fieldCheckers; - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); decoratedModelList = new UiThreadObservableList<>(viewModel.filesProperty()); Bindings.bindContentBidirectional(listView.itemsProperty().get(), decoratedModelList); @@ -108,25 +125,18 @@ public LinkedFilesEditor(Field field, @FXML private void initialize() { - this.viewModel = new LinkedFilesEditorViewModel( - field, - suggestionProvider, - dialogService, - databaseContext, - taskExecutor, - fieldCheckers, - preferences, - undoManager); + this.viewModel = new LinkedFilesEditorViewModel(field, suggestionProvider, dialogService, databaseContext, + taskExecutor, fieldCheckers, preferences, undoManager); new ViewModelListCellFactory() - .withStringTooltip(LinkedFileViewModel::getDescriptionAndLink) - .withGraphic(this::createFileDisplay) - .withOnMouseClickedEvent(this::handleItemMouseClick) - .setOnDragDetected(this::handleOnDragDetected) - .setOnDragDropped(this::handleOnDragDropped) - .setOnDragOver(this::handleOnDragOver) - .withValidation(LinkedFileViewModel::fileExistsValidationStatus) - .install(listView); + .withStringTooltip(LinkedFileViewModel::getDescriptionAndLink) + .withGraphic(this::createFileDisplay) + .withOnMouseClickedEvent(this::handleItemMouseClick) + .setOnDragDetected(this::handleOnDragDetected) + .setOnDragDropped(this::handleOnDragDropped) + .setOnDragOver(this::handleOnDragOver) + .withValidation(LinkedFileViewModel::fileExistsValidationStatus) + .install(listView); listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); fulltextFetcher.visibleProperty().bind(viewModel.fulltextLookupInProgressProperty().not()); @@ -136,7 +146,8 @@ private void initialize() { } private void handleOnDragOver(LinkedFileViewModel originalItem, DragEvent event) { - if ((event.getGestureSource() != originalItem) && event.getDragboard().hasContent(DragAndDropDataFormats.LINKED_FILE)) { + if ((event.getGestureSource() != originalItem) + && event.getDragboard().hasContent(DragAndDropDataFormats.LINKED_FILE)) { event.acceptTransferModes(TransferMode.MOVE); } } @@ -146,7 +157,8 @@ private void handleOnDragDetected(@SuppressWarnings("unused") LinkedFileViewMode if (selectedItem != null) { ClipboardContent content = new ClipboardContent(); Dragboard dragboard = listView.startDragAndDrop(TransferMode.MOVE); - // We have to use the model class here, as the content of the dragboard must be serializable + // We have to use the model class here, as the content of the dragboard must + // be serializable content.put(DragAndDropDataFormats.LINKED_FILE, selectedItem); dragboard.setContent(content); } @@ -189,7 +201,8 @@ private Node createFileDisplay(LinkedFileViewModel linkedFile) { Text link = new Text(); link.textProperty().bind(linkedFile.linkProperty()); link.getStyleClass().setAll("file-row-text"); - EasyBind.subscribe(linkedFile.isAutomaticallyFoundProperty(), found -> link.pseudoClassStateChanged(opacity, found)); + EasyBind.subscribe(linkedFile.isAutomaticallyFoundProperty(), + found -> link.pseudoClassStateChanged(opacity, found)); Text desc = new Text(); desc.textProperty().bind(linkedFile.descriptionProperty()); @@ -204,15 +217,18 @@ private Node createFileDisplay(LinkedFileViewModel linkedFile) { label.textProperty().bind(linkedFile.linkProperty()); label.getStyleClass().setAll("file-row-text"); label.textOverrunProperty().setValue(OverrunStyle.LEADING_ELLIPSIS); - EasyBind.subscribe(linkedFile.isAutomaticallyFoundProperty(), found -> label.pseudoClassStateChanged(opacity, found)); + EasyBind.subscribe(linkedFile.isAutomaticallyFoundProperty(), + found -> label.pseudoClassStateChanged(opacity, found)); HBox info = new HBox(8); HBox.setHgrow(info, Priority.ALWAYS); - info.setStyle("-fx-padding: 0.5em 0 0.5em 0;"); // To align with buttons below which also have 0.5em padding + info.setStyle("-fx-padding: 0.5em 0 0.5em 0;"); // To align with buttons below + // which also have 0.5em padding info.getChildren().setAll(label, progressIndicator); Button acceptAutoLinkedFile = IconTheme.JabRefIcons.AUTO_LINKED_FILE.asButton(); - acceptAutoLinkedFile.setTooltip(new Tooltip(Localization.lang("This file was found automatically. Do you want to link it to this entry?"))); + acceptAutoLinkedFile.setTooltip(new Tooltip( + Localization.lang("This file was found automatically. Do you want to link it to this entry?"))); acceptAutoLinkedFile.visibleProperty().bind(linkedFile.isAutomaticallyFoundProperty()); acceptAutoLinkedFile.managedProperty().bind(linkedFile.isAutomaticallyFoundProperty()); acceptAutoLinkedFile.setOnAction(event -> linkedFile.acceptAsLinked()); @@ -223,12 +239,9 @@ private Node createFileDisplay(LinkedFileViewModel linkedFile) { writeMetadataToPdf.visibleProperty().bind(linkedFile.isOfflinePdfProperty()); writeMetadataToPdf.getStyleClass().setAll("icon-button"); WriteMetadataToSinglePdfAction writeMetadataToSinglePdfAction = new WriteMetadataToSinglePdfAction( - linkedFile.getFile(), - bibEntry.getValueOrElse(new BibEntry()), - databaseContext, dialogService, preferences.getFieldPreferences(), - preferences.getFilePreferences(), preferences.getXmpPreferences(), abbreviationRepository, bibEntryTypesManager, - taskExecutor - ); + linkedFile.getFile(), bibEntry.getValueOrElse(new BibEntry()), databaseContext, dialogService, + preferences.getFieldPreferences(), preferences.getFilePreferences(), preferences.getXmpPreferences(), + abbreviationRepository, bibEntryTypesManager, taskExecutor); writeMetadataToPdf.disableProperty().bind(writeMetadataToSinglePdfAction.executableProperty().not()); writeMetadataToPdf.setOnAction(event -> writeMetadataToSinglePdfAction.execute()); @@ -266,8 +279,9 @@ private void setUpKeyBindings() { } private void deleteAttachedFilesWithConfirmation() { - new DeleteFileAction(dialogService, preferences.getFilePreferences(), databaseContext, - viewModel, listView.getSelectionModel().getSelectedItems()).execute(); + new DeleteFileAction(dialogService, preferences.getFilePreferences(), databaseContext, viewModel, + listView.getSelectionModel().getSelectedItems()) + .execute(); } public LinkedFilesEditorViewModel getViewModel() { @@ -287,7 +301,9 @@ public Parent getNode() { @FXML private void addNewFile() { - dialogService.showCustomDialogAndWait(new LinkedFileEditDialog()).filter(file -> !file.isEmpty()).ifPresent(newLinkedFile -> viewModel.addNewLinkedFile(newLinkedFile)); + dialogService.showCustomDialogAndWait(new LinkedFileEditDialog()) + .filter(file -> !file.isEmpty()) + .ifPresent(newLinkedFile -> viewModel.addNewLinkedFile(newLinkedFile)); } @FXML @@ -303,32 +319,28 @@ private void addFromURL() { private void handleItemMouseClick(LinkedFileViewModel linkedFile, MouseEvent event) { if (event.getButton() == MouseButton.PRIMARY && (event.getClickCount() == 2)) { linkedFile.open(); // Double-click: open file - } else if (activeContextMenu != null && event.getButton() == MouseButton.PRIMARY) { + } + else if (activeContextMenu != null && event.getButton() == MouseButton.PRIMARY) { activeContextMenu.hide(); // Hide context menu if left-click activeContextMenu = null; - } else if (event.getButton() == MouseButton.SECONDARY) { + } + else if (event.getButton() == MouseButton.SECONDARY) { if (activeContextMenu != null) { activeContextMenu.hide(); // Hide any existing context menu activeContextMenu = null; } - SingleContextCommandFactory contextCommandFactory = (action, file) -> - new ContextAction(action, file, databaseContext, bibEntry, preferences, viewModel); + SingleContextCommandFactory contextCommandFactory = (action, file) -> new ContextAction(action, file, + databaseContext, bibEntry, preferences, viewModel); - MultiContextCommandFactory multiContextCommandFactory = (action, files) -> - new MultiContextAction(action, files, databaseContext, bibEntry, preferences, viewModel); + MultiContextCommandFactory multiContextCommandFactory = (action, files) -> new MultiContextAction(action, + files, databaseContext, bibEntry, preferences, viewModel); - contextMenuFactory = new ContextMenuFactory( - dialogService, - preferences, - databaseContext, - bibEntry, - viewModel, - contextCommandFactory, - multiContextCommandFactory - ); + contextMenuFactory = new ContextMenuFactory(dialogService, preferences, databaseContext, bibEntry, + viewModel, contextCommandFactory, multiContextCommandFactory); - ContextMenu contextMenu = contextMenuFactory.createForSelection(listView.getSelectionModel().getSelectedItems()); + ContextMenu contextMenu = contextMenuFactory + .createForSelection(listView.getSelectionModel().getSelectedItems()); contextMenu.show(listView, event.getScreenX(), event.getScreenY()); activeContextMenu = contextMenu; } @@ -338,4 +350,5 @@ private void handleItemMouseClick(LinkedFileViewModel linkedFile, MouseEvent eve public double getWeight() { return 3; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java index b5c488750ad..9888aafff17 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java @@ -49,22 +49,25 @@ import org.slf4j.LoggerFactory; public class LinkedFilesEditorViewModel extends AbstractEditorViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(LinkedFilesEditorViewModel.class); - private final ListProperty files = new SimpleListProperty<>(FXCollections.observableArrayList(LinkedFileViewModel::getObservables)); + private final ListProperty files = new SimpleListProperty<>( + FXCollections.observableArrayList(LinkedFileViewModel::getObservables)); + private final BooleanProperty fulltextLookupInProgress = new SimpleBooleanProperty(false); + private final DialogService dialogService; + private final BibDatabaseContext databaseContext; + private final TaskExecutor taskExecutor; + private final GuiPreferences preferences; public LinkedFilesEditorViewModel(Field field, SuggestionProvider suggestionProvider, - DialogService dialogService, - BibDatabaseContext databaseContext, - TaskExecutor taskExecutor, - FieldCheckers fieldCheckers, - GuiPreferences preferences, - UndoManager undoManager) { + DialogService dialogService, BibDatabaseContext databaseContext, TaskExecutor taskExecutor, + FieldCheckers fieldCheckers, GuiPreferences preferences, UndoManager undoManager) { super(field, suggestionProvider, fieldCheckers, undoManager); @@ -73,47 +76,43 @@ public LinkedFilesEditorViewModel(Field field, SuggestionProvider suggestionP this.taskExecutor = taskExecutor; this.preferences = preferences; - BindingsHelper.bindContentBidirectional( - files, - text, - LinkedFilesEditorViewModel::getStringRepresentation, + BindingsHelper.bindContentBidirectional(files, text, LinkedFilesEditorViewModel::getStringRepresentation, this::parseToFileViewModel); } private static String getStringRepresentation(List files) { // Only serialize linked files, not the ones that are automatically found List filesToSerialize = files.stream() - .filter(file -> !file.isAutomaticallyFound()) - .map(LinkedFileViewModel::getFile) - .collect(Collectors.toList()); + .filter(file -> !file.isAutomaticallyFound()) + .map(LinkedFileViewModel::getFile) + .collect(Collectors.toList()); return FileFieldWriter.getStringRepresentation(filesToSerialize); } /** - * Creates an instance of {@link LinkedFile} based on the given file. - * We try to guess the file type and relativize the path against the given file directories. + * Creates an instance of {@link LinkedFile} based on the given file. We try to guess + * the file type and relativize the path against the given file directories. * - * TODO: Move this method to {@link LinkedFile} as soon as {@link CustomExternalFileType} lives in model. + * TODO: Move this method to {@link LinkedFile} as soon as + * {@link CustomExternalFileType} lives in model. */ - public static LinkedFile fromFile(Path file, List fileDirectories, ExternalApplicationsPreferences externalApplicationsPreferences) { + public static LinkedFile fromFile(Path file, List fileDirectories, + ExternalApplicationsPreferences externalApplicationsPreferences) { String fileExtension = FileUtil.getFileExtension(file).orElse(""); - ExternalFileType suggestedFileType = ExternalFileTypes.getExternalFileTypeByExt(fileExtension, externalApplicationsPreferences) - .orElse(new UnknownExternalFileType(fileExtension)); + ExternalFileType suggestedFileType = ExternalFileTypes + .getExternalFileTypeByExt(fileExtension, externalApplicationsPreferences) + .orElse(new UnknownExternalFileType(fileExtension)); Path relativePath = FileUtil.relativize(file, fileDirectories); return new LinkedFile("", relativePath, suggestedFileType.getName()); } private List parseToFileViewModel(String stringValue) { - return FileFieldParser.parse(stringValue).stream() - .map(linkedFile -> new LinkedFileViewModel( - linkedFile, - entry, - databaseContext, - taskExecutor, - dialogService, - preferences)) - .collect(Collectors.toList()); + return FileFieldParser.parse(stringValue) + .stream() + .map(linkedFile -> new LinkedFileViewModel(linkedFile, entry, databaseContext, taskExecutor, dialogService, + preferences)) + .collect(Collectors.toList()); } public ObservableList getFiles() { @@ -125,13 +124,8 @@ public ListProperty filesProperty() { } public void addNewLinkedFile(LinkedFile linkedFile) { - files.add(new LinkedFileViewModel( - linkedFile, - entry, - databaseContext, - taskExecutor, - dialogService, - preferences)); + files + .add(new LinkedFileViewModel(linkedFile, entry, databaseContext, taskExecutor, dialogService, preferences)); } @Override @@ -141,42 +135,36 @@ public void bindToEntry(BibEntry entry) { if (preferences.getEntryEditorPreferences().autoLinkFilesEnabled()) { LOGGER.debug("Auto-linking files for entry {}", entry); BackgroundTask> findAssociatedNotLinkedFiles = BackgroundTask - .wrap(() -> findAssociatedNotLinkedFiles(entry)) - .onSuccess(list -> { - if (!list.isEmpty()) { - LOGGER.debug("Found non-associated files: {}", list); - files.addAll(list); - } - }); + .wrap(() -> findAssociatedNotLinkedFiles(entry)) + .onSuccess(list -> { + if (!list.isEmpty()) { + LOGGER.debug("Found non-associated files: {}", list); + files.addAll(list); + } + }); taskExecutor.execute(findAssociatedNotLinkedFiles); } } /** - * Find files that are probably associated to the given entry but not yet linked. + * Find files that are probably associated to the given entry but not yet linked. */ private List findAssociatedNotLinkedFiles(BibEntry entry) { List result = new ArrayList<>(); - AutoSetFileLinksUtil util = new AutoSetFileLinksUtil( - databaseContext, - preferences.getExternalApplicationsPreferences(), - preferences.getFilePreferences(), + AutoSetFileLinksUtil util = new AutoSetFileLinksUtil(databaseContext, + preferences.getExternalApplicationsPreferences(), preferences.getFilePreferences(), preferences.getAutoLinkPreferences()); try { List linkedFiles = util.findAssociatedNotLinkedFiles(entry); for (LinkedFile linkedFile : linkedFiles) { - LinkedFileViewModel newLinkedFile = new LinkedFileViewModel( - linkedFile, - entry, - databaseContext, - taskExecutor, - dialogService, - preferences); + LinkedFileViewModel newLinkedFile = new LinkedFileViewModel(linkedFile, entry, databaseContext, + taskExecutor, dialogService, preferences); newLinkedFile.markAsAutomaticallyFound(); result.add(newLinkedFile); } - } catch (IOException e) { + } + catch (IOException e) { dialogService.showErrorDialogAndWait("Error accessing the file system", e); } @@ -189,17 +177,15 @@ public boolean downloadFile(String urlText) { URL url = URLUtil.create(urlText); addFromURLAndDownload(url); return true; - } catch (MalformedURLException exception) { - dialogService.showErrorDialogAndWait( - Localization.lang("Invalid URL"), - exception); + } + catch (MalformedURLException exception) { + dialogService.showErrorDialogAndWait(Localization.lang("Invalid URL"), exception); return false; } } public void fetchFulltext() { - FulltextFetchers fetcher = new FulltextFetchers( - preferences.getImportFormatPreferences(), + FulltextFetchers fetcher = new FulltextFetchers(preferences.getImportFormatPreferences(), preferences.getImporterPreferences()); Optional urlField = entry.getField(StandardField.URL); boolean download_success = false; @@ -207,14 +193,14 @@ public void fetchFulltext() { download_success = downloadFile(urlField.get()); } if (urlField.isEmpty() || !download_success) { - BackgroundTask - .wrap(() -> fetcher.findFullTextPDF(entry)) + BackgroundTask.wrap(() -> fetcher.findFullTextPDF(entry)) .onRunning(() -> fulltextLookupInProgress.setValue(true)) .onFinished(() -> fulltextLookupInProgress.setValue(false)) .onSuccess(url -> { if (url.isPresent()) { addFromURLAndDownload(url.get()); - } else { + } + else { dialogService.notify(Localization.lang("No full text document found")); } }) @@ -224,17 +210,12 @@ public void fetchFulltext() { public void addFromURL() { AttachFileFromURLAction.getUrlForDownloadFromClipBoardOrEntry(dialogService, entry) - .ifPresent(this::downloadFile); + .ifPresent(this::downloadFile); } private void addFromURLAndDownload(URL url) { - LinkedFileViewModel onlineFile = new LinkedFileViewModel( - new LinkedFile(url, ""), - entry, - databaseContext, - taskExecutor, - dialogService, - preferences); + LinkedFileViewModel onlineFile = new LinkedFileViewModel(new LinkedFile(url, ""), entry, databaseContext, + taskExecutor, dialogService, preferences); files.add(onlineFile); onlineFile.download(true); } @@ -242,7 +223,8 @@ private void addFromURLAndDownload(URL url) { public void deleteFile(LinkedFileViewModel file) { if (file.getFile().isOnlineLink()) { removeFileLink(file); - } else { + } + else { boolean deleteSuccessful = file.delete(); if (deleteSuccessful) { files.remove(file); @@ -257,4 +239,5 @@ public void removeFileLink(LinkedFileViewModel file) { public ReadOnlyBooleanProperty fulltextLookupInProgressProperty() { return fulltextLookupInProgress; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/MarkdownEditor.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/MarkdownEditor.java index 7d230d3db2a..9abb07f5ba6 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/MarkdownEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/MarkdownEditor.java @@ -15,9 +15,11 @@ import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter; public class MarkdownEditor extends SimpleEditor { + private final FlexmarkHtmlConverter flexmarkHtmlConverter = FlexmarkHtmlConverter.builder().build(); - public MarkdownEditor(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, GuiPreferences preferences, UndoManager undoManager, UndoAction undoAction, RedoAction redoAction) { + public MarkdownEditor(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + GuiPreferences preferences, UndoManager undoManager, UndoAction undoAction, RedoAction redoAction) { super(field, suggestionProvider, fieldCheckers, preferences, true, undoManager, undoAction, redoAction); } @@ -30,7 +32,8 @@ public void paste() { String htmlText = ClipBoardManager.getHtmlContents(); String mdText = flexmarkHtmlConverter.convert(htmlText); super.replaceSelection(mdText); - } else { + } + else { super.paste(); } } @@ -40,4 +43,5 @@ public void paste() { public void setEditable(boolean isEditable) { getTextInput().setEditable(isEditable); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/OwnerEditor.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/OwnerEditor.java index 823214d6d0f..838d505297f 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/OwnerEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/OwnerEditor.java @@ -21,26 +21,30 @@ public class OwnerEditor extends HBox implements FieldEditorFX { - @FXML private OwnerEditorViewModel viewModel; - @FXML private EditorTextField textField; - - @Inject private GuiPreferences preferences; - @Inject private KeyBindingRepository keyBindingRepository; - @Inject private UndoManager undoManager; - - public OwnerEditor(Field field, - SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers, - UndoAction undoAction, - RedoAction redoAction) { - ViewLoader.view(this) - .root(this) - .load(); + @FXML + private OwnerEditorViewModel viewModel; + + @FXML + private EditorTextField textField; + + @Inject + private GuiPreferences preferences; + + @Inject + private KeyBindingRepository keyBindingRepository; + + @Inject + private UndoManager undoManager; + + public OwnerEditor(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + UndoAction undoAction, RedoAction redoAction) { + ViewLoader.view(this).root(this).load(); this.viewModel = new OwnerEditorViewModel(field, suggestionProvider, preferences, fieldCheckers, undoManager); establishBinding(textField, viewModel.textProperty(), keyBindingRepository, undoAction, redoAction); textField.initContextMenu(EditorMenus.getNameMenu(textField), keyBindingRepository); - new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textField); + new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), + textField); } public OwnerEditorViewModel getViewModel() { @@ -61,4 +65,5 @@ public Parent getNode() { private void setOwner() { viewModel.setOwner(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/OwnerEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/OwnerEditorViewModel.java index 5334c20b731..1803e80bfed 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/OwnerEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/OwnerEditorViewModel.java @@ -8,13 +8,11 @@ import org.jabref.model.entry.field.Field; public class OwnerEditorViewModel extends AbstractEditorViewModel { + private final CliPreferences preferences; - public OwnerEditorViewModel(Field field, - SuggestionProvider suggestionProvider, - CliPreferences preferences, - FieldCheckers fieldCheckers, - UndoManager undoManager) { + public OwnerEditorViewModel(Field field, SuggestionProvider suggestionProvider, CliPreferences preferences, + FieldCheckers fieldCheckers, UndoManager undoManager) { super(field, suggestionProvider, fieldCheckers, undoManager); this.preferences = preferences; } @@ -22,4 +20,5 @@ public OwnerEditorViewModel(Field field, public void setOwner() { text.set(preferences.getOwnerPreferences().getDefaultOwner()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/PersonsEditor.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/PersonsEditor.java index 8a15574dee4..74d930252dc 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/PersonsEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/PersonsEditor.java @@ -23,27 +23,28 @@ public class PersonsEditor extends HBox implements FieldEditorFX { private final PersonsEditorViewModel viewModel; + private final TextInputControl textInput; + private final UiThreadStringProperty decoratedStringProperty; - public PersonsEditor(final Field field, - final SuggestionProvider suggestionProvider, - final FieldCheckers fieldCheckers, - final boolean isMultiLine, - final UndoManager undoManager, - UndoAction undoAction, - RedoAction redoAction) { + public PersonsEditor(final Field field, final SuggestionProvider suggestionProvider, + final FieldCheckers fieldCheckers, final boolean isMultiLine, final UndoManager undoManager, + UndoAction undoAction, RedoAction redoAction) { GuiPreferences preferences = Injector.instantiateModelOrService(GuiPreferences.class); KeyBindingRepository keyBindingRepository = preferences.getKeyBindingRepository(); - this.viewModel = new PersonsEditorViewModel(field, suggestionProvider, preferences.getAutoCompletePreferences(), fieldCheckers, undoManager); + this.viewModel = new PersonsEditorViewModel(field, suggestionProvider, preferences.getAutoCompletePreferences(), + fieldCheckers, undoManager); textInput = isMultiLine ? new EditorTextArea() : new EditorTextField(); decoratedStringProperty = new UiThreadStringProperty(viewModel.textProperty()); establishBinding(textInput, decoratedStringProperty, keyBindingRepository, undoAction, redoAction); ((ContextMenuAddable) textInput).initContextMenu(EditorMenus.getNameMenu(textInput), keyBindingRepository); this.getChildren().add(textInput); - AutoCompletionTextInputBinding.autoComplete(textInput, viewModel::complete, viewModel.getAutoCompletionConverter(), viewModel.getAutoCompletionStrategy()); - new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textInput); + AutoCompletionTextInputBinding.autoComplete(textInput, viewModel::complete, + viewModel.getAutoCompletionConverter(), viewModel.getAutoCompletionStrategy()); + new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), + textInput); } @Override @@ -60,4 +61,5 @@ public Parent getNode() { public void requestFocus() { textInput.requestFocus(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/PersonsEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/PersonsEditorViewModel.java index fb995bef63f..bf7627e35ff 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/PersonsEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/PersonsEditorViewModel.java @@ -21,7 +21,8 @@ public class PersonsEditorViewModel extends AbstractEditorViewModel { private final AutoCompletePreferences preferences; - public PersonsEditorViewModel(Field field, SuggestionProvider suggestionProvider, AutoCompletePreferences preferences, FieldCheckers fieldCheckers, UndoManager undoManager) { + public PersonsEditorViewModel(Field field, SuggestionProvider suggestionProvider, + AutoCompletePreferences preferences, FieldCheckers fieldCheckers, UndoManager undoManager) { super(field, suggestionProvider, fieldCheckers, undoManager); this.preferences = preferences; } @@ -38,4 +39,5 @@ public Collection complete(AutoCompletionBinding.ISuggestionRequest requ public AutoCompletionStrategy getAutoCompletionStrategy() { return new AppendPersonNamesStrategy(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/PopOverUtil.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/PopOverUtil.java index 4989adf0918..e29474669c0 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/PopOverUtil.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/PopOverUtil.java @@ -17,7 +17,8 @@ public class PopOverUtil { - public static void showJournalInfo(Button button, BibEntry entry, DialogService dialogService, TaskExecutor taskExecutor) { + public static void showJournalInfo(Button button, BibEntry entry, DialogService dialogService, + TaskExecutor taskExecutor) { Optional optionalIssn = entry.getField(StandardField.ISSN); Optional optionalJournalName = entry.getFieldOrAlias(StandardField.JOURNAL); @@ -33,20 +34,23 @@ public static void showJournalInfo(Button button, BibEntry entry, DialogService popOver.show(button, 0); BackgroundTask - .wrap(() -> new JournalInfoView().populateJournalInformation(optionalIssn.orElse(""), optionalJournalName.orElse(""))) - .onSuccess(updatedNode -> { - popOver.setContentNode(updatedNode); - popOver.show(button, 0); - }) - .onFailure(exception -> { - popOver.hide(); - String message = Localization.lang("Error while fetching journal information: %0", - exception.getMessage()); - dialogService.notify(message); - }) - .executeWith(taskExecutor); - } else { + .wrap(() -> new JournalInfoView().populateJournalInformation(optionalIssn.orElse(""), + optionalJournalName.orElse(""))) + .onSuccess(updatedNode -> { + popOver.setContentNode(updatedNode); + popOver.show(button, 0); + }) + .onFailure(exception -> { + popOver.hide(); + String message = Localization.lang("Error while fetching journal information: %0", + exception.getMessage()); + dialogService.notify(message); + }) + .executeWith(taskExecutor); + } + else { dialogService.notify(Localization.lang("ISSN or journal name required for fetching journal information")); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java index 9b9537c967e..5e238cac3e1 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java @@ -21,37 +21,39 @@ public class SimpleEditor extends HBox implements FieldEditorFX { private final SimpleEditorViewModel viewModel; + private final TextInputControl textInput; + private final boolean isMultiLine; - public SimpleEditor(final Field field, - final SuggestionProvider suggestionProvider, - final FieldCheckers fieldCheckers, - final GuiPreferences preferences, - final boolean isMultiLine, - final UndoManager undoManager, - UndoAction undoAction, - RedoAction redoAction) { + public SimpleEditor(final Field field, final SuggestionProvider suggestionProvider, + final FieldCheckers fieldCheckers, final GuiPreferences preferences, final boolean isMultiLine, + final UndoManager undoManager, UndoAction undoAction, RedoAction redoAction) { this.viewModel = new SimpleEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager); this.isMultiLine = isMultiLine; textInput = createTextInputControl(); HBox.setHgrow(textInput, Priority.ALWAYS); - establishBinding(textInput, viewModel.textProperty(), preferences.getKeyBindingRepository(), undoAction, redoAction); + establishBinding(textInput, viewModel.textProperty(), preferences.getKeyBindingRepository(), undoAction, + redoAction); - ((ContextMenuAddable) textInput).initContextMenu(new DefaultMenu(textInput), preferences.getKeyBindingRepository()); + ((ContextMenuAddable) textInput).initContextMenu(new DefaultMenu(textInput), + preferences.getKeyBindingRepository()); this.getChildren().add(textInput); if (!isMultiLine) { - AutoCompletionTextInputBinding autoCompleter = AutoCompletionTextInputBinding.autoComplete(textInput, viewModel::complete, viewModel.getAutoCompletionStrategy()); + AutoCompletionTextInputBinding autoCompleter = AutoCompletionTextInputBinding.autoComplete(textInput, + viewModel::complete, viewModel.getAutoCompletionStrategy()); if (suggestionProvider instanceof ContentSelectorSuggestionProvider) { - // If content selector values are present, then we want to show the auto complete suggestions immediately on focus + // If content selector values are present, then we want to show the auto + // complete suggestions immediately on focus autoCompleter.setShowOnFocus(true); } } - new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textInput); + new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), + textInput); } protected TextInputControl createTextInputControl() { @@ -76,4 +78,5 @@ public void requestFocus() { protected TextInputControl getTextInput() { return textInput; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/SimpleEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/SimpleEditorViewModel.java index acae084f6fc..88f2b33c63d 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/SimpleEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/SimpleEditorViewModel.java @@ -10,11 +10,13 @@ public class SimpleEditorViewModel extends AbstractEditorViewModel { - public SimpleEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, UndoManager undoManager) { + public SimpleEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + UndoManager undoManager) { super(field, suggestionProvider, fieldCheckers, undoManager); } public AutoCompletionStrategy getAutoCompletionStrategy() { return new AppendWordsStrategy(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/URLUtil.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/URLUtil.java index 0278036032e..e28097e804b 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/URLUtil.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/URLUtil.java @@ -21,11 +21,11 @@ private URLUtil() { * Look for the last '.' in the link, and return the following characters. *

    * This gives the extension for most reasonably named links. - * * @param link The link * @return The suffix, excluding the dot (e.g. "pdf") */ - public static Optional getSuffix(final String link, ExternalApplicationsPreferences externalApplicationsPreferences) { + public static Optional getSuffix(final String link, + ExternalApplicationsPreferences externalApplicationsPreferences) { String strippedLink = link; try { // Try to strip the query string, if any, to get the correct suffix: @@ -33,7 +33,8 @@ public static Optional getSuffix(final String link, ExternalApplications if ((url.getQuery() != null) && (url.getQuery().length() < (link.length() - 1))) { strippedLink = link.substring(0, link.length() - url.getQuery().length() - 1); } - } catch (MalformedURLException e) { + } + catch (MalformedURLException e) { // Don't report this error, since this getting the suffix is a non-critical // operation, and this error will be triggered and reported elsewhere. } @@ -42,7 +43,8 @@ public static Optional getSuffix(final String link, ExternalApplications int strippedLinkIndex = strippedLink.lastIndexOf('.'); if ((strippedLinkIndex <= 0) || (strippedLinkIndex == (strippedLink.length() - 1))) { suffix = null; - } else { + } + else { suffix = strippedLink.substring(strippedLinkIndex + 1); } if (!ExternalFileTypes.isExternalFileTypeByExt(suffix, externalApplicationsPreferences)) { @@ -51,24 +53,31 @@ public static Optional getSuffix(final String link, ExternalApplications int index = link.lastIndexOf('.'); if ((index <= 0) || (index == (link.length() - 1))) { // No occurrence, or at the end - // Check if there are path separators in the suffix - if so, it is definitely + // Check if there are path separators in the suffix - if so, it is + // definitely // not a proper suffix, so we should give up: if (strippedLink.substring(strippedLinkIndex + 1).indexOf('/') >= 1) { return Optional.empty(); - } else { + } + else { return Optional.of(suffix); // return the first one we found, anyway. } - } else { - // Check if there are path separators in the suffix - if so, it is definitely + } + else { + // Check if there are path separators in the suffix - if so, it is + // definitely // not a proper suffix, so we should give up: if (link.substring(index + 1).indexOf('/') >= 1) { return Optional.empty(); - } else { + } + else { return Optional.of(link.substring(index + 1)); } } - } else { + } + else { return Optional.ofNullable(suffix); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/UrlEditor.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/UrlEditor.java index b31a036b3da..b631255cf97 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/UrlEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/UrlEditor.java @@ -29,24 +29,30 @@ public class UrlEditor extends HBox implements FieldEditorFX { - @FXML private final UrlEditorViewModel viewModel; - @FXML private EditorTextField textField; + @FXML + private final UrlEditorViewModel viewModel; + + @FXML + private EditorTextField textField; + + @Inject + private DialogService dialogService; - @Inject private DialogService dialogService; - @Inject private GuiPreferences preferences; - @Inject private KeyBindingRepository keyBindingRepository; - @Inject private UndoManager undoManager; + @Inject + private GuiPreferences preferences; - public UrlEditor(Field field, - SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers, - UndoAction undoAction, - RedoAction redoAction) { - ViewLoader.view(this) - .root(this) - .load(); + @Inject + private KeyBindingRepository keyBindingRepository; - this.viewModel = new UrlEditorViewModel(field, suggestionProvider, dialogService, preferences, fieldCheckers, undoManager); + @Inject + private UndoManager undoManager; + + public UrlEditor(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + UndoAction undoAction, RedoAction redoAction) { + ViewLoader.view(this).root(this).load(); + + this.viewModel = new UrlEditorViewModel(field, suggestionProvider, dialogService, preferences, fieldCheckers, + undoManager); establishBinding(textField, viewModel.textProperty(), keyBindingRepository, undoAction, redoAction); @@ -54,9 +60,11 @@ public UrlEditor(Field field, textField.initContextMenu(contextMenuSupplier, preferences.getKeyBindingRepository()); // init paste handler for UrlEditor to format pasted url link in textArea - textField.setAdditionalPasteActionHandler(() -> textField.setText(new CleanupUrlFormatter().format(new TrimWhitespaceFormatter().format(textField.getText())))); + textField.setAdditionalPasteActionHandler(() -> textField + .setText(new CleanupUrlFormatter().format(new TrimWhitespaceFormatter().format(textField.getText())))); - new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textField); + new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), + textField); } public UrlEditorViewModel getViewModel() { @@ -77,4 +85,5 @@ public Parent getNode() { private void openExternalLink(ActionEvent event) { viewModel.openExternalLink(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/UrlEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/UrlEditorViewModel.java index 85f05a5c164..f84f2f6bb35 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/UrlEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/UrlEditorViewModel.java @@ -20,22 +20,20 @@ import com.tobiasdiez.easybind.EasyBind; public class UrlEditorViewModel extends AbstractEditorViewModel { + private final DialogService dialogService; + private final GuiPreferences preferences; + private final BooleanProperty validUrlIsNotPresent = new SimpleBooleanProperty(true); - public UrlEditorViewModel(Field field, - SuggestionProvider suggestionProvider, - DialogService dialogService, - GuiPreferences preferences, - FieldCheckers fieldCheckers, UndoManager undoManager) { + public UrlEditorViewModel(Field field, SuggestionProvider suggestionProvider, DialogService dialogService, + GuiPreferences preferences, FieldCheckers fieldCheckers, UndoManager undoManager) { super(field, suggestionProvider, fieldCheckers, undoManager); this.dialogService = dialogService; this.preferences = preferences; - validUrlIsNotPresent.bind( - EasyBind.map(text, input -> StringUtil.isBlank(input) || !URLUtil.isURL(input)) - ); + validUrlIsNotPresent.bind(EasyBind.map(text, input -> StringUtil.isBlank(input) || !URLUtil.isURL(input))); } public boolean isValidUrlIsNotPresent() { @@ -53,8 +51,10 @@ public void openExternalLink() { try { NativeDesktop.openBrowser(text.get(), preferences.getExternalApplicationsPreferences()); - } catch (IOException ex) { + } + catch (IOException ex) { dialogService.notify(Localization.lang("Unable to open link.")); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/WriteMetadataToSinglePdfAction.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/WriteMetadataToSinglePdfAction.java index ea1aa649c4f..62c2c2f0af6 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/WriteMetadataToSinglePdfAction.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/WriteMetadataToSinglePdfAction.java @@ -35,26 +35,29 @@ public class WriteMetadataToSinglePdfAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(WriteMetadataToSinglePdfAction.class); private final LinkedFile linkedFile; + private final BibEntry entry; + private final FieldPreferences fieldPreferences; + private final BibDatabaseContext databaseContext; + private final DialogService dialogService; + private final BibEntryTypesManager bibEntryTypesManager; + private final JournalAbbreviationRepository abbreviationRepository; + private final TaskExecutor taskExecutor; + private final FilePreferences filePreferences; + private final XmpPreferences xmpPreferences; - public WriteMetadataToSinglePdfAction(LinkedFile linkedFile, - BibEntry entry, - BibDatabaseContext databaseContext, - DialogService dialogService, - FieldPreferences fieldPreferences, - FilePreferences filePreferences, - XmpPreferences xmpPreferences, - JournalAbbreviationRepository abbreviationRepository, - BibEntryTypesManager bibEntryTypesManager, - TaskExecutor taskExecutor) { + public WriteMetadataToSinglePdfAction(LinkedFile linkedFile, BibEntry entry, BibDatabaseContext databaseContext, + DialogService dialogService, FieldPreferences fieldPreferences, FilePreferences filePreferences, + XmpPreferences xmpPreferences, JournalAbbreviationRepository abbreviationRepository, + BibEntryTypesManager bibEntryTypesManager, TaskExecutor taskExecutor) { this.linkedFile = linkedFile; this.entry = entry; this.fieldPreferences = fieldPreferences; @@ -72,43 +75,38 @@ public void execute() { BackgroundTask writeTask = BackgroundTask.wrap(() -> { Optional file = linkedFile.findIn(databaseContext, filePreferences); if (file.isEmpty()) { - dialogService.notify(Localization.lang("Failed to write metadata, file %1 not found.", file.map(Path::toString).orElse(""))); - } else { + dialogService.notify(Localization.lang("Failed to write metadata, file %1 not found.", + file.map(Path::toString).orElse(""))); + } + else { try { - writeMetadataToFile(file.get(), entry, databaseContext, abbreviationRepository, bibEntryTypesManager, fieldPreferences, filePreferences, xmpPreferences); + writeMetadataToFile(file.get(), entry, databaseContext, abbreviationRepository, + bibEntryTypesManager, fieldPreferences, filePreferences, xmpPreferences); dialogService.notify(Localization.lang("Success! Finished writing metadata.")); - } catch (IOException | TransformerException ex) { - dialogService.notify(Localization.lang("Error while writing metadata. See the error log for details.")); + } + catch (IOException | TransformerException ex) { + dialogService + .notify(Localization.lang("Error while writing metadata. See the error log for details.")); LOGGER.error("Error while writing metadata to {}", file.map(Path::toString).orElse(""), ex); } } return null; }); - writeTask - .onRunning(() -> setExecutable(false)) - .onFinished(() -> setExecutable(true)); + writeTask.onRunning(() -> setExecutable(false)).onFinished(() -> setExecutable(true)); taskExecutor.execute(writeTask); } - public static synchronized void writeMetadataToFile(Path file, - BibEntry entry, - BibDatabaseContext databaseContext, - JournalAbbreviationRepository abbreviationRepository, - BibEntryTypesManager bibEntryTypesManager, - FieldPreferences fieldPreferences, - FilePreferences filePreferences, - XmpPreferences xmpPreferences) throws IOException, TransformerException, SaveException, ParserConfigurationException { - // Similar code can be found at {@link org.jabref.gui.exporter.WriteMetadataToPdfAction.writeMetadataToFile} + public static synchronized void writeMetadataToFile(Path file, BibEntry entry, BibDatabaseContext databaseContext, + JournalAbbreviationRepository abbreviationRepository, BibEntryTypesManager bibEntryTypesManager, + FieldPreferences fieldPreferences, FilePreferences filePreferences, XmpPreferences xmpPreferences) + throws IOException, TransformerException, SaveException, ParserConfigurationException { + // Similar code can be found at {@link + // org.jabref.gui.exporter.WriteMetadataToPdfAction.writeMetadataToFile} new XmpUtilWriter(xmpPreferences).writeXmp(file, entry, databaseContext.getDatabase()); - EmbeddedBibFilePdfExporter embeddedBibExporter = new EmbeddedBibFilePdfExporter( - databaseContext.getMode(), - bibEntryTypesManager, - fieldPreferences); - embeddedBibExporter.exportToFileByPath( - databaseContext, - filePreferences, - file, - abbreviationRepository); + EmbeddedBibFilePdfExporter embeddedBibExporter = new EmbeddedBibFilePdfExporter(databaseContext.getMode(), + bibEntryTypesManager, fieldPreferences); + embeddedBibExporter.exportToFileByPath(databaseContext, filePreferences, file, abbreviationRepository); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ContextAction.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ContextAction.java index 09de1094123..43b8d4809d5 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ContextAction.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ContextAction.java @@ -16,43 +16,41 @@ public class ContextAction extends SimpleCommand { private final StandardActions command; + private final LinkedFileViewModel linkedFile; + private final LinkedFilesEditorViewModel viewModel; - public ContextAction(StandardActions command, - LinkedFileViewModel linkedFile, - BibDatabaseContext databaseContext, - ObservableOptionalValue bibEntry, - GuiPreferences preferences, - LinkedFilesEditorViewModel viewModel) { + public ContextAction(StandardActions command, LinkedFileViewModel linkedFile, BibDatabaseContext databaseContext, + ObservableOptionalValue bibEntry, GuiPreferences preferences, + LinkedFilesEditorViewModel viewModel) { this.command = command; this.linkedFile = linkedFile; this.viewModel = viewModel; - this.executable.bind( - switch (command) { - case RENAME_FILE_TO_PATTERN -> Bindings.createBooleanBinding( - () -> !linkedFile.getFile().isOnlineLink() - && linkedFile.getFile().findIn(databaseContext, preferences.getFilePreferences()).isPresent() - && !linkedFile.isGeneratedNameSameAsOriginal(), - linkedFile.getFile().linkProperty(), bibEntry.getValue().map(BibEntry::getFieldsObservable).orElse(null)); - case MOVE_FILE_TO_FOLDER, MOVE_FILE_TO_FOLDER_AND_RENAME -> Bindings.createBooleanBinding( - () -> !linkedFile.getFile().isOnlineLink() - && linkedFile.getFile().findIn(databaseContext, preferences.getFilePreferences()).isPresent() - && !linkedFile.isGeneratedPathSameAsOriginal(), - linkedFile.getFile().linkProperty(), bibEntry.getValue().map(BibEntry::getFieldsObservable).orElse(null)); - case DOWNLOAD_FILE -> Bindings.createBooleanBinding( - () -> linkedFile.getFile().isOnlineLink(), - linkedFile.getFile().linkProperty(), bibEntry.getValue().map(BibEntry::getFieldsObservable).orElse(null)); - case REDOWNLOAD_FILE -> Bindings.createBooleanBinding( - () -> !linkedFile.getFile().getSourceUrl().isEmpty(), - linkedFile.getFile().sourceUrlProperty(), bibEntry.getValue().map(BibEntry::getFieldsObservable).orElse(null)); - case OPEN_FILE, OPEN_FOLDER, RENAME_FILE_TO_NAME, DELETE_FILE -> Bindings.createBooleanBinding( - () -> !linkedFile.getFile().isOnlineLink() - && linkedFile.getFile().findIn(databaseContext, preferences.getFilePreferences()).isPresent(), - linkedFile.getFile().linkProperty(), bibEntry.getValue().map(BibEntry::getFieldsObservable).orElse(null)); - default -> BindingsHelper.constantOf(true); - }); + this.executable.bind(switch (command) { + case RENAME_FILE_TO_PATTERN -> Bindings.createBooleanBinding(() -> !linkedFile.getFile().isOnlineLink() + && linkedFile.getFile().findIn(databaseContext, preferences.getFilePreferences()).isPresent() + && !linkedFile.isGeneratedNameSameAsOriginal(), linkedFile.getFile().linkProperty(), + bibEntry.getValue().map(BibEntry::getFieldsObservable).orElse(null)); + case MOVE_FILE_TO_FOLDER, MOVE_FILE_TO_FOLDER_AND_RENAME -> + Bindings.createBooleanBinding(() -> !linkedFile.getFile().isOnlineLink() + && linkedFile.getFile().findIn(databaseContext, preferences.getFilePreferences()).isPresent() + && !linkedFile.isGeneratedPathSameAsOriginal(), linkedFile.getFile().linkProperty(), + bibEntry.getValue().map(BibEntry::getFieldsObservable).orElse(null)); + case DOWNLOAD_FILE -> Bindings.createBooleanBinding(() -> linkedFile.getFile().isOnlineLink(), + linkedFile.getFile().linkProperty(), + bibEntry.getValue().map(BibEntry::getFieldsObservable).orElse(null)); + case REDOWNLOAD_FILE -> Bindings.createBooleanBinding(() -> !linkedFile.getFile().getSourceUrl().isEmpty(), + linkedFile.getFile().sourceUrlProperty(), + bibEntry.getValue().map(BibEntry::getFieldsObservable).orElse(null)); + case OPEN_FILE, OPEN_FOLDER, RENAME_FILE_TO_NAME, DELETE_FILE -> + Bindings.createBooleanBinding(() -> !linkedFile.getFile().isOnlineLink() + && linkedFile.getFile().findIn(databaseContext, preferences.getFilePreferences()).isPresent(), + linkedFile.getFile().linkProperty(), + bibEntry.getValue().map(BibEntry::getFieldsObservable).orElse(null)); + default -> BindingsHelper.constantOf(true); + }); } @Override @@ -71,4 +69,5 @@ public void execute() { case REMOVE_LINK, REMOVE_LINKS -> viewModel.removeFileLink(linkedFile); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ContextMenuFactory.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ContextMenuFactory.java index a6b3262450e..76acf575056 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ContextMenuFactory.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ContextMenuFactory.java @@ -19,20 +19,23 @@ public class ContextMenuFactory { private final DialogService dialogService; + private final GuiPreferences preferences; + private final BibDatabaseContext databaseContext; + private final ObservableOptionalValue bibEntry; + private final LinkedFilesEditorViewModel viewModel; + private final SingleContextCommandFactory singleCommandFactory; + private final MultiContextCommandFactory multiCommandFactory; - public ContextMenuFactory(DialogService dialogService, - GuiPreferences preferences, - BibDatabaseContext databaseContext, - ObservableOptionalValue bibEntry, - LinkedFilesEditorViewModel viewModel, - SingleContextCommandFactory singleCommandFactory, - MultiContextCommandFactory multiCommandFactory) { + public ContextMenuFactory(DialogService dialogService, GuiPreferences preferences, + BibDatabaseContext databaseContext, ObservableOptionalValue bibEntry, + LinkedFilesEditorViewModel viewModel, SingleContextCommandFactory singleCommandFactory, + MultiContextCommandFactory multiCommandFactory) { this.dialogService = dialogService; this.preferences = preferences; this.databaseContext = databaseContext; @@ -45,7 +48,8 @@ public ContextMenuFactory(DialogService dialogService, public ContextMenu createForSelection(ObservableList selectedFiles) { if (selectedFiles.size() > 1) { return createContextMenuForMultiFile(selectedFiles); - } else if (!selectedFiles.isEmpty()) { + } + else if (!selectedFiles.isEmpty()) { return createContextMenuForFile(selectedFiles.getFirst()); } return new ContextMenu(); @@ -55,12 +59,9 @@ private ContextMenu createContextMenuForMultiFile(ObservableList selectedFiles); + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/DefaultMenu.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/DefaultMenu.java index c5f95b72fa8..74bc1b920cd 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/DefaultMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/DefaultMenu.java @@ -21,8 +21,8 @@ public class DefaultMenu implements Supplier> { TextInputControl textInputControl; /** - * The default menu that contains functions for changing the case of text and doing several conversions. - * + * The default menu that contains functions for changing the case of text and doing + * several conversions. * @param textInputControl that this menu will be connected to */ public DefaultMenu(TextInputControl textInputControl) { @@ -30,12 +30,8 @@ public DefaultMenu(TextInputControl textInputControl) { } public List get() { - return List.of( - getCaseChangeMenu(textInputControl), - getConversionMenu(textInputControl), - new SeparatorMenuItem(), - new ProtectedTermsMenu(textInputControl), - new SeparatorMenuItem(), + return List.of(getCaseChangeMenu(textInputControl), getConversionMenu(textInputControl), + new SeparatorMenuItem(), new ProtectedTermsMenu(textInputControl), new SeparatorMenuItem(), getClearFieldMenuItem(textInputControl)); } @@ -45,9 +41,10 @@ private static Menu getCaseChangeMenu(TextInputControl textInputControl) { for (final Formatter caseChanger : Formatters.getCaseChangers()) { MenuItem menuItem = new MenuItem(caseChanger.getName()); - EasyBind.subscribe(textInputControl.textProperty(), value -> menuItem.setDisable(StringUtil.isNullOrEmpty(value))); - menuItem.setOnAction(event -> - textInputControl.textProperty().set(caseChanger.format(textInputControl.textProperty().get()))); + EasyBind.subscribe(textInputControl.textProperty(), + value -> menuItem.setDisable(StringUtil.isNullOrEmpty(value))); + menuItem.setOnAction(event -> textInputControl.textProperty() + .set(caseChanger.format(textInputControl.textProperty().get()))); submenu.getItems().add(menuItem); } @@ -59,9 +56,10 @@ private static Menu getConversionMenu(TextInputControl textInputControl) { for (Formatter converter : Formatters.getConverters()) { MenuItem menuItem = new MenuItem(converter.getName()); - EasyBind.subscribe(textInputControl.textProperty(), value -> menuItem.setDisable(StringUtil.isNullOrEmpty(value))); - menuItem.setOnAction(event -> - textInputControl.textProperty().set(converter.format(textInputControl.textProperty().get()))); + EasyBind.subscribe(textInputControl.textProperty(), + value -> menuItem.setDisable(StringUtil.isNullOrEmpty(value))); + menuItem.setOnAction(event -> textInputControl.textProperty() + .set(converter.format(textInputControl.textProperty().get()))); submenu.getItems().add(menuItem); } @@ -75,4 +73,5 @@ private static MenuItem getClearFieldMenuItem(TextInputControl textInputControl) return menuItem; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorContextAction.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorContextAction.java index 4aee5fa948a..1fcd1f1c0a1 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorContextAction.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorContextAction.java @@ -23,6 +23,7 @@ public class EditorContextAction extends SimpleCommand { private static final boolean SHOW_HANDLES = Properties.IS_TOUCH_SUPPORTED && !OS.OS_X; private final StandardActions command; + private final TextInputControl textInputControl; public EditorContextAction(StandardActions command, TextInputControl textInputControl) { @@ -30,31 +31,39 @@ public EditorContextAction(StandardActions command, TextInputControl textInputCo this.textInputControl = textInputControl; BooleanProperty editableBinding = textInputControl.editableProperty(); - BooleanBinding hasTextBinding = Bindings.createBooleanBinding(() -> textInputControl.getLength() > 0, textInputControl.textProperty()); - BooleanBinding hasStringInClipboardBinding = (BooleanBinding) BindingsHelper.constantOf(Clipboard.getSystemClipboard().hasString()); - BooleanBinding hasSelectionBinding = Bindings.createBooleanBinding(() -> textInputControl.getSelection().getLength() > 0, textInputControl.selectionProperty()); - BooleanBinding allSelectedBinding = Bindings.createBooleanBinding(() -> textInputControl.getSelection().getLength() == textInputControl.getLength()); - BooleanBinding maskTextBinding = (BooleanBinding) BindingsHelper.constantOf(textInputControl instanceof PasswordField); // (maskText("A") != "A"); - BooleanBinding undoableBinding = Bindings.createBooleanBinding(textInputControl::isUndoable, textInputControl.undoableProperty()); - BooleanBinding redoableBinding = Bindings.createBooleanBinding(textInputControl::isRedoable, textInputControl.redoableProperty()); - - this.executable.bind( - switch (command) { - case COPY -> editableBinding.and(maskTextBinding.not()).and(hasSelectionBinding); - case CUT -> maskTextBinding.not().and(hasSelectionBinding); - case PASTE -> editableBinding.and(hasStringInClipboardBinding); - case DELETE -> editableBinding.and(hasSelectionBinding); - case UNDO -> undoableBinding; - case REDO -> redoableBinding; - case SELECT_ALL -> { - if (SHOW_HANDLES) { - yield hasTextBinding.and(allSelectedBinding.not()); - } else { - yield BindingsHelper.constantOf(true); - } - } - default -> BindingsHelper.constantOf(true); - }); + BooleanBinding hasTextBinding = Bindings.createBooleanBinding(() -> textInputControl.getLength() > 0, + textInputControl.textProperty()); + BooleanBinding hasStringInClipboardBinding = (BooleanBinding) BindingsHelper + .constantOf(Clipboard.getSystemClipboard().hasString()); + BooleanBinding hasSelectionBinding = Bindings.createBooleanBinding( + () -> textInputControl.getSelection().getLength() > 0, textInputControl.selectionProperty()); + BooleanBinding allSelectedBinding = Bindings + .createBooleanBinding(() -> textInputControl.getSelection().getLength() == textInputControl.getLength()); + BooleanBinding maskTextBinding = (BooleanBinding) BindingsHelper + .constantOf(textInputControl instanceof PasswordField); // (maskText("A") != + // "A"); + BooleanBinding undoableBinding = Bindings.createBooleanBinding(textInputControl::isUndoable, + textInputControl.undoableProperty()); + BooleanBinding redoableBinding = Bindings.createBooleanBinding(textInputControl::isRedoable, + textInputControl.redoableProperty()); + + this.executable.bind(switch (command) { + case COPY -> editableBinding.and(maskTextBinding.not()).and(hasSelectionBinding); + case CUT -> maskTextBinding.not().and(hasSelectionBinding); + case PASTE -> editableBinding.and(hasStringInClipboardBinding); + case DELETE -> editableBinding.and(hasSelectionBinding); + case UNDO -> undoableBinding; + case REDO -> redoableBinding; + case SELECT_ALL -> { + if (SHOW_HANDLES) { + yield hasTextBinding.and(allSelectedBinding.not()); + } + else { + yield BindingsHelper.constantOf(true); + } + } + default -> BindingsHelper.constantOf(true); + }); } @Override @@ -84,10 +93,15 @@ public static List getDefaultContextMenuItems(TextInputControl textInp } return List.of( - factory.createMenuItem(StandardActions.CUT, new EditorContextAction(StandardActions.CUT, textInputControl)), - factory.createMenuItem(StandardActions.COPY, new EditorContextAction(StandardActions.COPY, textInputControl)), - factory.createMenuItem(StandardActions.PASTE, new EditorContextAction(StandardActions.PASTE, textInputControl)), - factory.createMenuItem(StandardActions.DELETE, new EditorContextAction(StandardActions.DELETE, textInputControl)), + factory.createMenuItem(StandardActions.CUT, + new EditorContextAction(StandardActions.CUT, textInputControl)), + factory.createMenuItem(StandardActions.COPY, + new EditorContextAction(StandardActions.COPY, textInputControl)), + factory.createMenuItem(StandardActions.PASTE, + new EditorContextAction(StandardActions.PASTE, textInputControl)), + factory.createMenuItem(StandardActions.DELETE, + new EditorContextAction(StandardActions.DELETE, textInputControl)), selectAllMenuItem); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorMenus.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorMenus.java index f8c9f4ae6b4..b453b2e49d8 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorMenus.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorMenus.java @@ -23,24 +23,29 @@ import com.tobiasdiez.easybind.EasyBind; /** - * Provides context menus for the text fields of the entry editor. Note that we use {@link Supplier} to prevent an early - * instantiation of the menus. Therefore, they are attached to each text field but instantiation happens on the first - * right-click of the user in that field. The late instantiation is done by {@link - * org.jabref.gui.fieldeditors.EditorTextArea#initContextMenu(java.util.function.Supplier, org.jabref.gui.keyboard.KeyBindingRepository) EditorTextArea#initContextMenu}. + * Provides context menus for the text fields of the entry editor. Note that we use + * {@link Supplier} to prevent an early instantiation of the menus. Therefore, they are + * attached to each text field but instantiation happens on the first right-click of the + * user in that field. The late instantiation is done by + * {@link org.jabref.gui.fieldeditors.EditorTextArea#initContextMenu(java.util.function.Supplier, org.jabref.gui.keyboard.KeyBindingRepository) + * EditorTextArea#initContextMenu}. */ public class EditorMenus { /** - * The default context menu with a specific menu for normalizing person names regarding to BibTex rules. - * + * The default context menu with a specific menu for normalizing person names + * regarding to BibTex rules. * @param textInput text-input-control that this menu will be connected to - * @return menu containing items of the default menu and an item for normalizing person names + * @return menu containing items of the default menu and an item for normalizing + * person names */ public static Supplier> getNameMenu(final TextInputControl textInput) { return () -> { MenuItem normalizeNames = new MenuItem(Localization.lang("Normalize to BibTeX name format")); - EasyBind.subscribe(textInput.textProperty(), value -> normalizeNames.setDisable(StringUtil.isNullOrEmpty(value))); - normalizeNames.setOnAction(event -> textInput.setText(new NormalizeNamesFormatter().format(textInput.getText()))); + EasyBind.subscribe(textInput.textProperty(), + value -> normalizeNames.setDisable(StringUtil.isNullOrEmpty(value))); + normalizeNames + .setOnAction(event -> textInput.setText(new NormalizeNamesFormatter().format(textInput.getText()))); List menuItems = new ArrayList<>(6); menuItems.add(normalizeNames); menuItems.addAll(new DefaultMenu(textInput).get()); @@ -50,16 +55,18 @@ public static Supplier> getNameMenu(final TextInputControl textIn /** * The default context menu with a specific menu copying a DOI/ DOI URL. - * * @param textField text-field that this menu will be connected to - * @return menu containing items of the default menu and an item for copying a DOI/DOI URL + * @return menu containing items of the default menu and an item for copying a DOI/DOI + * URL */ public static Supplier> getDOIMenu(TextField textField, DialogService dialogService) { return () -> { ActionFactory factory = new ActionFactory(); ClipBoardManager clipBoardManager = Injector.instantiateModelOrService(ClipBoardManager.class); - MenuItem copyDoiMenuItem = factory.createMenuItem(StandardActions.COPY_DOI, new CopyDoiUrlAction(textField, StandardActions.COPY_DOI, dialogService, clipBoardManager)); - MenuItem copyDoiUrlMenuItem = factory.createMenuItem(StandardActions.COPY_DOI_URL, new CopyDoiUrlAction(textField, StandardActions.COPY_DOI_URL, dialogService, clipBoardManager)); + MenuItem copyDoiMenuItem = factory.createMenuItem(StandardActions.COPY_DOI, + new CopyDoiUrlAction(textField, StandardActions.COPY_DOI, dialogService, clipBoardManager)); + MenuItem copyDoiUrlMenuItem = factory.createMenuItem(StandardActions.COPY_DOI_URL, + new CopyDoiUrlAction(textField, StandardActions.COPY_DOI_URL, dialogService, clipBoardManager)); List menuItems = new ArrayList<>(); menuItems.add(copyDoiMenuItem); menuItems.add(copyDoiUrlMenuItem); @@ -71,7 +78,6 @@ public static Supplier> getDOIMenu(TextField textField, DialogSer /** * The default context menu with a specific menu item to cleanup URL. - * * @param textField text field that this menu will be connected to * @return menu containing items of the default menu and an item to cleanup a URL */ @@ -85,4 +91,5 @@ public static Supplier> getCleanupUrlMenu(TextField textField) { return menuItems; }; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/MultiContextAction.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/MultiContextAction.java index 1b868dbc9f5..51cef5072b4 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/MultiContextAction.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/MultiContextAction.java @@ -19,18 +19,20 @@ public class MultiContextAction extends SimpleCommand { private final StandardActions command; + private final ObservableList selectedFiles; + private final BibDatabaseContext databaseContext; + private final ObservableOptionalValue bibEntry; + private final GuiPreferences preferences; + private final LinkedFilesEditorViewModel viewModel; - public MultiContextAction(StandardActions command, - ObservableList selectedFiles, - BibDatabaseContext databaseContext, - ObservableOptionalValue bibEntry, - GuiPreferences preferences, - LinkedFilesEditorViewModel viewModel) { + public MultiContextAction(StandardActions command, ObservableList selectedFiles, + BibDatabaseContext databaseContext, ObservableOptionalValue bibEntry, GuiPreferences preferences, + LinkedFilesEditorViewModel viewModel) { this.command = command; this.selectedFiles = selectedFiles; this.databaseContext = databaseContext; @@ -38,10 +40,7 @@ public MultiContextAction(StandardActions command, this.preferences = preferences; this.viewModel = viewModel; - this.executable.bind(Bindings.createBooleanBinding( - () -> !selectedFiles.isEmpty(), - selectedFiles - )); + this.executable.bind(Bindings.createBooleanBinding(() -> !selectedFiles.isEmpty(), selectedFiles)); } @Override @@ -51,4 +50,5 @@ public void execute() { new ContextAction(command, linkedFile, databaseContext, bibEntry, preferences, viewModel).execute(); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ProtectedTermsMenu.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ProtectedTermsMenu.java index 6fd3b673019..37f2f278353 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ProtectedTermsMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ProtectedTermsMenu.java @@ -25,7 +25,9 @@ class ProtectedTermsMenu extends Menu { private static Formatter FORMATTER; + private final TextInputControl textInputControl; + private final ActionFactory factory = new ActionFactory(); private final Action protectSelectionActionInformation = new Action() { @@ -58,6 +60,7 @@ public String getDescription() { }; private class ProtectSelectionAction extends SimpleCommand { + ProtectSelectionAction() { this.executable.bind(textInputControl.selectedTextProperty().isNotEmpty()); } @@ -67,7 +70,8 @@ public void execute() { String selectedText = textInputControl.getSelectedText(); String firstStr = "{"; String lastStr = "}"; - // If the selected text contains spaces at the beginning and end, then add spaces before or after the brackets + // If the selected text contains spaces at the beginning and end, then add + // spaces before or after the brackets if (selectedText.startsWith(" ")) { firstStr = " {"; } @@ -76,6 +80,7 @@ public void execute() { } textInputControl.replaceSelection(firstStr + selectedText.strip() + lastStr); } + } private class UnprotectSelectionAction extends SimpleCommand { @@ -90,9 +95,11 @@ public void execute() { String formattedString = new UnprotectTermsFormatter().format(selectedText); textInputControl.replaceSelection(formattedString); } + } private class FormatFieldAction extends SimpleCommand { + FormatFieldAction() { this.executable.bind(textInputControl.textProperty().isNotEmpty()); } @@ -101,9 +108,11 @@ private class FormatFieldAction extends SimpleCommand { public void execute() { textInputControl.setText(FORMATTER.format(textInputControl.getText())); } + } private class AddToProtectedTermsAction extends SimpleCommand { + ProtectedTermsList list; public AddToProtectedTermsAction(ProtectedTermsList list) { @@ -129,11 +138,13 @@ public void execute() { ++endIdx; } list.addProtectedTerm(text.substring(beginIdx, endIdx)); - } else { + } + else { // Remove leading and trailing whitespaces list.addProtectedTerm(textInputControl.getSelectedText().strip()); } } + } public ProtectedTermsMenu(final TextInputControl textInputControl) { @@ -142,8 +153,7 @@ public ProtectedTermsMenu(final TextInputControl textInputControl) { FORMATTER = new ProtectTermsFormatter(Injector.instantiateModelOrService(ProtectedTermsLoader.class)); getItems().addAll(factory.createMenuItem(protectSelectionActionInformation, new ProtectSelectionAction()), - getExternalFilesMenu(), - new SeparatorMenuItem(), + getExternalFilesMenu(), new SeparatorMenuItem(), factory.createMenuItem(() -> Localization.lang("Format field"), new FormatFieldAction()), factory.createMenuItem(unprotectSelectionActionInformation, new UnprotectSelectionAction())); } @@ -151,10 +161,11 @@ public ProtectedTermsMenu(final TextInputControl textInputControl) { private Menu getExternalFilesMenu() { Menu protectedTermsMenu = factory.createSubMenu(() -> Localization.lang("Add selected text to list")); ProtectedTermsLoader loader = Injector.instantiateModelOrService(ProtectedTermsLoader.class); - loader.getProtectedTermsLists().stream() - .filter(list -> !list.isInternalList()) - .forEach(list -> protectedTermsMenu.getItems().add( - factory.createMenuItem(list::getDescription, new AddToProtectedTermsAction(list)))); + loader.getProtectedTermsLists() + .stream() + .filter(list -> !list.isInternalList()) + .forEach(list -> protectedTermsMenu.getItems() + .add(factory.createMenuItem(list::getDescription, new AddToProtectedTermsAction(list)))); if (protectedTermsMenu.getItems().isEmpty()) { MenuItem emptyItem = new MenuItem(Localization.lang("No list enabled")); @@ -164,4 +175,5 @@ private Menu getExternalFilesMenu() { return protectedTermsMenu; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/BaseIdentifierEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/BaseIdentifierEditorViewModel.java index 4d01cae5e41..efbaecab799 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/BaseIdentifierEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/BaseIdentifierEditorViewModel.java @@ -31,25 +31,32 @@ import org.slf4j.LoggerFactory; public abstract class BaseIdentifierEditorViewModel extends AbstractEditorViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(BaseIdentifierEditorViewModel.class); + protected BooleanProperty isInvalidIdentifier = new SimpleBooleanProperty(); + protected final BooleanProperty canShortenIdentifier = new SimpleBooleanProperty(false); + protected final BooleanProperty identifierLookupInProgress = new SimpleBooleanProperty(false); + protected final BooleanProperty canLookupIdentifier = new SimpleBooleanProperty(true); + protected final BooleanProperty canFetchBibliographyInformationById = new SimpleBooleanProperty(); + protected IdentifierParser identifierParser; + protected final ObjectProperty> identifier = new SimpleObjectProperty<>(Optional.empty()); + protected DialogService dialogService; + protected TaskExecutor taskExecutor; + protected GuiPreferences preferences; - public BaseIdentifierEditorViewModel(Field field, - SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers, - DialogService dialogService, - TaskExecutor taskExecutor, - GuiPreferences preferences, - UndoManager undoManager) { + public BaseIdentifierEditorViewModel(Field field, SuggestionProvider suggestionProvider, + FieldCheckers fieldCheckers, DialogService dialogService, TaskExecutor taskExecutor, + GuiPreferences preferences, UndoManager undoManager) { super(field, suggestionProvider, fieldCheckers, undoManager); this.dialogService = dialogService; this.taskExecutor = taskExecutor; @@ -57,13 +64,16 @@ public BaseIdentifierEditorViewModel(Field field, } /** - * Since it's not possible to perform the same actions on all identifiers, specific implementations can call the {@code configure} - * method to tell the actions they can perform and the actions they can't. Based on this configuration, the view will enable/disable or - * show/hide certain UI elements for certain identifier editors. + * Since it's not possible to perform the same actions on all identifiers, specific + * implementations can call the {@code configure} method to tell the actions they can + * perform and the actions they can't. Based on this configuration, the view will + * enable/disable or show/hide certain UI elements for certain identifier editors. *

    - * NOTE: This method MUST be called by all the implementation view models in their principal constructor + * NOTE: This method MUST be called by all the implementation view models in their + * principal constructor */ - protected final void configure(boolean canFetchBibliographyInformationById, boolean canLookupIdentifier, boolean canShortenIdentifier) { + protected final void configure(boolean canFetchBibliographyInformationById, boolean canLookupIdentifier, + boolean canShortenIdentifier) { this.canLookupIdentifier.set(canLookupIdentifier); this.canFetchBibliographyInformationById.set(canFetchBibliographyInformationById); this.canShortenIdentifier.set(canShortenIdentifier); @@ -81,13 +91,20 @@ protected Optional updateIdentifier() { protected void handleIdentifierFetchingError(Exception exception, IdFetcher fetcher) { LOGGER.error("Error while fetching identifier", exception); if (exception instanceof FetcherClientException) { - dialogService.showInformationDialogAndWait(Localization.lang("Look up %0", fetcher.getName()), Localization.lang("No data was found for the identifier")); - } else if (exception instanceof FetcherServerException) { - dialogService.showInformationDialogAndWait(Localization.lang("Look up %0", fetcher.getName()), Localization.lang("Server not available")); - } else if (exception.getCause() != null) { - dialogService.showWarningDialogAndWait(Localization.lang("Look up %0", fetcher.getName()), Localization.lang("Error occurred %0", exception.getCause().getMessage())); - } else { - dialogService.showWarningDialogAndWait(Localization.lang("Look up %0", fetcher.getName()), Localization.lang("Error occurred %0", exception.getCause().getMessage())); + dialogService.showInformationDialogAndWait(Localization.lang("Look up %0", fetcher.getName()), + Localization.lang("No data was found for the identifier")); + } + else if (exception instanceof FetcherServerException) { + dialogService.showInformationDialogAndWait(Localization.lang("Look up %0", fetcher.getName()), + Localization.lang("Server not available")); + } + else if (exception.getCause() != null) { + dialogService.showWarningDialogAndWait(Localization.lang("Look up %0", fetcher.getName()), + Localization.lang("Error occurred %0", exception.getCause().getMessage())); + } + else { + dialogService.showWarningDialogAndWait(Localization.lang("Look up %0", fetcher.getName()), + Localization.lang("Error occurred %0", exception.getCause().getMessage())); } } @@ -141,17 +158,18 @@ public void lookupIdentifier(BibEntry bibEntry) { public void openExternalLink() { identifier.get().flatMap(Identifier::getExternalURI).ifPresent(url -> { - try { - NativeDesktop.openBrowser(url, preferences.getExternalApplicationsPreferences()); - } catch (IOException ex) { - dialogService.showErrorDialogAndWait(Localization.lang("Unable to open link."), ex); - } - } - ); + try { + NativeDesktop.openBrowser(url, preferences.getExternalApplicationsPreferences()); + } + catch (IOException ex) { + dialogService.showErrorDialogAndWait(Localization.lang("Unable to open link."), ex); + } + }); } public void shortenID() throws UnsupportedOperationException { - throw new UnsupportedOperationException("Shortening of identifiers is not supported by this identifier editor."); + throw new UnsupportedOperationException( + "Shortening of identifiers is not supported by this identifier editor."); } @Override @@ -161,4 +179,5 @@ public void bindToEntry(BibEntry entry) { EasyBind.subscribe(textProperty(), ignored -> updateIdentifier()); EasyBind.subscribe(identifier, newIdentifier -> isInvalidIdentifier.set(newIdentifier.isEmpty())); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/DoiIdentifierEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/DoiIdentifierEditorViewModel.java index 91f60f0bd95..feb415b9ea8 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/DoiIdentifierEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/DoiIdentifierEditorViewModel.java @@ -22,20 +22,20 @@ import org.slf4j.LoggerFactory; public class DoiIdentifierEditorViewModel extends BaseIdentifierEditorViewModel { + public static final Logger LOGGER = LoggerFactory.getLogger(DoiIdentifierEditorViewModel.class); private final UndoManager undoManager; + private final StateManager stateManager; + private final ShortenDOIFormatter shortenDOIFormatter; - public DoiIdentifierEditorViewModel(SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers, - DialogService dialogService, - TaskExecutor taskExecutor, - GuiPreferences preferences, - UndoManager undoManager, - StateManager stateManager) { - super(StandardField.DOI, suggestionProvider, fieldCheckers, dialogService, taskExecutor, preferences, undoManager); + public DoiIdentifierEditorViewModel(SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + DialogService dialogService, TaskExecutor taskExecutor, GuiPreferences preferences, UndoManager undoManager, + StateManager stateManager) { + super(StandardField.DOI, suggestionProvider, fieldCheckers, dialogService, taskExecutor, preferences, + undoManager); this.undoManager = undoManager; this.stateManager = stateManager; this.shortenDOIFormatter = new ShortenDOIFormatter(); @@ -52,25 +52,28 @@ public void lookupIdentifier(BibEntry bibEntry) { .onSuccess(identifier -> { if (identifier.isPresent()) { entry.setField(field, identifier.get().asString()); - } else { + } + else { dialogService.notify(Localization.lang("No %0 found", field.getDisplayName())); } - }).onFailure(e -> handleIdentifierFetchingError(e, doiFetcher)).executeWith(taskExecutor); + }) + .onFailure(e -> handleIdentifierFetchingError(e, doiFetcher)) + .executeWith(taskExecutor); } @Override public void fetchBibliographyInformation(BibEntry bibEntry) { - stateManager.getActiveDatabase().ifPresentOrElse( - databaseContext -> new FetchAndMergeEntry(databaseContext, taskExecutor, preferences, dialogService, undoManager) + stateManager.getActiveDatabase() + .ifPresentOrElse( + databaseContext -> new FetchAndMergeEntry(databaseContext, taskExecutor, preferences, dialogService, + undoManager) .fetchAndMerge(entry, field), - () -> dialogService.notify(Localization.lang("No library selected")) - ); + () -> dialogService.notify(Localization.lang("No library selected"))); } @Override public void openExternalLink() { - identifier.get().map(DOI::asString) - .ifPresent(s -> NativeDesktop.openCustomDoi(s, preferences, dialogService)); + identifier.get().map(DOI::asString).ifPresent(s -> NativeDesktop.openCustomDoi(s, preferences, dialogService)); } @Override @@ -81,10 +84,12 @@ public void shortenID() { if (shortenedDOI.equals(doi)) { LOGGER.info("DOI is already shortened"); dialogService.notify(Localization.lang("DOI is already shortened")); - } else { - LOGGER.info("Shortened DOI: {} to {}", doi, shortenedDOI); - dialogService.notify(Localization.lang("Shortened DOI to: %0", shortenedDOI)); + } + else { + LOGGER.info("Shortened DOI: {} to {}", doi, shortenedDOI); + dialogService.notify(Localization.lang("Shortened DOI to: %0", shortenedDOI)); } }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/EprintIdentifierEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/EprintIdentifierEditorViewModel.java index 28f6f015799..e81e131c7ec 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/EprintIdentifierEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/EprintIdentifierEditorViewModel.java @@ -21,7 +21,8 @@ public class EprintIdentifierEditorViewModel extends BaseIdentifierEditorViewModel { - // The following listener will be wrapped in a weak reference change listener, thus it will be garbage collected + // The following listener will be wrapped in a weak reference change listener, thus it + // will be garbage collected // automatically once this object is disposed. // https://en.wikipedia.org/wiki/Lapsed_listener_problem private MapChangeListener eprintTypeFieldListener = change -> { @@ -31,19 +32,19 @@ public class EprintIdentifierEditorViewModel extends BaseIdentifierEditorViewMod } }; - public EprintIdentifierEditorViewModel(SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers, - DialogService dialogService, - TaskExecutor taskExecutor, - GuiPreferences preferences, - UndoManager undoManager) { - super(StandardField.EPRINT, suggestionProvider, fieldCheckers, dialogService, taskExecutor, preferences, undoManager); + public EprintIdentifierEditorViewModel(SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + DialogService dialogService, TaskExecutor taskExecutor, GuiPreferences preferences, + UndoManager undoManager) { + super(StandardField.EPRINT, suggestionProvider, fieldCheckers, dialogService, taskExecutor, preferences, + undoManager); configure(false, false, false); EasyBind.subscribe(identifier, newIdentifier -> newIdentifier.ifPresent(id -> { - // TODO: We already have a common superclass between ArXivIdentifier and ARK. This could be refactored further. + // TODO: We already have a common superclass between ArXivIdentifier and ARK. + // This could be refactored further. if (id instanceof ArXivIdentifier) { configure(true, false, false); - } else if (id instanceof ARK) { + } + else if (id instanceof ARK) { configure(false, false, false); } })); @@ -52,9 +53,13 @@ public EprintIdentifierEditorViewModel(SuggestionProvider suggestionProvider, @Override public void bindToEntry(BibEntry entry) { super.bindToEntry(entry); - // Unlike other identifiers (they only depend on their own field value), eprint depends on eprinttype thus - // its identity changes whenever the eprinttype field changes .e.g. If eprinttype equals 'arxiv' then the eprint identity - // will be of type ArXivIdentifier and if it equals 'ark' then it switches to type ARK. + // Unlike other identifiers (they only depend on their own field value), eprint + // depends on eprinttype thus + // its identity changes whenever the eprinttype field changes .e.g. If eprinttype + // equals 'arxiv' then the eprint identity + // will be of type ArXivIdentifier and if it equals 'ark' then it switches to type + // ARK. entry.getFieldsObservable().addListener(new WeakMapChangeListener<>(eprintTypeFieldListener)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/ISBNIdentifierEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/ISBNIdentifierEditorViewModel.java index ebd9115594c..cca84d1f066 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/ISBNIdentifierEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/ISBNIdentifierEditorViewModel.java @@ -15,17 +15,16 @@ import org.jabref.model.entry.identifier.ISBN; public class ISBNIdentifierEditorViewModel extends BaseIdentifierEditorViewModel { + private final UndoManager undoManager; + private final StateManager stateManager; - public ISBNIdentifierEditorViewModel(SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers, - DialogService dialogService, - TaskExecutor taskExecutor, - GuiPreferences preferences, - UndoManager undoManager, - StateManager stateManager) { - super(StandardField.ISBN, suggestionProvider, fieldCheckers, dialogService, taskExecutor, preferences, undoManager); + public ISBNIdentifierEditorViewModel(SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + DialogService dialogService, TaskExecutor taskExecutor, GuiPreferences preferences, UndoManager undoManager, + StateManager stateManager) { + super(StandardField.ISBN, suggestionProvider, fieldCheckers, dialogService, taskExecutor, preferences, + undoManager); this.undoManager = undoManager; this.stateManager = stateManager; configure(true, false, false); @@ -33,10 +32,12 @@ public ISBNIdentifierEditorViewModel(SuggestionProvider suggestionProvider, @Override public void fetchBibliographyInformation(BibEntry bibEntry) { - stateManager.getActiveDatabase().ifPresentOrElse( - databaseContext -> new FetchAndMergeEntry(databaseContext, taskExecutor, preferences, dialogService, undoManager) + stateManager.getActiveDatabase() + .ifPresentOrElse( + databaseContext -> new FetchAndMergeEntry(databaseContext, taskExecutor, preferences, dialogService, + undoManager) .fetchAndMerge(entry, field), - () -> dialogService.notify(Localization.lang("No library selected")) - ); + () -> dialogService.notify(Localization.lang("No library selected"))); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/IdentifierEditor.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/IdentifierEditor.java index e557900833b..16aa6d8b0fc 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/IdentifierEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/identifier/IdentifierEditor.java @@ -34,58 +34,73 @@ public class IdentifierEditor extends HBox implements FieldEditorFX { - @FXML private BaseIdentifierEditorViewModel viewModel; - @FXML private EditorTextField textField; - @FXML private Button shortenDOIButton; - @FXML private Button fetchInformationByIdentifierButton; - @FXML private Button lookupIdentifierButton; - - @Inject private DialogService dialogService; - @Inject private TaskExecutor taskExecutor; - @Inject private GuiPreferences preferences; - @Inject private UndoManager undoManager; - @Inject private StateManager stateManager; + @FXML + private BaseIdentifierEditorViewModel viewModel; + + @FXML + private EditorTextField textField; + + @FXML + private Button shortenDOIButton; + + @FXML + private Button fetchInformationByIdentifierButton; + + @FXML + private Button lookupIdentifierButton; + + @Inject + private DialogService dialogService; + + @Inject + private TaskExecutor taskExecutor; + + @Inject + private GuiPreferences preferences; + + @Inject + private UndoManager undoManager; + + @Inject + private StateManager stateManager; + private Optional entry = Optional.empty(); - public IdentifierEditor(Field field, - SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers) { + public IdentifierEditor(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers) { // Viewloader must be called after the viewmodel is loaded, // but we need the injected vars to create the viewmodels. Injector.registerExistingAndInject(this); switch (field) { - case DOI -> - this.viewModel = new DoiIdentifierEditorViewModel(suggestionProvider, fieldCheckers, dialogService, taskExecutor, preferences, undoManager, stateManager); - case ISBN -> - this.viewModel = new ISBNIdentifierEditorViewModel(suggestionProvider, fieldCheckers, dialogService, taskExecutor, preferences, undoManager, stateManager); - case EPRINT -> - this.viewModel = new EprintIdentifierEditorViewModel(suggestionProvider, fieldCheckers, dialogService, taskExecutor, preferences, undoManager); + case DOI -> this.viewModel = new DoiIdentifierEditorViewModel(suggestionProvider, fieldCheckers, + dialogService, taskExecutor, preferences, undoManager, stateManager); + case ISBN -> this.viewModel = new ISBNIdentifierEditorViewModel(suggestionProvider, fieldCheckers, + dialogService, taskExecutor, preferences, undoManager, stateManager); + case EPRINT -> this.viewModel = new EprintIdentifierEditorViewModel(suggestionProvider, fieldCheckers, + dialogService, taskExecutor, preferences, undoManager); // TODO: Add support for PMID case null, default -> { assert field != null; - throw new IllegalStateException("Unable to instantiate a view model for identifier field editor '%s'".formatted(field.getDisplayName())); + throw new IllegalStateException("Unable to instantiate a view model for identifier field editor '%s'" + .formatted(field.getDisplayName())); } } - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); textField.textProperty().bindBidirectional(viewModel.textProperty()); - fetchInformationByIdentifierButton.setTooltip( - new Tooltip(Localization.lang("Get bibliographic data from %0", field.getDisplayName()))); - lookupIdentifierButton.setTooltip( - new Tooltip(Localization.lang("Look up %0", field.getDisplayName()))); - shortenDOIButton.setTooltip( - new Tooltip(Localization.lang("Shorten %0", field.getDisplayName()))); + fetchInformationByIdentifierButton + .setTooltip(new Tooltip(Localization.lang("Get bibliographic data from %0", field.getDisplayName()))); + lookupIdentifierButton.setTooltip(new Tooltip(Localization.lang("Look up %0", field.getDisplayName()))); + shortenDOIButton.setTooltip(new Tooltip(Localization.lang("Shorten %0", field.getDisplayName()))); textField.initContextMenu(new DefaultMenu(textField), preferences.getKeyBindingRepository()); - new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textField); + new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), + textField); } public BaseIdentifierEditorViewModel getViewModel() { @@ -122,4 +137,5 @@ private void openExternalLink() { private void shortenID() { viewModel.shortenID(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/journalinfo/JournalInfoView.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/journalinfo/JournalInfoView.java index ce768fc7a6c..bded1622342 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/journalinfo/JournalInfoView.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/journalinfo/JournalInfoView.java @@ -11,25 +11,46 @@ import com.airhacks.afterburner.views.ViewLoader; public class JournalInfoView extends VBox { - @FXML private Label title; - @FXML private Label categories; - @FXML private Label publisher; - @FXML private Label hIndex; - @FXML private Label issn; - @FXML private LineChart sjrChart; - @FXML private LineChart citableDocsPrevious3YearsChart; - @FXML private LineChart citesOutgoingChart; - @FXML private LineChart citesOutgoingPerDocChart; - @FXML private LineChart citesIncomingByRecentlyPublishedChart; - @FXML private LineChart docsThisYearChart; + + @FXML + private Label title; + + @FXML + private Label categories; + + @FXML + private Label publisher; + + @FXML + private Label hIndex; + + @FXML + private Label issn; + + @FXML + private LineChart sjrChart; + + @FXML + private LineChart citableDocsPrevious3YearsChart; + + @FXML + private LineChart citesOutgoingChart; + + @FXML + private LineChart citesOutgoingPerDocChart; + + @FXML + private LineChart citesIncomingByRecentlyPublishedChart; + + @FXML + private LineChart docsThisYearChart; + private final JournalInfoViewModel viewModel; public JournalInfoView() { this.viewModel = new JournalInfoViewModel(); - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); title.textProperty().bind(viewModel.titleProperty()); categories.textProperty().bind(viewModel.categoriesProperty()); @@ -56,4 +77,5 @@ private void bindChartProperties() { citesIncomingByRecentlyPublishedChart.setData(viewModel.getCitesIncomingByRecentlyPublishedData()); docsThisYearChart.setData(viewModel.getDocsThisYearData()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/journalinfo/JournalInfoViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/journalinfo/JournalInfoViewModel.java index 671bcc3024b..9c7b8565167 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/journalinfo/JournalInfoViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/journalinfo/JournalInfoViewModel.java @@ -17,23 +17,42 @@ import org.jabref.logic.journals.JournalInformation; public class JournalInfoViewModel extends AbstractViewModel { + private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(); + private final ReadOnlyStringWrapper country = new ReadOnlyStringWrapper(); + private final ReadOnlyStringWrapper categories = new ReadOnlyStringWrapper(); + private final ReadOnlyStringWrapper publisher = new ReadOnlyStringWrapper(); + private final ReadOnlyStringWrapper scimagoId = new ReadOnlyStringWrapper(); + private final ReadOnlyStringWrapper hIndex = new ReadOnlyStringWrapper(); + private final ReadOnlyStringWrapper issn = new ReadOnlyStringWrapper(); + private final ObservableList> sjrData = FXCollections.observableArrayList(); + private final ObservableList> snipData = FXCollections.observableArrayList(); - private final ObservableList> citableDocsPrevious3YearsData = FXCollections.observableArrayList(); - private final ObservableList> citesOutgoingData = FXCollections.observableArrayList(); - private final ObservableList> citesOutgoingPerDocData = FXCollections.observableArrayList(); - private final ObservableList> citesIncomingByRecentlyPublishedData = FXCollections.observableArrayList(); + + private final ObservableList> citableDocsPrevious3YearsData = FXCollections + .observableArrayList(); + + private final ObservableList> citesOutgoingData = FXCollections + .observableArrayList(); + + private final ObservableList> citesOutgoingPerDocData = FXCollections + .observableArrayList(); + + private final ObservableList> citesIncomingByRecentlyPublishedData = FXCollections + .observableArrayList(); + private final ObservableList> docsThisYearData = FXCollections.observableArrayList(); public void populateJournalInformation(String issn, String journalName) throws FetcherException { - Optional journalInformationOptional = new JournalInformationFetcher().getJournalInformation(issn, journalName); + Optional journalInformationOptional = new JournalInformationFetcher() + .getJournalInformation(issn, journalName); journalInformationOptional.ifPresent(journalInformation -> { setTitle(journalInformation.title()); @@ -48,7 +67,8 @@ public void populateJournalInformation(String issn, String journalName) throws F citableDocsPrevious3YearsData.add(convertToSeries(journalInformation.citableDocsPrevious3Years())); citesOutgoingData.add(convertToSeries(journalInformation.citesOutgoing())); citesOutgoingPerDocData.add(convertToSeries(journalInformation.citesOutgoingPerDoc())); - citesIncomingByRecentlyPublishedData.add(convertToSeries(journalInformation.citesIncomingByRecentlyPublished())); + citesIncomingByRecentlyPublishedData + .add(convertToSeries(journalInformation.citesIncomingByRecentlyPublished())); docsThisYearData.add(convertToSeries(journalInformation.docsThisYear())); }); } @@ -175,8 +195,8 @@ public XYChart.Series convertToSeries(List private static String getFormattedCategories(JournalInformation journalInformation) { return Arrays.stream(journalInformation.categories().split(",")) - .map(String::trim) - .collect(Collectors.joining("\n")); + .map(String::trim) + .collect(Collectors.joining("\n")); } private static String getFormattedPublisher(JournalInformation journalInformation) { @@ -188,4 +208,5 @@ private static String getFormattedPublisher(JournalInformation journalInformatio } return publisher.toString(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/LanguageEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/LanguageEditorViewModel.java index 4869189f4b6..38fce55a1c9 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/LanguageEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/LanguageEditorViewModel.java @@ -15,9 +15,11 @@ import org.jabref.model.strings.StringUtil; public class LanguageEditorViewModel extends OptionEditorViewModel { + private BibDatabaseMode databaseMode; - public LanguageEditorViewModel(Field field, SuggestionProvider suggestionProvider, BibDatabaseMode databaseMode, FieldCheckers fieldCheckers, UndoManager undoManager) { + public LanguageEditorViewModel(Field field, SuggestionProvider suggestionProvider, BibDatabaseMode databaseMode, + FieldCheckers fieldCheckers, UndoManager undoManager) { super(field, suggestionProvider, fieldCheckers, undoManager); this.databaseMode = databaseMode; } @@ -29,8 +31,9 @@ public StringConverter getStringConverter() { public String toString(Langid object) { if (object == null) { return null; - } else { - return object.getLangid(); // Langid used as both display and value + } + else { + return object.getLangid(); // Langid used as both display and value } } @@ -38,7 +41,8 @@ public String toString(Langid object) { public Langid fromString(String string) { if (StringUtil.isNotBlank(string)) { return Langid.parse(string).orElse(null); - } else { + } + else { return null; } } @@ -52,6 +56,7 @@ public Collection getItems() { @Override public String convertToDisplayText(Langid object) { - return object.getName(); // Langid and display text are the same + return object.getName(); // Langid and display text are the same } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/MonthEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/MonthEditorViewModel.java index b3cff06364b..452ec6f25b1 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/MonthEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/MonthEditorViewModel.java @@ -15,9 +15,11 @@ import org.jabref.model.strings.StringUtil; public class MonthEditorViewModel extends OptionEditorViewModel { + private final BibDatabaseMode databaseMode; - public MonthEditorViewModel(Field field, SuggestionProvider suggestionProvider, BibDatabaseMode databaseMode, FieldCheckers fieldCheckers, UndoManager undoManager) { + public MonthEditorViewModel(Field field, SuggestionProvider suggestionProvider, BibDatabaseMode databaseMode, + FieldCheckers fieldCheckers, UndoManager undoManager) { super(field, suggestionProvider, fieldCheckers, undoManager); this.databaseMode = databaseMode; } @@ -29,10 +31,12 @@ public StringConverter getStringConverter() { public String toString(Month object) { if (object == null) { return null; - } else { + } + else { if (databaseMode == BibDatabaseMode.BIBLATEX) { return String.valueOf(object.getNumber()); - } else { + } + else { return object.getJabRefFormat(); } } @@ -42,7 +46,8 @@ public String toString(Month object) { public Month fromString(String string) { if (StringUtil.isNotBlank(string)) { return Month.parse(string).orElse(null); - } else { + } + else { return null; } } @@ -58,4 +63,5 @@ public Collection getItems() { public String convertToDisplayText(Month object) { return object.getFullName(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/OptionEditor.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/OptionEditor.java index 2cf48833504..b45092cc3ca 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/OptionEditor.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/OptionEditor.java @@ -18,13 +18,14 @@ */ public class OptionEditor extends HBox implements FieldEditorFX { - @FXML private final OptionEditorViewModel viewModel; - @FXML private ComboBox comboBox; + @FXML + private final OptionEditorViewModel viewModel; + + @FXML + private ComboBox comboBox; public OptionEditor(OptionEditorViewModel viewModel) { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); this.viewModel = viewModel; @@ -53,4 +54,5 @@ public void bindToEntry(BibEntry entry) { public Parent getNode() { return this; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/OptionEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/OptionEditorViewModel.java index e365781cfb5..c57f7484b20 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/OptionEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/OptionEditorViewModel.java @@ -13,7 +13,8 @@ public abstract class OptionEditorViewModel extends AbstractEditorViewModel { - public OptionEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, UndoManager undoManager) { + public OptionEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + UndoManager undoManager) { super(field, suggestionProvider, fieldCheckers, undoManager); } @@ -28,7 +29,9 @@ public OptionEditorViewModel(Field field, SuggestionProvider suggestionProvid public abstract Collection getItems(); /** - * Used for filling the ComboBox for selecting a value. Needs to return something meaningful for each item in {@link #getItems()} + * Used for filling the ComboBox for selecting a value. Needs to return something + * meaningful for each item in {@link #getItems()} */ public abstract String convertToDisplayText(T object); + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/CustomFieldEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/CustomFieldEditorViewModel.java index 09524b2e23d..8a602aaec2c 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/CustomFieldEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/CustomFieldEditorViewModel.java @@ -13,7 +13,7 @@ public class CustomFieldEditorViewModel extends StringMapBasedEditorViewModel { public CustomFieldEditorViewModel(Field field, SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers, UndoManager undoManager, List selectorValues) { + FieldCheckers fieldCheckers, UndoManager undoManager, List selectorValues) { super(field, suggestionProvider, fieldCheckers, undoManager, getMap(selectorValues)); } @@ -24,4 +24,5 @@ private static Map getMap(List selectorValues) { } return map; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/EditorTypeEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/EditorTypeEditorViewModel.java index 960572f0079..0064ecf3d52 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/EditorTypeEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/EditorTypeEditorViewModel.java @@ -11,14 +11,13 @@ public class EditorTypeEditorViewModel extends StringMapBasedEditorViewModel { - public EditorTypeEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, UndoManager undoManager) { - super(field, suggestionProvider, fieldCheckers, undoManager, Map.of( - "editor", Localization.lang("Editor"), - "compiler", Localization.lang("Compiler"), - "founder", Localization.lang("Founder"), - "continuator", Localization.lang("Continuator"), - "redactor", Localization.lang("Redactor"), - "reviser", Localization.lang("Reviser"), - "collaborator", Localization.lang("Collaborator"))); + public EditorTypeEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + UndoManager undoManager) { + super(field, suggestionProvider, fieldCheckers, undoManager, + Map.of("editor", Localization.lang("Editor"), "compiler", Localization.lang("Compiler"), "founder", + Localization.lang("Founder"), "continuator", Localization.lang("Continuator"), "redactor", + Localization.lang("Redactor"), "reviser", Localization.lang("Reviser"), "collaborator", + Localization.lang("Collaborator"))); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/GenderEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/GenderEditorViewModel.java index 496b949bca4..10c331b7532 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/GenderEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/GenderEditorViewModel.java @@ -11,14 +11,13 @@ public class GenderEditorViewModel extends StringMapBasedEditorViewModel { - public GenderEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, UndoManager undoManager) { - super(field, suggestionProvider, fieldCheckers, undoManager, Map.of( - "sf", Localization.lang("Female name"), - "sm", Localization.lang("Male name"), - "sn", Localization.lang("Neuter name"), - "pf", Localization.lang("Female names"), - "pm", Localization.lang("Male names"), - "pn", Localization.lang("Neuter names"), - "pp", Localization.lang("Mixed names"))); + public GenderEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + UndoManager undoManager) { + super(field, suggestionProvider, fieldCheckers, undoManager, + Map.of("sf", Localization.lang("Female name"), "sm", Localization.lang("Male name"), "sn", + Localization.lang("Neuter name"), "pf", Localization.lang("Female names"), "pm", + Localization.lang("Male names"), "pn", Localization.lang("Neuter names"), "pp", + Localization.lang("Mixed names"))); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/MapBasedEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/MapBasedEditorViewModel.java index a99106c74f9..21d2c6e85e4 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/MapBasedEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/MapBasedEditorViewModel.java @@ -22,7 +22,8 @@ public abstract class MapBasedEditorViewModel extends OptionEditorViewModel suggestionProvider, FieldCheckers fieldCheckers, UndoManager undoManager) { + public MapBasedEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + UndoManager undoManager) { super(field, suggestionProvider, fieldCheckers, undoManager); } @@ -35,7 +36,8 @@ public StringConverter getStringConverter() { public String toString(T object) { if (object == null) { return null; - } else { + } + else { // if the object is not found we simply return itself as string return getItemMap().inverse().getOrDefault(object, object.toString()); } @@ -45,14 +47,16 @@ public String toString(T object) { public T fromString(String string) { if (string == null) { return null; - } else { + } + else { return getItemMap().getOrDefault(string, getValueFromString(string)); } } }; } - /// Converts a String value to the Type T. If the type cannot be directly cast to T, this method must be overridden in a subclass + /// Converts a String value to the Type T. If the type cannot be directly cast to T, + /// this method must be overridden in a subclass /// /// @param string The input value to convert /// @return The value or null if the value could not be cast @@ -60,7 +64,8 @@ public T fromString(String string) { protected T getValueFromString(String string) { try { return (T) string; - } catch (ClassCastException ex) { + } + catch (ClassCastException ex) { LOGGER.error("Could not cast string to type {}", string.getClass(), ex); } return null; @@ -70,4 +75,5 @@ protected T getValueFromString(String string) { public Collection getItems() { return getItemMap().values(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/PaginationEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/PaginationEditorViewModel.java index 81864bbe575..bdaf4603d0a 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/PaginationEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/PaginationEditorViewModel.java @@ -11,14 +11,13 @@ public class PaginationEditorViewModel extends StringMapBasedEditorViewModel { - public PaginationEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, UndoManager undoManager) { - super(field, suggestionProvider, fieldCheckers, undoManager, Map.of( - "page", Localization.lang("Page"), - "column", Localization.lang("Column"), - "line", Localization.lang("Line"), - "verse", Localization.lang("Verse"), - "section", Localization.lang("Section"), - "paragraph", Localization.lang("Paragraph"), - "none", Localization.lang("None"))); + public PaginationEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + UndoManager undoManager) { + super(field, suggestionProvider, fieldCheckers, undoManager, + Map.of("page", Localization.lang("Page"), "column", Localization.lang("Column"), "line", + Localization.lang("Line"), "verse", Localization.lang("Verse"), "section", + Localization.lang("Section"), "paragraph", Localization.lang("Paragraph"), "none", + Localization.lang("None"))); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/PatentTypeEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/PatentTypeEditorViewModel.java index c602e1db665..042fdd41c99 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/PatentTypeEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/PatentTypeEditorViewModel.java @@ -12,7 +12,8 @@ public class PatentTypeEditorViewModel extends StringMapBasedEditorViewModel { - public PatentTypeEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, UndoManager undoManager) { + public PatentTypeEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + UndoManager undoManager) { super(field, suggestionProvider, fieldCheckers, undoManager, getMap()); } @@ -32,4 +33,5 @@ private static Map getMap() { itemMap.put("patrequs", Localization.lang("U.S. patent request")); return itemMap; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/StringMapBasedEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/StringMapBasedEditorViewModel.java index 9a6d6a7ae5e..d065c1668f2 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/StringMapBasedEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/StringMapBasedEditorViewModel.java @@ -15,7 +15,8 @@ public abstract class StringMapBasedEditorViewModel extends MapBasedEditorViewMo private BiMap itemMap; - public StringMapBasedEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, UndoManager undoManager, Map entries) { + public StringMapBasedEditorViewModel(Field field, SuggestionProvider suggestionProvider, + FieldCheckers fieldCheckers, UndoManager undoManager, Map entries) { super(field, suggestionProvider, fieldCheckers, undoManager); itemMap = HashBiMap.create(entries); @@ -30,4 +31,5 @@ protected BiMap getItemMap() { public String convertToDisplayText(String object) { return object; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/TypeEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/TypeEditorViewModel.java index 5b9b75f1243..6db4107d15b 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/TypeEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/TypeEditorViewModel.java @@ -11,16 +11,14 @@ public class TypeEditorViewModel extends StringMapBasedEditorViewModel { - public TypeEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, UndoManager undoManager) { - super(field, suggestionProvider, fieldCheckers, undoManager, Map.of( - "mathesis", Localization.lang("Master's thesis"), - "phdthesis", Localization.lang("PhD thesis"), - "candthesis", Localization.lang("Candidate thesis"), - "bathesis", Localization.lang("Bachelor's thesis"), - "techreport", Localization.lang("Technical report"), - "resreport", Localization.lang("Research report"), - "software", Localization.lang("Software"), - "datacd", Localization.lang("Data CD"), - "audiocd", Localization.lang("Audio CD"))); + public TypeEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + UndoManager undoManager) { + super(field, suggestionProvider, fieldCheckers, undoManager, + Map.of("mathesis", Localization.lang("Master's thesis"), "phdthesis", Localization.lang("PhD thesis"), + "candthesis", Localization.lang("Candidate thesis"), "bathesis", + Localization.lang("Bachelor's thesis"), "techreport", Localization.lang("Technical report"), + "resreport", Localization.lang("Research report"), "software", Localization.lang("Software"), + "datacd", Localization.lang("Data CD"), "audiocd", Localization.lang("Audio CD"))); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/YesNoEditorViewModel.java b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/YesNoEditorViewModel.java index 856f5372a79..ee19e615a95 100644 --- a/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/YesNoEditorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/YesNoEditorViewModel.java @@ -15,10 +15,9 @@ public class YesNoEditorViewModel extends StringMapBasedEditorViewModel { private BiMap itemMap = HashBiMap.create(2); - public YesNoEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, UndoManager undoManager) { - super(field, suggestionProvider, fieldCheckers, undoManager, Map.of( - "yes", "Yes", - "no", "No" - )); + public YesNoEditorViewModel(Field field, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers, + UndoManager undoManager) { + super(field, suggestionProvider, fieldCheckers, undoManager, Map.of("yes", "Yes", "no", "No")); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/frame/ExternalApplicationsPreferences.java b/jabgui/src/main/java/org/jabref/gui/frame/ExternalApplicationsPreferences.java index dbaa426796b..b58af5f4e5d 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/ExternalApplicationsPreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/ExternalApplicationsPreferences.java @@ -16,23 +16,25 @@ public class ExternalApplicationsPreferences { private final StringProperty eMailSubject; + private final BooleanProperty shouldAutoOpenEmailAttachmentsFolder; private final BooleanProperty useCustomTerminal; + private final StringProperty customTerminalCommand; + private final BooleanProperty useCustomFileBrowser; + private final StringProperty customFileBrowserCommand; + private final StringProperty kindleEmail; - private final ObservableSet externalFileTypes = FXCollections.observableSet(new TreeSet<>(Comparator.comparing(ExternalFileType::getName))); - public ExternalApplicationsPreferences(String eMailSubject, - boolean shouldAutoOpenEmailAttachmentsFolder, - Set externalFileTypes, - boolean useCustomTerminal, - String customTerminalCommand, - boolean useCustomFileBrowser, - String customFileBrowserCommand, - String kindleEmail) { + private final ObservableSet externalFileTypes = FXCollections + .observableSet(new TreeSet<>(Comparator.comparing(ExternalFileType::getName))); + + public ExternalApplicationsPreferences(String eMailSubject, boolean shouldAutoOpenEmailAttachmentsFolder, + Set externalFileTypes, boolean useCustomTerminal, String customTerminalCommand, + boolean useCustomFileBrowser, String customFileBrowserCommand, String kindleEmail) { this.eMailSubject = new SimpleStringProperty(eMailSubject); this.shouldAutoOpenEmailAttachmentsFolder = new SimpleBooleanProperty(shouldAutoOpenEmailAttachmentsFolder); @@ -131,4 +133,5 @@ public void setKindleEmail(String kindleEmail) { public ObservableSet getExternalFileTypes() { return this.externalFileTypes; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/frame/FileHistoryMenu.java b/jabgui/src/main/java/org/jabref/gui/frame/FileHistoryMenu.java index f7cc4f8a70c..ecb1c3727c0 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/FileHistoryMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/FileHistoryMenu.java @@ -17,11 +17,15 @@ public class FileHistoryMenu extends Menu { protected final MenuItem clearRecentLibraries; + private final FileHistory history; + private final DialogService dialogService; + private final OpenDatabaseAction openDatabaseAction; - public FileHistoryMenu(FileHistory fileHistory, DialogService dialogService, OpenDatabaseAction openDatabaseAction) { + public FileHistoryMenu(FileHistory fileHistory, DialogService dialogService, + OpenDatabaseAction openDatabaseAction) { setText(Localization.lang("Recent libraries")); this.clearRecentLibraries = new MenuItem(); @@ -33,7 +37,8 @@ public FileHistoryMenu(FileHistory fileHistory, DialogService dialogService, Ope this.openDatabaseAction = openDatabaseAction; if (history.isEmpty()) { setDisable(true); - } else { + } + else { setItems(); } history.addListener((InvalidationListener) obs -> setItems()); @@ -41,7 +46,6 @@ public FileHistoryMenu(FileHistory fileHistory, DialogService dialogService, Ope /** * This method is to use typed letters to access recent libraries in menu. - * * @param keyEvent a KeyEvent. * @return false if typed char is invalid or not a number. */ @@ -59,8 +63,8 @@ public boolean openFileByKey(KeyEvent keyEvent) { } /** - * Adds the filename to the top of the menu. If it already is in - * the menu, it is merely moved to the top. + * Adds the filename to the top of the menu. If it already is in the menu, it is + * merely moved to the top. */ public void newFile(Path file) { history.newFile(file); @@ -73,20 +77,21 @@ private void setItems() { for (int index = 0; index < history.size(); index++) { addItem(history.get(index), index + 1); } - getItems().addAll( - new SeparatorMenuItem(), - clearRecentLibraries - ); + getItems().addAll(new SeparatorMenuItem(), clearRecentLibraries); } private void addItem(Path file, int num) { String number = Integer.toString(num); MenuItem item = new MenuItem(number + ". " + file); - // By default mnemonic parsing is set to true for anything that is Labeled, if an underscore character - // is present, it would create a key combination ALT+the succeeding character (at least for Windows OS) + // By default mnemonic parsing is set to true for anything that is Labeled, if an + // underscore character + // is present, it would create a key combination ALT+the succeeding character (at + // least for Windows OS) // and the underscore character will be parsed (deleted). - // i.e if the file name was called "bib_test.bib", a key combination "ALT+t" will be created - // so to avoid this, mnemonic parsing should be set to false to print normally the underscore character. + // i.e if the file name was called "bib_test.bib", a key combination "ALT+t" will + // be created + // so to avoid this, mnemonic parsing should be set to false to print normally the + // underscore character. item.setMnemonicParsing(false); item.setOnAction(event -> openFile(file)); getItems().add(item); @@ -94,8 +99,7 @@ private void addItem(Path file, int num) { public void openFile(Path file) { if (!Files.exists(file)) { - this.dialogService.showErrorDialogAndWait( - Localization.lang("File not found"), + this.dialogService.showErrorDialogAndWait(Localization.lang("File not found"), Localization.lang("File not found") + ": " + file); return; } @@ -106,4 +110,5 @@ public void clearLibrariesHistory() { history.clear(); setDisable(true); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/frame/FrameDndHandler.java b/jabgui/src/main/java/org/jabref/gui/frame/FrameDndHandler.java index 1365fcfb959..cd046114b02 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/FrameDndHandler.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/FrameDndHandler.java @@ -29,17 +29,19 @@ import org.slf4j.LoggerFactory; public class FrameDndHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(FrameDndHandler.class); private final TabPane tabPane; + private final Supplier scene; + private final Supplier openDatabaseAction; + private final StateManager stateManager; - public FrameDndHandler(TabPane tabPane, - Supplier scene, - Supplier openDatabaseAction, - StateManager stateManager) { + public FrameDndHandler(TabPane tabPane, Supplier scene, Supplier openDatabaseAction, + StateManager stateManager) { this.tabPane = tabPane; this.scene = scene; this.openDatabaseAction = openDatabaseAction; @@ -60,11 +62,13 @@ void initDragAndDrop() { scene.get().setOnDragOver(event -> onSceneDragOver(event, dndIndicator)); scene.get().setOnDragEntered(event -> { // It is necessary to setOnDragOver for newly opened tabs - // drag'n'drop on tabs covered dnd on tabbedPane, so dnd on tabs should contain all dnds on tabbedPane + // drag'n'drop on tabs covered dnd on tabbedPane, so dnd on tabs should + // contain all dnds on tabbedPane for (Node destinationTabNode : tabPane.lookupAll(".tab")) { destinationTabNode.setOnDragOver(tabDragEvent -> onTabDragOver(event, tabDragEvent, dndIndicator)); destinationTabNode.setOnDragExited(tabDragEvent -> tabPane.getTabs().remove(dndIndicator)); - destinationTabNode.setOnDragDropped(tabDragEvent -> onTabDragDropped(destinationTabNode, tabDragEvent, dndIndicator)); + destinationTabNode.setOnDragDropped( + tabDragEvent -> onTabDragDropped(destinationTabNode, tabDragEvent, dndIndicator)); } event.consume(); }); @@ -83,7 +87,8 @@ private void onTabDragDropped(Node destinationTabNode, DragEvent tabDragEvent, T openDatabaseAction.openFiles(bibFiles); tabDragEvent.setDropCompleted(true); tabDragEvent.consume(); - } else { + } + else { if (stateManager.getActiveDatabase().isEmpty()) { LOGGER.warn("Active library is empty when dropping entries"); return; @@ -91,8 +96,8 @@ private void onTabDragDropped(Node destinationTabNode, DragEvent tabDragEvent, T LibraryTab destinationLibraryTab = null; for (Tab libraryTab : tabPane.getTabs()) { - if (libraryTab.getId().equals(destinationTabNode.getId()) && - !tabPane.getSelectionModel().getSelectedItem().equals(libraryTab)) { + if (libraryTab.getId().equals(destinationTabNode.getId()) + && !tabPane.getSelectionModel().getSelectedItem().equals(libraryTab)) { destinationLibraryTab = (LibraryTab) libraryTab; break; } @@ -104,10 +109,14 @@ private void onTabDragDropped(Node destinationTabNode, DragEvent tabDragEvent, T } if (hasEntries(dragboard)) { - List entryCopies = stateManager.getLocalDragboard().getBibEntries().stream() - .map(BibEntry::new).toList(); + List entryCopies = stateManager.getLocalDragboard() + .getBibEntries() + .stream() + .map(BibEntry::new) + .toList(); destinationLibraryTab.dropEntry(entryCopies); - } else if (hasGroups(dragboard)) { + } + else if (hasGroups(dragboard)) { dropGroups(dragboard, destinationLibraryTab); } @@ -120,20 +129,15 @@ private void dropGroups(Dragboard dragboard, LibraryTab destinationLibraryTab) { copyRootNode(destinationLibraryTab); - GroupTreeNode destinationLibraryGroupRoot = destinationLibraryTab - .getBibDatabaseContext() - .getMetaData() - .getGroups().get(); + GroupTreeNode destinationLibraryGroupRoot = destinationLibraryTab.getBibDatabaseContext() + .getMetaData() + .getGroups() + .get(); - GroupTreeNode groupsTreeNode = stateManager.getActiveDatabase().get() - .getMetaData() - .getGroups() - .get(); + GroupTreeNode groupsTreeNode = stateManager.getActiveDatabase().get().getMetaData().getGroups().get(); for (String pathToSource : groupPathToSources) { - GroupTreeNode groupTreeNodeToCopy = groupsTreeNode - .getChildByPath(pathToSource) - .get(); + GroupTreeNode groupTreeNodeToCopy = groupsTreeNode.getChildByPath(pathToSource).get(); copyGroupTreeNode(destinationLibraryTab, destinationLibraryGroupRoot, groupTreeNodeToCopy); } } @@ -149,7 +153,8 @@ private void onTabDragOver(DragEvent event, DragEvent tabDragEvent, Tab dndIndic tabPane.getTabs().add(dndIndicator); } event.consume(); - } else { + } + else { tabPane.getTabs().remove(dndIndicator); } @@ -166,7 +171,8 @@ private void onSceneDragOver(DragEvent event, Tab dndIndicator) { tabPane.getTabs().add(dndIndicator); } event.consume(); - } else { + } + else { tabPane.getTabs().remove(dndIndicator); } // Accept drag entries from MainTable @@ -192,25 +198,26 @@ private void copyRootNode(LibraryTab destinationLibraryTab) { } // a root (all entries) GroupTreeNode - GroupTreeNode currentLibraryGroupRoot = stateManager.getActiveDatabase().get() - .getMetaData() - .getGroups() - .get() - .copyNode(); + GroupTreeNode currentLibraryGroupRoot = stateManager.getActiveDatabase() + .get() + .getMetaData() + .getGroups() + .get() + .copyNode(); // add currentLibraryGroupRoot to the Library if it does not have a root. - destinationLibraryTab.getBibDatabaseContext() - .getMetaData() - .setGroups(currentLibraryGroupRoot); + destinationLibraryTab.getBibDatabaseContext().getMetaData().setGroups(currentLibraryGroupRoot); } - private void copyGroupTreeNode(LibraryTab destinationLibraryTab, GroupTreeNode parent, GroupTreeNode groupTreeNodeToCopy) { + private void copyGroupTreeNode(LibraryTab destinationLibraryTab, GroupTreeNode parent, + GroupTreeNode groupTreeNodeToCopy) { if (stateManager.getActiveDatabase().isEmpty()) { return; } List allEntries = stateManager.getActiveDatabase().get().getEntries(); - // add groupTreeNodeToCopy to the parent-- in the first run that will the source/main GroupTreeNode + // add groupTreeNodeToCopy to the parent-- in the first run that will the + // source/main GroupTreeNode GroupTreeNode copiedNode = parent.addSubgroup(groupTreeNodeToCopy.copyNode().getGroup()); // add all entries of a groupTreeNode to the new library. destinationLibraryTab.dropEntry(groupTreeNodeToCopy.getEntriesInGroup(allEntries)); @@ -232,8 +239,13 @@ private boolean hasBibFiles(Dragboard dragboard) { private List getBibFiles(Dragboard dragboard) { if (!dragboard.hasFiles()) { return List.of(); - } else { - return dragboard.getFiles().stream().map(File::toPath).filter(FileUtil::isBibFile).collect(Collectors.toList()); + } + else { + return dragboard.getFiles() + .stream() + .map(File::toPath) + .filter(FileUtil::isBibFile) + .collect(Collectors.toList()); } } @@ -244,8 +256,10 @@ private boolean hasGroups(Dragboard dragboard) { private List getGroups(Dragboard dragboard) { if (!dragboard.hasContent(DragAndDropDataFormats.GROUP)) { return List.of(); - } else { + } + else { return (List) dragboard.getContent(DragAndDropDataFormats.GROUP); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java index 815b92e8268..347ab24c1e9 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -77,55 +77,72 @@ * Represents the inner frame of the JabRef window */ public class JabRefFrame extends BorderPane implements LibraryTabContainer, UiMessageHandler { + /** * Defines the different modes that the tab can operate in */ - private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR } + private enum PanelMode { + + MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR + + } public static final String FRAME_TITLE = "JabRef"; private static final Logger LOGGER = LoggerFactory.getLogger(JabRefFrame.class); private final GuiPreferences preferences; + private final AiService aiService; + private final GlobalSearchBar globalSearchBar; private final FileHistoryMenu fileHistory; - @SuppressWarnings({"FieldCanBeLocal"}) private EasyObservableList openDatabaseList; + @SuppressWarnings({ "FieldCanBeLocal" }) + private EasyObservableList openDatabaseList; private final Stage mainStage; + private final StateManager stateManager; + private final CountingUndoManager undoManager; + private final DialogService dialogService; + private final FileUpdateMonitor fileUpdateMonitor; + private final BibEntryTypesManager entryTypesManager; + private final ClipBoardManager clipBoardManager; + private final TaskExecutor taskExecutor; private final JabRefFrameViewModel viewModel; + private final GuiPushToApplicationCommand pushToApplicationCommand; + private final SplitPane horizontalSplit = new SplitPane(); + private final SidePane sidePane; + private final SplitPane verticalSplit = new SplitPane(); + private final TabPane tabbedPane = new TabPane(); + private final EntryEditor entryEditor; + private final ObjectProperty panelMode = new SimpleObjectProperty<>(PanelMode.MAIN_TABLE); - // We need to keep a reference to the subscription, otherwise the binding gets garbage collected + // We need to keep a reference to the subscription, otherwise the binding gets garbage + // collected private Subscription horizontalDividerSubscription; + private Subscription verticalDividerSubscription; - public JabRefFrame(Stage mainStage, - DialogService dialogService, - FileUpdateMonitor fileUpdateMonitor, - GuiPreferences preferences, - AiService aiService, - StateManager stateManager, - CountingUndoManager undoManager, - BibEntryTypesManager entryTypesManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor) { + public JabRefFrame(Stage mainStage, DialogService dialogService, FileUpdateMonitor fileUpdateMonitor, + GuiPreferences preferences, AiService aiService, StateManager stateManager, CountingUndoManager undoManager, + BibEntryTypesManager entryTypesManager, ClipBoardManager clipBoardManager, TaskExecutor taskExecutor) { this.mainStage = mainStage; this.dialogService = dialogService; this.fileUpdateMonitor = fileUpdateMonitor; @@ -140,64 +157,34 @@ public JabRefFrame(Stage mainStage, setId("frame"); // Create components - this.viewModel = new JabRefFrameViewModel( - preferences, - aiService, - stateManager, - dialogService, - this, - this::getOpenDatabaseAction, - entryTypesManager, - fileUpdateMonitor, - undoManager, - clipBoardManager, + this.viewModel = new JabRefFrameViewModel(preferences, aiService, stateManager, dialogService, this, + this::getOpenDatabaseAction, entryTypesManager, fileUpdateMonitor, undoManager, clipBoardManager, taskExecutor); Injector.setModelOrService(UiMessageHandler.class, viewModel); - FrameDndHandler frameDndHandler = new FrameDndHandler( - tabbedPane, - mainStage::getScene, - this::getOpenDatabaseAction, - stateManager); - - this.globalSearchBar = new GlobalSearchBar( - this, - stateManager, - this.preferences, - undoManager, - dialogService, + FrameDndHandler frameDndHandler = new FrameDndHandler(tabbedPane, mainStage::getScene, + this::getOpenDatabaseAction, stateManager); + + this.globalSearchBar = new GlobalSearchBar(this, stateManager, this.preferences, undoManager, dialogService, SearchType.NORMAL_SEARCH); this.entryEditor = new EntryEditor(this::getCurrentLibraryTab, - // Actions are recreated here since this avoids passing more parameters and the amount of additional memory consumption is neglegtable. + // Actions are recreated here since this avoids passing more parameters + // and the amount of additional memory consumption is neglegtable. new UndoAction(this::getCurrentLibraryTab, undoManager, dialogService, stateManager), new RedoAction(this::getCurrentLibraryTab, undoManager, dialogService, stateManager)); Injector.setModelOrService(EntryEditor.class, entryEditor); - this.sidePane = new SidePane( - this, - this.preferences, - Injector.instantiateModelOrService(JournalAbbreviationRepository.class), - taskExecutor, - dialogService, - aiService, - stateManager, - entryEditor, - fileUpdateMonitor, - entryTypesManager, - clipBoardManager, + this.sidePane = new SidePane(this, this.preferences, + Injector.instantiateModelOrService(JournalAbbreviationRepository.class), taskExecutor, dialogService, + aiService, stateManager, entryEditor, fileUpdateMonitor, entryTypesManager, clipBoardManager, undoManager); - this.pushToApplicationCommand = new GuiPushToApplicationCommand( - stateManager, - dialogService, - this.preferences, + this.pushToApplicationCommand = new GuiPushToApplicationCommand(stateManager, dialogService, this.preferences, taskExecutor); - this.fileHistory = new FileHistoryMenu( - this.preferences.getLastFilesOpenedPreferences().getFileHistory(), - dialogService, - getOpenDatabaseAction()); + this.fileHistory = new FileHistoryMenu(this.preferences.getLastFilesOpenedPreferences().getFileHistory(), + dialogService, getOpenDatabaseAction()); fileHistory.disableProperty().bind(Bindings.isEmpty(fileHistory.getItems())); @@ -216,37 +203,14 @@ public JabRefFrame(Stage mainStage, } private void initLayout() { - MainToolBar mainToolBar = new MainToolBar( - this, - pushToApplicationCommand, - globalSearchBar, - dialogService, - stateManager, - preferences, - aiService, - fileUpdateMonitor, - taskExecutor, - entryTypesManager, - clipBoardManager, - undoManager); + MainToolBar mainToolBar = new MainToolBar(this, pushToApplicationCommand, globalSearchBar, dialogService, + stateManager, preferences, aiService, fileUpdateMonitor, taskExecutor, entryTypesManager, + clipBoardManager, undoManager); - MainMenu mainMenu = new MainMenu( - this, - fileHistory, - sidePane, - pushToApplicationCommand, - preferences, - stateManager, - fileUpdateMonitor, - taskExecutor, - dialogService, - Injector.instantiateModelOrService(JournalAbbreviationRepository.class), - entryTypesManager, - undoManager, - clipBoardManager, - this::getOpenDatabaseAction, - aiService, - entryEditor); + MainMenu mainMenu = new MainMenu(this, fileHistory, sidePane, pushToApplicationCommand, preferences, + stateManager, fileUpdateMonitor, taskExecutor, dialogService, + Injector.instantiateModelOrService(JournalAbbreviationRepository.class), entryTypesManager, undoManager, + clipBoardManager, this::getOpenDatabaseAction, aiService, entryEditor); VBox head = new VBox(mainMenu, mainToolBar); head.setSpacing(0d); @@ -272,7 +236,8 @@ private void updateSidePane() { horizontalDividerSubscription.unsubscribe(); } horizontalSplit.getItems().remove(sidePane); - } else { + } + else { if (!horizontalSplit.getItems().contains(sidePane)) { horizontalSplit.getItems().addFirst(sidePane); updateHorizontalDividerPosition(); @@ -286,7 +251,8 @@ private void updateEditorPane() { verticalDividerSubscription.unsubscribe(); } verticalSplit.getItems().remove(entryEditor); - } else { + } + else { if (!verticalSplit.getItems().contains(entryEditor)) { verticalSplit.getItems().addLast(entryEditor); updateVerticalDividerPosition(); @@ -296,10 +262,12 @@ private void updateEditorPane() { public void updateHorizontalDividerPosition() { if (mainStage.isShowing() && !sidePane.getChildren().isEmpty()) { - horizontalSplit.setDividerPositions(preferences.getGuiPreferences().getHorizontalDividerPosition() / horizontalSplit.getWidth()); + horizontalSplit.setDividerPositions( + preferences.getGuiPreferences().getHorizontalDividerPosition() / horizontalSplit.getWidth()); horizontalDividerSubscription = EasyBind.valueAt(horizontalSplit.getDividers(), 0) - .mapObservable(SplitPane.Divider::positionProperty) - .listenToValues((_, newValue) -> preferences.getGuiPreferences().setHorizontalDividerPosition(newValue.doubleValue())); + .mapObservable(SplitPane.Divider::positionProperty) + .listenToValues((_, newValue) -> preferences.getGuiPreferences() + .setHorizontalDividerPosition(newValue.doubleValue())); } } @@ -307,8 +275,9 @@ public void updateVerticalDividerPosition() { if (mainStage.isShowing() && panelMode.get() == PanelMode.MAIN_TABLE_AND_ENTRY_EDITOR) { verticalSplit.setDividerPositions(preferences.getGuiPreferences().getVerticalDividerPosition()); verticalDividerSubscription = EasyBind.valueAt(verticalSplit.getDividers(), 0) - .mapObservable(SplitPane.Divider::positionProperty) - .listenToValues((_, newValue) -> preferences.getGuiPreferences().setVerticalDividerPosition(newValue.doubleValue())); + .mapObservable(SplitPane.Divider::positionProperty) + .listenToValues((_, newValue) -> preferences.getGuiPreferences() + .setVerticalDividerPosition(newValue.doubleValue())); } } @@ -340,31 +309,49 @@ private void initKeyBindings() { globalSearchBar.openGlobalSearchDialog(); break; case NEW_ARTICLE: - new NewEntryAction(StandardEntryType.Article, this::getCurrentLibraryTab, dialogService, preferences, stateManager).execute(); + new NewEntryAction(StandardEntryType.Article, this::getCurrentLibraryTab, dialogService, + preferences, stateManager) + .execute(); break; case NEW_BOOK: - new NewEntryAction(StandardEntryType.Book, this::getCurrentLibraryTab, dialogService, preferences, stateManager).execute(); + new NewEntryAction(StandardEntryType.Book, this::getCurrentLibraryTab, dialogService, + preferences, stateManager) + .execute(); break; case NEW_INBOOK: - new NewEntryAction(StandardEntryType.InBook, this::getCurrentLibraryTab, dialogService, preferences, stateManager).execute(); + new NewEntryAction(StandardEntryType.InBook, this::getCurrentLibraryTab, dialogService, + preferences, stateManager) + .execute(); break; case NEW_MASTERSTHESIS: - new NewEntryAction(StandardEntryType.MastersThesis, this::getCurrentLibraryTab, dialogService, preferences, stateManager).execute(); + new NewEntryAction(StandardEntryType.MastersThesis, this::getCurrentLibraryTab, dialogService, + preferences, stateManager) + .execute(); break; case NEW_PHDTHESIS: - new NewEntryAction(StandardEntryType.PhdThesis, this::getCurrentLibraryTab, dialogService, preferences, stateManager).execute(); + new NewEntryAction(StandardEntryType.PhdThesis, this::getCurrentLibraryTab, dialogService, + preferences, stateManager) + .execute(); break; case NEW_PROCEEDINGS: - new NewEntryAction(StandardEntryType.Proceedings, this::getCurrentLibraryTab, dialogService, preferences, stateManager).execute(); + new NewEntryAction(StandardEntryType.Proceedings, this::getCurrentLibraryTab, dialogService, + preferences, stateManager) + .execute(); break; case NEW_TECHREPORT: - new NewEntryAction(StandardEntryType.TechReport, this::getCurrentLibraryTab, dialogService, preferences, stateManager).execute(); + new NewEntryAction(StandardEntryType.TechReport, this::getCurrentLibraryTab, dialogService, + preferences, stateManager) + .execute(); break; case NEW_UNPUBLISHED: - new NewEntryAction(StandardEntryType.Unpublished, this::getCurrentLibraryTab, dialogService, preferences, stateManager).execute(); + new NewEntryAction(StandardEntryType.Unpublished, this::getCurrentLibraryTab, dialogService, + preferences, stateManager) + .execute(); break; case NEW_INPROCEEDINGS: - new NewEntryAction(StandardEntryType.InProceedings, this::getCurrentLibraryTab, dialogService, preferences, stateManager).execute(); + new NewEntryAction(StandardEntryType.InProceedings, this::getCurrentLibraryTab, dialogService, + preferences, stateManager) + .execute(); break; default: } @@ -373,9 +360,11 @@ private void initKeyBindings() { } private void initBindings() { - BindingsHelper.bindContentFiltered(tabbedPane.getTabs(), stateManager.getOpenDatabases(), LibraryTab.class::isInstance); + BindingsHelper.bindContentFiltered(tabbedPane.getTabs(), stateManager.getOpenDatabases(), + LibraryTab.class::isInstance); - // the binding for stateManager.activeDatabaseProperty() is at org.jabref.gui.LibraryTab.onDatabaseLoadingSucceed + // the binding for stateManager.activeDatabaseProperty() is at + // org.jabref.gui.LibraryTab.onDatabaseLoadingSucceed // Subscribe to the search EasyBind.subscribe(stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH), query -> { @@ -385,8 +374,8 @@ private void initBindings() { }); // Wait for the scene to be created, otherwise focusOwnerProperty is not provided - Platform.runLater(() -> stateManager.focusOwnerProperty().bind( - EasyBind.map(mainStage.getScene().focusOwnerProperty(), Optional::ofNullable))); + Platform.runLater(() -> stateManager.focusOwnerProperty() + .bind(EasyBind.map(mainStage.getScene().focusOwnerProperty(), Optional::ofNullable))); EasyBind.subscribe(tabbedPane.getSelectionModel().selectedItemProperty(), selectedTab -> { if (selectedTab instanceof LibraryTab libraryTab) { @@ -396,9 +385,12 @@ private void initBindings() { // Update active search query when switching between databases if (preferences.getSearchPreferences().shouldKeepSearchString()) { - libraryTab.searchQueryProperty().set(stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH).get()); - } else { - stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH).set(libraryTab.searchQueryProperty().get()); + libraryTab.searchQueryProperty() + .set(stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH).get()); + } + else { + stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH) + .set(libraryTab.searchQueryProperty().get()); } stateManager.searchResultSize(SearchType.NORMAL_SEARCH).bind(libraryTab.resultSizeProperty()); globalSearchBar.setAutoCompleter(libraryTab.getAutoCompleter()); @@ -407,16 +399,23 @@ private void initBindings() { Platform.runLater(() -> libraryTab.getMainTable().requestFocus()); // Set window title dynamically - mainStage.titleProperty().bind(Bindings.createStringBinding( - () -> libraryTab.textProperty().getValue() + " – " + FRAME_TITLE, // not a minus, but codepoint 2013 - libraryTab.textProperty())); - } else { + mainStage.titleProperty() + .bind(Bindings.createStringBinding(() -> libraryTab.textProperty().getValue() + " – " + FRAME_TITLE, // not + // a + // minus, + // but + // codepoint + // 2013 + libraryTab.textProperty())); + } + else { // Check if the previously active database was closed if (stateManager.getActiveDatabase().isPresent()) { String activeUID = stateManager.getActiveDatabase().get().getUid(); - boolean wasClosed = tabbedPane.getTabs().stream() - .filter(tab -> tab instanceof LibraryTab) - .noneMatch(ltab -> ((LibraryTab) ltab).getBibDatabaseContext().getUid().equals(activeUID)); + boolean wasClosed = tabbedPane.getTabs() + .stream() + .filter(tab -> tab instanceof LibraryTab) + .noneMatch(ltab -> ((LibraryTab) ltab).getBibDatabaseContext().getUid().equals(activeUID)); if (wasClosed) { tabbedPane.getSelectionModel().selectNext(); } @@ -442,7 +441,8 @@ private void initBindings() { }); // Hide tab bar - stateManager.getOpenDatabases().addListener((ListChangeListener) _ -> updateTabBarVisible()); + stateManager.getOpenDatabases() + .addListener((ListChangeListener) _ -> updateTabBarVisible()); EasyBind.subscribe(preferences.getWorkspacePreferences().hideTabBarProperty(), _ -> updateTabBarVisible()); } @@ -451,12 +451,14 @@ private void updateTabBarVisible() { if (!tabbedPane.getStyleClass().contains("hide-tab-bar")) { tabbedPane.getStyleClass().add("hide-tab-bar"); } - } else { + } + else { tabbedPane.getStyleClass().remove("hide-tab-bar"); } } - /* ************************************************************************ + /* + * ************************************************************************ * * Public API * @@ -485,7 +487,8 @@ public void showLibraryTab(@NonNull LibraryTab libraryTab) { } public void showWelcomeTab() { - // The loop iterates through all tabs in tabbedPane to check if a WelcomeTab already exists. If yes, it is selected. + // The loop iterates through all tabs in tabbedPane to check if a WelcomeTab + // already exists. If yes, it is selected. for (Tab tab : tabbedPane.getTabs()) { if (!(tab instanceof LibraryTab)) { tabbedPane.getSelectionModel().select(tab); @@ -494,43 +497,22 @@ public void showWelcomeTab() { } // WelcomeTab not found - WelcomeTab welcomeTab = new WelcomeTab( - Injector.instantiateModelOrService(Stage.class), - this, - preferences, - aiService, - dialogService, - stateManager, - fileUpdateMonitor, - entryTypesManager, - undoManager, - clipBoardManager, - taskExecutor, - fileHistory, - Injector.instantiateModelOrService(BuildInfo.class) - ); + WelcomeTab welcomeTab = new WelcomeTab(Injector.instantiateModelOrService(Stage.class), this, preferences, + aiService, dialogService, stateManager, fileUpdateMonitor, entryTypesManager, undoManager, + clipBoardManager, taskExecutor, fileHistory, Injector.instantiateModelOrService(BuildInfo.class)); tabbedPane.getTabs().add(welcomeTab); tabbedPane.getSelectionModel().select(welcomeTab); } /** - * Opens a new tab with existing data. - * Asynchronous loading is done at {@link LibraryTab#createLibraryTab}. - * Similar method: {@link OpenDatabaseAction#openTheFile(Path)} + * Opens a new tab with existing data. Asynchronous loading is done at + * {@link LibraryTab#createLibraryTab}. Similar method: + * {@link OpenDatabaseAction#openTheFile(Path)} */ public void addTab(@NonNull BibDatabaseContext databaseContext, boolean raisePanel) { Objects.requireNonNull(databaseContext); - LibraryTab libraryTab = LibraryTab.createLibraryTab( - databaseContext, - this, - dialogService, - aiService, - preferences, - stateManager, - fileUpdateMonitor, - entryTypesManager, - undoManager, - clipBoardManager, + LibraryTab libraryTab = LibraryTab.createLibraryTab(databaseContext, this, dialogService, aiService, + preferences, stateManager, fileUpdateMonitor, entryTypesManager, undoManager, clipBoardManager, taskExecutor); addTab(libraryTab, raisePanel); } @@ -550,14 +532,20 @@ private ContextMenu createTabContextMenuFor(LibraryTab tab) { ContextMenu contextMenu = new ContextMenu(); ActionFactory factory = new ActionFactory(); - contextMenu.getItems().addAll( - factory.createMenuItem(StandardActions.LIBRARY_PROPERTIES, new LibraryPropertiesAction(tab::getBibDatabaseContext, stateManager)), - factory.createMenuItem(StandardActions.OPEN_DATABASE_FOLDER, new OpenDatabaseFolder(dialogService, stateManager, preferences, tab::getBibDatabaseContext)), - factory.createMenuItem(StandardActions.OPEN_CONSOLE, new OpenConsoleAction(tab::getBibDatabaseContext, stateManager, preferences, dialogService)), - new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.CLOSE_LIBRARY, new CloseDatabaseAction(this, tab, stateManager)), - factory.createMenuItem(StandardActions.CLOSE_OTHER_LIBRARIES, new CloseOthersDatabaseAction(tab)), - factory.createMenuItem(StandardActions.CLOSE_ALL_LIBRARIES, new CloseAllDatabaseAction())); + contextMenu.getItems() + .addAll(factory.createMenuItem(StandardActions.LIBRARY_PROPERTIES, + new LibraryPropertiesAction(tab::getBibDatabaseContext, stateManager)), + factory.createMenuItem(StandardActions.OPEN_DATABASE_FOLDER, + new OpenDatabaseFolder(dialogService, stateManager, preferences, + tab::getBibDatabaseContext)), + factory.createMenuItem(StandardActions.OPEN_CONSOLE, + new OpenConsoleAction( + tab::getBibDatabaseContext, stateManager, preferences, dialogService)), + new SeparatorMenuItem(), + factory.createMenuItem(StandardActions.CLOSE_LIBRARY, + new CloseDatabaseAction(this, tab, stateManager)), + factory.createMenuItem(StandardActions.CLOSE_OTHER_LIBRARIES, new CloseOthersDatabaseAction(tab)), + factory.createMenuItem(StandardActions.CLOSE_ALL_LIBRARIES, new CloseAllDatabaseAction())); return contextMenu; } @@ -572,10 +560,7 @@ public boolean closeTab(LibraryTab tab) { public boolean closeTabs(@NonNull List tabs) { // Only accept library tabs that are shown in the tab container - List toClose = tabs.stream() - .distinct() - .filter(getLibraryTabs()::contains) - .toList(); + List toClose = tabs.stream().distinct().filter(getLibraryTabs()::contains).toList(); if (toClose.isEmpty()) { // Nothing to do @@ -603,24 +588,16 @@ public boolean closeTabs(@NonNull List tabs) { } private OpenDatabaseAction getOpenDatabaseAction() { - return new OpenDatabaseAction( - this, - preferences, - aiService, - dialogService, - stateManager, - fileUpdateMonitor, - entryTypesManager, - undoManager, - clipBoardManager, - taskExecutor); + return new OpenDatabaseAction(this, preferences, aiService, dialogService, stateManager, fileUpdateMonitor, + entryTypesManager, undoManager, clipBoardManager, taskExecutor); } /** * Refreshes the ui after preferences changes */ public void refresh() { - // Disabled, because Bindings implement automatic update. Left here as commented out code to guide if something does not work after updating the preferences. + // Disabled, because Bindings implement automatic update. Left here as commented + // out code to guide if something does not work after updating the preferences. // getLibraryTabs().forEach(LibraryTab::setupMainPanel); getLibraryTabs().forEach(tab -> tab.getMainTable().getTableModel().resetFieldFormatter()); } @@ -665,11 +642,13 @@ public void execute() { frame.mainStage.close(); } } + } static protected class CloseDatabaseAction extends SimpleCommand { private final LibraryTabContainer tabContainer; + private final LibraryTab libraryTab; public CloseDatabaseAction(LibraryTabContainer tabContainer, LibraryTab libraryTab, StateManager stateManager) { @@ -679,7 +658,8 @@ public CloseDatabaseAction(LibraryTabContainer tabContainer, LibraryTab libraryT } /** - * Using this constructor will result in executing the command on the currently open library tab + * Using this constructor will result in executing the command on the currently + * open library tab */ public CloseDatabaseAction(LibraryTabContainer tabContainer, StateManager stateManager) { this(tabContainer, null, stateManager); @@ -694,11 +674,13 @@ public void execute() { return; } tabContainer.closeTab(tabContainer.getCurrentLibraryTab()); - } else { + } + else { tabContainer.closeTab(libraryTab); } }); } + } private class CloseOthersDatabaseAction extends SimpleCommand { @@ -720,6 +702,7 @@ public void execute() { } } } + } private class CloseAllDatabaseAction extends SimpleCommand { @@ -731,15 +714,19 @@ public void execute() { Platform.runLater(() -> closeTab((LibraryTab) tab)); } } + } public static class OpenDatabaseFolder extends SimpleCommand { private final Supplier databaseContext; + private final DialogService dialogService; + private final GuiPreferences preferences; - public OpenDatabaseFolder(DialogService dialogService, StateManager stateManager, GuiPreferences preferences, Supplier databaseContext) { + public OpenDatabaseFolder(DialogService dialogService, StateManager stateManager, GuiPreferences preferences, + Supplier databaseContext) { this.dialogService = dialogService; this.preferences = preferences; this.databaseContext = databaseContext; @@ -750,11 +737,15 @@ public OpenDatabaseFolder(DialogService dialogService, StateManager stateManager public void execute() { Optional.of(databaseContext.get()).flatMap(BibDatabaseContext::getDatabasePath).ifPresent(path -> { try { - NativeDesktop.openFolderAndSelectFile(path, preferences.getExternalApplicationsPreferences(), dialogService); - } catch (IOException e) { + NativeDesktop.openFolderAndSelectFile(path, preferences.getExternalApplicationsPreferences(), + dialogService); + } + catch (IOException e) { LOGGER.info("Could not open folder", e); } }); } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrameViewModel.java b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrameViewModel.java index c16ad7ff8ca..1f37f23d91b 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrameViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrameViewModel.java @@ -49,31 +49,36 @@ import org.slf4j.LoggerFactory; public class JabRefFrameViewModel implements UiMessageHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(JabRefFrameViewModel.class); private final GuiPreferences preferences; + private final AiService aiService; + private final StateManager stateManager; + private final DialogService dialogService; + private final LibraryTabContainer tabContainer; + private final Supplier openDatabaseAction; + private final BibEntryTypesManager entryTypesManager; + private final FileUpdateMonitor fileUpdateMonitor; + private final UndoManager undoManager; + private final ClipBoardManager clipBoardManager; + private final TaskExecutor taskExecutor; - public JabRefFrameViewModel(GuiPreferences preferences, - AiService aiService, - StateManager stateManager, - DialogService dialogService, - LibraryTabContainer tabContainer, - Supplier openDatabaseAction, - BibEntryTypesManager entryTypesManager, - FileUpdateMonitor fileUpdateMonitor, - UndoManager undoManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor) { + public JabRefFrameViewModel(GuiPreferences preferences, AiService aiService, StateManager stateManager, + DialogService dialogService, LibraryTabContainer tabContainer, + Supplier openDatabaseAction, BibEntryTypesManager entryTypesManager, + FileUpdateMonitor fileUpdateMonitor, UndoManager undoManager, ClipBoardManager clipBoardManager, + TaskExecutor taskExecutor) { this.preferences = preferences; this.aiService = aiService; this.stateManager = stateManager; @@ -89,11 +94,13 @@ public JabRefFrameViewModel(GuiPreferences preferences, void storeLastOpenedFiles(List filenames, Path focusedDatabase) { if (preferences.getWorkspacePreferences().shouldOpenLastEdited()) { - // Here we store the names of all current files. If there is no current file, we remove any + // Here we store the names of all current files. If there is no current file, + // we remove any // previously stored filename. if (filenames.isEmpty()) { preferences.getLastFilesOpenedPreferences().getLastFilesOpened().clear(); - } else { + } + else { preferences.getLastFilesOpenedPreferences().setLastFilesOpened(filenames); preferences.getLastFilesOpenedPreferences().setLastFocusedFile(focusedDatabase); } @@ -102,39 +109,42 @@ void storeLastOpenedFiles(List filenames, Path focusedDatabase) { /** * Quit JabRef - * * @return true if the user chose to quit; false otherwise */ public boolean close() { - // Ask if the user really wants to close, if there are still background tasks running + // Ask if the user really wants to close, if there are still background tasks + // running // The background tasks may make changes themselves that need saving. if (stateManager.getAnyTasksThatWillNotBeRecoveredRunning().getValue()) { Optional shouldClose = dialogService.showBackgroundProgressDialogAndWait( Localization.lang("Please wait..."), - Localization.lang("Waiting for background tasks to finish. Quit anyway?"), - stateManager); + Localization.lang("Waiting for background tasks to finish. Quit anyway?"), stateManager); if (!(shouldClose.isPresent() && (shouldClose.get() == ButtonType.YES))) { return false; } } // Read the opened and focused databases before closing them - List openedLibraries = tabContainer.getLibraryTabs().stream() - .map(LibraryTab::getBibDatabaseContext) - .map(BibDatabaseContext::getDatabasePath) - .flatMap(Optional::stream) - .toList(); + List openedLibraries = tabContainer.getLibraryTabs() + .stream() + .map(LibraryTab::getBibDatabaseContext) + .map(BibDatabaseContext::getDatabasePath) + .flatMap(Optional::stream) + .toList(); Path focusedLibraries = Optional.ofNullable(tabContainer.getCurrentLibraryTab()) - .map(LibraryTab::getBibDatabaseContext) - .flatMap(BibDatabaseContext::getDatabasePath) - .orElse(null); + .map(LibraryTab::getBibDatabaseContext) + .flatMap(BibDatabaseContext::getDatabasePath) + .orElse(null); - // Then ask if the user really wants to close, if the library has not been saved since last save. + // Then ask if the user really wants to close, if the library has not been saved + // since last save. if (!tabContainer.closeTabs(tabContainer.getLibraryTabs())) { return false; } - storeLastOpenedFiles(openedLibraries, focusedLibraries); // store only if successfully having closed the libraries + storeLastOpenedFiles(openedLibraries, focusedLibraries); // store only if + // successfully having + // closed the libraries ProcessingLibraryDialog processingLibraryDialog = new ProcessingLibraryDialog(dialogService); processingLibraryDialog.showAndWait(tabContainer.getLibraryTabs()); @@ -143,9 +153,9 @@ public boolean close() { } /** - * Handles commands submitted by the command line or by the remote host to be executed in the ui - * Needs to run in a certain order. E.g. databases have to be loaded before selecting an entry. - * + * Handles commands submitted by the command line or by the remote host to be executed + * in the ui Needs to run in a certain order. E.g. databases have to be loaded before + * selecting an entry. * @param uiCommands to be handled */ @Override @@ -164,62 +174,70 @@ public void handleUiCommands(List uiCommands) { // Handle OpenDatabases if (!blank) { uiCommands.stream() - .filter(UiCommand.OpenLibraries.class::isInstance) - .map(UiCommand.OpenLibraries.class::cast) - .forEach(command -> openDatabaseAction.get().openFiles(command.toImport())); + .filter(UiCommand.OpenLibraries.class::isInstance) + .map(UiCommand.OpenLibraries.class::cast) + .forEach(command -> openDatabaseAction.get().openFiles(command.toImport())); + + uiCommands.stream() + .filter(UiCommand.AppendToCurrentLibrary.class::isInstance) + .map(UiCommand.AppendToCurrentLibrary.class::cast) + .map(UiCommand.AppendToCurrentLibrary::toAppend) + .filter(Objects::nonNull) + .findAny() + .ifPresent(toAppend -> { + LOGGER.debug("Append to current library {} requested", toAppend); + waitForLoadingFinished(() -> appendToCurrentLibrary(toAppend)); + }); + + uiCommands.stream() + .filter(UiCommand.AppendFileOrUrlToCurrentLibrary.class::isInstance) + .map(UiCommand.AppendFileOrUrlToCurrentLibrary.class::cast) + .findAny() + .ifPresent(importFile -> importFromFileAndOpen(importFile.location())); uiCommands.stream() - .filter(UiCommand.AppendToCurrentLibrary.class::isInstance) - .map(UiCommand.AppendToCurrentLibrary.class::cast) - .map(UiCommand.AppendToCurrentLibrary::toAppend) - .filter(Objects::nonNull) - .findAny().ifPresent(toAppend -> { - LOGGER.debug("Append to current library {} requested", toAppend); - waitForLoadingFinished(() -> appendToCurrentLibrary(toAppend)); - }); - - uiCommands.stream().filter(UiCommand.AppendFileOrUrlToCurrentLibrary.class::isInstance) - .map(UiCommand.AppendFileOrUrlToCurrentLibrary.class::cast) - .findAny().ifPresent(importFile -> importFromFileAndOpen(importFile.location())); - - uiCommands.stream().filter(UiCommand.AppendBibTeXToCurrentLibrary.class::isInstance) - .map(UiCommand.AppendBibTeXToCurrentLibrary.class::cast) - .findAny().ifPresent(importBibTex -> importBibtexStringAndOpen(importBibTex.bibtex())); + .filter(UiCommand.AppendBibTeXToCurrentLibrary.class::isInstance) + .map(UiCommand.AppendBibTeXToCurrentLibrary.class::cast) + .findAny() + .ifPresent(importBibTex -> importBibtexStringAndOpen(importBibTex.bibtex())); } // Handle jumpToEntry // Needs to go last, because it requires all libraries opened uiCommands.stream() - .filter(UiCommand.JumpToEntryKey.class::isInstance) - .map(UiCommand.JumpToEntryKey.class::cast) - .map(UiCommand.JumpToEntryKey::citationKey) - .filter(Objects::nonNull) - .findAny().ifPresent(entryKey -> { - LOGGER.debug("Jump to entry {} requested", entryKey); - // tabs must be present and contents async loaded for an entry to be selected - waitForLoadingFinished(() -> jumpToEntry(entryKey)); - }); + .filter(UiCommand.JumpToEntryKey.class::isInstance) + .map(UiCommand.JumpToEntryKey.class::cast) + .map(UiCommand.JumpToEntryKey::citationKey) + .filter(Objects::nonNull) + .findAny() + .ifPresent(entryKey -> { + LOGGER.debug("Jump to entry {} requested", entryKey); + // tabs must be present and contents async loaded for an entry to be + // selected + waitForLoadingFinished(() -> jumpToEntry(entryKey)); + }); } /// @deprecated used by the browser extension only private void importBibtexStringAndOpen(String importStr) { LOGGER.debug("ImportBibtex {} requested", importStr); BackgroundTask.wrap(() -> { - BibtexParser parser = new BibtexParser(preferences.getImportFormatPreferences()); - List entries = parser.parseEntries(importStr); - return new ParserResult(entries); - }).onSuccess(this::addParserResult) - .onFailure(e -> LOGGER.error("Unable to parse provided bibtex {}", importStr, e)) - .executeWith(taskExecutor); + BibtexParser parser = new BibtexParser(preferences.getImportFormatPreferences()); + List entries = parser.parseEntries(importStr); + return new ParserResult(entries); + }) + .onSuccess(this::addParserResult) + .onFailure(e -> LOGGER.error("Unable to parse provided bibtex {}", importStr, e)) + .executeWith(taskExecutor); } /// @deprecated used by the browser extension only private void importFromFileAndOpen(String location) { LOGGER.debug("Import file {} requested", location); BackgroundTask.wrap(() -> CliImportHelper.importFile(location, preferences, false)) - .onSuccess(result -> result.ifPresent(this::addParserResult)) - .onFailure(t -> LOGGER.error("Unable to import file {} ", location, t)) - .executeWith(taskExecutor); + .onSuccess(result -> result.ifPresent(this::addParserResult)) + .onFailure(t -> LOGGER.error("Unable to import file {} ", location, t)) + .executeWith(taskExecutor); } private void checkForBibInUpperDir() { @@ -231,13 +249,15 @@ private void checkForBibInUpperDir() { } } - /// Use case: User starts `JabRef.bat` or `JabRef.exe`. JabRef should open a "close by" bib file. - /// By "close by" a `.bib` file in the current folder or one level up of `JabRef.exe`is meant. + /// Use case: User starts `JabRef.bat` or `JabRef.exe`. JabRef should open a "close + /// by" bib file. + /// By "close by" a `.bib` file in the current folder or one level up of + /// `JabRef.exe`is meant. /// /// Paths: - /// - `...\{example-dir}\JabRef\JabRef.exe` (Windows) - /// - `.../{example-dir}/JabRef/bin/JabRef` (Linux) - /// - `...\{example-dir}\JabRef\runtime\bin\JabRef.bat` (Windows) + /// - `...\{example-dir}\JabRef\JabRef.exe` (Windows) + /// - `.../{example-dir}/JabRef/bin/JabRef` (Linux) + /// - `...\{example-dir}\JabRef\runtime\bin\JabRef.bat` (Windows) /// /// In the example, `...\{example-dir}\example.bib` should be found. /// @@ -261,22 +281,28 @@ private Optional firstBibFile() { ArrayList dirsToCheck = new ArrayList<>(2); dirsToCheck.add(Path.of("")); if (isJabRefExe) { - dirsToCheck.add(Path.of("../")); // directory above `JabRef.exe` directory - } else if (isJabRefBat) { - dirsToCheck.add(Path.of("../../../")); // directory above `runtime\bin\JabRef.bat` - } else if (isJabRef) { - dirsToCheck.add(Path.of("../..(/")); // directory above `bin/JabRef` directory + dirsToCheck.add(Path.of("../")); // directory above `JabRef.exe` directory + } + else if (isJabRefBat) { + dirsToCheck.add(Path.of("../../../")); // directory above + // `runtime\bin\JabRef.bat` + } + else if (isJabRef) { + dirsToCheck.add(Path.of("../..(/")); // directory above `bin/JabRef` directory } - // We want to check dirsToCheck only, not all subdirs (due to unnecessary disk i/o) + // We want to check dirsToCheck only, not all subdirs (due to unnecessary disk + // i/o) try { return dirsToCheck.stream() - .map(Path::toAbsolutePath) - .flatMap(Unchecked.function(Files::list)) - .filter(path -> FileUtil.getFileExtension(path).equals(Optional.of("bib"))) - .findFirst(); - } catch (UncheckedIOException ex) { - // Could be access denied exception - when this is started from the application directory + .map(Path::toAbsolutePath) + .flatMap(Unchecked.function(Files::list)) + .filter(path -> FileUtil.getFileExtension(path).equals(Optional.of("bib"))) + .findFirst(); + } + catch (UncheckedIOException ex) { + // Could be access denied exception - when this is started from the + // application directory // Therefore log level "debug" LOGGER.debug("Could not check for existing bib file {}", dirsToCheck, ex); return Optional.empty(); @@ -287,20 +313,17 @@ private void appendToCurrentLibrary(List libraries) { List parserResults = new ArrayList<>(); try { for (Path file : libraries) { - parserResults.add(OpenDatabase.loadDatabase( - file, - preferences.getImportFormatPreferences(), - fileUpdateMonitor)); + parserResults + .add(OpenDatabase.loadDatabase(file, preferences.getImportFormatPreferences(), fileUpdateMonitor)); } - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Could not open bib file {}", libraries, e); return; } // Remove invalid databases - List invalidDatabases = parserResults.stream() - .filter(ParserResult::isInvalid) - .toList(); + List invalidDatabases = parserResults.stream().filter(ParserResult::isInvalid).toList(); final List failed = new ArrayList<>(invalidDatabases); parserResults.removeAll(invalidDatabases); @@ -311,8 +334,8 @@ private void appendToCurrentLibrary(List libraries) { for (ParserResult parserResult : failed) { String message = Localization.lang("Error opening file '%0'", - parserResult.getPath().map(Path::toString).orElse("(File name unknown)")) + "\n" + - parserResult.getErrorMessage(); + parserResult.getPath().map(Path::toString).orElse("(File name unknown)")) + "\n" + + parserResult.getErrorMessage(); dialogService.showErrorDialogAndWait(Localization.lang("Error opening file"), message); } } @@ -322,7 +345,8 @@ private void addParserResult(ParserResult parserResult) { LibraryTab libraryTab = tabContainer.getCurrentLibraryTab(); BackgroundTask task = BackgroundTask.wrap(() -> parserResult); - ImportCleanup cleanup = ImportCleanup.targeting(libraryTab.getBibDatabaseContext().getMode(), preferences.getFieldPreferences()); + ImportCleanup cleanup = ImportCleanup.targeting(libraryTab.getBibDatabaseContext().getMode(), + preferences.getFieldPreferences()); cleanup.doPostCleanup(parserResult.getDatabase().getEntries()); ImportEntriesDialog dialog = new ImportEntriesDialog(libraryTab.getBibDatabaseContext(), task); @@ -335,9 +359,10 @@ private void waitForLoadingFinished(Runnable runnable) { CompletableFuture future = new CompletableFuture<>(); - List loadings = tabContainer.getLibraryTabs().stream() - .map(LibraryTab::getLoading) - .collect(Collectors.toList()); + List loadings = tabContainer.getLibraryTabs() + .stream() + .map(LibraryTab::getLoading) + .collect(Collectors.toList()); // Create a listener for each observable ChangeListener listener = (observable, _, _) -> { @@ -347,7 +372,8 @@ private void waitForLoadingFinished(Runnable runnable) { } if (LOGGER.isTraceEnabled()) { LOGGER.trace("Count of loading tabs: {}", loadings.size()); - LOGGER.trace("Count of loading tabs really true: {}", loadings.stream().filter(ObservableBooleanValue::get).count()); + LOGGER.trace("Count of loading tabs really true: {}", + loadings.stream().filter(ObservableBooleanValue::get).count()); } for (ObservableBooleanValue obs : loadings) { if (obs.get()) { @@ -365,7 +391,8 @@ private void waitForLoadingFinished(Runnable runnable) { } LOGGER.trace("Fire once"); - // Due to concurrency, it might be that the observables are already false, so we trigger one evaluation + // Due to concurrency, it might be that the observables are already false, so we + // trigger one evaluation listener.changed(null, null, false); LOGGER.trace("Waiting for state changes..."); @@ -381,15 +408,16 @@ private void waitForLoadingFinished(Runnable runnable) { private void jumpToEntry(String entryKey) { // check current library tab first LibraryTab currentLibraryTab = tabContainer.getCurrentLibraryTab(); - List sortedTabs = tabContainer.getLibraryTabs().stream() - .sorted(Comparator.comparing(tab -> tab != currentLibraryTab)) - .toList(); + List sortedTabs = tabContainer.getLibraryTabs() + .stream() + .sorted(Comparator.comparing(tab -> tab != currentLibraryTab)) + .toList(); for (LibraryTab libraryTab : sortedTabs) { Optional bibEntry = libraryTab.getDatabase() - .getEntries().stream() - .filter(entry -> entry.getCitationKey().orElse("") - .equals(entryKey)) - .findAny(); + .getEntries() + .stream() + .filter(entry -> entry.getCitationKey().orElse("").equals(entryKey)) + .findAny(); if (bibEntry.isPresent()) { LOGGER.debug("Found entry {} in library tab {}", entryKey, libraryTab); libraryTab.clearAndSelect(bibEntry.get()); @@ -401,7 +429,9 @@ private void jumpToEntry(String entryKey) { LOGGER.trace("End of loop"); if (stateManager.getSelectedEntries().isEmpty()) { - dialogService.notify(Localization.lang("Citation key '%0' to select not found in open libraries.", entryKey)); + dialogService + .notify(Localization.lang("Citation key '%0' to select not found in open libraries.", entryKey)); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java b/jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java index 283a9c6a3c5..d27e867a79b 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java @@ -95,39 +95,46 @@ import com.airhacks.afterburner.injection.Injector; public class MainMenu extends MenuBar { + private final JabRefFrame frame; + private final FileHistoryMenu fileHistoryMenu; + private final SidePane sidePane; + private final GuiPushToApplicationCommand pushToApplicationCommand; + private final GuiPreferences preferences; + private final StateManager stateManager; + private final FileUpdateMonitor fileUpdateMonitor; + private final TaskExecutor taskExecutor; + private final DialogService dialogService; + private final JournalAbbreviationRepository abbreviationRepository; + private final BibEntryTypesManager entryTypesManager; + private final CountingUndoManager undoManager; + private final ClipBoardManager clipBoardManager; + private final Supplier openDatabaseActionSupplier; + private final AiService aiService; + private final PreviewControls previewControls; - public MainMenu(JabRefFrame frame, - FileHistoryMenu fileHistoryMenu, - SidePane sidePane, - GuiPushToApplicationCommand pushToApplicationCommand, - GuiPreferences preferences, - StateManager stateManager, - FileUpdateMonitor fileUpdateMonitor, - TaskExecutor taskExecutor, - DialogService dialogService, - JournalAbbreviationRepository abbreviationRepository, - BibEntryTypesManager entryTypesManager, - CountingUndoManager undoManager, - ClipBoardManager clipBoardManager, - Supplier openDatabaseActionSupplier, - AiService aiService, - PreviewControls previewControls) { + public MainMenu(JabRefFrame frame, FileHistoryMenu fileHistoryMenu, SidePane sidePane, + GuiPushToApplicationCommand pushToApplicationCommand, GuiPreferences preferences, StateManager stateManager, + FileUpdateMonitor fileUpdateMonitor, TaskExecutor taskExecutor, DialogService dialogService, + JournalAbbreviationRepository abbreviationRepository, BibEntryTypesManager entryTypesManager, + CountingUndoManager undoManager, ClipBoardManager clipBoardManager, + Supplier openDatabaseActionSupplier, AiService aiService, + PreviewControls previewControls) { this.frame = frame; this.fileHistoryMenu = fileHistoryMenu; this.sidePane = sidePane; @@ -159,95 +166,152 @@ private void createMenu() { Menu tools = new Menu(Localization.lang("Tools")); Menu help = new Menu(Localization.lang("Help")); - file.getItems().addAll( - factory.createMenuItem(StandardActions.NEW_LIBRARY, new NewDatabaseAction(frame, preferences)), - factory.createMenuItem(StandardActions.OPEN_LIBRARY, openDatabaseActionSupplier.get()), - fileHistoryMenu, - factory.createMenuItem(StandardActions.SAVE_LIBRARY, new SaveAction(SaveAction.SaveMethod.SAVE, frame::getCurrentLibraryTab, dialogService, preferences, stateManager)), - factory.createMenuItem(StandardActions.SAVE_LIBRARY_AS, new SaveAction(SaveAction.SaveMethod.SAVE_AS, frame::getCurrentLibraryTab, dialogService, preferences, stateManager)), - factory.createMenuItem(StandardActions.SAVE_ALL, new SaveAllAction(frame::getLibraryTabs, preferences, dialogService, stateManager)), - factory.createMenuItem(StandardActions.CLOSE_LIBRARY, new JabRefFrame.CloseDatabaseAction(frame, stateManager)), - - new SeparatorMenuItem(), - - factory.createSubMenu(StandardActions.IMPORT, - factory.createMenuItem(StandardActions.IMPORT_INTO_CURRENT_LIBRARY, new ImportCommand(frame, ImportCommand.ImportMethod.TO_EXISTING, preferences, stateManager, fileUpdateMonitor, taskExecutor, dialogService)), - factory.createMenuItem(StandardActions.IMPORT_INTO_NEW_LIBRARY, new ImportCommand(frame, ImportCommand.ImportMethod.AS_NEW, preferences, stateManager, fileUpdateMonitor, taskExecutor, dialogService))), - - factory.createSubMenu(StandardActions.EXPORT, - factory.createMenuItem(StandardActions.EXPORT_ALL, new ExportCommand(ExportCommand.ExportMethod.EXPORT_ALL, frame::getCurrentLibraryTab, stateManager, dialogService, preferences, entryTypesManager, abbreviationRepository, taskExecutor)), - factory.createMenuItem(StandardActions.EXPORT_SELECTED, new ExportCommand(ExportCommand.ExportMethod.EXPORT_SELECTED, frame::getCurrentLibraryTab, stateManager, dialogService, preferences, entryTypesManager, abbreviationRepository, taskExecutor)), - factory.createMenuItem(StandardActions.SAVE_SELECTED_AS_PLAIN_BIBTEX, new SaveAction(SaveAction.SaveMethod.SAVE_SELECTED, frame::getCurrentLibraryTab, dialogService, preferences, stateManager))), - - new SeparatorMenuItem(), - - // region: Sharing of the library - - // TODO: Should be only enabled if not yet shared. - factory.createSubMenu(StandardActions.GIT, - factory.createMenuItem(StandardActions.GIT_SHARE, new GitShareToGitHubAction(dialogService, stateManager)) - ), - - factory.createSubMenu(StandardActions.REMOTE_DB, - factory.createMenuItem(StandardActions.CONNECT_TO_SHARED_DB, new ConnectToSharedDatabaseCommand(frame, dialogService)), - factory.createMenuItem(StandardActions.PULL_CHANGES_FROM_SHARED_DB, new PullChangesFromSharedAction(stateManager))), - - // endregion - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.SHOW_PREFS, new ShowPreferencesAction(frame, dialogService)), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.QUIT, new JabRefFrame.CloseAction(frame)) - ); - - edit.getItems().addAll( - factory.createMenuItem(StandardActions.UNDO, new UndoAction(frame::getCurrentLibraryTab, undoManager, dialogService, stateManager)), - factory.createMenuItem(StandardActions.REDO, new RedoAction(frame::getCurrentLibraryTab, undoManager, dialogService, stateManager)), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.CUT, new EditAction(StandardActions.CUT, frame::getCurrentLibraryTab, stateManager, undoManager)), - - factory.createMenuItem(StandardActions.COPY, new EditAction(StandardActions.COPY, frame::getCurrentLibraryTab, stateManager, undoManager)), - factory.createSubMenu(StandardActions.COPY_MORE, - factory.createMenuItem(StandardActions.COPY_TITLE, new CopyMoreAction(StandardActions.COPY_TITLE, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), - factory.createMenuItem(StandardActions.COPY_KEY, new CopyMoreAction(StandardActions.COPY_KEY, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), - factory.createMenuItem(StandardActions.COPY_CITE_KEY, new CopyMoreAction(StandardActions.COPY_CITE_KEY, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), - factory.createMenuItem(StandardActions.COPY_KEY_AND_TITLE, new CopyMoreAction(StandardActions.COPY_KEY_AND_TITLE, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), - factory.createMenuItem(StandardActions.COPY_KEY_AND_LINK, new CopyMoreAction(StandardActions.COPY_KEY_AND_LINK, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), - factory.createMenuItem(StandardActions.COPY_CITATION_PREVIEW, new CopyCitationAction(CitationStyleOutputFormat.HTML, dialogService, stateManager, clipBoardManager, taskExecutor, preferences, abbreviationRepository)), - factory.createMenuItem(StandardActions.EXPORT_SELECTED_TO_CLIPBOARD, new ExportToClipboardAction(dialogService, stateManager, clipBoardManager, taskExecutor, preferences))), - - factory.createMenuItem(StandardActions.PASTE, new EditAction(StandardActions.PASTE, frame::getCurrentLibraryTab, stateManager, undoManager)), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.REPLACE_ALL, new ReplaceStringAction(frame::getCurrentLibraryTab, stateManager, dialogService)), - factory.createMenuItem(StandardActions.GENERATE_CITE_KEYS, new GenerateCitationKeyAction(frame::getCurrentLibraryTab, dialogService, stateManager, taskExecutor, preferences, undoManager)), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.MANAGE_KEYWORDS, new ManageKeywordsAction(stateManager)), - factory.createMenuItem(StandardActions.AUTOMATIC_FIELD_EDITOR, new AutomaticFieldEditorAction(stateManager, dialogService, undoManager))); + file.getItems() + .addAll(factory.createMenuItem(StandardActions.NEW_LIBRARY, new NewDatabaseAction(frame, preferences)), + factory.createMenuItem(StandardActions.OPEN_LIBRARY, openDatabaseActionSupplier.get()), + fileHistoryMenu, + factory.createMenuItem(StandardActions.SAVE_LIBRARY, + new SaveAction(SaveAction.SaveMethod.SAVE, frame::getCurrentLibraryTab, dialogService, + preferences, stateManager)), + factory.createMenuItem(StandardActions.SAVE_LIBRARY_AS, + new SaveAction(SaveAction.SaveMethod.SAVE_AS, frame::getCurrentLibraryTab, dialogService, + preferences, stateManager)), + factory.createMenuItem(StandardActions.SAVE_ALL, + new SaveAllAction(frame::getLibraryTabs, preferences, dialogService, stateManager)), + factory.createMenuItem(StandardActions.CLOSE_LIBRARY, + new JabRefFrame.CloseDatabaseAction(frame, stateManager)), + + new SeparatorMenuItem(), + + factory.createSubMenu(StandardActions.IMPORT, + factory.createMenuItem(StandardActions.IMPORT_INTO_CURRENT_LIBRARY, + new ImportCommand(frame, ImportCommand.ImportMethod.TO_EXISTING, preferences, + stateManager, fileUpdateMonitor, taskExecutor, dialogService)), + factory.createMenuItem(StandardActions.IMPORT_INTO_NEW_LIBRARY, + new ImportCommand(frame, ImportCommand.ImportMethod.AS_NEW, preferences, + stateManager, fileUpdateMonitor, taskExecutor, dialogService))), + + factory.createSubMenu(StandardActions.EXPORT, + factory.createMenuItem(StandardActions.EXPORT_ALL, + new ExportCommand(ExportCommand.ExportMethod.EXPORT_ALL, + frame::getCurrentLibraryTab, stateManager, dialogService, preferences, + entryTypesManager, abbreviationRepository, taskExecutor)), + factory.createMenuItem(StandardActions.EXPORT_SELECTED, + new ExportCommand(ExportCommand.ExportMethod.EXPORT_SELECTED, + frame::getCurrentLibraryTab, stateManager, dialogService, preferences, + entryTypesManager, abbreviationRepository, taskExecutor)), + factory.createMenuItem(StandardActions.SAVE_SELECTED_AS_PLAIN_BIBTEX, + new SaveAction(SaveAction.SaveMethod.SAVE_SELECTED, frame::getCurrentLibraryTab, + dialogService, preferences, stateManager))), + + new SeparatorMenuItem(), + + // region: Sharing of the library + + // TODO: Should be only enabled if not yet shared. + factory.createSubMenu(StandardActions.GIT, + factory.createMenuItem(StandardActions.GIT_SHARE, + new GitShareToGitHubAction(dialogService, stateManager))), + + factory.createSubMenu(StandardActions.REMOTE_DB, + factory.createMenuItem(StandardActions.CONNECT_TO_SHARED_DB, + new ConnectToSharedDatabaseCommand(frame, dialogService)), + factory.createMenuItem(StandardActions.PULL_CHANGES_FROM_SHARED_DB, + new PullChangesFromSharedAction(stateManager))), + + // endregion + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.SHOW_PREFS, new ShowPreferencesAction(frame, dialogService)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.QUIT, new JabRefFrame.CloseAction(frame))); + + edit.getItems() + .addAll(factory.createMenuItem(StandardActions.UNDO, + new UndoAction(frame::getCurrentLibraryTab, undoManager, dialogService, stateManager)), + factory.createMenuItem(StandardActions.REDO, + new RedoAction(frame::getCurrentLibraryTab, undoManager, dialogService, stateManager)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.CUT, + new EditAction(StandardActions.CUT, frame::getCurrentLibraryTab, stateManager, + undoManager)), + + factory.createMenuItem(StandardActions.COPY, + new EditAction(StandardActions.COPY, frame::getCurrentLibraryTab, stateManager, + undoManager)), + factory.createSubMenu(StandardActions.COPY_MORE, + factory.createMenuItem(StandardActions.COPY_TITLE, + new CopyMoreAction(StandardActions.COPY_TITLE, dialogService, stateManager, + clipBoardManager, preferences, abbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_KEY, + new CopyMoreAction(StandardActions.COPY_KEY, dialogService, stateManager, + clipBoardManager, preferences, abbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_CITE_KEY, + new CopyMoreAction(StandardActions.COPY_CITE_KEY, dialogService, stateManager, + clipBoardManager, preferences, abbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_KEY_AND_TITLE, + new CopyMoreAction(StandardActions.COPY_KEY_AND_TITLE, dialogService, stateManager, + clipBoardManager, preferences, abbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_KEY_AND_LINK, + new CopyMoreAction(StandardActions.COPY_KEY_AND_LINK, dialogService, stateManager, + clipBoardManager, preferences, abbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_CITATION_PREVIEW, + new CopyCitationAction(CitationStyleOutputFormat.HTML, dialogService, stateManager, + clipBoardManager, taskExecutor, preferences, abbreviationRepository)), + factory.createMenuItem(StandardActions.EXPORT_SELECTED_TO_CLIPBOARD, + new ExportToClipboardAction(dialogService, stateManager, clipBoardManager, + taskExecutor, preferences))), + + factory.createMenuItem(StandardActions.PASTE, + new EditAction(StandardActions.PASTE, frame::getCurrentLibraryTab, stateManager, + undoManager)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.REPLACE_ALL, + new ReplaceStringAction(frame::getCurrentLibraryTab, stateManager, dialogService)), + factory.createMenuItem(StandardActions.GENERATE_CITE_KEYS, + new GenerateCitationKeyAction(frame::getCurrentLibraryTab, dialogService, stateManager, + taskExecutor, preferences, undoManager)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.MANAGE_KEYWORDS, new ManageKeywordsAction(stateManager)), + factory.createMenuItem(StandardActions.AUTOMATIC_FIELD_EDITOR, + new AutomaticFieldEditorAction(stateManager, dialogService, undoManager))); SeparatorMenuItem specialFieldsSeparator = new SeparatorMenuItem(); - specialFieldsSeparator.visibleProperty().bind(preferences.getSpecialFieldsPreferences().specialFieldsEnabledProperty()); - - edit.getItems().addAll( - specialFieldsSeparator, - // ToDo: SpecialField needs the active BasePanel to mark it as changed. - // Refactor BasePanel, should mark the BibDatabaseContext or the UndoManager as dirty instead! - SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.RANKING, factory, frame::getCurrentLibraryTab, dialogService, preferences, undoManager, stateManager), - SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.RELEVANCE, factory, frame::getCurrentLibraryTab, dialogService, preferences, undoManager, stateManager), - SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.QUALITY, factory, frame::getCurrentLibraryTab, dialogService, preferences, undoManager, stateManager), - SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.PRINTED, factory, frame::getCurrentLibraryTab, dialogService, preferences, undoManager, stateManager), - SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.PRIORITY, factory, frame::getCurrentLibraryTab, dialogService, preferences, undoManager, stateManager), - SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.READ_STATUS, factory, frame::getCurrentLibraryTab, dialogService, preferences, undoManager, stateManager)); + specialFieldsSeparator.visibleProperty() + .bind(preferences.getSpecialFieldsPreferences().specialFieldsEnabledProperty()); + + edit.getItems() + .addAll(specialFieldsSeparator, + // ToDo: SpecialField needs the active BasePanel to mark it as + // changed. + // Refactor BasePanel, should mark the BibDatabaseContext or the + // UndoManager as dirty instead! + SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.RANKING, factory, + frame::getCurrentLibraryTab, dialogService, preferences, undoManager, stateManager), + SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.RELEVANCE, factory, + frame::getCurrentLibraryTab, dialogService, preferences, undoManager, stateManager), + SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.QUALITY, factory, + frame::getCurrentLibraryTab, dialogService, preferences, undoManager, stateManager), + SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.PRINTED, factory, + frame::getCurrentLibraryTab, dialogService, preferences, undoManager, stateManager), + SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.PRIORITY, factory, + frame::getCurrentLibraryTab, dialogService, preferences, undoManager, stateManager), + SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.READ_STATUS, factory, + frame::getCurrentLibraryTab, dialogService, preferences, undoManager, stateManager)); edit.addEventHandler(ActionEvent.ACTION, event -> { - // Work around for mac only issue, where cmd+v on a dialogue triggers the paste action of menu item, resulting in addition of the pasted content in the MainTable. - // If the mainscreen is not focused, the actions captured by menu are consumed. + // Work around for mac only issue, where cmd+v on a dialogue triggers the + // paste action of menu item, resulting in addition of the pasted content in + // the MainTable. + // If the mainscreen is not focused, the actions captured by menu are + // consumed. boolean isStageUnfocused = !Injector.instantiateModelOrService(Stage.class).focusedProperty().get(); if (OS.OS_X && isStageUnfocused) { @@ -255,194 +319,283 @@ private void createMenu() { } }); - library.getItems().addAll( - factory.createMenuItem(StandardActions.ADD_ENTRY_IMMEDIATE, new NewEntryAction(true, frame::getCurrentLibraryTab, dialogService, preferences, stateManager)), - factory.createMenuItem(StandardActions.ADD_ENTRY, new NewEntryAction(false, frame::getCurrentLibraryTab, dialogService, preferences, stateManager)), - factory.createMenuItem(StandardActions.ADD_ENTRY_IDENTIFIER, new NewEntryAction(NewEntryDialogTab.ENTER_IDENTIFIER, frame::getCurrentLibraryTab, dialogService, preferences, stateManager)), - factory.createMenuItem(StandardActions.ADD_ENTRY_PLAINTEXT, new NewEntryAction(NewEntryDialogTab.INTERPRET_CITATIONS, frame::getCurrentLibraryTab, dialogService, preferences, stateManager)), - factory.createMenuItem(StandardActions.DELETE_ENTRY, new EditAction(StandardActions.DELETE_ENTRY, frame::getCurrentLibraryTab, stateManager, undoManager)), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.OPEN_DATABASE_FOLDER, new JabRefFrame.OpenDatabaseFolder(dialogService, stateManager, preferences, () -> stateManager.getActiveDatabase().orElse(null))), - factory.createMenuItem(StandardActions.OPEN_CONSOLE, new OpenConsoleAction(stateManager, preferences, dialogService)), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.LIBRARY_PROPERTIES, new LibraryPropertiesAction(stateManager)) - ); - - quality.getItems().addAll( - factory.createMenuItem(StandardActions.FIND_DUPLICATES, new DuplicateSearch(frame::getCurrentLibraryTab, dialogService, stateManager, preferences, entryTypesManager, taskExecutor)), - factory.createMenuItem(StandardActions.MERGE_ENTRIES, new MergeEntriesAction(dialogService, stateManager, undoManager, preferences)), - factory.createMenuItem(StandardActions.CHECK_INTEGRITY, new IntegrityCheckAction(frame::getCurrentLibraryTab, preferences, dialogService, stateManager, (UiTaskExecutor) taskExecutor, abbreviationRepository)), - factory.createMenuItem(StandardActions.CHECK_CONSISTENCY, new ConsistencyCheckAction(frame::getCurrentLibraryTab, dialogService, stateManager, preferences, entryTypesManager, (UiTaskExecutor) taskExecutor)), - factory.createMenuItem(StandardActions.CLEANUP_ENTRIES, new CleanupAction(frame::getCurrentLibraryTab, preferences, dialogService, stateManager, taskExecutor, undoManager)), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.SET_FILE_LINKS, new AutoLinkFilesAction(dialogService, preferences, stateManager, undoManager, (UiTaskExecutor) taskExecutor)), - - new SeparatorMenuItem(), - - factory.createSubMenu(StandardActions.ABBREVIATE, - factory.createMenuItem(StandardActions.ABBREVIATE_DEFAULT, new AbbreviateAction(StandardActions.ABBREVIATE_DEFAULT, frame::getCurrentLibraryTab, dialogService, stateManager, preferences.getJournalAbbreviationPreferences(), abbreviationRepository, taskExecutor, undoManager)), - factory.createMenuItem(StandardActions.ABBREVIATE_DOTLESS, new AbbreviateAction(StandardActions.ABBREVIATE_DOTLESS, frame::getCurrentLibraryTab, dialogService, stateManager, preferences.getJournalAbbreviationPreferences(), abbreviationRepository, taskExecutor, undoManager)), - factory.createMenuItem(StandardActions.ABBREVIATE_SHORTEST_UNIQUE, new AbbreviateAction(StandardActions.ABBREVIATE_SHORTEST_UNIQUE, frame::getCurrentLibraryTab, dialogService, stateManager, preferences.getJournalAbbreviationPreferences(), abbreviationRepository, taskExecutor, undoManager)), - factory.createMenuItem(StandardActions.ABBREVIATE_LTWA, new AbbreviateAction(StandardActions.ABBREVIATE_LTWA, frame::getCurrentLibraryTab, dialogService, stateManager, preferences.getJournalAbbreviationPreferences(), abbreviationRepository, taskExecutor, undoManager))), - - factory.createMenuItem(StandardActions.UNABBREVIATE, new AbbreviateAction(StandardActions.UNABBREVIATE, frame::getCurrentLibraryTab, dialogService, stateManager, preferences.getJournalAbbreviationPreferences(), abbreviationRepository, taskExecutor, undoManager)) - ); + library.getItems() + .addAll(factory.createMenuItem(StandardActions.ADD_ENTRY_IMMEDIATE, + new NewEntryAction(true, frame::getCurrentLibraryTab, dialogService, preferences, stateManager)), + factory.createMenuItem(StandardActions.ADD_ENTRY, + new NewEntryAction(false, frame::getCurrentLibraryTab, dialogService, preferences, + stateManager)), + factory.createMenuItem(StandardActions.ADD_ENTRY_IDENTIFIER, + new NewEntryAction(NewEntryDialogTab.ENTER_IDENTIFIER, frame::getCurrentLibraryTab, + dialogService, preferences, stateManager)), + factory.createMenuItem(StandardActions.ADD_ENTRY_PLAINTEXT, + new NewEntryAction(NewEntryDialogTab.INTERPRET_CITATIONS, frame::getCurrentLibraryTab, + dialogService, preferences, stateManager)), + factory.createMenuItem(StandardActions.DELETE_ENTRY, + new EditAction(StandardActions.DELETE_ENTRY, frame::getCurrentLibraryTab, stateManager, + undoManager)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.OPEN_DATABASE_FOLDER, + new JabRefFrame.OpenDatabaseFolder(dialogService, stateManager, preferences, + () -> stateManager.getActiveDatabase().orElse(null))), + factory.createMenuItem(StandardActions.OPEN_CONSOLE, + new OpenConsoleAction(stateManager, preferences, dialogService)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.LIBRARY_PROPERTIES, + new LibraryPropertiesAction(stateManager))); + + quality.getItems() + .addAll(factory.createMenuItem(StandardActions.FIND_DUPLICATES, + new DuplicateSearch(frame::getCurrentLibraryTab, dialogService, stateManager, preferences, + entryTypesManager, taskExecutor)), + factory.createMenuItem(StandardActions.MERGE_ENTRIES, + new MergeEntriesAction(dialogService, stateManager, undoManager, preferences)), + factory.createMenuItem(StandardActions.CHECK_INTEGRITY, + new IntegrityCheckAction(frame::getCurrentLibraryTab, preferences, dialogService, + stateManager, (UiTaskExecutor) taskExecutor, abbreviationRepository)), + factory.createMenuItem(StandardActions.CHECK_CONSISTENCY, + new ConsistencyCheckAction(frame::getCurrentLibraryTab, dialogService, stateManager, + preferences, entryTypesManager, (UiTaskExecutor) taskExecutor)), + factory.createMenuItem(StandardActions.CLEANUP_ENTRIES, + new CleanupAction(frame::getCurrentLibraryTab, preferences, dialogService, stateManager, + taskExecutor, undoManager)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.SET_FILE_LINKS, + new AutoLinkFilesAction(dialogService, preferences, stateManager, undoManager, + (UiTaskExecutor) taskExecutor)), + + new SeparatorMenuItem(), + + factory.createSubMenu(StandardActions.ABBREVIATE, factory.createMenuItem( + StandardActions.ABBREVIATE_DEFAULT, + new AbbreviateAction(StandardActions.ABBREVIATE_DEFAULT, frame::getCurrentLibraryTab, + dialogService, stateManager, preferences.getJournalAbbreviationPreferences(), + abbreviationRepository, taskExecutor, undoManager)), + factory.createMenuItem(StandardActions.ABBREVIATE_DOTLESS, + new AbbreviateAction(StandardActions.ABBREVIATE_DOTLESS, + frame::getCurrentLibraryTab, dialogService, stateManager, + preferences.getJournalAbbreviationPreferences(), abbreviationRepository, + taskExecutor, undoManager)), + factory.createMenuItem(StandardActions.ABBREVIATE_SHORTEST_UNIQUE, + new AbbreviateAction(StandardActions.ABBREVIATE_SHORTEST_UNIQUE, + frame::getCurrentLibraryTab, dialogService, stateManager, + preferences.getJournalAbbreviationPreferences(), abbreviationRepository, + taskExecutor, undoManager)), + factory.createMenuItem(StandardActions.ABBREVIATE_LTWA, + new AbbreviateAction(StandardActions.ABBREVIATE_LTWA, frame::getCurrentLibraryTab, + dialogService, stateManager, + preferences.getJournalAbbreviationPreferences(), abbreviationRepository, + taskExecutor, undoManager))), + + factory.createMenuItem(StandardActions.UNABBREVIATE, + new AbbreviateAction(StandardActions.UNABBREVIATE, frame::getCurrentLibraryTab, + dialogService, stateManager, preferences.getJournalAbbreviationPreferences(), + abbreviationRepository, taskExecutor, undoManager))); Menu lookupIdentifiers = factory.createSubMenu(StandardActions.LOOKUP_DOC_IDENTIFIER); for (IdFetcher fetcher : WebFetchers.getIdFetchers(preferences.getImportFormatPreferences())) { - LookupIdentifierAction identifierAction = new LookupIdentifierAction<>(fetcher, stateManager, undoManager, dialogService, taskExecutor); + LookupIdentifierAction identifierAction = new LookupIdentifierAction<>(fetcher, stateManager, + undoManager, dialogService, taskExecutor); lookupIdentifiers.getItems().add(factory.createMenuItem(identifierAction.getAction(), identifierAction)); } - lookup.getItems().addAll( - // region identifier-related - lookupIdentifiers, - - factory.createMenuItem( - StandardActions.MERGE_WITH_FETCHED_ENTRY, - new MergeWithFetchedEntryAction(dialogService, stateManager, taskExecutor, preferences, undoManager)), - - factory.createMenuItem( - StandardActions.BATCH_MERGE_WITH_FETCHED_ENTRY, - new BatchEntryMergeWithFetchedDataAction(stateManager, undoManager, preferences, dialogService, taskExecutor)), - // endregion - - new SeparatorMenuItem(), - - // region file-related - factory.createMenuItem(StandardActions.DOWNLOAD_FULL_TEXT, new DownloadFullTextAction(dialogService, stateManager, preferences, (UiTaskExecutor) taskExecutor)), - factory.createMenuItem(StandardActions.FIND_UNLINKED_FILES, new FindUnlinkedFilesAction(dialogService, stateManager)) - // endregion - ); - - final MenuItem pushToApplicationMenuItem = factory.createMenuItem(pushToApplicationCommand.getAction(), pushToApplicationCommand); + lookup.getItems() + .addAll( + // region identifier-related + lookupIdentifiers, + + factory.createMenuItem(StandardActions.MERGE_WITH_FETCHED_ENTRY, + new MergeWithFetchedEntryAction(dialogService, stateManager, taskExecutor, preferences, + undoManager)), + + factory.createMenuItem(StandardActions.BATCH_MERGE_WITH_FETCHED_ENTRY, + new BatchEntryMergeWithFetchedDataAction(stateManager, undoManager, preferences, + dialogService, taskExecutor)), + // endregion + + new SeparatorMenuItem(), + + // region file-related + factory.createMenuItem(StandardActions.DOWNLOAD_FULL_TEXT, + new DownloadFullTextAction(dialogService, stateManager, preferences, + (UiTaskExecutor) taskExecutor)), + factory.createMenuItem(StandardActions.FIND_UNLINKED_FILES, + new FindUnlinkedFilesAction(dialogService, stateManager)) + // endregion + ); + + final MenuItem pushToApplicationMenuItem = factory.createMenuItem(pushToApplicationCommand.getAction(), + pushToApplicationCommand); pushToApplicationCommand.registerReconfigurable(pushToApplicationMenuItem); - tools.getItems().addAll( - factory.createMenuItem(StandardActions.PARSE_LATEX, new ParseLatexAction(stateManager)), - factory.createMenuItem(StandardActions.NEW_SUB_LIBRARY_FROM_AUX, new NewSubLibraryAction(frame, stateManager, dialogService)), - factory.createMenuItem(StandardActions.NEW_LIBRARY_FROM_PDF_ONLINE, new NewLibraryFromPdfActionOnline(frame, stateManager, dialogService, preferences, taskExecutor)), - factory.createMenuItem(StandardActions.NEW_LIBRARY_FROM_PDF_OFFLINE, new NewLibraryFromPdfActionOffline(frame, stateManager, dialogService, preferences, taskExecutor)), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.WRITE_METADATA_TO_PDF, - new WriteMetadataToLinkedPdfsAction(dialogService, preferences.getFieldPreferences(), preferences.getFilePreferences(), preferences.getXmpPreferences(), entryTypesManager, abbreviationRepository, taskExecutor, stateManager)), - factory.createMenuItem(StandardActions.COPY_LINKED_FILES, new CopyFilesAction(dialogService, preferences, stateManager, (UiTaskExecutor) taskExecutor)), // we know at this point that this is a UITaskExecutor - - new SeparatorMenuItem(), - - createSendSubMenu(factory, dialogService, stateManager, preferences), - pushToApplicationMenuItem, - - new SeparatorMenuItem(), - - // Systematic Literature Review (SLR) - factory.createMenuItem(StandardActions.START_NEW_STUDY, new StartNewStudyAction(frame, openDatabaseActionSupplier, fileUpdateMonitor, taskExecutor, preferences, stateManager, dialogService)), - factory.createMenuItem(StandardActions.EDIT_EXISTING_STUDY, new EditExistingStudyAction(dialogService, stateManager)), - factory.createMenuItem(StandardActions.UPDATE_SEARCH_RESULTS_OF_STUDY, new ExistingStudySearchAction(frame, openDatabaseActionSupplier, dialogService, fileUpdateMonitor, taskExecutor, preferences, stateManager)), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.REBUILD_FULLTEXT_SEARCH_INDEX, new RebuildFulltextSearchIndexAction(stateManager, frame::getCurrentLibraryTab, dialogService, preferences)), - factory.createMenuItem(StandardActions.CLEAR_EMBEDDINGS_CACHE, new ClearEmbeddingsAction(stateManager, dialogService, aiService, taskExecutor)), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.REDOWNLOAD_MISSING_FILES, new RedownloadMissingFilesAction(stateManager, dialogService, preferences.getExternalApplicationsPreferences(), preferences.getFilePreferences(), taskExecutor)) - ); + tools.getItems() + .addAll(factory.createMenuItem(StandardActions.PARSE_LATEX, new ParseLatexAction(stateManager)), + factory.createMenuItem(StandardActions.NEW_SUB_LIBRARY_FROM_AUX, + new NewSubLibraryAction(frame, stateManager, dialogService)), + factory.createMenuItem(StandardActions.NEW_LIBRARY_FROM_PDF_ONLINE, + new NewLibraryFromPdfActionOnline(frame, stateManager, dialogService, preferences, + taskExecutor)), + factory.createMenuItem(StandardActions.NEW_LIBRARY_FROM_PDF_OFFLINE, + new NewLibraryFromPdfActionOffline(frame, stateManager, dialogService, preferences, + taskExecutor)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.WRITE_METADATA_TO_PDF, + new WriteMetadataToLinkedPdfsAction(dialogService, preferences.getFieldPreferences(), + preferences.getFilePreferences(), preferences.getXmpPreferences(), + entryTypesManager, abbreviationRepository, taskExecutor, stateManager)), + factory.createMenuItem(StandardActions.COPY_LINKED_FILES, + new CopyFilesAction(dialogService, preferences, stateManager, + (UiTaskExecutor) taskExecutor)), // we know at this + // point that this is + // a UITaskExecutor + + new SeparatorMenuItem(), + + createSendSubMenu(factory, dialogService, stateManager, preferences), pushToApplicationMenuItem, + + new SeparatorMenuItem(), + + // Systematic Literature Review (SLR) + factory.createMenuItem(StandardActions.START_NEW_STUDY, + new StartNewStudyAction(frame, openDatabaseActionSupplier, fileUpdateMonitor, taskExecutor, + preferences, stateManager, dialogService)), + factory.createMenuItem(StandardActions.EDIT_EXISTING_STUDY, + new EditExistingStudyAction(dialogService, stateManager)), + factory.createMenuItem(StandardActions.UPDATE_SEARCH_RESULTS_OF_STUDY, + new ExistingStudySearchAction(frame, openDatabaseActionSupplier, dialogService, + fileUpdateMonitor, taskExecutor, preferences, stateManager)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.REBUILD_FULLTEXT_SEARCH_INDEX, + new RebuildFulltextSearchIndexAction(stateManager, frame::getCurrentLibraryTab, + dialogService, preferences)), + factory.createMenuItem(StandardActions.CLEAR_EMBEDDINGS_CACHE, + new ClearEmbeddingsAction(stateManager, dialogService, aiService, taskExecutor)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.REDOWNLOAD_MISSING_FILES, + new RedownloadMissingFilesAction(stateManager, dialogService, + preferences.getExternalApplicationsPreferences(), preferences.getFilePreferences(), + taskExecutor))); SidePaneType webSearchPane = SidePaneType.WEB_SEARCH; SidePaneType groupsPane = SidePaneType.GROUPS; SidePaneType openOfficePane = SidePaneType.OPEN_OFFICE; - view.getItems().addAll( - factory.createCheckMenuItem(webSearchPane.getToggleAction(), sidePane.getToggleCommandFor(webSearchPane), sidePane.paneVisibleBinding(webSearchPane)), - factory.createCheckMenuItem(groupsPane.getToggleAction(), sidePane.getToggleCommandFor(groupsPane), sidePane.paneVisibleBinding(groupsPane)), - factory.createCheckMenuItem(openOfficePane.getToggleAction(), sidePane.getToggleCommandFor(openOfficePane), sidePane.paneVisibleBinding(openOfficePane)), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.NEXT_PREVIEW_STYLE, new PreviewSwitchAction(PreviewSwitchAction.Direction.NEXT, previewControls, stateManager)), - factory.createMenuItem(StandardActions.PREVIOUS_PREVIEW_STYLE, new PreviewSwitchAction(PreviewSwitchAction.Direction.PREVIOUS, previewControls, stateManager)), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.SHOW_PDF_VIEWER, new ShowDocumentViewerAction(stateManager, preferences)), - factory.createMenuItem(StandardActions.EDIT_ENTRY, new OpenEntryEditorAction(frame::getCurrentLibraryTab, stateManager)), - factory.createMenuItem(StandardActions.OPEN_CONSOLE, new OpenConsoleAction(stateManager, preferences, dialogService)) - ); - - help.getItems().addAll( - factory.createMenuItem(StandardActions.HELP, new HelpAction(HelpFile.CONTENTS, dialogService, preferences.getExternalApplicationsPreferences())), - factory.createMenuItem(StandardActions.OPEN_FORUM, new OpenBrowserAction(URLs.DONATE_URL, dialogService, preferences.getExternalApplicationsPreferences())), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.ERROR_CONSOLE, new ErrorConsoleAction()), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.DONATE, new OpenBrowserAction(URLs.DONATION_URL, dialogService, preferences.getExternalApplicationsPreferences())), - factory.createMenuItem(StandardActions.SEARCH_FOR_UPDATES, new SearchForUpdateAction(preferences, dialogService, taskExecutor)), - factory.createSubMenu(StandardActions.WEB_MENU, - factory.createMenuItem(StandardActions.OPEN_WEBPAGE, new OpenBrowserAction(URLs.WEBPAGE_URL, dialogService, preferences.getExternalApplicationsPreferences())), - factory.createMenuItem(StandardActions.OPEN_PRIVACY_POLICY, new OpenBrowserAction(URLs.PRIVACY_POLICY_URL, dialogService, preferences.getExternalApplicationsPreferences())), - factory.createMenuItem(StandardActions.OPEN_BLOG, new OpenBrowserAction(URLs.BLOG_URL, dialogService, preferences.getExternalApplicationsPreferences())), - factory.createMenuItem(StandardActions.OPEN_LINKEDIN, new OpenBrowserAction(URLs.LINKEDIN_URL, dialogService, preferences.getExternalApplicationsPreferences())), - factory.createMenuItem(StandardActions.OPEN_FACEBOOK, new OpenBrowserAction(URLs.FACEBOOK_URL, dialogService, preferences.getExternalApplicationsPreferences())), - factory.createMenuItem(StandardActions.OPEN_MASTODON, new OpenBrowserAction(URLs.MASTODON_URL, dialogService, preferences.getExternalApplicationsPreferences())), - factory.createMenuItem(StandardActions.OPEN_GITHUB, new OpenBrowserAction(URLs.GITHUB_URL, dialogService, preferences.getExternalApplicationsPreferences())), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.OPEN_DEV_VERSION_LINK, new OpenBrowserAction(URLs.DEV_VERSION_LINK_URL, dialogService, preferences.getExternalApplicationsPreferences())), - factory.createMenuItem(StandardActions.OPEN_CHANGELOG, new OpenBrowserAction(URLs.CHANGELOG_URL, dialogService, preferences.getExternalApplicationsPreferences())) - ), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.OPEN_WELCOME_TAB, new SimpleCommand() { - @Override - public void execute() { - frame.showWelcomeTab(); - } - }), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.ABOUT, new AboutAction(dialogService)) - ); + view.getItems() + .addAll(factory.createCheckMenuItem(webSearchPane.getToggleAction(), + sidePane.getToggleCommandFor(webSearchPane), sidePane.paneVisibleBinding(webSearchPane)), + factory.createCheckMenuItem(groupsPane.getToggleAction(), sidePane.getToggleCommandFor(groupsPane), + sidePane.paneVisibleBinding(groupsPane)), + factory.createCheckMenuItem(openOfficePane.getToggleAction(), + sidePane.getToggleCommandFor(openOfficePane), sidePane.paneVisibleBinding(openOfficePane)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.NEXT_PREVIEW_STYLE, + new PreviewSwitchAction(PreviewSwitchAction.Direction.NEXT, previewControls, stateManager)), + factory.createMenuItem(StandardActions.PREVIOUS_PREVIEW_STYLE, + new PreviewSwitchAction(PreviewSwitchAction.Direction.PREVIOUS, previewControls, + stateManager)), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.SHOW_PDF_VIEWER, + new ShowDocumentViewerAction(stateManager, preferences)), + factory.createMenuItem(StandardActions.EDIT_ENTRY, + new OpenEntryEditorAction(frame::getCurrentLibraryTab, stateManager)), + factory.createMenuItem(StandardActions.OPEN_CONSOLE, + new OpenConsoleAction(stateManager, preferences, dialogService))); + + help.getItems() + .addAll(factory.createMenuItem(StandardActions.HELP, + new HelpAction(HelpFile.CONTENTS, dialogService, preferences.getExternalApplicationsPreferences())), + factory.createMenuItem(StandardActions.OPEN_FORUM, + new OpenBrowserAction(URLs.DONATE_URL, dialogService, + preferences.getExternalApplicationsPreferences())), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.ERROR_CONSOLE, new ErrorConsoleAction()), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.DONATE, + new OpenBrowserAction(URLs.DONATION_URL, dialogService, + preferences.getExternalApplicationsPreferences())), + factory.createMenuItem(StandardActions.SEARCH_FOR_UPDATES, + new SearchForUpdateAction(preferences, dialogService, taskExecutor)), + factory.createSubMenu(StandardActions.WEB_MENU, + factory.createMenuItem(StandardActions.OPEN_WEBPAGE, + new OpenBrowserAction(URLs.WEBPAGE_URL, dialogService, + preferences.getExternalApplicationsPreferences())), + factory.createMenuItem(StandardActions.OPEN_PRIVACY_POLICY, + new OpenBrowserAction(URLs.PRIVACY_POLICY_URL, dialogService, + preferences.getExternalApplicationsPreferences())), + factory.createMenuItem(StandardActions.OPEN_BLOG, + new OpenBrowserAction(URLs.BLOG_URL, dialogService, + preferences.getExternalApplicationsPreferences())), + factory.createMenuItem(StandardActions.OPEN_LINKEDIN, + new OpenBrowserAction(URLs.LINKEDIN_URL, dialogService, + preferences.getExternalApplicationsPreferences())), + factory.createMenuItem(StandardActions.OPEN_FACEBOOK, + new OpenBrowserAction(URLs.FACEBOOK_URL, dialogService, + preferences.getExternalApplicationsPreferences())), + factory.createMenuItem(StandardActions.OPEN_MASTODON, + new OpenBrowserAction(URLs.MASTODON_URL, dialogService, + preferences.getExternalApplicationsPreferences())), + factory.createMenuItem(StandardActions.OPEN_GITHUB, + new OpenBrowserAction(URLs.GITHUB_URL, dialogService, + preferences.getExternalApplicationsPreferences())), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.OPEN_DEV_VERSION_LINK, + new OpenBrowserAction(URLs.DEV_VERSION_LINK_URL, dialogService, + preferences.getExternalApplicationsPreferences())), + factory.createMenuItem(StandardActions.OPEN_CHANGELOG, + new OpenBrowserAction(URLs.CHANGELOG_URL, dialogService, + preferences.getExternalApplicationsPreferences()))), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.OPEN_WELCOME_TAB, new SimpleCommand() { + @Override + public void execute() { + frame.showWelcomeTab(); + } + }), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.ABOUT, new AboutAction(dialogService))); // @formatter:on getStyleClass().add("mainMenu"); - getMenus().addAll( - file, - edit, - library, - quality, - lookup, - tools, - view, - help); + getMenus().addAll(file, edit, library, quality, lookup, tools, view, help); setUseSystemMenuBar(true); } - private Menu createSendSubMenu(ActionFactory factory, - DialogService dialogService, - StateManager stateManager, - GuiPreferences preferences) { + private Menu createSendSubMenu(ActionFactory factory, DialogService dialogService, StateManager stateManager, + GuiPreferences preferences) { Menu sendMenu = factory.createMenu(StandardActions.SEND); - sendMenu.getItems().addAll( - factory.createMenuItem(StandardActions.SEND_AS_EMAIL, new SendAsStandardEmailAction(dialogService, preferences, stateManager, entryTypesManager, taskExecutor)), - factory.createMenuItem(StandardActions.SEND_TO_KINDLE, new SendAsKindleEmailAction(dialogService, preferences, stateManager, taskExecutor)) - ); + sendMenu.getItems() + .addAll(factory.createMenuItem(StandardActions.SEND_AS_EMAIL, + new SendAsStandardEmailAction(dialogService, preferences, stateManager, entryTypesManager, + taskExecutor)), + factory.createMenuItem(StandardActions.SEND_TO_KINDLE, + new SendAsKindleEmailAction(dialogService, preferences, stateManager, taskExecutor))); return sendMenu; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/frame/MainToolBar.java b/jabgui/src/main/java/org/jabref/gui/frame/MainToolBar.java index bf27beab906..19f0fdbd14b 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/MainToolBar.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/MainToolBar.java @@ -46,35 +46,42 @@ import org.controlsfx.control.TaskProgressView; public class MainToolBar extends ToolBar { + private final LibraryTabContainer frame; + private final GuiPushToApplicationCommand pushToApplicationCommand; + private final GlobalSearchBar globalSearchBar; + private final DialogService dialogService; + private final StateManager stateManager; + private final GuiPreferences preferences; + private final AiService aiService; + private final FileUpdateMonitor fileUpdateMonitor; + private final TaskExecutor taskExecutor; + private final BibEntryTypesManager entryTypesManager; + private final ClipBoardManager clipBoardManager; + private final CountingUndoManager undoManager; private PopOver entryFromIdPopOver; + private PopOver progressViewPopOver; + private Subscription taskProgressSubscription; - public MainToolBar(LibraryTabContainer tabContainer, - GuiPushToApplicationCommand pushToApplicationCommand, - GlobalSearchBar globalSearchBar, - DialogService dialogService, - StateManager stateManager, - GuiPreferences preferences, - AiService aiService, - FileUpdateMonitor fileUpdateMonitor, - TaskExecutor taskExecutor, - BibEntryTypesManager entryTypesManager, - ClipBoardManager clipBoardManager, - CountingUndoManager undoManager) { + public MainToolBar(LibraryTabContainer tabContainer, GuiPushToApplicationCommand pushToApplicationCommand, + GlobalSearchBar globalSearchBar, DialogService dialogService, StateManager stateManager, + GuiPreferences preferences, AiService aiService, FileUpdateMonitor fileUpdateMonitor, + TaskExecutor taskExecutor, BibEntryTypesManager entryTypesManager, ClipBoardManager clipBoardManager, + CountingUndoManager undoManager) { this.frame = tabContainer; this.pushToApplicationCommand = pushToApplicationCommand; this.globalSearchBar = globalSearchBar; @@ -97,53 +104,77 @@ private void createToolBar() { final Region leftSpacer = new Region(); final Region rightSpacer = new Region(); - final Button pushToApplicationButton = factory.createIconButton(pushToApplicationCommand.getAction(), pushToApplicationCommand); + final Button pushToApplicationButton = factory.createIconButton(pushToApplicationCommand.getAction(), + pushToApplicationCommand); pushToApplicationCommand.registerReconfigurable(pushToApplicationButton); // Setup Toolbar - getItems().addAll( - new HBox( - factory.createIconButton(StandardActions.NEW_LIBRARY, new NewDatabaseAction(frame, preferences)), - factory.createIconButton(StandardActions.OPEN_LIBRARY, new OpenDatabaseAction(frame, preferences, aiService, dialogService, stateManager, fileUpdateMonitor, entryTypesManager, undoManager, clipBoardManager, taskExecutor)), - factory.createIconButton(StandardActions.SAVE_LIBRARY, new SaveAction(SaveAction.SaveMethod.SAVE, frame::getCurrentLibraryTab, dialogService, preferences, stateManager))), - - leftSpacer, - - globalSearchBar, - - rightSpacer, - - new HBox( - factory.createIconButton(StandardActions.ADD_ENTRY_IMMEDIATE, new NewEntryAction(true, frame::getCurrentLibraryTab, dialogService, preferences, stateManager)), - factory.createIconButton(StandardActions.ADD_ENTRY, new NewEntryAction(false, frame::getCurrentLibraryTab, dialogService, preferences, stateManager)), - factory.createIconButton(StandardActions.DELETE_ENTRY, new EditAction(StandardActions.DELETE_ENTRY, frame::getCurrentLibraryTab, stateManager, undoManager))), - - new Separator(Orientation.VERTICAL), - - new HBox( - factory.createIconButton(StandardActions.UNDO, new UndoAction(frame::getCurrentLibraryTab, undoManager, dialogService, stateManager)), - factory.createIconButton(StandardActions.REDO, new RedoAction(frame::getCurrentLibraryTab, undoManager, dialogService, stateManager)), - factory.createIconButton(StandardActions.CUT, new EditAction(StandardActions.CUT, frame::getCurrentLibraryTab, stateManager, undoManager)), - factory.createIconButton(StandardActions.COPY, new EditAction(StandardActions.COPY, frame::getCurrentLibraryTab, stateManager, undoManager)), - factory.createIconButton(StandardActions.PASTE, new EditAction(StandardActions.PASTE, frame::getCurrentLibraryTab, stateManager, undoManager))), - - new Separator(Orientation.VERTICAL), - - new HBox( - pushToApplicationButton, - factory.createIconButton(StandardActions.GENERATE_CITE_KEYS, new GenerateCitationKeyAction(frame::getCurrentLibraryTab, dialogService, stateManager, taskExecutor, preferences, undoManager)), - factory.createIconButton(StandardActions.CLEANUP_ENTRIES, new CleanupAction(frame::getCurrentLibraryTab, preferences, dialogService, stateManager, taskExecutor, undoManager))), - - new Separator(Orientation.VERTICAL), - - new HBox( - createTaskIndicator()), - - new Separator(Orientation.VERTICAL), - - new HBox( - factory.createIconButton(StandardActions.OPEN_GITHUB, new OpenBrowserAction("https://github.com/JabRef/jabref", dialogService, preferences.getExternalApplicationsPreferences())))); + getItems() + .addAll(new HBox( + factory.createIconButton(StandardActions.NEW_LIBRARY, new NewDatabaseAction(frame, preferences)), + factory.createIconButton(StandardActions.OPEN_LIBRARY, + new OpenDatabaseAction(frame, preferences, aiService, dialogService, stateManager, + fileUpdateMonitor, entryTypesManager, undoManager, clipBoardManager, taskExecutor)), + factory.createIconButton(StandardActions.SAVE_LIBRARY, + new SaveAction(SaveAction.SaveMethod.SAVE, frame::getCurrentLibraryTab, dialogService, + preferences, stateManager))), + + leftSpacer, + + globalSearchBar, + + rightSpacer, + + new HBox( + factory.createIconButton(StandardActions.ADD_ENTRY_IMMEDIATE, + new NewEntryAction(true, frame::getCurrentLibraryTab, dialogService, preferences, + stateManager)), + factory.createIconButton(StandardActions.ADD_ENTRY, + new NewEntryAction(false, frame::getCurrentLibraryTab, dialogService, preferences, + stateManager)), + factory.createIconButton(StandardActions.DELETE_ENTRY, + new EditAction(StandardActions.DELETE_ENTRY, frame::getCurrentLibraryTab, + stateManager, undoManager))), + + new Separator(Orientation.VERTICAL), + + new HBox( + factory.createIconButton(StandardActions.UNDO, + new UndoAction(frame::getCurrentLibraryTab, undoManager, dialogService, + stateManager)), + factory.createIconButton(StandardActions.REDO, + new RedoAction(frame::getCurrentLibraryTab, undoManager, dialogService, + stateManager)), + factory.createIconButton(StandardActions.CUT, + new EditAction(StandardActions.CUT, frame::getCurrentLibraryTab, stateManager, + undoManager)), + factory.createIconButton(StandardActions.COPY, + new EditAction(StandardActions.COPY, frame::getCurrentLibraryTab, stateManager, + undoManager)), + factory.createIconButton(StandardActions.PASTE, + new EditAction(StandardActions.PASTE, frame::getCurrentLibraryTab, stateManager, + undoManager))), + + new Separator(Orientation.VERTICAL), + + new HBox(pushToApplicationButton, + factory.createIconButton(StandardActions.GENERATE_CITE_KEYS, + new GenerateCitationKeyAction(frame::getCurrentLibraryTab, dialogService, + stateManager, taskExecutor, preferences, undoManager)), + factory.createIconButton(StandardActions.CLEANUP_ENTRIES, + new CleanupAction(frame::getCurrentLibraryTab, preferences, dialogService, + stateManager, taskExecutor, undoManager))), + + new Separator(Orientation.VERTICAL), + + new HBox(createTaskIndicator()), + + new Separator(Orientation.VERTICAL), + + new HBox(factory.createIconButton(StandardActions.OPEN_GITHUB, + new OpenBrowserAction("https://github.com/JabRef/jabref", dialogService, + preferences.getExternalApplicationsPreferences())))); leftSpacer.setPrefWidth(50); leftSpacer.setMinWidth(Region.USE_PREF_SIZE); @@ -165,7 +196,8 @@ Group createTaskIndicator() { stateManager.getAnyTaskRunning().addListener((observable, oldValue, newValue) -> { if (newValue) { indicator.setTooltip(someTasksRunning); - } else { + } + else { indicator.setTooltip(noTasksRunning); } }); @@ -174,7 +206,8 @@ Group createTaskIndicator() { // hide it and clip it to a square of (width x width) each time width is updated. indicator.widthProperty().addListener((observable, oldValue, newValue) -> { // The indeterminate spinner is wider than the determinate spinner. - // We must make sure they are the same width for the clipping to result in a square of the same size always. + // We must make sure they are the same width for the clipping to result in a + // square of the same size always. if (!indicator.isIndeterminate()) { indicator.setPrefWidth(newValue.doubleValue()); } @@ -192,9 +225,11 @@ Group createTaskIndicator() { } TaskProgressView> taskProgressView = new TaskProgressView<>(); - taskProgressSubscription = EasyBind.bindContent(taskProgressView.getTasks(), stateManager.getRunningBackgroundTasks()); + taskProgressSubscription = EasyBind.bindContent(taskProgressView.getTasks(), + stateManager.getRunningBackgroundTasks()); taskProgressView.setRetainTasks(false); - taskProgressView.setGraphicFactory(task -> ThemeManager.getDownloadIconTitleMap.getOrDefault(task.getTitle(), null)); + taskProgressView + .setGraphicFactory(task -> ThemeManager.getDownloadIconTitleMap.getOrDefault(task.getTitle(), null)); if (progressViewPopOver == null) { progressViewPopOver = new PopOver(taskProgressView); @@ -208,4 +243,5 @@ Group createTaskIndicator() { return new Group(indicator); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/frame/OpenConsoleAction.java b/jabgui/src/main/java/org/jabref/gui/frame/OpenConsoleAction.java index c60451c37f0..64d0e696bff 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/OpenConsoleAction.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/OpenConsoleAction.java @@ -18,18 +18,23 @@ public class OpenConsoleAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(OpenConsoleAction.class); + private final Supplier databaseContext; + private final StateManager stateManager; + private final GuiPreferences preferences; + private final DialogService dialogService; /** - * Creates a command that opens the console at the path of the supplied database, - * or defaults to the active database. Use - * {@link #OpenConsoleAction(StateManager, GuiPreferences, DialogService)} if not supplying - * another database. + * Creates a command that opens the console at the path of the supplied database, or + * defaults to the active database. Use + * {@link #OpenConsoleAction(StateManager, GuiPreferences, DialogService)} if not + * supplying another database. */ - public OpenConsoleAction(Supplier databaseContext, StateManager stateManager, GuiPreferences preferences, DialogService dialogService) { + public OpenConsoleAction(Supplier databaseContext, StateManager stateManager, + GuiPreferences preferences, DialogService dialogService) { this.databaseContext = databaseContext; this.stateManager = stateManager; this.preferences = preferences; @@ -47,12 +52,17 @@ public OpenConsoleAction(StateManager stateManager, GuiPreferences preferences, @Override public void execute() { - Optional.ofNullable(databaseContext.get()).or(stateManager::getActiveDatabase).flatMap(BibDatabaseContext::getDatabasePath).ifPresent(path -> { - try { - NativeDesktop.openConsole(path, preferences, dialogService); - } catch (IOException e) { - LOGGER.info("Could not open console", e); - } - }); + Optional.ofNullable(databaseContext.get()) + .or(stateManager::getActiveDatabase) + .flatMap(BibDatabaseContext::getDatabasePath) + .ifPresent(path -> { + try { + NativeDesktop.openConsole(path, preferences, dialogService); + } + catch (IOException e) { + LOGGER.info("Could not open console", e); + } + }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/frame/ProcessingLibraryDialog.java b/jabgui/src/main/java/org/jabref/gui/frame/ProcessingLibraryDialog.java index 02061dbdc67..4bdc563bd50 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/ProcessingLibraryDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/ProcessingLibraryDialog.java @@ -10,7 +10,8 @@ import org.jabref.logic.l10n.Localization; /** - * Dialog shown when closing of application needs to wait for a save operation to finish. + * Dialog shown when closing of application needs to wait for a save operation to + * finish. */ public class ProcessingLibraryDialog { @@ -28,7 +29,8 @@ protected Void call() throws InterruptedException { while (libraryTabs.stream().anyMatch(LibraryTab::isSaving)) { if (isCancelled()) { return null; - } else { + } + else { Thread.sleep(100); } } @@ -37,11 +39,9 @@ protected Void call() throws InterruptedException { }; UiTaskExecutor.runInJavaFXThread(waitForSaveFinished); - dialogService.showProgressDialogAndWait( - Localization.lang("Please wait..."), - Localization.lang("Waiting for save operation to finish..."), - waitForSaveFinished - ); + dialogService.showProgressDialogAndWait(Localization.lang("Please wait..."), + Localization.lang("Waiting for save operation to finish..."), waitForSaveFinished); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/frame/SendAsEMailAction.java b/jabgui/src/main/java/org/jabref/gui/frame/SendAsEMailAction.java index f459878e40d..0cce31f6ac7 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/SendAsEMailAction.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/SendAsEMailAction.java @@ -29,24 +29,25 @@ *

    * It uses the mailto:-mechanism *

    - * Microsoft Outlook does not support attachments via mailto - * Therefore, the folder(s), where the file(s) belonging to the entry are stored, - * are opened. This feature is disabled by default and can be switched on at - * preferences/external programs + * Microsoft Outlook does not support attachments via mailto Therefore, the folder(s), + * where the file(s) belonging to the entry are stored, are opened. This feature is + * disabled by default and can be switched on at preferences/external programs */ @AllowedToUseAwt("Requires AWT to send an email") public abstract class SendAsEMailAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(SendAsEMailAction.class); + private final DialogService dialogService; + private final GuiPreferences preferences; + private final StateManager stateManager; + private final TaskExecutor taskExecutor; - public SendAsEMailAction(DialogService dialogService, - GuiPreferences preferences, - StateManager stateManager, - TaskExecutor taskExecutor) { + public SendAsEMailAction(DialogService dialogService, GuiPreferences preferences, StateManager stateManager, + TaskExecutor taskExecutor) { this.dialogService = dialogService; this.preferences = preferences; this.stateManager = stateManager; @@ -55,14 +56,11 @@ public SendAsEMailAction(DialogService dialogService, @Override public void execute() { - BackgroundTask.wrap(this::sendEmail) - .onSuccess(dialogService::notify) - .onFailure(e -> { - String message = Localization.lang("Error creating email"); - LOGGER.warn(message, e); - dialogService.notify(message); - }) - .executeWith(taskExecutor); + BackgroundTask.wrap(this::sendEmail).onSuccess(dialogService::notify).onFailure(e -> { + String message = Localization.lang("Error creating email"); + LOGGER.warn(message, e); + dialogService.notify(message); + }).executeWith(taskExecutor); } private String sendEmail() throws URISyntaxException, IOException { @@ -100,20 +98,24 @@ private URI getUriMailTo(List entries) throws URISyntaxException { } private List getAttachments(List entries) { - // open folders is needed to indirectly support email programs, which cannot handle - // the unofficial "mailto:attachment" property + // open folders is needed to indirectly support email programs, which cannot + // handle + // the unofficial "mailto:attachment" property boolean openFolders = preferences.getExternalApplicationsPreferences().shouldAutoOpenEmailAttachmentsFolder(); BibDatabaseContext databaseContext = stateManager.getActiveDatabase().get(); - List fileList = FileUtil.getListOfLinkedFiles(entries, databaseContext.getFileDirectories(preferences.getFilePreferences())); + List fileList = FileUtil.getListOfLinkedFiles(entries, + databaseContext.getFileDirectories(preferences.getFilePreferences())); List attachments = new ArrayList<>(); for (Path path : fileList) { attachments.add(path.toAbsolutePath().toString()); if (openFolders) { try { - NativeDesktop.openFolderAndSelectFile(path.toAbsolutePath(), preferences.getExternalApplicationsPreferences(), dialogService); - } catch (IOException e) { + NativeDesktop.openFolderAndSelectFile(path.toAbsolutePath(), + preferences.getExternalApplicationsPreferences(), dialogService); + } + catch (IOException e) { LOGGER.debug("Cannot open file", e); } } @@ -126,4 +128,5 @@ private List getAttachments(List entries) { protected abstract String getSubject(); protected abstract String getBody(); + } diff --git a/jabgui/src/main/java/org/jabref/gui/frame/SendAsKindleEmailAction.java b/jabgui/src/main/java/org/jabref/gui/frame/SendAsKindleEmailAction.java index 23df424a3c9..a5c0359be28 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/SendAsKindleEmailAction.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/SendAsKindleEmailAction.java @@ -8,16 +8,18 @@ import org.jabref.logic.util.TaskExecutor; /** - * Sends attachments for selected entries to the - * configured Kindle email + * Sends attachments for selected entries to the configured Kindle email */ public class SendAsKindleEmailAction extends SendAsEMailAction { + private final GuiPreferences preferences; - public SendAsKindleEmailAction(DialogService dialogService, GuiPreferences preferences, StateManager stateManager, TaskExecutor taskExecutor) { + public SendAsKindleEmailAction(DialogService dialogService, GuiPreferences preferences, StateManager stateManager, + TaskExecutor taskExecutor) { super(dialogService, preferences, stateManager, taskExecutor); this.preferences = preferences; - this.executable.bind(ActionHelper.needsEntriesSelected(stateManager).and(ActionHelper.hasLinkedFileForSelectedEntries(stateManager))); + this.executable.bind(ActionHelper.needsEntriesSelected(stateManager) + .and(ActionHelper.hasLinkedFileForSelectedEntries(stateManager))); } @Override @@ -34,4 +36,5 @@ protected String getSubject() { protected String getBody() { return Localization.lang("Send to Kindle"); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/frame/SendAsStandardEmailAction.java b/jabgui/src/main/java/org/jabref/gui/frame/SendAsStandardEmailAction.java index 3e379f9533a..59c2258c30b 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/SendAsStandardEmailAction.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/SendAsStandardEmailAction.java @@ -21,20 +21,20 @@ import org.slf4j.LoggerFactory; /** - * Sends the selected entries to any specifiable email - * by populating the email body + * Sends the selected entries to any specifiable email by populating the email body */ public class SendAsStandardEmailAction extends SendAsEMailAction { + private static final Logger LOGGER = LoggerFactory.getLogger(SendAsStandardEmailAction.class); + private final GuiPreferences preferences; + private final StateManager stateManager; + private final BibEntryTypesManager entryTypesManager; - public SendAsStandardEmailAction(DialogService dialogService, - GuiPreferences preferences, - StateManager stateManager, - BibEntryTypesManager entryTypesManager, - TaskExecutor taskExecutor) { + public SendAsStandardEmailAction(DialogService dialogService, GuiPreferences preferences, StateManager stateManager, + BibEntryTypesManager entryTypesManager, TaskExecutor taskExecutor) { super(dialogService, preferences, stateManager, taskExecutor); this.preferences = preferences; this.stateManager = stateManager; @@ -59,16 +59,19 @@ protected String getBody() { StringWriter rawEntries = new StringWriter(); BibWriter bibWriter = new BibWriter(rawEntries, OS.NEWLINE); - BibEntryWriter bibtexEntryWriter = new BibEntryWriter(new FieldWriter(preferences.getFieldPreferences()), entryTypesManager); + BibEntryWriter bibtexEntryWriter = new BibEntryWriter(new FieldWriter(preferences.getFieldPreferences()), + entryTypesManager); for (BibEntry entry : entries) { try { bibtexEntryWriter.write(entry, bibWriter, databaseContext.getMode()); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.warn("Problem creating BibTeX file for mailing.", e); } } return rawEntries.toString(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/frame/SidePanePreferences.java b/jabgui/src/main/java/org/jabref/gui/frame/SidePanePreferences.java index 86c6751d659..94a8de8c763 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/SidePanePreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/SidePanePreferences.java @@ -12,13 +12,15 @@ import org.jabref.gui.sidepane.SidePaneType; public class SidePanePreferences { + private final ObservableSet visiblePanes; + private final ObservableMap preferredPositions; + private final IntegerProperty webSearchFetcherSelected; - public SidePanePreferences(Set visiblePanes, - Map preferredPositions, - int webSearchFetcherSelected) { + public SidePanePreferences(Set visiblePanes, Map preferredPositions, + int webSearchFetcherSelected) { this.visiblePanes = FXCollections.observableSet(visiblePanes); this.preferredPositions = FXCollections.observableMap(preferredPositions); this.webSearchFetcherSelected = new SimpleIntegerProperty(webSearchFetcherSelected); @@ -48,4 +50,5 @@ public IntegerProperty webSearchFetcherSelectedProperty() { public void setWebSearchFetcherSelected(int webSearchFetcherSelected) { this.webSearchFetcherSelected.set(webSearchFetcherSelected); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/frame/UiMessageHandler.java b/jabgui/src/main/java/org/jabref/gui/frame/UiMessageHandler.java index 38a41d7d83c..4b07bc966f2 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/UiMessageHandler.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/UiMessageHandler.java @@ -12,4 +12,5 @@ public interface UiMessageHandler { void handleUiCommands(List uiCommands); + } diff --git a/jabgui/src/main/java/org/jabref/gui/git/GitConflictResolverDialog.java b/jabgui/src/main/java/org/jabref/gui/git/GitConflictResolverDialog.java index 3a47ca04ea1..027c29311da 100644 --- a/jabgui/src/main/java/org/jabref/gui/git/GitConflictResolverDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/git/GitConflictResolverDialog.java @@ -16,7 +16,9 @@ /// /// Receives a semantic conflict (ThreeWayEntryConflict), pops up an interactive GUI (belonging to mergeentries), and returns a user-confirmed BibEntry merge result. public class GitConflictResolverDialog { + private final DialogService dialogService; + private final GuiPreferences preferences; public GitConflictResolverDialog(DialogService dialogService, GuiPreferences preferences) { @@ -32,13 +34,11 @@ public Optional resolveConflict(ThreeWayEntryConflict conflict) { MergeEntriesDialog dialog = new MergeEntriesDialog(local, remote, preferences); dialog.setLeftHeaderText(Localization.lang("Local")); dialog.setRightHeaderText(Localization.lang("Remote")); - ShowDiffConfig diffConfig = new ShowDiffConfig( - ThreeWayMergeToolbar.DiffView.SPLIT, - DiffHighlighter.BasicDiffMethod.WORDS - ); + ShowDiffConfig diffConfig = new ShowDiffConfig(ThreeWayMergeToolbar.DiffView.SPLIT, + DiffHighlighter.BasicDiffMethod.WORDS); dialog.configureDiff(diffConfig); - return dialogService.showCustomDialogAndWait(dialog) - .map(result -> result.mergedEntry()); + return dialogService.showCustomDialogAndWait(dialog).map(result -> result.mergedEntry()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/git/GitPullAction.java b/jabgui/src/main/java/org/jabref/gui/git/GitPullAction.java index 8cc48df17b8..b42c5ab16c1 100644 --- a/jabgui/src/main/java/org/jabref/gui/git/GitPullAction.java +++ b/jabgui/src/main/java/org/jabref/gui/git/GitPullAction.java @@ -25,16 +25,17 @@ public class GitPullAction extends SimpleCommand { private final DialogService dialogService; + private final StateManager stateManager; + private final GuiPreferences guiPreferences; + private final TaskExecutor taskExecutor; + private final GitHandlerRegistry handlerRegistry; - public GitPullAction(DialogService dialogService, - StateManager stateManager, - GuiPreferences guiPreferences, - TaskExecutor taskExecutor, - GitHandlerRegistry handlerRegistry) { + public GitPullAction(DialogService dialogService, StateManager stateManager, GuiPreferences guiPreferences, + TaskExecutor taskExecutor, GitHandlerRegistry handlerRegistry) { this.dialogService = dialogService; this.stateManager = stateManager; this.guiPreferences = guiPreferences; @@ -46,20 +47,16 @@ public GitPullAction(DialogService dialogService, public void execute() { Optional activeDatabaseOpt = stateManager.getActiveDatabase(); if (activeDatabaseOpt.isEmpty()) { - dialogService.showErrorDialogAndWait( - Localization.lang("No library open"), - Localization.lang("Please open a library before pulling.") - ); + dialogService.showErrorDialogAndWait(Localization.lang("No library open"), + Localization.lang("Please open a library before pulling.")); return; } BibDatabaseContext activeDatabase = activeDatabaseOpt.get(); Optional bibFilePathOpt = activeDatabase.getDatabasePath(); if (bibFilePathOpt.isEmpty()) { - dialogService.showErrorDialogAndWait( - Localization.lang("No library file path"), - Localization.lang("Cannot pull from Git: No file is associated with this library.") - ); + dialogService.showErrorDialogAndWait(Localization.lang("No library file path"), + Localization.lang("Cannot pull from Git: No file is associated with this library.")); return; } @@ -67,54 +64,40 @@ public void execute() { GitHandler handler = new GitHandler(bibFilePath.getParent()); GitConflictResolverDialog dialog = new GitConflictResolverDialog(dialogService, guiPreferences); GitConflictResolverStrategy resolver = new GuiGitConflictResolverStrategy(dialog); - GitSemanticMergeExecutor mergeExecutor = new GitSemanticMergeExecutorImpl(guiPreferences.getImportFormatPreferences()); + GitSemanticMergeExecutor mergeExecutor = new GitSemanticMergeExecutorImpl( + guiPreferences.getImportFormatPreferences()); - GitSyncService syncService = new GitSyncService(guiPreferences.getImportFormatPreferences(), handlerRegistry, resolver, mergeExecutor); + GitSyncService syncService = new GitSyncService(guiPreferences.getImportFormatPreferences(), handlerRegistry, + resolver, mergeExecutor); GitStatusViewModel statusViewModel = new GitStatusViewModel(stateManager, bibFilePath); GitPullViewModel viewModel = new GitPullViewModel(syncService, statusViewModel); - BackgroundTask - .wrap(() -> viewModel.pull()) - .onSuccess(result -> { - if (result.isSuccessful()) { - dialogService.showInformationDialogAndWait( - Localization.lang("Git Pull"), - Localization.lang("Successfully merged and updated.") - ); - } else { - dialogService.showWarningDialogAndWait( - Localization.lang("Git Pull"), - Localization.lang("Merge completed with conflicts.") - ); - } - }) - .onFailure(ex -> { - if (ex instanceof JabRefException e) { - dialogService.showErrorDialogAndWait( - Localization.lang("Git Pull Failed"), - e.getLocalizedMessage(), - e - ); - } else if (ex instanceof GitAPIException e) { - dialogService.showErrorDialogAndWait( - Localization.lang("Git Pull Failed"), - Localization.lang("An unexpected Git error occurred: %0", e.getLocalizedMessage()), - e - ); - } else if (ex instanceof IOException e) { - dialogService.showErrorDialogAndWait( - Localization.lang("Git Pull Failed"), - Localization.lang("I/O error: %0", e.getLocalizedMessage()), - e - ); - } else { - dialogService.showErrorDialogAndWait( - Localization.lang("Git Pull Failed"), - Localization.lang("Unexpected error: %0", ex.getLocalizedMessage()), - ex - ); - } - }) - .executeWith(taskExecutor); + BackgroundTask.wrap(() -> viewModel.pull()).onSuccess(result -> { + if (result.isSuccessful()) { + dialogService.showInformationDialogAndWait(Localization.lang("Git Pull"), + Localization.lang("Successfully merged and updated.")); + } + else { + dialogService.showWarningDialogAndWait(Localization.lang("Git Pull"), + Localization.lang("Merge completed with conflicts.")); + } + }).onFailure(ex -> { + if (ex instanceof JabRefException e) { + dialogService.showErrorDialogAndWait(Localization.lang("Git Pull Failed"), e.getLocalizedMessage(), e); + } + else if (ex instanceof GitAPIException e) { + dialogService.showErrorDialogAndWait(Localization.lang("Git Pull Failed"), + Localization.lang("An unexpected Git error occurred: %0", e.getLocalizedMessage()), e); + } + else if (ex instanceof IOException e) { + dialogService.showErrorDialogAndWait(Localization.lang("Git Pull Failed"), + Localization.lang("I/O error: %0", e.getLocalizedMessage()), e); + } + else { + dialogService.showErrorDialogAndWait(Localization.lang("Git Pull Failed"), + Localization.lang("Unexpected error: %0", ex.getLocalizedMessage()), ex); + } + }).executeWith(taskExecutor); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/git/GitPullViewModel.java b/jabgui/src/main/java/org/jabref/gui/git/GitPullViewModel.java index 6b036abc6d2..b3cfb4417ea 100644 --- a/jabgui/src/main/java/org/jabref/gui/git/GitPullViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/git/GitPullViewModel.java @@ -14,7 +14,9 @@ import org.eclipse.jgit.api.errors.GitAPIException; public class GitPullViewModel extends AbstractViewModel { + private final GitSyncService syncService; + private final GitStatusViewModel gitStatusViewModel; public GitPullViewModel(GitSyncService syncService, GitStatusViewModel gitStatusViewModel) { @@ -29,9 +31,9 @@ public MergeResult pull() throws IOException, GitAPIException, JabRefException { } BibDatabaseContext localBibDatabaseContext = databaseContextOpt.get(); - Path bibFilePath = localBibDatabaseContext.getDatabasePath().orElseThrow(() -> - new JabRefException(Localization.lang("Cannot pull: Please save the library to a file first.")) - ); + Path bibFilePath = localBibDatabaseContext.getDatabasePath() + .orElseThrow(() -> new JabRefException( + Localization.lang("Cannot pull: Please save the library to a file first."))); MergeResult result = syncService.fetchAndMerge(localBibDatabaseContext, bibFilePath); @@ -41,4 +43,5 @@ public MergeResult pull() throws IOException, GitAPIException, JabRefException { return result; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/git/GitShareToGitHubAction.java b/jabgui/src/main/java/org/jabref/gui/git/GitShareToGitHubAction.java index d66c97f1724..05f2f53d286 100644 --- a/jabgui/src/main/java/org/jabref/gui/git/GitShareToGitHubAction.java +++ b/jabgui/src/main/java/org/jabref/gui/git/GitShareToGitHubAction.java @@ -9,12 +9,12 @@ import static org.jabref.gui.actions.ActionHelper.needsDatabase; public class GitShareToGitHubAction extends SimpleCommand { + private final DialogService dialogService; + private final StateManager stateManager; - public GitShareToGitHubAction( - DialogService dialogService, - StateManager stateManager) { + public GitShareToGitHubAction(DialogService dialogService, StateManager stateManager) { this.dialogService = dialogService; this.stateManager = stateManager; @@ -27,12 +27,17 @@ public void execute() { } private BooleanExpression enabledGitShare() { - // TODO: Determine the correct condition for enabling "Git Share". This currently only requires an open library. - // In the future, this may need to check whether: - // - the repo is initialized (because without a repository, the current implementation does not work -> future work) - // - etc. - // Can be called independent if a remote is configured or not -- it will be done in the dialog - // HowTo: Inject the observables (or maybe the stateManager) containing these constraints + // TODO: Determine the correct condition for enabling "Git Share". This currently + // only requires an open library. + // In the future, this may need to check whether: + // - the repo is initialized (because without a repository, the current + // implementation does not work -> future work) + // - etc. + // Can be called independent if a remote is configured or not -- it will be done + // in the dialog + // HowTo: Inject the observables (or maybe the stateManager) containing these + // constraints return needsDatabase(stateManager); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/git/GitShareToGitHubDialogView.java b/jabgui/src/main/java/org/jabref/gui/git/GitShareToGitHubDialogView.java index 34262c93658..06b1e19c6cf 100644 --- a/jabgui/src/main/java/org/jabref/gui/git/GitShareToGitHubDialogView.java +++ b/jabgui/src/main/java/org/jabref/gui/git/GitShareToGitHubDialogView.java @@ -23,20 +23,37 @@ import jakarta.inject.Inject; public class GitShareToGitHubDialogView extends BaseDialog { - private static final String GITHUB_PAT_DOCS_URL = - "https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens"; + + private static final String GITHUB_PAT_DOCS_URL = "https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens"; private static final String GITHUB_NEW_REPO_URL = "https://github.com/new"; - @FXML private TextField repositoryUrl; - @FXML private TextField username; - @FXML private PasswordField personalAccessToken; - @FXML private ButtonType shareButton; - @FXML private Label patHelpIcon; - @FXML private Tooltip patHelpTooltip; - @FXML private CheckBox rememberSettingsCheck; - @FXML private Label repoHelpIcon; - @FXML private Tooltip repoHelpTooltip; + @FXML + private TextField repositoryUrl; + + @FXML + private TextField username; + + @FXML + private PasswordField personalAccessToken; + + @FXML + private ButtonType shareButton; + + @FXML + private Label patHelpIcon; + + @FXML + private Tooltip patHelpTooltip; + + @FXML + private CheckBox rememberSettingsCheck; + + @FXML + private Label repoHelpIcon; + + @FXML + private Tooltip repoHelpTooltip; private GitShareToGitHubDialogViewModel viewModel; @@ -55,14 +72,13 @@ public class GitShareToGitHubDialogView extends BaseDialog { private final ControlsFxVisualizer visualizer = new ControlsFxVisualizer(); public GitShareToGitHubDialogView() { - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); } @FXML private void initialize() { - this.viewModel = new GitShareToGitHubDialogViewModel(preferences.getGitPreferences(), stateManager, dialogService, taskExecutor); + this.viewModel = new GitShareToGitHubDialogViewModel(preferences.getGitPreferences(), stateManager, + dialogService, taskExecutor); this.setTitle(Localization.lang("Share this Library to GitHub")); @@ -78,33 +94,20 @@ private void initialize() { return null; }); - patHelpTooltip.setText( - Localization.lang("Click to open GitHub Personal Access Token documentation") - ); + patHelpTooltip.setText(Localization.lang("Click to open GitHub Personal Access Token documentation")); username.setPromptText(Localization.lang("Your GitHub username")); personalAccessToken.setPromptText(Localization.lang("PAT with repo access")); - repoHelpTooltip.setText( - Localization.lang("Create an empty repository on GitHub, then copy the HTTPS URL (ends with .git). Click to open GitHub.") - ); + repoHelpTooltip.setText(Localization.lang( + "Create an empty repository on GitHub, then copy the HTTPS URL (ends with .git). Click to open GitHub.")); Tooltip.install(repoHelpIcon, repoHelpTooltip); - repoHelpIcon.setOnMouseClicked(e -> - NativeDesktop.openBrowserShowPopup( - GITHUB_NEW_REPO_URL, - dialogService, - preferences.getExternalApplicationsPreferences() - ) - ); + repoHelpIcon.setOnMouseClicked(e -> NativeDesktop.openBrowserShowPopup(GITHUB_NEW_REPO_URL, dialogService, + preferences.getExternalApplicationsPreferences())); Tooltip.install(patHelpIcon, patHelpTooltip); - patHelpIcon.setOnMouseClicked(e -> - NativeDesktop.openBrowserShowPopup( - GITHUB_PAT_DOCS_URL, - dialogService, - preferences.getExternalApplicationsPreferences() - ) - ); + patHelpIcon.setOnMouseClicked(e -> NativeDesktop.openBrowserShowPopup(GITHUB_PAT_DOCS_URL, dialogService, + preferences.getExternalApplicationsPreferences())); repositoryUrl.textProperty().bindBidirectional(viewModel.repositoryUrlProperty()); username.textProperty().bindBidirectional(viewModel.usernameProperty()); @@ -126,4 +129,5 @@ private void initialize() { private void shareToGitHub() { viewModel.shareToGitHub(() -> this.close()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/git/GitShareToGitHubDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/git/GitShareToGitHubDialogViewModel.java index ac675234274..851b1561664 100644 --- a/jabgui/src/main/java/org/jabref/gui/git/GitShareToGitHubDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/git/GitShareToGitHubDialogViewModel.java @@ -37,77 +37,70 @@ /// "Preferences" dialog for sharing library to GitHub. /// We do not put it into the JabRef preferences dialog because we want these settings to be close to the user. public class GitShareToGitHubDialogViewModel extends AbstractViewModel { + private final StateManager stateManager; // The preferences stored in JabRef private final GitPreferences gitPreferences; + private final DialogService dialogService; + private final TaskExecutor taskExecutor; // The preferences of this dialog private final StringProperty usernameProperty = new SimpleStringProperty(""); + private final StringProperty patProperty = new SimpleStringProperty(""); - // TODO: This should be a library preference -> the library is connected to repository; not all JabRef libraries to the same one - // Reason: One could have https://github.com/JabRef/JabRef-exmple-libraries as one repo and https://github.com/myexampleuser/demolibs as onther repository - // Both share the same secrets, but are different URLs. - // Also think of having two .bib files in the same folder - they will have the same repository URL -- should make no issues, but let's see... + // TODO: This should be a library preference -> the library is connected to + // repository; not all JabRef libraries to the same one + // Reason: One could have https://github.com/JabRef/JabRef-exmple-libraries as one + // repo and https://github.com/myexampleuser/demolibs as onther repository + // Both share the same secrets, but are different URLs. + // Also think of having two .bib files in the same folder - they will have the same + // repository URL -- should make no issues, but let's see... private final StringProperty repositoryUrlProperty = new SimpleStringProperty(""); + private final BooleanProperty rememberPatProperty = new SimpleBooleanProperty(); private final Validator repositoryUrlValidator; + private final Validator githubUsernameValidator; + private final Validator githubPatValidator; - public GitShareToGitHubDialogViewModel( - GitPreferences gitPreferences, - StateManager stateManager, - DialogService dialogService, - TaskExecutor taskExecutor) { + public GitShareToGitHubDialogViewModel(GitPreferences gitPreferences, StateManager stateManager, + DialogService dialogService, TaskExecutor taskExecutor) { this.stateManager = stateManager; this.gitPreferences = gitPreferences; this.dialogService = dialogService; this.taskExecutor = taskExecutor; - repositoryUrlValidator = new FunctionBasedValidator<>( - repositoryUrlProperty, - githubHttpsUrlValidator(), - ValidationMessage.error(Localization.lang("Please enter a valid HTTPS GitHub repository URL")) - ); - githubUsernameValidator = new FunctionBasedValidator<>( - usernameProperty, - notEmptyValidator(), - ValidationMessage.error(Localization.lang("GitHub username is required")) - ); - githubPatValidator = new FunctionBasedValidator<>( - patProperty, - notEmptyValidator(), - ValidationMessage.error(Localization.lang("Personal Access Token is required")) - ); + repositoryUrlValidator = new FunctionBasedValidator<>(repositoryUrlProperty, githubHttpsUrlValidator(), + ValidationMessage.error(Localization.lang("Please enter a valid HTTPS GitHub repository URL"))); + githubUsernameValidator = new FunctionBasedValidator<>(usernameProperty, notEmptyValidator(), + ValidationMessage.error(Localization.lang("GitHub username is required"))); + githubPatValidator = new FunctionBasedValidator<>(patProperty, notEmptyValidator(), + ValidationMessage.error(Localization.lang("Personal Access Token is required"))); } /// @implNote `close` Is a runnable to make testing easier public void shareToGitHub(Runnable close) { - // We store the settings because "Share" implies that the settings should be used as typed - // We also have the option to not store the settings permanently: This is implemented in JabRefCliPreferences at the listeners. + // We store the settings because "Share" implies that the settings should be used + // as typed + // We also have the option to not store the settings permanently: This is + // implemented in JabRefCliPreferences at the listeners. this.storeSettings(); - BackgroundTask - .wrap(() -> { - this.doShareToGitHub(); - return null; - }) - .onSuccess(_ -> { - dialogService.notify(Localization.lang("Successfully pushed to GitHub.")); - close.run(); - }) - .onFailure(e -> - dialogService.showErrorDialogAndWait( - Localization.lang("GitHub share failed"), - e.getMessage(), - e - ) - ) - .executeWith(taskExecutor); + BackgroundTask.wrap(() -> { + this.doShareToGitHub(); + return null; + }).onSuccess(_ -> { + dialogService.notify(Localization.lang("Successfully pushed to GitHub.")); + close.run(); + }) + .onFailure(e -> dialogService.showErrorDialogAndWait(Localization.lang("GitHub share failed"), + e.getMessage(), e)) + .executeWith(taskExecutor); } /// Method assumes that settings are stored before. @@ -120,12 +113,15 @@ private void doShareToGitHub() throws JabRefException, IOException, GitAPIExcept BibDatabaseContext activeDatabase = activeDatabaseOpt.get(); Optional bibFilePathOpt = activeDatabase.getDatabasePath(); if (bibFilePathOpt.isEmpty()) { - throw new JabRefException(Localization.lang("No library file path. Please save the library to a file first.")); + throw new JabRefException( + Localization.lang("No library file path. Please save the library to a file first.")); } - // We don't get a new preference object (and re-use the existing one instead), because of ADR-0016 + // We don't get a new preference object (and re-use the existing one instead), + // because of ADR-0016 - // TODO: Read remove from the git configuration - and only prompt for a repository if there is none + // TODO: Read remove from the git configuration - and only prompt for a repository + // if there is none String url = gitPreferences.getRepositoryUrl(); String user = gitPreferences.getUsername(); String pat = gitPreferences.getPat(); @@ -142,14 +138,16 @@ private void doShareToGitHub() throws JabRefException, IOException, GitAPIExcept GitStatusSnapshot status = GitStatusChecker.checkStatusAndFetch(handler); if (status.syncStatus() == SyncStatus.BEHIND) { - throw new JabRefException(Localization.lang("Remote repository is not empty. Please pull changes before pushing.")); + throw new JabRefException( + Localization.lang("Remote repository is not empty. Please pull changes before pushing.")); } handler.createCommitOnCurrentBranch(Localization.lang("Share library to GitHub"), false); if (status.syncStatus() == SyncStatus.REMOTE_EMPTY) { handler.pushCurrentBranchCreatingUpstream(); - } else { + } + else { handler.pushCommitsToRemoteRepository(); } } @@ -203,4 +201,5 @@ public StringProperty repositoryUrlProperty() { public BooleanProperty rememberPatProperty() { return rememberPatProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/git/GitStatusViewModel.java b/jabgui/src/main/java/org/jabref/gui/git/GitStatusViewModel.java index 20486d19e2f..d38650a2be2 100644 --- a/jabgui/src/main/java/org/jabref/gui/git/GitStatusViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/git/GitStatusViewModel.java @@ -33,14 +33,22 @@ ///

  • The current sync status (e.g., {@code UP_TO_DATE}, {@code DIVERGED}, etc.)
  • /// public class GitStatusViewModel extends AbstractViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(GitStatusViewModel.class); + private final StateManager stateManager; + private final ObjectProperty databaseContext = new SimpleObjectProperty<>(); + private final ObjectProperty syncStatus = new SimpleObjectProperty<>(SyncStatus.UNTRACKED); + private final BooleanProperty isTracking = new SimpleBooleanProperty(false); + private final BooleanProperty conflictDetected = new SimpleBooleanProperty(false); + // "" denotes that no commit was pulled private final StringProperty lastPulledCommit = new SimpleStringProperty(""); + private @Nullable GitHandler activeHandler = null; public GitStatusViewModel(StateManager stateManager, Path bibFilePath) { @@ -50,7 +58,8 @@ public GitStatusViewModel(StateManager stateManager, Path bibFilePath) { BibDatabaseContext databaseContext1 = newDb.get(); databaseContext.set(databaseContext1); updateStatusFromContext(databaseContext1); - } else { + } + else { LOGGER.debug("No active database with path; resetting Git status."); reset(); } @@ -88,8 +97,8 @@ protected void updateStatusFromContext(BibDatabaseContext context) { } /** - * Clears all internal state to defaults. - * Should be called when switching projects or Git context is lost + * Clears all internal state to defaults. Should be called when switching projects or + * Git context is lost */ public void reset() { activeHandler = null; @@ -104,8 +113,7 @@ public Optional getDatabaseContext() { } public Optional getCurrentBibFile() { - return getDatabaseContext() - .flatMap(BibDatabaseContext::getDatabasePath); + return getDatabaseContext().flatMap(BibDatabaseContext::getDatabasePath); } public ObjectProperty syncStatusProperty() { @@ -159,4 +167,5 @@ public void setLastPulledCommit(String commitHash) { public Optional getActiveHandler() { return Optional.ofNullable(activeHandler); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/git/GuiGitConflictResolverStrategy.java b/jabgui/src/main/java/org/jabref/gui/git/GuiGitConflictResolverStrategy.java index 46ed97339e3..748bfc4ae68 100644 --- a/jabgui/src/main/java/org/jabref/gui/git/GuiGitConflictResolverStrategy.java +++ b/jabgui/src/main/java/org/jabref/gui/git/GuiGitConflictResolverStrategy.java @@ -12,7 +12,9 @@ import org.slf4j.LoggerFactory; public class GuiGitConflictResolverStrategy implements GitConflictResolverStrategy { + private final static Logger LOGGER = LoggerFactory.getLogger(GuiGitConflictResolverStrategy.class); + private final GitConflictResolverDialog dialog; public GuiGitConflictResolverStrategy(GitConflictResolverDialog dialog) { @@ -25,11 +27,13 @@ public List resolveConflicts(List conflicts) { for (ThreeWayEntryConflict conflict : conflicts) { Optional entryOpt = dialog.resolveConflict(conflict); if (entryOpt.isEmpty()) { - LOGGER.debug("User cancelled conflict resolution for entry {}", conflict.local().getCitationKey().orElse("")); + LOGGER.debug("User cancelled conflict resolution for entry {}", + conflict.local().getCitationKey().orElse("")); return List.of(); } resolved.add(entryOpt.get()); } return resolved; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/GroupColorPicker.java b/jabgui/src/main/java/org/jabref/gui/groups/GroupColorPicker.java index 78fcafe65cf..3a1eccfb92e 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/GroupColorPicker.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/GroupColorPicker.java @@ -59,4 +59,5 @@ public static Color generateColor(List siblingColors, @Nullable Color par private static Color generateSubGroupColor(Color baseColor) { return baseColor.deriveColor(0.0, 1.0, .9, 1.0); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/GroupDescriptions.java b/jabgui/src/main/java/org/jabref/gui/groups/GroupDescriptions.java index 606f7a586a9..108d08868d1 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/GroupDescriptions.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/GroupDescriptions.java @@ -17,7 +17,8 @@ public static String getShortDescriptionKeywordGroup(KeywordGroup keywordGroup, sb.append(""); if (showDynamic) { sb.append("").append(StringUtil.quoteForHTML(keywordGroup.getName())).append(""); - } else { + } + else { sb.append(StringUtil.quoteForHTML(keywordGroup.getName())); } sb.append(" - "); @@ -71,7 +72,8 @@ public static String getShortDescription(SearchGroup searchGroup, boolean showDy sb.append(""); if (showDynamic) { sb.append("").append(StringUtil.quoteForHTML(searchGroup.getName())).append(""); - } else { + } + else { sb.append(StringUtil.quoteForHTML(searchGroup.getName())); } sb.append(" - "); @@ -91,4 +93,5 @@ public static String getShortDescription(SearchGroup searchGroup, boolean showDy } return sb.toString(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/GroupDialogHeader.java b/jabgui/src/main/java/org/jabref/gui/groups/GroupDialogHeader.java index 0cf5d92b173..8285dd7873b 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/GroupDialogHeader.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/GroupDialogHeader.java @@ -1,5 +1,7 @@ package org.jabref.gui.groups; public enum GroupDialogHeader { + GROUP, SUBGROUP + } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/GroupDialogView.java b/jabgui/src/main/java/org/jabref/gui/groups/GroupDialogView.java index 190a80e8fdd..89695dc540e 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/GroupDialogView.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/GroupDialogView.java @@ -66,76 +66,132 @@ public class GroupDialogView extends BaseDialog { private static boolean useAutoColoring = false; // Basic Settings - @FXML private TextField nameField; - @FXML private TextField descriptionField; - @FXML private TextField iconField; - @FXML private Button iconPickerButton; - @FXML private CheckBox colorUseCheckbox; - @FXML private ColorPicker colorField; - @FXML private ComboBox hierarchicalContextCombo; - @FXML private CheckBox autoColorCheckbox; + @FXML + private TextField nameField; + + @FXML + private TextField descriptionField; + + @FXML + private TextField iconField; + + @FXML + private Button iconPickerButton; + + @FXML + private CheckBox colorUseCheckbox; + + @FXML + private ColorPicker colorField; + + @FXML + private ComboBox hierarchicalContextCombo; + + @FXML + private CheckBox autoColorCheckbox; // Type - @FXML private RadioButton explicitRadioButton; - @FXML private RadioButton keywordsRadioButton; - @FXML private RadioButton searchRadioButton; - @FXML private RadioButton autoRadioButton; - @FXML private RadioButton texRadioButton; + @FXML + private RadioButton explicitRadioButton; + + @FXML + private RadioButton keywordsRadioButton; + + @FXML + private RadioButton searchRadioButton; + + @FXML + private RadioButton autoRadioButton; + + @FXML + private RadioButton texRadioButton; // Option Groups - @FXML private TextField keywordGroupSearchTerm; - @FXML private TextField keywordGroupSearchField; - @FXML private CheckBox keywordGroupCaseSensitive; - @FXML private CheckBox keywordGroupRegex; + @FXML + private TextField keywordGroupSearchTerm; - @FXML private TextField searchGroupSearchTerm; - @FXML private CheckBox searchGroupCaseSensitive; - @FXML private CheckBox searchGroupRegex; + @FXML + private TextField keywordGroupSearchField; - @FXML private RadioButton autoGroupKeywordsOption; - @FXML private TextField autoGroupKeywordsField; - @FXML private TextField autoGroupKeywordsDeliminator; - @FXML private TextField autoGroupKeywordsHierarchicalDeliminator; - @FXML private RadioButton autoGroupPersonsOption; - @FXML private TextField autoGroupPersonsField; + @FXML + private CheckBox keywordGroupCaseSensitive; - @FXML private TextField texGroupFilePath; + @FXML + private CheckBox keywordGroupRegex; + + @FXML + private TextField searchGroupSearchTerm; + + @FXML + private CheckBox searchGroupCaseSensitive; + + @FXML + private CheckBox searchGroupRegex; + + @FXML + private RadioButton autoGroupKeywordsOption; + + @FXML + private TextField autoGroupKeywordsField; + + @FXML + private TextField autoGroupKeywordsDeliminator; + + @FXML + private TextField autoGroupKeywordsHierarchicalDeliminator; + + @FXML + private RadioButton autoGroupPersonsOption; + + @FXML + private TextField autoGroupPersonsField; + + @FXML + private TextField texGroupFilePath; private final EnumMap hierarchyText = new EnumMap<>(GroupHierarchyType.class); + private final EnumMap hierarchyToolTip = new EnumMap<>(GroupHierarchyType.class); private final ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer(); private final BibDatabaseContext currentDatabase; + private final @Nullable GroupTreeNode parentNode; + private final @Nullable AbstractGroup editedGroup; private GroupDialogViewModel viewModel; - @Inject private FileUpdateMonitor fileUpdateMonitor; - @Inject private DialogService dialogService; - @Inject private GuiPreferences preferences; - @Inject private StateManager stateManager; + @Inject + private FileUpdateMonitor fileUpdateMonitor; + + @Inject + private DialogService dialogService; + + @Inject + private GuiPreferences preferences; + + @Inject + private StateManager stateManager; - public GroupDialogView(BibDatabaseContext currentDatabase, - @Nullable GroupTreeNode parentNode, - @Nullable AbstractGroup editedGroup, - GroupDialogHeader groupDialogHeader) { + public GroupDialogView(BibDatabaseContext currentDatabase, @Nullable GroupTreeNode parentNode, + @Nullable AbstractGroup editedGroup, GroupDialogHeader groupDialogHeader) { this.currentDatabase = currentDatabase; this.parentNode = parentNode; this.editedGroup = editedGroup; - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); if (editedGroup == null) { if (groupDialogHeader == GroupDialogHeader.GROUP) { this.setTitle(Localization.lang("Add group")); - } else if (groupDialogHeader == GroupDialogHeader.SUBGROUP) { + } + else if (groupDialogHeader == GroupDialogHeader.SUBGROUP) { this.setTitle(Localization.lang("Add subgroup")); } - } else { + } + else { this.setTitle(Localization.lang("Edit group") + " " + editedGroup.getName()); } @@ -146,11 +202,9 @@ public GroupDialogView(BibDatabaseContext currentDatabase, final Button helpButton = (Button) getDialogPane().lookupButton(helpButtonType); ActionFactory actionFactory = new ActionFactory(); - HelpAction helpAction = new HelpAction(HelpFile.GROUPS, dialogService, preferences.getExternalApplicationsPreferences()); - actionFactory.configureIconButton( - StandardActions.HELP_GROUPS, - helpAction, - helpButton); + HelpAction helpAction = new HelpAction(HelpFile.GROUPS, dialogService, + preferences.getExternalApplicationsPreferences()); + actionFactory.configureIconButton(StandardActions.HELP_GROUPS, helpAction, helpButton); // Consume the dialog close event, but execute the help action helpButton.addEventFilter(ActionEvent.ACTION, event -> { @@ -166,23 +220,28 @@ public GroupDialogView(BibDatabaseContext currentDatabase, private @Nullable AbstractGroup parentGroup() { if (parentNode == null) { return null; - } else { + } + else { return parentNode.getGroup(); } } @FXML public void initialize() { - viewModel = new GroupDialogViewModel(dialogService, currentDatabase, preferences, editedGroup, parentNode, fileUpdateMonitor, stateManager); + viewModel = new GroupDialogViewModel(dialogService, currentDatabase, preferences, editedGroup, parentNode, + fileUpdateMonitor, stateManager); setResultConverter(viewModel::resultConverter); hierarchyText.put(GroupHierarchyType.INCLUDING, Localization.lang("Union")); - hierarchyToolTip.put(GroupHierarchyType.INCLUDING, Localization.lang("Include subgroups: When selected, view entries contained in this group or its subgroups")); + hierarchyToolTip.put(GroupHierarchyType.INCLUDING, Localization + .lang("Include subgroups: When selected, view entries contained in this group or its subgroups")); hierarchyText.put(GroupHierarchyType.REFINING, Localization.lang("Intersection")); - hierarchyToolTip.put(GroupHierarchyType.REFINING, Localization.lang("Refine supergroup: When selected, view entries contained in both this group and its supergroup")); + hierarchyToolTip.put(GroupHierarchyType.REFINING, Localization + .lang("Refine supergroup: When selected, view entries contained in both this group and its supergroup")); hierarchyText.put(GroupHierarchyType.INDEPENDENT, Localization.lang("Independent")); - hierarchyToolTip.put(GroupHierarchyType.INDEPENDENT, Localization.lang("Independent group: When selected, view only this group's entries")); + hierarchyToolTip.put(GroupHierarchyType.INDEPENDENT, + Localization.lang("Independent group: When selected, view only this group's entries")); nameField.textProperty().bindBidirectional(viewModel.nameProperty()); descriptionField.textProperty().bindBidirectional(viewModel.descriptionProperty()); @@ -190,10 +249,9 @@ public void initialize() { colorUseCheckbox.selectedProperty().bindBidirectional(viewModel.colorUseProperty()); colorField.valueProperty().bindBidirectional(viewModel.colorFieldProperty()); hierarchicalContextCombo.itemsProperty().bind(viewModel.groupHierarchyListProperty()); - new ViewModelListCellFactory() - .withText(hierarchyText::get) - .withStringTooltip(hierarchyToolTip::get) - .install(hierarchicalContextCombo); + new ViewModelListCellFactory().withText(hierarchyText::get) + .withStringTooltip(hierarchyToolTip::get) + .install(hierarchicalContextCombo); hierarchicalContextCombo.valueProperty().bindBidirectional(viewModel.groupHierarchySelectedProperty()); explicitRadioButton.selectedProperty().bindBidirectional(viewModel.typeExplicitProperty()); @@ -208,15 +266,22 @@ public void initialize() { keywordGroupRegex.selectedProperty().bindBidirectional(viewModel.keywordGroupRegexProperty()); searchGroupSearchTerm.textProperty().bindBidirectional(viewModel.searchGroupSearchTermProperty()); - searchGroupCaseSensitive.setSelected(viewModel.searchFlagsProperty().getValue().contains(SearchFlags.CASE_SENSITIVE)); - searchGroupCaseSensitive.selectedProperty().addListener((observable, oldValue, newValue) -> viewModel.setSearchFlag(SearchFlags.CASE_SENSITIVE, newValue)); - searchGroupRegex.setSelected(viewModel.searchFlagsProperty().getValue().contains(SearchFlags.REGULAR_EXPRESSION)); - searchGroupRegex.selectedProperty().addListener((observable, oldValue, newValue) -> viewModel.setSearchFlag(SearchFlags.REGULAR_EXPRESSION, newValue)); + searchGroupCaseSensitive + .setSelected(viewModel.searchFlagsProperty().getValue().contains(SearchFlags.CASE_SENSITIVE)); + searchGroupCaseSensitive.selectedProperty() + .addListener( + (observable, oldValue, newValue) -> viewModel.setSearchFlag(SearchFlags.CASE_SENSITIVE, newValue)); + searchGroupRegex + .setSelected(viewModel.searchFlagsProperty().getValue().contains(SearchFlags.REGULAR_EXPRESSION)); + searchGroupRegex.selectedProperty() + .addListener((observable, oldValue, newValue) -> viewModel.setSearchFlag(SearchFlags.REGULAR_EXPRESSION, + newValue)); autoGroupKeywordsOption.selectedProperty().bindBidirectional(viewModel.autoGroupKeywordsOptionProperty()); autoGroupKeywordsField.textProperty().bindBidirectional(viewModel.autoGroupKeywordsFieldProperty()); autoGroupKeywordsDeliminator.textProperty().bindBidirectional(viewModel.autoGroupKeywordsDeliminatorProperty()); - autoGroupKeywordsHierarchicalDeliminator.textProperty().bindBidirectional(viewModel.autoGroupKeywordsHierarchicalDeliminatorProperty()); + autoGroupKeywordsHierarchicalDeliminator.textProperty() + .bindBidirectional(viewModel.autoGroupKeywordsHierarchicalDeliminatorProperty()); autoGroupPersonsOption.selectedProperty().bindBidirectional(viewModel.autoGroupPersonsOptionProperty()); autoGroupPersonsField.textProperty().bindBidirectional(viewModel.autoGroupPersonsFieldProperty()); @@ -227,10 +292,13 @@ public void initialize() { validationVisualizer.initVisualization(viewModel.nameValidationStatus(), nameField); validationVisualizer.initVisualization(viewModel.nameContainsDelimiterValidationStatus(), nameField, false); validationVisualizer.initVisualization(viewModel.sameNameValidationStatus(), nameField); - validationVisualizer.initVisualization(viewModel.searchSearchTermEmptyValidationStatus(), searchGroupSearchTerm); + validationVisualizer.initVisualization(viewModel.searchSearchTermEmptyValidationStatus(), + searchGroupSearchTerm); validationVisualizer.initVisualization(viewModel.keywordRegexValidationStatus(), keywordGroupSearchTerm); - validationVisualizer.initVisualization(viewModel.keywordSearchTermEmptyValidationStatus(), keywordGroupSearchTerm); - validationVisualizer.initVisualization(viewModel.keywordFieldEmptyValidationStatus(), keywordGroupSearchField); + validationVisualizer.initVisualization(viewModel.keywordSearchTermEmptyValidationStatus(), + keywordGroupSearchTerm); + validationVisualizer.initVisualization(viewModel.keywordFieldEmptyValidationStatus(), + keywordGroupSearchField); validationVisualizer.initVisualization(viewModel.texGroupFilePathValidatonStatus(), texGroupFilePath); nameField.requestFocus(); }); @@ -245,12 +313,15 @@ public void initialize() { viewModel.colorFieldProperty().setValue(IconTheme.getDefaultGroupColor()); return; } - List colorsOfSiblings = parentNode.getChildren().stream().map(child -> child.getGroup().getColor()) - .flatMap(Optional::stream) - .toList(); + List colorsOfSiblings = parentNode.getChildren() + .stream() + .map(child -> child.getGroup().getColor()) + .flatMap(Optional::stream) + .toList(); Optional parentColor = parentGroup().getColor(); Color color; - color = parentColor.map(value -> GroupColorPicker.generateColor(colorsOfSiblings, value)).orElseGet(() -> GroupColorPicker.generateColor(colorsOfSiblings)); + color = parentColor.map(value -> GroupColorPicker.generateColor(colorsOfSiblings, value)) + .orElseGet(() -> GroupColorPicker.generateColor(colorsOfSiblings)); viewModel.colorFieldProperty().setValue(color); }); } @@ -265,7 +336,8 @@ private void openIconPicker() { ObservableList ikonList = FXCollections.observableArrayList(); FilteredList filteredList = new FilteredList<>(ikonList); - for (IkonProvider provider : ServiceLoader.load(IkonProvider.class.getModule().getLayer(), IkonProvider.class)) { + for (IkonProvider provider : ServiceLoader.load(IkonProvider.class.getModule().getLayer(), + IkonProvider.class)) { if (provider.getClass() != JabrefIconProvider.class) { ikonList.addAll(EnumSet.allOf(provider.getIkon())); } @@ -274,9 +346,9 @@ private void openIconPicker() { CustomTextField searchBox = new CustomTextField(); searchBox.setPromptText(Localization.lang("Search...")); searchBox.setLeft(IconTheme.JabRefIcons.SEARCH.getGraphicNode()); - searchBox.textProperty().addListener((obs, oldValue, newValue) -> - filteredList.setPredicate(ikon -> newValue.isEmpty() || ikon.getDescription().toLowerCase() - .contains(newValue.toLowerCase()))); + searchBox.textProperty() + .addListener((obs, oldValue, newValue) -> filteredList.setPredicate(ikon -> newValue.isEmpty() + || ikon.getDescription().toLowerCase().contains(newValue.toLowerCase()))); GridView ikonGridView = new GridView<>(FXCollections.observableArrayList()); ikonGridView.setCellFactory(gridView -> new IkonliCell()); @@ -302,20 +374,23 @@ private void openIconPicker() { } public class IkonliCell extends GridCell { + @Override protected void updateItem(Ikon ikon, boolean empty) { super.updateItem(ikon, empty); if (empty || (ikon == null)) { setText(null); setGraphic(null); - } else { + } + else { FontIcon fontIcon = FontIcon.of(ikon); fontIcon.getStyleClass().setAll("font-icon"); fontIcon.setIconSize(22); setGraphic(fontIcon); setAlignment(Pos.BASELINE_CENTER); setPadding(new Insets(1)); - setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderStroke.THIN))); + setBorder(new Border( + new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderStroke.THIN))); setOnMouseClicked(event -> { iconField.textProperty().setValue(String.valueOf(fontIcon.getIconCode())); @@ -324,5 +399,7 @@ protected void updateItem(Ikon ikon, boolean empty) { }); } } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java index 282de43bb15..abf872a3130 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java @@ -64,65 +64,96 @@ import org.jspecify.annotations.Nullable; public class GroupDialogViewModel { + // Basic Settings private final StringProperty nameProperty = new SimpleStringProperty(""); + private final StringProperty descriptionProperty = new SimpleStringProperty(""); + private final StringProperty iconProperty = new SimpleStringProperty(""); + private final BooleanProperty colorUseProperty = new SimpleBooleanProperty(); + private final ObjectProperty colorProperty = new SimpleObjectProperty<>(); + private final ListProperty groupHierarchyListProperty = new SimpleListProperty<>(); + private final ObjectProperty groupHierarchySelectedProperty = new SimpleObjectProperty<>(); // Type private final BooleanProperty typeExplicitProperty = new SimpleBooleanProperty(); + private final BooleanProperty typeKeywordsProperty = new SimpleBooleanProperty(); + private final BooleanProperty typeSearchProperty = new SimpleBooleanProperty(); + private final BooleanProperty typeAutoProperty = new SimpleBooleanProperty(); + private final BooleanProperty typeTexProperty = new SimpleBooleanProperty(); // Option Groups private final StringProperty keywordGroupSearchTermProperty = new SimpleStringProperty(""); + private final StringProperty keywordGroupSearchFieldProperty = new SimpleStringProperty(""); + private final BooleanProperty keywordGroupCaseSensitiveProperty = new SimpleBooleanProperty(); + private final BooleanProperty keywordGroupRegexProperty = new SimpleBooleanProperty(); private final StringProperty searchGroupSearchTermProperty = new SimpleStringProperty(""); - private final ObjectProperty> searchFlagsProperty = new SimpleObjectProperty<>(EnumSet.noneOf(SearchFlags.class)); + + private final ObjectProperty> searchFlagsProperty = new SimpleObjectProperty<>( + EnumSet.noneOf(SearchFlags.class)); private final BooleanProperty autoGroupKeywordsOptionProperty = new SimpleBooleanProperty(); + private final StringProperty autoGroupKeywordsFieldProperty = new SimpleStringProperty(""); + private final StringProperty autoGroupKeywordsDelimiterProperty = new SimpleStringProperty(""); + private final StringProperty autoGroupKeywordsHierarchicalDelimiterProperty = new SimpleStringProperty(""); + private final BooleanProperty autoGroupPersonsOptionProperty = new SimpleBooleanProperty(); + private final StringProperty autoGroupPersonsFieldProperty = new SimpleStringProperty(""); private final StringProperty texGroupFilePathProperty = new SimpleStringProperty(""); private Validator nameValidator; + private Validator nameContainsDelimiterValidator; + private Validator sameNameValidator; + private Validator keywordRegexValidator; + private Validator keywordFieldEmptyValidator; + private Validator keywordSearchTermEmptyValidator; + private Validator searchSearchTermEmptyValidator; + private Validator texGroupFilePathValidator; + private CompositeValidator validator; private final DialogService dialogService; + private final GuiPreferences preferences; + private final BibDatabaseContext currentDatabase; + private final AbstractGroup editedGroup; + private final GroupTreeNode parentNode; + private final FileUpdateMonitor fileUpdateMonitor; + private final StateManager stateManager; - public GroupDialogViewModel(DialogService dialogService, - BibDatabaseContext currentDatabase, - GuiPreferences preferences, - @Nullable AbstractGroup editedGroup, - @Nullable GroupTreeNode parentNode, - FileUpdateMonitor fileUpdateMonitor, - StateManager stateManager) { + public GroupDialogViewModel(DialogService dialogService, BibDatabaseContext currentDatabase, + GuiPreferences preferences, @Nullable AbstractGroup editedGroup, @Nullable GroupTreeNode parentNode, + FileUpdateMonitor fileUpdateMonitor, StateManager stateManager) { this.dialogService = dialogService; this.preferences = preferences; this.currentDatabase = currentDatabase; @@ -138,142 +169,121 @@ public GroupDialogViewModel(DialogService dialogService, private void setupValidation() { validator = new CompositeValidator(); - nameValidator = new FunctionBasedValidator<>( - nameProperty, - StringUtil::isNotBlank, + nameValidator = new FunctionBasedValidator<>(nameProperty, StringUtil::isNotBlank, ValidationMessage.error(Localization.lang("Please enter a name for the group."))); - nameContainsDelimiterValidator = new FunctionBasedValidator<>( - nameProperty, + nameContainsDelimiterValidator = new FunctionBasedValidator<>(nameProperty, name -> !name.contains(Character.toString(preferences.getBibEntryPreferences().getKeywordSeparator())), - ValidationMessage.warning( - Localization.lang( - "The group name contains the keyword separator \"%0\" and thus probably does not work as expected.", - Character.toString(preferences.getBibEntryPreferences().getKeywordSeparator()) - ))); - - sameNameValidator = new FunctionBasedValidator<>( - nameProperty, - name -> { - Optional rootGroup = currentDatabase.getMetaData().getGroups(); - if (rootGroup.isPresent()) { - boolean groupsExistWithSameName = !rootGroup.get().findChildrenSatisfying(group -> group.getName().equals(name)).isEmpty(); - if ((editedGroup == null) && groupsExistWithSameName) { - // New group but there is already one group with the same name - return false; - } - - // Edit group, changed name to something that is already present - return (editedGroup == null) || editedGroup.getName().equals(name) || !groupsExistWithSameName; - } - return true; - }, - ValidationMessage.warning( - Localization.lang("There already exists a group with the same name.\nIf you use it, it will inherit all entries from this other group.") - ) - ); - - keywordRegexValidator = new FunctionBasedValidator<>( - keywordGroupSearchTermProperty, - input -> { - if (!keywordGroupRegexProperty.get()) { - return true; - } + ValidationMessage.warning(Localization.lang( + "The group name contains the keyword separator \"%0\" and thus probably does not work as expected.", + Character.toString(preferences.getBibEntryPreferences().getKeywordSeparator())))); + + sameNameValidator = new FunctionBasedValidator<>(nameProperty, name -> { + Optional rootGroup = currentDatabase.getMetaData().getGroups(); + if (rootGroup.isPresent()) { + boolean groupsExistWithSameName = !rootGroup.get() + .findChildrenSatisfying(group -> group.getName().equals(name)) + .isEmpty(); + if ((editedGroup == null) && groupsExistWithSameName) { + // New group but there is already one group with the same name + return false; + } - if (StringUtil.isNullOrEmpty(input)) { - return false; - } + // Edit group, changed name to something that is already present + return (editedGroup == null) || editedGroup.getName().equals(name) || !groupsExistWithSameName; + } + return true; + }, ValidationMessage.warning(Localization.lang( + "There already exists a group with the same name.\nIf you use it, it will inherit all entries from this other group."))); - try { - Pattern.compile(input); - return true; - } catch (PatternSyntaxException _) { - return false; - } - }, - ValidationMessage.error("%s > %n %s %n %n %s".formatted( - Localization.lang("Searching for a keyword"), - Localization.lang("Keywords"), - Localization.lang("Invalid regular expression.")))); - - keywordFieldEmptyValidator = new FunctionBasedValidator<>( - keywordGroupSearchFieldProperty, + keywordRegexValidator = new FunctionBasedValidator<>(keywordGroupSearchTermProperty, input -> { + if (!keywordGroupRegexProperty.get()) { + return true; + } + + if (StringUtil.isNullOrEmpty(input)) { + return false; + } + + try { + Pattern.compile(input); + return true; + } + catch (PatternSyntaxException _) { + return false; + } + }, ValidationMessage.error("%s > %n %s %n %n %s".formatted(Localization.lang("Searching for a keyword"), + Localization.lang("Keywords"), Localization.lang("Invalid regular expression.")))); + + keywordFieldEmptyValidator = new FunctionBasedValidator<>(keywordGroupSearchFieldProperty, StringUtil::isNotBlank, ValidationMessage.error(Localization.lang("Please enter a field name to search for a keyword."))); - keywordSearchTermEmptyValidator = new FunctionBasedValidator<>( - keywordGroupSearchTermProperty, + keywordSearchTermEmptyValidator = new FunctionBasedValidator<>(keywordGroupSearchTermProperty, input -> !StringUtil.isNullOrEmpty(input), - ValidationMessage.error("%s > %n %s %n %n %s".formatted( - Localization.lang("Searching for a keyword"), - Localization.lang("Keywords"), - Localization.lang("Search term is empty.") - ))); - - searchSearchTermEmptyValidator = new FunctionBasedValidator<>( - searchGroupSearchTermProperty, - input -> { - if (StringUtil.isNullOrEmpty(input)) { - return false; - } - return new SearchQuery(input).isValid(); - }, - ValidationMessage.error(Localization.lang("Illegal search expression"))); - - texGroupFilePathValidator = new FunctionBasedValidator<>( - texGroupFilePathProperty, - input -> { - if (StringUtil.isBlank(input)) { - return false; - } else { - Path inputPath = getAbsoluteTexGroupPath(input); - if (!inputPath.isAbsolute() || !Files.isRegularFile(inputPath)) { - return false; - } - return FileUtil.getFileExtension(input) - .map("aux"::equalsIgnoreCase) - .orElse(false); - } - }, - ValidationMessage.error(Localization.lang("Please provide a valid aux file."))); + ValidationMessage.error("%s > %n %s %n %n %s".formatted(Localization.lang("Searching for a keyword"), + Localization.lang("Keywords"), Localization.lang("Search term is empty.")))); + + searchSearchTermEmptyValidator = new FunctionBasedValidator<>(searchGroupSearchTermProperty, input -> { + if (StringUtil.isNullOrEmpty(input)) { + return false; + } + return new SearchQuery(input).isValid(); + }, ValidationMessage.error(Localization.lang("Illegal search expression"))); + + texGroupFilePathValidator = new FunctionBasedValidator<>(texGroupFilePathProperty, input -> { + if (StringUtil.isBlank(input)) { + return false; + } + else { + Path inputPath = getAbsoluteTexGroupPath(input); + if (!inputPath.isAbsolute() || !Files.isRegularFile(inputPath)) { + return false; + } + return FileUtil.getFileExtension(input).map("aux"::equalsIgnoreCase).orElse(false); + } + }, ValidationMessage.error(Localization.lang("Please provide a valid aux file."))); typeSearchProperty.addListener((_, _, isSelected) -> { if (Boolean.TRUE.equals(isSelected)) { validator.addValidators(searchSearchTermEmptyValidator); - } else { + } + else { validator.removeValidators(searchSearchTermEmptyValidator); } }); typeKeywordsProperty.addListener((_, _, isSelected) -> { if (Boolean.TRUE.equals(isSelected)) { - validator.addValidators(keywordFieldEmptyValidator, keywordRegexValidator, keywordSearchTermEmptyValidator); - } else { - validator.removeValidators(keywordFieldEmptyValidator, keywordRegexValidator, keywordSearchTermEmptyValidator); + validator.addValidators(keywordFieldEmptyValidator, keywordRegexValidator, + keywordSearchTermEmptyValidator); + } + else { + validator.removeValidators(keywordFieldEmptyValidator, keywordRegexValidator, + keywordSearchTermEmptyValidator); } }); typeTexProperty.addListener((_, _, isSelected) -> { if (Boolean.TRUE.equals(isSelected)) { validator.addValidators(texGroupFilePathValidator); - } else { + } + else { validator.removeValidators(texGroupFilePathValidator); } }); - validator.addValidators(nameValidator, - nameContainsDelimiterValidator, - sameNameValidator); + validator.addValidators(nameValidator, nameContainsDelimiterValidator, sameNameValidator); } /** * Gets the absolute path relative to the LatexFileDirectory, if given a relative path - * * @param input the user input path * @return an absolute path if LatexFileDirectory exists; otherwise, returns input */ private Path getAbsoluteTexGroupPath(String input) { - Optional latexFileDirectory = currentDatabase.getMetaData().getLatexFileDirectory(preferences.getFilePreferences().getUserAndHost()); + Optional latexFileDirectory = currentDatabase.getMetaData() + .getLatexFileDirectory(preferences.getFilePreferences().getUserAndHost()); return latexFileDirectory.map(path -> path.resolve(input)).orElse(Path.of(input)); } @@ -296,54 +306,55 @@ public AbstractGroup resultConverter(ButtonType button) { try { String groupName = nameProperty.getValue().trim(); if (Boolean.TRUE.equals(typeExplicitProperty.getValue())) { - resultingGroup = new ExplicitGroup( - groupName, - groupHierarchySelectedProperty.getValue(), + resultingGroup = new ExplicitGroup(groupName, groupHierarchySelectedProperty.getValue(), preferences.getBibEntryPreferences().getKeywordSeparator()); - } else if (Boolean.TRUE.equals(typeKeywordsProperty.getValue())) { + } + else if (Boolean.TRUE.equals(typeKeywordsProperty.getValue())) { if (Boolean.TRUE.equals(keywordGroupRegexProperty.getValue())) { - resultingGroup = new RegexKeywordGroup( - groupName, - groupHierarchySelectedProperty.getValue(), + resultingGroup = new RegexKeywordGroup(groupName, groupHierarchySelectedProperty.getValue(), FieldFactory.parseField(keywordGroupSearchFieldProperty.getValue().trim()), keywordGroupSearchTermProperty.getValue().trim(), keywordGroupCaseSensitiveProperty.getValue()); - } else { - resultingGroup = new WordKeywordGroup( - groupName, - groupHierarchySelectedProperty.getValue(), + } + else { + resultingGroup = new WordKeywordGroup(groupName, groupHierarchySelectedProperty.getValue(), FieldFactory.parseField(keywordGroupSearchFieldProperty.getValue().trim()), keywordGroupSearchTermProperty.getValue().trim(), keywordGroupCaseSensitiveProperty.getValue(), - preferences.getBibEntryPreferences().getKeywordSeparator(), - false); + preferences.getBibEntryPreferences().getKeywordSeparator(), false); } - } else if (Boolean.TRUE.equals(typeSearchProperty.getValue())) { - resultingGroup = new SearchGroup( - groupName, - groupHierarchySelectedProperty.getValue(), - searchGroupSearchTermProperty.getValue().trim(), - searchFlagsProperty.getValue()); + } + else if (Boolean.TRUE.equals(typeSearchProperty.getValue())) { + resultingGroup = new SearchGroup(groupName, groupHierarchySelectedProperty.getValue(), + searchGroupSearchTermProperty.getValue().trim(), searchFlagsProperty.getValue()); if (currentDatabase.getMetaData().getGroupSearchSyntaxVersion().isEmpty()) { - // If the syntax version for search groups is not present, it indicates that the groups - // have not been migrated to the new syntax, or this is the first search group in the library. - // If this is the first search group, set the syntax version to the new version. - // Otherwise, it means that the user did not accept the migration to the new version. + // If the syntax version for search groups is not present, it + // indicates that the groups + // have not been migrated to the new syntax, or this is the first + // search group in the library. + // If this is the first search group, set the syntax version to the + // new version. + // Otherwise, it means that the user did not accept the migration to + // the new version. Optional groups = currentDatabase.getMetaData().getGroups(); if (groups.filter(this::groupOrSubgroupIsSearchGroup).isEmpty()) { - currentDatabase.getMetaData().setGroupSearchSyntaxVersion(SearchGroupsMigrationAction.VERSION_6_0_ALPHA_1); + currentDatabase.getMetaData() + .setGroupSearchSyntaxVersion(SearchGroupsMigrationAction.VERSION_6_0_ALPHA_1); } } Optional indexManager = stateManager.getIndexManager(currentDatabase); if (indexManager.isPresent()) { SearchGroup searchGroup = (SearchGroup) resultingGroup; - searchGroup.setMatchedEntries(indexManager.get().search(searchGroup.getSearchQuery()).getMatchedEntries()); + searchGroup + .setMatchedEntries(indexManager.get().search(searchGroup.getSearchQuery()).getMatchedEntries()); } - } else if (Boolean.TRUE.equals(typeAutoProperty.getValue())) { + } + else if (Boolean.TRUE.equals(typeAutoProperty.getValue())) { if (Boolean.TRUE.equals(autoGroupKeywordsOptionProperty.getValue())) { - // Set default value for delimiters: ',' for base and '>' for hierarchical + // Set default value for delimiters: ',' for base and '>' for + // hierarchical char delimiter = ','; char hierarDelimiter = Keyword.DEFAULT_HIERARCHICAL_DELIMITER; autoGroupKeywordsOptionProperty.setValue(Boolean.TRUE); @@ -354,39 +365,35 @@ public AbstractGroup resultConverter(ButtonType button) { if (!autoGroupKeywordsHierarchicalDelimiterProperty.getValue().isEmpty()) { hierarDelimiter = autoGroupKeywordsHierarchicalDelimiterProperty.getValue().charAt(0); } - resultingGroup = new AutomaticKeywordGroup( - groupName, - groupHierarchySelectedProperty.getValue(), - FieldFactory.parseField(autoGroupKeywordsFieldProperty.getValue().trim()), - delimiter, + resultingGroup = new AutomaticKeywordGroup(groupName, groupHierarchySelectedProperty.getValue(), + FieldFactory.parseField(autoGroupKeywordsFieldProperty.getValue().trim()), delimiter, hierarDelimiter); - } else { - resultingGroup = new AutomaticPersonsGroup( - groupName, - groupHierarchySelectedProperty.getValue(), + } + else { + resultingGroup = new AutomaticPersonsGroup(groupName, groupHierarchySelectedProperty.getValue(), FieldFactory.parseField(autoGroupPersonsFieldProperty.getValue().trim())); } - } else if (Boolean.TRUE.equals(typeTexProperty.getValue())) { - resultingGroup = TexGroup.create( - groupName, - groupHierarchySelectedProperty.getValue(), - Path.of(texGroupFilePathProperty.getValue().trim()), - new DefaultAuxParser(new BibDatabase()), - fileUpdateMonitor, - currentDatabase.getMetaData()); + } + else if (Boolean.TRUE.equals(typeTexProperty.getValue())) { + resultingGroup = TexGroup.create(groupName, groupHierarchySelectedProperty.getValue(), + Path.of(texGroupFilePathProperty.getValue().trim()), new DefaultAuxParser(new BibDatabase()), + fileUpdateMonitor, currentDatabase.getMetaData()); } if (resultingGroup != null) { - preferences.getGroupsPreferences().setDefaultHierarchicalContext(groupHierarchySelectedProperty.getValue()); + preferences.getGroupsPreferences() + .setDefaultHierarchicalContext(groupHierarchySelectedProperty.getValue()); - resultingGroup.setColor(Boolean.TRUE.equals(colorUseProperty.getValue()) ? colorProperty.getValue() : null); + resultingGroup + .setColor(Boolean.TRUE.equals(colorUseProperty.getValue()) ? colorProperty.getValue() : null); resultingGroup.setDescription(descriptionProperty.getValue()); resultingGroup.setIconName(iconProperty.getValue()); return resultingGroup; } return null; - } catch (IllegalArgumentException | IOException exception) { + } + catch (IllegalArgumentException | IOException exception) { dialogService.showErrorDialogAndWait(exception.getLocalizedMessage(), exception); return null; } @@ -397,21 +404,23 @@ public void setValues() { if (editedGroup == null) { // creating new group -> defaults! - // TODO: Create default group (via org.jabref.logic.groups.DefaultGroupsFactory) and use values + // TODO: Create default group (via + // org.jabref.logic.groups.DefaultGroupsFactory) and use values colorUseProperty.setValue(false); colorProperty.setValue(determineColor()); if (parentNode != null) { parentNode.getGroup() - .getIconName() - .filter(iconName -> !DefaultGroupsFactory.ALL_ENTRIES_GROUP_DEFAULT_ICON.equals(iconName)) - .ifPresent(iconProperty::setValue); + .getIconName() + .filter(iconName -> !DefaultGroupsFactory.ALL_ENTRIES_GROUP_DEFAULT_ICON.equals(iconName)) + .ifPresent(iconProperty::setValue); parentNode.getGroup().getColor().ifPresent(color -> colorUseProperty.setValue(true)); } typeExplicitProperty.setValue(true); groupHierarchySelectedProperty.setValue(preferences.getGroupsPreferences().getDefaultHierarchicalContext()); autoGroupKeywordsOptionProperty.setValue(Boolean.TRUE); - } else { + } + else { nameProperty.setValue(editedGroup.getName()); colorUseProperty.setValue(editedGroup.getColor().isPresent()); colorProperty.setValue(editedGroup.getColor().orElse(IconTheme.getDefaultGroupColor())); @@ -427,7 +436,8 @@ public void setValues() { keywordGroupSearchTermProperty.setValue(group.getSearchExpression()); keywordGroupCaseSensitiveProperty.setValue(group.isCaseSensitive()); keywordGroupRegexProperty.setValue(false); - } else if (editedGroup.getClass() == RegexKeywordGroup.class) { + } + else if (editedGroup.getClass() == RegexKeywordGroup.class) { typeKeywordsProperty.setValue(true); RegexKeywordGroup group = (RegexKeywordGroup) editedGroup; @@ -435,29 +445,35 @@ public void setValues() { keywordGroupSearchTermProperty.setValue(group.getSearchExpression()); keywordGroupCaseSensitiveProperty.setValue(group.isCaseSensitive()); keywordGroupRegexProperty.setValue(true); - } else if (editedGroup.getClass() == SearchGroup.class) { + } + else if (editedGroup.getClass() == SearchGroup.class) { typeSearchProperty.setValue(true); SearchGroup group = (SearchGroup) editedGroup; searchGroupSearchTermProperty.setValue(group.getSearchExpression()); searchFlagsProperty.setValue(group.getSearchFlags()); - } else if (editedGroup.getClass() == ExplicitGroup.class) { + } + else if (editedGroup.getClass() == ExplicitGroup.class) { typeExplicitProperty.setValue(true); - } else if (editedGroup instanceof AutomaticGroup) { + } + else if (editedGroup instanceof AutomaticGroup) { typeAutoProperty.setValue(true); if (editedGroup.getClass() == AutomaticKeywordGroup.class) { AutomaticKeywordGroup group = (AutomaticKeywordGroup) editedGroup; autoGroupKeywordsOptionProperty.setValue(Boolean.TRUE); autoGroupKeywordsDelimiterProperty.setValue(group.getKeywordDelimiter().toString()); - autoGroupKeywordsHierarchicalDelimiterProperty.setValue(group.getKeywordHierarchicalDelimiter().toString()); + autoGroupKeywordsHierarchicalDelimiterProperty + .setValue(group.getKeywordHierarchicalDelimiter().toString()); autoGroupKeywordsFieldProperty.setValue(group.getField().getName()); - } else if (editedGroup.getClass() == AutomaticPersonsGroup.class) { + } + else if (editedGroup.getClass() == AutomaticPersonsGroup.class) { AutomaticPersonsGroup group = (AutomaticPersonsGroup) editedGroup; autoGroupPersonsOptionProperty.setValue(Boolean.TRUE); autoGroupPersonsFieldProperty.setValue(group.getField().getName()); } - } else if (editedGroup.getClass() == TexGroup.class) { + } + else if (editedGroup.getClass() == TexGroup.class) { typeTexProperty.setValue(true); TexGroup group = (TexGroup) editedGroup; @@ -470,28 +486,32 @@ private Color determineColor() { Color color; if (parentNode == null) { color = GroupColorPicker.generateColor(List.of()); - } else { - List colorsOfSiblings = parentNode.getChildren().stream().map(child -> child.getGroup().getColor()) - .flatMap(Optional::stream) - .toList(); + } + else { + List colorsOfSiblings = parentNode.getChildren() + .stream() + .map(child -> child.getGroup().getColor()) + .flatMap(Optional::stream) + .toList(); Optional parentColor = parentNode.getGroup().getColor(); color = parentColor.map(value -> GroupColorPicker.generateColor(colorsOfSiblings, value)) - .orElseGet(() -> GroupColorPicker.generateColor(colorsOfSiblings)); + .orElseGet(() -> GroupColorPicker.generateColor(colorsOfSiblings)); } return color; } public void texGroupBrowse() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.AUX) - .withDefaultExtension(StandardFileType.AUX) - .withInitialDirectory(currentDatabase.getMetaData() - .getLatexFileDirectory(preferences.getFilePreferences().getUserAndHost()) - .orElse(FileUtil.getInitialDirectory(currentDatabase, preferences.getFilePreferences().getWorkingDirectory()))).build(); + .addExtensionFilter(StandardFileType.AUX) + .withDefaultExtension(StandardFileType.AUX) + .withInitialDirectory(currentDatabase.getMetaData() + .getLatexFileDirectory(preferences.getFilePreferences().getUserAndHost()) + .orElse(FileUtil.getInitialDirectory(currentDatabase, + preferences.getFilePreferences().getWorkingDirectory()))) + .build(); dialogService.showFileOpenDialog(fileDialogConfiguration) - .ifPresent(file -> texGroupFilePathProperty.setValue( - FileUtil.relativize(file.toAbsolutePath(), getFileDirectoriesAsPaths()).toString() - )); + .ifPresent(file -> texGroupFilePathProperty + .setValue(FileUtil.relativize(file.toAbsolutePath(), getFileDirectoriesAsPaths()).toString())); } private List getFileDirectoriesAsPaths() { @@ -613,7 +633,8 @@ public ObjectProperty> searchFlagsProperty() { public void setSearchFlag(SearchFlags searchFlag, boolean value) { if (value) { searchFlagsProperty.getValue().add(searchFlag); - } else { + } + else { searchFlagsProperty.getValue().remove(searchFlag); } } @@ -657,4 +678,5 @@ private boolean groupOrSubgroupIsSearchGroup(GroupTreeNode groupTreeNode) { } return false; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/GroupModeViewModel.java b/jabgui/src/main/java/org/jabref/gui/groups/GroupModeViewModel.java index 6813de6227e..15582dc0e2a 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/GroupModeViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/GroupModeViewModel.java @@ -19,7 +19,8 @@ public GroupModeViewModel(Set mode) { public Node getUnionIntersectionGraphic() { if (mode.contains(GroupViewMode.INTERSECTION)) { return JabRefIcons.GROUP_INTERSECTION.getGraphicNode(); - } else { + } + else { return JabRefIcons.GROUP_UNION.getGraphicNode(); } } @@ -27,8 +28,10 @@ public Node getUnionIntersectionGraphic() { public Tooltip getUnionIntersectionTooltip() { if (!mode.contains(GroupViewMode.INTERSECTION)) { return new Tooltip(Localization.lang("Toggle intersection")); - } else { + } + else { return new Tooltip(Localization.lang("Toggle union")); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java b/jabgui/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java index 66ff70471a0..9986e500bd3 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java @@ -62,26 +62,42 @@ public class GroupNodeViewModel { private final SimpleObjectProperty displayName; + private final boolean isRoot; + private final ObservableList children; + private final BibDatabaseContext databaseContext; + private final StateManager stateManager; + private final GroupTreeNode groupNode; + @ADR(38) private final ObservableSet matchedEntries = FXCollections.observableSet(); + private final SimpleBooleanProperty hasChildren; + private final SimpleBooleanProperty expandedProperty = new SimpleBooleanProperty(); + private final BooleanBinding anySelectedEntriesMatched; + private final BooleanBinding allSelectedEntriesMatched; + private final TaskExecutor taskExecutor; + private final CustomLocalDragboard localDragBoard; + private final GuiPreferences preferences; + @SuppressWarnings("FieldCanBeLocal") private final ObservableList entriesList; + @SuppressWarnings("FieldCanBeLocal") private final InvalidationListener onInvalidatedGroup = _ -> refreshGroup(); - public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager stateManager, TaskExecutor taskExecutor, GroupTreeNode groupNode, CustomLocalDragboard localDragBoard, GuiPreferences preferences) { + public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager stateManager, TaskExecutor taskExecutor, + GroupTreeNode groupNode, CustomLocalDragboard localDragBoard, GuiPreferences preferences) { this.databaseContext = Objects.requireNonNull(databaseContext); this.taskExecutor = Objects.requireNonNull(taskExecutor); this.stateManager = Objects.requireNonNull(stateManager); @@ -93,16 +109,18 @@ public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager state isRoot = groupNode.isRoot(); if (groupNode.getGroup() instanceof AutomaticGroup automaticGroup) { children = automaticGroup.createSubgroups(this.databaseContext.getDatabase().getEntries()) - .stream() - .map(this::toViewModel) - .sorted((group1, group2) -> group1.getDisplayName().compareToIgnoreCase(group2.getDisplayName())) - .collect(Collectors.toCollection(FXCollections::observableArrayList)); - } else { + .stream() + .map(this::toViewModel) + .sorted((group1, group2) -> group1.getDisplayName().compareToIgnoreCase(group2.getDisplayName())) + .collect(Collectors.toCollection(FXCollections::observableArrayList)); + } + else { children = EasyBind.mapBacked(groupNode.getChildren(), this::toViewModel); } if (groupNode.getGroup() instanceof TexGroup) { databaseContext.getMetaData().groupsBinding().addListener(new WeakInvalidationListener(onInvalidatedGroup)); - } else if (groupNode.getGroup() instanceof SearchGroup searchGroup) { + } + else if (groupNode.getGroup() instanceof SearchGroup searchGroup) { stateManager.getIndexManager(databaseContext).ifPresent(indexManager -> { searchGroup.setMatchedEntries(indexManager.search(searchGroup.getSearchQuery()).getMatchedEntries()); refreshGroup(); @@ -117,24 +135,32 @@ public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager state expandedProperty.addListener((_, _, newValue) -> groupNode.getGroup().setExpanded(newValue)); // Register listener - // The wrapper created by the FXCollections will set a weak listener on the wrapped list. This weak listener gets garbage collected. Hence, we need to maintain a reference to this list. + // The wrapper created by the FXCollections will set a weak listener on the + // wrapped list. This weak listener gets garbage collected. Hence, we need to + // maintain a reference to this list. entriesList = databaseContext.getDatabase().getEntries(); entriesList.addListener(this::onDatabaseChanged); - EasyObservableList selectedEntriesMatchStatus = EasyBind.map(stateManager.getSelectedEntries(), groupNode::matches); + EasyObservableList selectedEntriesMatchStatus = EasyBind.map(stateManager.getSelectedEntries(), + groupNode::matches); anySelectedEntriesMatched = selectedEntriesMatchStatus.anyMatch(matched -> matched); // 'all' returns 'true' for empty streams, so this has to be checked explicitly - allSelectedEntriesMatched = selectedEntriesMatchStatus.isEmptyBinding().not().and(selectedEntriesMatchStatus.allMatch(matched -> matched)); + allSelectedEntriesMatched = selectedEntriesMatchStatus.isEmptyBinding() + .not() + .and(selectedEntriesMatchStatus.allMatch(matched -> matched)); this.databaseContext.getDatabase().registerListener(new SearchIndexListener()); } - public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager stateManager, TaskExecutor taskExecutor, AbstractGroup group, CustomLocalDragboard localDragboard, GuiPreferences preferences) { + public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager stateManager, TaskExecutor taskExecutor, + AbstractGroup group, CustomLocalDragboard localDragboard, GuiPreferences preferences) { this(databaseContext, stateManager, taskExecutor, new GroupTreeNode(group), localDragboard, preferences); } - static GroupNodeViewModel getAllEntriesGroup(BibDatabaseContext newDatabase, StateManager stateManager, TaskExecutor taskExecutor, CustomLocalDragboard localDragBoard, GuiPreferences preferences) { - return new GroupNodeViewModel(newDatabase, stateManager, taskExecutor, DefaultGroupsFactory.getAllEntriesGroup(), localDragBoard, preferences); + static GroupNodeViewModel getAllEntriesGroup(BibDatabaseContext newDatabase, StateManager stateManager, + TaskExecutor taskExecutor, CustomLocalDragboard localDragBoard, GuiPreferences preferences) { + return new GroupNodeViewModel(newDatabase, stateManager, taskExecutor, + DefaultGroupsFactory.getAllEntriesGroup(), localDragBoard, preferences); } private GroupNodeViewModel toViewModel(GroupTreeNode child) { @@ -142,10 +168,12 @@ private GroupNodeViewModel toViewModel(GroupTreeNode child) { } public List addEntriesToGroup(List entries) { - // TODO: warn if assignment has undesired side effects (modifies a field != keywords) - // if (!WarnAssignmentSideEffects.warnAssignmentSideEffects(group, groupSelector.frame)) + // TODO: warn if assignment has undesired side effects (modifies a field != + // keywords) + // if (!WarnAssignmentSideEffects.warnAssignmentSideEffects(group, + // groupSelector.frame)) // { - // return; // user aborted operation + // return; // user aborted operation // } List changes = groupNode.addEntriesToGroup(entries); @@ -157,7 +185,8 @@ public List addEntriesToGroup(List entries) { return changes; // TODO: Store undo // if (!undo.isEmpty()) { - // groupSelector.concludeAssignment(UndoableChangeEntriesOfGroup.getUndoableEdit(target, undo), target.getNode(), assignedEntries); + // groupSelector.concludeAssignment(UndoableChangeEntriesOfGroup.getUndoableEdit(target, + // undo), target.getNode(), assignedEntries); } public SimpleBooleanProperty expandedProperty() { @@ -212,15 +241,9 @@ public boolean equals(Object o) { @Override public String toString() { - return "GroupNodeViewModel{" + - "displayName='" + displayName + '\'' + - ", isRoot=" + isRoot + - ", icon='" + getIcon() + '\'' + - ", children=" + children + - ", databaseContext=" + databaseContext + - ", groupNode=" + groupNode + - ", matchedEntries=" + matchedEntries + - '}'; + return "GroupNodeViewModel{" + "displayName='" + displayName + '\'' + ", isRoot=" + isRoot + ", icon='" + + getIcon() + '\'' + ", children=" + children + ", databaseContext=" + databaseContext + ", groupNode=" + + groupNode + ", matchedEntries=" + matchedEntries + '}'; } @Override @@ -230,8 +253,7 @@ public int hashCode() { public JabRefIcon getIcon() { Optional iconName = groupNode.getGroup().getIconName(); - return iconName.flatMap(this::parseIcon) - .orElseGet(this::createDefaultIcon); + return iconName.flatMap(this::parseIcon).orElseGet(this::createDefaultIcon); } private JabRefIcon createDefaultIcon() { @@ -263,17 +285,20 @@ private void onDatabaseChanged(ListChangeListener.Change cha while (change.next()) { if (change.wasPermutated()) { // Nothing to do, as permutation doesn't change matched entries - } else if (change.wasUpdated()) { + } + else if (change.wasUpdated()) { for (BibEntry changedEntry : change.getList().subList(change.getFrom(), change.getTo())) { if (groupNode.matches(changedEntry)) { // ADR-0038 matchedEntries.add(changedEntry.getId()); - } else { + } + else { // ADR-0038 matchedEntries.remove(changedEntry.getId()); } } - } else { + } + else { for (BibEntry removedEntry : change.getRemoved()) { // ADR-0038 matchedEntries.remove(removedEntry.getId()); @@ -291,7 +316,8 @@ private void onDatabaseChanged(ListChangeListener.Change cha private void refreshGroup() { UiTaskExecutor.runInJavaFXThread(() -> { updateMatchedEntries(); // Update the entries matched by the group - // "Re-add" to the selected groups if it were selected, this refreshes the entries the user views + // "Re-add" to the selected groups if it were selected, this refreshes the + // entries the user views ObservableList selectedGroups = this.stateManager.getSelectedGroups(this.databaseContext); if (selectedGroups.remove(this.groupNode)) { selectedGroups.add(this.groupNode); @@ -301,17 +327,15 @@ private void refreshGroup() { private void updateMatchedEntries() { // We calculate the new hit value - // We could be more intelligent and try to figure out the new number of hits based on the entry change + // We could be more intelligent and try to figure out the new number of hits based + // on the entry change // for example, a previously matched entry gets removed -> hits = hits - 1 if (preferences.getGroupsPreferences().shouldDisplayGroupCount()) { - BackgroundTask - .wrap(() -> groupNode.findMatches(databaseContext.getDatabase())) - .onSuccess(entries -> { - matchedEntries.clear(); - // ADR-0038 - entries.forEach(entry -> matchedEntries.add(entry.getId())); - }) - .executeWith(taskExecutor); + BackgroundTask.wrap(() -> groupNode.findMatches(databaseContext.getDatabase())).onSuccess(entries -> { + matchedEntries.clear(); + // ADR-0038 + entries.forEach(entry -> matchedEntries.add(entry.getId())); + }).executeWith(taskExecutor); } } @@ -340,10 +364,12 @@ public Optional getChildByPath(String pathToSource) { } /** - * Decides if the content stored in the given {@link Dragboard} can be dropped on the given target row. Currently, the following sources are allowed: + * Decides if the content stored in the given {@link Dragboard} can be dropped on the + * given target row. Currently, the following sources are allowed: *
      - *
    • another group (will be added as subgroup on drop)
    • - *
    • entries if the group implements {@link GroupEntryChanger} (will be assigned to group on drop)
    • + *
    • another group (will be added as subgroup on drop)
    • + *
    • entries if the group implements {@link GroupEntryChanger} (will be assigned to + * group on drop)
    • *
    */ public boolean acceptableDrop(Dragboard dragboard) { @@ -356,13 +382,17 @@ public boolean acceptableDrop(Dragboard dragboard) { public void moveTo(GroupNodeViewModel target) { // TODO: Add undo and display message - // MoveGroupChange undo = new MoveGroupChange(((GroupTreeNodeViewModel)source.getParent()).getNode(), - // source.getNode().getPositionInParent(), target.getNode(), target.getChildCount()); + // MoveGroupChange undo = new + // MoveGroupChange(((GroupTreeNodeViewModel)source.getParent()).getNode(), + // source.getNode().getPositionInParent(), target.getNode(), + // target.getChildCount()); getGroupNode().moveTo(target.getGroupNode()); - // panel.getUndoManager().addEdit(new UndoableMoveGroup(this.groupsRoot, moveChange)); + // panel.getUndoManager().addEdit(new UndoableMoveGroup(this.groupsRoot, + // moveChange)); // panel.markBaseChanged(); - // frame.output(Localization.lang("Moved group \"%0\".", node.getNode().getGroup().getName())); + // frame.output(Localization.lang("Moved group \"%0\".", + // node.getNode().getGroup().getName())); } public void moveTo(GroupTreeNode target, int targetIndex) { @@ -391,7 +421,8 @@ public void draggedOn(GroupNodeViewModel target, DroppingMouseLocation mouseLoca } } - // Different actions depending on where the user releases the drop in the target row + // Different actions depending on where the user releases the drop in the + // target row // Bottom + top -> insert source row before / after this row // Center -> add as child switch (mouseLocation) { @@ -399,7 +430,8 @@ public void draggedOn(GroupNodeViewModel target, DroppingMouseLocation mouseLoca case CENTER -> this.moveTo(target); case TOP -> this.moveTo(targetParent.get(), targetIndex); } - } else { + } + else { // No parent = root -> just add this.moveTo(target); } @@ -419,9 +451,9 @@ public boolean isAllEntriesGroup() { public boolean hasSimilarSearchGroup(SearchGroup searchGroup) { return getChildren().stream() - .filter(child -> child.getGroupNode().getGroup() instanceof SearchGroup) - .map(child -> (SearchGroup) child.getGroupNode().getGroup()) - .anyMatch(group -> group.equals(searchGroup)); + .filter(child -> child.getGroupNode().getGroup() instanceof SearchGroup) + .map(child -> (SearchGroup) child.getGroupNode().getGroup()) + .anyMatch(group -> group.equals(searchGroup)); } public boolean hasAllSuggestedGroups() { @@ -433,28 +465,39 @@ public boolean canAddEntriesIn() { AbstractGroup group = groupNode.getGroup(); if (group instanceof AllEntriesGroup) { return false; - } else if (group instanceof SmartGroup) { + } + else if (group instanceof SmartGroup) { return false; - } else if (group instanceof ExplicitGroup) { + } + else if (group instanceof ExplicitGroup) { return true; - } else if (group instanceof LastNameGroup || group instanceof RegexKeywordGroup) { + } + else if (group instanceof LastNameGroup || group instanceof RegexKeywordGroup) { return groupNode.getParent() - .map(GroupTreeNode::getGroup) - .map(groupParent -> groupParent instanceof AutomaticKeywordGroup || groupParent instanceof AutomaticPersonsGroup) - .orElse(false); - } else if (group instanceof KeywordGroup) { + .map(GroupTreeNode::getGroup) + .map(groupParent -> groupParent instanceof AutomaticKeywordGroup + || groupParent instanceof AutomaticPersonsGroup) + .orElse(false); + } + else if (group instanceof KeywordGroup) { // also covers WordKeywordGroup return true; - } else if (group instanceof SearchGroup) { + } + else if (group instanceof SearchGroup) { return false; - } else if (group instanceof AutomaticKeywordGroup) { + } + else if (group instanceof AutomaticKeywordGroup) { return false; - } else if (group instanceof AutomaticPersonsGroup) { + } + else if (group instanceof AutomaticPersonsGroup) { return false; - } else if (group instanceof TexGroup) { + } + else if (group instanceof TexGroup) { return false; - } else { - throw new UnsupportedOperationException("canAddEntriesIn method not yet implemented in group: " + group.getClass().getName()); + } + else { + throw new UnsupportedOperationException( + "canAddEntriesIn method not yet implemented in group: " + group.getClass().getName()); } } @@ -464,15 +507,17 @@ public boolean canBeDragged() { case AllEntriesGroup _, SmartGroup _ -> false; case ExplicitGroup _, SearchGroup _, AutomaticKeywordGroup _, AutomaticPersonsGroup _, TexGroup _ -> true; case KeywordGroup _ -> - // KeywordGroup is parent of LastNameGroup, RegexKeywordGroup and WordKeywordGroup - groupNode.getParent() - .map(GroupTreeNode::getGroup) - .map(groupParent -> - !(groupParent instanceof AutomaticKeywordGroup || groupParent instanceof AutomaticPersonsGroup)) - .orElse(false); + // KeywordGroup is parent of LastNameGroup, RegexKeywordGroup and + // WordKeywordGroup + groupNode.getParent() + .map(GroupTreeNode::getGroup) + .map(groupParent -> !(groupParent instanceof AutomaticKeywordGroup + || groupParent instanceof AutomaticPersonsGroup)) + .orElse(false); case null -> throw new IllegalArgumentException("Group cannot be null"); - default -> throw new UnsupportedOperationException("canBeDragged method not yet implemented in group: " + group.getClass().getName()); + default -> throw new UnsupportedOperationException( + "canBeDragged method not yet implemented in group: " + group.getClass().getName()); }; } @@ -482,13 +527,16 @@ public boolean canAddGroupsIn() { case AllEntriesGroup _, ExplicitGroup _, SearchGroup _, TexGroup _ -> true; case AutomaticKeywordGroup _, AutomaticPersonsGroup _, SmartGroup _ -> false; case KeywordGroup _ -> - // KeywordGroup is parent of LastNameGroup, RegexKeywordGroup and WordKeywordGroup - groupNode.getParent() - .map(GroupTreeNode::getGroup) - .map(groupParent -> !(groupParent instanceof AutomaticKeywordGroup || groupParent instanceof AutomaticPersonsGroup)) - .orElse(false); + // KeywordGroup is parent of LastNameGroup, RegexKeywordGroup and + // WordKeywordGroup + groupNode.getParent() + .map(GroupTreeNode::getGroup) + .map(groupParent -> !(groupParent instanceof AutomaticKeywordGroup + || groupParent instanceof AutomaticPersonsGroup)) + .orElse(false); case null -> throw new IllegalArgumentException("Group cannot be null"); - default -> throw new UnsupportedOperationException("canAddGroupsIn method not yet implemented in group: " + group.getClass().getName()); + default -> throw new UnsupportedOperationException( + "canAddGroupsIn method not yet implemented in group: " + group.getClass().getName()); }; } @@ -498,13 +546,16 @@ public boolean canRemove() { case AllEntriesGroup _, SmartGroup _ -> false; case ExplicitGroup _, SearchGroup _, AutomaticKeywordGroup _, AutomaticPersonsGroup _, TexGroup _ -> true; case KeywordGroup _ -> - // KeywordGroup is parent of LastNameGroup, RegexKeywordGroup and WordKeywordGroup - groupNode.getParent() - .map(GroupTreeNode::getGroup) - .map(groupParent -> !(groupParent instanceof AutomaticKeywordGroup || groupParent instanceof AutomaticPersonsGroup)) - .orElse(false); + // KeywordGroup is parent of LastNameGroup, RegexKeywordGroup and + // WordKeywordGroup + groupNode.getParent() + .map(GroupTreeNode::getGroup) + .map(groupParent -> !(groupParent instanceof AutomaticKeywordGroup + || groupParent instanceof AutomaticPersonsGroup)) + .orElse(false); case null -> throw new IllegalArgumentException("Group cannot be null"); - default -> throw new UnsupportedOperationException("canRemove method not yet implemented in group: " + group.getClass().getName()); + default -> throw new UnsupportedOperationException( + "canRemove method not yet implemented in group: " + group.getClass().getName()); }; } @@ -514,23 +565,28 @@ public boolean isEditable() { case AllEntriesGroup _, SmartGroup _ -> false; case ExplicitGroup _, SearchGroup _, AutomaticKeywordGroup _, AutomaticPersonsGroup _, TexGroup _ -> true; case KeywordGroup _ -> - // KeywordGroup is parent of LastNameGroup, RegexKeywordGroup and WordKeywordGroup - groupNode.getParent() - .map(GroupTreeNode::getGroup) - .map(groupParent -> !(groupParent instanceof AutomaticKeywordGroup || groupParent instanceof AutomaticPersonsGroup)) - .orElse(false); + // KeywordGroup is parent of LastNameGroup, RegexKeywordGroup and + // WordKeywordGroup + groupNode.getParent() + .map(GroupTreeNode::getGroup) + .map(groupParent -> !(groupParent instanceof AutomaticKeywordGroup + || groupParent instanceof AutomaticPersonsGroup)) + .orElse(false); case null -> throw new IllegalArgumentException("Group cannot be null"); - default -> throw new UnsupportedOperationException("isEditable method not yet implemented in group: " + group.getClass().getName()); + default -> throw new UnsupportedOperationException( + "isEditable method not yet implemented in group: " + group.getClass().getName()); }; } class SearchIndexListener { + @Subscribe public void listen(IndexStartedEvent event) { if (groupNode.getGroup() instanceof SearchGroup searchGroup) { stateManager.getIndexManager(databaseContext).ifPresent(indexManager -> { - searchGroup.setMatchedEntries(indexManager.search(searchGroup.getSearchQuery()).getMatchedEntries()); + searchGroup + .setMatchedEntries(indexManager.search(searchGroup.getSearchQuery()).getMatchedEntries()); refreshGroup(); databaseContext.getMetaData().groupsBinding().invalidate(); }); @@ -542,13 +598,15 @@ public void listen(IndexAddedOrUpdatedEvent event) { if (groupNode.getGroup() instanceof SearchGroup searchGroup) { stateManager.getIndexManager(databaseContext).ifPresent(indexManager -> BackgroundTask.wrap(() -> { for (BibEntry entry : event.entries()) { - searchGroup.updateMatches(entry, indexManager.isEntryMatched(entry, searchGroup.getSearchQuery())); + searchGroup.updateMatches(entry, + indexManager.isEntryMatched(entry, searchGroup.getSearchQuery())); } }).onFinished(() -> { for (BibEntry entry : event.entries()) { if (groupNode.matches(entry)) { matchedEntries.add(entry.getId()); - } else { + } + else { matchedEntries.remove(entry.getId()); } } @@ -572,5 +630,7 @@ public void listen(IndexClosedEvent event) { databaseContext.getDatabase().unregisterListener(this); } } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/GroupTreeNodeViewModel.java b/jabgui/src/main/java/org/jabref/gui/groups/GroupTreeNodeViewModel.java index d8f454931da..17e90cfddf9 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/GroupTreeNodeViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/GroupTreeNodeViewModel.java @@ -20,6 +20,7 @@ import org.jabref.model.groups.SmartGroup; public class GroupTreeNodeViewModel { + private final GroupTreeNode node; public GroupTreeNodeViewModel(GroupTreeNode node) { @@ -52,17 +53,12 @@ public String getDescription() { String shortDescription = ""; boolean showDynamic = true; shortDescription = switch (group) { - case SmartGroup smartGroup -> - GroupDescriptions.getShortDescriptionSmartGroup(smartGroup); - case ExplicitGroup explicitGroup -> - GroupDescriptions.getShortDescriptionExplicitGroup(explicitGroup); + case SmartGroup smartGroup -> GroupDescriptions.getShortDescriptionSmartGroup(smartGroup); + case ExplicitGroup explicitGroup -> GroupDescriptions.getShortDescriptionExplicitGroup(explicitGroup); case KeywordGroup keywordGroup -> - GroupDescriptions.getShortDescriptionKeywordGroup(keywordGroup, showDynamic); - case SearchGroup searchGroup -> - GroupDescriptions.getShortDescription(searchGroup, showDynamic); - case null, - default -> - GroupDescriptions.getShortDescriptionAllEntriesGroup(); + GroupDescriptions.getShortDescriptionKeywordGroup(keywordGroup, showDynamic); + case SearchGroup searchGroup -> GroupDescriptions.getShortDescription(searchGroup, showDynamic); + case null, default -> GroupDescriptions.getShortDescriptionAllEntriesGroup(); }; return "" + shortDescription + ""; } @@ -108,13 +104,11 @@ public boolean canBeEdited() { } public boolean canMoveUp() { - return (getNode().getPreviousSibling().isPresent()) - && !(getNode().getGroup() instanceof AllEntriesGroup); + return (getNode().getPreviousSibling().isPresent()) && !(getNode().getGroup() instanceof AllEntriesGroup); } public boolean canMoveDown() { - return (getNode().getNextSibling().isPresent()) - && !(getNode().getGroup() instanceof AllEntriesGroup); + return (getNode().getNextSibling().isPresent()) && !(getNode().getGroup() instanceof AllEntriesGroup); } public boolean canMoveLeft() { @@ -124,8 +118,7 @@ public boolean canMoveLeft() { } public boolean canMoveRight() { - return (getNode().getPreviousSibling().isPresent()) - && !(getNode().getGroup() instanceof AllEntriesGroup); + return (getNode().getPreviousSibling().isPresent()) && !(getNode().getGroup() instanceof AllEntriesGroup); } public void changeEntriesTo(List entries, UndoManager undoManager) { @@ -143,7 +136,8 @@ public void changeEntriesTo(List entries, UndoManager undoManager) { // Sort according to current state of the entries if (group.contains(entry)) { toRemove.add(entry); - } else { + } + else { toAdd.add(entry); } } @@ -165,7 +159,8 @@ public void changeEntriesTo(List entries, UndoManager undoManager) { undoRemove.addEdit(UndoableChangeEntriesOfGroup.getUndoableEdit(this, changesAdd)); } undoManager.addEdit(undoRemove); - } else if (!changesAdd.isEmpty()) { + } + else if (!changesAdd.isEmpty()) { undoManager.addEdit(UndoableChangeEntriesOfGroup.getUndoableEdit(this, changesAdd)); } } @@ -182,9 +177,7 @@ public void addNewGroup(AbstractGroup newGroup, CountingUndoManager undoManager) GroupTreeNode newNode = GroupTreeNode.fromGroup(newGroup); this.getNode().addChild(newNode); - UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup( - this, - new GroupTreeNodeViewModel(newNode), + UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup(this, new GroupTreeNodeViewModel(newNode), UndoableAddOrRemoveGroup.ADD_NODE); undoManager.addEdit(undo); } @@ -199,4 +192,5 @@ public List addEntriesToGroup(List entries) { public void subscribeToDescendantChanged(Consumer subscriber) { getNode().subscribeToDescendantChanged(node -> subscriber.accept(new GroupTreeNodeViewModel(node))); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/GroupTreeView.java b/jabgui/src/main/java/org/jabref/gui/groups/GroupTreeView.java index a22b7445ab4..9ad62eb4f59 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/GroupTreeView.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/GroupTreeView.java @@ -83,50 +83,74 @@ public class GroupTreeView extends BorderPane { private static final Logger LOGGER = LoggerFactory.getLogger(GroupTreeView.class); + private static final PseudoClass PSEUDOCLASS_ANYSELECTED = PseudoClass.getPseudoClass("any-selected"); + private static final PseudoClass PSEUDOCLASS_ALLSELECTED = PseudoClass.getPseudoClass("all-selected"); + private static final PseudoClass PSEUDOCLASS_ROOTELEMENT = PseudoClass.getPseudoClass("root"); - private static final PseudoClass PSEUDOCLASS_SUBELEMENT = PseudoClass.getPseudoClass("sub"); // > 1 deep + + private static final PseudoClass PSEUDOCLASS_SUBELEMENT = PseudoClass.getPseudoClass("sub"); // > + // 1 + // deep private final StateManager stateManager; + private final DialogService dialogService; + private final AiService aiService; + private final TaskExecutor taskExecutor; + private final AdaptVisibleTabs adaptVisibleTabs; + private final GuiPreferences preferences; + private final UndoManager undoManager; + private final FileUpdateMonitor fileUpdateMonitor; - private final KeyBindingRepository keyBindingRepository; + private final KeyBindingRepository keyBindingRepository; private TreeTableView groupTree; + private TreeTableColumn mainColumn; + private TreeTableColumn numberColumn; + private TreeTableColumn expansionNodeColumn; + private CustomTextField searchField; + private GroupTreeViewModel viewModel; + private CustomLocalDragboard localDragboard; + private DragExpansionHandler dragExpansionHandler; + private Timer scrollTimer; + private ImportHandler importHandler; + private BibDatabaseContext database; + private double scrollVelocity = 0; + private double scrollableAreaHeight; + private double upperBorder; + private double lowerBorder; + private double baseFactor; /** - * Note: This panel is deliberately not created in fxml, since parsing equivalent fxml takes about 500 msecs + * Note: This panel is deliberately not created in fxml, since parsing equivalent fxml + * takes about 500 msecs */ - public GroupTreeView(TaskExecutor taskExecutor, - StateManager stateManager, - AdaptVisibleTabs adaptVisibleTabs, - GuiPreferences preferences, - DialogService dialogService, - AiService aiService, - UndoManager undoManager, - FileUpdateMonitor fileUpdateMonitor) { + public GroupTreeView(TaskExecutor taskExecutor, StateManager stateManager, AdaptVisibleTabs adaptVisibleTabs, + GuiPreferences preferences, DialogService dialogService, AiService aiService, UndoManager undoManager, + FileUpdateMonitor fileUpdateMonitor) { this.taskExecutor = taskExecutor; this.stateManager = stateManager; this.adaptVisibleTabs = adaptVisibleTabs; @@ -192,25 +216,23 @@ private void createNodes() { private void initialize() { this.localDragboard = stateManager.getLocalDragboard(); - viewModel = new GroupTreeViewModel(stateManager, dialogService, aiService, preferences, adaptVisibleTabs, taskExecutor, localDragboard); + viewModel = new GroupTreeViewModel(stateManager, dialogService, aiService, preferences, adaptVisibleTabs, + taskExecutor, localDragboard); // Set-up groups tree groupTree.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); dragExpansionHandler = new DragExpansionHandler(); // Set-up bindings - Platform.runLater(() -> - BindingsHelper.bindContentBidirectional( - groupTree.getSelectionModel().getSelectedItems(), - viewModel.selectedGroupsProperty(), - newSelectedGroups -> { - groupTree.getSelectionModel().clearSelection(); - newSelectedGroups.forEach(this::selectNode); - }, - this::updateSelection - )); - - // We try to prevent publishing changes in the search field directly to the search task that takes some time + Platform + .runLater(() -> BindingsHelper.bindContentBidirectional(groupTree.getSelectionModel().getSelectedItems(), + viewModel.selectedGroupsProperty(), newSelectedGroups -> { + groupTree.getSelectionModel().clearSelection(); + newSelectedGroups.forEach(this::selectNode); + }, this::updateSelection)); + + // We try to prevent publishing changes in the search field directly to the search + // task that takes some time // for larger group structures. final Timer searchTask = FxTimer.create(Duration.ofMillis(400), () -> { LOGGER.debug("Run group search {}", searchField.getText()); @@ -218,68 +240,69 @@ private void initialize() { }); searchField.textProperty().addListener((observable, oldValue, newValue) -> searchTask.restart()); - groupTree.rootProperty().bind( - EasyBind.map(viewModel.rootGroupProperty(), - group -> { - if (group == null) { - return null; - } else { - return new RecursiveTreeItem<>( - group, - GroupNodeViewModel::getChildren, - GroupNodeViewModel::expandedProperty, - viewModel.filterPredicateProperty()); - } - })); + groupTree.rootProperty().bind(EasyBind.map(viewModel.rootGroupProperty(), group -> { + if (group == null) { + return null; + } + else { + return new RecursiveTreeItem<>(group, GroupNodeViewModel::getChildren, + GroupNodeViewModel::expandedProperty, viewModel.filterPredicateProperty()); + } + })); // Icon and group name - new ViewModelTreeTableCellFactory() - .withText(GroupNodeViewModel::getDisplayName) - .withIcon(GroupNodeViewModel::getIcon) - .withTooltip(GroupNodeViewModel::getDescription) - .install(mainColumn); + new ViewModelTreeTableCellFactory().withText(GroupNodeViewModel::getDisplayName) + .withIcon(GroupNodeViewModel::getIcon) + .withTooltip(GroupNodeViewModel::getDescription) + .install(mainColumn); // Number of hits (only if user wants to see them) - new ViewModelTreeTableCellFactory() - .withGraphic(this::createNumberCell) - .install(numberColumn); + new ViewModelTreeTableCellFactory().withGraphic(this::createNumberCell) + .install(numberColumn); // Arrow indicating expanded status - new ViewModelTreeTableCellFactory() - .withGraphic(this::getArrowCell) - .withOnMouseClickedEvent(group -> event -> { - group.toggleExpansion(); + new ViewModelTreeTableCellFactory().withGraphic(this::getArrowCell) + .withOnMouseClickedEvent(group -> event -> { + group.toggleExpansion(); + event.consume(); + }) + .install(expansionNodeColumn); + + new ViewModelTreeTableRowFactory().withContextMenu(this::createContextMenuForGroup) + .withEventFilter(MouseEvent.MOUSE_PRESSED, (row, event) -> { + if (((MouseEvent) event).getButton() == MouseButton.SECONDARY + && !stateManager.getSelectedEntries().isEmpty()) { + // Prevent right-click to select group whe we have selected entries event.consume(); - }) - .install(expansionNodeColumn); - - new ViewModelTreeTableRowFactory() - .withContextMenu(this::createContextMenuForGroup) - .withEventFilter(MouseEvent.MOUSE_PRESSED, (row, event) -> { - if (((MouseEvent) event).getButton() == MouseButton.SECONDARY && !stateManager.getSelectedEntries().isEmpty()) { - // Prevent right-click to select group whe we have selected entries + } + else if (event.getTarget() instanceof StackPane pane) { + if (pane.getStyleClass().contains("arrow") + || pane.getStyleClass().contains("tree-disclosure-node")) { event.consume(); - } else if (event.getTarget() instanceof StackPane pane) { - if (pane.getStyleClass().contains("arrow") || pane.getStyleClass().contains("tree-disclosure-node")) { - event.consume(); - } } - }) - .withCustomInitializer(row -> { - // Remove disclosure node since we display custom version in separate column - // Simply setting to null is not enough since it would be replaced by the default node on every change - row.setDisclosureNode(null); - row.disclosureNodeProperty().addListener((observable, oldValue, newValue) -> row.setDisclosureNode(null)); - }) - .setOnDragDetected(this::handleOnDragDetected) - .setOnDragDropped(this::handleOnDragDropped) - .setOnDragExited(this::handleOnDragExited) - .setOnDragOver(this::handleOnDragOver) - .withPseudoClass(PSEUDOCLASS_ROOTELEMENT, row -> Bindings.createBooleanBinding( - () -> (row != null) && (groupTree.getRoot() != null) && (row.getItem() == groupTree.getRoot().getValue()), row.treeItemProperty())) - .withPseudoClass(PSEUDOCLASS_SUBELEMENT, row -> Bindings.createBooleanBinding( - () -> (row != null) && (groupTree.getTreeItemLevel(row.getTreeItem()) > 1), row.treeItemProperty())) - .install(groupTree); + } + }) + .withCustomInitializer(row -> { + // Remove disclosure node since we display custom version in separate + // column + // Simply setting to null is not enough since it would be replaced by the + // default node on every change + row.setDisclosureNode(null); + row.disclosureNodeProperty() + .addListener((observable, oldValue, newValue) -> row.setDisclosureNode(null)); + }) + .setOnDragDetected(this::handleOnDragDetected) + .setOnDragDropped(this::handleOnDragDropped) + .setOnDragExited(this::handleOnDragExited) + .setOnDragOver(this::handleOnDragOver) + .withPseudoClass(PSEUDOCLASS_ROOTELEMENT, + row -> Bindings.createBooleanBinding(() -> (row != null) && (groupTree.getRoot() != null) + && (row.getItem() == groupTree.getRoot().getValue()), row.treeItemProperty())) + .withPseudoClass(PSEUDOCLASS_SUBELEMENT, + row -> Bindings.createBooleanBinding( + () -> (row != null) && (groupTree.getTreeItemLevel(row.getTreeItem()) > 1), + row.treeItemProperty())) + .install(groupTree); setupDragScrolling(); @@ -308,20 +331,19 @@ private StackPane createNumberCell(GroupNodeViewModel group) { group.allSelectedEntriesMatchedProperty()); } Text text = new Text(); - EasyBind.subscribe(preferences.getGroupsPreferences().displayGroupCountProperty(), - shouldDisplayGroupCount -> { - if (text.textProperty().isBound()) { - text.textProperty().unbind(); - text.setText(""); - } + EasyBind.subscribe(preferences.getGroupsPreferences().displayGroupCountProperty(), shouldDisplayGroupCount -> { + if (text.textProperty().isBound()) { + text.textProperty().unbind(); + text.setText(""); + } - if (shouldDisplayGroupCount) { - text.textProperty().bind(group.getHits().map(Number::intValue).map(this::getFormattedNumber)); - Tooltip tooltip = new Tooltip(); - tooltip.textProperty().bind(group.getHits().asString()); - Tooltip.install(text, tooltip); - } - }); + if (shouldDisplayGroupCount) { + text.textProperty().bind(group.getHits().map(Number::intValue).map(this::getFormattedNumber)); + Tooltip tooltip = new Tooltip(); + tooltip.textProperty().bind(group.getHits().asString()); + Tooltip.install(text, tooltip); + } + }); text.getStyleClass().setAll("text"); text.styleProperty().bind(Bindings.createStringBinding(() -> { @@ -330,11 +352,14 @@ private StackPane createNumberCell(GroupNodeViewModel group) { // For each breaking point, the font size is reduced 0.20 em to fix issue 8797 if (font_size > 26.0) { reducedFontSize = 0.25; - } else if (font_size > 22.0) { + } + else if (font_size > 22.0) { reducedFontSize = 0.35; - } else if (font_size > 18.0) { + } + else if (font_size > 18.0) { reducedFontSize = 0.55; - } else { + } + else { reducedFontSize = 0.75; } return "-fx-font-size: %fem;".formatted(reducedFontSize); @@ -345,13 +370,17 @@ private StackPane createNumberCell(GroupNodeViewModel group) { return node; } - private void handleOnDragExited(TreeTableRow row, GroupNodeViewModel fieldViewModel, DragEvent dragEvent) { + private void handleOnDragExited(TreeTableRow row, GroupNodeViewModel fieldViewModel, + DragEvent dragEvent) { ControlHelper.removeDroppingPseudoClasses(row); } - private void handleOnDragDetected(TreeTableRow row, GroupNodeViewModel groupViewModel, MouseEvent event) { + private void handleOnDragDetected(TreeTableRow row, GroupNodeViewModel groupViewModel, + MouseEvent event) { List groupsToMove = new ArrayList<>(); - for (TreeItem selectedItem : row.getTreeTableView().getSelectionModel().getSelectedItems()) { + for (TreeItem selectedItem : row.getTreeTableView() + .getSelectionModel() + .getSelectedItems()) { if ((selectedItem != null) && (selectedItem.getValue() != null)) { groupsToMove.add(selectedItem.getValue().getPath()); } @@ -371,7 +400,8 @@ private void handleOnDragDetected(TreeTableRow row, GroupNod event.consume(); } - private void handleOnDragDropped(TreeTableRow row, GroupNodeViewModel originalItem, DragEvent event) { + private void handleOnDragDropped(TreeTableRow row, GroupNodeViewModel originalItem, + DragEvent event) { Dragboard dragboard = event.getDragboard(); boolean success = false; @@ -379,9 +409,7 @@ private void handleOnDragDropped(TreeTableRow row, GroupNode List pathToSources = (List) dragboard.getContent(DragAndDropDataFormats.GROUP); List changedGroups = new LinkedList<>(); for (String pathToSource : pathToSources) { - Optional source = viewModel - .rootGroupProperty().get() - .getChildByPath(pathToSource); + Optional source = viewModel.rootGroupProperty().get().getChildByPath(pathToSource); if (source.isPresent() && source.get().canBeDragged()) { source.get().draggedOn(row.getItem(), ControlHelper.getDroppingMouseLocation(row, event)); changedGroups.add(source.get()); @@ -403,25 +431,21 @@ private void handleOnDragDropped(TreeTableRow row, GroupNode if (dragboard.hasFiles()) { this.database = stateManager.getActiveDatabase().orElse(null); - this.importHandler = new ImportHandler( - database, - preferences, - fileUpdateMonitor, - undoManager, - stateManager, - dialogService, - taskExecutor); + this.importHandler = new ImportHandler(database, preferences, fileUpdateMonitor, undoManager, stateManager, + dialogService, taskExecutor); List files = dragboard.getFiles().stream().map(File::toPath).collect(Collectors.toList()); stateManager.setSelectedGroups(database, List.of(row.getItem().getGroupNode())); - importHandler.importFilesInBackground(files, database, preferences.getFilePreferences(), event.getTransferMode()) - .executeWith(taskExecutor); + importHandler + .importFilesInBackground(files, database, preferences.getFilePreferences(), event.getTransferMode()) + .executeWith(taskExecutor); success = true; } event.setDropCompleted(success); event.consume(); } - private void handleOnDragOver(TreeTableRow row, GroupNodeViewModel originalItem, DragEvent event) { + private void handleOnDragOver(TreeTableRow row, GroupNodeViewModel originalItem, + DragEvent event) { Dragboard dragboard = event.getDragboard(); if ((event.getGestureSource() != row) && (row.getItem() != null) && row.getItem().acceptableDrop(dragboard)) { event.acceptTransferModes(TransferMode.MOVE, TransferMode.LINK); @@ -431,7 +455,8 @@ private void handleOnDragOver(TreeTableRow row, GroupNodeVie if (localDragboard.hasBibEntries() || dragboard.hasFiles()) { ControlHelper.setDroppingPseudoClasses(row); - } else { + } + else { ControlHelper.setDroppingPseudoClasses(row, event); } } @@ -440,8 +465,12 @@ private void handleOnDragOver(TreeTableRow row, GroupNodeVie private void updateSelection(List> newSelectedGroups) { if ((newSelectedGroups == null) || newSelectedGroups.isEmpty()) { viewModel.selectedGroupsProperty().clear(); - } else { - List list = newSelectedGroups.stream().filter(model -> (model != null) && (model.getValue() != null)).map(TreeItem::getValue).collect(Collectors.toList()); + } + else { + List list = newSelectedGroups.stream() + .filter(model -> (model != null) && (model.getValue() != null)) + .map(TreeItem::getValue) + .collect(Collectors.toList()); viewModel.selectedGroupsProperty().setAll(list); } } @@ -451,17 +480,16 @@ private void selectNode(GroupNodeViewModel value) { } private void selectNode(GroupNodeViewModel value, boolean expandParents) { - getTreeItemByValue(value) - .ifPresent(treeItem -> { - if (expandParents) { - TreeItem parent = treeItem.getParent(); - while (parent != null) { - parent.setExpanded(true); - parent = parent.getParent(); - } - } - groupTree.getSelectionModel().select(treeItem); - }); + getTreeItemByValue(value).ifPresent(treeItem -> { + if (expandParents) { + TreeItem parent = treeItem.getParent(); + while (parent != null) { + parent.setExpanded(true); + parent = parent.getParent(); + } + } + groupTree.getSelectionModel().select(treeItem); + }); } private Optional> getTreeItemByValue(GroupNodeViewModel value) { @@ -469,7 +497,7 @@ private Optional> getTreeItemByValue(GroupNodeViewM } private Optional> getTreeItemByValue(TreeItem root, - GroupNodeViewModel value) { + GroupNodeViewModel value) { if (root == null) { return Optional.empty(); } @@ -490,11 +518,12 @@ private Optional> getTreeItemByValue(TreeItem - getVerticalScrollbar().ifPresent(scrollBar -> { + scrollTimer = FxTimer.createPeriodic(Duration.ofMillis(100), + () -> getVerticalScrollbar().ifPresent(scrollBar -> { double newValue = scrollBar.getValue() + scrollVelocity; newValue = Math.min(newValue, 1d); newValue = Math.max(newValue, 0d); @@ -520,12 +549,15 @@ private void setupDragScrolling() { double distanceFromNonScrollableInsideArea; if (scrollingUp) { distanceFromNonScrollableInsideArea = scrollableAreaHeight - event.getY(); - } else { + } + else { distanceFromNonScrollableInsideArea = scrollableAreaHeight - (groupTree.getHeight() - event.getY()); } - // part "(1+x)" of formula "speed = 20px/s (1+x)" (proposed by https://github.com/JabRef/jabref/issues/9754#issuecomment-1766864908) - // / 10.0 is because of the 100 milliseconds above. (it is 20px per second, 10.0 * 100.0 ms = 1 second) + // part "(1+x)" of formula "speed = 20px/s (1+x)" (proposed by + // https://github.com/JabRef/jabref/issues/9754#issuecomment-1766864908) + // / 10.0 is because of the 100 milliseconds above. (it is 20px per second, + // 10.0 * 100.0 ms = 1 second) scrollVelocity = (baseFactor * (1.0 + distanceFromNonScrollableInsideArea)) / 10.0; if (scrollingUp) { scrollVelocity = -scrollVelocity; @@ -550,7 +582,8 @@ private void initScrolling() { double heightOfOneNode = groupTree.getChildrenUnmodifiable().getFirst().getLayoutBounds().getHeight(); // heightOfOneNode is the size of text. We need including surroundings. // We found no way to get this. We can only do a heuristics here. - // 2.0 is backed by measurement using the screen ruler utility (https://learn.microsoft.com/en-us/windows/powertoys/screen-ruler) + // 2.0 is backed by measurement using the screen ruler utility + // (https://learn.microsoft.com/en-us/windows/powertoys/screen-ruler) heightOfOneNode = heightOfOneNode * 2.0; // At most scroll area is three entries large @@ -558,16 +591,17 @@ private void initScrolling() { upperBorder = scrollableAreaHeight; lowerBorder = groupTree.getHeight() - scrollableAreaHeight; - // 20 is derived from "speed = 20px/s (1+x)" (proposed by https://github.com/JabRef/jabref/issues/9754#issuecomment-1766864908) - // (1.0 / groupTree.getHeight()) is the factor to convert from px to fraction of total height + // 20 is derived from "speed = 20px/s (1+x)" (proposed by + // https://github.com/JabRef/jabref/issues/9754#issuecomment-1766864908) + // (1.0 / groupTree.getHeight()) is the factor to convert from px to fraction of + // total height double totalHeight = heightOfOneNode * numberOfShownGroups; baseFactor = 20.0 * (1.0 / totalHeight); } private Optional getVerticalScrollbar() { for (Node node : groupTree.lookupAll(".scroll-bar")) { - if (node instanceof ScrollBar scrollbar - && scrollbar.getOrientation() == Orientation.VERTICAL) { + if (node instanceof ScrollBar scrollbar && scrollbar.getOrientation() == Orientation.VERTICAL) { return Optional.of(scrollbar); } } @@ -588,41 +622,54 @@ private ContextMenu createContextMenuForGroup(GroupNodeViewModel group) { factory.createMenuItem(StandardActions.GROUP_REMOVE_KEEP_SUBGROUPS, new GroupTreeView.ContextAction(StandardActions.GROUP_REMOVE_KEEP_SUBGROUPS, group)), factory.createMenuItem(StandardActions.GROUP_REMOVE_WITH_SUBGROUPS, - new GroupTreeView.ContextAction(StandardActions.GROUP_REMOVE_WITH_SUBGROUPS, group)) - ); - } else { - removeGroup = factory.createMenuItem(StandardActions.GROUP_REMOVE, new GroupTreeView.ContextAction(StandardActions.GROUP_REMOVE, group)); + new GroupTreeView.ContextAction(StandardActions.GROUP_REMOVE_WITH_SUBGROUPS, group))); + } + else { + removeGroup = factory.createMenuItem(StandardActions.GROUP_REMOVE, + new GroupTreeView.ContextAction(StandardActions.GROUP_REMOVE, group)); } if (preferences.getAiPreferences().getEnableAi()) { - contextMenu.getItems().add(factory.createMenuItem(StandardActions.GROUP_CHAT, new ContextAction(StandardActions.GROUP_CHAT, group))); + contextMenu.getItems() + .add(factory.createMenuItem(StandardActions.GROUP_CHAT, + new ContextAction(StandardActions.GROUP_CHAT, group))); } - contextMenu.getItems().addAll( - factory.createMenuItem(StandardActions.GROUP_EDIT, new ContextAction(StandardActions.GROUP_EDIT, group)), - factory.createMenuItem(StandardActions.GROUP_GENERATE_EMBEDDINGS, new ContextAction(StandardActions.GROUP_GENERATE_EMBEDDINGS, group)), - factory.createMenuItem(StandardActions.GROUP_GENERATE_SUMMARIES, new ContextAction(StandardActions.GROUP_GENERATE_SUMMARIES, group)), - removeGroup, - new SeparatorMenuItem() - ); + contextMenu.getItems() + .addAll(factory.createMenuItem(StandardActions.GROUP_EDIT, + new ContextAction(StandardActions.GROUP_EDIT, group)), + factory.createMenuItem(StandardActions.GROUP_GENERATE_EMBEDDINGS, + new ContextAction(StandardActions.GROUP_GENERATE_EMBEDDINGS, group)), + factory.createMenuItem(StandardActions.GROUP_GENERATE_SUMMARIES, + new ContextAction(StandardActions.GROUP_GENERATE_SUMMARIES, group)), + removeGroup, new SeparatorMenuItem()); if (group.isAllEntriesGroup()) { - contextMenu.getItems().add(factory.createMenuItem(StandardActions.GROUP_SUGGESTED_GROUPS_ADD, - new ContextAction(StandardActions.GROUP_SUGGESTED_GROUPS_ADD, group))); + contextMenu.getItems() + .add(factory.createMenuItem(StandardActions.GROUP_SUGGESTED_GROUPS_ADD, + new ContextAction(StandardActions.GROUP_SUGGESTED_GROUPS_ADD, group))); } - contextMenu.getItems().addAll( - factory.createMenuItem(StandardActions.GROUP_SUBGROUP_ADD, new ContextAction(StandardActions.GROUP_SUBGROUP_ADD, group)), - factory.createMenuItem(StandardActions.GROUP_SUBGROUP_RENAME, new ContextAction(StandardActions.GROUP_SUBGROUP_RENAME, group)), - factory.createMenuItem(StandardActions.GROUP_SUBGROUP_REMOVE, new ContextAction(StandardActions.GROUP_SUBGROUP_REMOVE, group)), - factory.createMenuItem(StandardActions.GROUP_SUBGROUP_SORT, new ContextAction(StandardActions.GROUP_SUBGROUP_SORT, group)), - factory.createMenuItem(StandardActions.GROUP_SUBGROUP_SORT_REVERSE, new ContextAction(StandardActions.GROUP_SUBGROUP_SORT_REVERSE, group)), - factory.createMenuItem(StandardActions.GROUP_SUBGROUP_SORT_ENTRIES, new ContextAction(StandardActions.GROUP_SUBGROUP_SORT_ENTRIES, group)), - factory.createMenuItem(StandardActions.GROUP_SUBGROUP_SORT_ENTRIES_REVERSE, new ContextAction(StandardActions.GROUP_SUBGROUP_SORT_ENTRIES_REVERSE, group)), - new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.GROUP_ENTRIES_ADD, new ContextAction(StandardActions.GROUP_ENTRIES_ADD, group)), - factory.createMenuItem(StandardActions.GROUP_ENTRIES_REMOVE, new ContextAction(StandardActions.GROUP_ENTRIES_REMOVE, group)) - ); + contextMenu.getItems() + .addAll(factory.createMenuItem(StandardActions.GROUP_SUBGROUP_ADD, + new ContextAction(StandardActions.GROUP_SUBGROUP_ADD, group)), + factory.createMenuItem(StandardActions.GROUP_SUBGROUP_RENAME, + new ContextAction(StandardActions.GROUP_SUBGROUP_RENAME, group)), + factory.createMenuItem(StandardActions.GROUP_SUBGROUP_REMOVE, + new ContextAction(StandardActions.GROUP_SUBGROUP_REMOVE, group)), + factory.createMenuItem(StandardActions.GROUP_SUBGROUP_SORT, + new ContextAction(StandardActions.GROUP_SUBGROUP_SORT, group)), + factory.createMenuItem(StandardActions.GROUP_SUBGROUP_SORT_REVERSE, + new ContextAction(StandardActions.GROUP_SUBGROUP_SORT_REVERSE, group)), + factory.createMenuItem(StandardActions.GROUP_SUBGROUP_SORT_ENTRIES, + new ContextAction(StandardActions.GROUP_SUBGROUP_SORT_ENTRIES, group)), + factory.createMenuItem(StandardActions.GROUP_SUBGROUP_SORT_ENTRIES_REVERSE, + new ContextAction(StandardActions.GROUP_SUBGROUP_SORT_ENTRIES_REVERSE, group)), + new SeparatorMenuItem(), + factory.createMenuItem(StandardActions.GROUP_ENTRIES_ADD, + new ContextAction(StandardActions.GROUP_ENTRIES_ADD, group)), + factory.createMenuItem(StandardActions.GROUP_ENTRIES_REMOVE, + new ContextAction(StandardActions.GROUP_ENTRIES_REMOVE, group))); contextMenu.getItems().forEach(item -> item.setGraphic(null)); contextMenu.getStyleClass().add("context-menu"); @@ -637,7 +684,8 @@ private String getFormattedNumber(int hits) { if (hits >= 1000000) { double millions = hits / 1000000.0; return new DecimalFormat("#,##0.#").format(millions) + "m"; - } else if (hits >= 1000) { + } + else if (hits >= 1000) { double thousands = hits / 1000.0; return new DecimalFormat("#,##0.#").format(thousands) + "k"; } @@ -645,13 +693,15 @@ private String getFormattedNumber(int hits) { } // ToDo: reflective access, should be removed - // Workaround taken from https://github.com/controlsfx/controlsfx/issues/330 + // Workaround taken from https://github.com/controlsfx/controlsfx/issues/330 private void setupClearButtonField(CustomTextField customTextField) { try { - Method m = TextFields.class.getDeclaredMethod("setupClearButtonField", TextField.class, ObjectProperty.class); + Method m = TextFields.class.getDeclaredMethod("setupClearButtonField", TextField.class, + ObjectProperty.class); m.setAccessible(true); m.invoke(null, customTextField, customTextField.rightProperty()); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { + } + catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { LOGGER.error("Failed to decorate text field with clear button", ex); } } @@ -660,15 +710,16 @@ private void setupClearButtonField(CustomTextField customTextField) { * Creates an observable boolean value that is true if no database is open */ private ObservableBooleanValue groupsDisabledProperty() { - return Bindings.createBooleanBinding( - () -> stateManager.getOpenDatabases().isEmpty(), - stateManager.getOpenDatabases() - ); + return Bindings.createBooleanBinding(() -> stateManager.getOpenDatabases().isEmpty(), + stateManager.getOpenDatabases()); } private static class DragExpansionHandler { + private static final long DRAG_TIME_BEFORE_EXPANDING_MS = 1000; + private TreeItem draggedItem; + private long dragStarted; public void expandGroup(TreeItem treeItem) { @@ -683,84 +734,63 @@ public void expandGroup(TreeItem treeItem) { // expand or collapse the tree item and reset the time this.dragStarted = System.currentTimeMillis(); this.draggedItem.setExpanded(!this.draggedItem.isExpanded()); - } else { + } + else { // leave the expansion state of the tree item as it is this.draggedItem.setExpanded(this.draggedItem.isExpanded()); } } + } private class ContextAction extends SimpleCommand { private final StandardActions command; + private final GroupNodeViewModel group; public ContextAction(StandardActions command, GroupNodeViewModel group) { this.command = command; this.group = group; - this.executable.bind(BindingsHelper.constantOf( - switch (command) { - case GROUP_EDIT, GROUP_SUBGROUP_RENAME -> - group.isEditable(); - case GROUP_REMOVE, GROUP_REMOVE_WITH_SUBGROUPS, GROUP_REMOVE_KEEP_SUBGROUPS -> - group.isEditable() && group.canRemove(); - case GROUP_SUGGESTED_GROUPS_ADD -> - !group.hasAllSuggestedGroups(); - case GROUP_SUBGROUP_ADD -> - group.isEditable() && group.canAddGroupsIn() - || group.isRoot(); - case GROUP_SUBGROUP_REMOVE -> - group.isEditable() && group.hasSubgroups() && group.canRemove() - || group.isRoot(); - case GROUP_SUBGROUP_SORT -> - group.isEditable() && group.hasSubgroups() && group.canAddEntriesIn() - || group.isRoot(); - case GROUP_ENTRIES_ADD, GROUP_ENTRIES_REMOVE -> - group.canAddEntriesIn(); - default -> - true; - })); + this.executable.bind(BindingsHelper.constantOf(switch (command) { + case GROUP_EDIT, GROUP_SUBGROUP_RENAME -> group.isEditable(); + case GROUP_REMOVE, GROUP_REMOVE_WITH_SUBGROUPS, GROUP_REMOVE_KEEP_SUBGROUPS -> + group.isEditable() && group.canRemove(); + case GROUP_SUGGESTED_GROUPS_ADD -> !group.hasAllSuggestedGroups(); + case GROUP_SUBGROUP_ADD -> group.isEditable() && group.canAddGroupsIn() || group.isRoot(); + case GROUP_SUBGROUP_REMOVE -> + group.isEditable() && group.hasSubgroups() && group.canRemove() || group.isRoot(); + case GROUP_SUBGROUP_SORT -> + group.isEditable() && group.hasSubgroups() && group.canAddEntriesIn() || group.isRoot(); + case GROUP_ENTRIES_ADD, GROUP_ENTRIES_REMOVE -> group.canAddEntriesIn(); + default -> true; + })); } @Override public void execute() { switch (command) { - case GROUP_REMOVE -> - viewModel.removeGroupNoSubgroups(group); - case GROUP_REMOVE_KEEP_SUBGROUPS -> - viewModel.removeGroupKeepSubgroups(group); - case GROUP_REMOVE_WITH_SUBGROUPS -> - viewModel.removeGroupAndSubgroups(group); - case GROUP_EDIT, GROUP_SUBGROUP_RENAME -> - viewModel.editGroup(group); - case GROUP_GENERATE_EMBEDDINGS -> - viewModel.generateEmbeddings(group); - case GROUP_GENERATE_SUMMARIES -> - viewModel.generateSummaries(group); - case GROUP_CHAT -> - viewModel.chatWithGroup(group); - case GROUP_SUGGESTED_GROUPS_ADD -> - viewModel.addSuggestedGroups(group); - case GROUP_SUBGROUP_ADD -> - viewModel.addNewSubgroup(group, GroupDialogHeader.SUBGROUP); - case GROUP_SUBGROUP_REMOVE -> - viewModel.removeSubgroups(group); - case GROUP_SUBGROUP_SORT -> - viewModel.sortAlphabeticallyRecursive(group.getGroupNode()); - case GROUP_SUBGROUP_SORT_REVERSE -> - viewModel.sortReverseAlphabeticallyRecursive(group.getGroupNode()); - case GROUP_SUBGROUP_SORT_ENTRIES -> - viewModel.sortEntriesRecursive(group.getGroupNode()); - case GROUP_SUBGROUP_SORT_ENTRIES_REVERSE -> - viewModel.sortReverseEntriesRecursive(group.getGroupNode()); - case GROUP_ENTRIES_ADD -> - viewModel.addSelectedEntries(group); - case GROUP_ENTRIES_REMOVE -> - viewModel.removeSelectedEntries(group); + case GROUP_REMOVE -> viewModel.removeGroupNoSubgroups(group); + case GROUP_REMOVE_KEEP_SUBGROUPS -> viewModel.removeGroupKeepSubgroups(group); + case GROUP_REMOVE_WITH_SUBGROUPS -> viewModel.removeGroupAndSubgroups(group); + case GROUP_EDIT, GROUP_SUBGROUP_RENAME -> viewModel.editGroup(group); + case GROUP_GENERATE_EMBEDDINGS -> viewModel.generateEmbeddings(group); + case GROUP_GENERATE_SUMMARIES -> viewModel.generateSummaries(group); + case GROUP_CHAT -> viewModel.chatWithGroup(group); + case GROUP_SUGGESTED_GROUPS_ADD -> viewModel.addSuggestedGroups(group); + case GROUP_SUBGROUP_ADD -> viewModel.addNewSubgroup(group, GroupDialogHeader.SUBGROUP); + case GROUP_SUBGROUP_REMOVE -> viewModel.removeSubgroups(group); + case GROUP_SUBGROUP_SORT -> viewModel.sortAlphabeticallyRecursive(group.getGroupNode()); + case GROUP_SUBGROUP_SORT_REVERSE -> viewModel.sortReverseAlphabeticallyRecursive(group.getGroupNode()); + case GROUP_SUBGROUP_SORT_ENTRIES -> viewModel.sortEntriesRecursive(group.getGroupNode()); + case GROUP_SUBGROUP_SORT_ENTRIES_REVERSE -> viewModel.sortReverseEntriesRecursive(group.getGroupNode()); + case GROUP_ENTRIES_ADD -> viewModel.addSelectedEntries(group); + case GROUP_ENTRIES_REMOVE -> viewModel.removeSelectedEntries(group); } groupTree.refresh(); } + } /** @@ -769,4 +799,5 @@ public void execute() { public void requestFocusGroupTree() { groupTree.requestFocus(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java b/jabgui/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java index 1977ea34c24..5880e907ccc 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java @@ -52,42 +52,51 @@ public class GroupTreeViewModel extends AbstractViewModel { private final ObjectProperty rootGroup = new SimpleObjectProperty<>(); - private final ListProperty selectedGroups = new SimpleListProperty<>(FXCollections.observableArrayList()); + + private final ListProperty selectedGroups = new SimpleListProperty<>( + FXCollections.observableArrayList()); + private final StateManager stateManager; + private final DialogService dialogService; + private final AiService aiService; + private final GuiPreferences preferences; + private final AdaptVisibleTabs adaptVisibleTabs; + private final TaskExecutor taskExecutor; + private final CustomLocalDragboard localDragboard; + private final ObjectProperty> filterPredicate = new SimpleObjectProperty<>(); + private final StringProperty filterText = new SimpleStringProperty(); - private final Comparator compAlphabetIgnoreCase = (GroupTreeNode v1, GroupTreeNode v2) -> v1 - .getName() - .compareToIgnoreCase(v2.getName()); - private final Comparator compAlphabetIgnoreCaseReverse = (GroupTreeNode v1, GroupTreeNode v2) -> v2 - .getName() - .compareToIgnoreCase(v1.getName()); + + private final Comparator compAlphabetIgnoreCase = (GroupTreeNode v1, + GroupTreeNode v2) -> v1.getName().compareToIgnoreCase(v2.getName()); + + private final Comparator compAlphabetIgnoreCaseReverse = (GroupTreeNode v1, + GroupTreeNode v2) -> v2.getName().compareToIgnoreCase(v1.getName()); + private final Comparator compEntries = (GroupTreeNode v1, GroupTreeNode v2) -> { int numChildren1 = v1.getEntriesInGroup(this.currentDatabase.get().getEntries()).size(); int numChildren2 = v2.getEntriesInGroup(this.currentDatabase.get().getEntries()).size(); return Integer.compare(numChildren2, numChildren1); }; + private final Comparator compEntriesReverse = (GroupTreeNode v1, GroupTreeNode v2) -> { int numChildren1 = v1.getEntriesInGroup(this.currentDatabase.get().getEntries()).size(); int numChildren2 = v2.getEntriesInGroup(this.currentDatabase.get().getEntries()).size(); return Integer.compare(numChildren1, numChildren2); }; + private Optional currentDatabase = Optional.empty(); - public GroupTreeViewModel(StateManager stateManager, - DialogService dialogService, - AiService aiService, - GuiPreferences preferences, - AdaptVisibleTabs adaptVisibleTabs, - TaskExecutor taskExecutor, - CustomLocalDragboard localDragboard - ) { + public GroupTreeViewModel(StateManager stateManager, DialogService dialogService, AiService aiService, + GuiPreferences preferences, AdaptVisibleTabs adaptVisibleTabs, TaskExecutor taskExecutor, + CustomLocalDragboard localDragboard) { this.stateManager = Objects.requireNonNull(stateManager); this.dialogService = Objects.requireNonNull(dialogService); this.aiService = Objects.requireNonNull(aiService); @@ -125,8 +134,8 @@ public StringProperty filterTextProperty() { } /** - * Gets invoked if the user selects a different group. - * We need to notify the {@link StateManager} about this change so that the main table gets updated. + * Gets invoked if the user selects a different group. We need to notify the + * {@link StateManager} about this change so that the main table gets updated. */ private void onSelectedGroupChanged(ObservableList newValue) { if (!currentDatabase.equals(stateManager.activeDatabaseProperty().getValue())) { @@ -137,8 +146,10 @@ private void onSelectedGroupChanged(ObservableList newValue) currentDatabase.ifPresent(database -> { if ((newValue == null) || newValue.isEmpty()) { stateManager.clearSelectedGroups(database); - } else { - stateManager.setSelectedGroups(database, newValue.stream().map(GroupNodeViewModel::getGroupNode).collect(Collectors.toList())); + } + else { + stateManager.setSelectedGroups(database, + newValue.stream().map(GroupNodeViewModel::getGroupNode).collect(Collectors.toList())); } }); } @@ -149,32 +160,37 @@ private void onSelectedGroupChanged(ObservableList newValue) public void addNewGroupToRoot() { if (currentDatabase.isPresent()) { addNewSubgroup(rootGroup.get(), GroupDialogHeader.GROUP); - } else { - dialogService.showWarningDialogAndWait(Localization.lang("Cannot create group"), Localization.lang("Cannot create group. Please create a library first.")); + } + else { + dialogService.showWarningDialogAndWait(Localization.lang("Cannot create group"), + Localization.lang("Cannot create group. Please create a library first.")); } } /** - * Gets invoked if the user changes the active database. - * We need to get the new group tree and update the view + * Gets invoked if the user changes the active database. We need to get the new group + * tree and update the view */ private void onActiveDatabaseChanged(Optional newDatabase) { if (newDatabase.isPresent()) { - GroupNodeViewModel newRoot = newDatabase - .map(BibDatabaseContext::getMetaData) - .flatMap(MetaData::getGroups) - .map(root -> new GroupNodeViewModel(newDatabase.get(), stateManager, taskExecutor, root, localDragboard, preferences)) - .orElse(GroupNodeViewModel.getAllEntriesGroup(newDatabase.get(), stateManager, taskExecutor, localDragboard, preferences)); + GroupNodeViewModel newRoot = newDatabase.map(BibDatabaseContext::getMetaData) + .flatMap(MetaData::getGroups) + .map(root -> new GroupNodeViewModel(newDatabase.get(), stateManager, taskExecutor, root, localDragboard, + preferences)) + .orElse(GroupNodeViewModel.getAllEntriesGroup(newDatabase.get(), stateManager, taskExecutor, + localDragboard, preferences)); rootGroup.setValue(newRoot); if (stateManager.getSelectedGroups(newDatabase.get()).isEmpty()) { stateManager.setSelectedGroups(newDatabase.get(), List.of(newRoot.getGroupNode())); } - selectedGroups.setAll( - stateManager.getSelectedGroups(newDatabase.get()).stream() - .map(selectedGroup -> new GroupNodeViewModel(newDatabase.get(), stateManager, taskExecutor, selectedGroup, localDragboard, preferences)) - .collect(Collectors.toList())); - } else { + selectedGroups.setAll(stateManager.getSelectedGroups(newDatabase.get()) + .stream() + .map(selectedGroup -> new GroupNodeViewModel(newDatabase.get(), stateManager, taskExecutor, + selectedGroup, localDragboard, preferences)) + .collect(Collectors.toList())); + } + else { rootGroup.setValue(null); } currentDatabase = newDatabase; @@ -189,37 +205,39 @@ private void addGroupImportEntries(GroupNodeViewModel parent) { String grpName = preferences.getLibraryPreferences().getAddImportedEntriesGroupName(); AbstractGroup importEntriesGroup = new SmartGroup(grpName, GroupHierarchyType.INDEPENDENT, ','); boolean isGrpExist = parent.getGroupNode() - .getChildren() - .stream() - .map(GroupTreeNode::getGroup) - .anyMatch(grp -> grp instanceof SmartGroup); + .getChildren() + .stream() + .map(GroupTreeNode::getGroup) + .anyMatch(grp -> grp instanceof SmartGroup); if (!isGrpExist) { currentDatabase.ifPresent(db -> { GroupTreeNode newSubgroup = parent.addSubgroup(importEntriesGroup); newSubgroup.moveTo(parent.getGroupNode(), 0); - selectedGroups.setAll(new GroupNodeViewModel(db, stateManager, taskExecutor, newSubgroup, localDragboard, preferences)); + selectedGroups.setAll(new GroupNodeViewModel(db, stateManager, taskExecutor, newSubgroup, + localDragboard, preferences)); writeGroupChangesToMetaData(); }); } } /** - * Opens "New Group Dialog" and adds the resulting group as subgroup to the specified group + * Opens "New Group Dialog" and adds the resulting group as subgroup to the specified + * group */ public void addNewSubgroup(GroupNodeViewModel parent, GroupDialogHeader groupDialogHeader) { currentDatabase.ifPresent(database -> { - Optional newGroup = dialogService.showCustomDialogAndWait(new GroupDialogView( - database, - parent.getGroupNode(), - null, - groupDialogHeader)); + Optional newGroup = dialogService + .showCustomDialogAndWait(new GroupDialogView(database, parent.getGroupNode(), null, groupDialogHeader)); newGroup.ifPresent(group -> { GroupTreeNode newSubgroup = parent.addSubgroup(group); - selectedGroups.setAll(new GroupNodeViewModel(database, stateManager, taskExecutor, newSubgroup, localDragboard, preferences)); + selectedGroups.setAll(new GroupNodeViewModel(database, stateManager, taskExecutor, newSubgroup, + localDragboard, preferences)); // TODO: Add undo - // UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup(parent, new GroupTreeNodeViewModel(newGroupNode), UndoableAddOrRemoveGroup.ADD_NODE); + // UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup(parent, + // new GroupTreeNodeViewModel(newGroupNode), + // UndoableAddOrRemoveGroup.ADD_NODE); // panel.getUndoManager().addEdit(undo); // TODO: Expand parent to make new group visible @@ -239,9 +257,8 @@ private boolean isGroupTypeEqual(AbstractGroup oldGroup, AbstractGroup newGroup) } /** - * Adds JabRef suggested groups under the "All Entries" parent node. - * Assumes the parent is already validated as "All Entries" by the caller. - * + * Adds JabRef suggested groups under the "All Entries" parent node. Assumes the + * parent is already validated as "All Entries" by the caller. * @param parent The "All Entries" parent node. */ public void addSuggestedGroups(GroupNodeViewModel parent) { @@ -263,60 +280,75 @@ public void addSuggestedGroups(GroupNodeViewModel parent) { newSuggestedSubgroups.add(subGroup); } - selectedGroups.setAll(newSuggestedSubgroups - .stream() - .map(newSubGroup -> new GroupNodeViewModel(database, stateManager, taskExecutor, newSubGroup, localDragboard, preferences)) - .toList()); + selectedGroups.setAll(newSuggestedSubgroups.stream() + .map(newSubGroup -> new GroupNodeViewModel(database, stateManager, taskExecutor, newSubGroup, + localDragboard, preferences)) + .toList()); writeGroupChangesToMetaData(); - dialogService.notify(Localization.lang("Created %0 suggested groups.", String.valueOf(newSuggestedSubgroups.size()))); + dialogService.notify( + Localization.lang("Created %0 suggested groups.", String.valueOf(newSuggestedSubgroups.size()))); }); } /** * Check if it is necessary to show a group modified, reassign entry dialog
    * Group name change is handled separately - * * @param oldGroup Original Group * @param newGroup Edited group - * @return true if just trivial modifications (e.g. color or description) or the relevant group properties are equal, false otherwise + * @return true if just trivial modifications (e.g. color or description) or the + * relevant group properties are equal, false otherwise */ boolean onlyMinorChanges(AbstractGroup oldGroup, AbstractGroup newGroup) { - // we need to use getclass here because we have different subclass inheritance e.g. ExplicitGroup is a subclass of WordKeyWordGroup + // we need to use getclass here because we have different subclass inheritance + // e.g. ExplicitGroup is a subclass of WordKeyWordGroup if (oldGroup.getClass() == WordKeywordGroup.class) { WordKeywordGroup oldWordKeywordGroup = (WordKeywordGroup) oldGroup; WordKeywordGroup newWordKeywordGroup = (WordKeywordGroup) newGroup; - return Objects.equals(oldWordKeywordGroup.getSearchField().getName(), newWordKeywordGroup.getSearchField().getName()) - && Objects.equals(oldWordKeywordGroup.getSearchExpression(), newWordKeywordGroup.getSearchExpression()) + return Objects.equals(oldWordKeywordGroup.getSearchField().getName(), + newWordKeywordGroup.getSearchField().getName()) + && Objects.equals(oldWordKeywordGroup.getSearchExpression(), + newWordKeywordGroup.getSearchExpression()) && Objects.equals(oldWordKeywordGroup.isCaseSensitive(), newWordKeywordGroup.isCaseSensitive()); - } else if (oldGroup.getClass() == RegexKeywordGroup.class) { + } + else if (oldGroup.getClass() == RegexKeywordGroup.class) { RegexKeywordGroup oldRegexKeywordGroup = (RegexKeywordGroup) oldGroup; RegexKeywordGroup newRegexKeywordGroup = (RegexKeywordGroup) newGroup; - return Objects.equals(oldRegexKeywordGroup.getSearchField().getName(), newRegexKeywordGroup.getSearchField().getName()) - && Objects.equals(oldRegexKeywordGroup.getSearchExpression(), newRegexKeywordGroup.getSearchExpression()) + return Objects.equals(oldRegexKeywordGroup.getSearchField().getName(), + newRegexKeywordGroup.getSearchField().getName()) + && Objects.equals(oldRegexKeywordGroup.getSearchExpression(), + newRegexKeywordGroup.getSearchExpression()) && Objects.equals(oldRegexKeywordGroup.isCaseSensitive(), newRegexKeywordGroup.isCaseSensitive()); - } else if (oldGroup.getClass() == SearchGroup.class) { + } + else if (oldGroup.getClass() == SearchGroup.class) { SearchGroup oldSearchGroup = (SearchGroup) oldGroup; SearchGroup newSearchGroup = (SearchGroup) newGroup; return Objects.equals(oldSearchGroup.getSearchExpression(), newSearchGroup.getSearchExpression()) && Objects.equals(oldSearchGroup.getSearchFlags(), newSearchGroup.getSearchFlags()); - } else if (oldGroup.getClass() == AutomaticKeywordGroup.class) { + } + else if (oldGroup.getClass() == AutomaticKeywordGroup.class) { AutomaticKeywordGroup oldAutomaticKeywordGroup = (AutomaticKeywordGroup) oldGroup; AutomaticKeywordGroup newAutomaticKeywordGroup = (AutomaticKeywordGroup) oldGroup; - return Objects.equals(oldAutomaticKeywordGroup.getKeywordDelimiter(), newAutomaticKeywordGroup.getKeywordDelimiter()) - && Objects.equals(oldAutomaticKeywordGroup.getKeywordHierarchicalDelimiter(), newAutomaticKeywordGroup.getKeywordHierarchicalDelimiter()) - && Objects.equals(oldAutomaticKeywordGroup.getField().getName(), newAutomaticKeywordGroup.getField().getName()); - } else if (oldGroup.getClass() == AutomaticPersonsGroup.class) { + return Objects.equals(oldAutomaticKeywordGroup.getKeywordDelimiter(), + newAutomaticKeywordGroup.getKeywordDelimiter()) + && Objects.equals(oldAutomaticKeywordGroup.getKeywordHierarchicalDelimiter(), + newAutomaticKeywordGroup.getKeywordHierarchicalDelimiter()) + && Objects.equals(oldAutomaticKeywordGroup.getField().getName(), + newAutomaticKeywordGroup.getField().getName()); + } + else if (oldGroup.getClass() == AutomaticPersonsGroup.class) { AutomaticPersonsGroup oldAutomaticPersonsGroup = (AutomaticPersonsGroup) oldGroup; AutomaticPersonsGroup newAutomaticPersonsGroup = (AutomaticPersonsGroup) newGroup; - return Objects.equals(oldAutomaticPersonsGroup.getField().getName(), newAutomaticPersonsGroup.getField().getName()); - } else if (oldGroup.getClass() == TexGroup.class) { + return Objects.equals(oldAutomaticPersonsGroup.getField().getName(), + newAutomaticPersonsGroup.getField().getName()); + } + else if (oldGroup.getClass() == TexGroup.class) { TexGroup oldTexGroup = (TexGroup) oldGroup; TexGroup newTexGroup = (TexGroup) newGroup; return Objects.equals(oldTexGroup.getFilePath().toString(), newTexGroup.getFilePath().toString()); @@ -329,11 +361,9 @@ boolean onlyMinorChanges(AbstractGroup oldGroup, AbstractGroup newGroup) { */ public void editGroup(GroupNodeViewModel oldGroup) { currentDatabase.ifPresent(database -> { - Optional newGroup = dialogService.showCustomDialogAndWait(new GroupDialogView( - database, - oldGroup.getGroupNode().getParent().orElse(null), - oldGroup.getGroupNode().getGroup(), - GroupDialogHeader.SUBGROUP)); + Optional newGroup = dialogService + .showCustomDialogAndWait(new GroupDialogView(database, oldGroup.getGroupNode().getParent().orElse(null), + oldGroup.getGroupNode().getGroup(), GroupDialogHeader.SUBGROUP)); newGroup.ifPresent(group -> { AbstractGroup oldGroupDef = oldGroup.getGroupNode().getGroup(); @@ -342,37 +372,36 @@ public void editGroup(GroupNodeViewModel oldGroup) { boolean groupTypeEqual = isGroupTypeEqual(oldGroupDef, group); boolean onlyMinorModifications = groupTypeEqual && onlyMinorChanges(oldGroupDef, group); - // dialog already warns us about this if the new group is named like another existing group - // We need to check if only the name changed as this is relevant for the entry's group field + // dialog already warns us about this if the new group is named like + // another existing group + // We need to check if only the name changed as this is relevant for the + // entry's group field if (groupTypeEqual && !group.getName().equals(oldGroupName) && onlyMinorModifications) { int groupsWithSameName = 0; Optional databaseRootGroup = currentDatabase.get().getMetaData().getGroups(); if (databaseRootGroup.isPresent()) { - // we need to check the old name for duplicates. If the new group name occurs more than once, it won't matter - groupsWithSameName = databaseRootGroup.get().findChildrenSatisfying(g -> g.getName().equals(oldGroupName)).size(); + // we need to check the old name for duplicates. If the new group + // name occurs more than once, it won't matter + groupsWithSameName = databaseRootGroup.get() + .findChildrenSatisfying(g -> g.getName().equals(oldGroupName)) + .size(); } - // We found more than 2 groups, so we cannot simply remove old assignment + // We found more than 2 groups, so we cannot simply remove old + // assignment boolean removePreviousAssignments = groupsWithSameName < 2; - oldGroup.getGroupNode().setGroup( - group, - true, - removePreviousAssignments, - database.getEntries()); + oldGroup.getGroupNode().setGroup(group, true, removePreviousAssignments, database.getEntries()); dialogService.notify(Localization.lang("Modified group \"%0\".", group.getName())); writeGroupChangesToMetaData(); - // This is ugly, but we have no proper update mechanism in place to propagate the changes, so redraw everything + // This is ugly, but we have no proper update mechanism in place to + // propagate the changes, so redraw everything refresh(); return; } if (groupTypeEqual && onlyMinorChanges(oldGroup.getGroupNode().getGroup(), group)) { - oldGroup.getGroupNode().setGroup( - group, - true, - true, - database.getEntries()); + oldGroup.getGroupNode().setGroup(group, true, true, database.getEntries()); writeGroupChangesToMetaData(); refresh(); @@ -383,19 +412,17 @@ public void editGroup(GroupNodeViewModel oldGroup) { String content = Localization.lang("Assign the original group's entries to this group?"); ButtonType keepAssignments = new ButtonType(Localization.lang("Assign"), ButtonBar.ButtonData.YES); - ButtonType removeAssignments = new ButtonType(Localization.lang("Do not assign"), ButtonBar.ButtonData.NO); + ButtonType removeAssignments = new ButtonType(Localization.lang("Do not assign"), + ButtonBar.ButtonData.NO); ButtonType cancel = new ButtonType(Localization.lang("Cancel"), ButtonBar.ButtonData.CANCEL_CLOSE); if (newGroup.get().getClass() == WordKeywordGroup.class) { - content = content + "\n\n" + - Localization.lang("(Note: If original entries lack keywords to qualify for the new group configuration, confirming here will add them)"); + content = content + "\n\n" + Localization.lang( + "(Note: If original entries lack keywords to qualify for the new group configuration, confirming here will add them)"); } - Optional previousAssignments = dialogService.showCustomButtonDialogAndWait(Alert.AlertType.WARNING, - Localization.lang("Change of Grouping Method"), - content, - keepAssignments, - removeAssignments, - cancel); + Optional previousAssignments = dialogService.showCustomButtonDialogAndWait( + Alert.AlertType.WARNING, Localization.lang("Change of Grouping Method"), content, + keepAssignments, removeAssignments, cancel); boolean removePreviousAssignments = (oldGroup.getGroupNode().getGroup() instanceof ExplicitGroup) && (group instanceof ExplicitGroup); @@ -403,7 +430,9 @@ public void editGroup(GroupNodeViewModel oldGroup) { Optional databaseRootGroup = currentDatabase.get().getMetaData().getGroups(); if (databaseRootGroup.isPresent()) { String name = oldGroup.getGroupNode().getGroup().getName(); - groupsWithSameName = databaseRootGroup.get().findChildrenSatisfying(g -> g.getName().equals(name)).size(); + groupsWithSameName = databaseRootGroup.get() + .findChildrenSatisfying(g -> g.getName().equals(name)) + .size(); } // okay we found more than 2 groups with the same name // If we only found one we can still do it @@ -411,19 +440,16 @@ public void editGroup(GroupNodeViewModel oldGroup) { removePreviousAssignments = false; } - if (previousAssignments.isPresent() && (previousAssignments.get().getButtonData() == ButtonBar.ButtonData.YES)) { - oldGroup.getGroupNode().setGroup( - group, - true, - removePreviousAssignments, - database.getEntries()); - } else if (previousAssignments.isPresent() && (previousAssignments.get().getButtonData() == ButtonBar.ButtonData.NO)) { - oldGroup.getGroupNode().setGroup( - group, - false, - removePreviousAssignments, - database.getEntries()); - } else if (previousAssignments.isPresent() && (previousAssignments.get().getButtonData() == ButtonBar.ButtonData.CANCEL_CLOSE)) { + if (previousAssignments.isPresent() + && (previousAssignments.get().getButtonData() == ButtonBar.ButtonData.YES)) { + oldGroup.getGroupNode().setGroup(group, true, removePreviousAssignments, database.getEntries()); + } + else if (previousAssignments.isPresent() + && (previousAssignments.get().getButtonData() == ButtonBar.ButtonData.NO)) { + oldGroup.getGroupNode().setGroup(group, false, removePreviousAssignments, database.getEntries()); + } + else if (previousAssignments.isPresent() + && (previousAssignments.get().getButtonData() == ButtonBar.ButtonData.CANCEL_CLOSE)) { return; } @@ -432,23 +458,26 @@ public void editGroup(GroupNodeViewModel oldGroup) { // TODO: Add undo // Store undo information. // AbstractUndoableEdit undoAddPreviousEntries = null; - // UndoableModifyGroup undo = new UndoableModifyGroup(GroupSelector.this, groupsRoot, node, newGroup); + // UndoableModifyGroup undo = new UndoableModifyGroup(GroupSelector.this, + // groupsRoot, node, newGroup); // if (undoAddPreviousEntries == null) { - // panel.getUndoManager().addEdit(undo); + // panel.getUndoManager().addEdit(undo); // } else { - // NamedCompound nc = new NamedCompound("Modify Group"); - // nc.addEdit(undo); - // nc.addEdit(undoAddPreviousEntries); - // nc.end();/ - // panel.getUndoManager().addEdit(nc); + // NamedCompound nc = new NamedCompound("Modify Group"); + // nc.addEdit(undo); + // nc.addEdit(undoAddPreviousEntries); + // nc.end();/ + // panel.getUndoManager().addEdit(nc); // } // if (!addChange.isEmpty()) { - // undoAddPreviousEntries = UndoableChangeEntriesOfGroup.getUndoableEdit(null, addChange); + // undoAddPreviousEntries = + // UndoableChangeEntriesOfGroup.getUndoableEdit(null, addChange); // } dialogService.notify(Localization.lang("Modified group \"%0\".", group.getName())); writeGroupChangesToMetaData(); - // This is ugly, but we have no proper update mechanism in place to propagate the changes, so redraw everything + // This is ugly, but we have no proper update mechanism in place to + // propagate the changes, so redraw everything refresh(); }); }); @@ -462,32 +491,32 @@ public void chatWithGroup(GroupNodeViewModel group) { // We localize the name here, because it is used as the title of the window. // See documentation for {@link AiChatGuardedComponent#name}. StringProperty nameProperty = new SimpleStringProperty(Localization.lang("Group %0", groupNameProperty.get())); - groupNameProperty.addListener((obs, oldValue, newValue) -> nameProperty.setValue(Localization.lang("Group %0", groupNameProperty.get()))); + groupNameProperty.addListener((obs, oldValue, newValue) -> nameProperty + .setValue(Localization.lang("Group %0", groupNameProperty.get()))); - ObservableList chatHistory = aiService.getChatHistoryService().getChatHistoryForGroup(currentDatabase.get(), group.getGroupNode()); - ObservableList bibEntries = FXCollections.observableArrayList(group.getGroupNode().findMatches(currentDatabase.get().getDatabase())); + ObservableList chatHistory = aiService.getChatHistoryService() + .getChatHistoryForGroup(currentDatabase.get(), group.getGroupNode()); + ObservableList bibEntries = FXCollections + .observableArrayList(group.getGroupNode().findMatches(currentDatabase.get().getDatabase())); openAiChat(nameProperty, chatHistory, currentDatabase.get(), bibEntries); } - private void openAiChat(StringProperty name, ObservableList chatHistory, BibDatabaseContext bibDatabaseContext, ObservableList entries) { - Optional existingWindow = stateManager.getAiChatWindows().stream().filter(window -> window.getChatName().equals(name.get())).findFirst(); + private void openAiChat(StringProperty name, ObservableList chatHistory, + BibDatabaseContext bibDatabaseContext, ObservableList entries) { + Optional existingWindow = stateManager.getAiChatWindows() + .stream() + .filter(window -> window.getChatName().equals(name.get())) + .findFirst(); if (existingWindow.isPresent()) { existingWindow.get().requestFocus(); - } else { - AiChatWindow aiChatWindow = new AiChatWindow( - aiService, - dialogService, - preferences.getAiPreferences(), - preferences.getExternalApplicationsPreferences(), - adaptVisibleTabs, - taskExecutor - ); - - aiChatWindow.setOnCloseRequest(event -> - stateManager.getAiChatWindows().remove(aiChatWindow) - ); + } + else { + AiChatWindow aiChatWindow = new AiChatWindow(aiService, dialogService, preferences.getAiPreferences(), + preferences.getExternalApplicationsPreferences(), adaptVisibleTabs, taskExecutor); + + aiChatWindow.setOnCloseRequest(event -> stateManager.getAiChatWindows().remove(aiChatWindow)); stateManager.getAiChatWindows().add(aiChatWindow); dialogService.showCustomWindow(aiChatWindow); @@ -501,20 +530,15 @@ public void generateEmbeddings(GroupNodeViewModel groupNode) { AbstractGroup group = groupNode.getGroupNode().getGroup(); - List linkedFiles = currentDatabase - .get() - .getDatabase() - .getEntries() - .stream() - .filter(group::isMatch) - .flatMap(entry -> entry.getFiles().stream()) - .toList(); + List linkedFiles = currentDatabase.get() + .getDatabase() + .getEntries() + .stream() + .filter(group::isMatch) + .flatMap(entry -> entry.getFiles().stream()) + .toList(); - aiService.getIngestionService().ingest( - group.nameProperty(), - linkedFiles, - currentDatabase.get() - ); + aiService.getIngestionService().ingest(group.nameProperty(), linkedFiles, currentDatabase.get()); dialogService.notify(Localization.lang("Ingestion started for group \"%0\".", group.getName())); } @@ -524,30 +548,25 @@ public void generateSummaries(GroupNodeViewModel groupNode) { AbstractGroup group = groupNode.getGroupNode().getGroup(); - List entries = currentDatabase - .get() - .getDatabase() - .getEntries() - .stream() - .filter(group::isMatch) - .toList(); + List entries = currentDatabase.get() + .getDatabase() + .getEntries() + .stream() + .filter(group::isMatch) + .toList(); - aiService.getSummariesService().summarize( - group.nameProperty(), - entries, - currentDatabase.get() - ); + aiService.getSummariesService().summarize(group.nameProperty(), entries, currentDatabase.get()); dialogService.notify(Localization.lang("Summarization started for group \"%0\".", group.getName())); } public void removeSubgroups(GroupNodeViewModel group) { - boolean confirmation = dialogService.showConfirmationDialogAndWait( - Localization.lang("Remove subgroups"), + boolean confirmation = dialogService.showConfirmationDialogAndWait(Localization.lang("Remove subgroups"), Localization.lang("Remove all subgroups of \"%0\"?", group.getDisplayName())); if (confirmation) { /// TODO: Add undo - // final UndoableModifySubtree undo = new UndoableModifySubtree(getGroupTreeRoot(), node, "Remove subgroups"); + // final UndoableModifySubtree undo = new + /// UndoableModifySubtree(getGroupTreeRoot(), node, "Remove subgroups"); // panel.getUndoManager().addEdit(undo); for (GroupNodeViewModel child : group.getChildren()) { removeGroupsAndSubGroupsFromEntries(child); @@ -561,20 +580,21 @@ public void removeSubgroups(GroupNodeViewModel group) { public void removeGroupKeepSubgroups(GroupNodeViewModel group) { boolean confirmed; if (selectedGroups.size() <= 1) { - confirmed = dialogService.showConfirmationDialogAndWait( - Localization.lang("Remove group"), + confirmed = dialogService.showConfirmationDialogAndWait(Localization.lang("Remove group"), Localization.lang("Remove group \"%0\" and keep its subgroups?", group.getDisplayName()), Localization.lang("Remove")); - } else { - confirmed = dialogService.showConfirmationDialogAndWait( - Localization.lang("Remove groups"), + } + else { + confirmed = dialogService.showConfirmationDialogAndWait(Localization.lang("Remove groups"), Localization.lang("Remove all selected groups and keep their subgroups?"), Localization.lang("Remove all")); } if (confirmed) { // TODO: Add undo - // final UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup(groupsRoot, node, UndoableAddOrRemoveGroup.REMOVE_NODE_KEEP_CHILDREN); + // final UndoableAddOrRemoveGroup undo = new + // UndoableAddOrRemoveGroup(groupsRoot, node, + // UndoableAddOrRemoveGroup.REMOVE_NODE_KEEP_CHILDREN); // panel.getUndoManager().addEdit(undo); List selectedGroupNodes = new ArrayList<>(selectedGroups); @@ -582,13 +602,14 @@ public void removeGroupKeepSubgroups(GroupNodeViewModel group) { GroupTreeNode groupNode = eachNode.getGroupNode(); groupNode.getParent() - .ifPresent(parent -> groupNode.moveAllChildrenTo(parent, parent.getIndexOfChild(groupNode).get())); + .ifPresent(parent -> groupNode.moveAllChildrenTo(parent, parent.getIndexOfChild(groupNode).get())); groupNode.removeFromParent(); }); if (selectedGroupNodes.size() > 1) { dialogService.notify(Localization.lang("Removed all selected groups.")); - } else { + } + else { dialogService.notify(Localization.lang("Removed group \"%0\".", group.getDisplayName())); } writeGroupChangesToMetaData(); @@ -601,20 +622,21 @@ public void removeGroupKeepSubgroups(GroupNodeViewModel group) { public void removeGroupAndSubgroups(GroupNodeViewModel group) { boolean confirmed; if (selectedGroups.size() <= 1) { - confirmed = dialogService.showConfirmationDialogAndWait( - Localization.lang("Remove group and subgroups"), + confirmed = dialogService.showConfirmationDialogAndWait(Localization.lang("Remove group and subgroups"), Localization.lang("Remove group \"%0\" and its subgroups?", group.getDisplayName()), Localization.lang("Remove")); - } else { - confirmed = dialogService.showConfirmationDialogAndWait( - Localization.lang("Remove groups and subgroups"), + } + else { + confirmed = dialogService.showConfirmationDialogAndWait(Localization.lang("Remove groups and subgroups"), Localization.lang("Remove all selected groups and their subgroups?"), Localization.lang("Remove all")); } if (confirmed) { // TODO: Add undo - // final UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup(groupsRoot, node, UndoableAddOrRemoveGroup.REMOVE_NODE_AND_CHILDREN); + // final UndoableAddOrRemoveGroup undo = new + // UndoableAddOrRemoveGroup(groupsRoot, node, + // UndoableAddOrRemoveGroup.REMOVE_NODE_AND_CHILDREN); // panel.getUndoManager().addEdit(undo); ArrayList selectedGroupNodes = new ArrayList<>(selectedGroups); @@ -625,8 +647,10 @@ public void removeGroupAndSubgroups(GroupNodeViewModel group) { if (selectedGroupNodes.size() > 1) { dialogService.notify(Localization.lang("Removed all selected groups and their subgroups.")); - } else { - dialogService.notify(Localization.lang("Removed group \"%0\" and its subgroups.", group.getDisplayName())); + } + else { + dialogService + .notify(Localization.lang("Removed group \"%0\" and its subgroups.", group.getDisplayName())); } writeGroupChangesToMetaData(); } @@ -638,20 +662,20 @@ public void removeGroupAndSubgroups(GroupNodeViewModel group) { public void removeGroupNoSubgroups(GroupNodeViewModel group) { boolean confirmed; if (selectedGroups.size() <= 1) { - confirmed = dialogService.showConfirmationDialogAndWait( - Localization.lang("Remove group"), - Localization.lang("Remove group \"%0\"?", group.getDisplayName()), - Localization.lang("Remove")); - } else { - confirmed = dialogService.showConfirmationDialogAndWait( - Localization.lang("Remove groups and subgroups"), + confirmed = dialogService.showConfirmationDialogAndWait(Localization.lang("Remove group"), + Localization.lang("Remove group \"%0\"?", group.getDisplayName()), Localization.lang("Remove")); + } + else { + confirmed = dialogService.showConfirmationDialogAndWait(Localization.lang("Remove groups and subgroups"), Localization.lang("Remove all selected groups and their subgroups?"), Localization.lang("Remove all")); } if (confirmed) { // TODO: Add undo - // final UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup(groupsRoot, node, UndoableAddOrRemoveGroup.REMOVE_NODE_WITHOUT_CHILDREN); + // final UndoableAddOrRemoveGroup undo = new + // UndoableAddOrRemoveGroup(groupsRoot, node, + // UndoableAddOrRemoveGroup.REMOVE_NODE_WITHOUT_CHILDREN); // panel.getUndoManager().addEdit(undo); ArrayList selectedGroupNodes = new ArrayList<>(selectedGroups); @@ -662,7 +686,8 @@ public void removeGroupNoSubgroups(GroupNodeViewModel group) { if (selectedGroupNodes.size() > 1) { dialogService.notify(Localization.lang("Removed all selected groups.")); - } else { + } + else { dialogService.notify(Localization.lang("Removed group \"%0\".", group.getDisplayName())); } writeGroupChangesToMetaData(); @@ -674,7 +699,8 @@ void removeGroupsAndSubGroupsFromEntries(GroupNodeViewModel group) { removeGroupsAndSubGroupsFromEntries(child); } - // only remove explicit groups from the entries, keyword groups should not be deleted + // only remove explicit groups from the entries, keyword groups should not be + // deleted if (group.getGroupNode().getGroup() instanceof ExplicitGroup) { int groupsWithSameName = 0; String name = group.getGroupNode().getGroup().getName(); @@ -683,7 +709,8 @@ void removeGroupsAndSubGroupsFromEntries(GroupNodeViewModel group) { groupsWithSameName = rootGroup.get().findChildrenSatisfying(g -> g.getName().equals(name)).size(); } if (groupsWithSameName < 2) { - List entriesInGroup = group.getGroupNode().getEntriesInGroup(this.currentDatabase.get().getEntries()); + List entriesInGroup = group.getGroupNode() + .getEntriesInGroup(this.currentDatabase.get().getEntries()); group.getGroupNode().removeEntriesFromGroup(entriesInGroup); } } @@ -691,42 +718,53 @@ void removeGroupsAndSubGroupsFromEntries(GroupNodeViewModel group) { public void addSelectedEntries(GroupNodeViewModel group) { // TODO: Warn - // if (!WarnAssignmentSideEffects.warnAssignmentSideEffects(node.getNode().getGroup(), panel.frame())) { - // return; // user aborted operation + // if + // (!WarnAssignmentSideEffects.warnAssignmentSideEffects(node.getNode().getGroup(), + // panel.frame())) { + // return; // user aborted operation group.getGroupNode().addEntriesToGroup(stateManager.getSelectedEntries()); // TODO: Add undo - // NamedCompound undoAll = new NamedCompound(Localization.lang("change assignment of entries")); - // if (!undoAdd.isEmpty()) { undo.addEdit(UndoableChangeEntriesOfGroup.getUndoableEdit(node, undoAdd)); } + // NamedCompound undoAll = new NamedCompound(Localization.lang("change assignment + // of entries")); + // if (!undoAdd.isEmpty()) { + // undo.addEdit(UndoableChangeEntriesOfGroup.getUndoableEdit(node, undoAdd)); } // panel.getUndoManager().addEdit(undoAll); // TODO Display massages // if (undo == null) { - // frame.output(Localization.lang("The group \"%0\" already contains the selection.", - // node.getGroup().getName())); - // return; + // frame.output(Localization.lang("The group \"%0\" already contains the + // selection.", + // node.getGroup().getName())); + // return; // } // panel.getUndoManager().addEdit(undo); // final String groupName = node.getGroup().getName(); // if (assignedEntries == 1) { - // frame.output(Localization.lang("Assigned 1 entry to group \"%0\".", groupName)); + // frame.output(Localization.lang("Assigned 1 entry to group \"%0\".", + // groupName)); // } else { - // frame.output(Localization.lang("Assigned %0 entries to group \"%1\".", String.valueOf(assignedEntries), - // groupName)); + // frame.output(Localization.lang("Assigned %0 entries to group \"%1\".", + // String.valueOf(assignedEntries), + // groupName)); // } } public void removeSelectedEntries(GroupNodeViewModel group) { - // TODO: warn if assignment has undesired side effects (modifies a field != keywords) - // if (!WarnAssignmentSideEffects.warnAssignmentSideEffects(mNode.getNode().getGroup(), mPanel.frame())) { - // return; // user aborted operation + // TODO: warn if assignment has undesired side effects (modifies a field != + // keywords) + // if + // (!WarnAssignmentSideEffects.warnAssignmentSideEffects(mNode.getNode().getGroup(), + // mPanel.frame())) { + // return; // user aborted operation group.getGroupNode().removeEntriesFromGroup(stateManager.getSelectedEntries()); // TODO: Add undo // if (!undo.isEmpty()) { - // mPanel.getUndoManager().addEdit(UndoableChangeEntriesOfGroup.getUndoableEdit(mNode, undo)); + // mPanel.getUndoManager().addEdit(UndoableChangeEntriesOfGroup.getUndoableEdit(mNode, + // undo)); } public void sortAlphabeticallyRecursive(GroupTreeNode group) { @@ -744,4 +782,5 @@ public void sortEntriesRecursive(GroupTreeNode group) { public void sortReverseEntriesRecursive(GroupTreeNode group) { group.sortChildren(compEntriesReverse, true); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/GroupViewMode.java b/jabgui/src/main/java/org/jabref/gui/groups/GroupViewMode.java index 23784615fa3..e2699b950f0 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/GroupViewMode.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/GroupViewMode.java @@ -1,3 +1,7 @@ package org.jabref.gui.groups; -public enum GroupViewMode { INTERSECTION, FILTER, INVERT } +public enum GroupViewMode { + + INTERSECTION, FILTER, INVERT + +} diff --git a/jabgui/src/main/java/org/jabref/gui/groups/GroupsPreferences.java b/jabgui/src/main/java/org/jabref/gui/groups/GroupsPreferences.java index 9c71262b4de..82cb4c6d210 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/GroupsPreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/GroupsPreferences.java @@ -17,16 +17,16 @@ public class GroupsPreferences { private final SetProperty groupViewMode; + private final BooleanProperty shouldAutoAssignGroup; + private final BooleanProperty shouldDisplayGroupCount; + private final ObjectProperty defaultHierarchicalContext; - public GroupsPreferences(boolean viewModeIntersection, - boolean viewModeFilter, - boolean viewModeInvert, - boolean shouldAutoAssignGroup, - boolean shouldDisplayGroupCount, - GroupHierarchyType defaultHierarchicalContext) { + public GroupsPreferences(boolean viewModeIntersection, boolean viewModeFilter, boolean viewModeInvert, + boolean shouldAutoAssignGroup, boolean shouldDisplayGroupCount, + GroupHierarchyType defaultHierarchicalContext) { this.groupViewMode = new SimpleSetProperty<>(FXCollections.observableSet()); this.shouldAutoAssignGroup = new SimpleBooleanProperty(shouldAutoAssignGroup); @@ -45,10 +45,8 @@ public GroupsPreferences(boolean viewModeIntersection, } @VisibleForTesting - public GroupsPreferences(EnumSet groupViewMode, - boolean shouldAutoAssignGroup, - boolean shouldDisplayGroupCount, - GroupHierarchyType defaultHierarchicalContext) { + public GroupsPreferences(EnumSet groupViewMode, boolean shouldAutoAssignGroup, + boolean shouldDisplayGroupCount, GroupHierarchyType defaultHierarchicalContext) { this.groupViewMode = new SimpleSetProperty<>(FXCollections.observableSet(groupViewMode)); this.shouldAutoAssignGroup = new SimpleBooleanProperty(shouldAutoAssignGroup); this.shouldDisplayGroupCount = new SimpleBooleanProperty(shouldDisplayGroupCount); @@ -69,7 +67,8 @@ public SetProperty groupViewModeProperty() { public void setGroupViewMode(GroupViewMode mode, boolean value) { if (value) { groupViewMode.add(mode); - } else { + } + else { groupViewMode.remove(mode); } } @@ -109,4 +108,5 @@ public ObjectProperty defaultHierarchicalContextProperty() { public void setDefaultHierarchicalContext(GroupHierarchyType defaultHierarchicalContext) { this.defaultHierarchicalContext.set(defaultHierarchicalContext); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/JabRefSuggestedGroups.java b/jabgui/src/main/java/org/jabref/gui/groups/JabRefSuggestedGroups.java index 644452de5db..0791d53cc1c 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/JabRefSuggestedGroups.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/JabRefSuggestedGroups.java @@ -10,18 +10,13 @@ public class JabRefSuggestedGroups { public static SearchGroup createWithoutFilesGroup() { - return new SearchGroup( - Localization.lang("Entries without linked files"), - GroupHierarchyType.INDEPENDENT, - "file !=~.*", - EnumSet.noneOf(SearchFlags.class)); + return new SearchGroup(Localization.lang("Entries without linked files"), GroupHierarchyType.INDEPENDENT, + "file !=~.*", EnumSet.noneOf(SearchFlags.class)); } public static SearchGroup createWithoutGroupsGroup() { - return new SearchGroup( - Localization.lang("Entries without groups"), - GroupHierarchyType.INDEPENDENT, - "groups !=~.*", - EnumSet.noneOf(SearchFlags.class)); + return new SearchGroup(Localization.lang("Entries without groups"), GroupHierarchyType.INDEPENDENT, + "groups !=~.*", EnumSet.noneOf(SearchFlags.class)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/MoveGroupChange.java b/jabgui/src/main/java/org/jabref/gui/groups/MoveGroupChange.java index e52f9cbef8d..7b35b017373 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/MoveGroupChange.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/MoveGroupChange.java @@ -5,12 +5,15 @@ public class MoveGroupChange { private GroupTreeNode oldParent; + private int oldChildIndex; + private GroupTreeNode newParent; + private int newChildIndex; /** - * @param newParent The new parent node to which the node will be moved. + * @param newParent The new parent node to which the node will be moved. * @param newChildIndex The child index at newParent to which the node will be moved. */ public MoveGroupChange(GroupTreeNode oldParent, int oldChildIndex, GroupTreeNode newParent, int newChildIndex) { @@ -35,4 +38,5 @@ public GroupTreeNode getNewParent() { public int getNewChildIndex() { return newChildIndex; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/UndoableAddOrRemoveGroup.java b/jabgui/src/main/java/org/jabref/gui/groups/UndoableAddOrRemoveGroup.java index 69a7bc3e715..7e55a34bcd8 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/UndoableAddOrRemoveGroup.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/UndoableAddOrRemoveGroup.java @@ -34,7 +34,8 @@ public class UndoableAddOrRemoveGroup extends AbstractUndoableJabRefEdit { private final GroupTreeNode m_subtreeBackup; /** - * In case of removing a node but keeping all of its children, the number of children has to be stored. + * In case of removing a node but keeping all of its children, the number of children + * has to be stored. */ private final int m_subtreeRootChildCount; @@ -44,35 +45,30 @@ public class UndoableAddOrRemoveGroup extends AbstractUndoableJabRefEdit { private final List m_pathToNode; /** - * The type of the editing (ADD_NODE, REMOVE_NODE_KEEP_CHILDREN, REMOVE_NODE_AND_CHILDREN) + * The type of the editing (ADD_NODE, REMOVE_NODE_KEEP_CHILDREN, + * REMOVE_NODE_AND_CHILDREN) */ private final int m_editType; /** * Creates an object that can undo/redo an edit event. - * - * @param groupsRoot - * The global groups root. - * @param editType - * The type of editing (ADD_NODE, REMOVE_NODE_KEEP_CHILDREN, - * REMOVE_NODE_AND_CHILDREN) - * @param editedNode - * The edited node (which was added or will be removed). The node - * must be a descendant of node groupsRoot! This means - * that, in case of adding, you first have to add it to the tree, - * then call this constructor. When removing, you first have to - * call this constructor, then remove the node. + * @param groupsRoot The global groups root. + * @param editType The type of editing (ADD_NODE, REMOVE_NODE_KEEP_CHILDREN, + * REMOVE_NODE_AND_CHILDREN) + * @param editedNode The edited node (which was added or will be removed). The node + * must be a descendant of node groupsRoot! This means that, in case of adding, + * you first have to add it to the tree, then call this constructor. When removing, + * you first have to call this constructor, then remove the node. */ - public UndoableAddOrRemoveGroup(GroupTreeNodeViewModel groupsRoot, - GroupTreeNodeViewModel editedNode, int editType) { + public UndoableAddOrRemoveGroup(GroupTreeNodeViewModel groupsRoot, GroupTreeNodeViewModel editedNode, + int editType) { m_groupsRootHandle = groupsRoot; m_editType = editType; m_subtreeRootChildCount = editedNode.getChildren().size(); // storing a backup of the whole subtree is not required when children // are kept - m_subtreeBackup = editType != UndoableAddOrRemoveGroup.REMOVE_NODE_KEEP_CHILDREN ? - editedNode.getNode() - .copySubtree() + m_subtreeBackup = editType != UndoableAddOrRemoveGroup.REMOVE_NODE_KEEP_CHILDREN + ? editedNode.getNode().copySubtree() : GroupTreeNode.fromGroup(editedNode.getNode().getGroup().deepCopy()); // remember path to edited node. this cannot be stored as a reference, // because the reference itself might change. the method below is more @@ -117,8 +113,7 @@ private void doOperation(boolean undo) { case REMOVE_NODE_KEEP_CHILDREN: // move all children to newNode, then add newNode GroupTreeNode newNode = m_subtreeBackup.copySubtree(); - for (int i = childIndex; i < (childIndex - + m_subtreeRootChildCount); ++i) { + for (int i = childIndex; i < (childIndex + m_subtreeRootChildCount); ++i) { cursor.getChildAt(childIndex).get().moveTo(newNode); } newNode.moveTo(cursor, childIndex); @@ -129,15 +124,15 @@ private void doOperation(boolean undo) { default: break; } - } else { // redo + } + else { // redo switch (m_editType) { case ADD_NODE: m_subtreeBackup.copySubtree().moveTo(cursor, childIndex); break; case REMOVE_NODE_KEEP_CHILDREN: // remove node, then insert all children - GroupTreeNode removedNode = cursor - .getChildAt(childIndex).get(); + GroupTreeNode removedNode = cursor.getChildAt(childIndex).get(); cursor.removeChild(childIndex); while (removedNode.getNumberOfChildren() > 0) { removedNode.getFirstChild().get().moveTo(cursor, childIndex); @@ -151,4 +146,5 @@ private void doOperation(boolean undo) { } } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/UndoableChangeEntriesOfGroup.java b/jabgui/src/main/java/org/jabref/gui/groups/UndoableChangeEntriesOfGroup.java index 3f4b36b6351..9507c490aca 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/UndoableChangeEntriesOfGroup.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/UndoableChangeEntriesOfGroup.java @@ -27,4 +27,5 @@ public static AbstractUndoableEdit getUndoableEdit(GroupTreeNodeViewModel node, } return null; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/UndoableModifySubtree.java b/jabgui/src/main/java/org/jabref/gui/groups/UndoableModifySubtree.java index 8f92c717259..c782a9958c6 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/UndoableModifySubtree.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/UndoableModifySubtree.java @@ -28,10 +28,10 @@ public class UndoableModifySubtree extends AbstractUndoableJabRefEdit { private final String m_name; /** - * @param subtree The root node of the subtree that was modified (this node may not be modified, it is just used as a convenience handle). + * @param subtree The root node of the subtree that was modified (this node may not be + * modified, it is just used as a convenience handle). */ - public UndoableModifySubtree(GroupTreeNodeViewModel groupRoot, - GroupTreeNodeViewModel subtree, String name) { + public UndoableModifySubtree(GroupTreeNodeViewModel groupRoot, GroupTreeNodeViewModel subtree, String name) { m_subtreeBackup = subtree.getNode().copySubtree(); m_groupRoot = groupRoot.getNode(); m_subtreeRootPath = subtree.getNode().getIndexedPathFromRoot(); @@ -49,7 +49,8 @@ public void undo() { // remember modified children for redo m_modifiedSubtree.clear(); // get node to edit - final GroupTreeNode subtreeRoot = m_groupRoot.getDescendant(m_subtreeRootPath).get(); // TODO: NULL + final GroupTreeNode subtreeRoot = m_groupRoot.getDescendant(m_subtreeRootPath).get(); // TODO: + // NULL m_modifiedSubtree.addAll(subtreeRoot.getChildren()); // keep subtree handle, but restore everything else from backup subtreeRoot.removeAllChildren(); @@ -61,10 +62,12 @@ public void undo() { @Override public void redo() { super.redo(); - final GroupTreeNode subtreeRoot = m_groupRoot.getDescendant(m_subtreeRootPath).get(); // TODO: NULL + final GroupTreeNode subtreeRoot = m_groupRoot.getDescendant(m_subtreeRootPath).get(); // TODO: + // NULL subtreeRoot.removeAllChildren(); for (GroupTreeNode modifiedNode : m_modifiedSubtree) { modifiedNode.moveTo(subtreeRoot); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/groups/UndoableMoveGroup.java b/jabgui/src/main/java/org/jabref/gui/groups/UndoableMoveGroup.java index c4f367d31f7..21d56944a37 100644 --- a/jabgui/src/main/java/org/jabref/gui/groups/UndoableMoveGroup.java +++ b/jabgui/src/main/java/org/jabref/gui/groups/UndoableMoveGroup.java @@ -10,9 +10,13 @@ class UndoableMoveGroup extends AbstractUndoableJabRefEdit { private final GroupTreeNodeViewModel root; + private final List pathToNewParent; + private final int newChildIndex; + private final List pathToOldParent; + private final int oldChildIndex; public UndoableMoveGroup(GroupTreeNodeViewModel root, MoveGroupChange moveChange) { @@ -33,7 +37,8 @@ public String getPresentationName() { public void undo() { super.undo(); - GroupTreeNode newParent = root.getNode().getDescendant(pathToNewParent).get(); // TODO: NULL + GroupTreeNode newParent = root.getNode().getDescendant(pathToNewParent).get(); // TODO: + // NULL GroupTreeNode node = newParent.getChildAt(newChildIndex).get(); // TODO: Null // TODO: NULL node.moveTo(root.getNode().getDescendant(pathToOldParent).get(), oldChildIndex); @@ -43,9 +48,11 @@ public void undo() { public void redo() { super.redo(); - GroupTreeNode oldParent = root.getNode().getDescendant(pathToOldParent).get(); // TODO: NULL + GroupTreeNode oldParent = root.getNode().getDescendant(pathToOldParent).get(); // TODO: + // NULL GroupTreeNode node = oldParent.getChildAt(oldChildIndex).get(); // TODO:Null // TODO: NULL node.moveTo(root.getNode().getDescendant(pathToNewParent).get(), newChildIndex); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/help/AboutAction.java b/jabgui/src/main/java/org/jabref/gui/help/AboutAction.java index b123a8f8396..14aa270ec89 100644 --- a/jabgui/src/main/java/org/jabref/gui/help/AboutAction.java +++ b/jabgui/src/main/java/org/jabref/gui/help/AboutAction.java @@ -6,6 +6,7 @@ public class AboutAction extends SimpleCommand { private final AboutDialogView aboutDialogView; + private final DialogService dialogService; public AboutAction(final DialogService dialogService) { @@ -17,4 +18,5 @@ public AboutAction(final DialogService dialogService) { public void execute() { dialogService.showCustomDialog(aboutDialogView); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/help/AboutDialogView.java b/jabgui/src/main/java/org/jabref/gui/help/AboutDialogView.java index dab1cbabd9c..2787345fdb0 100644 --- a/jabgui/src/main/java/org/jabref/gui/help/AboutDialogView.java +++ b/jabgui/src/main/java/org/jabref/gui/help/AboutDialogView.java @@ -18,33 +18,41 @@ public class AboutDialogView extends BaseDialog { - @FXML private ButtonType copyVersionButton; - @FXML private TextArea textAreaVersions; + @FXML + private ButtonType copyVersionButton; + + @FXML + private TextArea textAreaVersions; + + @Inject + private DialogService dialogService; - @Inject private DialogService dialogService; - @Inject private GuiPreferences preferences; - @Inject private ClipBoardManager clipBoardManager; - @Inject private BuildInfo buildInfo; - @Inject private ThemeManager themeManager; + @Inject + private GuiPreferences preferences; + + @Inject + private ClipBoardManager clipBoardManager; + + @Inject + private BuildInfo buildInfo; + + @Inject + private ThemeManager themeManager; private AboutDialogViewModel viewModel; public AboutDialogView() { this.setTitle(Localization.lang("About JabRef")); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); ControlHelper.setAction(copyVersionButton, getDialogPane(), ignored -> copyVersionToClipboard()); - getDialogPane() - .sceneProperty() - .addListener((_, ignored, newScene) -> { - if (newScene != null) { - themeManager.updateFontStyle(newScene); - } - }); + getDialogPane().sceneProperty().addListener((_, ignored, newScene) -> { + if (newScene != null) { + themeManager.updateFontStyle(newScene); + } + }); } public AboutDialogViewModel getViewModel() { @@ -102,4 +110,5 @@ public void openDonation() { public void openPrivacyPolicy() { viewModel.openPrivacyPolicy(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/help/AboutDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/help/AboutDialogViewModel.java index 4f430c43e23..ac9f3be2b9a 100644 --- a/jabgui/src/main/java/org/jabref/gui/help/AboutDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/help/AboutDialogViewModel.java @@ -24,20 +24,33 @@ import org.slf4j.LoggerFactory; public class AboutDialogViewModel extends AbstractViewModel { + private final String changelogUrl; + private final String versionInfo; + private final ReadOnlyStringWrapper environmentInfo = new ReadOnlyStringWrapper(); + private final Logger logger = LoggerFactory.getLogger(AboutDialogViewModel.class); + private final ReadOnlyStringWrapper heading = new ReadOnlyStringWrapper(); + private final ReadOnlyStringWrapper maintainers = new ReadOnlyStringWrapper(); + private final ReadOnlyStringWrapper license = new ReadOnlyStringWrapper(); + private final ReadOnlyBooleanWrapper isDevelopmentVersion = new ReadOnlyBooleanWrapper(); + private final DialogService dialogService; + private final GuiPreferences preferences; + private final ReadOnlyStringWrapper developmentVersion = new ReadOnlyStringWrapper(); + private final ClipBoardManager clipBoardManager; - public AboutDialogViewModel(DialogService dialogService, GuiPreferences preferences, ClipBoardManager clipBoardManager, BuildInfo buildInfo) { + public AboutDialogViewModel(DialogService dialogService, GuiPreferences preferences, + ClipBoardManager clipBoardManager, BuildInfo buildInfo) { this.dialogService = Objects.requireNonNull(dialogService); this.preferences = Objects.requireNonNull(preferences); this.clipBoardManager = Objects.requireNonNull(clipBoardManager); @@ -46,17 +59,21 @@ public AboutDialogViewModel(DialogService dialogService, GuiPreferences preferen if (version.length == 1) { isDevelopmentVersion.set(false); - } else { + } + else { isDevelopmentVersion.set(true); - String dev = Lists.newArrayList(version).stream().filter(string -> !string.equals(version[0])).collect( - Collectors.joining("--")); + String dev = Lists.newArrayList(version) + .stream() + .filter(string -> !string.equals(version[0])) + .collect(Collectors.joining("--")); developmentVersion.set(dev); } maintainers.set(buildInfo.maintainers); license.set(Localization.lang("License") + ":"); changelogUrl = buildInfo.version.getChangelogUrl(); - String javafx_version = System.getProperty("javafx.runtime.version", BuildInfo.UNKNOWN_VERSION).toLowerCase(Locale.ROOT); + String javafx_version = System.getProperty("javafx.runtime.version", BuildInfo.UNKNOWN_VERSION) + .toLowerCase(Locale.ROOT); versionInfo = "JabRef %s%n%s %s %s %nJava %s %nJavaFX %s".formatted(buildInfo.version, BuildInfo.OS, BuildInfo.OS_VERSION, BuildInfo.OS_ARCH, BuildInfo.JAVA_VERSION, javafx_version); @@ -146,7 +163,8 @@ public void openDonation() { private void openWebsite(String url) { try { NativeDesktop.openBrowser(url, preferences.getExternalApplicationsPreferences()); - } catch (IOException e) { + } + catch (IOException e) { dialogService.showErrorDialogAndWait(Localization.lang("Could not open website."), e); logger.error("Could not open default browser.", e); } @@ -155,4 +173,5 @@ private void openWebsite(String url) { public void openPrivacyPolicy() { openWebsite(URLs.PRIVACY_POLICY_URL); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/help/ErrorConsoleAction.java b/jabgui/src/main/java/org/jabref/gui/help/ErrorConsoleAction.java index a27ffacee40..88acd69f617 100644 --- a/jabgui/src/main/java/org/jabref/gui/help/ErrorConsoleAction.java +++ b/jabgui/src/main/java/org/jabref/gui/help/ErrorConsoleAction.java @@ -7,9 +7,9 @@ import com.airhacks.afterburner.injection.Injector; /** - * Such an error console can be - * useful in getting complete bug reports, especially from Windows users, - * without asking users to run JabRef in a command window to catch the error info. + * Such an error console can be useful in getting complete bug reports, especially from + * Windows users, without asking users to run JabRef in a command window to catch the + * error info. * * It offers a separate tab for the log output. */ @@ -20,4 +20,5 @@ public void execute() { DialogService dialogService = Injector.instantiateModelOrService(DialogService.class); dialogService.showCustomDialog(new ErrorConsoleView()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/help/HelpAction.java b/jabgui/src/main/java/org/jabref/gui/help/HelpAction.java index 2db01bebfcf..775b22faf47 100644 --- a/jabgui/src/main/java/org/jabref/gui/help/HelpAction.java +++ b/jabgui/src/main/java/org/jabref/gui/help/HelpAction.java @@ -7,27 +7,32 @@ import org.jabref.logic.help.HelpFile; /** - * This Action keeps a reference to a URL. When activated, it shows the help - * Dialog unless it is already visible, and shows the URL in it. + * This Action keeps a reference to a URL. When activated, it shows the help Dialog unless + * it is already visible, and shows the URL in it. */ public class HelpAction extends SimpleCommand { private final HelpFile helpPage; + private final DialogService dialogService; + private final ExternalApplicationsPreferences externalApplicationPreferences; - public HelpAction(HelpFile helpPage, DialogService dialogService, ExternalApplicationsPreferences externalApplicationsPreferences) { + public HelpAction(HelpFile helpPage, DialogService dialogService, + ExternalApplicationsPreferences externalApplicationsPreferences) { this.helpPage = helpPage; this.dialogService = dialogService; this.externalApplicationPreferences = externalApplicationsPreferences; } void openHelpPage(HelpFile helpPage) { - NativeDesktop.openBrowserShowPopup("https://docs.jabref.org/" + helpPage.getPageName(), dialogService, externalApplicationPreferences); + NativeDesktop.openBrowserShowPopup("https://docs.jabref.org/" + helpPage.getPageName(), dialogService, + externalApplicationPreferences); } @Override public void execute() { openHelpPage(helpPage); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/help/NewVersionDialog.java b/jabgui/src/main/java/org/jabref/gui/help/NewVersionDialog.java index 66fe6548e6e..d2cb50957f9 100644 --- a/jabgui/src/main/java/org/jabref/gui/help/NewVersionDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/help/NewVersionDialog.java @@ -16,21 +16,23 @@ public class NewVersionDialog extends BaseDialog { - public NewVersionDialog(Version currentVersion, - Version latestVersion, - DialogService dialogService, - ExternalApplicationsPreferences externalApplicationsPreferences) { + public NewVersionDialog(Version currentVersion, Version latestVersion, DialogService dialogService, + ExternalApplicationsPreferences externalApplicationsPreferences) { this.setTitle(Localization.lang("New version available")); - ButtonType btnIgnoreUpdate = new ButtonType(Localization.lang("Ignore this update"), ButtonBar.ButtonData.CANCEL_CLOSE); + ButtonType btnIgnoreUpdate = new ButtonType(Localization.lang("Ignore this update"), + ButtonBar.ButtonData.CANCEL_CLOSE); ButtonType btnDownloadUpdate = new ButtonType(Localization.lang("Download update"), ButtonBar.ButtonData.APPLY); - ButtonType btnRemindMeLater = new ButtonType(Localization.lang("Remind me later"), ButtonBar.ButtonData.CANCEL_CLOSE); + ButtonType btnRemindMeLater = new ButtonType(Localization.lang("Remind me later"), + ButtonBar.ButtonData.CANCEL_CLOSE); this.getDialogPane().getButtonTypes().addAll(btnIgnoreUpdate, btnDownloadUpdate, btnRemindMeLater); this.setResultConverter(button -> { if (button == btnIgnoreUpdate) { return false; - } else if (button == btnDownloadUpdate) { - NativeDesktop.openBrowserShowPopup(Version.JABREF_DOWNLOAD_URL, dialogService, externalApplicationsPreferences); + } + else if (button == btnDownloadUpdate) { + NativeDesktop.openBrowserShowPopup(Version.JABREF_DOWNLOAD_URL, dialogService, + externalApplicationsPreferences); } return true; }); @@ -38,17 +40,15 @@ public NewVersionDialog(Version currentVersion, defaultButton.setDefaultButton(true); Hyperlink lblMoreInformation = new Hyperlink(Localization.lang("See what's new")); - lblMoreInformation.setOnAction(_ -> - NativeDesktop.openBrowserShowPopup(latestVersion.getChangelogUrl(), dialogService, externalApplicationsPreferences) - ); + lblMoreInformation.setOnAction(_ -> NativeDesktop.openBrowserShowPopup(latestVersion.getChangelogUrl(), + dialogService, externalApplicationsPreferences)); - VBox container = new VBox( - new Label(Localization.lang("A new version of JabRef is available!")), + VBox container = new VBox(new Label(Localization.lang("A new version of JabRef is available!")), new Label(Localization.lang("Latest version: %0", latestVersion.getFullVersion())), new Label(Localization.lang("Installed version: %0", currentVersion.getFullVersion())), - lblMoreInformation - ); + lblMoreInformation); getDialogPane().setContent(container); getDialogPane().setPrefWidth(450); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/help/SearchForUpdateAction.java b/jabgui/src/main/java/org/jabref/gui/help/SearchForUpdateAction.java index 85edd123485..3ea7898d525 100644 --- a/jabgui/src/main/java/org/jabref/gui/help/SearchForUpdateAction.java +++ b/jabgui/src/main/java/org/jabref/gui/help/SearchForUpdateAction.java @@ -11,12 +11,12 @@ public class SearchForUpdateAction extends SimpleCommand { private final GuiPreferences preferences; + private final DialogService dialogService; + private final TaskExecutor taskExecutor; - public SearchForUpdateAction(GuiPreferences preferences, - DialogService dialogService, - TaskExecutor taskExecutor) { + public SearchForUpdateAction(GuiPreferences preferences, DialogService dialogService, TaskExecutor taskExecutor) { this.preferences = preferences; this.dialogService = dialogService; this.taskExecutor = taskExecutor; @@ -25,7 +25,7 @@ public SearchForUpdateAction(GuiPreferences preferences, @Override public void execute() { BuildInfo buildInfo = Injector.instantiateModelOrService(BuildInfo.class); - new VersionWorker(buildInfo.version, dialogService, taskExecutor, preferences) - .checkForNewVersionAsync(); + new VersionWorker(buildInfo.version, dialogService, taskExecutor, preferences).checkForNewVersionAsync(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/help/VersionWorker.java b/jabgui/src/main/java/org/jabref/gui/help/VersionWorker.java index a68781ef4a4..affb1578b91 100644 --- a/jabgui/src/main/java/org/jabref/gui/help/VersionWorker.java +++ b/jabgui/src/main/java/org/jabref/gui/help/VersionWorker.java @@ -19,12 +19,12 @@ import org.slf4j.LoggerFactory; /** - * This worker checks if there is a new version of JabRef available. If there is it will display a dialog to the user - * offering him multiple options to proceed (see changelog, go to the download page, ignore this version, and remind - * later). + * This worker checks if there is a new version of JabRef available. If there is it will + * display a dialog to the user offering him multiple options to proceed (see changelog, + * go to the download page, ignore this version, and remind later). * - * If the versions check is executed manually and this is the latest version it will also display a dialog to inform the - * user. + * If the versions check is executed manually and this is the latest version it will also + * display a dialog to inform the user. */ public class VersionWorker { @@ -36,14 +36,15 @@ public class VersionWorker { private final Version installedVersion; private final DialogService dialogService; + private final TaskExecutor taskExecutor; + private final InternalPreferences internalPreferences; + private final ExternalApplicationsPreferences externalApplicationsPreferences; - public VersionWorker(Version installedVersion, - DialogService dialogService, - TaskExecutor taskExecutor, - GuiPreferences preferences) { + public VersionWorker(Version installedVersion, DialogService dialogService, TaskExecutor taskExecutor, + GuiPreferences preferences) { this.installedVersion = Objects.requireNonNull(installedVersion); this.dialogService = Objects.requireNonNull(dialogService); this.taskExecutor = Objects.requireNonNull(taskExecutor); @@ -52,8 +53,9 @@ public VersionWorker(Version installedVersion, } /** - * Returns a newer version excluding any non-stable versions, except if the installed one is unstable too. If no - * newer version was found, then an empty optional is returned. + * Returns a newer version excluding any non-stable versions, except if the installed + * one is unstable too. If no newer version was found, then an empty optional is + * returned. */ private Optional getNewVersion() throws IOException { List availableVersions = Version.getAllAvailableVersions(); @@ -62,47 +64,54 @@ private Optional getNewVersion() throws IOException { public void checkForNewVersionAsync() { BackgroundTask.wrap(this::getNewVersion) - .onSuccess(version -> showUpdateInfo(version, true)) - .onFailure(exception -> showConnectionError(exception, true)) - .executeWith(taskExecutor); + .onSuccess(version -> showUpdateInfo(version, true)) + .onFailure(exception -> showConnectionError(exception, true)) + .executeWith(taskExecutor); } public void checkForNewVersionDelayed() { BackgroundTask.wrap(this::getNewVersion) - .onSuccess(version -> showUpdateInfo(version, false)) - .onFailure(exception -> showConnectionError(exception, false)) - .scheduleWith(taskExecutor, 30, TimeUnit.SECONDS); + .onSuccess(version -> showUpdateInfo(version, false)) + .onFailure(exception -> showConnectionError(exception, false)) + .scheduleWith(taskExecutor, 30, TimeUnit.SECONDS); } /** - * Prints the connection problem to the status bar and shows a dialog if it was executed manually + * Prints the connection problem to the status bar and shows a dialog if it was + * executed manually */ private void showConnectionError(Exception exception, boolean manualExecution) { if (manualExecution) { String couldNotConnect = Localization.lang("Could not connect to the update server."); String tryLater = Localization.lang("Please try again later and/or check your network connection."); - dialogService.showErrorDialogAndWait(Localization.lang("Error"), couldNotConnect + "\n" + tryLater, exception); + dialogService.showErrorDialogAndWait(Localization.lang("Error"), couldNotConnect + "\n" + tryLater, + exception); } LOGGER.debug("Could not connect to the update server.", exception); } /** - * Prints up-to-date to the status bar (and shows a dialog it was executed manually) if there is now new version. - * Shows a "New Version" Dialog to the user if there is. + * Prints up-to-date to the status bar (and shows a dialog it was executed manually) + * if there is now new version. Shows a "New Version" Dialog to the user if there is. */ private void showUpdateInfo(Optional newerVersion, boolean manualExecution) { - // no new version could be found, only respect the ignored version on automated version checks - if (newerVersion.isEmpty() || (newerVersion.get().equals(internalPreferences.getIgnoredVersion()) && !manualExecution)) { + // no new version could be found, only respect the ignored version on automated + // version checks + if (newerVersion.isEmpty() + || (newerVersion.get().equals(internalPreferences.getIgnoredVersion()) && !manualExecution)) { if (manualExecution) { dialogService.notify(Localization.lang("JabRef is up-to-date.")); } - } else { + } + else { // notify the user about a newer version - if (dialogService.showCustomDialogAndWait( - new NewVersionDialog(installedVersion, newerVersion.get(), dialogService, externalApplicationsPreferences)) - .orElse(true)) { + if (dialogService + .showCustomDialogAndWait(new NewVersionDialog(installedVersion, newerVersion.get(), dialogService, + externalApplicationsPreferences)) + .orElse(true)) { internalPreferences.setIgnoredVersion(newerVersion.get()); } } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/icon/IconTheme.java b/jabgui/src/main/java/org/jabref/gui/icon/IconTheme.java index 4b7f7a254bc..1b3ffc675e0 100644 --- a/jabgui/src/main/java/org/jabref/gui/icon/IconTheme.java +++ b/jabgui/src/main/java/org/jabref/gui/icon/IconTheme.java @@ -56,10 +56,16 @@ public class IconTheme { public static final Color DEFAULT_DISABLED_COLOR = Color.web("#c8c8c8"); + public static final Color SELECTED_COLOR = Color.web("#50618F"); + private static final String DEFAULT_ICON_PATH = "/images/external/red.png"; + private static final Logger LOGGER = LoggerFactory.getLogger(IconTheme.class); - private static final Map KEY_TO_ICON = readIconThemeFile(IconTheme.class.getResource("/images/Icons.properties"), "/images/external/"); + + private static final Map KEY_TO_ICON = readIconThemeFile( + IconTheme.class.getResource("/images/Icons.properties"), "/images/external/"); + private static final Set ICON_NAMES = new HashSet<>(); public static Color getDefaultGroupColor() { @@ -70,8 +76,10 @@ public static Optional findIcon(String code, Color color) { if (ICON_NAMES.isEmpty()) { loadAllIkons(); } - return ICON_NAMES.stream().filter(icon -> icon.toString().equals(code.toUpperCase(Locale.ENGLISH))) - .map(internalMat -> new InternalMaterialDesignIcon(internalMat).withColor(color)).findFirst(); + return ICON_NAMES.stream() + .filter(icon -> icon.toString().equals(code.toUpperCase(Locale.ENGLISH))) + .map(internalMat -> new InternalMaterialDesignIcon(internalMat).withColor(color)) + .findFirst(); } public static Image getJabRefImage() { @@ -87,10 +95,11 @@ private static void loadAllIkons() { } /* - * Constructs an {@link Image} for the image representing the given function, in the resource - * file listing images. + * Constructs an {@link Image} for the image representing the given function, in the + * resource file listing images. * * @param name The name of the icon, such as "open", "save", "saveAs" etc. + * * @return The {@link Image} for the function. */ private static Image getImageFX(String name) { @@ -100,27 +109,26 @@ private static Image getImageFX(String name) { /** * Looks up the URL for the image representing the given function, in the resource * file listing images. - * * @param name The name of the icon, such as "open", "save", "saveAs" etc. * @return The URL to the actual image to use. */ public static URL getIconUrl(String name) { String key = Objects.requireNonNull(name, "icon name"); if (!KEY_TO_ICON.containsKey(key)) { - LOGGER.warn("Could not find icon url by name {}, so falling back on default icon {}", name, DEFAULT_ICON_PATH); + LOGGER.warn("Could not find icon url by name {}, so falling back on default icon {}", name, + DEFAULT_ICON_PATH); } String path = KEY_TO_ICON.getOrDefault(key, DEFAULT_ICON_PATH); return Objects.requireNonNull(IconTheme.class.getResource(path), "Path must not be null for key " + key); } /** - * Read a typical java property url into a Map. Currently doesn't support escaping - * of the '=' character - it simply looks for the first '=' to determine where the key ends. - * Both the key and the value is trimmed for whitespace at the ends. - * - * @param url The URL to read information from. - * @param prefix A String to prefix to all values read. Can represent e.g. the directory where icon files are to be - * found. + * Read a typical java property url into a Map. Currently doesn't support escaping of + * the '=' character - it simply looks for the first '=' to determine where the key + * ends. Both the key and the value is trimmed for whitespace at the ends. + * @param url The URL to read information from. + * @param prefix A String to prefix to all values read. Can represent e.g. the + * directory where icon files are to be found. * @return A Map containing all key-value pairs found. */ // FIXME: prefix can be removed?! @@ -143,7 +151,8 @@ private static Map readIconThemeFile(URL url, String prefix) { String value = prefix + line.substring(index + 1).trim(); result.put(key, value); } - } catch (IOException e) { + } + catch (IOException e) { LOGGER.warn("Unable to read default icon theme.", e); } return result; @@ -164,214 +173,112 @@ public static List getLogoSetFX() { public enum JabRefIcons implements JabRefIcon { - ADD(MaterialDesignP.PLUS_CIRCLE_OUTLINE), - ADD_FILLED(MaterialDesignP.PLUS_CIRCLE), - ADD_NOBOX(MaterialDesignP.PLUS), - ADD_ARTICLE(MaterialDesignP.PLUS), - ADD_ENTRY(MaterialDesignP.PLAYLIST_PLUS), - CASE_SENSITIVE(MaterialDesignA.ALPHABETICAL), - EDIT_ENTRY(MaterialDesignT.TOOLTIP_EDIT), - EDIT_STRINGS(MaterialDesignT.TOOLTIP_TEXT), - FOLDER(MaterialDesignF.FOLDER_OUTLINE), - REMOVE(MaterialDesignM.MINUS_BOX), - REMOVE_NOBOX(MaterialDesignM.MINUS), - FILE(MaterialDesignF.FILE_OUTLINE), - PDF_FILE(MaterialDesignF.FILE_PDF_BOX), - DOI(MaterialDesignB.BARCODE_SCAN), - DUPLICATE(MaterialDesignC.CONTENT_DUPLICATE), - EDIT(MaterialDesignP.PENCIL), - NEW(MaterialDesignF.FOLDER_PLUS), - SAVE(MaterialDesignC.CONTENT_SAVE), - SAVE_ALL(MaterialDesignC.CONTENT_SAVE_ALL), - CLOSE(MaterialDesignC.CLOSE_CIRCLE), - PASTE(JabRefMaterialDesignIcon.PASTE), - CUT(MaterialDesignC.CONTENT_CUT), - COPY(MaterialDesignC.CONTENT_COPY), - COMMENT(MaterialDesignC.COMMENT), - REDO(MaterialDesignR.REDO), - UNDO(MaterialDesignU.UNDO), - MARKER(MaterialDesignM.MARKER), - REFRESH(MaterialDesignR.REFRESH), - MEMORYSTICK(MaterialDesignU.USB_FLASH_DRIVE_OUTLINE), - DELETE_ENTRY(MaterialDesignD.DELETE), - SEARCH(MaterialDesignM.MAGNIFY), - FILE_SEARCH(MaterialDesignF.FILE_FIND), - FILE_STAR(MaterialDesignF.FILE_STAR), - PDF_METADATA_READ(MaterialDesignF.FORMAT_ALIGN_TOP), - PDF_METADATA_WRITE(MaterialDesignF.FORMAT_ALIGN_BOTTOM), - ADVANCED_SEARCH(Color.CYAN, MaterialDesignM.MAGNIFY), - PREFERENCES(MaterialDesignC.COG), - SELECTORS(MaterialDesignS.STAR_SETTINGS), - HELP(MaterialDesignH.HELP_CIRCLE), - UP(MaterialDesignA.ARROW_UP), - DOWN(MaterialDesignA.ARROW_DOWN), - LEFT(MaterialDesignA.ARROW_LEFT_BOLD), - RIGHT(MaterialDesignA.ARROW_RIGHT_BOLD), - SOURCE(MaterialDesignC.CODE_BRACES), - MAKE_KEY(MaterialDesignK.KEY_VARIANT), - CLEANUP_ENTRIES(MaterialDesignB.BROOM), - PRIORITY(MaterialDesignF.FLAG), - PRIORITY_HIGH(Color.RED, MaterialDesignF.FLAG), - PRIORITY_MEDIUM(Color.ORANGE, MaterialDesignF.FLAG), - PRIORITY_LOW(Color.rgb(111, 204, 117), MaterialDesignF.FLAG), - PRINTED(MaterialDesignP.PRINTER), + ADD(MaterialDesignP.PLUS_CIRCLE_OUTLINE), ADD_FILLED(MaterialDesignP.PLUS_CIRCLE), + ADD_NOBOX(MaterialDesignP.PLUS), ADD_ARTICLE(MaterialDesignP.PLUS), ADD_ENTRY(MaterialDesignP.PLAYLIST_PLUS), + CASE_SENSITIVE(MaterialDesignA.ALPHABETICAL), EDIT_ENTRY(MaterialDesignT.TOOLTIP_EDIT), + EDIT_STRINGS(MaterialDesignT.TOOLTIP_TEXT), FOLDER(MaterialDesignF.FOLDER_OUTLINE), + REMOVE(MaterialDesignM.MINUS_BOX), REMOVE_NOBOX(MaterialDesignM.MINUS), FILE(MaterialDesignF.FILE_OUTLINE), + PDF_FILE(MaterialDesignF.FILE_PDF_BOX), DOI(MaterialDesignB.BARCODE_SCAN), + DUPLICATE(MaterialDesignC.CONTENT_DUPLICATE), EDIT(MaterialDesignP.PENCIL), NEW(MaterialDesignF.FOLDER_PLUS), + SAVE(MaterialDesignC.CONTENT_SAVE), SAVE_ALL(MaterialDesignC.CONTENT_SAVE_ALL), + CLOSE(MaterialDesignC.CLOSE_CIRCLE), PASTE(JabRefMaterialDesignIcon.PASTE), CUT(MaterialDesignC.CONTENT_CUT), + COPY(MaterialDesignC.CONTENT_COPY), COMMENT(MaterialDesignC.COMMENT), REDO(MaterialDesignR.REDO), + UNDO(MaterialDesignU.UNDO), MARKER(MaterialDesignM.MARKER), REFRESH(MaterialDesignR.REFRESH), + MEMORYSTICK(MaterialDesignU.USB_FLASH_DRIVE_OUTLINE), DELETE_ENTRY(MaterialDesignD.DELETE), + SEARCH(MaterialDesignM.MAGNIFY), FILE_SEARCH(MaterialDesignF.FILE_FIND), FILE_STAR(MaterialDesignF.FILE_STAR), + PDF_METADATA_READ(MaterialDesignF.FORMAT_ALIGN_TOP), PDF_METADATA_WRITE(MaterialDesignF.FORMAT_ALIGN_BOTTOM), + ADVANCED_SEARCH(Color.CYAN, MaterialDesignM.MAGNIFY), PREFERENCES(MaterialDesignC.COG), + SELECTORS(MaterialDesignS.STAR_SETTINGS), HELP(MaterialDesignH.HELP_CIRCLE), UP(MaterialDesignA.ARROW_UP), + DOWN(MaterialDesignA.ARROW_DOWN), LEFT(MaterialDesignA.ARROW_LEFT_BOLD), + RIGHT(MaterialDesignA.ARROW_RIGHT_BOLD), SOURCE(MaterialDesignC.CODE_BRACES), + MAKE_KEY(MaterialDesignK.KEY_VARIANT), CLEANUP_ENTRIES(MaterialDesignB.BROOM), PRIORITY(MaterialDesignF.FLAG), + PRIORITY_HIGH(Color.RED, MaterialDesignF.FLAG), PRIORITY_MEDIUM(Color.ORANGE, MaterialDesignF.FLAG), + PRIORITY_LOW(Color.rgb(111, 204, 117), MaterialDesignF.FLAG), PRINTED(MaterialDesignP.PRINTER), RANKING(MaterialDesignS.STAR), - RANK1(MaterialDesignS.STAR, MaterialDesignS.STAR_OUTLINE, MaterialDesignS.STAR_OUTLINE, MaterialDesignS.STAR_OUTLINE, MaterialDesignS.STAR_OUTLINE), - RANK2(MaterialDesignS.STAR, MaterialDesignS.STAR, MaterialDesignS.STAR_OUTLINE, MaterialDesignS.STAR_OUTLINE, MaterialDesignS.STAR_OUTLINE), - RANK3(MaterialDesignS.STAR, MaterialDesignS.STAR, MaterialDesignS.STAR, MaterialDesignS.STAR_OUTLINE, MaterialDesignS.STAR_OUTLINE), - RANK4(MaterialDesignS.STAR, MaterialDesignS.STAR, MaterialDesignS.STAR, MaterialDesignS.STAR, MaterialDesignS.STAR_OUTLINE), - RANK5(MaterialDesignS.STAR, MaterialDesignS.STAR, MaterialDesignS.STAR, MaterialDesignS.STAR, MaterialDesignS.STAR), - WWW(MaterialDesignW.WEB), - GROUP_INCLUDING(MaterialDesignF.FILTER_OUTLINE), - GROUP_REFINING(MaterialDesignF.FILTER), - AUTO_GROUP(MaterialDesignA.AUTO_FIX), - GROUP_INTERSECTION(JabRefMaterialDesignIcon.SET_CENTER), - GROUP_UNION(JabRefMaterialDesignIcon.SET_ALL), - EMAIL(MaterialDesignE.EMAIL), - EXPORT_TO_CLIPBOARD(MaterialDesignC.CLIPBOARD_ARROW_LEFT), - ATTACH_FILE(MaterialDesignP.PAPERCLIP), - AUTO_FILE_LINK(MaterialDesignF.FILE_FIND), - AUTO_RENAME(MaterialDesignA.AUTO_FIX), - DOWNLOAD_FILE(MaterialDesignD.DOWNLOAD), - MOVE_TO_FOLDER(MaterialDesignF.FILE_SEND), - COPY_TO_FOLDER(MaterialDesignC.CONTENT_COPY), - RENAME(MaterialDesignR.RENAME_BOX), - DELETE_FILE(MaterialDesignD.DELETE_FOREVER), - REMOVE_LINK(MaterialDesignL.LINK_OFF), - AUTO_LINKED_FILE(MaterialDesignL.LINK_PLUS), - QUALITY_ASSURED(MaterialDesignC.CERTIFICATE), - QUALITY(MaterialDesignC.CERTIFICATE), - OPEN(MaterialDesignF.FOLDER_OUTLINE), - OPEN_LIST(MaterialDesignF.FOLDER_OPEN_OUTLINE), - ADD_ROW(MaterialDesignS.SERVER_PLUS), - REMOVE_ROW(MaterialDesignS.SERVER_MINUS), - PICTURE(MaterialDesignF.FILE_IMAGE), - READ_STATUS_READ(Color.rgb(111, 204, 117, 1), MaterialDesignE.EYE), - READ_STATUS_SKIMMED(Color.ORANGE, MaterialDesignE.EYE), - READ_STATUS(MaterialDesignE.EYE), - RELEVANCE(MaterialDesignS.STAR_CIRCLE), - MERGE_ENTRIES(MaterialDesignC.COMPARE), + RANK1(MaterialDesignS.STAR, MaterialDesignS.STAR_OUTLINE, MaterialDesignS.STAR_OUTLINE, + MaterialDesignS.STAR_OUTLINE, MaterialDesignS.STAR_OUTLINE), + RANK2(MaterialDesignS.STAR, MaterialDesignS.STAR, MaterialDesignS.STAR_OUTLINE, MaterialDesignS.STAR_OUTLINE, + MaterialDesignS.STAR_OUTLINE), + RANK3(MaterialDesignS.STAR, MaterialDesignS.STAR, MaterialDesignS.STAR, MaterialDesignS.STAR_OUTLINE, + MaterialDesignS.STAR_OUTLINE), + RANK4(MaterialDesignS.STAR, MaterialDesignS.STAR, MaterialDesignS.STAR, MaterialDesignS.STAR, + MaterialDesignS.STAR_OUTLINE), + RANK5(MaterialDesignS.STAR, MaterialDesignS.STAR, MaterialDesignS.STAR, MaterialDesignS.STAR, + MaterialDesignS.STAR), + WWW(MaterialDesignW.WEB), GROUP_INCLUDING(MaterialDesignF.FILTER_OUTLINE), + GROUP_REFINING(MaterialDesignF.FILTER), AUTO_GROUP(MaterialDesignA.AUTO_FIX), + GROUP_INTERSECTION(JabRefMaterialDesignIcon.SET_CENTER), GROUP_UNION(JabRefMaterialDesignIcon.SET_ALL), + EMAIL(MaterialDesignE.EMAIL), EXPORT_TO_CLIPBOARD(MaterialDesignC.CLIPBOARD_ARROW_LEFT), + ATTACH_FILE(MaterialDesignP.PAPERCLIP), AUTO_FILE_LINK(MaterialDesignF.FILE_FIND), + AUTO_RENAME(MaterialDesignA.AUTO_FIX), DOWNLOAD_FILE(MaterialDesignD.DOWNLOAD), + MOVE_TO_FOLDER(MaterialDesignF.FILE_SEND), COPY_TO_FOLDER(MaterialDesignC.CONTENT_COPY), + RENAME(MaterialDesignR.RENAME_BOX), DELETE_FILE(MaterialDesignD.DELETE_FOREVER), + REMOVE_LINK(MaterialDesignL.LINK_OFF), AUTO_LINKED_FILE(MaterialDesignL.LINK_PLUS), + QUALITY_ASSURED(MaterialDesignC.CERTIFICATE), QUALITY(MaterialDesignC.CERTIFICATE), + OPEN(MaterialDesignF.FOLDER_OUTLINE), OPEN_LIST(MaterialDesignF.FOLDER_OPEN_OUTLINE), + ADD_ROW(MaterialDesignS.SERVER_PLUS), REMOVE_ROW(MaterialDesignS.SERVER_MINUS), + PICTURE(MaterialDesignF.FILE_IMAGE), READ_STATUS_READ(Color.rgb(111, 204, 117, 1), MaterialDesignE.EYE), + READ_STATUS_SKIMMED(Color.ORANGE, MaterialDesignE.EYE), READ_STATUS(MaterialDesignE.EYE), + RELEVANCE(MaterialDesignS.STAR_CIRCLE), MERGE_ENTRIES(MaterialDesignC.COMPARE), CONNECT_OPEN_OFFICE(MaterialDesignO.OPEN_IN_APP), PLAIN_TEXT_IMPORT_TODO(MaterialDesignC.CHECKBOX_BLANK_CIRCLE_OUTLINE), - PLAIN_TEXT_IMPORT_DONE(MaterialDesignC.CHECKBOX_MARKED_CIRCLE_OUTLINE), - DONATE(MaterialDesignG.GIFT), - MOVE_TAB_ARROW(MaterialDesignA.ARROW_UP_BOLD), - OPTIONAL(MaterialDesignL.LABEL_OUTLINE), - REQUIRED(MaterialDesignL.LABEL), - INTEGRITY_FAIL(Color.RED, MaterialDesignC.CLOSE_CIRCLE), - INTEGRITY_INFO(MaterialDesignI.INFORMATION), - INTEGRITY_WARN(MaterialDesignA.ALERT_CIRCLE), - INTEGRITY_SUCCESS(MaterialDesignC.CHECKBOX_MARKED_CIRCLE_OUTLINE), - GITHUB(MaterialDesignG.GITHUB), - TOGGLE_ENTRY_PREVIEW(MaterialDesignL.LIBRARY), - TOGGLE_GROUPS(MaterialDesignV.VIEW_LIST), - SHOW_PREFERENCES_LIST(MaterialDesignV.VIEW_LIST), - WRITE_XMP(MaterialDesignI.IMPORT), - FILE_WORD(MaterialDesignF.FILE_WORD), - FILE_EXCEL(MaterialDesignF.FILE_EXCEL), - FILE_POWERPOINT(MaterialDesignF.FILE_POWERPOINT), - FILE_TEXT(MaterialDesignF.FILE_DOCUMENT), - FILE_MULTIPLE(MaterialDesignF.FILE_MULTIPLE), - FILE_OPENOFFICE(JabRefMaterialDesignIcon.OPEN_OFFICE), - APPLICATION_GENERIC(MaterialDesignA.APPLICATION), - APPLICATION_EMACS(JabRefMaterialDesignIcon.EMACS), - APPLICATION_LYX(JabRefMaterialDesignIcon.LYX), - APPLICATION_TEXSTUDIO(JabRefMaterialDesignIcon.TEX_STUDIO), - APPLICATION_TEXMAKER(JabRefMaterialDesignIcon.TEX_MAKER), - APPLICATION_VIM(JabRefMaterialDesignIcon.VIM), + PLAIN_TEXT_IMPORT_DONE(MaterialDesignC.CHECKBOX_MARKED_CIRCLE_OUTLINE), DONATE(MaterialDesignG.GIFT), + MOVE_TAB_ARROW(MaterialDesignA.ARROW_UP_BOLD), OPTIONAL(MaterialDesignL.LABEL_OUTLINE), + REQUIRED(MaterialDesignL.LABEL), INTEGRITY_FAIL(Color.RED, MaterialDesignC.CLOSE_CIRCLE), + INTEGRITY_INFO(MaterialDesignI.INFORMATION), INTEGRITY_WARN(MaterialDesignA.ALERT_CIRCLE), + INTEGRITY_SUCCESS(MaterialDesignC.CHECKBOX_MARKED_CIRCLE_OUTLINE), GITHUB(MaterialDesignG.GITHUB), + TOGGLE_ENTRY_PREVIEW(MaterialDesignL.LIBRARY), TOGGLE_GROUPS(MaterialDesignV.VIEW_LIST), + SHOW_PREFERENCES_LIST(MaterialDesignV.VIEW_LIST), WRITE_XMP(MaterialDesignI.IMPORT), + FILE_WORD(MaterialDesignF.FILE_WORD), FILE_EXCEL(MaterialDesignF.FILE_EXCEL), + FILE_POWERPOINT(MaterialDesignF.FILE_POWERPOINT), FILE_TEXT(MaterialDesignF.FILE_DOCUMENT), + FILE_MULTIPLE(MaterialDesignF.FILE_MULTIPLE), FILE_OPENOFFICE(JabRefMaterialDesignIcon.OPEN_OFFICE), + APPLICATION_GENERIC(MaterialDesignA.APPLICATION), APPLICATION_EMACS(JabRefMaterialDesignIcon.EMACS), + APPLICATION_LYX(JabRefMaterialDesignIcon.LYX), APPLICATION_TEXSTUDIO(JabRefMaterialDesignIcon.TEX_STUDIO), + APPLICATION_TEXMAKER(JabRefMaterialDesignIcon.TEX_MAKER), APPLICATION_VIM(JabRefMaterialDesignIcon.VIM), APPLICATION_WINEDT(JabRefMaterialDesignIcon.WINEDT), APPLICATION_SUBLIMETEXT(JabRefMaterialDesignIcon.SUBLIME_TEXT), - APPLICATION_TEXSHOP(JabRefMaterialDesignIcon.TEXSHOP), - APPLICATION_TEXWORS(JabRefMaterialDesignIcon.TEXWORKS), - APPLICATION_VSCODE(JabRefMaterialDesignIcon.VSCODE), - KEY_BINDINGS(MaterialDesignK.KEYBOARD), - FIND_DUPLICATES(MaterialDesignC.CODE_EQUAL), - CONNECT_DB(MaterialDesignC.CLOUD_UPLOAD), - SUCCESS(MaterialDesignC.CHECK_CIRCLE), - CHECK(MaterialDesignC.CHECK), - WARNING(MaterialDesignA.ALERT), - ERROR(MaterialDesignA.ALERT_CIRCLE), - REG_EX(MaterialDesignR.REGEX), - FULLTEXT(MaterialDesignF.FILE_EYE), - FILTER(MaterialDesignF.FILTER), - INVERT(MaterialDesignI.INVERT_COLORS), - CONSOLE(MaterialDesignC.CONSOLE), - FORUM(MaterialDesignF.FORUM), - FACEBOOK(MaterialDesignF.FACEBOOK), - MASTODON(MaterialDesignM.MASTODON), - LINKEDIN(MaterialDesignL.LINKEDIN), - TWITTER(MaterialDesignT.TWITTER), - BLOG(MaterialDesignR.RSS), - DATE_PICKER(MaterialDesignC.CALENDAR), - DEFAULT_GROUP_ICON_COLORED(MaterialDesignR.RECORD), - DEFAULT_GROUP_ICON(MaterialDesignF.FILE_TREE), - DEFAULT_GROUP_ICON_COLUMN(MaterialDesignL.LABEL_OUTLINE), - ALL_ENTRIES_GROUP_ICON(MaterialDesignD.DATABASE), - IMPORT(MaterialDesignC.CALL_RECEIVED), - EXPORT(MaterialDesignC.CALL_MADE), - PREVIOUS_LEFT(MaterialDesignC.CHEVRON_LEFT), - PREVIOUS_UP(MaterialDesignC.CHEVRON_UP), - NEXT_RIGHT(MaterialDesignC.CHEVRON_RIGHT), - NEXT_DOWN(MaterialDesignC.CHEVRON_DOWN), - LIST_MOVE_LEFT(MaterialDesignC.CHEVRON_LEFT), - LIST_MOVE_UP(MaterialDesignC.CHEVRON_UP), - LIST_MOVE_RIGHT(MaterialDesignC.CHEVRON_RIGHT), - LIST_MOVE_DOWN(MaterialDesignC.CHEVRON_DOWN), - FIT_WIDTH(MaterialDesignA.ARROW_EXPAND_ALL), - FIT_SINGLE_PAGE(MaterialDesignN.NOTE), - ZOOM_OUT(MaterialDesignM.MAGNIFY_MINUS), - ZOOM_IN(MaterialDesignM.MAGNIFY_PLUS), - ENTRY_TYPE(MaterialDesignP.PENCIL), - NEW_GROUP(MaterialDesignP.PLUS), - OPEN_LINK(MaterialDesignO.OPEN_IN_NEW), - LOOKUP_IDENTIFIER(MaterialDesignS.SEARCH_WEB), - ADD_ENTRY_IMMEDIATE(MaterialDesignP.PLUS), - ADD_ENTRY_IDENTIFIER(MaterialDesignC.CALL_RECEIVED), - ADD_ENTRY_PLAINTEXT(MaterialDesignP.PLUS_BOX), - LINKED_FILE_ADD(MaterialDesignP.PLUS), - FETCH_FULLTEXT(MaterialDesignS.SEARCH_WEB), - FETCH_BY_IDENTIFIER(MaterialDesignC.CLIPBOARD_ARROW_DOWN), + APPLICATION_TEXSHOP(JabRefMaterialDesignIcon.TEXSHOP), APPLICATION_TEXWORS(JabRefMaterialDesignIcon.TEXWORKS), + APPLICATION_VSCODE(JabRefMaterialDesignIcon.VSCODE), KEY_BINDINGS(MaterialDesignK.KEYBOARD), + FIND_DUPLICATES(MaterialDesignC.CODE_EQUAL), CONNECT_DB(MaterialDesignC.CLOUD_UPLOAD), + SUCCESS(MaterialDesignC.CHECK_CIRCLE), CHECK(MaterialDesignC.CHECK), WARNING(MaterialDesignA.ALERT), + ERROR(MaterialDesignA.ALERT_CIRCLE), REG_EX(MaterialDesignR.REGEX), FULLTEXT(MaterialDesignF.FILE_EYE), + FILTER(MaterialDesignF.FILTER), INVERT(MaterialDesignI.INVERT_COLORS), CONSOLE(MaterialDesignC.CONSOLE), + FORUM(MaterialDesignF.FORUM), FACEBOOK(MaterialDesignF.FACEBOOK), MASTODON(MaterialDesignM.MASTODON), + LINKEDIN(MaterialDesignL.LINKEDIN), TWITTER(MaterialDesignT.TWITTER), BLOG(MaterialDesignR.RSS), + DATE_PICKER(MaterialDesignC.CALENDAR), DEFAULT_GROUP_ICON_COLORED(MaterialDesignR.RECORD), + DEFAULT_GROUP_ICON(MaterialDesignF.FILE_TREE), DEFAULT_GROUP_ICON_COLUMN(MaterialDesignL.LABEL_OUTLINE), + ALL_ENTRIES_GROUP_ICON(MaterialDesignD.DATABASE), IMPORT(MaterialDesignC.CALL_RECEIVED), + EXPORT(MaterialDesignC.CALL_MADE), PREVIOUS_LEFT(MaterialDesignC.CHEVRON_LEFT), + PREVIOUS_UP(MaterialDesignC.CHEVRON_UP), NEXT_RIGHT(MaterialDesignC.CHEVRON_RIGHT), + NEXT_DOWN(MaterialDesignC.CHEVRON_DOWN), LIST_MOVE_LEFT(MaterialDesignC.CHEVRON_LEFT), + LIST_MOVE_UP(MaterialDesignC.CHEVRON_UP), LIST_MOVE_RIGHT(MaterialDesignC.CHEVRON_RIGHT), + LIST_MOVE_DOWN(MaterialDesignC.CHEVRON_DOWN), FIT_WIDTH(MaterialDesignA.ARROW_EXPAND_ALL), + FIT_SINGLE_PAGE(MaterialDesignN.NOTE), ZOOM_OUT(MaterialDesignM.MAGNIFY_MINUS), + ZOOM_IN(MaterialDesignM.MAGNIFY_PLUS), ENTRY_TYPE(MaterialDesignP.PENCIL), NEW_GROUP(MaterialDesignP.PLUS), + OPEN_LINK(MaterialDesignO.OPEN_IN_NEW), LOOKUP_IDENTIFIER(MaterialDesignS.SEARCH_WEB), + ADD_ENTRY_IMMEDIATE(MaterialDesignP.PLUS), ADD_ENTRY_IDENTIFIER(MaterialDesignC.CALL_RECEIVED), + ADD_ENTRY_PLAINTEXT(MaterialDesignP.PLUS_BOX), LINKED_FILE_ADD(MaterialDesignP.PLUS), + FETCH_FULLTEXT(MaterialDesignS.SEARCH_WEB), FETCH_BY_IDENTIFIER(MaterialDesignC.CLIPBOARD_ARROW_DOWN), TOGGLE_ABBREVIATION(MaterialDesignF.FORMAT_ALIGN_CENTER), - VIEW_JOURNAL_INFO(MaterialDesignI.INFORMATION_VARIANT), - NEW_FILE(MaterialDesignP.PLUS), - DOWNLOAD(MaterialDesignD.DOWNLOAD), - OWNER(MaterialDesignA.ACCOUNT), - CLOSE_JABREF(MaterialDesignD.DOOR), - ARTICLE(MaterialDesignF.FILE_DOCUMENT), - BOOK(MaterialDesignB.BOOK_OPEN_PAGE_VARIANT), - LATEX_CITATIONS(JabRefMaterialDesignIcon.TEX_STUDIO), - LATEX_FILE_DIRECTORY(MaterialDesignF.FOLDER_OUTLINE), - LATEX_FILE(MaterialDesignF.FILE_OUTLINE), - LATEX_COMMENT(MaterialDesignC.COMMENT_TEXT_OUTLINE), - LATEX_LINE(MaterialDesignF.FORMAT_LINE_SPACING), - PASSWORD_REVEALED(MaterialDesignE.EYE), - ADD_ABBREVIATION_LIST(MaterialDesignP.PLUS), - OPEN_ABBREVIATION_LIST(MaterialDesignF.FOLDER_OUTLINE), - REMOVE_ABBREVIATION_LIST(MaterialDesignM.MINUS), - ADD_ABBREVIATION(MaterialDesignP.PLAYLIST_PLUS), - REMOVE_ABBREVIATION(MaterialDesignP.PLAYLIST_MINUS), - REMOTE_DATABASE(MaterialDesignD.DATABASE), - HOME(MaterialDesignH.HOME), - LINK(MaterialDesignL.LINK), - LINK_VARIANT(MaterialDesignL.LINK_VARIANT), - PROTECT_STRING(MaterialDesignC.CODE_BRACES), - SELECT_ICONS(MaterialDesignA.APPS), - KEEP_SEARCH_STRING(MaterialDesignE.EARTH), - KEEP_ON_TOP(MaterialDesignP.PIN), - KEEP_ON_TOP_OFF(MaterialDesignP.PIN_OFF), - OPEN_GLOBAL_SEARCH(MaterialDesignO.OPEN_IN_NEW), - REMOVE_TAGS(MaterialDesignC.CLOSE), - ACCEPT_LEFT(MaterialDesignS.SUBDIRECTORY_ARROW_LEFT), - ACCEPT_RIGHT(MaterialDesignS.SUBDIRECTORY_ARROW_RIGHT), - MERGE_GROUPS(MaterialDesignS.SOURCE_MERGE), - ADD_OR_MAKE_BIBLIOGRAPHY(JabRefMaterialDesignIcon.BIBLIOGRAPHY), - CONSISTENCY_UNSET_FIELD(MaterialDesignM.MINUS), - CONSISTENCY_REQUIRED_FIELD(MaterialDesignC.CLOSE), - CONSISTENCY_OPTIONAL_FIELD(MaterialDesignC.CIRCLE_OUTLINE), - CONSISTENCY_UNKNOWN_FIELD(MaterialDesignH.HELP), - ABSOLUTE_PATH(MaterialDesignF.FAMILY_TREE), - GIT_SYNC(MaterialDesignG.GIT), - RELATIVE_PATH(MaterialDesignF.FILE_TREE_OUTLINE), + VIEW_JOURNAL_INFO(MaterialDesignI.INFORMATION_VARIANT), NEW_FILE(MaterialDesignP.PLUS), + DOWNLOAD(MaterialDesignD.DOWNLOAD), OWNER(MaterialDesignA.ACCOUNT), CLOSE_JABREF(MaterialDesignD.DOOR), + ARTICLE(MaterialDesignF.FILE_DOCUMENT), BOOK(MaterialDesignB.BOOK_OPEN_PAGE_VARIANT), + LATEX_CITATIONS(JabRefMaterialDesignIcon.TEX_STUDIO), LATEX_FILE_DIRECTORY(MaterialDesignF.FOLDER_OUTLINE), + LATEX_FILE(MaterialDesignF.FILE_OUTLINE), LATEX_COMMENT(MaterialDesignC.COMMENT_TEXT_OUTLINE), + LATEX_LINE(MaterialDesignF.FORMAT_LINE_SPACING), PASSWORD_REVEALED(MaterialDesignE.EYE), + ADD_ABBREVIATION_LIST(MaterialDesignP.PLUS), OPEN_ABBREVIATION_LIST(MaterialDesignF.FOLDER_OUTLINE), + REMOVE_ABBREVIATION_LIST(MaterialDesignM.MINUS), ADD_ABBREVIATION(MaterialDesignP.PLAYLIST_PLUS), + REMOVE_ABBREVIATION(MaterialDesignP.PLAYLIST_MINUS), REMOTE_DATABASE(MaterialDesignD.DATABASE), + HOME(MaterialDesignH.HOME), LINK(MaterialDesignL.LINK), LINK_VARIANT(MaterialDesignL.LINK_VARIANT), + PROTECT_STRING(MaterialDesignC.CODE_BRACES), SELECT_ICONS(MaterialDesignA.APPS), + KEEP_SEARCH_STRING(MaterialDesignE.EARTH), KEEP_ON_TOP(MaterialDesignP.PIN), + KEEP_ON_TOP_OFF(MaterialDesignP.PIN_OFF), OPEN_GLOBAL_SEARCH(MaterialDesignO.OPEN_IN_NEW), + REMOVE_TAGS(MaterialDesignC.CLOSE), ACCEPT_LEFT(MaterialDesignS.SUBDIRECTORY_ARROW_LEFT), + ACCEPT_RIGHT(MaterialDesignS.SUBDIRECTORY_ARROW_RIGHT), MERGE_GROUPS(MaterialDesignS.SOURCE_MERGE), + ADD_OR_MAKE_BIBLIOGRAPHY(JabRefMaterialDesignIcon.BIBLIOGRAPHY), CONSISTENCY_UNSET_FIELD(MaterialDesignM.MINUS), + CONSISTENCY_REQUIRED_FIELD(MaterialDesignC.CLOSE), CONSISTENCY_OPTIONAL_FIELD(MaterialDesignC.CIRCLE_OUTLINE), + CONSISTENCY_UNKNOWN_FIELD(MaterialDesignH.HELP), ABSOLUTE_PATH(MaterialDesignF.FAMILY_TREE), + GIT_SYNC(MaterialDesignG.GIT), RELATIVE_PATH(MaterialDesignF.FILE_TREE_OUTLINE), SHORTEN_DOI(MaterialDesignA.ARROW_COLLAPSE_HORIZONTAL); private final JabRefIcon icon; @@ -417,5 +324,7 @@ public JabRefIcon withColor(Color color) { public JabRefIcon disabled() { return icon.disabled(); } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/icon/InternalMaterialDesignIcon.java b/jabgui/src/main/java/org/jabref/gui/icon/InternalMaterialDesignIcon.java index 3205426df65..d123953c516 100644 --- a/jabgui/src/main/java/org/jabref/gui/icon/InternalMaterialDesignIcon.java +++ b/jabgui/src/main/java/org/jabref/gui/icon/InternalMaterialDesignIcon.java @@ -16,7 +16,9 @@ public class InternalMaterialDesignIcon implements JabRefIcon { private final List icons; + private Optional color; + private final String unicode; public InternalMaterialDesignIcon(Color color, Ikon... icons) { @@ -45,9 +47,9 @@ public Node getGraphicNode() { fontIcon.getStyleClass().add("glyph-icon"); // Override the default color from the css files - color.ifPresent(color -> fontIcon.setStyle(fontIcon.getStyle() + - "-fx-fill: %s;".formatted(ColorUtil.toRGBCode(color)) + - "-fx-icon-color: %s;".formatted(ColorUtil.toRGBCode(color)))); + color.ifPresent( + color -> fontIcon.setStyle(fontIcon.getStyle() + "-fx-fill: %s;".formatted(ColorUtil.toRGBCode(color)) + + "-fx-icon-color: %s;".formatted(ColorUtil.toRGBCode(color)))); return fontIcon; } @@ -75,4 +77,5 @@ public String getCode() { public Ikon getIkon() { return icons.getFirst(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/icon/JabRefIcon.java b/jabgui/src/main/java/org/jabref/gui/icon/JabRefIcon.java index c7d37eb2d53..4acb865c248 100644 --- a/jabgui/src/main/java/org/jabref/gui/icon/JabRefIcon.java +++ b/jabgui/src/main/java/org/jabref/gui/icon/JabRefIcon.java @@ -16,4 +16,5 @@ public interface JabRefIcon { JabRefIcon disabled(); Ikon getIkon(); + } diff --git a/jabgui/src/main/java/org/jabref/gui/icon/JabRefIconView.java b/jabgui/src/main/java/org/jabref/gui/icon/JabRefIconView.java index ed0f4e54b29..3690a7bf680 100644 --- a/jabgui/src/main/java/org/jabref/gui/icon/JabRefIconView.java +++ b/jabgui/src/main/java/org/jabref/gui/icon/JabRefIconView.java @@ -13,10 +13,12 @@ public class JabRefIconView extends FontIcon { /** - * This property is only needed to get proper IDE support in FXML files - * (e.g. validation that parameter passed to "icon" is indeed of type {@link IconTheme.JabRefIcons}). + * This property is only needed to get proper IDE support in FXML files (e.g. + * validation that parameter passed to "icon" is indeed of type + * {@link IconTheme.JabRefIcons}). */ private final ObjectProperty glyph; + private final ObjectProperty glyphSize; public JabRefIconView(JabRefIcons icon, int size) { @@ -71,4 +73,5 @@ public ObjectProperty glyphSizeProperty() { public Number getGlyphSize() { return glyphSize.getValue(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/icon/JabRefIkonHandler.java b/jabgui/src/main/java/org/jabref/gui/icon/JabRefIkonHandler.java index b09703d9189..cb1a2549f72 100644 --- a/jabgui/src/main/java/org/jabref/gui/icon/JabRefIkonHandler.java +++ b/jabgui/src/main/java/org/jabref/gui/icon/JabRefIkonHandler.java @@ -37,4 +37,5 @@ public InputStream getFontResourceAsStream() { public String getFontFamily() { return "JabRefMaterialDesign"; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/icon/JabRefMaterialDesignIcon.java b/jabgui/src/main/java/org/jabref/gui/icon/JabRefMaterialDesignIcon.java index f0d26d1e39b..021a6b45c39 100644 --- a/jabgui/src/main/java/org/jabref/gui/icon/JabRefMaterialDesignIcon.java +++ b/jabgui/src/main/java/org/jabref/gui/icon/JabRefMaterialDesignIcon.java @@ -3,39 +3,32 @@ import org.kordamp.ikonli.Ikon; /** - * Provides the same true-type font interface as MaterialDesignIcon itself, but uses a font we created ourselves that - * contains icons that are not available in MaterialDesignIcons. + * Provides the same true-type font interface as MaterialDesignIcon itself, but uses a + * font we created ourselves that contains icons that are not available in + * MaterialDesignIcons. *

    - * The glyphs of the ttf (speak: the icons) were created with Illustrator and a template from the material design icons - * web-page. The art boards for each icon was exported as SVG and then converted with - * IcoMoon. The final TTF font is located in the resource folder. + * The glyphs of the ttf (speak: the icons) were created with Illustrator and a template + * from the material design icons web-page. The art boards for each icon was exported as + * SVG and then converted with IcoMoon. The final + * TTF font is located in the resource folder. * - * @see Tutorial on our Wiki - * @see Material Design Icon custom page + * @see Tutorial on our + * Wiki + * @see Material Design Icon custom + * page */ public enum JabRefMaterialDesignIcon implements Ikon { - TEX_STUDIO("jab-texstudio", '\ue900'), - TEX_MAKER("jab-textmaker", '\ue901'), - EMACS("jab-emacs", '\ue902'), - OPEN_OFFICE("jab-oo", '\ue903'), - VIM("jab-vim", '\ue904'), - VIM2("jab-vim2", '\ue905'), - LYX("jab-lyx", '\ue906'), - WINEDT("jab-winedt", '\ue907'), - ARXIV("jab-arxiv", '\ue908'), - COPY("jab-copy", '\ue909'), - PASTE("jab-paste", '\ue90a'), - SET_CENTER("jab-setcenter", '\ue90b'), - SET_ALL("jab-setall", '\ue90c'), - VSCODE("jab-vsvode", '\ue90d'), - CANCEL("jab-cancel", '\ue90e'), - SUBLIME_TEXT("jab-sublime-text", '\ue90f'), - TEXSHOP("jab-texshop", '\ue910'), - TEXWORKS("jab-texworks", '\ue911'), - BIBLIOGRAPHY("jab-bibliography", '\ue912'); + TEX_STUDIO("jab-texstudio", '\ue900'), TEX_MAKER("jab-textmaker", '\ue901'), EMACS("jab-emacs", '\ue902'), + OPEN_OFFICE("jab-oo", '\ue903'), VIM("jab-vim", '\ue904'), VIM2("jab-vim2", '\ue905'), LYX("jab-lyx", '\ue906'), + WINEDT("jab-winedt", '\ue907'), ARXIV("jab-arxiv", '\ue908'), COPY("jab-copy", '\ue909'), + PASTE("jab-paste", '\ue90a'), SET_CENTER("jab-setcenter", '\ue90b'), SET_ALL("jab-setall", '\ue90c'), + VSCODE("jab-vsvode", '\ue90d'), CANCEL("jab-cancel", '\ue90e'), SUBLIME_TEXT("jab-sublime-text", '\ue90f'), + TEXSHOP("jab-texshop", '\ue910'), TEXWORKS("jab-texworks", '\ue911'), BIBLIOGRAPHY("jab-bibliography", '\ue912'); private final String description; + private final int code; JabRefMaterialDesignIcon(String description, int code) { @@ -61,4 +54,5 @@ public String getDescription() { public int getCode() { return code; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/icon/JabrefIconProvider.java b/jabgui/src/main/java/org/jabref/gui/icon/JabrefIconProvider.java index f794bbccd47..b6db672a821 100644 --- a/jabgui/src/main/java/org/jabref/gui/icon/JabrefIconProvider.java +++ b/jabgui/src/main/java/org/jabref/gui/icon/JabrefIconProvider.java @@ -8,4 +8,5 @@ public class JabrefIconProvider implements IkonProvider getIkon() { return JabRefMaterialDesignIcon.class; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/BibEntryTypePrefsAndFileViewModel.java b/jabgui/src/main/java/org/jabref/gui/importer/BibEntryTypePrefsAndFileViewModel.java index 27735d96c4a..ab88fc0251b 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/BibEntryTypePrefsAndFileViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/BibEntryTypePrefsAndFileViewModel.java @@ -4,9 +4,11 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntryType; -public record BibEntryTypePrefsAndFileViewModel(BibEntryType customTypeFromPreferences, BibEntryType customTypeFromFile) { +public record BibEntryTypePrefsAndFileViewModel(BibEntryType customTypeFromPreferences, + BibEntryType customTypeFromFile) { /** - * Used to render in the UI. This is different from {@link BibEntryType#toString()}, because this is the serialization the user expects + * Used to render in the UI. This is different from {@link BibEntryType#toString()}, + * because this is the serialization the user expects */ @Override public String toString() { @@ -15,4 +17,3 @@ public String toString() { MetaDataSerializer.serializeCustomEntryTypes(customTypeFromPreferences)); } } - diff --git a/jabgui/src/main/java/org/jabref/gui/importer/GrobidUseDialogHelper.java b/jabgui/src/main/java/org/jabref/gui/importer/GrobidUseDialogHelper.java index 352f37b7a85..fe23be530e6 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/GrobidUseDialogHelper.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/GrobidUseDialogHelper.java @@ -5,18 +5,18 @@ import org.jabref.logic.l10n.Localization; /** - * Metadata extraction from PDFs and plaintext works very well using Grobid, but we do not want to enable it by default - * due to data privacy concerns. - * To make users aware of the feature, we ask before querying for the first time Grobid, saving the users' preference + * Metadata extraction from PDFs and plaintext works very well using Grobid, but we do not + * want to enable it by default due to data privacy concerns. To make users aware of the + * feature, we ask before querying for the first time Grobid, saving the users' preference */ public class GrobidUseDialogHelper { /** - * If the user has not explicitly opted-in/out of Grobid, we ask for permission to send data to Grobid by using - * a dialog. The users' preference is saved. - * + * If the user has not explicitly opted-in/out of Grobid, we ask for permission to + * send data to Grobid by using a dialog. The users' preference is saved. * @param dialogService the DialogService to use - * @return if the user enabled Grobid, either in the past or after being asked by the dialog. + * @return if the user enabled Grobid, either in the past or after being asked by the + * dialog. */ public static boolean showAndWaitIfUserIsUndecided(DialogService dialogService, GrobidPreferences preferences) { if (preferences.isGrobidUseAsked()) { @@ -26,13 +26,13 @@ public static boolean showAndWaitIfUserIsUndecided(DialogService dialogService, preferences.setGrobidUseAsked(true); return true; } - boolean grobidEnabled = dialogService.showConfirmationDialogAndWait( - Localization.lang("Remote services"), - Localization.lang("Allow sending PDF files and raw citation strings to a JabRef online service (Grobid) to determine Metadata. This produces better results."), - Localization.lang("Send to Grobid"), - Localization.lang("Do not send")); + boolean grobidEnabled = dialogService + .showConfirmationDialogAndWait(Localization.lang("Remote services"), Localization.lang( + "Allow sending PDF files and raw citation strings to a JabRef online service (Grobid) to determine Metadata. This produces better results."), + Localization.lang("Send to Grobid"), Localization.lang("Do not send")); preferences.setGrobidUseAsked(true); preferences.setGrobidEnabled(grobidEnabled); return grobidEnabled; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/ImportCommand.java b/jabgui/src/main/java/org/jabref/gui/importer/ImportCommand.java index 5a9f78e4d48..eb7138e6208 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/ImportCommand.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/ImportCommand.java @@ -43,25 +43,30 @@ * Perform an import action */ public class ImportCommand extends SimpleCommand { + private static final Logger LOGGER = LoggerFactory.getLogger(ImportCommand.class); - public enum ImportMethod { AS_NEW, TO_EXISTING } + public enum ImportMethod { + + AS_NEW, TO_EXISTING + + } private final LibraryTabContainer tabContainer; + private final ImportMethod importMethod; private final DialogService dialogService; + private final CliPreferences preferences; + private final FileUpdateMonitor fileUpdateMonitor; + private final TaskExecutor taskExecutor; - public ImportCommand(LibraryTabContainer tabContainer, - ImportMethod importMethod, - CliPreferences preferences, - StateManager stateManager, - FileUpdateMonitor fileUpdateMonitor, - TaskExecutor taskExecutor, - DialogService dialogService) { + public ImportCommand(LibraryTabContainer tabContainer, ImportMethod importMethod, CliPreferences preferences, + StateManager stateManager, FileUpdateMonitor fileUpdateMonitor, TaskExecutor taskExecutor, + DialogService dialogService) { this.tabContainer = tabContainer; this.importMethod = importMethod; this.preferences = preferences; @@ -76,20 +81,17 @@ public ImportCommand(LibraryTabContainer tabContainer, @Override public void execute() { - ImportFormatReader importFormatReader = new ImportFormatReader( - preferences.getImporterPreferences(), - preferences.getImportFormatPreferences(), - preferences.getCitationKeyPatternPreferences(), - fileUpdateMonitor - ); + ImportFormatReader importFormatReader = new ImportFormatReader(preferences.getImporterPreferences(), + preferences.getImportFormatPreferences(), preferences.getCitationKeyPatternPreferences(), + fileUpdateMonitor); SortedSet importers = importFormatReader.getImportFormats(); FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(FileFilterConverter.ANY_FILE) - .addExtensionFilter(FileFilterConverter.forAllImporters(importers)) - .addExtensionFilter(FileFilterConverter.importerToExtensionFilter(importers)) - .withInitialDirectory(preferences.getImporterPreferences().getImportWorkingDirectory()) - .build(); + .addExtensionFilter(FileFilterConverter.ANY_FILE) + .addExtensionFilter(FileFilterConverter.forAllImporters(importers)) + .addExtensionFilter(FileFilterConverter.importerToExtensionFilter(importers)) + .withInitialDirectory(preferences.getImporterPreferences().getImportWorkingDirectory()) + .build(); List selectedFiles = dialogService.showFileOpenDialogAndGetMultipleFiles(fileDialogConfiguration); @@ -100,7 +102,8 @@ public void execute() { importMultipleFiles(selectedFiles, importers, fileDialogConfiguration.getSelectedExtensionFilter()); } - private void importMultipleFiles(List files, SortedSet importers, FileChooser.ExtensionFilter selectedExtensionFilter) { + private void importMultipleFiles(List files, SortedSet importers, + FileChooser.ExtensionFilter selectedExtensionFilter) { for (Path file : files) { if (!Files.exists(file)) { dialogService.showErrorDialogAndWait(Localization.lang("Import"), @@ -118,11 +121,13 @@ private void importMultipleFiles(List files, SortedSet importers if (!isGeneralFilter) { // User picked a specific format format = FileFilterConverter.getImporter(selectedExtensionFilter, importers); - } else if (files.size() == 1) { + } + else if (files.size() == 1) { // Infer if only one file and no specific filter selectedExtensionFilter = FileFilterConverter.determineExtensionFilter(files.getFirst()); format = FileFilterConverter.getImporter(selectedExtensionFilter, importers); - } else { + } + else { format = Optional.empty(); } @@ -135,15 +140,15 @@ private void importMultipleFiles(List files, SortedSet importers // while no library is currently open. if (importMethod == ImportMethod.AS_NEW || tab == null) { task.onSuccess(parserResult -> { - tabContainer.addTab(parserResult.getDatabaseContext(), true); - dialogService.notify(Localization.lang("%0 entry(s) imported", parserResult.getDatabase().getEntries().size())); - }) - .onFailure(ex -> { - LOGGER.error("Error importing", ex); - dialogService.notify(Localization.lang("Error importing. See the error log for details.")); - }) - .executeWith(taskExecutor); - } else { + tabContainer.addTab(parserResult.getDatabaseContext(), true); + dialogService + .notify(Localization.lang("%0 entry(s) imported", parserResult.getDatabase().getEntries().size())); + }).onFailure(ex -> { + LOGGER.error("Error importing", ex); + dialogService.notify(Localization.lang("Error importing. See the error log for details.")); + }).executeWith(taskExecutor); + } + else { ImportEntriesDialog dialog = new ImportEntriesDialog(tab.getBibDatabaseContext(), task); dialog.setTitle(Localization.lang("Import")); dialogService.showCustomDialogAndWait(dialog); @@ -160,51 +165,52 @@ private ParserResult doImport(List files, Importer importFormat) throws IO Optional importer = Optional.ofNullable(importFormat); // We import all files and collect their results List imports = new ArrayList<>(); - ImportFormatReader importFormatReader = new ImportFormatReader( - preferences.getImporterPreferences(), - preferences.getImportFormatPreferences(), - preferences.getCitationKeyPatternPreferences(), - fileUpdateMonitor - ); + ImportFormatReader importFormatReader = new ImportFormatReader(preferences.getImporterPreferences(), + preferences.getImportFormatPreferences(), preferences.getCitationKeyPatternPreferences(), + fileUpdateMonitor); for (Path filename : files) { try { if (importer.isEmpty()) { // Unknown format UiTaskExecutor.runAndWaitInJavaFXThread(() -> { - if (FileUtil.isPDFFile(filename) && GrobidUseDialogHelper.showAndWaitIfUserIsUndecided(dialogService, preferences.getGrobidPreferences())) { + if (FileUtil.isPDFFile(filename) && GrobidUseDialogHelper + .showAndWaitIfUserIsUndecided(dialogService, preferences.getGrobidPreferences())) { importFormatReader.reset(); } - dialogService.notify(Localization.lang("Importing file %0 as unknown format", filename.getFileName().toString())); + dialogService.notify(Localization.lang("Importing file %0 as unknown format", + filename.getFileName().toString())); }); // This import method never throws an IOException imports.add(importFormatReader.importUnknownFormat(filename, fileUpdateMonitor)); - } else { + } + else { UiTaskExecutor.runAndWaitInJavaFXThread(() -> { - if (((importer.get() instanceof PdfGrobidImporter) || (importer.get() instanceof PdfMergeMetadataImporter)) - && GrobidUseDialogHelper.showAndWaitIfUserIsUndecided(dialogService, preferences.getGrobidPreferences())) { + if (((importer.get() instanceof PdfGrobidImporter) + || (importer.get() instanceof PdfMergeMetadataImporter)) + && GrobidUseDialogHelper.showAndWaitIfUserIsUndecided(dialogService, + preferences.getGrobidPreferences())) { importFormatReader.reset(); } - dialogService.notify(Localization.lang("Importing in %0 format", importer.get().getName()) + "..."); + dialogService + .notify(Localization.lang("Importing in %0 format", importer.get().getName()) + "..."); }); // Specific importer ParserResult pr = importer.get().importDatabase(filename); imports.add(new ImportFormatReader.UnknownFormatImport(importer.get().getName(), pr)); } - } catch (ImportException ex) { + } + catch (ImportException ex) { UiTaskExecutor.runAndWaitInJavaFXThread( - () -> dialogService.showWarningDialogAndWait( - Localization.lang("Import error"), - Localization.lang("Please check your library file for wrong syntax.") - + "\n\n" - + ex.getLocalizedMessage())); + () -> dialogService.showWarningDialogAndWait(Localization.lang("Import error"), + Localization.lang("Please check your library file for wrong syntax.") + "\n\n" + + ex.getLocalizedMessage())); } } if (imports.isEmpty()) { - UiTaskExecutor.runAndWaitInJavaFXThread( - () -> dialogService.showWarningDialogAndWait( - Localization.lang("Import error"), - Localization.lang("No entries found. Please make sure you are using the correct import filter."))); + UiTaskExecutor.runAndWaitInJavaFXThread(() -> dialogService.showWarningDialogAndWait( + Localization.lang("Import error"), + Localization.lang("No entries found. Please make sure you are using the correct import filter."))); return new ParserResult(); } @@ -229,17 +235,22 @@ public ParserResult mergeImportResults(List path.getFileName().toString()).orElse("unknown"), + result.getMetaData(), parserResult.getMetaData(), + importResult.parserResult() + .getPath() + .map(path -> path.getFileName().toString()) + .orElse("unknown"), parserResult.getDatabase().getEntries()); } - // TODO: collect errors into ParserResult, because they are currently ignored (see caller of this method) + // TODO: collect errors into ParserResult, because they are currently ignored + // (see caller of this method) } // set timestamp and owner - UpdateField.setAutomaticFields(resultDatabase.getEntries(), preferences.getOwnerPreferences(), preferences.getTimestampPreferences()); // set timestamp and owner + UpdateField.setAutomaticFields(resultDatabase.getEntries(), preferences.getOwnerPreferences(), + preferences.getTimestampPreferences()); // set timestamp and owner return result; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/ImportCustomEntryTypesDialog.java b/jabgui/src/main/java/org/jabref/gui/importer/ImportCustomEntryTypesDialog.java index 849478abb0b..b7c0bf28264 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/ImportCustomEntryTypesDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/ImportCustomEntryTypesDialog.java @@ -22,30 +22,36 @@ public class ImportCustomEntryTypesDialog extends BaseDialog { private final List customEntryTypes; - @Inject private CliPreferences preferences; - @FXML private VBox boxDifferentCustomization; + @Inject + private CliPreferences preferences; - @FXML private CheckListView unknownEntryTypesCheckList; - @FXML private CheckListView differentCustomizationCheckList; + @FXML + private VBox boxDifferentCustomization; + + @FXML + private CheckListView unknownEntryTypesCheckList; + + @FXML + private CheckListView differentCustomizationCheckList; private final BibDatabaseMode mode; + private ImportCustomEntryTypesDialogViewModel viewModel; public ImportCustomEntryTypesDialog(BibDatabaseMode mode, List customEntryTypes) { this.mode = mode; this.customEntryTypes = customEntryTypes; - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); setResultConverter(btn -> { if (btn == ButtonType.OK) { - viewModel.importBibEntryTypes( - unknownEntryTypesCheckList.getCheckModel().getCheckedItems(), - differentCustomizationCheckList.getCheckModel().getCheckedItems().stream() - .map(BibEntryTypePrefsAndFileViewModel::customTypeFromPreferences) - .toList()); + viewModel.importBibEntryTypes(unknownEntryTypesCheckList.getCheckModel().getCheckedItems(), + differentCustomizationCheckList.getCheckModel() + .getCheckedItems() + .stream() + .map(BibEntryTypePrefsAndFileViewModel::customTypeFromPreferences) + .toList()); } return null; }); @@ -59,13 +65,15 @@ public void initialize() { boxDifferentCustomization.visibleProperty().bind(Bindings.isNotEmpty(viewModel.differentCustomizations())); boxDifferentCustomization.managedProperty().bind(Bindings.isNotEmpty(viewModel.differentCustomizations())); unknownEntryTypesCheckList.setItems(viewModel.newTypes()); - unknownEntryTypesCheckList.setCellFactory(listView -> new CheckBoxListCell<>(unknownEntryTypesCheckList::getItemBooleanProperty) { - @Override - public void updateItem(BibEntryType bibEntryType, boolean empty) { - super.updateItem(bibEntryType, empty); - setText(bibEntryType == null ? "" : bibEntryType.getType().getDisplayName()); - } - }); + unknownEntryTypesCheckList + .setCellFactory(listView -> new CheckBoxListCell<>(unknownEntryTypesCheckList::getItemBooleanProperty) { + @Override + public void updateItem(BibEntryType bibEntryType, boolean empty) { + super.updateItem(bibEntryType, empty); + setText(bibEntryType == null ? "" : bibEntryType.getType().getDisplayName()); + } + }); differentCustomizationCheckList.setItems(viewModel.differentCustomizations()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/ImportCustomEntryTypesDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/importer/ImportCustomEntryTypesDialogViewModel.java index b7aa3063eff..c777c621cfa 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/ImportCustomEntryTypesDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/ImportCustomEntryTypesDialogViewModel.java @@ -21,12 +21,16 @@ public class ImportCustomEntryTypesDialogViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(ImportCustomEntryTypesDialogViewModel.class); private final BibDatabaseMode mode; + private final CliPreferences preferences; private final ObservableList newTypes = FXCollections.observableArrayList(); - private final ObservableList differentCustomizationTypes = FXCollections.observableArrayList(); - public ImportCustomEntryTypesDialogViewModel(BibDatabaseMode mode, List entryTypes, CliPreferences preferences) { + private final ObservableList differentCustomizationTypes = FXCollections + .observableArrayList(); + + public ImportCustomEntryTypesDialogViewModel(BibDatabaseMode mode, List entryTypes, + CliPreferences preferences) { this.mode = mode; this.preferences = preferences; @@ -35,11 +39,13 @@ public ImportCustomEntryTypesDialogViewModel(BibDatabaseMode mode, List currentlyStoredType = entryTypesManager.enrich(customType.getType(), mode); if (currentlyStoredType.isEmpty()) { newTypes.add(customType); - } else { + } + else { if (!EntryTypeFactory.nameAndFieldsAreEqual(customType, currentlyStoredType.get())) { LOGGER.info("currently stored type: {}", currentlyStoredType.get()); LOGGER.info("type provided by library: {}", customType); - differentCustomizationTypes.add(new BibEntryTypePrefsAndFileViewModel(currentlyStoredType.get(), customType)); + differentCustomizationTypes + .add(new BibEntryTypePrefsAndFileViewModel(currentlyStoredType.get(), customType)); } } } @@ -53,7 +59,8 @@ public ObservableList differentCustomizations return this.differentCustomizationTypes; } - public void importBibEntryTypes(List checkedUnknownEntryTypes, List checkedDifferentEntryTypes) { + public void importBibEntryTypes(List checkedUnknownEntryTypes, + List checkedDifferentEntryTypes) { BibEntryTypesManager entryTypesManager = Injector.instantiateModelOrService(BibEntryTypesManager.class); if (!checkedUnknownEntryTypes.isEmpty()) { checkedUnknownEntryTypes.forEach(type -> entryTypesManager.addCustomOrModifiedType(type, mode)); @@ -64,4 +71,5 @@ public void importBibEntryTypes(List checkedUnknownEntryTypes, Lis preferences.storeCustomEntryTypesRepository(entryTypesManager); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/ImportEntriesDialog.java b/jabgui/src/main/java/org/jabref/gui/importer/ImportEntriesDialog.java index 4fc5a89000e..e615e983abe 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/ImportEntriesDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/ImportEntriesDialog.java @@ -54,41 +54,73 @@ public class ImportEntriesDialog extends BaseDialog { - @FXML private CheckListView entriesListView; - @FXML private ComboBox libraryListView; - @FXML private ButtonType importButton; - @FXML private Label totalItems; - @FXML private Label selectedItems; - @FXML private Label bibTeXDataLabel; - @FXML private CheckBox downloadLinkedOnlineFiles; - @FXML private CheckBox showEntryInformation; - @FXML private CodeArea bibTeXData; - @FXML private VBox bibTeXDataBox; + @FXML + private CheckListView entriesListView; + + @FXML + private ComboBox libraryListView; + + @FXML + private ButtonType importButton; + + @FXML + private Label totalItems; + + @FXML + private Label selectedItems; + + @FXML + private Label bibTeXDataLabel; + + @FXML + private CheckBox downloadLinkedOnlineFiles; + + @FXML + private CheckBox showEntryInformation; + + @FXML + private CodeArea bibTeXData; + + @FXML + private VBox bibTeXDataBox; private final BackgroundTask task; + private final BibDatabaseContext database; + private ImportEntriesViewModel viewModel; - @Inject private TaskExecutor taskExecutor; - @Inject private DialogService dialogService; - @Inject private UndoManager undoManager; - @Inject private GuiPreferences preferences; - @Inject private StateManager stateManager; - @Inject private BibEntryTypesManager entryTypesManager; - @Inject private FileUpdateMonitor fileUpdateMonitor; + @Inject + private TaskExecutor taskExecutor; + + @Inject + private DialogService dialogService; + + @Inject + private UndoManager undoManager; + + @Inject + private GuiPreferences preferences; + + @Inject + private StateManager stateManager; + + @Inject + private BibEntryTypesManager entryTypesManager; + + @Inject + private FileUpdateMonitor fileUpdateMonitor; /** - * Imports the given entries into the given database. The entries are provided using the BackgroundTask - * + * Imports the given entries into the given database. The entries are provided using + * the BackgroundTask * @param database the database to import into - * @param task the task executed for parsing the selected files(s). + * @param task the task executed for parsing the selected files(s). */ public ImportEntriesDialog(BibDatabaseContext database, BackgroundTask task) { this.database = database; this.task = task; - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); BooleanBinding booleanBind = Bindings.isEmpty(entriesListView.getCheckModel().getCheckedItems()); Button btn = (Button) this.getDialogPane().lookupButton(importButton); @@ -98,8 +130,10 @@ public ImportEntriesDialog(BibDatabaseContext database, BackgroundTask { if (button == importButton) { - viewModel.importEntries(entriesListView.getCheckModel().getCheckedItems(), downloadLinkedOnlineFiles.isSelected()); - } else { + viewModel.importEntries(entriesListView.getCheckModel().getCheckedItems(), + downloadLinkedOnlineFiles.isSelected()); + } + else { dialogService.notify(Localization.lang("Import canceled")); } @@ -109,7 +143,8 @@ public ImportEntriesDialog(BibDatabaseContext database, BackgroundTask() - .withText(database -> { - Optional dbOpt = Optional.empty(); - if (database.getDatabasePath().isPresent()) { - dbOpt = FileUtil.getUniquePathFragment(stateManager.getAllDatabasePaths(), database.getDatabasePath().get()); - } - if (database.getLocation() == DatabaseLocation.SHARED) { - return database.getDBMSSynchronizer().getDBName() + " [" + Localization.lang("shared") + "]"; - } - - return dbOpt.orElseGet(() -> Localization.lang("untitled")); - }) - .install(libraryListView); + new ViewModelListCellFactory().withText(database -> { + Optional dbOpt = Optional.empty(); + if (database.getDatabasePath().isPresent()) { + dbOpt = FileUtil.getUniquePathFragment(stateManager.getAllDatabasePaths(), + database.getDatabasePath().get()); + } + if (database.getLocation() == DatabaseLocation.SHARED) { + return database.getDBMSSynchronizer().getDBName() + " [" + Localization.lang("shared") + "]"; + } + + return dbOpt.orElseGet(() -> Localization.lang("untitled")); + }).install(libraryListView); viewModel.selectedDbProperty().bind(libraryListView.getSelectionModel().selectedItemProperty()); stateManager.getActiveDatabase().ifPresent(database1 -> libraryListView.getSelectionModel().select(database1)); PseudoClass entrySelected = PseudoClass.getPseudoClass("selected"); - new ViewModelListCellFactory() - .withGraphic(entry -> { - ToggleButton addToggle = IconTheme.JabRefIcons.ADD.asToggleButton(); - EasyBind.subscribe(addToggle.selectedProperty(), selected -> { - if (selected) { - addToggle.setGraphic(IconTheme.JabRefIcons.ADD_FILLED.withColor(IconTheme.SELECTED_COLOR).getGraphicNode()); - } else { - addToggle.setGraphic(IconTheme.JabRefIcons.ADD.getGraphicNode()); - } - }); - addToggle.getStyleClass().add("addEntryButton"); - addToggle.selectedProperty().bindBidirectional(entriesListView.getItemBooleanProperty(entry)); - HBox separator = new HBox(); - HBox.setHgrow(separator, Priority.SOMETIMES); - Node entryNode = BibEntryView.getEntryNode(entry); - HBox.setHgrow(entryNode, Priority.ALWAYS); - HBox container = new HBox(entryNode, separator, addToggle); - container.getStyleClass().add("entry-container"); - container.prefWidthProperty().bind(entriesListView.widthProperty().subtract(25)); - - BackgroundTask.wrap(() -> viewModel.hasDuplicate(entry)).onSuccess(duplicateFound -> { - if (duplicateFound) { - Node icon = IconTheme.JabRefIcons.ERROR.getGraphicNode(); - Tooltip tooltip = new Tooltip(Localization.lang("Possible duplicate of existing entry. Will be resolved on import.")); - Tooltip.install(icon, tooltip); - container.getChildren().add(icon); - } - }).executeWith(taskExecutor); - - /* - inserted the if-statement here, since a Platform.runLater() call did not work. - also tried to move it to the end of the initialize method, but it did not select the entry. - */ - if (entriesListView.getItems().size() == 1) { - selectAllNewEntries(); - } - - return container; - }) - .withOnMouseClickedEvent((entry, event) -> { - entriesListView.getCheckModel().toggleCheckState(entry); - displayBibTeX(entry, viewModel.getSourceString(entry)); - }) - .withPseudoClass(entrySelected, entriesListView::getItemBooleanProperty) - .install(entriesListView); + new ViewModelListCellFactory().withGraphic(entry -> { + ToggleButton addToggle = IconTheme.JabRefIcons.ADD.asToggleButton(); + EasyBind.subscribe(addToggle.selectedProperty(), selected -> { + if (selected) { + addToggle.setGraphic( + IconTheme.JabRefIcons.ADD_FILLED.withColor(IconTheme.SELECTED_COLOR).getGraphicNode()); + } + else { + addToggle.setGraphic(IconTheme.JabRefIcons.ADD.getGraphicNode()); + } + }); + addToggle.getStyleClass().add("addEntryButton"); + addToggle.selectedProperty().bindBidirectional(entriesListView.getItemBooleanProperty(entry)); + HBox separator = new HBox(); + HBox.setHgrow(separator, Priority.SOMETIMES); + Node entryNode = BibEntryView.getEntryNode(entry); + HBox.setHgrow(entryNode, Priority.ALWAYS); + HBox container = new HBox(entryNode, separator, addToggle); + container.getStyleClass().add("entry-container"); + container.prefWidthProperty().bind(entriesListView.widthProperty().subtract(25)); + + BackgroundTask.wrap(() -> viewModel.hasDuplicate(entry)).onSuccess(duplicateFound -> { + if (duplicateFound) { + Node icon = IconTheme.JabRefIcons.ERROR.getGraphicNode(); + Tooltip tooltip = new Tooltip( + Localization.lang("Possible duplicate of existing entry. Will be resolved on import.")); + Tooltip.install(icon, tooltip); + container.getChildren().add(icon); + } + }).executeWith(taskExecutor); + + /* + * inserted the if-statement here, since a Platform.runLater() call did not + * work. also tried to move it to the end of the initialize method, but it did + * not select the entry. + */ + if (entriesListView.getItems().size() == 1) { + selectAllNewEntries(); + } + + return container; + }).withOnMouseClickedEvent((entry, event) -> { + entriesListView.getCheckModel().toggleCheckState(entry); + displayBibTeX(entry, viewModel.getSourceString(entry)); + }).withPseudoClass(entrySelected, entriesListView::getItemBooleanProperty).install(entriesListView); selectedItems.textProperty().bind(Bindings.size(entriesListView.getCheckModel().getCheckedItems()).asString()); totalItems.textProperty().bind(Bindings.size(entriesListView.getItems()).asString()); @@ -192,14 +226,16 @@ private void displayBibTeX(BibEntry entry, String bibTeX) { bibTeXData.appendText(bibTeX); bibTeXData.moveTo(0); bibTeXData.requestFollowCaret(); - } else { + } + else { bibTeXData.clear(); } } private void initBibTeX() { bibTeXDataLabel.setText(Localization.lang("%0 source", "BibTeX")); - bibTeXData.setBorder(new Border(new BorderStroke(Color.GREY, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths.DEFAULT))); + bibTeXData.setBorder(new Border( + new BorderStroke(Color.GREY, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths.DEFAULT))); bibTeXData.setPadding(new Insets(5.0)); showEntryInformation.selectedProperty().addListener((observableValue, old_val, new_val) -> { bibTeXDataBox.setVisible(new_val); @@ -225,4 +261,5 @@ public void selectAllEntries() { unselectAll(); entriesListView.getCheckModel().checkAll(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/ImportEntriesViewModel.java b/jabgui/src/main/java/org/jabref/gui/importer/ImportEntriesViewModel.java index 8dc031f249a..ee608def03a 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/ImportEntriesViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/ImportEntriesViewModel.java @@ -42,31 +42,37 @@ public class ImportEntriesViewModel extends AbstractViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(ImportEntriesViewModel.class); private final StringProperty message; + private final TaskExecutor taskExecutor; + private final BibDatabaseContext databaseContext; + private final DialogService dialogService; + private final UndoManager undoManager; + private final StateManager stateManager; + private final FileUpdateMonitor fileUpdateMonitor; + private ParserResult parserResult = null; + private final ObservableList entries; + private final GuiPreferences preferences; + private final BibEntryTypesManager entryTypesManager; + private final ObjectProperty selectedDb; /** * @param databaseContext the database to import into - * @param task the task executed for parsing the selected files(s). + * @param task the task executed for parsing the selected files(s). */ - public ImportEntriesViewModel(BackgroundTask task, - TaskExecutor taskExecutor, - BibDatabaseContext databaseContext, - DialogService dialogService, - UndoManager undoManager, - GuiPreferences preferences, - StateManager stateManager, - BibEntryTypesManager entryTypesManager, - FileUpdateMonitor fileUpdateMonitor) { + public ImportEntriesViewModel(BackgroundTask task, TaskExecutor taskExecutor, + BibDatabaseContext databaseContext, DialogService dialogService, UndoManager undoManager, + GuiPreferences preferences, StateManager stateManager, BibEntryTypesManager entryTypesManager, + FileUpdateMonitor fileUpdateMonitor) { this.taskExecutor = taskExecutor; this.databaseContext = databaseContext; this.dialogService = dialogService; @@ -115,9 +121,9 @@ public ObservableList getEntries() { } public boolean hasDuplicate(BibEntry entry) { - return findInternalDuplicate(entry).isPresent() || - new DuplicateCheck(entryTypesManager) - .containsDuplicate(selectedDb.getValue().getDatabase(), entry, selectedDb.getValue().getMode()).isPresent(); + return findInternalDuplicate(entry).isPresent() || new DuplicateCheck(entryTypesManager) + .containsDuplicate(selectedDb.getValue().getDatabase(), entry, selectedDb.getValue().getMode()) + .isPresent(); } public String getSourceString(BibEntry entry) { @@ -126,7 +132,8 @@ public String getSourceString(BibEntry entry) { FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(preferences.getFieldPreferences()); try { new BibEntryWriter(fieldWriter, entryTypesManager).write(entry, bibWriter, selectedDb.getValue().getMode()); - } catch (IOException ioException) { + } + catch (IOException ioException) { return ""; } return writer.toString(); @@ -134,35 +141,26 @@ public String getSourceString(BibEntry entry) { /** * Called after the user selected the entries to import. Does the real import stuff. - * * @param entriesToImport subset of the entries contained in parserResult */ public void importEntries(List entriesToImport, boolean shouldDownloadFiles) { // Remember the selection in the dialog preferences.getFilePreferences().setDownloadLinkedFiles(shouldDownloadFiles); - new DatabaseMerger(preferences.getBibEntryPreferences().getKeywordSeparator()).mergeStrings( - databaseContext.getDatabase(), - parserResult.getDatabase()); + new DatabaseMerger(preferences.getBibEntryPreferences().getKeywordSeparator()) + .mergeStrings(databaseContext.getDatabase(), parserResult.getDatabase()); new DatabaseMerger(preferences.getBibEntryPreferences().getKeywordSeparator()).mergeMetaData( - databaseContext.getMetaData(), - parserResult.getMetaData(), + databaseContext.getMetaData(), parserResult.getMetaData(), parserResult.getPath().map(path -> path.getFileName().toString()).orElse("unknown"), parserResult.getDatabase().getEntries()); - ImportHandler importHandler = new ImportHandler( - selectedDb.getValue(), - preferences, - fileUpdateMonitor, - undoManager, - stateManager, - dialogService, - taskExecutor); + ImportHandler importHandler = new ImportHandler(selectedDb.getValue(), preferences, fileUpdateMonitor, + undoManager, stateManager, dialogService, taskExecutor); importHandler.importEntriesWithDuplicateCheck(selectedDb.getValue(), entriesToImport); } /** - * Checks if there are duplicates to the given entry in the list of entries to be imported. - * + * Checks if there are duplicates to the given entry in the list of entries to be + * imported. * @param entry The entry to search for duplicates of. * @return A possible duplicate, if any, or null if none were found. */ @@ -177,4 +175,5 @@ private Optional findInternalDuplicate(BibEntry entry) { } return Optional.empty(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/ImporterViewModel.java b/jabgui/src/main/java/org/jabref/gui/importer/ImporterViewModel.java index 9ec890a57a1..70624ebe304 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/ImporterViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/ImporterViewModel.java @@ -6,9 +6,13 @@ import org.jabref.logic.importer.fileformat.CustomImporter; public class ImporterViewModel { + private final CustomImporter importer; + private final StringProperty name = new SimpleStringProperty(); + private final StringProperty classname = new SimpleStringProperty(); + private final StringProperty basePath = new SimpleStringProperty(); public ImporterViewModel(CustomImporter importer) { @@ -33,4 +37,5 @@ public StringProperty className() { public StringProperty basePath() { return this.basePath; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/NewDatabaseAction.java b/jabgui/src/main/java/org/jabref/gui/importer/NewDatabaseAction.java index 43fb3d836d2..b00f6187c60 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/NewDatabaseAction.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/NewDatabaseAction.java @@ -11,12 +11,12 @@ public class NewDatabaseAction extends SimpleCommand { private final LibraryTabContainer tabContainer; + private final CliPreferences preferences; /** * Constructs a command to create a new library of the default type - * - * @param tabContainer the ui container for libraries + * @param tabContainer the ui container for libraries * @param preferences the preferencesService of JabRef */ public NewDatabaseAction(LibraryTabContainer tabContainer, CliPreferences preferences) { @@ -30,4 +30,5 @@ public void execute() { bibDatabaseContext.setMode(preferences.getLibraryPreferences().getDefaultBibDatabaseMode()); tabContainer.addTab(bibDatabaseContext, true); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/NewEntryAction.java b/jabgui/src/main/java/org/jabref/gui/importer/NewEntryAction.java index 0fc704c7a1f..71d194e541a 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/NewEntryAction.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/NewEntryAction.java @@ -28,16 +28,19 @@ public class NewEntryAction extends SimpleCommand { private final GuiPreferences preferences; private NewEntryDialogTab initialApproach; + private boolean isImmediate; + private Optional immediateType; /** - * Launches a tool for creating new entries. - * If `createImmediately` is `true`, a new entry will be immediately be created (using the last-selected entry type - * from the tool dialog was last run with). - * If `createImmediately` is `false`, a dialog will appear asking the user to specify inputs for the new entry. + * Launches a tool for creating new entries. If `createImmediately` is `true`, a new + * entry will be immediately be created (using the last-selected entry type from the + * tool dialog was last run with). If `createImmediately` is `false`, a dialog will + * appear asking the user to specify inputs for the new entry. */ - public NewEntryAction(boolean createImmediately, Supplier tabSupplier, DialogService dialogService, GuiPreferences preferences, StateManager stateManager) { + public NewEntryAction(boolean createImmediately, Supplier tabSupplier, DialogService dialogService, + GuiPreferences preferences, StateManager stateManager) { this.tabSupplier = tabSupplier; this.dialogService = dialogService; this.preferences = preferences; @@ -50,23 +53,26 @@ public NewEntryAction(boolean createImmediately, Supplier tabSupplie } /** - * Launches a dialog asking the user for inputs for the new entry to create. - * This dialog initially opens to the tab specified by `approach`. If `approach` is `null`, then the last-used tab - * from previous use of the tool is restored. + * Launches a dialog asking the user for inputs for the new entry to create. This + * dialog initially opens to the tab specified by `approach`. If `approach` is `null`, + * then the last-used tab from previous use of the tool is restored. */ - public NewEntryAction(NewEntryDialogTab approach, Supplier tabSupplier, DialogService dialogService, GuiPreferences preferences, StateManager stateManager) { + public NewEntryAction(NewEntryDialogTab approach, Supplier tabSupplier, DialogService dialogService, + GuiPreferences preferences, StateManager stateManager) { this(false, tabSupplier, dialogService, preferences, stateManager); this.initialApproach = approach; } /** - * Directly creates a new empty entry of the type `immediateType`, without opening a dialog for the user to provide - * inputs. - * If `immediateType` is `null`, the last-selected immediate type from the previous use of the tool to create an empty - * instance of a particular type is used (the `Article` standard entry type by default). + * Directly creates a new empty entry of the type `immediateType`, without opening a + * dialog for the user to provide inputs. If `immediateType` is `null`, the + * last-selected immediate type from the previous use of the tool to create an empty + * instance of a particular type is used (the `Article` standard entry type by + * default). */ - public NewEntryAction(EntryType immediateType, Supplier tabSupplier, DialogService dialogService, GuiPreferences preferences, StateManager stateManager) { + public NewEntryAction(EntryType immediateType, Supplier tabSupplier, DialogService dialogService, + GuiPreferences preferences, StateManager stateManager) { this(true, tabSupplier, dialogService, preferences, stateManager); this.immediateType = Optional.ofNullable(immediateType); @@ -76,7 +82,8 @@ public NewEntryAction(EntryType immediateType, Supplier tabSupplier, public void execute() { // Without a tab supplier, we can only log an error message and abort. if (tabSupplier.get() == null) { - // We skip logging the error if we were launched due to a keyboard shortcut though, since this isn't + // We skip logging the error if we were launched due to a keyboard shortcut + // though, since this isn't // something that can be disabled anyway. if (this.initialApproach == null && !this.isImmediate) { LOGGER.error("Action 'New Entry' must be disabled when no database is open."); @@ -91,22 +98,29 @@ public void execute() { if (immediateType.isPresent()) { // And we were created with an immediate type, then we use that type. type = immediateType.get(); - } else { - // Otherwise, we query the last-selected entry type from the NewEntry dialogue. + } + else { + // Otherwise, we query the last-selected entry type from the NewEntry + // dialogue. type = preferences.getNewEntryPreferences().getLatestImmediateType(); } // ...and create a new entry using this type. newEntry = new BibEntry(type); - } else { - // Otherwise, we launch a panel asking the user to specify details of the new entry. - NewEntryView newEntryDialog = new NewEntryView(initialApproach, preferences, tabSupplier.get(), dialogService); + } + else { + // Otherwise, we launch a panel asking the user to specify details of the new + // entry. + NewEntryView newEntryDialog = new NewEntryView(initialApproach, preferences, tabSupplier.get(), + dialogService); newEntry = dialogService.showCustomDialogAndWait(newEntryDialog).orElse(null); } - // This dialogue might handle inserting the new entry directly, so we don't do anything if the dialogue returns + // This dialogue might handle inserting the new entry directly, so we don't do + // anything if the dialogue returns // `null`. if (newEntry != null) { tabSupplier.get().insertEntry(newEntry); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/ParserResultWarningDialog.java b/jabgui/src/main/java/org/jabref/gui/importer/ParserResultWarningDialog.java index 63ae19877bd..c0e1757f3b7 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/ParserResultWarningDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/ParserResultWarningDialog.java @@ -17,11 +17,10 @@ private ParserResultWarningDialog() { /** * Shows a dialog with the warnings from an import or open of a file - * - * @param parserResult - ParserResult for the current import/open + * @param parserResult - ParserResult for the current import/open */ public static void showParserResultWarningDialog(final ParserResult parserResult, - final DialogService dialogService) { + final DialogService dialogService) { Objects.requireNonNull(parserResult); // Return if no warnings if (!parserResult.hasWarnings()) { @@ -41,11 +40,13 @@ public static void showParserResultWarningDialog(final ParserResult parserResult String dialogTitle; if (parserResult.getPath().isEmpty()) { dialogTitle = Localization.lang("Warnings"); - } else { + } + else { dialogTitle = Localization.lang("Warnings") + " (" + parserResult.getPath().get().getFileName() + ")"; } // Show dialog dialogService.showWarningDialogAndWait(dialogTitle, dialogContent.toString()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/actions/CheckForNewEntryTypesAction.java b/jabgui/src/main/java/org/jabref/gui/importer/actions/CheckForNewEntryTypesAction.java index e63af24ab95..a8f38d71b64 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/actions/CheckForNewEntryTypesAction.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/actions/CheckForNewEntryTypesAction.java @@ -15,34 +15,40 @@ import com.airhacks.afterburner.injection.Injector; /** - * This action checks whether any new custom entry types were loaded from this - * BIB file. If so, an offer to remember these entry types is given. + * This action checks whether any new custom entry types were loaded from this BIB file. + * If so, an offer to remember these entry types is given. */ public class CheckForNewEntryTypesAction implements GUIPostOpenAction { @Override - public boolean isActionNecessary(ParserResult parserResult, DialogService dialogService, CliPreferences preferences) { + public boolean isActionNecessary(ParserResult parserResult, DialogService dialogService, + CliPreferences preferences) { return !getListOfUnknownAndUnequalCustomizations(parserResult, preferences.getLibraryPreferences()).isEmpty(); } @Override - public void performAction(ParserResult parserResult, DialogService dialogService, CliPreferences preferencesService) { + public void performAction(ParserResult parserResult, DialogService dialogService, + CliPreferences preferencesService) { LibraryPreferences preferences = preferencesService.getLibraryPreferences(); BibDatabaseMode mode = getBibDatabaseModeFromParserResult(parserResult, preferences); - dialogService.showCustomDialogAndWait(new ImportCustomEntryTypesDialog(mode, getListOfUnknownAndUnequalCustomizations(parserResult, preferences))); + dialogService.showCustomDialogAndWait(new ImportCustomEntryTypesDialog(mode, + getListOfUnknownAndUnequalCustomizations(parserResult, preferences))); } - private List getListOfUnknownAndUnequalCustomizations(ParserResult parserResult, LibraryPreferences preferences) { + private List getListOfUnknownAndUnequalCustomizations(ParserResult parserResult, + LibraryPreferences preferences) { BibDatabaseMode mode = getBibDatabaseModeFromParserResult(parserResult, preferences); BibEntryTypesManager entryTypesManager = Injector.instantiateModelOrService(BibEntryTypesManager.class); return parserResult.getEntryTypes() - .stream() - .filter(type -> entryTypesManager.isDifferentCustomOrModifiedType(type, mode)) - .collect(Collectors.toList()); + .stream() + .filter(type -> entryTypesManager.isDifferentCustomOrModifiedType(type, mode)) + .collect(Collectors.toList()); } - private BibDatabaseMode getBibDatabaseModeFromParserResult(ParserResult parserResult, LibraryPreferences preferences) { + private BibDatabaseMode getBibDatabaseModeFromParserResult(ParserResult parserResult, + LibraryPreferences preferences) { return parserResult.getMetaData().getMode().orElse(preferences.getDefaultBibDatabaseMode()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/actions/GUIPostOpenAction.java b/jabgui/src/main/java/org/jabref/gui/importer/actions/GUIPostOpenAction.java index f4eec73e339..64728ae9a49 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/actions/GUIPostOpenAction.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/actions/GUIPostOpenAction.java @@ -5,34 +5,32 @@ import org.jabref.logic.preferences.CliPreferences; /** - * This interface defines potential actions that may need to be taken after - * opening a BIB file into JabRef. This can for instance be file upgrade actions - * that should be offered due to new features in JabRef, and may depend on e.g. - * which JabRef version the file was last written by. + * This interface defines potential actions that may need to be taken after opening a BIB + * file into JabRef. This can for instance be file upgrade actions that should be offered + * due to new features in JabRef, and may depend on e.g. which JabRef version the file was + * last written by. *

    - * This interface is introduced in an attempt to add such functionality in a - * flexible manner. + * This interface is introduced in an attempt to add such functionality in a flexible + * manner. */ public interface GUIPostOpenAction { /** * This method is queried in order to find out whether the action needs to be * performed or not. - * * @param pr The result of the BIB parse operation. * @return true if the action should be called, false otherwise. */ boolean isActionNecessary(ParserResult pr, DialogService dialogService, CliPreferences preferences); /** - * This method is called after the new database has been added to the GUI, if - * the isActionNecessary() method returned true. + * This method is called after the new database has been added to the GUI, if the + * isActionNecessary() method returned true. *

    - * Note: if several such methods need to be called sequentially, it is - * important that all implementations of this method do not return - * until the operation is finished. - * - * @param pr The result of the BIB parse operation. + * Note: if several such methods need to be called sequentially, it is important that + * all implementations of this method do not return until the operation is finished. + * @param pr The result of the BIB parse operation. */ void performAction(ParserResult pr, DialogService dialogService, CliPreferences preferences); + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentAction.java b/jabgui/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentAction.java index 3d69e620558..be179762e5f 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentAction.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentAction.java @@ -11,7 +11,8 @@ public class MergeReviewIntoCommentAction implements GUIPostOpenAction { @Override - public boolean isActionNecessary(ParserResult parserResult, DialogService dialogService, CliPreferences preferences) { + public boolean isActionNecessary(ParserResult parserResult, DialogService dialogService, + CliPreferences preferences) { return MergeReviewIntoCommentMigration.needsMigration(parserResult); } @@ -21,8 +22,10 @@ public void performAction(ParserResult parserResult, DialogService dialogService migration.performMigration(parserResult); List conflicts = MergeReviewIntoCommentMigration.collectConflicts(parserResult); - if (!conflicts.isEmpty() && new MergeReviewIntoCommentConfirmationDialog(dialogService).askUserForMerge(conflicts)) { + if (!conflicts.isEmpty() + && new MergeReviewIntoCommentConfirmationDialog(dialogService).askUserForMerge(conflicts)) { migration.performConflictingMigration(parserResult); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentConfirmationDialog.java b/jabgui/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentConfirmationDialog.java index ece4bb4e079..004f75a4ddf 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentConfirmationDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentConfirmationDialog.java @@ -17,22 +17,20 @@ public MergeReviewIntoCommentConfirmationDialog(DialogService dialogService) { } public boolean askUserForMerge(List conflicts) { - String bibKeys = conflicts - .stream() - .map(BibEntry::getCitationKey) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(Collectors.joining(",\n")); - - String content = bibKeys + " " + - Localization.lang("has/have both a 'Comment' and a 'Review' field.") + "\n" + - Localization.lang("Since the 'Review' field was deprecated in JabRef 4.2, these two fields are about to be merged into the 'Comment' field.") + "\n" + - Localization.lang("The conflicting fields of these entries will be merged into the 'Comment' field."); - - return dialogService.showConfirmationDialogAndWait( - Localization.lang("Review Field Migration"), - content, - Localization.lang("Merge fields") - ); + String bibKeys = conflicts.stream() + .map(BibEntry::getCitationKey) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.joining(",\n")); + + String content = bibKeys + " " + Localization.lang("has/have both a 'Comment' and a 'Review' field.") + "\n" + + Localization.lang( + "Since the 'Review' field was deprecated in JabRef 4.2, these two fields are about to be merged into the 'Comment' field.") + + "\n" + + Localization.lang("The conflicting fields of these entries will be merged into the 'Comment' field."); + + return dialogService.showConfirmationDialogAndWait(Localization.lang("Review Field Migration"), content, + Localization.lang("Merge fields")); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java b/jabgui/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java index 3e872e4ff31..bf7f365c41b 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java @@ -58,30 +58,34 @@ public class OpenDatabaseAction extends SimpleCommand { new MergeReviewIntoCommentAction(), // Check for new custom entry types loaded from the BIB file: new CheckForNewEntryTypesAction(), - // Migrate search groups fielded terms to use the new operators (RegEx, case sensitive) + // Migrate search groups fielded terms to use the new operators (RegEx, case + // sensitive) new SearchGroupsMigrationAction()); private final LibraryTabContainer tabContainer; + private final GuiPreferences preferences; + private final AiService aiService; + private final StateManager stateManager; + private final FileUpdateMonitor fileUpdateMonitor; + private final DialogService dialogService; + private final BibEntryTypesManager entryTypesManager; + private final CountingUndoManager undoManager; + private final ClipBoardManager clipboardManager; + private final TaskExecutor taskExecutor; - public OpenDatabaseAction(LibraryTabContainer tabContainer, - GuiPreferences preferences, - AiService aiService, - DialogService dialogService, - StateManager stateManager, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager, - CountingUndoManager undoManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor) { + public OpenDatabaseAction(LibraryTabContainer tabContainer, GuiPreferences preferences, AiService aiService, + DialogService dialogService, StateManager stateManager, FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager, CountingUndoManager undoManager, ClipBoardManager clipBoardManager, + TaskExecutor taskExecutor) { this.tabContainer = tabContainer; this.preferences = preferences; this.aiService = aiService; @@ -94,7 +98,8 @@ public OpenDatabaseAction(LibraryTabContainer tabContainer, this.taskExecutor = taskExecutor; } - public static void performPostOpenActions(ParserResult result, DialogService dialogService, CliPreferences preferences) { + public static void performPostOpenActions(ParserResult result, DialogService dialogService, + CliPreferences preferences) { for (GUIPostOpenAction action : OpenDatabaseAction.POST_OPEN_ACTIONS) { if (action.isActionNecessary(result, dialogService, preferences)) { action.performAction(result, dialogService, preferences); @@ -115,7 +120,8 @@ List getFilesToOpen() { try { FileDialogConfiguration initialDirectoryConfig = getFileDialogConfiguration(getInitialDirectory()); filesToOpen = dialogService.showFileOpenDialogAndGetMultipleFiles(initialDirectoryConfig); - } catch (IllegalArgumentException e) { + } + catch (IllegalArgumentException e) { // See https://github.com/JabRef/jabref/issues/10548 for details // Rebuild a new config with the home directory FileDialogConfiguration homeDirectoryConfig = getFileDialogConfiguration(Directories.getUserDirectory()); @@ -126,18 +132,16 @@ List getFilesToOpen() { } /** - * Builds a new FileDialogConfiguration using the given path as the initial directory for use in - * dialogService.showFileOpenDialogAndGetMultipleFiles(). - * + * Builds a new FileDialogConfiguration using the given path as the initial directory + * for use in dialogService.showFileOpenDialogAndGetMultipleFiles(). * @param initialDirectory Path to use as the initial directory * @return new FileDialogConfig with given initial directory */ public FileDialogConfiguration getFileDialogConfiguration(Path initialDirectory) { - return new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.BIBTEX_DB) - .withDefaultExtension(StandardFileType.BIBTEX_DB) - .withInitialDirectory(initialDirectory) - .build(); + return new FileDialogConfiguration.Builder().addExtensionFilter(StandardFileType.BIBTEX_DB) + .withDefaultExtension(StandardFileType.BIBTEX_DB) + .withInitialDirectory(initialDirectory) + .build(); } /** @@ -148,16 +152,16 @@ Path getInitialDirectory() { if (tabContainer.getLibraryTabs().isEmpty() || tabContainer.getCurrentLibraryTab() == null) { // currentTab might not be a LibraryTab but the WelcomeTab return preferences.getFilePreferences().getWorkingDirectory(); - } else { + } + else { Optional databasePath = tabContainer.getCurrentLibraryTab().getBibDatabaseContext().getDatabasePath(); return databasePath.map(Path::getParent).orElse(preferences.getFilePreferences().getWorkingDirectory()); } } /** - * Opens the given file. If null or 404, nothing happens. - * In case the file is already opened, that panel is raised. - * + * Opens the given file. If null or 404, nothing happens. In case the file is already + * opened, that panel is raised. * @param file the file, may be null or not existing */ public void openFile(Path file) { @@ -165,9 +169,8 @@ public void openFile(Path file) { } /** - * Opens the given files. If one of it is null or 404, nothing happens. - * In case the file is already opened, that panel is raised. - * + * Opens the given files. If one of it is null or 404, nothing happens. In case the + * file is already opened, that panel is raised. * @param filesToOpen the filesToOpen, may be null or not existing */ public void openFiles(List filesToOpen) { @@ -176,7 +179,7 @@ public void openFiles(List filesToOpen) { int removed = 0; // Check if any of the files are already open: - for (Iterator iterator = filesToOpen.iterator(); iterator.hasNext(); ) { + for (Iterator iterator = filesToOpen.iterator(); iterator.hasNext();) { Path file = iterator.next(); for (LibraryTab libraryTab : tabContainer.getLibraryTabs()) { if ((libraryTab.getBibDatabaseContext().getDatabasePath().isPresent()) @@ -200,11 +203,13 @@ public void openFiles(List filesToOpen) { assert fileUpdateMonitor != null; FileHistory fileHistory = preferences.getLastFilesOpenedPreferences().getFileHistory(); filesToOpen.forEach(theFile -> { - // This method will execute the concrete file opening and loading in a background thread + // This method will execute the concrete file opening and loading in a + // background thread openTheFile(theFile); fileHistory.newFile(theFile); }); - } else if (toRaise != null && tabContainer.getCurrentLibraryTab() == null) { + } + else if (toRaise != null && tabContainer.getCurrentLibraryTab() == null) { // If no files are remaining to open, this could mean that a file was // already open. If so, we may have to raise the correct tab: // If there is already a library focused, do not show this library @@ -215,8 +220,8 @@ public void openFiles(List filesToOpen) { /** * This is the real file opening. Should be called via {@link #openFile(Path)} * - * Similar method: {@link org.jabref.gui.frame.JabRefFrame#addTab(org.jabref.model.database.BibDatabaseContext, boolean)}. - * + * Similar method: + * {@link org.jabref.gui.frame.JabRefFrame#addTab(org.jabref.model.database.BibDatabaseContext, boolean)}. * @param file the file, may be NOT null, but may not be existing */ private void openTheFile(Path file) { @@ -229,23 +234,14 @@ private void openTheFile(Path file) { BackgroundTask backgroundTask = BackgroundTask.wrap(() -> loadDatabase(file)); // The backgroundTask is executed within the method createLibraryTab - LibraryTab newTab = LibraryTab.createLibraryTab( - backgroundTask, - file, - dialogService, - aiService, - preferences, - stateManager, - tabContainer, - fileUpdateMonitor, - entryTypesManager, - undoManager, - clipboardManager, + LibraryTab newTab = LibraryTab.createLibraryTab(backgroundTask, file, dialogService, aiService, preferences, + stateManager, tabContainer, fileUpdateMonitor, entryTypesManager, undoManager, clipboardManager, taskExecutor); tabContainer.addTab(newTab, true); } - private ParserResult loadDatabase(Path file) throws NotASharedDatabaseException, SQLException, InvalidDBMSConnectionPropertiesException, DatabaseNotSupportedException { + private ParserResult loadDatabase(Path file) throws NotASharedDatabaseException, SQLException, + InvalidDBMSConnectionPropertiesException, DatabaseNotSupportedException { Path fileToLoad = file.toAbsolutePath(); dialogService.notify(Localization.lang("Opening") + ": '" + file + "'"); @@ -256,78 +252,58 @@ private ParserResult loadDatabase(Path file) throws NotASharedDatabaseException, ParserResult parserResult = null; if (BackupManager.backupFileDiffers(fileToLoad, backupDir)) { // In case the backup differs, ask the user what to do. - // In case the user opted for restoring a backup, the content of the backup is contained in parserResult. - parserResult = BackupUIManager.showRestoreBackupDialog(dialogService, fileToLoad, preferences, fileUpdateMonitor, undoManager, stateManager) - .orElse(null); + // In case the user opted for restoring a backup, the content of the backup is + // contained in parserResult. + parserResult = BackupUIManager + .showRestoreBackupDialog(dialogService, fileToLoad, preferences, fileUpdateMonitor, undoManager, + stateManager) + .orElse(null); } try { if (parserResult == null) { // No backup was restored, do the "normal" loading - parserResult = OpenDatabase.loadDatabase(fileToLoad, - preferences.getImportFormatPreferences(), + parserResult = OpenDatabase.loadDatabase(fileToLoad, preferences.getImportFormatPreferences(), fileUpdateMonitor); } if (parserResult.hasWarnings()) { - String content = Localization.lang("Please check your library file for wrong syntax.") - + "\n\n" + parserResult.getErrorMessage(); - UiTaskExecutor.runInJavaFXThread(() -> - dialogService.showWarningDialogAndWait(Localization.lang("Open library error"), content)); + String content = Localization.lang("Please check your library file for wrong syntax.") + "\n\n" + + parserResult.getErrorMessage(); + UiTaskExecutor.runInJavaFXThread( + () -> dialogService.showWarningDialogAndWait(Localization.lang("Open library error"), content)); } - } catch (IOException e) { + } + catch (IOException e) { parserResult = ParserResult.fromError(e); LOGGER.error("Error opening file '{}'", fileToLoad, e); } if (parserResult.getDatabase().isShared()) { - openSharedDatabase( - parserResult, - tabContainer, - dialogService, - preferences, - aiService, - stateManager, - entryTypesManager, - fileUpdateMonitor, - undoManager, - clipboardManager, - taskExecutor); + openSharedDatabase(parserResult, tabContainer, dialogService, preferences, aiService, stateManager, + entryTypesManager, fileUpdateMonitor, undoManager, clipboardManager, taskExecutor); } return parserResult; } - public static void openSharedDatabase(ParserResult parserResult, - LibraryTabContainer tabContainer, - DialogService dialogService, - GuiPreferences preferences, - AiService aiService, - StateManager stateManager, - BibEntryTypesManager entryTypesManager, - FileUpdateMonitor fileUpdateMonitor, - UndoManager undoManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor) - throws SQLException, DatabaseNotSupportedException, InvalidDBMSConnectionPropertiesException, NotASharedDatabaseException { + public static void openSharedDatabase(ParserResult parserResult, LibraryTabContainer tabContainer, + DialogService dialogService, GuiPreferences preferences, AiService aiService, StateManager stateManager, + BibEntryTypesManager entryTypesManager, FileUpdateMonitor fileUpdateMonitor, UndoManager undoManager, + ClipBoardManager clipBoardManager, TaskExecutor taskExecutor) throws SQLException, + DatabaseNotSupportedException, InvalidDBMSConnectionPropertiesException, NotASharedDatabaseException { try { - new SharedDatabaseUIManager( - tabContainer, - dialogService, - preferences, - aiService, - stateManager, - entryTypesManager, - fileUpdateMonitor, - undoManager, - clipBoardManager, - taskExecutor) - .openSharedDatabaseFromParserResult(parserResult); - } catch (SQLException | DatabaseNotSupportedException | InvalidDBMSConnectionPropertiesException | - NotASharedDatabaseException e) { - parserResult.getDatabaseContext().clearDatabasePath(); // do not open the original file + new SharedDatabaseUIManager(tabContainer, dialogService, preferences, aiService, stateManager, + entryTypesManager, fileUpdateMonitor, undoManager, clipBoardManager, taskExecutor) + .openSharedDatabaseFromParserResult(parserResult); + } + catch (SQLException | DatabaseNotSupportedException | InvalidDBMSConnectionPropertiesException + | NotASharedDatabaseException e) { + parserResult.getDatabaseContext().clearDatabasePath(); // do not open the + // original file parserResult.getDatabase().clearSharedDatabaseID(); throw e; } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/actions/SearchGroupsMigrationAction.java b/jabgui/src/main/java/org/jabref/gui/importer/actions/SearchGroupsMigrationAction.java index b473228d4a8..3909ec5698f 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/actions/SearchGroupsMigrationAction.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/actions/SearchGroupsMigrationAction.java @@ -15,23 +15,28 @@ import org.antlr.v4.runtime.misc.ParseCancellationException; /** - * This action checks whether the syntax for SearchGroups is the new one. - * If not we ask the user whether to migrate. + * This action checks whether the syntax for SearchGroups is the new one. If not we ask + * the user whether to migrate. */ public class SearchGroupsMigrationAction implements GUIPostOpenAction { // We cannot have this constant in `Version.java` because of recursion errors - // Thus, we keep it here, because it is (currently) used only in the context of groups migration. + // Thus, we keep it here, because it is (currently) used only in the context of groups + // migration. public static final Version VERSION_6_0_ALPHA = Version.parse("6.0-alpha"); + public static final Version VERSION_6_0_ALPHA_1 = Version.parse("6.0-alpha_1"); @Override - public boolean isActionNecessary(ParserResult parserResult, DialogService dialogService, CliPreferences preferences) { + public boolean isActionNecessary(ParserResult parserResult, DialogService dialogService, + CliPreferences preferences) { Optional currentVersion = parserResult.getMetaData().getGroupSearchSyntaxVersion(); if (currentVersion.isPresent()) { if (currentVersion.get().equals(VERSION_6_0_ALPHA)) { - // TODO: This text will only be shown after releasing 6.0-alpha and then removed - dialogService.showErrorDialogAndWait("Search groups migration of " + parserResult.getPath().map(Path::toString).orElse(""), + // TODO: This text will only be shown after releasing 6.0-alpha and then + // removed + dialogService.showErrorDialogAndWait( + "Search groups migration of " + parserResult.getPath().map(Path::toString).orElse(""), "The search groups syntax has been reverted to the old one. Please use the backup you made before using 6.0-alpha."); } return false; @@ -55,7 +60,9 @@ private boolean groupOrSubgroupIsSearchGroup(GroupTreeNode groupTreeNode) { @Override public void performAction(ParserResult parserResult, DialogService dialogService, CliPreferences preferences) { - if (!dialogService.showConfirmationDialogAndWait(Localization.lang("Search groups migration of %0", parserResult.getPath().map(Path::toString).orElse("")), + if (!dialogService.showConfirmationDialogAndWait( + Localization.lang("Search groups migration of %0", + parserResult.getPath().map(Path::toString).orElse("")), Localization.lang("The search groups syntax is outdated. Do you want to migrate to the new syntax?"), Localization.lang("Migrate"), Localization.lang("Keep as is"))) { return; @@ -70,12 +77,15 @@ private void migrateGroups(GroupTreeNode node, DialogService dialogService) { if (node.getGroup() instanceof SearchGroup searchGroup) { try { if (searchGroup.getSearchQuery().isValid()) { - String newSearchExpression = SearchQueryConversion.flagsToSearchExpression(searchGroup.getSearchQuery()); + String newSearchExpression = SearchQueryConversion + .flagsToSearchExpression(searchGroup.getSearchQuery()); searchGroup.setSearchExpression(newSearchExpression); - } else { + } + else { showAskForNewSearchExpressionDialog(dialogService, searchGroup); } - } catch (ParseCancellationException e) { + } + catch (ParseCancellationException e) { showAskForNewSearchExpressionDialog(dialogService, searchGroup); } } @@ -87,9 +97,11 @@ private void migrateGroups(GroupTreeNode node, DialogService dialogService) { private void showAskForNewSearchExpressionDialog(DialogService dialogService, SearchGroup searchGroup) { Optional newSearchExpression = dialogService.showInputDialogWithDefaultAndWait( Localization.lang("Search group migration failed"), - Localization.lang("The search group '%0' could not be migrated. Please enter the new search expression.", + Localization.lang( + "The search group '%0' could not be migrated. Please enter the new search expression.", searchGroup.getName()), searchGroup.getSearchExpression()); newSearchExpression.ifPresent(searchGroup::setSearchExpression); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/fetcher/LookupIdentifierAction.java b/jabgui/src/main/java/org/jabref/gui/importer/fetcher/LookupIdentifierAction.java index 0b73e3bccab..3be0444d243 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/fetcher/LookupIdentifierAction.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/fetcher/LookupIdentifierAction.java @@ -33,16 +33,17 @@ public class LookupIdentifierAction extends SimpleCommand private static final Logger LOGGER = LoggerFactory.getLogger(LookupIdentifierAction.class); private final IdFetcher fetcher; + private final StateManager stateManager; + private final UndoManager undoManager; + private final DialogService dialogService; + private final TaskExecutor taskExecutor; - public LookupIdentifierAction(IdFetcher fetcher, - StateManager stateManager, - UndoManager undoManager, - DialogService dialogService, - TaskExecutor taskExecutor) { + public LookupIdentifierAction(IdFetcher fetcher, StateManager stateManager, UndoManager undoManager, + DialogService dialogService, TaskExecutor taskExecutor) { this.fetcher = fetcher; this.stateManager = stateManager; this.undoManager = undoManager; @@ -50,16 +51,18 @@ public LookupIdentifierAction(IdFetcher fetcher, this.taskExecutor = taskExecutor; this.executable.bind(needsDatabase(this.stateManager).and(needsEntriesSelected(this.stateManager))); - this.statusMessage.bind(BindingsHelper.ifThenElse(executable, "", Localization.lang("This operation requires one or more entries to be selected."))); + this.statusMessage.bind(BindingsHelper.ifThenElse(executable, "", + Localization.lang("This operation requires one or more entries to be selected."))); } @Override public void execute() { try { BackgroundTask.wrap(() -> lookupIdentifiers(stateManager.getSelectedEntries())) - .onSuccess(dialogService::notify) - .executeWith(taskExecutor); - } catch (Exception e) { + .onSuccess(dialogService::notify) + .executeWith(taskExecutor); + } + catch (Exception e) { LOGGER.error("Problem running ID Worker", e); } } @@ -81,16 +84,19 @@ private String lookupIdentifiers(List bibEntries) { Optional identifier = Optional.empty(); try { identifier = fetcher.findIdentifier(bibEntry); - } catch (FetcherException e) { + } + catch (FetcherException e) { LOGGER.error("Could not fetch {}", fetcher.getIdentifierName(), e); } if (identifier.isPresent() && !bibEntry.hasField(identifier.get().getDefaultField())) { - Optional fieldChange = bibEntry.setField(identifier.get().getDefaultField(), identifier.get().asString()); + Optional fieldChange = bibEntry.setField(identifier.get().getDefaultField(), + identifier.get().asString()); if (fieldChange.isPresent()) { namedCompound.addEdit(new UndoableFieldChange(fieldChange.get())); foundCount++; - final String nextStatusMessage = Localization.lang("Looking up %0... - entry %1 out of %2 - found %3", - fetcher.getIdentifierName(), Integer.toString(count), totalCount, Integer.toString(foundCount)); + final String nextStatusMessage = Localization.lang( + "Looking up %0... - entry %1 out of %2 - found %3", fetcher.getIdentifierName(), + Integer.toString(count), totalCount, Integer.toString(foundCount)); UiTaskExecutor.runInJavaFXThread(() -> dialogService.notify(nextStatusMessage)); } } @@ -99,6 +105,8 @@ private String lookupIdentifiers(List bibEntries) { if (foundCount > 0) { UiTaskExecutor.runInJavaFXThread(() -> undoManager.addEdit(namedCompound)); } - return Localization.lang("Determined %0 for %1 entries", fetcher.getIdentifierName(), Integer.toString(foundCount)); + return Localization.lang("Determined %0 for %1 entries", fetcher.getIdentifierName(), + Integer.toString(foundCount)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneView.java b/jabgui/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneView.java index d5347ccc061..d966fefcdba 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneView.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneView.java @@ -33,8 +33,11 @@ public class WebSearchPaneView extends VBox { private static final PseudoClass QUERY_INVALID = PseudoClass.getPseudoClass("invalid"); private final WebSearchPaneViewModel viewModel; + private final GuiPreferences preferences; + private final DialogService dialogService; + private final StateManager stateManager; public WebSearchPaneView(GuiPreferences preferences, DialogService dialogService, StateManager stateManager) { @@ -49,11 +52,7 @@ private void initialize() { StackPane helpButtonContainer = createHelpButtonContainer(); HBox fetcherContainer = new HBox(createFetcherComboBox(), helpButtonContainer); - getChildren().addAll( - fetcherContainer, - createQueryField(), - createSearchButton() - ); + getChildren().addAll(fetcherContainer, createQueryField(), createSearchButton()); this.disableProperty().bind(searchDisabledProperty()); } @@ -69,16 +68,16 @@ private void enableEnterToTriggerSearch(TextField query) { } private void addQueryValidationHints(TextField query) { - EasyBind.subscribe(viewModel.queryValidationStatus().validProperty(), - valid -> { - if (!valid && viewModel.queryValidationStatus().getHighestMessage().isPresent()) { - query.setTooltip(new Tooltip(viewModel.queryValidationStatus().getHighestMessage().get().getMessage())); - query.pseudoClassStateChanged(QUERY_INVALID, true); - } else { - query.setTooltip(null); - query.pseudoClassStateChanged(QUERY_INVALID, false); - } - }); + EasyBind.subscribe(viewModel.queryValidationStatus().validProperty(), valid -> { + if (!valid && viewModel.queryValidationStatus().getHighestMessage().isPresent()) { + query.setTooltip(new Tooltip(viewModel.queryValidationStatus().getHighestMessage().get().getMessage())); + query.pseudoClassStateChanged(QUERY_INVALID, true); + } + else { + query.setTooltip(null); + query.pseudoClassStateChanged(QUERY_INVALID, false); + } + }); } /** @@ -86,9 +85,7 @@ private void addQueryValidationHints(TextField query) { */ private ComboBox createFetcherComboBox() { ComboBox fetchers = new ComboBox<>(); - new ViewModelListCellFactory() - .withText(SearchBasedFetcher::getName) - .install(fetchers); + new ViewModelListCellFactory().withText(SearchBasedFetcher::getName).install(fetchers); fetchers.itemsProperty().bind(viewModel.fetchersProperty()); fetchers.valueProperty().bindBidirectional(viewModel.selectedFetcherProperty()); fetchers.setMaxWidth(Double.POSITIVE_INFINITY); @@ -129,9 +126,11 @@ private StackPane createHelpButtonContainer() { ActionFactory factory = new ActionFactory(); EasyBind.subscribe(viewModel.selectedFetcherProperty(), fetcher -> { if ((fetcher != null) && fetcher.getHelpPage().isPresent()) { - Button helpButton = factory.createIconButton(StandardActions.HELP, new HelpAction(fetcher.getHelpPage().get(), dialogService, preferences.getExternalApplicationsPreferences())); + Button helpButton = factory.createIconButton(StandardActions.HELP, new HelpAction( + fetcher.getHelpPage().get(), dialogService, preferences.getExternalApplicationsPreferences())); helpButtonContainer.getChildren().setAll(helpButton); - } else { + } + else { helpButtonContainer.getChildren().clear(); } }); @@ -142,9 +141,8 @@ private StackPane createHelpButtonContainer() { * Creates an observable boolean value that is true if no database is open */ private ObservableBooleanValue searchDisabledProperty() { - return Bindings.createBooleanBinding( - () -> stateManager.getOpenDatabases().isEmpty(), - stateManager.getOpenDatabases() - ); + return Bindings.createBooleanBinding(() -> stateManager.getOpenDatabases().isEmpty(), + stateManager.getOpenDatabases()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java b/jabgui/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java index 9057d8078b8..b3e8b5f3fb3 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java @@ -40,13 +40,20 @@ public class WebSearchPaneViewModel { private final ObjectProperty selectedFetcher = new SimpleObjectProperty<>(); - private final ListProperty fetchers = new SimpleListProperty<>(FXCollections.observableArrayList()); + + private final ListProperty fetchers = new SimpleListProperty<>( + FXCollections.observableArrayList()); + private final StringProperty query = new SimpleStringProperty(); + private final DialogService dialogService; + private final GuiPreferences preferences; + private final StateManager stateManager; private final Validator searchQueryValidator; + private final SyntaxParser parser = new StandardSyntaxParser(); public WebSearchPaneViewModel(GuiPreferences preferences, DialogService dialogService, StateManager stateManager) { @@ -54,8 +61,7 @@ public WebSearchPaneViewModel(GuiPreferences preferences, DialogService dialogSe this.stateManager = stateManager; this.preferences = preferences; - fetchers.setAll(WebFetchers.getSearchBasedFetchers( - preferences.getImportFormatPreferences(), + fetchers.setAll(WebFetchers.getSearchBasedFetchers(preferences.getImportFormatPreferences(), preferences.getImporterPreferences())); // Choose last-selected fetcher as default @@ -63,7 +69,8 @@ public WebSearchPaneViewModel(GuiPreferences preferences, DialogService dialogSe int defaultFetcherIndex = sidePanePreferences.getWebSearchFetcherSelected(); if ((defaultFetcherIndex <= 0) || (defaultFetcherIndex >= fetchers.size())) { selectedFetcherProperty().setValue(fetchers.getFirst()); - } else { + } + else { selectedFetcherProperty().setValue(fetchers.get(defaultFetcherIndex)); } EasyBind.subscribe(selectedFetcherProperty(), newFetcher -> { @@ -71,34 +78,37 @@ public WebSearchPaneViewModel(GuiPreferences preferences, DialogService dialogSe sidePanePreferences.setWebSearchFetcherSelected(newIndex); }); - searchQueryValidator = new FunctionBasedValidator<>( - query, - queryText -> { - if (StringUtil.isBlank(queryText)) { - // in case user did not enter something, it is treated as valid (to avoid UI WTFs) - return null; - } - - if (CompositeIdFetcher.containsValidId(queryText)) { - // in case the query contains any ID, it is treated as valid - return null; - } - - try { - parser.parse(queryText, NO_EXPLICIT_FIELD); - return null; - } catch (ParseException e) { - String element = e.currentToken.image; - int position = e.currentToken.beginColumn; - if (element == null) { - return ValidationMessage.error(Localization.lang("Invalid query. Check position %0.", position)); - } else { - return ValidationMessage.error(Localization.lang("Invalid query element '%0' at position %1", element, position)); - } - } catch (QueryNodeParseException e) { - return ValidationMessage.error(""); - } - }); + searchQueryValidator = new FunctionBasedValidator<>(query, queryText -> { + if (StringUtil.isBlank(queryText)) { + // in case user did not enter something, it is treated as valid (to avoid + // UI WTFs) + return null; + } + + if (CompositeIdFetcher.containsValidId(queryText)) { + // in case the query contains any ID, it is treated as valid + return null; + } + + try { + parser.parse(queryText, NO_EXPLICIT_FIELD); + return null; + } + catch (ParseException e) { + String element = e.currentToken.image; + int position = e.currentToken.beginColumn; + if (element == null) { + return ValidationMessage.error(Localization.lang("Invalid query. Check position %0.", position)); + } + else { + return ValidationMessage + .error(Localization.lang("Invalid query element '%0' at position %1", element, position)); + } + } + catch (QueryNodeParseException e) { + return ValidationMessage.error(""); + } + }); } public ObservableList getFetchers() { @@ -151,15 +161,18 @@ public void search() { if (CompositeIdFetcher.containsValidId(query)) { CompositeIdFetcher compositeIdFetcher = new CompositeIdFetcher(preferences.getImportFormatPreferences()); - parserResultCallable = () -> new ParserResult(OptionalUtil.toList(compositeIdFetcher.performSearchById(query))); + parserResultCallable = () -> new ParserResult( + OptionalUtil.toList(compositeIdFetcher.performSearchById(query))); fetcherName = Localization.lang("Identifier-based Web Search"); - } else { - // Exceptions are handled below at "task.onFailure(dialogService::showErrorDialogAndWait)" + } + else { + // Exceptions are handled below at + // "task.onFailure(dialogService::showErrorDialogAndWait)" parserResultCallable = () -> new ParserResult(activeFetcher.performSearch(query)); } BackgroundTask task = BackgroundTask.wrap(parserResultCallable) - .withInitialMessage(Localization.lang("Processing \"%0\"...", query)); + .withInitialMessage(Localization.lang("Processing \"%0\"...", query)); task.onFailure(dialogService::showErrorDialogAndWait); ImportEntriesDialog dialog = new ImportEntriesDialog(stateManager.getActiveDatabase().get(), task); @@ -170,4 +183,5 @@ public void search() { public ValidationStatus queryValidationStatus() { return searchQueryValidator.getValidationStatus(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/integrity/BibLogSettingsPane.java b/jabgui/src/main/java/org/jabref/gui/integrity/BibLogSettingsPane.java index 096e5633884..c75d8bf9a28 100644 --- a/jabgui/src/main/java/org/jabref/gui/integrity/BibLogSettingsPane.java +++ b/jabgui/src/main/java/org/jabref/gui/integrity/BibLogSettingsPane.java @@ -21,15 +21,20 @@ /** * Controller for the .blg file settings panel. * - * Binds the path text field to the ViewModel, - * and handles browse/reset button actions. + * Binds the path text field to the ViewModel, and handles browse/reset button actions. */ public class BibLogSettingsPane { + private static final Logger LOGGER = LoggerFactory.getLogger(BibLogSettingsPane.class); + @FXML private TextField pathField; + private BibLogSettingsViewModel viewModel; - @Inject private DialogService dialogService; + + @Inject + private DialogService dialogService; + private Runnable onBlgPathChanged; public void initializeViewModel(BibDatabaseContext context, Runnable onBlgPathChanged) throws JabRefException { @@ -75,14 +80,15 @@ public BibLogSettingsViewModel getViewModel() { private FileDialogConfiguration createBlgFileDialogConfig() { Path initialDir = viewModel.getInitialDirectory(); FileDialogConfiguration config = new FileDialogConfiguration.Builder() - .addExtensionFilter(Localization.lang("BibTeX log files"), StandardFileType.BLG) - .withDefaultExtension(Localization.lang("BibTeX log files"), StandardFileType.BLG) - .withInitialDirectory(viewModel.getInitialDirectory()) - .build(); + .addExtensionFilter(Localization.lang("BibTeX log files"), StandardFileType.BLG) + .withDefaultExtension(Localization.lang("BibTeX log files"), StandardFileType.BLG) + .withInitialDirectory(viewModel.getInitialDirectory()) + .build(); return config; } public boolean wasBlgFileManuallySelected() { return viewModel.wasBlgFileManuallySelected(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/integrity/BibLogSettingsViewModel.java b/jabgui/src/main/java/org/jabref/gui/integrity/BibLogSettingsViewModel.java index e6a1c39351b..cbffa795a5a 100644 --- a/jabgui/src/main/java/org/jabref/gui/integrity/BibLogSettingsViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/integrity/BibLogSettingsViewModel.java @@ -26,12 +26,19 @@ /// 2. Wraps .blg warnings as IntegrityMessages. /// 3. Supports file browsing and reset actions. public class BibLogSettingsViewModel extends AbstractViewModel { + private final ObservableList blgWarnings = FXCollections.observableArrayList(); + private final StringProperty path = new SimpleStringProperty(""); + private final MetaData metaData; + private final Optional bibPath; + private final String user; + private Optional lastResolvedBlgPath = Optional.empty(); + private boolean userManuallySelectedBlgFile = false; public BibLogSettingsViewModel(MetaData metaData, Optional bibPath) { @@ -39,22 +46,21 @@ public BibLogSettingsViewModel(MetaData metaData, Optional bibPath) { this.bibPath = bibPath; this.user = System.getProperty("user.name"); - BibLogPathResolver.resolve(metaData, bibPath, user) - .ifPresent(resolvedPath -> { - this.path.set(resolvedPath.toString()); - if (metaData.getBlgFilePath(user).isEmpty()) { - metaData.setBlgFilePath(user, resolvedPath); - this.lastResolvedBlgPath = Optional.of(resolvedPath); - } - }); + BibLogPathResolver.resolve(metaData, bibPath, user).ifPresent(resolvedPath -> { + this.path.set(resolvedPath.toString()); + if (metaData.getBlgFilePath(user).isEmpty()) { + metaData.setBlgFilePath(user, resolvedPath); + this.lastResolvedBlgPath = Optional.of(resolvedPath); + } + }); } /** * Parses the .blg file (if it exists) into the observable list. - * - * @param databaseContext the current database context used to resolve citation keys in warnings. - * @return An Optional containing the list of integrity messages if the file exists and can be parsed, - * or an empty Optional if the file does not exist. + * @param databaseContext the current database context used to resolve citation keys + * in warnings. + * @return An Optional containing the list of integrity messages if the file exists + * and can be parsed, or an empty Optional if the file does not exist. * @throws JabRefException if the .blg file cannot be parsed or read */ public Optional> getBlgWarnings(BibDatabaseContext databaseContext) throws JabRefException { @@ -70,16 +76,15 @@ public Optional> getBlgWarnings(BibDatabaseContext databa try { BibtexLogParser parser = new BibtexLogParser(); List warnings = parser.parseBiblog(path); - List newWarnings = BibWarningToIntegrityMessageConverter.convert(warnings, databaseContext); + List newWarnings = BibWarningToIntegrityMessageConverter.convert(warnings, + databaseContext); blgWarnings.setAll(newWarnings); return Optional.of(newWarnings); - } catch (IOException e) { + } + catch (IOException e) { blgWarnings.clear(); - throw new JabRefException( - "Failed to parse .blg file", - Localization.lang("Could not read BibTeX log file. Please check the file path and try again."), - e - ); + throw new JabRefException("Failed to parse .blg file", + Localization.lang("Could not read BibTeX log file. Please check the file path and try again."), e); } } @@ -114,16 +119,16 @@ public void resetBlgFilePath() { } public Optional getResolvedBlgPath() { - return BibLogPathResolver.resolve(metaData, bibPath, user) - .filter(Files::exists); + return BibLogPathResolver.resolve(metaData, bibPath, user).filter(Files::exists); } public Path getInitialDirectory() { return bibPath.flatMap(path -> Optional.ofNullable(path.getParent())) - .orElse(Path.of(System.getProperty("user.home"))); + .orElse(Path.of(System.getProperty("user.home"))); } public boolean wasBlgFileManuallySelected() { return userManuallySelectedBlgFile; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/integrity/IntegrityCheckAction.java b/jabgui/src/main/java/org/jabref/gui/integrity/IntegrityCheckAction.java index e5c0e048a0e..748f659697a 100644 --- a/jabgui/src/main/java/org/jabref/gui/integrity/IntegrityCheckAction.java +++ b/jabgui/src/main/java/org/jabref/gui/integrity/IntegrityCheckAction.java @@ -25,18 +25,20 @@ public class IntegrityCheckAction extends SimpleCommand { private final UiTaskExecutor taskExecutor; + private final DialogService dialogService; + private final Supplier tabSupplier; + private final GuiPreferences preferences; + private final StateManager stateManager; + private final JournalAbbreviationRepository abbreviationRepository; - public IntegrityCheckAction(Supplier tabSupplier, - GuiPreferences preferences, - DialogService dialogService, - StateManager stateManager, - UiTaskExecutor taskExecutor, - JournalAbbreviationRepository abbreviationRepository) { + public IntegrityCheckAction(Supplier tabSupplier, GuiPreferences preferences, + DialogService dialogService, StateManager stateManager, UiTaskExecutor taskExecutor, + JournalAbbreviationRepository abbreviationRepository) { this.tabSupplier = tabSupplier; this.stateManager = stateManager; this.taskExecutor = taskExecutor; @@ -48,11 +50,10 @@ public IntegrityCheckAction(Supplier tabSupplier, @Override public void execute() { - BibDatabaseContext database = stateManager.getActiveDatabase().orElseThrow(() -> new NullPointerException("Database null")); - IntegrityCheck check = new IntegrityCheck(database, - preferences.getFilePreferences(), - preferences.getCitationKeyPatternPreferences(), - abbreviationRepository, + BibDatabaseContext database = stateManager.getActiveDatabase() + .orElseThrow(() -> new NullPointerException("Database null")); + IntegrityCheck check = new IntegrityCheck(database, preferences.getFilePreferences(), + preferences.getCitationKeyPatternPreferences(), abbreviationRepository, preferences.getEntryEditorPreferences().shouldAllowIntegerEditionBibtex()); Task> task = new Task<>() { @@ -76,16 +77,17 @@ protected List call() { List messages = task.getValue(); if (messages.isEmpty()) { dialogService.notify(Localization.lang("No problems found.")); - } else { - dialogService.showCustomDialogAndWait(new IntegrityCheckDialog(messages, tabSupplier.get(), dialogService)); + } + else { + dialogService + .showCustomDialogAndWait(new IntegrityCheckDialog(messages, tabSupplier.get(), dialogService)); } }); task.setOnFailed(event -> dialogService.showErrorDialogAndWait("Integrity check failed.", task.getException())); - dialogService.showProgressDialog( - Localization.lang("Checking integrity..."), - Localization.lang("Waiting for the check to finish..."), - task); + dialogService.showProgressDialog(Localization.lang("Checking integrity..."), + Localization.lang("Waiting for the check to finish..."), task); taskExecutor.execute(task); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java b/jabgui/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java index 502e45abe00..584bbf0b53f 100644 --- a/jabgui/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java @@ -37,27 +37,56 @@ import org.slf4j.LoggerFactory; public class IntegrityCheckDialog extends BaseDialog { + private static final Logger LOGGER = LoggerFactory.getLogger(IntegrityCheckDialog.class); - @FXML private TableView messagesTable; - @FXML private TableColumn keyColumn; - @FXML private TableColumn fieldColumn; - @FXML private TableColumn messageColumn; - @FXML private MenuButton keyFilterButton; - @FXML private MenuButton fieldFilterButton; - @FXML private MenuButton messageFilterButton; - @FXML private VBox dialogVBox; - - @Inject private EntryEditor entryEditor; - @Inject private ThemeManager themeManager; - @Inject private StateManager stateManager; + @FXML + private TableView messagesTable; + + @FXML + private TableColumn keyColumn; + + @FXML + private TableColumn fieldColumn; + + @FXML + private TableColumn messageColumn; + + @FXML + private MenuButton keyFilterButton; + + @FXML + private MenuButton fieldFilterButton; + + @FXML + private MenuButton messageFilterButton; + + @FXML + private VBox dialogVBox; + + @Inject + private EntryEditor entryEditor; + + @Inject + private ThemeManager themeManager; + + @Inject + private StateManager stateManager; + private final List messages; + private final LibraryTab libraryTab; + private final DialogService dialogService; + private IntegrityCheckDialogViewModel viewModel; + private TableFilter tableFilter; + private BibLogSettingsPane bibLogSettingsPane; + private final List blgWarnings = new ArrayList<>(); + public IntegrityCheckDialog(List messages, LibraryTab libraryTab, DialogService dialogService) { this.messages = messages; this.libraryTab = libraryTab; @@ -66,9 +95,7 @@ public IntegrityCheckDialog(List messages, LibraryTab libraryT this.setTitle(Localization.lang("Check integrity")); this.initModality(Modality.NONE); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); themeManager.updateFontStyle(getDialogPane().getScene()); } @@ -95,21 +122,19 @@ public IntegrityCheckDialogViewModel getViewModel() { private void initialize() { viewModel = new IntegrityCheckDialogViewModel(messages); - new ViewModelTableRowFactory() - .withOnMouseClickedEvent(this::handleRowClick) - .install(messagesTable); + new ViewModelTableRowFactory().withOnMouseClickedEvent(this::handleRowClick) + .install(messagesTable); messagesTable.setItems(viewModel.getMessages()); - keyColumn.setCellValueFactory(row -> new ReadOnlyStringWrapper(row.getValue().entry().getCitationKey().orElse(""))); + keyColumn + .setCellValueFactory(row -> new ReadOnlyStringWrapper(row.getValue().entry().getCitationKey().orElse(""))); fieldColumn.setCellValueFactory(row -> new ReadOnlyStringWrapper(row.getValue().field().getDisplayName())); messageColumn.setCellValueFactory(row -> new ReadOnlyStringWrapper(row.getValue().message())); - new ValueTableCellFactory() - .withText(Function.identity()) - .withTooltip(Function.identity()) - .install(messageColumn); + new ValueTableCellFactory().withText(Function.identity()) + .withTooltip(Function.identity()) + .install(messageColumn); - tableFilter = TableFilter.forTableView(messagesTable) - .apply(); + tableFilter = TableFilter.forTableView(messagesTable).apply(); addMessageColumnFilter(keyColumn, keyFilterButton); addMessageColumnFilter(fieldColumn, fieldFilterButton); @@ -118,7 +143,8 @@ private void initialize() { loadBibLogSettingsPane(); } - private void addMessageColumnFilter(TableColumn messageColumn, MenuButton messageFilterButton) { + private void addMessageColumnFilter(TableColumn messageColumn, + MenuButton messageFilterButton) { tableFilter.getColumnFilter(messageColumn).ifPresent(columnFilter -> { ContextMenu messageContextMenu = messageColumn.getContextMenu(); if (messageContextMenu != null) { @@ -128,7 +154,8 @@ private void addMessageColumnFilter(TableColumn messag if (messageContextMenu.isShowing()) { messageContextMenu.setX(event.getScreenX()); messageContextMenu.setY(event.getScreenY()); - } else { + } + else { messageContextMenu.show(messageFilterButton, event.getScreenX(), event.getScreenY()); } } @@ -148,8 +175,7 @@ public void clearFilters() { } /** - * Loads the BibLogSettingsPane.fxml view - * and initializes its controller. + * Loads the BibLogSettingsPane.fxml view and initializes its controller. */ private void loadBibLogSettingsPane() { try { @@ -162,36 +188,35 @@ private void loadBibLogSettingsPane() { LOGGER.error("Failed to get controller from ViewLoader"); return; } - bibLogSettingsPane.initializeViewModel( - libraryTab.getBibDatabaseContext(), - this::reloadBlgWarnings - ); + bibLogSettingsPane.initializeViewModel(libraryTab.getBibDatabaseContext(), this::reloadBlgWarnings); dialogVBox.getChildren().add(1, settingsNode); reloadBlgWarnings(); - } catch (JabRefException e) { + } + catch (JabRefException e) { dialogService.notify(e.getLocalizedMessage()); LOGGER.error("Failed to initialize BibLogSettingsPane", e); } } /** - * Called on: - * (1) Dialog initialization (default load) - * (2) User triggers Browse or Reset in BibLogSettingsPane + * Called on: (1) Dialog initialization (default load) (2) User triggers Browse or + * Reset in BibLogSettingsPane * * This reloads .blg warnings and merges them into the main message list. */ private void reloadBlgWarnings() { try { bibLogSettingsPane.refreshWarnings(libraryTab.getBibDatabaseContext()); - } catch (JabRefException e) { + } + catch (JabRefException e) { dialogService.notify(e.getLocalizedMessage()); LOGGER.warn("Failed to load .blg warnings", e); } List newWarnings = new ArrayList<>(bibLogSettingsPane.getBlgWarnings()); if (newWarnings.isEmpty() && bibLogSettingsPane.wasBlgFileManuallySelected()) { - dialogService.notify(Localization.lang("No warnings found. Please check if the .blg file matches the current library.")); + dialogService.notify( + Localization.lang("No warnings found. Please check if the .blg file matches the current library.")); } messages.removeAll(blgWarnings); @@ -201,4 +226,5 @@ private void reloadBlgWarnings() { viewModel.getMessages().setAll(messages); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialogViewModel.java index 1313417642e..ee658914307 100644 --- a/jabgui/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialogViewModel.java @@ -19,4 +19,5 @@ public IntegrityCheckDialogViewModel(List messages) { public ObservableList getMessages() { return messages; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/journals/AbbreviateAction.java b/jabgui/src/main/java/org/jabref/gui/journals/AbbreviateAction.java index edb54c40529..f1f06ba6ebe 100644 --- a/jabgui/src/main/java/org/jabref/gui/journals/AbbreviateAction.java +++ b/jabgui/src/main/java/org/jabref/gui/journals/AbbreviateAction.java @@ -25,31 +25,34 @@ import org.slf4j.LoggerFactory; /** - * Converts journal full names to either iso or medline abbreviations for all selected entries. + * Converts journal full names to either iso or medline abbreviations for all selected + * entries. */ public class AbbreviateAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(AbbreviateAction.class); private final StandardActions action; + private final Supplier tabSupplier; + private final DialogService dialogService; + private final StateManager stateManager; + private final JournalAbbreviationPreferences journalAbbreviationPreferences; + private final JournalAbbreviationRepository abbreviationRepository; + private final TaskExecutor taskExecutor; + private final UndoManager undoManager; private AbbreviationType abbreviationType; - public AbbreviateAction(StandardActions action, - Supplier tabSupplier, - DialogService dialogService, - StateManager stateManager, - JournalAbbreviationPreferences abbreviationPreferences, - JournalAbbreviationRepository abbreviationRepository, - TaskExecutor taskExecutor, - UndoManager undoManager) { + public AbbreviateAction(StandardActions action, Supplier tabSupplier, DialogService dialogService, + StateManager stateManager, JournalAbbreviationPreferences abbreviationPreferences, + JournalAbbreviationRepository abbreviationRepository, TaskExecutor taskExecutor, UndoManager undoManager) { this.action = action; this.tabSupplier = tabSupplier; this.dialogService = dialogService; @@ -60,16 +63,11 @@ public AbbreviateAction(StandardActions action, this.undoManager = undoManager; switch (action) { - case ABBREVIATE_DEFAULT -> - abbreviationType = AbbreviationType.DEFAULT; - case ABBREVIATE_DOTLESS -> - abbreviationType = AbbreviationType.DOTLESS; - case ABBREVIATE_SHORTEST_UNIQUE -> - abbreviationType = AbbreviationType.SHORTEST_UNIQUE; - case ABBREVIATE_LTWA -> - abbreviationType = AbbreviationType.LTWA; - default -> - LOGGER.debug("Unknown action: {}", action.name()); + case ABBREVIATE_DEFAULT -> abbreviationType = AbbreviationType.DEFAULT; + case ABBREVIATE_DOTLESS -> abbreviationType = AbbreviationType.DOTLESS; + case ABBREVIATE_SHORTEST_UNIQUE -> abbreviationType = AbbreviationType.SHORTEST_UNIQUE; + case ABBREVIATE_LTWA -> abbreviationType = AbbreviationType.LTWA; + default -> LOGGER.debug("Unknown action: {}", action.name()); } this.executable.bind(ActionHelper.needsEntriesSelected(stateManager)); @@ -77,37 +75,42 @@ public AbbreviateAction(StandardActions action, @Override public void execute() { - if ((action == StandardActions.ABBREVIATE_DEFAULT) - || (action == StandardActions.ABBREVIATE_DOTLESS) + if ((action == StandardActions.ABBREVIATE_DEFAULT) || (action == StandardActions.ABBREVIATE_DOTLESS) || (action == StandardActions.ABBREVIATE_SHORTEST_UNIQUE) || (action == StandardActions.ABBREVIATE_LTWA)) { dialogService.notify(Localization.lang("Abbreviating...")); - stateManager.getActiveDatabase().ifPresent(_ -> - BackgroundTask.wrap(() -> abbreviate(stateManager.getActiveDatabase().get(), stateManager.getSelectedEntries())) - .onSuccess(dialogService::notify) - .executeWith(taskExecutor)); - } else if (action == StandardActions.UNABBREVIATE) { + stateManager.getActiveDatabase() + .ifPresent(_ -> BackgroundTask + .wrap(() -> abbreviate(stateManager.getActiveDatabase().get(), stateManager.getSelectedEntries())) + .onSuccess(dialogService::notify) + .executeWith(taskExecutor)); + } + else if (action == StandardActions.UNABBREVIATE) { dialogService.notify(Localization.lang("Unabbreviating...")); - stateManager.getActiveDatabase().ifPresent(_ -> - BackgroundTask.wrap(() -> unabbreviate(stateManager.getActiveDatabase().get(), stateManager.getSelectedEntries())) - .onSuccess(dialogService::notify) - .executeWith(taskExecutor)); - } else { + stateManager.getActiveDatabase() + .ifPresent(_ -> BackgroundTask + .wrap(() -> unabbreviate(stateManager.getActiveDatabase().get(), stateManager.getSelectedEntries())) + .onSuccess(dialogService::notify) + .executeWith(taskExecutor)); + } + else { LOGGER.debug("Unknown action: {}", action.name()); } } private String abbreviate(BibDatabaseContext databaseContext, List entries) { - UndoableAbbreviator undoableAbbreviator = new UndoableAbbreviator( - abbreviationRepository, - abbreviationType, + UndoableAbbreviator undoableAbbreviator = new UndoableAbbreviator(abbreviationRepository, abbreviationType, journalAbbreviationPreferences.shouldUseFJournalField()); NamedCompound ce = new NamedCompound(Localization.lang("Abbreviate journal names")); - int count = entries.stream().mapToInt(entry -> - (int) FieldFactory.getJournalNameFields().stream().filter(journalField -> - undoableAbbreviator.abbreviate(databaseContext.getDatabase(), entry, journalField, ce)).count()).sum(); + int count = entries.stream() + .mapToInt(entry -> (int) FieldFactory.getJournalNameFields() + .stream() + .filter(journalField -> undoableAbbreviator.abbreviate(databaseContext.getDatabase(), entry, + journalField, ce)) + .count()) + .sum(); if (count == 0) { return Localization.lang("No journal names could be abbreviated."); @@ -123,9 +126,13 @@ private String unabbreviate(BibDatabaseContext databaseContext, List e UndoableUnabbreviator undoableAbbreviator = new UndoableUnabbreviator(abbreviationRepository); NamedCompound ce = new NamedCompound(Localization.lang("Unabbreviate journal names")); - int count = entries.stream().mapToInt(entry -> - (int) FieldFactory.getJournalNameFields().stream().filter(journalField -> - undoableAbbreviator.unabbreviate(databaseContext.getDatabase(), entry, journalField, ce)).count()).sum(); + int count = entries.stream() + .mapToInt(entry -> (int) FieldFactory.getJournalNameFields() + .stream() + .filter(journalField -> undoableAbbreviator.unabbreviate(databaseContext.getDatabase(), entry, + journalField, ce)) + .count()) + .sum(); if (count == 0) { return Localization.lang("No journal names could be unabbreviated."); } @@ -135,4 +142,5 @@ private String unabbreviate(BibDatabaseContext databaseContext, List e tabSupplier.get().markBaseChanged(); return Localization.lang("Unabbreviated %0 journal names.", String.valueOf(count)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/journals/AbbreviationType.java b/jabgui/src/main/java/org/jabref/gui/journals/AbbreviationType.java index 221141bf7cc..d82906dfbc4 100644 --- a/jabgui/src/main/java/org/jabref/gui/journals/AbbreviationType.java +++ b/jabgui/src/main/java/org/jabref/gui/journals/AbbreviationType.java @@ -3,14 +3,13 @@ /** * Defines the different abbreviation types that JabRef can operate with. * - * DEFAULT: Default abbreviation type, which is the standard behavior. - * DOTLESS: Abbreviation type that does not include dots in the abbreviation. - * SHORTEST_UNIQUE: Abbreviation type that generates the shortest unique abbreviation. - * LTWA: Abbreviation type that uses the LTWA (List of Title Word Abbreviations)/ISO4 method. + * DEFAULT: Default abbreviation type, which is the standard behavior. DOTLESS: + * Abbreviation type that does not include dots in the abbreviation. SHORTEST_UNIQUE: + * Abbreviation type that generates the shortest unique abbreviation. LTWA: Abbreviation + * type that uses the LTWA (List of Title Word Abbreviations)/ISO4 method. */ public enum AbbreviationType { - DEFAULT, - DOTLESS, - SHORTEST_UNIQUE, - LTWA + + DEFAULT, DOTLESS, SHORTEST_UNIQUE, LTWA + } diff --git a/jabgui/src/main/java/org/jabref/gui/journals/UndoableAbbreviator.java b/jabgui/src/main/java/org/jabref/gui/journals/UndoableAbbreviator.java index b4cb7199a93..d2b877a4f5d 100644 --- a/jabgui/src/main/java/org/jabref/gui/journals/UndoableAbbreviator.java +++ b/jabgui/src/main/java/org/jabref/gui/journals/UndoableAbbreviator.java @@ -15,11 +15,15 @@ // Undo redo stuff public class UndoableAbbreviator { + private final JournalAbbreviationRepository journalAbbreviationRepository; + private final AbbreviationType abbreviationType; + private final boolean useFJournalField; - public UndoableAbbreviator(JournalAbbreviationRepository journalAbbreviationRepository, AbbreviationType abbreviationType, boolean useFJournalField) { + public UndoableAbbreviator(JournalAbbreviationRepository journalAbbreviationRepository, + AbbreviationType abbreviationType, boolean useFJournalField) { this.journalAbbreviationRepository = journalAbbreviationRepository; this.abbreviationType = abbreviationType; this.useFJournalField = useFJournalField; @@ -27,11 +31,10 @@ public UndoableAbbreviator(JournalAbbreviationRepository journalAbbreviationRepo /** * Abbreviate the journal name of the given entry. - * - * @param database The database the entry belongs to, or null if no database. - * @param entry The entry to be treated. + * @param database The database the entry belongs to, or null if no database. + * @param entry The entry to be treated. * @param fieldName The field name (e.g. "journal") - * @param ce If the entry is changed, add an edit to this compound. + * @param ce If the entry is changed, add an edit to this compound. * @return true if the entry was changed, false otherwise. */ public boolean abbreviate(BibDatabase database, BibEntry entry, Field fieldName, CompoundEdit ce) { @@ -75,14 +78,11 @@ public boolean abbreviate(BibDatabase database, BibEntry entry, Field fieldName, private String getAbbreviatedName(Abbreviation text) { return switch (abbreviationType) { - case DEFAULT -> - text.getAbbreviation(); - case DOTLESS -> - text.getDotlessAbbreviation(); - case SHORTEST_UNIQUE -> - text.getShortestUniqueAbbreviation(); - default -> - throw new IllegalStateException("Unexpected value: %s".formatted(abbreviationType)); + case DEFAULT -> text.getAbbreviation(); + case DOTLESS -> text.getDotlessAbbreviation(); + case SHORTEST_UNIQUE -> text.getShortestUniqueAbbreviation(); + default -> throw new IllegalStateException("Unexpected value: %s".formatted(abbreviationType)); }; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/journals/UndoableUnabbreviator.java b/jabgui/src/main/java/org/jabref/gui/journals/UndoableUnabbreviator.java index acb101aef58..d06ac74c148 100644 --- a/jabgui/src/main/java/org/jabref/gui/journals/UndoableUnabbreviator.java +++ b/jabgui/src/main/java/org/jabref/gui/journals/UndoableUnabbreviator.java @@ -21,10 +21,9 @@ public UndoableUnabbreviator(JournalAbbreviationRepository journalAbbreviationRe /** * Unabbreviate the journal name of the given entry. - * * @param entry The entry to be treated. * @param field The field - * @param ce If the entry is changed, add an edit to this compound. + * @param ce If the entry is changed, add an edit to this compound. * @return true if the entry was changed, false otherwise. */ public boolean unabbreviate(BibDatabase database, BibEntry entry, Field field, CompoundEdit ce) { @@ -58,7 +57,8 @@ public boolean unabbreviate(BibDatabase database, BibEntry entry, Field field, C } public boolean restoreFromFJournal(BibEntry entry, Field field, CompoundEdit ce) { - if ((StandardField.JOURNAL != field && StandardField.JOURNALTITLE != field) || !entry.hasField(AMSField.FJOURNAL)) { + if ((StandardField.JOURNAL != field && StandardField.JOURNALTITLE != field) + || !entry.hasField(AMSField.FJOURNAL)) { return false; } @@ -73,4 +73,5 @@ public boolean restoreFromFJournal(BibEntry entry, Field field, CompoundEdit ce) return true; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java b/jabgui/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java index 8c199b02c97..b9c12bf584a 100644 --- a/jabgui/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java +++ b/jabgui/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java @@ -107,5 +107,5 @@ public static void call(CodeArea codeArea, KeyEvent event, KeyBindingRepository } }); } -} +} diff --git a/jabgui/src/main/java/org/jabref/gui/keyboard/KeyBinding.java b/jabgui/src/main/java/org/jabref/gui/keyboard/KeyBinding.java index 0002b8dd0dc..89b0c2c148f 100644 --- a/jabgui/src/main/java/org/jabref/gui/keyboard/KeyBinding.java +++ b/jabgui/src/main/java/org/jabref/gui/keyboard/KeyBinding.java @@ -3,79 +3,117 @@ import org.jabref.logic.l10n.Localization; /** - * @implNote Cannot be sorted alphabetically, as {@link KeyBindingRepository#getKeyCombination(KeyBinding)} iterates over the enum in order and returns the first match. + * @implNote Cannot be sorted alphabetically, as + * {@link KeyBindingRepository#getKeyCombination(KeyBinding)} iterates over the enum in + * order and returns the first match. */ public enum KeyBinding { + EDITOR_DELETE("Delete", Localization.lang("Delete text"), "", KeyBindingCategory.EDITOR), // DELETE BACKWARDS = Rubout EDITOR_BACKWARD("Move caret left", Localization.lang("Move caret left"), "", KeyBindingCategory.EDITOR), EDITOR_FORWARD("Move caret right", Localization.lang("Move caret right"), "", KeyBindingCategory.EDITOR), - EDITOR_WORD_BACKWARD("Move caret to previous word", Localization.lang("Move caret to previous word"), "", KeyBindingCategory.EDITOR), - EDITOR_WORD_FORWARD("Move caret to next word", Localization.lang("Move caret to next word"), "", KeyBindingCategory.EDITOR), - EDITOR_BEGINNING("Move caret to beginning of line", Localization.lang("Move caret to beginning of line"), "", KeyBindingCategory.EDITOR), + EDITOR_WORD_BACKWARD("Move caret to previous word", Localization.lang("Move caret to previous word"), "", + KeyBindingCategory.EDITOR), + EDITOR_WORD_FORWARD("Move caret to next word", Localization.lang("Move caret to next word"), "", + KeyBindingCategory.EDITOR), + EDITOR_BEGINNING("Move caret to beginning of line", Localization.lang("Move caret to beginning of line"), "", + KeyBindingCategory.EDITOR), EDITOR_END("Move caret to of line", Localization.lang("Move caret to end of line"), "", KeyBindingCategory.EDITOR), - EDITOR_BEGINNING_DOC("Move caret to beginning of text", Localization.lang("Move the caret to the beginning of text"), "", KeyBindingCategory.EDITOR), - EDITOR_END_DOC("Move caret to end of text", Localization.lang("Move the caret to the end of text"), "", KeyBindingCategory.EDITOR), + EDITOR_BEGINNING_DOC("Move caret to beginning of text", + Localization.lang("Move the caret to the beginning of text"), "", KeyBindingCategory.EDITOR), + EDITOR_END_DOC("Move caret to end of text", Localization.lang("Move the caret to the end of text"), "", + KeyBindingCategory.EDITOR), EDITOR_UP("Move caret up", Localization.lang("Move the caret up"), "", KeyBindingCategory.EDITOR), EDITOR_DOWN("Move caret down", Localization.lang("Move the caret down"), "", KeyBindingCategory.EDITOR), EDITOR_CAPITALIZE("Capitalize word", Localization.lang("Capitalize current word"), "", KeyBindingCategory.EDITOR), EDITOR_LOWERCASE("Lowercase word", Localization.lang("Make current word lowercase"), "", KeyBindingCategory.EDITOR), EDITOR_UPPERCASE("Uppercase word", Localization.lang("Make current word uppercase"), "", KeyBindingCategory.EDITOR), - EDITOR_KILL_LINE("Remove all characters caret to end of line", Localization.lang("Remove line after caret"), "", KeyBindingCategory.EDITOR), - EDITOR_KILL_WORD("Remove characters until next word", Localization.lang("Remove characters until next word"), "", KeyBindingCategory.EDITOR), - EDITOR_KILL_WORD_BACKWARD("Characters until previous word", Localization.lang("Remove the current word backwards"), "", KeyBindingCategory.EDITOR), + EDITOR_KILL_LINE("Remove all characters caret to end of line", Localization.lang("Remove line after caret"), "", + KeyBindingCategory.EDITOR), + EDITOR_KILL_WORD("Remove characters until next word", Localization.lang("Remove characters until next word"), "", + KeyBindingCategory.EDITOR), + EDITOR_KILL_WORD_BACKWARD("Characters until previous word", Localization.lang("Remove the current word backwards"), + "", KeyBindingCategory.EDITOR), ABBREVIATE("Abbreviate", Localization.lang("Abbreviate journal names"), "ctrl+alt+A", KeyBindingCategory.TOOLS), - AUTOGENERATE_CITATION_KEYS("Autogenerate citation keys", Localization.lang("Autogenerate citation keys"), "ctrl+G", KeyBindingCategory.QUALITY), + AUTOGENERATE_CITATION_KEYS("Autogenerate citation keys", Localization.lang("Autogenerate citation keys"), "ctrl+G", + KeyBindingCategory.QUALITY), ACCEPT("Accept", Localization.lang("Accept"), "ctrl+ENTER", KeyBindingCategory.EDIT), - AUTOMATICALLY_LINK_FILES("Automatically link files", Localization.lang("Automatically set file links"), "F7", KeyBindingCategory.QUALITY), + AUTOMATICALLY_LINK_FILES("Automatically link files", Localization.lang("Automatically set file links"), "F7", + KeyBindingCategory.QUALITY), CHECK_INTEGRITY("Check integrity", Localization.lang("Check integrity"), "ctrl+F8", KeyBindingCategory.QUALITY), - CHECK_CONSISTENCY("Check consistency", Localization.lang("Check consistency"), "ctrl+F9", KeyBindingCategory.QUALITY), + CHECK_CONSISTENCY("Check consistency", Localization.lang("Check consistency"), "ctrl+F9", + KeyBindingCategory.QUALITY), CLEANUP("Clean up", Localization.lang("Clean up entries"), "alt+F8", KeyBindingCategory.QUALITY), CLOSE_DATABASE("Close library", Localization.lang("Close library"), "ctrl+W", KeyBindingCategory.FILE), CLOSE("Close dialog", Localization.lang("Close dialog"), "Esc", KeyBindingCategory.VIEW), COPY("Copy", Localization.lang("Copy"), "ctrl+C", KeyBindingCategory.EDIT), COPY_TITLE("Copy title", Localization.lang("Copy title"), "ctrl+shift+alt+T", KeyBindingCategory.EDIT), - // We migrated from "Copy \\cite{citation key}" to "Copy citation key with configured cite command", therefore we keep the "old string" for backwards comppatibility - COPY_CITE_CITATION_KEY("Copy \\cite{citation key}", Localization.lang("Copy citation key with configured cite command"), "ctrl+K", KeyBindingCategory.EDIT), + // We migrated from "Copy \\cite{citation key}" to "Copy citation key with configured + // cite command", therefore we keep the "old string" for backwards comppatibility + COPY_CITE_CITATION_KEY("Copy \\cite{citation key}", + Localization.lang("Copy citation key with configured cite command"), "ctrl+K", KeyBindingCategory.EDIT), - COPY_CITATION_KEY("Copy citation key", Localization.lang("Copy citation key"), "ctrl+shift+K", KeyBindingCategory.EDIT), - COPY_CITATION_KEY_AND_TITLE("Copy citation key and title", Localization.lang("Copy citation key and title"), "ctrl+shift+alt+K", KeyBindingCategory.EDIT), - COPY_CITATION_KEY_AND_LINK("Copy citation key and link", Localization.lang("Copy citation key and link"), "ctrl+alt+K", KeyBindingCategory.EDIT), + COPY_CITATION_KEY("Copy citation key", Localization.lang("Copy citation key"), "ctrl+shift+K", + KeyBindingCategory.EDIT), + COPY_CITATION_KEY_AND_TITLE("Copy citation key and title", Localization.lang("Copy citation key and title"), + "ctrl+shift+alt+K", KeyBindingCategory.EDIT), + COPY_CITATION_KEY_AND_LINK("Copy citation key and link", Localization.lang("Copy citation key and link"), + "ctrl+alt+K", KeyBindingCategory.EDIT), COPY_PREVIEW("Copy preview", Localization.lang("Copy preview"), "ctrl+shift+C", KeyBindingCategory.VIEW), CUT("Cut", Localization.lang("Cut"), "ctrl+X", KeyBindingCategory.EDIT), - // We have to put Entry Editor Previous before, because otherwise the decrease font size is found first - ENTRY_EDITOR_PREVIOUS_PANEL_2("Entry editor, previous panel 2", Localization.lang("Entry editor, previous panel 2"), "ctrl+MINUS", KeyBindingCategory.VIEW), + // We have to put Entry Editor Previous before, because otherwise the decrease font + // size is found first + ENTRY_EDITOR_PREVIOUS_PANEL_2("Entry editor, previous panel 2", Localization.lang("Entry editor, previous panel 2"), + "ctrl+MINUS", KeyBindingCategory.VIEW), DELETE_ENTRY("Delete entry", Localization.lang("Delete entry"), "DELETE", KeyBindingCategory.BIBTEX), - DEFAULT_DIALOG_ACTION("Execute default action in dialog", Localization.lang("Execute default action in dialog"), "ctrl+ENTER", KeyBindingCategory.VIEW), - DOWNLOAD_FULL_TEXT("Download full text documents", Localization.lang("Download full text documents"), "alt+F7", KeyBindingCategory.QUALITY), - OPEN_CLOSE_ENTRY_EDITOR("Open / close entry editor", Localization.lang("Open / close entry editor"), "ctrl+E", KeyBindingCategory.VIEW), + DEFAULT_DIALOG_ACTION("Execute default action in dialog", Localization.lang("Execute default action in dialog"), + "ctrl+ENTER", KeyBindingCategory.VIEW), + DOWNLOAD_FULL_TEXT("Download full text documents", Localization.lang("Download full text documents"), "alt+F7", + KeyBindingCategory.QUALITY), + OPEN_CLOSE_ENTRY_EDITOR("Open / close entry editor", Localization.lang("Open / close entry editor"), "ctrl+E", + KeyBindingCategory.VIEW), EXPORT("Export", Localization.lang("Export"), "ctrl+alt+e", KeyBindingCategory.FILE), - EXPORT_SELECTED("Export Selected", Localization.lang("Export selected entries"), "ctrl+shift+e", KeyBindingCategory.FILE), + EXPORT_SELECTED("Export Selected", Localization.lang("Export selected entries"), "ctrl+shift+e", + KeyBindingCategory.FILE), EDIT_STRINGS("Edit strings", Localization.lang("Edit strings"), "ctrl+T", KeyBindingCategory.BIBTEX), - ENTRY_EDITOR_NEXT_ENTRY("Entry editor, next entry", Localization.lang("Entry editor, next entry"), "alt+DOWN", KeyBindingCategory.VIEW), - ENTRY_EDITOR_NEXT_PANEL("Entry editor, next panel", Localization.lang("Entry editor, next panel"), "ctrl+TAB", KeyBindingCategory.VIEW), - ENTRY_EDITOR_NEXT_PANEL_2("Entry editor, next panel 2", Localization.lang("Entry editor, next panel 2"), "ctrl+PLUS", KeyBindingCategory.VIEW), - ENTRY_EDITOR_PREVIOUS_ENTRY("Entry editor, previous entry", Localization.lang("Entry editor, previous entry"), "alt+UP", KeyBindingCategory.VIEW), - ENTRY_EDITOR_PREVIOUS_PANEL("Entry editor, previous panel", Localization.lang("Entry editor, previous panel"), "ctrl+shift+TAB", KeyBindingCategory.VIEW), - FILE_LIST_EDITOR_MOVE_ENTRY_DOWN("File list editor, move entry down", Localization.lang("File list editor, move entry down"), "ctrl+DOWN", KeyBindingCategory.VIEW), - FILE_LIST_EDITOR_MOVE_ENTRY_UP("File list editor, move entry up", Localization.lang("File list editor, move entry up"), "ctrl+UP", KeyBindingCategory.VIEW), - FIND_UNLINKED_FILES("Search for unlinked local files", Localization.lang("Search for unlinked local files"), "shift+F7", KeyBindingCategory.QUALITY), + ENTRY_EDITOR_NEXT_ENTRY("Entry editor, next entry", Localization.lang("Entry editor, next entry"), "alt+DOWN", + KeyBindingCategory.VIEW), + ENTRY_EDITOR_NEXT_PANEL("Entry editor, next panel", Localization.lang("Entry editor, next panel"), "ctrl+TAB", + KeyBindingCategory.VIEW), + ENTRY_EDITOR_NEXT_PANEL_2("Entry editor, next panel 2", Localization.lang("Entry editor, next panel 2"), + "ctrl+PLUS", KeyBindingCategory.VIEW), + ENTRY_EDITOR_PREVIOUS_ENTRY("Entry editor, previous entry", Localization.lang("Entry editor, previous entry"), + "alt+UP", KeyBindingCategory.VIEW), + ENTRY_EDITOR_PREVIOUS_PANEL("Entry editor, previous panel", Localization.lang("Entry editor, previous panel"), + "ctrl+shift+TAB", KeyBindingCategory.VIEW), + FILE_LIST_EDITOR_MOVE_ENTRY_DOWN("File list editor, move entry down", + Localization.lang("File list editor, move entry down"), "ctrl+DOWN", KeyBindingCategory.VIEW), + FILE_LIST_EDITOR_MOVE_ENTRY_UP("File list editor, move entry up", + Localization.lang("File list editor, move entry up"), "ctrl+UP", KeyBindingCategory.VIEW), + FIND_UNLINKED_FILES("Search for unlinked local files", Localization.lang("Search for unlinked local files"), + "shift+F7", KeyBindingCategory.QUALITY), FOCUS_ENTRY_TABLE("Focus entry table", Localization.lang("Focus entry table"), "alt+1", KeyBindingCategory.VIEW), FOCUS_GROUP_LIST("Focus group list", Localization.lang("Focus group list"), "alt+s", KeyBindingCategory.VIEW), HELP("Help", Localization.lang("Help"), "F1", KeyBindingCategory.FILE), - IMPORT_INTO_CURRENT_DATABASE("Import into current library", Localization.lang("Import into current library"), "ctrl+I", KeyBindingCategory.FILE), - IMPORT_INTO_NEW_DATABASE("Import into new library", Localization.lang("Import into new library"), "ctrl+alt+I", KeyBindingCategory.FILE), + IMPORT_INTO_CURRENT_DATABASE("Import into current library", Localization.lang("Import into current library"), + "ctrl+I", KeyBindingCategory.FILE), + IMPORT_INTO_NEW_DATABASE("Import into new library", Localization.lang("Import into new library"), "ctrl+alt+I", + KeyBindingCategory.FILE), MERGE_ENTRIES("Merge entries", Localization.lang("Merge entries"), "ctrl+M", KeyBindingCategory.TOOLS), ADD_ENTRY("Add entry", Localization.lang("Add entry"), "ctrl+N", KeyBindingCategory.BIBTEX), - ADD_ENTRY_IDENTIFIER("Enter identifier", Localization.lang("Enter identifier"), "ctrl+alt+shift+N", KeyBindingCategory.BIBTEX), - ADD_ENTRY_PLAINTEXT("Interpret citations", Localization.lang("Interpret citations"), "ctrl+shift+N", KeyBindingCategory.BIBTEX), + ADD_ENTRY_IDENTIFIER("Enter identifier", Localization.lang("Enter identifier"), "ctrl+alt+shift+N", + KeyBindingCategory.BIBTEX), + ADD_ENTRY_PLAINTEXT("Interpret citations", Localization.lang("Interpret citations"), "ctrl+shift+N", + KeyBindingCategory.BIBTEX), NEW_ARTICLE("New article", Localization.lang("New article"), "ctrl+shift+A", KeyBindingCategory.BIBTEX), NEW_BOOK("New book", Localization.lang("New book"), "ctrl+shift+B", KeyBindingCategory.BIBTEX), NEW_INBOOK("New inbook", Localization.lang("New inbook"), "ctrl+shift+I", KeyBindingCategory.BIBTEX), - NEW_MASTERSTHESIS("New mastersthesis", Localization.lang("New mastersthesis"), "ctrl+shift+M", KeyBindingCategory.BIBTEX), + NEW_MASTERSTHESIS("New mastersthesis", Localization.lang("New mastersthesis"), "ctrl+shift+M", + KeyBindingCategory.BIBTEX), NEW_PHDTHESIS("New phdthesis", Localization.lang("New phdthesis"), "ctrl+shift+T", KeyBindingCategory.BIBTEX), NEW_PROCEEDINGS("New proceedings", Localization.lang("New proceedings"), "ctrl+shift+P", KeyBindingCategory.BIBTEX), NEW_UNPUBLISHED("New unpublished", Localization.lang("New unpublished"), "ctrl+shift+U", KeyBindingCategory.BIBTEX), @@ -84,52 +122,74 @@ public enum KeyBinding { NEXT_PREVIEW_LAYOUT("Next preview layout", Localization.lang("Next preview layout"), "F9", KeyBindingCategory.VIEW), NEXT_LIBRARY("Next library", Localization.lang("Next library"), "ctrl+PAGE_DOWN", KeyBindingCategory.VIEW), - OPEN_CONSOLE("Open terminal here", Localization.lang("Open terminal here"), "ctrl+shift+L", KeyBindingCategory.TOOLS), + OPEN_CONSOLE("Open terminal here", Localization.lang("Open terminal here"), "ctrl+shift+L", + KeyBindingCategory.TOOLS), OPEN_DATABASE("Open library", Localization.lang("Open library"), "ctrl+O", KeyBindingCategory.FILE), OPEN_FILE("Open file", Localization.lang("Open file"), "F4", KeyBindingCategory.TOOLS), OPEN_FOLDER("Open folder", Localization.lang("Open folder"), "ctrl+shift+O", KeyBindingCategory.TOOLS), - OPEN_OPEN_OFFICE_LIBRE_OFFICE_CONNECTION("Open OpenOffice/LibreOffice connection", Localization.lang("Open OpenOffice/LibreOffice connection"), "alt+0", KeyBindingCategory.TOOLS), + OPEN_OPEN_OFFICE_LIBRE_OFFICE_CONNECTION("Open OpenOffice/LibreOffice connection", + Localization.lang("Open OpenOffice/LibreOffice connection"), "alt+0", KeyBindingCategory.TOOLS), SHOW_PREFS("Preferences", Localization.lang("Preferences"), "ctrl+,", KeyBindingCategory.FILE), OPEN_URL_OR_DOI("Open URL or DOI", Localization.lang("Open URL or DOI"), "F3", KeyBindingCategory.TOOLS), PASTE("Paste", Localization.lang("Paste"), "ctrl+V", KeyBindingCategory.EDIT), - PULL_CHANGES_FROM_SHARED_DATABASE("Pull changes from shared database", Localization.lang("Pull changes from shared database"), "ctrl+shift+R", KeyBindingCategory.FILE), - PREVIOUS_PREVIEW_LAYOUT("Previous preview layout", Localization.lang("Previous preview layout"), "shift+F9", KeyBindingCategory.VIEW), - PREVIOUS_LIBRARY("Previous library", Localization.lang("Previous library"), "ctrl+PAGE_UP", KeyBindingCategory.VIEW), - SCROLL_TO_NEXT_MATCH_CATEGORY("Scroll to next match category", Localization.lang("Scroll to next match category"), "right", KeyBindingCategory.VIEW), - SCROLL_TO_PREVIOUS_MATCH_CATEGORY("Scroll to previous match category", Localization.lang("Scroll to previous match category"), "left", KeyBindingCategory.VIEW), - PUSH_TO_APPLICATION("Push to application", Localization.lang("Push to application"), "ctrl+L", KeyBindingCategory.TOOLS), + PULL_CHANGES_FROM_SHARED_DATABASE("Pull changes from shared database", + Localization.lang("Pull changes from shared database"), "ctrl+shift+R", KeyBindingCategory.FILE), + PREVIOUS_PREVIEW_LAYOUT("Previous preview layout", Localization.lang("Previous preview layout"), "shift+F9", + KeyBindingCategory.VIEW), + PREVIOUS_LIBRARY("Previous library", Localization.lang("Previous library"), "ctrl+PAGE_UP", + KeyBindingCategory.VIEW), + SCROLL_TO_NEXT_MATCH_CATEGORY("Scroll to next match category", Localization.lang("Scroll to next match category"), + "right", KeyBindingCategory.VIEW), + SCROLL_TO_PREVIOUS_MATCH_CATEGORY("Scroll to previous match category", + Localization.lang("Scroll to previous match category"), "left", KeyBindingCategory.VIEW), + PUSH_TO_APPLICATION("Push to application", Localization.lang("Push to application"), "ctrl+L", + KeyBindingCategory.TOOLS), QUIT_JABREF("Quit JabRef", Localization.lang("Quit JabRef"), "ctrl+Q", KeyBindingCategory.FILE), REDO("Redo", Localization.lang("Redo"), "ctrl+Y", KeyBindingCategory.EDIT), - REFRESH_OO("Refresh OO", Localization.lang("Refresh OpenOffice/LibreOffice"), "ctrl+alt+O", KeyBindingCategory.TOOLS), + REFRESH_OO("Refresh OO", Localization.lang("Refresh OpenOffice/LibreOffice"), "ctrl+alt+O", + KeyBindingCategory.TOOLS), REPLACE_STRING("Replace string", Localization.lang("Replace string"), "ctrl+R", KeyBindingCategory.SEARCH), - RESOLVE_DUPLICATE_CITATION_KEYS("Resolve duplicate citation keys", Localization.lang("Resolve duplicate citation keys"), "ctrl+shift+D", KeyBindingCategory.BIBTEX), + RESOLVE_DUPLICATE_CITATION_KEYS("Resolve duplicate citation keys", + Localization.lang("Resolve duplicate citation keys"), "ctrl+shift+D", KeyBindingCategory.BIBTEX), SAVE_ALL("Save all", Localization.lang("Save all"), "ctrl+alt+S", KeyBindingCategory.FILE), SAVE_DATABASE("Save library", Localization.lang("Save library"), "ctrl+S", KeyBindingCategory.FILE), - SAVE_DATABASE_AS("Save library as ...", Localization.lang("Save library as..."), "ctrl+shift+S", KeyBindingCategory.FILE), + SAVE_DATABASE_AS("Save library as ...", Localization.lang("Save library as..."), "ctrl+shift+S", + KeyBindingCategory.FILE), SEARCH("Search", Localization.lang("Search"), "ctrl+F", KeyBindingCategory.SEARCH), - OPEN_GLOBAL_SEARCH_DIALOG("Open global search window", Localization.lang("Open global search window"), "ctrl+shift+F", KeyBindingCategory.SEARCH), + OPEN_GLOBAL_SEARCH_DIALOG("Open global search window", Localization.lang("Open global search window"), + "ctrl+shift+F", KeyBindingCategory.SEARCH), SELECT_ALL("Select all", Localization.lang("Select all"), "ctrl+A", KeyBindingCategory.EDIT), SELECT_FIRST_ENTRY("Select first entry", Localization.lang("Select first entry"), "HOME", KeyBindingCategory.EDIT), SELECT_LAST_ENTRY("Select last entry", Localization.lang("Select last entry"), "END", KeyBindingCategory.EDIT), - STRING_DIALOG_ADD_STRING("String dialog, add string", Localization.lang("String dialog, add string"), "ctrl+N", KeyBindingCategory.FILE), - STRING_DIALOG_REMOVE_STRING("String dialog, remove string", Localization.lang("String dialog, remove string"), "shift+DELETE", KeyBindingCategory.FILE), - SYNCHRONIZE_FILES("Synchronize files", Localization.lang("Synchronize files"), "ctrl+shift+F7", KeyBindingCategory.QUALITY), - TOGGLE_GROUPS_INTERFACE("Toggle groups interface", Localization.lang("Toggle groups interface"), "alt+3", KeyBindingCategory.VIEW), + STRING_DIALOG_ADD_STRING("String dialog, add string", Localization.lang("String dialog, add string"), "ctrl+N", + KeyBindingCategory.FILE), + STRING_DIALOG_REMOVE_STRING("String dialog, remove string", Localization.lang("String dialog, remove string"), + "shift+DELETE", KeyBindingCategory.FILE), + SYNCHRONIZE_FILES("Synchronize files", Localization.lang("Synchronize files"), "ctrl+shift+F7", + KeyBindingCategory.QUALITY), + TOGGLE_GROUPS_INTERFACE("Toggle groups interface", Localization.lang("Toggle groups interface"), "alt+3", + KeyBindingCategory.VIEW), UNABBREVIATE("Unabbreviate", Localization.lang("Unabbreviate"), "ctrl+alt+shift+A", KeyBindingCategory.TOOLS), UNDO("Undo", Localization.lang("Undo"), "ctrl+Z", KeyBindingCategory.EDIT), WEB_SEARCH("Web search", Localization.lang("Web search"), "alt+4", KeyBindingCategory.SEARCH), - WRITE_METADATA_TO_PDF("Write metadata to PDF files", Localization.lang("Write metadata to PDF files"), "F6", KeyBindingCategory.TOOLS), + WRITE_METADATA_TO_PDF("Write metadata to PDF files", Localization.lang("Write metadata to PDF files"), "F6", + KeyBindingCategory.TOOLS), CLEAR_SEARCH("Clear search", Localization.lang("Clear search"), "Esc", KeyBindingCategory.SEARCH), CLEAR_READ_STATUS("Clear read status", Localization.lang("Clear read status"), "", KeyBindingCategory.EDIT), READ("Set read status to read", Localization.lang("Set read status to read"), "", KeyBindingCategory.EDIT), SKIMMED("Set read status to skimmed", Localization.lang("Set read status to skimmed"), "", KeyBindingCategory.EDIT), GROUP_SUBGROUP_RENAME("Rename group", Localization.lang("Rename group"), "F2", KeyBindingCategory.EDIT), - MERGE_WITH_FETCHED_ENTRY("Get bibliographic data from %0, \"DOI/ISBN/...\"", Localization.lang("Get bibliographic data from %0", "DOI/ISBN/..."), "F5", KeyBindingCategory.EDIT), - LOOKUP_DOC_IDENTIFIER("Search document identifier online", Localization.lang("Search document identifier online"), "alt+F", KeyBindingCategory.EDIT); + MERGE_WITH_FETCHED_ENTRY("Get bibliographic data from %0, \"DOI/ISBN/...\"", + Localization.lang("Get bibliographic data from %0", "DOI/ISBN/..."), "F5", KeyBindingCategory.EDIT), + LOOKUP_DOC_IDENTIFIER("Search document identifier online", Localization.lang("Search document identifier online"), + "alt+F", KeyBindingCategory.EDIT); private final String constant; + private final String localization; + private final String defaultBinding; + private final KeyBindingCategory category; KeyBinding(String constantName, String localization, String defaultKeyBinding, KeyBindingCategory category) { @@ -152,7 +212,6 @@ public String getLocalization() { /** * This method returns the default key binding, the key(s) which are assigned - * * @return The default key binding */ public String getDefaultKeyBinding() { @@ -162,4 +221,5 @@ public String getDefaultKeyBinding() { public KeyBindingCategory getCategory() { return category; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/keyboard/KeyBindingCategory.java b/jabgui/src/main/java/org/jabref/gui/keyboard/KeyBindingCategory.java index ae8cb0b673f..d69f5c1d3a6 100644 --- a/jabgui/src/main/java/org/jabref/gui/keyboard/KeyBindingCategory.java +++ b/jabgui/src/main/java/org/jabref/gui/keyboard/KeyBindingCategory.java @@ -5,14 +5,9 @@ public enum KeyBindingCategory { - FILE(Localization.lang("File")), - EDIT(Localization.lang("Edit")), - SEARCH(Localization.lang("Search")), - VIEW(Localization.lang("View")), - BIBTEX(BibDatabaseMode.BIBTEX.getFormattedName()), - QUALITY(Localization.lang("Quality")), - TOOLS(Localization.lang("Tools")), - EDITOR(Localization.lang("Text editor")); + FILE(Localization.lang("File")), EDIT(Localization.lang("Edit")), SEARCH(Localization.lang("Search")), + VIEW(Localization.lang("View")), BIBTEX(BibDatabaseMode.BIBTEX.getFormattedName()), + QUALITY(Localization.lang("Quality")), TOOLS(Localization.lang("Tools")), EDITOR(Localization.lang("Text editor")); private final String name; @@ -23,4 +18,5 @@ public enum KeyBindingCategory { public String getName() { return name; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/keyboard/KeyBindingRepository.java b/jabgui/src/main/java/org/jabref/gui/keyboard/KeyBindingRepository.java index ce6fece4fda..7468eb729eb 100644 --- a/jabgui/src/main/java/org/jabref/gui/keyboard/KeyBindingRepository.java +++ b/jabgui/src/main/java/org/jabref/gui/keyboard/KeyBindingRepository.java @@ -20,8 +20,8 @@ import org.jabref.logic.os.OS; /** - * Handles keyboard shortcuts. Including checking whether a keybinding matches. - * See {@link #matches}. + * Handles keyboard shortcuts. Including checking whether a keybinding matches. See + * {@link #matches}. */ public class KeyBindingRepository { @@ -39,14 +39,16 @@ public KeyBindingRepository(SortedMap bindings) { } public KeyBindingRepository(List bindNames, List bindings) { - this.bindings = new SimpleMapProperty<>(FXCollections.observableMap(new TreeMap<>(Comparator.comparing(KeyBinding::getLocalization)))); + this.bindings = new SimpleMapProperty<>( + FXCollections.observableMap(new TreeMap<>(Comparator.comparing(KeyBinding::getLocalization)))); if ((bindNames.isEmpty()) || (bindings.isEmpty()) || (bindNames.size() != bindings.size())) { // Use default key bindings for (KeyBinding keyBinding : KeyBinding.values()) { put(keyBinding, keyBinding.getDefaultKeyBinding()); } - } else { + } + else { for (int i = 0; i < bindNames.size(); i++) { put(bindNames.get(i), bindings.get(i)); } @@ -55,9 +57,8 @@ public KeyBindingRepository(List bindNames, List bindings) { /** * Check if the given keyCombination equals the given keyEvent - * * @param combination as KeyCombination - * @param keyEvent as KeEvent + * @param keyEvent as KeEvent * @return true if matching, else false */ public static boolean checkKeyCombinationEquality(KeyCombination combination, KeyEvent keyEvent) { @@ -79,9 +80,11 @@ public String get(String key) { if (result.isPresent()) { return result.get(); - } else if (keyBinding.isPresent()) { + } + else if (keyBinding.isPresent()) { return keyBinding.get().getDefaultKeyBinding(); - } else { + } + else { return "Not associated"; } } @@ -118,9 +121,11 @@ public int size() { } /** - * Searches the key bindings for the given KeyEvent. Only the first matching key binding is returned. + * Searches the key bindings for the given KeyEvent. Only the first matching key + * binding is returned. *

    - * If you need all matching key bindings, use {@link #mapToKeyBindings(KeyEvent)} instead. + * If you need all matching key bindings, use {@link #mapToKeyBindings(KeyEvent)} + * instead. */ public Optional mapToKeyBinding(KeyEvent keyEvent) { for (KeyBinding binding : KeyBinding.values()) { @@ -136,19 +141,18 @@ public Optional mapToKeyBinding(KeyEvent keyEvent) { */ private Set mapToKeyBindings(KeyEvent keyEvent) { return Arrays.stream(KeyBinding.values()) - .filter(binding -> checkKeyCombinationEquality(binding, keyEvent)) - .collect(Collectors.toSet()); + .filter(binding -> checkKeyCombinationEquality(binding, keyEvent)) + .collect(Collectors.toSet()); } /** * Checks if the given KeyEvent matches the given KeyBinding. *

    - * Used if a keyboard shortcut leads to multiple actions (e.g., ESC for closing a dialog and clearing the search). + * Used if a keyboard shortcut leads to multiple actions (e.g., ESC for closing a + * dialog and clearing the search). */ public boolean matches(KeyEvent event, KeyBinding keyBinding) { - return mapToKeyBindings(event) - .stream() - .anyMatch(binding -> binding == keyBinding); + return mapToKeyBindings(event).stream().anyMatch(binding -> binding == keyBinding); } public Optional getKeyCombination(KeyBinding bindName) { @@ -164,14 +168,13 @@ public Optional getKeyCombination(KeyBinding bindName) { /** * Check if the given KeyBinding equals the given keyEvent - * - * @param binding as KeyBinding + * @param binding as KeyBinding * @param keyEvent as KeEvent * @return true if matching, else false */ public boolean checkKeyCombinationEquality(KeyBinding binding, KeyEvent keyEvent) { return getKeyCombination(binding).filter(combination -> checkKeyCombinationEquality(combination, keyEvent)) - .isPresent(); + .isPresent(); } public List getBindNames() { @@ -204,4 +207,5 @@ public boolean equals(Object o) { public int hashCode() { return bindings.hashCode(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/keyboard/SelectableTextFlowKeyBindings.java b/jabgui/src/main/java/org/jabref/gui/keyboard/SelectableTextFlowKeyBindings.java index 377b1f8f0c4..51e33e7cb72 100644 --- a/jabgui/src/main/java/org/jabref/gui/keyboard/SelectableTextFlowKeyBindings.java +++ b/jabgui/src/main/java/org/jabref/gui/keyboard/SelectableTextFlowKeyBindings.java @@ -6,6 +6,7 @@ import org.jabref.gui.util.SelectableTextFlow; public class SelectableTextFlowKeyBindings { + public static void call(Scene scene, KeyEvent event, KeyBindingRepository keyBindingRepository) { if (scene.focusOwnerProperty().get() instanceof SelectableTextFlow selectableTextFlow) { keyBindingRepository.mapToKeyBinding(event).ifPresent(binding -> { @@ -22,4 +23,5 @@ public static void call(Scene scene, KeyEvent event, KeyBindingRepository keyBin }); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/keyboard/TextInputKeyBindings.java b/jabgui/src/main/java/org/jabref/gui/keyboard/TextInputKeyBindings.java index 7140fefe4a6..102723e8854 100644 --- a/jabgui/src/main/java/org/jabref/gui/keyboard/TextInputKeyBindings.java +++ b/jabgui/src/main/java/org/jabref/gui/keyboard/TextInputKeyBindings.java @@ -92,4 +92,5 @@ public static void call(Scene scene, KeyEvent event, KeyBindingRepository keyBin }); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/keyboard/WalkthroughKeyBindings.java b/jabgui/src/main/java/org/jabref/gui/keyboard/WalkthroughKeyBindings.java index ef25cfd3830..5c3a24ba034 100644 --- a/jabgui/src/main/java/org/jabref/gui/keyboard/WalkthroughKeyBindings.java +++ b/jabgui/src/main/java/org/jabref/gui/keyboard/WalkthroughKeyBindings.java @@ -9,15 +9,19 @@ public class WalkthroughKeyBindings { /// Handles ESC key to quit active walkthrough with confirmation. /// - /// @param event the key event - /// @param stateManager the state manager + /// @param event the key event + /// @param stateManager the state manager /// @param keyBindingRepository the key binding repository public static void call(KeyEvent event, StateManager stateManager, KeyBindingRepository keyBindingRepository) { keyBindingRepository.mapToKeyBinding(event).ifPresent(binding -> { - if (binding == KeyBinding.CLOSE) { // NOTE: CLOSE is using Esc key. Therefore, we didn't introduce a new key binding entry since this would lead to conflicts with other key bindings. + if (binding == KeyBinding.CLOSE) { // NOTE: CLOSE is using Esc key. Therefore, + // we didn't introduce a new key binding + // entry since this would lead to conflicts + // with other key bindings. stateManager.getActiveWalkthrough().ifPresent(Walkthrough::showQuitConfirmationAndQuit); event.consume(); } }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/l10n/LocalizationLocator.java b/jabgui/src/main/java/org/jabref/gui/l10n/LocalizationLocator.java index 3d9776901c7..35fd684f4cb 100644 --- a/jabgui/src/main/java/org/jabref/gui/l10n/LocalizationLocator.java +++ b/jabgui/src/main/java/org/jabref/gui/l10n/LocalizationLocator.java @@ -7,8 +7,10 @@ import com.airhacks.afterburner.views.ResourceLocator; public class LocalizationLocator implements ResourceLocator { + @Override public ResourceBundle getResourceBundle(String s) { return Localization.getMessages(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/AbstractPropertiesTabView.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/AbstractPropertiesTabView.java index f967733b32c..fa5de128baf 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/AbstractPropertiesTabView.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/AbstractPropertiesTabView.java @@ -8,11 +8,14 @@ import jakarta.inject.Inject; -public abstract class AbstractPropertiesTabView extends VBox implements PropertiesTab { +public abstract class AbstractPropertiesTabView extends VBox + implements PropertiesTab { - @Inject protected DialogService dialogService; + @Inject + protected DialogService dialogService; protected BibDatabaseContext databaseContext; + protected T viewModel; @Override @@ -34,4 +37,5 @@ public void storeSettings() { public boolean validateSettings() { return viewModel.validateSettings(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesAction.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesAction.java index 1632c696d43..71a3e6e7bcd 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesAction.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesAction.java @@ -14,9 +14,11 @@ import static org.jabref.gui.actions.ActionHelper.needsDatabase; public class LibraryPropertiesAction extends SimpleCommand { + private static final Logger LOGGER = LoggerFactory.getLogger(LibraryPropertiesAction.class); private final StateManager stateManager; + private final Supplier alternateDatabase; public LibraryPropertiesAction(StateManager stateManager) { @@ -35,12 +37,16 @@ public void execute() { if (alternateDatabase != null) { dialogService.showCustomDialogAndWait(new LibraryPropertiesView(alternateDatabase.get())); - } else { + } + else { if (stateManager.getActiveDatabase().isPresent()) { - dialogService.showCustomDialogAndWait(new LibraryPropertiesView(stateManager.getActiveDatabase().get())); - } else { + dialogService + .showCustomDialogAndWait(new LibraryPropertiesView(stateManager.getActiveDatabase().get())); + } + else { LOGGER.warn("No library selected."); } } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesView.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesView.java index c5c41b458ae..4c834edfa4d 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesView.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesView.java @@ -17,26 +17,31 @@ public class LibraryPropertiesView extends BaseDialog { - @FXML private TabPane tabPane; - @FXML private ButtonType saveButton; + @FXML + private TabPane tabPane; + + @FXML + private ButtonType saveButton; - @Inject private ThemeManager themeManager; + @Inject + private ThemeManager themeManager; private final BibDatabaseContext databaseContext; + private LibraryPropertiesViewModel viewModel; public LibraryPropertiesView(BibDatabaseContext databaseContext) { this.databaseContext = databaseContext; - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); ControlHelper.setAction(saveButton, getDialogPane(), event -> savePreferencesAndCloseDialog()); if (databaseContext.getDatabasePath().isPresent()) { - setTitle(Localization.lang("%0 - Library properties", databaseContext.getDatabasePath().get().getFileName())); - } else { + setTitle(Localization.lang("%0 - Library properties", + databaseContext.getDatabasePath().get().getFileName())); + } + else { setTitle(Localization.lang("Library properties")); } @@ -71,4 +76,5 @@ private void savePreferencesAndCloseDialog() { close(); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesViewModel.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesViewModel.java index 9c2cd72039b..6d510e1ff07 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesViewModel.java @@ -15,14 +15,9 @@ public class LibraryPropertiesViewModel { private final List propertiesTabs; public LibraryPropertiesViewModel(BibDatabaseContext databaseContext) { - propertiesTabs = List.of( - new GeneralPropertiesView(databaseContext), - new SavingPropertiesView(databaseContext), - new KeyPatternPropertiesView(databaseContext), - new ConstantsPropertiesView(databaseContext), - new ContentSelectorView(databaseContext), - new PreamblePropertiesView(databaseContext) - ); + propertiesTabs = List.of(new GeneralPropertiesView(databaseContext), new SavingPropertiesView(databaseContext), + new KeyPatternPropertiesView(databaseContext), new ConstantsPropertiesView(databaseContext), + new ContentSelectorView(databaseContext), new PreamblePropertiesView(databaseContext)); } public void setValues() { @@ -40,7 +35,7 @@ public void storeAllSettings() { public boolean validateAllSettings() { for (PropertiesTab propertiesTab : propertiesTabs) { if (!propertiesTab.validateSettings()) { - return false; + return false; } } return true; @@ -49,4 +44,5 @@ public boolean validateAllSettings() { public List getPropertiesTabs() { return propertiesTabs; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/PropertiesTab.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/PropertiesTab.java index 87cc0e5b0b9..0bf8ca850ea 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/PropertiesTab.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/PropertiesTab.java @@ -3,6 +3,7 @@ import javafx.scene.Node; public interface PropertiesTab { + Node getBuilder(); String getTabName(); @@ -12,4 +13,5 @@ public interface PropertiesTab { void storeSettings(); boolean validateSettings(); + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/PropertiesTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/PropertiesTabViewModel.java index e14e84b1224..d0771257de5 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/PropertiesTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/PropertiesTabViewModel.java @@ -9,4 +9,5 @@ public interface PropertiesTabViewModel { default boolean validateSettings() { return true; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsItemModel.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsItemModel.java index fb262a59e80..4b5483cd5c1 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsItemModel.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsItemModel.java @@ -19,10 +19,13 @@ public class ConstantsItemModel { private final static Pattern IS_NUMBER = Pattern.compile("-?\\d+(\\.\\d+)?"); private final StringProperty labelProperty = new SimpleStringProperty(); + private final StringProperty contentProperty = new SimpleStringProperty(); private final Validator labelValidator; + private final Validator contentValidator; + private final CompositeValidator combinedValidator; public ConstantsItemModel(String label, String content) { @@ -65,15 +68,21 @@ public void setContent(String content) { private static ValidationMessage validateLabel(String input) { if (input == null) { return ValidationMessage.error("May not be null"); - } else if (input.trim().isEmpty()) { + } + else if (input.trim().isEmpty()) { return ValidationMessage.error(Localization.lang("Please enter the string's label")); - } else if (IS_NUMBER.matcher(input).matches()) { + } + else if (IS_NUMBER.matcher(input).matches()) { return ValidationMessage.error(Localization.lang("The label of the string cannot be a number.")); - } else if (input.contains("#")) { - return ValidationMessage.error(Localization.lang("The label of the string cannot contain the '#' character.")); - } else if (input.contains(" ")) { + } + else if (input.contains("#")) { + return ValidationMessage + .error(Localization.lang("The label of the string cannot contain the '#' character.")); + } + else if (input.contains(" ")) { return ValidationMessage.error(Localization.lang("The label of the string cannot contain spaces.")); - } else { + } + else { return null; // everything is ok } } @@ -81,10 +90,13 @@ private static ValidationMessage validateLabel(String input) { private static ValidationMessage validateContent(String input) { if (input == null) { return ValidationMessage.error(Localization.lang("Must not be empty!")); - } else if (input.trim().isEmpty()) { + } + else if (input.trim().isEmpty()) { return ValidationMessage.error(Localization.lang("Must not be empty!")); - } else { + } + else { return null; // everything is ok } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsPropertiesView.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsPropertiesView.java index 7c295f3c276..c5ec746fc3f 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsPropertiesView.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsPropertiesView.java @@ -22,23 +22,34 @@ import com.airhacks.afterburner.views.ViewLoader; import jakarta.inject.Inject; -public class ConstantsPropertiesView extends AbstractPropertiesTabView implements PropertiesTab { +public class ConstantsPropertiesView extends AbstractPropertiesTabView + implements PropertiesTab { - @FXML private TableView stringsList; - @FXML private TableColumn labelColumn; - @FXML private TableColumn contentColumn; - @FXML private TableColumn actionsColumn; - @FXML private Button addStringButton; + @FXML + private TableView stringsList; + + @FXML + private TableColumn labelColumn; + + @FXML + private TableColumn contentColumn; + + @FXML + private TableColumn actionsColumn; + + @FXML + private Button addStringButton; - @Inject private GuiPreferences preferences; - @Inject private DialogService dialogService; + @Inject + private GuiPreferences preferences; + + @Inject + private DialogService dialogService; public ConstantsPropertiesView(BibDatabaseContext databaseContext) { this.databaseContext = databaseContext; - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -47,7 +58,8 @@ public String getTabName() { } public void initialize() { - this.viewModel = new ConstantsPropertiesViewModel(databaseContext, dialogService, preferences.getExternalApplicationsPreferences()); + this.viewModel = new ConstantsPropertiesViewModel(databaseContext, dialogService, + preferences.getExternalApplicationsPreferences()); addStringButton.setTooltip(new Tooltip(Localization.lang("New string"))); @@ -56,26 +68,26 @@ public void initialize() { labelColumn.setCellValueFactory(cellData -> cellData.getValue().labelProperty()); new ViewModelTextFieldTableCellVisualizationFactory() - .withValidation(ConstantsItemModel::labelValidation) - .install(labelColumn, new DefaultStringConverter()); + .withValidation(ConstantsItemModel::labelValidation) + .install(labelColumn, new DefaultStringConverter()); labelColumn.setOnEditCommit((TableColumn.CellEditEvent cellEvent) -> { TableView tableView = cellEvent.getTableView(); - ConstantsItemModel cellItem = tableView.getItems() - .get(cellEvent.getTablePosition().getRow()); + ConstantsItemModel cellItem = tableView.getItems().get(cellEvent.getTablePosition().getRow()); Optional existingItem = viewModel.labelAlreadyExists(cellEvent.getNewValue()); if (existingItem.isPresent() && !existingItem.get().equals(cellItem)) { - dialogService.showErrorDialogAndWait(Localization.lang( - "A string with the label '%0' already exists.", - cellEvent.getNewValue())); + dialogService.showErrorDialogAndWait( + Localization.lang("A string with the label '%0' already exists.", cellEvent.getNewValue())); cellItem.setLabel(cellEvent.getOldValue()); - } else { + } + else { cellItem.setLabel(cellEvent.getNewValue()); } - // Resort the entries based on the keys and set the focus to the newly-created entry + // Resort the entries based on the keys and set the focus to the newly-created + // entry viewModel.resortStrings(); TableView.TableViewSelectionModel selectionModel = tableView.getSelectionModel(); selectionModel.select(cellItem); @@ -88,20 +100,19 @@ public void initialize() { contentColumn.setReorderable(false); contentColumn.setCellValueFactory(cellData -> cellData.getValue().contentProperty()); new ViewModelTextFieldTableCellVisualizationFactory() - .withValidation(ConstantsItemModel::contentValidation) - .install(contentColumn, new DefaultStringConverter()); - contentColumn.setOnEditCommit((TableColumn.CellEditEvent cell) -> - cell.getRowValue().setContent(cell.getNewValue())); + .withValidation(ConstantsItemModel::contentValidation) + .install(contentColumn, new DefaultStringConverter()); + contentColumn.setOnEditCommit((TableColumn.CellEditEvent cell) -> cell.getRowValue() + .setContent(cell.getNewValue())); actionsColumn.setSortable(false); actionsColumn.setReorderable(false); actionsColumn.setCellValueFactory(cellData -> cellData.getValue().labelProperty()); new ValueTableCellFactory() - .withGraphic(_ -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) - .withTooltip(label -> Localization.lang("Remove string %0", label)) - .withOnMouseClickedEvent(_ -> _ -> - viewModel.removeString(stringsList.getFocusModel().getFocusedItem())) - .install(actionsColumn); + .withGraphic(_ -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) + .withTooltip(label -> Localization.lang("Remove string %0", label)) + .withOnMouseClickedEvent(_ -> _ -> viewModel.removeString(stringsList.getFocusModel().getFocusedItem())) + .install(actionsColumn); stringsList.itemsProperty().bindBidirectional(viewModel.stringsListProperty()); stringsList.setEditable(true); @@ -117,4 +128,5 @@ private void addString() { private void openHelp() { viewModel.openHelpPage(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsPropertiesViewModel.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsPropertiesViewModel.java index 3d84596e872..2b703fdcf8d 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsPropertiesViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsPropertiesViewModel.java @@ -28,32 +28,36 @@ public class ConstantsPropertiesViewModel implements PropertiesTabViewModel { private static final String NEW_STRING_LABEL = "NewString"; // must not contain spaces - private final ListProperty stringsListProperty = - new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty stringsListProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); private final BooleanProperty validProperty = new SimpleBooleanProperty(); private final BibDatabaseContext databaseContext; private final DialogService dialogService; + private final ExternalApplicationsPreferences externalApplicationsPreferences; - public ConstantsPropertiesViewModel(BibDatabaseContext databaseContext, DialogService dialogService, ExternalApplicationsPreferences externalApplicationsPreferences) { + public ConstantsPropertiesViewModel(BibDatabaseContext databaseContext, DialogService dialogService, + ExternalApplicationsPreferences externalApplicationsPreferences) { this.databaseContext = databaseContext; this.dialogService = dialogService; this.externalApplicationsPreferences = externalApplicationsPreferences; - ObservableList> allValidProperty = - EasyBind.map(stringsListProperty, ConstantsItemModel::combinedValidationValidProperty); + ObservableList> allValidProperty = EasyBind.map(stringsListProperty, + ConstantsItemModel::combinedValidationValidProperty); validProperty.bind(EasyBind.combine(allValidProperty, stream -> stream.allMatch(valid -> valid))); } @Override public void setValues() { - stringsListProperty.addAll(databaseContext.getDatabase().getStringValues().stream() - .sorted(new BibtexStringComparator(false)) - .map(this::convertFromBibTexString) - .toList()); + stringsListProperty.addAll(databaseContext.getDatabase() + .getStringValues() + .stream() + .sorted(new BibtexStringComparator(false)) + .map(this::convertFromBibTexString) + .toList()); } public void addNewString() { @@ -64,7 +68,8 @@ public void addNewString() { i++; } newItem = new ConstantsItemModel(NEW_STRING_LABEL + i, ""); - } else { + } + else { newItem = new ConstantsItemModel(NEW_STRING_LABEL, ""); } @@ -86,9 +91,7 @@ private ConstantsItemModel convertFromBibTexString(BibtexString bibtexString) { @Override public void storeSettings() { - List strings = stringsListProperty.stream() - .map(this::fromBibtexStringViewModel) - .toList(); + List strings = stringsListProperty.stream().map(this::fromBibtexStringViewModel).toList(); databaseContext.getDatabase().setStrings(strings); } @@ -99,9 +102,7 @@ private BibtexString fromBibtexStringViewModel(ConstantsItemModel viewModel) { } public Optional labelAlreadyExists(String label) { - return stringsListProperty.stream() - .filter(item -> item.labelProperty().getValue().equals(label)) - .findFirst(); + return stringsListProperty.stream().filter(item -> item.labelProperty().getValue().equals(label)).findFirst(); } public void openHelpPage() { @@ -115,4 +116,5 @@ public ListProperty stringsListProperty() { public BooleanProperty validProperty() { return validProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/contentselectors/ContentSelectorView.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/contentselectors/ContentSelectorView.java index 90f354b71ce..6d990e97604 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/contentselectors/ContentSelectorView.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/contentselectors/ContentSelectorView.java @@ -22,22 +22,30 @@ public class ContentSelectorView extends AbstractPropertiesTabView { - @FXML private Button removeFieldNameButton; - @FXML private Button addKeywordButton; - @FXML private Button removeKeywordButton; - @FXML private ListView fieldsListView; - @FXML private ListView keywordsListView; + @FXML + private Button removeFieldNameButton; + + @FXML + private Button addKeywordButton; + + @FXML + private Button removeKeywordButton; - @Inject private DialogService dialogService; + @FXML + private ListView fieldsListView; + + @FXML + private ListView keywordsListView; + + @Inject + private DialogService dialogService; private final BibDatabaseContext databaseContext; public ContentSelectorView(BibDatabaseContext databaseContext) { this.databaseContext = databaseContext; - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -56,9 +64,7 @@ public void initialize() { private void initFieldNameComponents() { initListView(fieldsListView, viewModel::getFieldNamesBackingList); viewModel.selectedFieldProperty().bind(fieldsListView.getSelectionModel().selectedItemProperty()); - new ViewModelListCellFactory() - .withText(Field::getDisplayName) - .install(fieldsListView); + new ViewModelListCellFactory().withText(Field::getDisplayName).install(fieldsListView); removeFieldNameButton.disableProperty().bind(viewModel.isNoFieldNameSelected()); EasyBind.subscribe(viewModel.selectedFieldProperty(), viewModel::populateKeywords); } @@ -106,4 +112,5 @@ private Optional getSelectedField() { private Optional getSelectedKeyword() { return Optional.of(keywordsListView.getSelectionModel()).map(SelectionModel::getSelectedItem); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/contentselectors/ContentSelectorViewModel.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/contentselectors/ContentSelectorViewModel.java index 6633dbe1ac9..86df9c33e2d 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/contentselectors/ContentSelectorViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/contentselectors/ContentSelectorViewModel.java @@ -39,8 +39,11 @@ public class ContentSelectorViewModel implements PropertiesTabViewModel { private Map> fieldKeywordsMap = new HashMap<>(); private final ListProperty fields = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty keywords = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ObjectProperty selectedField = new SimpleObjectProperty<>(); + private final StringProperty selectedKeyword = new SimpleStringProperty(); ContentSelectorViewModel(BibDatabaseContext databaseContext, DialogService dialogService) { @@ -70,7 +73,8 @@ public void storeSettings() { if (ContentSelectors.isDefaultMap(fieldKeywordsMap)) { // Remove all fields of the content selector fieldNamesToRemove = metaData.getContentSelectorsSorted().stream().map(ContentSelector::getField).toList(); - } else { + } + else { fieldNamesToRemove = determineFieldsToRemove(); } @@ -107,18 +111,19 @@ BooleanBinding isNoKeywordSelected() { } void showInputFieldNameDialog() { - dialogService.showEditableChoiceDialogAndWait(Localization.lang("Add new field name"), - Localization.lang("Field name"), - Localization.lang("Add"), - FXCollections.observableArrayList(FieldFactory.getStandardFieldsWithCitationKey()), - FieldsUtil.FIELD_STRING_CONVERTER) - .ifPresent(this::addFieldIfUnique); + dialogService + .showEditableChoiceDialogAndWait(Localization.lang("Add new field name"), Localization.lang("Field name"), + Localization.lang("Add"), + FXCollections.observableArrayList(FieldFactory.getStandardFieldsWithCitationKey()), + FieldsUtil.FIELD_STRING_CONVERTER) + .ifPresent(this::addFieldIfUnique); } private void addFieldIfUnique(Field fieldToAdd) { boolean exists = fieldKeywordsMap.containsKey(fieldToAdd); if (exists) { - dialogService.showErrorDialogAndWait(Localization.lang("Field name \"%0\" already exists", fieldToAdd.getDisplayName())); + dialogService.showErrorDialogAndWait( + Localization.lang("Field name \"%0\" already exists", fieldToAdd.getDisplayName())); return; } @@ -132,10 +137,9 @@ void showRemoveFieldNameConfirmationDialog(Field fieldToRemove) { return; } - boolean deleteConfirmed = dialogService.showConfirmationDialogAndWait( - Localization.lang("Remove field name"), - Localization.lang("Are you sure you want to remove field name: \"%0\"?", fieldToRemove.getDisplayName()) - ); + boolean deleteConfirmed = dialogService.showConfirmationDialogAndWait(Localization.lang("Remove field name"), + Localization.lang("Are you sure you want to remove field name: \"%0\"?", + fieldToRemove.getDisplayName())); if (deleteConfirmed) { removeFieldName(fieldToRemove); @@ -156,7 +160,7 @@ void populateKeywords(Field selectedField) { void showInputKeywordDialog(Field selectedField) { dialogService.showInputDialogAndWait(Localization.lang("Add new keyword"), Localization.lang("Keyword:")) - .ifPresent(newKeyword -> addKeywordIfUnique(selectedField, newKeyword)); + .ifPresent(newKeyword -> addKeywordIfUnique(selectedField, newKeyword)); } private void addKeywordIfUnique(Field field, String keywordToAdd) { @@ -174,7 +178,8 @@ private void addKeywordIfUnique(Field field, String keywordToAdd) { } void showRemoveKeywordConfirmationDialog(Field field, String keywordToRemove) { - boolean deleteConfirmed = dialogService.showConfirmationDialogAndWait(Localization.lang("Remove keyword"), Localization.lang("Are you sure you want to remove keyword: \"%0\"?", keywordToRemove)); + boolean deleteConfirmed = dialogService.showConfirmationDialogAndWait(Localization.lang("Remove keyword"), + Localization.lang("Are you sure you want to remove keyword: \"%0\"?", keywordToRemove)); if (deleteConfirmed) { removeKeyword(field, keywordToRemove); } @@ -189,22 +194,26 @@ private void removeKeyword(Field field, String keywordToRemove) { * Determines the list of fields to remove in case a non-default map: * *

      - *
    • Fields that are not in the new list of fields and
    • - *
    • >all default fields that have no associated keywords
    • + *
    • Fields that are not in the new list of fields and
    • + *
    • >all default fields that have no associated keywords
    • *
    */ private List determineFieldsToRemove() { Set newlyAddedKeywords = fieldKeywordsMap.keySet(); // Remove all content selectors that are not in the new list - List result = new ArrayList<>(metaData.getContentSelectors().getFieldsWithSelectors().stream() - .filter(field -> !newlyAddedKeywords.contains(field)) - .toList()); + List result = new ArrayList<>(metaData.getContentSelectors() + .getFieldsWithSelectors() + .stream() + .filter(field -> !newlyAddedKeywords.contains(field)) + .toList()); // Remove all unset default fields result.addAll(fieldKeywordsMap.entrySet() - .stream() - .filter(entry -> ContentSelectors.DEFAULT_FIELD_NAMES.contains(entry.getKey()) && entry.getValue().isEmpty()).map(Map.Entry::getKey) - .toList()); + .stream() + .filter(entry -> ContentSelectors.DEFAULT_FIELD_NAMES.contains(entry.getKey()) + && entry.getValue().isEmpty()) + .map(Map.Entry::getKey) + .toList()); return result; } @@ -233,4 +242,5 @@ private boolean keywordsHaveChanged(Field field, List keywords) { private HashSet asHashSet(List listToConvert) { return new HashSet<>(listToConvert); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesView.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesView.java index 2b9ab5f3194..9c6644b50bd 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesView.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesView.java @@ -27,38 +27,77 @@ import static org.jabref.gui.icon.IconTheme.JabRefIcons.RELATIVE_PATH; public class GeneralPropertiesView extends AbstractPropertiesTabView { - @FXML private ComboBox encoding; - @FXML private ComboBox databaseMode; - @FXML private TextField librarySpecificFileDirectory; - @FXML private TextField userSpecificFileDirectory; - @FXML private TextField latexFileDirectory; - @FXML private Button libSpecificFileDirSwitchId; - @FXML private Button userSpecificFileDirSwitchId; - @FXML private Button laTexSpecificFileDirSwitchId; - @FXML private JabRefIconView libSpecificFileDirSwitchIcon; - @FXML private JabRefIconView userSpecificFileDirSwitchIcon; - @FXML private JabRefIconView laTexSpecificFileDirSwitchIcon; - @FXML private Tooltip libSpecificFileDirSwitchTooltip; - @FXML private Tooltip userSpecificFileDirSwitchTooltip; - @FXML private Tooltip laTexSpecificFileDirSwitchTooltip; - @FXML private Tooltip userSpecificFileDirectoryTooltip; - @FXML private Tooltip latexFileDirectoryTooltip; - @FXML private Tooltip librarySpecificFileDirectoryTooltip; + + @FXML + private ComboBox encoding; + + @FXML + private ComboBox databaseMode; + + @FXML + private TextField librarySpecificFileDirectory; + + @FXML + private TextField userSpecificFileDirectory; + + @FXML + private TextField latexFileDirectory; + + @FXML + private Button libSpecificFileDirSwitchId; + + @FXML + private Button userSpecificFileDirSwitchId; + + @FXML + private Button laTexSpecificFileDirSwitchId; + + @FXML + private JabRefIconView libSpecificFileDirSwitchIcon; + + @FXML + private JabRefIconView userSpecificFileDirSwitchIcon; + + @FXML + private JabRefIconView laTexSpecificFileDirSwitchIcon; + + @FXML + private Tooltip libSpecificFileDirSwitchTooltip; + + @FXML + private Tooltip userSpecificFileDirSwitchTooltip; + + @FXML + private Tooltip laTexSpecificFileDirSwitchTooltip; + + @FXML + private Tooltip userSpecificFileDirectoryTooltip; + + @FXML + private Tooltip latexFileDirectoryTooltip; + + @FXML + private Tooltip librarySpecificFileDirectoryTooltip; private final ControlsFxVisualizer librarySpecificFileDirectoryValidationVisualizer = new ControlsFxVisualizer(); + private final ControlsFxVisualizer userSpecificFileDirectoryValidationVisualizer = new ControlsFxVisualizer(); + private final ControlsFxVisualizer latexFileDirectoryValidationVisualizer = new ControlsFxVisualizer(); - private final String switchToRelativeText = Localization.lang("Switch to relative path: converts the path to a relative path."); - private final String switchToAbsoluteText = Localization.lang("Switch to absolute path: converts the path to an absolute path."); - @Inject private CliPreferences preferences; + private final String switchToRelativeText = Localization + .lang("Switch to relative path: converts the path to a relative path."); + + private final String switchToAbsoluteText = Localization + .lang("Switch to absolute path: converts the path to an absolute path."); + + @Inject + private CliPreferences preferences; public GeneralPropertiesView(BibDatabaseContext databaseContext) { this.databaseContext = databaseContext; - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -69,16 +108,13 @@ public String getTabName() { public void initialize() { this.viewModel = new GeneralPropertiesViewModel(databaseContext, dialogService, preferences); - new ViewModelListCellFactory() - .withText(Charset::displayName) - .install(encoding); + new ViewModelListCellFactory().withText(Charset::displayName).install(encoding); encoding.disableProperty().bind(viewModel.encodingDisableProperty()); encoding.itemsProperty().bind(viewModel.encodingsProperty()); encoding.valueProperty().bindBidirectional(viewModel.selectedEncodingProperty()); - new ViewModelListCellFactory() - .withText(BibDatabaseMode::getFormattedName) - .install(databaseMode); + new ViewModelListCellFactory().withText(BibDatabaseMode::getFormattedName) + .install(databaseMode); databaseMode.itemsProperty().bind(viewModel.databaseModesProperty()); databaseMode.valueProperty().bindBidirectional(viewModel.selectedDatabaseModeProperty()); @@ -88,7 +124,8 @@ public void initialize() { userSpecificFileDirectory.textProperty().bindBidirectional(viewModel.userSpecificFileDirectoryProperty()); latexFileDirectory.textProperty().bindBidirectional(viewModel.laTexFileDirectoryProperty()); - userSpecificFileDirectoryTooltip.setText(Localization.lang("User-specific file directory: %0", preferences.getFilePreferences().getUserAndHost())); + userSpecificFileDirectoryTooltip.setText(Localization.lang("User-specific file directory: %0", + preferences.getFilePreferences().getUserAndHost())); userSpecificFileDirectory.setTooltip(userSpecificFileDirectoryTooltip); librarySpecificFileDirectoryValidationVisualizer.setDecoration(new IconValidationDecorator()); @@ -103,8 +140,9 @@ public void initialize() { boolean isAbsolute = Path.of(newValue).isAbsolute(); libSpecificFileDirSwitchIcon.setGlyph(isAbsolute ? RELATIVE_PATH : ABSOLUTE_PATH); libSpecificFileDirSwitchTooltip.setText(isAbsolute ? switchToRelativeText : switchToAbsoluteText); - librarySpecificFileDirectoryTooltip.setText(newValue.trim().isEmpty() ? - Localization.lang("Library-specific file directory") : Localization.lang("Library-specific file directory: %0", newValue)); + librarySpecificFileDirectoryTooltip + .setText(newValue.trim().isEmpty() ? Localization.lang("Library-specific file directory") + : Localization.lang("Library-specific file directory: %0", newValue)); }); userSpecificFileDirectory.textProperty().addListener((_, _, newValue) -> { boolean isAbsolute = Path.of(newValue).isAbsolute(); @@ -116,14 +154,17 @@ public void initialize() { boolean isAbsolute = Path.of(newValue).isAbsolute(); laTexSpecificFileDirSwitchIcon.setGlyph(isAbsolute ? RELATIVE_PATH : ABSOLUTE_PATH); laTexSpecificFileDirSwitchTooltip.setText(isAbsolute ? switchToRelativeText : switchToAbsoluteText); - latexFileDirectoryTooltip.setText(newValue.trim().isEmpty() - ? Localization.lang("LaTeX file directory") : Localization.lang("LaTeX file directory: %0", newValue)); + latexFileDirectoryTooltip.setText(newValue.trim().isEmpty() ? Localization.lang("LaTeX file directory") + : Localization.lang("LaTeX file directory: %0", newValue)); }); Platform.runLater(() -> { - librarySpecificFileDirectoryValidationVisualizer.initVisualization(viewModel.librarySpecificFileDirectoryStatus(), librarySpecificFileDirectory); - userSpecificFileDirectoryValidationVisualizer.initVisualization(viewModel.userSpecificFileDirectoryStatus(), userSpecificFileDirectory); - latexFileDirectoryValidationVisualizer.initVisualization(viewModel.laTexFileDirectoryStatus(), latexFileDirectory); + librarySpecificFileDirectoryValidationVisualizer + .initVisualization(viewModel.librarySpecificFileDirectoryStatus(), librarySpecificFileDirectory); + userSpecificFileDirectoryValidationVisualizer.initVisualization(viewModel.userSpecificFileDirectoryStatus(), + userSpecificFileDirectory); + latexFileDirectoryValidationVisualizer.initVisualization(viewModel.laTexFileDirectoryStatus(), + latexFileDirectory); librarySpecificFileDirectory.requestFocus(); }); @@ -158,4 +199,5 @@ void userSpecificFileDirPathSwitch() { void laTexSpecificFileDirPathSwitch() { viewModel.togglePath(viewModel.laTexFileDirectoryProperty()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesViewModel.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesViewModel.java index 6df51a1f897..99bd4f45909 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesViewModel.java @@ -36,56 +36,71 @@ public class GeneralPropertiesViewModel implements PropertiesTabViewModel { private final BooleanProperty encodingDisableProperty = new SimpleBooleanProperty(); - private final ListProperty encodingsProperty = new SimpleListProperty<>(FXCollections.observableArrayList(OS.ENCODINGS)); - private final ObjectProperty selectedEncodingProperty = new SimpleObjectProperty<>(OS.ENCODINGS.getFirst()); - private final ListProperty databaseModesProperty = new SimpleListProperty<>(FXCollections.observableArrayList(BibDatabaseMode.values())); - private final SimpleObjectProperty selectedDatabaseModeProperty = new SimpleObjectProperty<>(BibDatabaseMode.BIBLATEX); + + private final ListProperty encodingsProperty = new SimpleListProperty<>( + FXCollections.observableArrayList(OS.ENCODINGS)); + + private final ObjectProperty selectedEncodingProperty = new SimpleObjectProperty<>( + OS.ENCODINGS.getFirst()); + + private final ListProperty databaseModesProperty = new SimpleListProperty<>( + FXCollections.observableArrayList(BibDatabaseMode.values())); + + private final SimpleObjectProperty selectedDatabaseModeProperty = new SimpleObjectProperty<>( + BibDatabaseMode.BIBLATEX); + private final StringProperty librarySpecificDirectoryProperty = new SimpleStringProperty(""); + private final StringProperty userSpecificFileDirectoryProperty = new SimpleStringProperty(""); + private final StringProperty laTexFileDirectoryProperty = new SimpleStringProperty(""); private final Validator librarySpecificFileDirectoryValidator; + private final Validator userSpecificFileDirectoryValidator; + private final Validator laTexFileDirectoryValidator; private final DialogService dialogService; + private final CliPreferences preferences; private final BibDatabaseContext databaseContext; + private final MetaData metaData; - GeneralPropertiesViewModel(BibDatabaseContext databaseContext, DialogService dialogService, CliPreferences preferences) { + GeneralPropertiesViewModel(BibDatabaseContext databaseContext, DialogService dialogService, + CliPreferences preferences) { this.dialogService = dialogService; this.preferences = preferences; this.databaseContext = databaseContext; this.metaData = databaseContext.getMetaData(); - librarySpecificFileDirectoryValidator = new FunctionBasedValidator<>( - librarySpecificDirectoryProperty, - mainDirectoryPath -> validateDirectory(mainDirectoryPath, "Library-specific") - ); + librarySpecificFileDirectoryValidator = new FunctionBasedValidator<>(librarySpecificDirectoryProperty, + mainDirectoryPath -> validateDirectory(mainDirectoryPath, "Library-specific")); - userSpecificFileDirectoryValidator = new FunctionBasedValidator<>( - userSpecificFileDirectoryProperty, - mainDirectoryPath -> validateDirectory(mainDirectoryPath, "User-specific") - ); + userSpecificFileDirectoryValidator = new FunctionBasedValidator<>(userSpecificFileDirectoryProperty, + mainDirectoryPath -> validateDirectory(mainDirectoryPath, "User-specific")); - laTexFileDirectoryValidator = new FunctionBasedValidator<>( - laTexFileDirectoryProperty, - mainDirectoryPath -> validateDirectory(mainDirectoryPath, "LaTeX") - ); + laTexFileDirectoryValidator = new FunctionBasedValidator<>(laTexFileDirectoryProperty, + mainDirectoryPath -> validateDirectory(mainDirectoryPath, "LaTeX")); } @Override public void setValues() { boolean isShared = databaseContext.getLocation() == DatabaseLocation.SHARED; - encodingDisableProperty.setValue(isShared); // the encoding of shared database is always UTF-8 + encodingDisableProperty.setValue(isShared); // the encoding of shared database is + // always UTF-8 selectedEncodingProperty.setValue(metaData.getEncoding().orElse(StandardCharsets.UTF_8)); selectedDatabaseModeProperty.setValue(metaData.getMode().orElse(BibDatabaseMode.BIBLATEX)); librarySpecificDirectoryProperty.setValue(metaData.getLibrarySpecificFileDirectory().orElse("").trim()); - userSpecificFileDirectoryProperty.setValue(metaData.getUserFileDirectory(preferences.getFilePreferences().getUserAndHost()).orElse("").trim()); - laTexFileDirectoryProperty.setValue(metaData.getLatexFileDirectory(preferences.getFilePreferences().getUserAndHost()).map(Path::toString).orElse("")); + userSpecificFileDirectoryProperty.setValue( + metaData.getUserFileDirectory(preferences.getFilePreferences().getUserAndHost()).orElse("").trim()); + laTexFileDirectoryProperty + .setValue(metaData.getLatexFileDirectory(preferences.getFilePreferences().getUserAndHost()) + .map(Path::toString) + .orElse("")); } @Override @@ -98,22 +113,27 @@ public void storeSettings() { String librarySpecificFileDirectory = librarySpecificDirectoryProperty.getValue().trim(); if (librarySpecificFileDirectory.isEmpty()) { newMetaData.clearLibrarySpecificFileDirectory(); - } else if (librarySpecificFileDirectoryStatus().isValid()) { + } + else if (librarySpecificFileDirectoryStatus().isValid()) { newMetaData.setLibrarySpecificFileDirectory(librarySpecificFileDirectory); } String userSpecificFileDirectory = userSpecificFileDirectoryProperty.getValue(); if (userSpecificFileDirectory.isEmpty()) { newMetaData.clearUserFileDirectory(preferences.getFilePreferences().getUserAndHost()); - } else if (userSpecificFileDirectoryStatus().isValid()) { - newMetaData.setUserFileDirectory(preferences.getFilePreferences().getUserAndHost(), userSpecificFileDirectory); + } + else if (userSpecificFileDirectoryStatus().isValid()) { + newMetaData.setUserFileDirectory(preferences.getFilePreferences().getUserAndHost(), + userSpecificFileDirectory); } String latexFileDirectory = laTexFileDirectoryProperty.getValue(); if (latexFileDirectory.isEmpty()) { newMetaData.clearLatexFileDirectory(preferences.getFilePreferences().getUserAndHost()); - } else if (laTexFileDirectoryStatus().isValid()) { - newMetaData.setLatexFileDirectory(preferences.getFilePreferences().getUserAndHost(), Path.of(latexFileDirectory)); + } + else if (laTexFileDirectoryStatus().isValid()) { + newMetaData.setLatexFileDirectory(preferences.getFilePreferences().getUserAndHost(), + Path.of(latexFileDirectory)); } databaseContext.setMetaData(newMetaData); @@ -137,30 +157,33 @@ public boolean validateSettings() { ValidationStatus userSpecificFileDirectoryStatus = userSpecificFileDirectoryStatus(); ValidationStatus laTexFileDirectoryStatus = laTexFileDirectoryStatus(); - return promptUserToConfirmAction(librarySpecificFileDirectoryStatus) && - promptUserToConfirmAction(userSpecificFileDirectoryStatus) && - promptUserToConfirmAction(laTexFileDirectoryStatus); + return promptUserToConfirmAction(librarySpecificFileDirectoryStatus) + && promptUserToConfirmAction(userSpecificFileDirectoryStatus) + && promptUserToConfirmAction(laTexFileDirectoryStatus); } public void browseLibrarySpecificDir() { DirectoryDialogConfiguration directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(getBrowseDirectory(librarySpecificDirectoryProperty.getValue())).build(); + .withInitialDirectory(getBrowseDirectory(librarySpecificDirectoryProperty.getValue())) + .build(); dialogService.showDirectorySelectionDialog(directoryDialogConfiguration) - .ifPresent(dir -> setDirectory(librarySpecificDirectoryProperty, dir)); + .ifPresent(dir -> setDirectory(librarySpecificDirectoryProperty, dir)); } public void browseUserDir() { DirectoryDialogConfiguration directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(getBrowseDirectory(userSpecificFileDirectoryProperty.getValue())).build(); + .withInitialDirectory(getBrowseDirectory(userSpecificFileDirectoryProperty.getValue())) + .build(); dialogService.showDirectorySelectionDialog(directoryDialogConfiguration) - .ifPresent(dir -> setDirectory(userSpecificFileDirectoryProperty, dir)); + .ifPresent(dir -> setDirectory(userSpecificFileDirectoryProperty, dir)); } public void browseLatexDir() { DirectoryDialogConfiguration directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(getBrowseDirectory(laTexFileDirectoryProperty.getValue())).build(); + .withInitialDirectory(getBrowseDirectory(laTexFileDirectoryProperty.getValue())) + .build(); dialogService.showDirectorySelectionDialog(directoryDialogConfiguration) - .ifPresent(dir -> setDirectory(laTexFileDirectoryProperty, dir)); + .ifPresent(dir -> setDirectory(laTexFileDirectoryProperty, dir)); } public BooleanProperty encodingDisableProperty() { @@ -211,7 +234,8 @@ private Path getBrowseDirectory(String configuredDir) { // configuredDir can be input manually, which may lead it to being invalid if (!Files.isDirectory(configuredPath)) { - dialogService.notify(Localization.lang("Path %0 could not be resolved. Using working directory.", configuredDir)); + dialogService + .notify(Localization.lang("Path %0 could not be resolved. Using working directory.", configuredDir)); return workingDir; } @@ -228,16 +252,16 @@ private ValidationMessage validateDirectory(String directoryPath, String message } try { if (!libPath.map(p -> p.getParent().resolve(directoryPath).normalize()) - .map(Files::isDirectory) - .orElse(false)) { - return ValidationMessage.error( - Localization.lang("The file directory '%0' for the %1 file path is not found or is inaccessible.", directoryPath, messageKey) - ); + .map(Files::isDirectory) + .orElse(false)) { + return ValidationMessage.error(Localization.lang( + "The file directory '%0' for the %1 file path is not found or is inaccessible.", directoryPath, + messageKey)); } - } catch (InvalidPathException ex) { - return ValidationMessage.error( - Localization.lang("Invalid path: '%0'.\nCheck \"%1\".", directoryPath, messageKey) - ); + } + catch (InvalidPathException ex) { + return ValidationMessage + .error(Localization.lang("Invalid path: '%0'.\nCheck \"%1\".", directoryPath, messageKey)); } // Directory is valid return null; @@ -246,12 +270,12 @@ private ValidationMessage validateDirectory(String directoryPath, String message private boolean promptUserToConfirmAction(ValidationStatus status) { if (!status.isValid()) { return status.getHighestMessage() - .map(message -> dialogService.showConfirmationDialogAndWait( - Localization.lang("Action required: override default file directories"), - message.getMessage() + "\n" + Localization.lang("Would you like to save your other preferences?"), - Localization.lang("Save"), - Localization.lang("Return to Properties"))) - .orElse(false); + .map(message -> dialogService.showConfirmationDialogAndWait( + Localization.lang("Action required: override default file directories"), + message.getMessage() + "\n" + + Localization.lang("Would you like to save your other preferences?"), + Localization.lang("Save"), Localization.lang("Return to Properties"))) + .orElse(false); } return true; } @@ -270,22 +294,25 @@ public void togglePath(StringProperty fileDirectory) { if (!currPath.isAbsolute()) { newPath = parentPath.resolve(fileDirectory.get()).toAbsolutePath().toString(); - } else if (currPath.isAbsolute()) { + } + else if (currPath.isAbsolute()) { newPath = parentPath.relativize(currPath).toString(); - } else { + } + else { // case: convert to relative path and currPath is relative return; } fileDirectory.setValue(newPath); - } catch (InvalidPathException ex) { + } + catch (InvalidPathException ex) { dialogService.showErrorDialogAndWait(Localization.lang("Error occurred %0", ex.getMessage())); } } /** - * For a saved library, any directory relative to the library path will be set as relative; otherwise, it will be set as absolute. - * + * For a saved library, any directory relative to the library path will be set as + * relative; otherwise, it will be set as absolute. * @param fileDirectory file directory to be updated (lib/user/laTex) * @param selectedDirPath path of directory (selected by user) */ @@ -299,8 +326,7 @@ private void setDirectory(StringProperty fileDirectory, Path selectedDirPath) { } // set relative path - fileDirectory.setValue(libPath.get() - .getParent() - .relativize(selectedDirPath).toString()); + fileDirectory.setValue(libPath.get().getParent().relativize(selectedDirPath).toString()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/keypattern/KeyPatternPropertiesView.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/keypattern/KeyPatternPropertiesView.java index e391efb5370..8930fb21a6e 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/keypattern/KeyPatternPropertiesView.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/keypattern/KeyPatternPropertiesView.java @@ -18,20 +18,25 @@ import com.airhacks.afterburner.views.ViewLoader; import jakarta.inject.Inject; -public class KeyPatternPropertiesView extends AbstractPropertiesTabView implements PropertiesTab { +public class KeyPatternPropertiesView extends AbstractPropertiesTabView + implements PropertiesTab { - @FXML private Button keyPatternHelp; - @FXML private CitationKeyPatternsPanel bibtexKeyPatternTable; + @FXML + private Button keyPatternHelp; + + @FXML + private CitationKeyPatternsPanel bibtexKeyPatternTable; - @Inject private GuiPreferences preferences; - @Inject private BibEntryTypesManager bibEntryTypesManager; + @Inject + private GuiPreferences preferences; + + @Inject + private BibEntryTypesManager bibEntryTypesManager; public KeyPatternPropertiesView(BibDatabaseContext databaseContext) { this.databaseContext = databaseContext; - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -46,21 +51,26 @@ public void initialize() { bibtexKeyPatternTable.defaultKeyPatternProperty().bindBidirectional(viewModel.defaultKeyPatternProperty()); ActionFactory actionFactory = new ActionFactory(); - actionFactory.configureIconButton(StandardActions.HELP_KEY_PATTERNS, new HelpAction(HelpFile.CITATION_KEY_PATTERN, dialogService, preferences.getExternalApplicationsPreferences()), keyPatternHelp); + actionFactory.configureIconButton(StandardActions.HELP_KEY_PATTERNS, + new HelpAction(HelpFile.CITATION_KEY_PATTERN, dialogService, + preferences.getExternalApplicationsPreferences()), + keyPatternHelp); } @Override public void setValues() { viewModel.setValues(); bibtexKeyPatternTable.setValues( - bibEntryTypesManager.getAllTypes(databaseContext.getMetaData().getMode() - .orElse(preferences.getLibraryPreferences() - .getDefaultBibDatabaseMode())), - databaseContext.getMetaData().getCiteKeyPatterns(preferences.getCitationKeyPatternPreferences().getKeyPatterns())); + bibEntryTypesManager.getAllTypes(databaseContext.getMetaData() + .getMode() + .orElse(preferences.getLibraryPreferences().getDefaultBibDatabaseMode())), + databaseContext.getMetaData() + .getCiteKeyPatterns(preferences.getCitationKeyPatternPreferences().getKeyPatterns())); } @FXML public void resetAllKeyPatterns() { bibtexKeyPatternTable.resetAll(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/keypattern/KeyPatternPropertiesViewModel.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/keypattern/KeyPatternPropertiesViewModel.java index 0ee72445c80..e0e9cadcc36 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/keypattern/KeyPatternPropertiesViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/keypattern/KeyPatternPropertiesViewModel.java @@ -15,9 +15,13 @@ public class KeyPatternPropertiesViewModel implements PropertiesTabViewModel { - // The list and the default properties are being overwritten by the bound properties of the tableView, but to - // prevent an NPE on storing the preferences before lazy-loading of the setValues, they need to be initialized. - private final ListProperty patternListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + // The list and the default properties are being overwritten by the bound properties + // of the tableView, but to + // prevent an NPE on storing the preferences before lazy-loading of the setValues, + // they need to be initialized. + private final ListProperty patternListProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); + private final ObjectProperty defaultKeyPatternProperty = new SimpleObjectProperty<>( new CitationKeyPatternsPanelItemModel(new CitationKeyPatternsPanelViewModel.DefaultEntryType(), "")); @@ -37,7 +41,8 @@ public void setValues() { @Override public void storeSettings() { - DatabaseCitationKeyPatterns newKeyPattern = new DatabaseCitationKeyPatterns(preferences.getCitationKeyPatternPreferences().getKeyPatterns()); + DatabaseCitationKeyPatterns newKeyPattern = new DatabaseCitationKeyPatterns( + preferences.getCitationKeyPatternPreferences().getKeyPatterns()); patternListProperty.forEach(item -> { String patternString = item.getPattern(); @@ -49,7 +54,8 @@ public void storeSettings() { }); if (!defaultKeyPatternProperty.getValue().getPattern().trim().isEmpty()) { - // we do not trim the value at the assignment to enable users to have spaces at the beginning and + // we do not trim the value at the assignment to enable users to have spaces + // at the beginning and // at the end of the pattern newKeyPattern.setDefaultValue(defaultKeyPatternProperty.getValue().getPattern()); } @@ -64,4 +70,5 @@ public ListProperty patternListProperty() { public ObjectProperty defaultKeyPatternProperty() { return defaultKeyPatternProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/preamble/PreamblePropertiesView.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/preamble/PreamblePropertiesView.java index 36a5f106fe0..4af0ee9686b 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/preamble/PreamblePropertiesView.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/preamble/PreamblePropertiesView.java @@ -13,16 +13,17 @@ import jakarta.inject.Inject; public class PreamblePropertiesView extends AbstractPropertiesTabView { - @FXML private TextArea preamble; - @Inject private UndoManager undoManager; + @FXML + private TextArea preamble; + + @Inject + private UndoManager undoManager; public PreamblePropertiesView(BibDatabaseContext databaseContext) { this.databaseContext = databaseContext; - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -35,4 +36,5 @@ public void initialize() { preamble.textProperty().bindBidirectional(viewModel.preambleProperty()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/preamble/PreamblePropertiesViewModel.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/preamble/PreamblePropertiesViewModel.java index 5ac3f81b951..bd5c375e1bf 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/preamble/PreamblePropertiesViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/preamble/PreamblePropertiesViewModel.java @@ -10,9 +10,11 @@ import org.jabref.model.database.BibDatabaseContext; public class PreamblePropertiesViewModel implements PropertiesTabViewModel { + private final StringProperty preambleProperty = new SimpleStringProperty(""); private final BibDatabaseContext databaseContext; + private final UndoManager undoManager; PreamblePropertiesViewModel(BibDatabaseContext databaseContext, UndoManager undoManager) { @@ -29,7 +31,8 @@ public void setValues() { public void storeSettings() { String newPreamble = preambleProperty.getValue(); if (!databaseContext.getDatabase().getPreamble().orElse("").equals(newPreamble)) { - undoManager.addEdit(new UndoablePreambleChange(databaseContext.getDatabase(), databaseContext.getDatabase().getPreamble().orElse(null), newPreamble)); + undoManager.addEdit(new UndoablePreambleChange(databaseContext.getDatabase(), + databaseContext.getDatabase().getPreamble().orElse(null), newPreamble)); databaseContext.getDatabase().setPreamble(newPreamble); } } @@ -37,4 +40,5 @@ public void storeSettings() { public StringProperty preambleProperty() { return this.preambleProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/saving/SavingPropertiesView.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/saving/SavingPropertiesView.java index c1b481662f2..71eab470a64 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/saving/SavingPropertiesView.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/saving/SavingPropertiesView.java @@ -14,20 +14,25 @@ import com.airhacks.afterburner.views.ViewLoader; import jakarta.inject.Inject; -public class SavingPropertiesView extends AbstractPropertiesTabView implements PropertiesTab { +public class SavingPropertiesView extends AbstractPropertiesTabView + implements PropertiesTab { - @FXML private CheckBox protect; - @FXML private SaveOrderConfigPanel saveOrderConfigPanel; - @FXML private FieldFormatterCleanupsPanel fieldFormatterCleanupsPanel; + @FXML + private CheckBox protect; - @Inject private CliPreferences preferences; + @FXML + private SaveOrderConfigPanel saveOrderConfigPanel; + + @FXML + private FieldFormatterCleanupsPanel fieldFormatterCleanupsPanel; + + @Inject + private CliPreferences preferences; public SavingPropertiesView(BibDatabaseContext databaseContext) { this.databaseContext = databaseContext; - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -50,4 +55,5 @@ public void initialize() { fieldFormatterCleanupsPanel.cleanupsDisableProperty().bindBidirectional(viewModel.cleanupsDisableProperty()); fieldFormatterCleanupsPanel.cleanupsProperty().bindBidirectional(viewModel.cleanupsProperty()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/saving/SavingPropertiesViewModel.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/saving/SavingPropertiesViewModel.java index e2701a309c3..7c9a40edcaf 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/saving/SavingPropertiesViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/saving/SavingPropertiesViewModel.java @@ -27,31 +27,42 @@ public class SavingPropertiesViewModel implements PropertiesTabViewModel { - private static final SaveOrder UI_DEFAULT_SAVE_ORDER = new SaveOrder(SaveOrder.OrderType.ORIGINAL, List.of( - new SaveOrder.SortCriterion(StandardField.AUTHOR), - new SaveOrder.SortCriterion(StandardField.YEAR), - new SaveOrder.SortCriterion(StandardField.TITLE), - // Pro users generate their citation keys well. They can just delete the above three proposals and get a well-sorted library. - new SaveOrder.SortCriterion(InternalField.KEY_FIELD) - )); + private static final SaveOrder UI_DEFAULT_SAVE_ORDER = new SaveOrder(SaveOrder.OrderType.ORIGINAL, + List.of(new SaveOrder.SortCriterion(StandardField.AUTHOR), new SaveOrder.SortCriterion(StandardField.YEAR), + new SaveOrder.SortCriterion(StandardField.TITLE), + // Pro users generate their citation keys well. They can just delete + // the above three proposals and get a well-sorted library. + new SaveOrder.SortCriterion(InternalField.KEY_FIELD))); private final BooleanProperty protectDisableProperty = new SimpleBooleanProperty(); + private final BooleanProperty libraryProtectedProperty = new SimpleBooleanProperty(); // SaveOrderConfigPanel private final BooleanProperty saveInOriginalProperty = new SimpleBooleanProperty(); + private final BooleanProperty saveInTableOrderProperty = new SimpleBooleanProperty(); + private final BooleanProperty saveInSpecifiedOrderProperty = new SimpleBooleanProperty(); - private final ListProperty sortableFieldsProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ListProperty sortCriteriaProperty = new SimpleListProperty<>(FXCollections.observableArrayList(new ArrayList<>())); + + private final ListProperty sortableFieldsProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); + + private final ListProperty sortCriteriaProperty = new SimpleListProperty<>( + FXCollections.observableArrayList(new ArrayList<>())); // FieldFormatterCleanupsPanel private final BooleanProperty cleanupsDisableProperty = new SimpleBooleanProperty(); - private final ListProperty cleanupsProperty = new SimpleListProperty<>(FXCollections.emptyObservableList()); + + private final ListProperty cleanupsProperty = new SimpleListProperty<>( + FXCollections.emptyObservableList()); private final BibDatabaseContext databaseContext; + private final MetaData initialMetaData; + private final SaveOrder saveOrder; + private final CliPreferences preferences; public SavingPropertiesViewModel(BibDatabaseContext databaseContext, CliPreferences preferences) { @@ -83,9 +94,7 @@ public void setValues() { sortableFieldsProperty.addAll(FieldFactory.getStandardFieldsWithCitationKey()); sortCriteriaProperty.clear(); - sortCriteriaProperty.addAll(saveOrder.getSortCriteria().stream() - .map(SortCriterionViewModel::new) - .toList()); + sortCriteriaProperty.addAll(saveOrder.getSortCriteria().stream().map(SortCriterionViewModel::new).toList()); // FieldFormatterCleanupsPanel, included via in FXML @@ -96,7 +105,8 @@ public void setValues() { }, () -> { CleanupPreferences defaultPreset = preferences.getDefaultCleanupPreset(); cleanupsDisableProperty.setValue(!defaultPreset.getFieldFormatterCleanups().isEnabled()); - cleanupsProperty.setValue(FXCollections.observableArrayList(defaultPreset.getFieldFormatterCleanups().getConfiguredActions())); + cleanupsProperty.setValue(FXCollections + .observableArrayList(defaultPreset.getFieldFormatterCleanups().getConfiguredActions())); }); } @@ -106,33 +116,37 @@ public void storeSettings() { if (libraryProtectedProperty.getValue()) { newMetaData.markAsProtected(); - } else { + } + else { newMetaData.markAsNotProtected(); } FieldFormatterCleanups fieldFormatterCleanups = new FieldFormatterCleanups( - !cleanupsDisableProperty().getValue(), - cleanupsProperty()); + !cleanupsDisableProperty().getValue(), cleanupsProperty()); if (FieldFormatterCleanups.DEFAULT_SAVE_ACTIONS.equals(fieldFormatterCleanups.getConfiguredActions())) { newMetaData.clearSaveActions(); - } else { + } + else { // if all actions have been removed, remove the save actions from the MetaData if (fieldFormatterCleanups.getConfiguredActions().isEmpty()) { newMetaData.clearSaveActions(); - } else { + } + else { newMetaData.setSaveActions(fieldFormatterCleanups); } } SaveOrder newSaveOrder = new SaveOrder( - SaveOrder.OrderType.fromBooleans(saveInSpecifiedOrderProperty.getValue(), saveInOriginalProperty.getValue()), + SaveOrder.OrderType.fromBooleans(saveInSpecifiedOrderProperty.getValue(), + saveInOriginalProperty.getValue()), sortCriteriaProperty.stream().map(SortCriterionViewModel::getCriterion).toList()); if (!newSaveOrder.equals(saveOrder)) { if (newSaveOrder.equals(SaveOrder.getDefaultSaveOrder())) { newMetaData.clearSaveOrder(); - } else { + } + else { newMetaData.setSaveOrder(newSaveOrder); } } @@ -179,4 +193,5 @@ public BooleanProperty cleanupsDisableProperty() { public ListProperty cleanupsProperty() { return cleanupsProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/linkedfile/AttachFileAction.java b/jabgui/src/main/java/org/jabref/gui/linkedfile/AttachFileAction.java index 941c5c92da7..fff4d87e2f5 100644 --- a/jabgui/src/main/java/org/jabref/gui/linkedfile/AttachFileAction.java +++ b/jabgui/src/main/java/org/jabref/gui/linkedfile/AttachFileAction.java @@ -22,16 +22,17 @@ public class AttachFileAction extends SimpleCommand { private final LibraryTab libraryTab; + private final StateManager stateManager; + private final DialogService dialogService; + private final FilePreferences filePreferences; + private final ExternalApplicationsPreferences externalApplicationsPreferences; - public AttachFileAction(LibraryTab libraryTab, - DialogService dialogService, - StateManager stateManager, - FilePreferences filePreferences, - ExternalApplicationsPreferences externalApplicationsPreferences) { + public AttachFileAction(LibraryTab libraryTab, DialogService dialogService, StateManager stateManager, + FilePreferences filePreferences, ExternalApplicationsPreferences externalApplicationsPreferences) { this.libraryTab = libraryTab; this.stateManager = stateManager; this.dialogService = dialogService; @@ -61,35 +62,35 @@ public void execute() { if (filePreferences.shouldOpenFileExplorerInFileDirectory()) { initialDirectory = databaseContext.getFirstExistingFileDir(filePreferences) - .orElse(filePreferences.getWorkingDirectory()); - } else if (filePreferences.shouldOpenFileExplorerInLastUsedDirectory()) { + .orElse(filePreferences.getWorkingDirectory()); + } + else if (filePreferences.shouldOpenFileExplorerInLastUsedDirectory()) { initialDirectory = filePreferences.getLastUsedDirectory(); - } else { + } + else { initialDirectory = filePreferences.getWorkingDirectory(); } FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .withInitialDirectory(initialDirectory) - .build(); + .withInitialDirectory(initialDirectory) + .build(); dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(newFile -> { filePreferences.setLastUsedDirectory(newFile.getParent()); - LinkedFile linkedFile = LinkedFilesEditorViewModel.fromFile( - newFile, - databaseContext.getFileDirectories(filePreferences), - externalApplicationsPreferences); + LinkedFile linkedFile = LinkedFilesEditorViewModel.fromFile(newFile, + databaseContext.getFileDirectories(filePreferences), externalApplicationsPreferences); LinkedFileEditDialog dialog = new LinkedFileEditDialog(linkedFile); - dialogService.showCustomDialogAndWait(dialog) - .ifPresent(editedLinkedFile -> { - Optional fieldChange = entry.addFile(editedLinkedFile); - fieldChange.ifPresent(change -> { - UndoableFieldChange ce = new UndoableFieldChange(change); - libraryTab.getUndoManager().addEdit(ce); - libraryTab.markBaseChanged(); - }); - }); + dialogService.showCustomDialogAndWait(dialog).ifPresent(editedLinkedFile -> { + Optional fieldChange = entry.addFile(editedLinkedFile); + fieldChange.ifPresent(change -> { + UndoableFieldChange ce = new UndoableFieldChange(change); + libraryTab.getUndoManager().addEdit(ce); + libraryTab.markBaseChanged(); + }); + }); }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/linkedfile/AttachFileFromURLAction.java b/jabgui/src/main/java/org/jabref/gui/linkedfile/AttachFileFromURLAction.java index 8ac056f8119..c04e9546088 100644 --- a/jabgui/src/main/java/org/jabref/gui/linkedfile/AttachFileFromURLAction.java +++ b/jabgui/src/main/java/org/jabref/gui/linkedfile/AttachFileFromURLAction.java @@ -22,14 +22,15 @@ public class AttachFileFromURLAction extends SimpleCommand { private final StateManager stateManager; + private final DialogService dialogService; + private final GuiPreferences preferences; + private final TaskExecutor taskExecutor; - public AttachFileFromURLAction(DialogService dialogService, - StateManager stateManager, - TaskExecutor taskExecutor, - GuiPreferences preferences) { + public AttachFileFromURLAction(DialogService dialogService, StateManager stateManager, TaskExecutor taskExecutor, + GuiPreferences preferences) { this.stateManager = stateManager; this.dialogService = dialogService; this.taskExecutor = taskExecutor; @@ -62,15 +63,11 @@ public void execute() { try { URL url = URLUtil.create(urlforDownload.get()); - LinkedFileViewModel onlineFile = new LinkedFileViewModel( - new LinkedFile(url, ""), - entry, - databaseContext, - taskExecutor, - dialogService, - preferences); + LinkedFileViewModel onlineFile = new LinkedFileViewModel(new LinkedFile(url, ""), entry, databaseContext, + taskExecutor, dialogService, preferences); onlineFile.download(true); - } catch (MalformedURLException exception) { + } + catch (MalformedURLException exception) { dialogService.showErrorDialogAndWait(Localization.lang("Invalid URL"), exception); } } @@ -80,15 +77,18 @@ public static Optional getUrlForDownloadFromClipBoardOrEntry(DialogServi Optional urlText; String urlField = entry.getField(StandardField.URL).orElse(""); if (clipText.startsWith("http://") || clipText.startsWith("https://") || clipText.startsWith("ftp://")) { - urlText = dialogService.showInputDialogWithDefaultAndWait( - Localization.lang("Download file"), Localization.lang("Enter URL to download"), clipText); - } else if (urlField.startsWith("http://") || urlField.startsWith("https://") || urlField.startsWith("ftp://")) { - urlText = dialogService.showInputDialogWithDefaultAndWait( - Localization.lang("Download file"), Localization.lang("Enter URL to download"), urlField); - } else { - urlText = dialogService.showInputDialogAndWait( - Localization.lang("Download file"), Localization.lang("Enter URL to download")); + urlText = dialogService.showInputDialogWithDefaultAndWait(Localization.lang("Download file"), + Localization.lang("Enter URL to download"), clipText); + } + else if (urlField.startsWith("http://") || urlField.startsWith("https://") || urlField.startsWith("ftp://")) { + urlText = dialogService.showInputDialogWithDefaultAndWait(Localization.lang("Download file"), + Localization.lang("Enter URL to download"), urlField); + } + else { + urlText = dialogService.showInputDialogAndWait(Localization.lang("Download file"), + Localization.lang("Enter URL to download")); } return urlText; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/linkedfile/DeleteFileAction.java b/jabgui/src/main/java/org/jabref/gui/linkedfile/DeleteFileAction.java index 143e3c6c43f..2b1cc19c458 100644 --- a/jabgui/src/main/java/org/jabref/gui/linkedfile/DeleteFileAction.java +++ b/jabgui/src/main/java/org/jabref/gui/linkedfile/DeleteFileAction.java @@ -38,17 +38,20 @@ public class DeleteFileAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(DeleteFileAction.class); private final DialogService dialogService; + private final FilePreferences filePreferences; + private final BibDatabaseContext databaseContext; + private final @Nullable LinkedFilesEditorViewModel viewModel; + private final List filesToDelete; + private boolean success = false; - public DeleteFileAction(DialogService dialogService, - FilePreferences filePreferences, - BibDatabaseContext databaseContext, - @Nullable LinkedFilesEditorViewModel viewModel, - List filesToDelete) { + public DeleteFileAction(DialogService dialogService, FilePreferences filePreferences, + BibDatabaseContext databaseContext, @Nullable LinkedFilesEditorViewModel viewModel, + List filesToDelete) { this.dialogService = dialogService; this.filePreferences = filePreferences; this.databaseContext = databaseContext; @@ -59,10 +62,8 @@ public DeleteFileAction(DialogService dialogService, /** * Called when the user wants to delete a complete entry. */ - public DeleteFileAction(DialogService dialogService, - FilePreferences filePreferences, - BibDatabaseContext databaseContext, - List filesToDelete) { + public DeleteFileAction(DialogService dialogService, FilePreferences filePreferences, + BibDatabaseContext databaseContext, List filesToDelete) { this(dialogService, filePreferences, databaseContext, null, filesToDelete); } @@ -91,17 +92,20 @@ public void execute() { dialogDescription = Localization.lang("How should these files be handled?"); if (numberOfLinkedFiles != 1) { dialogTitle = Localization.lang("Delete %0 files", numberOfLinkedFiles); - } else { + } + else { LinkedFile linkedFile = filesToDelete.getFirst().getFile(); Optional file = linkedFile.findIn(databaseContext, filePreferences); if (file.isPresent()) { Path path = file.get(); dialogTitle = Localization.lang("Delete '%0'", path.getFileName().toString()); - } else { + } + else { LOGGER.warn("Could not find file {}", linkedFile.getLink()); dialogService.notify(Localization.lang("Error accessing file '%0'.", linkedFile.getLink())); - // We can trigger deletion of "all" files from the entry (no deletion on disk), because in this case, there is only one files + // We can trigger deletion of "all" files from the entry (no deletion on + // disk), because in this case, there is only one files assert numberOfLinkedFiles == 1; deleteFiles(false); @@ -117,21 +121,23 @@ public void execute() { String label; if (filePreferences.moveToTrash()) { label = Localization.lang("Move file(s) to trash"); - } else { + } + else { label = Localization.lang("Delete from disk"); } ButtonType deleteFromDisk = new ButtonType(label); ButtonType removeFromEntry = new ButtonType(Localization.lang("Keep file(s)"), ButtonBar.ButtonData.YES); - Optional buttonType = dialogService.showCustomDialogAndWait( - dialogTitle, dialogPane, removeFromEntry, deleteFromDisk, ButtonType.CANCEL); + Optional buttonType = dialogService.showCustomDialogAndWait(dialogTitle, dialogPane, + removeFromEntry, deleteFromDisk, ButtonType.CANCEL); if (buttonType.isPresent()) { ButtonType theButtonType = buttonType.get(); if (theButtonType.equals(removeFromEntry)) { deleteFiles(false); - } else if (theButtonType.equals(deleteFromDisk)) { + } + else if (theButtonType.equals(deleteFromDisk)) { deleteFiles(true); } } @@ -146,10 +152,10 @@ private DialogPane createDeleteFilesDialog(String description) { -fx-padding: 10px; -fx-background-color: -fx-background;"""); - ListView filesToDeleteList = new ListView<>(FXCollections.observableArrayList(filesToDelete)); - new ViewModelListCellFactory() - .withText(item -> item.getFile().getLink()) - .install(filesToDeleteList); + ListView filesToDeleteList = new ListView<>( + FXCollections.observableArrayList(filesToDelete)); + new ViewModelListCellFactory().withText(item -> item.getFile().getLink()) + .install(filesToDeleteList); VBox content = new VBox(header, filesToDeleteList); DialogPane dialogPane = new DialogPane(); @@ -160,8 +166,8 @@ private DialogPane createDeleteFilesDialog(String description) { /** * Deletes the files from the entry and optionally from disk. - * - * @param deleteFromDisk if true, the files are deleted from disk, otherwise they are only removed from the entry + * @param deleteFromDisk if true, the files are deleted from disk, otherwise they are + * only removed from the entry */ private void deleteFiles(boolean deleteFromDisk) { // default: We have a success @@ -178,8 +184,8 @@ private void deleteFiles(boolean deleteFromDisk) { /** * Helper method to delete the specified file from disk - * - * @param linkedFile The LinkedFile (file which linked to an entry) to be deleted from disk + * @param linkedFile The LinkedFile (file which linked to an entry) to be deleted from + * disk */ private void deleteFileHelper(BibDatabaseContext databaseContext, LinkedFile linkedFile) { Optional file = linkedFile.findIn(databaseContext, filePreferences); @@ -199,14 +205,17 @@ private void deleteFileHelper(BibDatabaseContext databaseContext, LinkedFile lin if (preferencesMoveToTrash) { LOGGER.debug("Moving to trash: {}", theFile); NativeDesktop.get().moveToTrash(theFile); - } else { + } + else { LOGGER.debug("Deleting: {}", theFile); Files.delete(theFile); } success = true; - } catch (IOException ex) { + } + catch (IOException ex) { success = false; - dialogService.showErrorDialogAndWait(Localization.lang("Cannot delete file '%0'", theFile), Localization.lang("File permission error")); + dialogService.showErrorDialogAndWait(Localization.lang("Cannot delete file '%0'", theFile), + Localization.lang("File permission error")); LOGGER.warn("Error while deleting: {}", linkedFile, ex); } } @@ -214,4 +223,5 @@ private void deleteFileHelper(BibDatabaseContext databaseContext, LinkedFile lin public boolean isSuccess() { return success; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java b/jabgui/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java index 871eaee5e1d..019939b87e2 100644 --- a/jabgui/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java +++ b/jabgui/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java @@ -48,30 +48,33 @@ public class DownloadLinkedFileAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(DownloadLinkedFileAction.class); private final DialogService dialogService; + private final BibEntry entry; + private final LinkedFile linkedFile; + private final String suggestedName; + private final String downloadUrl; + private final ExternalApplicationsPreferences externalApplicationsPreferences; + private final FilePreferences filePreferences; + private final TaskExecutor taskExecutor; + private final boolean keepHtmlLink; private final BibDatabaseContext databaseContext; private final DoubleProperty downloadProgress = new SimpleDoubleProperty(); + private final LinkedFileHandler linkedFileHandler; - public DownloadLinkedFileAction(BibDatabaseContext databaseContext, - BibEntry entry, - LinkedFile linkedFile, - String downloadUrl, - DialogService dialogService, - ExternalApplicationsPreferences externalApplicationsPreferences, - FilePreferences filePreferences, - TaskExecutor taskExecutor, - String suggestedName, - boolean keepHtmlLink) { + public DownloadLinkedFileAction(BibDatabaseContext databaseContext, BibEntry entry, LinkedFile linkedFile, + String downloadUrl, DialogService dialogService, + ExternalApplicationsPreferences externalApplicationsPreferences, FilePreferences filePreferences, + TaskExecutor taskExecutor, String suggestedName, boolean keepHtmlLink) { this.databaseContext = databaseContext; this.entry = entry; this.linkedFile = linkedFile; @@ -87,26 +90,15 @@ public DownloadLinkedFileAction(BibDatabaseContext databaseContext, } /** - * Downloads the given linked file to the first existing file directory. It keeps HTML files as URLs. + * Downloads the given linked file to the first existing file directory. It keeps HTML + * files as URLs. */ - public DownloadLinkedFileAction(BibDatabaseContext databaseContext, - BibEntry entry, - LinkedFile linkedFile, - String downloadUrl, - DialogService dialogService, - ExternalApplicationsPreferences externalApplicationsPreferences, - FilePreferences filePreferences, - TaskExecutor taskExecutor) { - this(databaseContext, - entry, - linkedFile, - downloadUrl, - dialogService, - externalApplicationsPreferences, - filePreferences, - taskExecutor, - "", - true); + public DownloadLinkedFileAction(BibDatabaseContext databaseContext, BibEntry entry, LinkedFile linkedFile, + String downloadUrl, DialogService dialogService, + ExternalApplicationsPreferences externalApplicationsPreferences, FilePreferences filePreferences, + TaskExecutor taskExecutor) { + this(databaseContext, entry, linkedFile, downloadUrl, dialogService, externalApplicationsPreferences, + filePreferences, taskExecutor, "", true); } @Override @@ -118,7 +110,8 @@ public void execute() { Optional targetDirectory = databaseContext.getFirstExistingFileDir(filePreferences); if (targetDirectory.isEmpty()) { - LOGGER.warn("File directory not available while downloading {}. Storing as URL in file field.", downloadUrl); + LOGGER.warn("File directory not available while downloading {}. Storing as URL in file field.", + downloadUrl); return; } @@ -129,7 +122,8 @@ public void execute() { } doDownload(targetDirectory.get(), urlDownload); - } catch (MalformedURLException exception) { + } + catch (MalformedURLException exception) { dialogService.showErrorDialogAndWait(Localization.lang("Invalid URL"), exception); } } @@ -139,9 +133,11 @@ private void doDownload(Path targetDirectory, URLDownload urlDownload) { downloadProgress.bind(downloadTask.workDonePercentageProperty()); downloadTask.titleProperty().set(Localization.lang("Downloading")); - entry.getCitationKey().ifPresentOrElse( - citationkey -> downloadTask.messageProperty().set(Localization.lang("Fulltext for %0", citationkey)), - () -> downloadTask.messageProperty().set(Localization.lang("Fulltext for a new entry"))); + entry.getCitationKey() + .ifPresentOrElse( + citationkey -> downloadTask.messageProperty() + .set(Localization.lang("Fulltext for %0", citationkey)), + () -> downloadTask.messageProperty().set(Localization.lang("Fulltext for a new entry"))); downloadTask.showToUser(true); downloadTask.onFailure(ex -> onFailure(urlDownload, ex)); @@ -159,30 +155,35 @@ private void onSuccess(Path targetDirectory, Path downloadedFile) { boolean isDuplicate; boolean isHtml; try { - isDuplicate = FileNameUniqueness.isDuplicatedFile(targetDirectory, downloadedFile.getFileName(), dialogService::notify); - } catch (IOException e) { + isDuplicate = FileNameUniqueness.isDuplicatedFile(targetDirectory, downloadedFile.getFileName(), + dialogService::notify); + } + catch (IOException e) { LOGGER.error("FileNameUniqueness.isDuplicatedFile failed", e); return; } if (isDuplicate) { // We do not add duplicate files. - // The downloaded file was deleted in {@link org.jabref.logic.util.io.FileNameUniqueness.isDuplicatedFile]} - LOGGER.info("File {} already exists in target directory {}.", downloadedFile.getFileName(), targetDirectory); + // The downloaded file was deleted in {@link + // org.jabref.logic.util.io.FileNameUniqueness.isDuplicatedFile]} + LOGGER.info("File {} already exists in target directory {}.", downloadedFile.getFileName(), + targetDirectory); return; } - // we need to call LinkedFileViewModel#fromFile, because we need to make the path relative to the configured directories - LinkedFile newLinkedFile = LinkedFilesEditorViewModel.fromFile( - downloadedFile, - databaseContext.getFileDirectories(filePreferences), - externalApplicationsPreferences); + // we need to call LinkedFileViewModel#fromFile, because we need to make the path + // relative to the configured directories + LinkedFile newLinkedFile = LinkedFilesEditorViewModel.fromFile(downloadedFile, + databaseContext.getFileDirectories(filePreferences), externalApplicationsPreferences); if (newLinkedFile.getDescription().isEmpty() && !linkedFile.getDescription().isEmpty()) { newLinkedFile.setDescription(linkedFile.getDescription()); } - if (linkedFile.getSourceUrl().isEmpty() && LinkedFile.isOnlineLink(linkedFile.getLink()) && filePreferences.shouldKeepDownloadUrl()) { + if (linkedFile.getSourceUrl().isEmpty() && LinkedFile.isOnlineLink(linkedFile.getLink()) + && filePreferences.shouldKeepDownloadUrl()) { newLinkedFile.setSourceURL(linkedFile.getLink()); - } else if (filePreferences.shouldKeepDownloadUrl()) { + } + else if (filePreferences.shouldKeepDownloadUrl()) { newLinkedFile.setSourceURL(linkedFile.getSourceUrl()); } @@ -190,18 +191,21 @@ private void onSuccess(Path targetDirectory, Path downloadedFile) { if (isHtml) { if (this.keepHtmlLink) { dialogService.notify(Localization.lang("Download '%0' was a HTML file. Keeping URL.", downloadUrl)); - } else { + } + else { dialogService.notify(Localization.lang("Download '%0' was a HTML file. Removed.", downloadUrl)); List newFiles = new ArrayList<>(entry.getFiles()); newFiles.remove(linkedFile); entry.setFiles(newFiles); try { Files.delete(downloadedFile); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Could not delete downloaded file {}.", downloadedFile, e); } } - } else { + } + else { entry.replaceDownloadedFile(linkedFile.getLink(), newLinkedFile); } } @@ -210,27 +214,34 @@ private void onFailure(URLDownload urlDownload, Exception ex) { LOGGER.error("Error downloading from URL: {}", urlDownload, ex); if (ex instanceof FetcherException fetcherException) { dialogService.showErrorDialogAndWait(fetcherException); - } else { + } + else { String fetcherExceptionMessage = ex.getLocalizedMessage(); String failedTitle = Localization.lang("Failed to download from URL"); - dialogService.showErrorDialogAndWait(failedTitle, Localization.lang("Please check the URL and try again.\nURL: %0\nDetails: %1", urlDownload.getSource(), fetcherExceptionMessage)); + dialogService.showErrorDialogAndWait(failedTitle, + Localization.lang("Please check the URL and try again.\nURL: %0\nDetails: %1", + urlDownload.getSource(), fetcherExceptionMessage)); } } private boolean checkSSLHandshake(URLDownload urlDownload) { try { urlDownload.canBeReached(); - } catch (UnirestException ex) { + } + catch (UnirestException ex) { if (ex.getCause() instanceof SSLHandshakeException) { if (dialogService.showConfirmationDialogAndWait(Localization.lang("Download file"), - Localization.lang("Unable to find valid certification path to requested target(%0), download anyway?", + Localization.lang( + "Unable to find valid certification path to requested target(%0), download anyway?", urlDownload.getSource().toString()))) { return true; - } else { + } + else { dialogService.notify(Localization.lang("Download operation canceled.")); return false; } - } else { + } + else { LOGGER.error("Error while checking if the file can be downloaded", ex); dialogService.notify(Localization.lang("Error downloading")); return false; @@ -240,27 +251,29 @@ private boolean checkSSLHandshake(URLDownload urlDownload) { } private BackgroundTask prepareDownloadTask(Path targetDirectory, URLDownload urlDownload) { - return BackgroundTask - .wrap(() -> { - String suggestedName; - if (this.suggestedName.isEmpty()) { - Optional suggestedType = inferFileType(urlDownload); - ExternalFileType externalFileType = suggestedType.orElse(StandardExternalFileType.PDF); - suggestedName = linkedFileHandler.getSuggestedFileName(externalFileType.getExtension()); - } else { - suggestedName = this.suggestedName; - } - String fulltextDir = FileUtil.createDirNameFromPattern(databaseContext.getDatabase(), entry, filePreferences.getFileDirectoryPattern()); - suggestedName = FileNameUniqueness.getNonOverWritingFileName(targetDirectory.resolve(fulltextDir), suggestedName); - - return targetDirectory.resolve(fulltextDir).resolve(suggestedName); - }) - .then(destination -> new FileDownloadTask(urlDownload.getSource(), destination)) - .onFailure(ex -> LOGGER.error("Error in download", ex)) - .onFinished(() -> { - downloadProgress.unbind(); - downloadProgress.set(1); - }); + return BackgroundTask.wrap(() -> { + String suggestedName; + if (this.suggestedName.isEmpty()) { + Optional suggestedType = inferFileType(urlDownload); + ExternalFileType externalFileType = suggestedType.orElse(StandardExternalFileType.PDF); + suggestedName = linkedFileHandler.getSuggestedFileName(externalFileType.getExtension()); + } + else { + suggestedName = this.suggestedName; + } + String fulltextDir = FileUtil.createDirNameFromPattern(databaseContext.getDatabase(), entry, + filePreferences.getFileDirectoryPattern()); + suggestedName = FileNameUniqueness.getNonOverWritingFileName(targetDirectory.resolve(fulltextDir), + suggestedName); + + return targetDirectory.resolve(fulltextDir).resolve(suggestedName); + }) + .then(destination -> new FileDownloadTask(urlDownload.getSource(), destination)) + .onFailure(ex -> LOGGER.error("Error in download", ex)) + .onFinished(() -> { + downloadProgress.unbind(); + downloadProgress.set(1); + }); } private Optional inferFileType(URLDownload urlDownload) { @@ -274,16 +287,16 @@ private Optional inferFileType(URLDownload urlDownload) { } private Optional inferFileTypeFromMimeType(URLDownload urlDownload) { - return urlDownload.getMimeType() - .flatMap(mimeType -> { - LOGGER.debug("MIME Type suggested: {}", mimeType); - return ExternalFileTypes.getExternalFileTypeByMimeType(mimeType, externalApplicationsPreferences); - }); + return urlDownload.getMimeType().flatMap(mimeType -> { + LOGGER.debug("MIME Type suggested: {}", mimeType); + return ExternalFileTypes.getExternalFileTypeByMimeType(mimeType, externalApplicationsPreferences); + }); } private Optional inferFileTypeFromURL(String url) { return URLUtil.getSuffix(url, externalApplicationsPreferences) - .flatMap(extension -> ExternalFileTypes.getExternalFileTypeByExt(extension, externalApplicationsPreferences)); + .flatMap(extension -> ExternalFileTypes.getExternalFileTypeByExt(extension, + externalApplicationsPreferences)); } public ReadOnlyDoubleProperty downloadProgress() { @@ -291,7 +304,9 @@ public ReadOnlyDoubleProperty downloadProgress() { } private static class FileDownloadTask extends BackgroundTask { + private final URL source; + private final Path destination; public FileDownloadTask(URL source, Path destination) { @@ -303,8 +318,7 @@ public FileDownloadTask(URL source, Path destination) { public Path call() throws FetcherException, IOException { URLDownload download = new URLDownload(source); try (ProgressInputStream inputStream = download.asInputStream()) { - EasyBind.subscribe( - inputStream.totalNumBytesReadProperty(), + EasyBind.subscribe(inputStream.totalNumBytesReadProperty(), bytesRead -> updateProgress(bytesRead.longValue(), inputStream.getMaxNumBytes())); // Make sure directory exists since otherwise copy fails Files.createDirectories(destination.getParent()); @@ -312,5 +326,7 @@ public Path call() throws FetcherException, IOException { } return destination; } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/linkedfile/LinkedFileEditDialog.java b/jabgui/src/main/java/org/jabref/gui/linkedfile/LinkedFileEditDialog.java index 57907637c01..a0723e50f33 100644 --- a/jabgui/src/main/java/org/jabref/gui/linkedfile/LinkedFileEditDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/linkedfile/LinkedFileEditDialog.java @@ -23,19 +23,34 @@ public class LinkedFileEditDialog extends BaseDialog { - private static final ButtonType ADD_BUTTON = new ButtonType(Localization.lang("Add"), ButtonType.OK.getButtonData()); + private static final ButtonType ADD_BUTTON = new ButtonType(Localization.lang("Add"), + ButtonType.OK.getButtonData()); + private static final ButtonType EDIT_BUTTON = ButtonType.APPLY; - @FXML private TextField link; - @FXML private TextField description; - @FXML private ComboBox fileType; - @FXML private TextField sourceUrl; + @FXML + private TextField link; + + @FXML + private TextField description; - @Inject private DialogService dialogService; - @Inject private StateManager stateManager; - @Inject private GuiPreferences preferences; + @FXML + private ComboBox fileType; + + @FXML + private TextField sourceUrl; + + @Inject + private DialogService dialogService; + + @Inject + private StateManager stateManager; + + @Inject + private GuiPreferences preferences; private LinkedFileEditDialogViewModel viewModel; + private final LinkedFile linkedFile; /** @@ -48,7 +63,6 @@ public LinkedFileEditDialog() { /** * Constructor for editing an existing LinkedFile. - * * @param linkedFile The linked file to be edited. */ public LinkedFileEditDialog(LinkedFile linkedFile) { @@ -57,9 +71,7 @@ public LinkedFileEditDialog(LinkedFile linkedFile) { } private void initializeDialog(String title, ButtonType primaryButtonType) { - ViewLoader.view(this) - .load() - .setAsContent(this.getDialogPane()); + ViewLoader.view(this).load().setAsContent(this.getDialogPane()); this.setTitle(title); this.setResizable(false); @@ -68,7 +80,8 @@ private void initializeDialog(String title, ButtonType primaryButtonType) { this.setResultConverter(button -> { if (button == primaryButtonType) { return viewModel.getNewLinkedFile(); - } else { + } + else { return null; } }); @@ -76,13 +89,13 @@ private void initializeDialog(String title, ButtonType primaryButtonType) { @FXML private void initialize() { - viewModel = new LinkedFileEditDialogViewModel(linkedFile, stateManager.getActiveDatabase().get(), dialogService, preferences.getExternalApplicationsPreferences(), preferences.getFilePreferences()); + viewModel = new LinkedFileEditDialogViewModel(linkedFile, stateManager.getActiveDatabase().get(), dialogService, + preferences.getExternalApplicationsPreferences(), preferences.getFilePreferences()); fileType.itemsProperty().bindBidirectional(viewModel.externalFileTypeProperty()); - new ViewModelListCellFactory() - .withIcon(ExternalFileType::getIcon) - .withText(ExternalFileType::getName) - .install(fileType); + new ViewModelListCellFactory().withIcon(ExternalFileType::getIcon) + .withText(ExternalFileType::getName) + .install(fileType); description.textProperty().bindBidirectional(viewModel.descriptionProperty()); link.textProperty().bindBidirectional(viewModel.linkProperty()); @@ -100,4 +113,5 @@ private void openBrowseDialog(ActionEvent event) { private void onDialogShow(DialogEvent event) { UiTaskExecutor.runInJavaFXThread(link::requestFocus); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/linkedfile/LinkedFileEditDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/linkedfile/LinkedFileEditDialogViewModel.java index c360fe6e90f..a9ce8d19cca 100644 --- a/jabgui/src/main/java/org/jabref/gui/linkedfile/LinkedFileEditDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/linkedfile/LinkedFileEditDialogViewModel.java @@ -41,27 +41,37 @@ public class LinkedFileEditDialogViewModel extends AbstractViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(LinkedFileEditDialogViewModel.class); private static final Pattern REMOTE_LINK_PATTERN = Pattern.compile("[a-z]+://.*"); + private final StringProperty link = new SimpleStringProperty(""); + private final StringProperty description = new SimpleStringProperty(""); + private final StringProperty sourceUrl = new SimpleStringProperty(""); - private final ListProperty allExternalFileTypes = new SimpleListProperty<>(FXCollections.emptyObservableList()); + + private final ListProperty allExternalFileTypes = new SimpleListProperty<>( + FXCollections.emptyObservableList()); + private final ObjectProperty selectedExternalFileType = new SimpleObjectProperty<>(); + private final ObservableOptionalValue monadicSelectedExternalFileType; + private final BibDatabaseContext database; + private final DialogService dialogService; + private final ExternalApplicationsPreferences externalApplicationsPreferences; + private final FilePreferences filePreferences; - public LinkedFileEditDialogViewModel(LinkedFile linkedFile, - BibDatabaseContext database, - DialogService dialogService, - ExternalApplicationsPreferences externalApplicationsPreferences, - FilePreferences filePreferences) { + public LinkedFileEditDialogViewModel(LinkedFile linkedFile, BibDatabaseContext database, + DialogService dialogService, ExternalApplicationsPreferences externalApplicationsPreferences, + FilePreferences filePreferences) { this.database = database; this.dialogService = dialogService; this.filePreferences = filePreferences; this.externalApplicationsPreferences = externalApplicationsPreferences; - allExternalFileTypes.set(FXCollections.observableArrayList(externalApplicationsPreferences.getExternalFileTypes())); + allExternalFileTypes + .set(FXCollections.observableArrayList(externalApplicationsPreferences.getExternalFileTypes())); monadicSelectedExternalFileType = EasyBind.wrapNullable(selectedExternalFileType); setValues(linkedFile); @@ -72,13 +82,13 @@ private void setExternalFileTypeByExtension(String link) { // Check if this looks like a remote link: if (REMOTE_LINK_PATTERN.matcher(link).matches()) { ExternalFileTypes.getExternalFileTypeByExt("html", externalApplicationsPreferences) - .ifPresent(selectedExternalFileType::setValue); + .ifPresent(selectedExternalFileType::setValue); } // Try to guess the file type: String theLink = link.trim(); ExternalFileTypes.getExternalFileTypeForName(theLink, externalApplicationsPreferences) - .ifPresent(selectedExternalFileType::setValue); + .ifPresent(selectedExternalFileType::setValue); } } @@ -91,9 +101,9 @@ public void openBrowseDialog() { String fileName = Path.of(fileText).getFileName().toString(); FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .withInitialDirectory(workingDir) - .withInitialFileName(fileName) - .build(); + .withInitialDirectory(workingDir) + .withInitialFileName(fileName) + .build(); dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(this::checkForBadFileNameAndAdd); } @@ -103,8 +113,11 @@ void checkForBadFileNameAndAdd(Path fileToAdd) { if (FileUtil.detectBadFileName(fileToAdd.toString())) { String newFilename = FileNameCleaner.cleanFileName(fileToAdd.getFileName().toString()); - boolean correctButtonPressed = dialogService.showConfirmationDialogAndWait(Localization.lang("File \"%0\" cannot be added!", fileToAdd.getFileName()), - Localization.lang("Illegal characters in the file name detected.\nFile will be renamed to \"%0\" and added.", newFilename), + boolean correctButtonPressed = dialogService.showConfirmationDialogAndWait( + Localization.lang("File \"%0\" cannot be added!", fileToAdd.getFileName()), + Localization.lang( + "Illegal characters in the file name detected.\nFile will be renamed to \"%0\" and added.", + newFilename), Localization.lang("Rename and add")); if (correctButtonPressed) { @@ -114,12 +127,14 @@ void checkForBadFileNameAndAdd(Path fileToAdd) { link.set(relativize(correctPath)); filePreferences.setWorkingDirectory(correctPath); setExternalFileTypeByExtension(link.getValueSafe()); - } catch (IOException ex) { + } + catch (IOException ex) { LOGGER.error("Error moving file", ex); dialogService.showErrorDialogAndWait(ex); } } - } else { + } + else { link.set(relativize(fileToAdd)); filePreferences.setWorkingDirectory(fileToAdd); setExternalFileTypeByExtension(link.getValueSafe()); @@ -132,15 +147,18 @@ public void setValues(LinkedFile linkedFile) { if (linkedFile.isOnlineLink()) { link.setValue(linkedFile.getLink()); // Might be an URL - } else { + } + else { link.setValue(relativize(Path.of(linkedFile.getLink()))); } // See what is a reasonable selection for the type combobox: - Optional fileType = ExternalFileTypes.getExternalFileTypeByLinkedFile(linkedFile, false, externalApplicationsPreferences); + Optional fileType = ExternalFileTypes.getExternalFileTypeByLinkedFile(linkedFile, false, + externalApplicationsPreferences); if (fileType.isPresent() && !(fileType.get() instanceof UnknownExternalFileType)) { selectedExternalFileType.setValue(fileType.get()); - } else if ((linkedFile.getLink() != null) && (!linkedFile.getLink().isEmpty())) { + } + else if ((linkedFile.getLink() != null) && (!linkedFile.getLink().isEmpty())) { setExternalFileTypeByExtension(linkedFile.getLink()); } } @@ -170,8 +188,10 @@ public LinkedFile getNewLinkedFile() { if (LinkedFile.isOnlineLink(link.getValue())) { try { - return new LinkedFile(description.getValue(), URLUtil.create(link.getValue()), fileType, sourceUrl.getValue()); - } catch (MalformedURLException e) { + return new LinkedFile(description.getValue(), URLUtil.create(link.getValue()), fileType, + sourceUrl.getValue()); + } + catch (MalformedURLException e) { return new LinkedFile(description.getValue(), link.getValue(), fileType, sourceUrl.getValue()); } } @@ -181,4 +201,5 @@ public LinkedFile getNewLinkedFile() { private String relativize(Path filePath) { return FileUtil.relativize(filePath, database, filePreferences).toString(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/linkedfile/RedownloadMissingFilesAction.java b/jabgui/src/main/java/org/jabref/gui/linkedfile/RedownloadMissingFilesAction.java index d7de66854cb..c10369e43fa 100644 --- a/jabgui/src/main/java/org/jabref/gui/linkedfile/RedownloadMissingFilesAction.java +++ b/jabgui/src/main/java/org/jabref/gui/linkedfile/RedownloadMissingFilesAction.java @@ -24,18 +24,20 @@ public class RedownloadMissingFilesAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(RedownloadMissingFilesAction.class); private final StateManager stateManager; + private final DialogService dialogService; + private final ExternalApplicationsPreferences externalApplicationsPreferences; + private final FilePreferences filePreferences; + private final TaskExecutor taskExecutor; private BibDatabaseContext databaseContext; - public RedownloadMissingFilesAction(StateManager stateManager, - DialogService dialogService, - ExternalApplicationsPreferences externalApplicationsPreferences, - FilePreferences filePreferences, - TaskExecutor taskExecutor) { + public RedownloadMissingFilesAction(StateManager stateManager, DialogService dialogService, + ExternalApplicationsPreferences externalApplicationsPreferences, FilePreferences filePreferences, + TaskExecutor taskExecutor) { this.stateManager = stateManager; this.dialogService = dialogService; this.externalApplicationsPreferences = externalApplicationsPreferences; @@ -49,8 +51,7 @@ public RedownloadMissingFilesAction(StateManager stateManager, public void execute() { if (stateManager.getActiveDatabase().isPresent()) { databaseContext = stateManager.getActiveDatabase().get(); - boolean confirm = dialogService.showConfirmationDialogAndWait( - Localization.lang("Redownload missing files"), + boolean confirm = dialogService.showConfirmationDialogAndWait(Localization.lang("Redownload missing files"), Localization.lang("Redownload missing files for current library?")); if (!confirm) { return; @@ -60,12 +61,12 @@ public void execute() { } /** - * @implNote Similar method {@link org.jabref.gui.fieldeditors.LinkedFileViewModel#redownload} + * @implNote Similar method + * {@link org.jabref.gui.fieldeditors.LinkedFileViewModel#redownload} */ private void redownloadMissing(BibDatabaseContext databaseContext) { LOGGER.info("Redownloading missing files"); - databaseContext.getEntries().forEach(entry -> - entry.getFiles().forEach(linkedFile -> { + databaseContext.getEntries().forEach(entry -> entry.getFiles().forEach(linkedFile -> { if (linkedFile.isOnlineLink() || linkedFile.getSourceUrl().isEmpty()) { return; } @@ -76,18 +77,11 @@ private void redownloadMissing(BibDatabaseContext databaseContext) { } String fileName = Path.of(linkedFile.getLink()).getFileName().toString(); - DownloadLinkedFileAction downloadAction = new DownloadLinkedFileAction( - this.databaseContext, - entry, - linkedFile, - linkedFile.getSourceUrl(), - dialogService, - externalApplicationsPreferences, - filePreferences, - taskExecutor, - fileName, - true); + DownloadLinkedFileAction downloadAction = new DownloadLinkedFileAction(this.databaseContext, entry, + linkedFile, linkedFile.getSourceUrl(), dialogService, externalApplicationsPreferences, + filePreferences, taskExecutor, fileName, true); downloadAction.execute(); })); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/logging/GuiWriter.java b/jabgui/src/main/java/org/jabref/gui/logging/GuiWriter.java index 941485989a0..6f4122ebf4a 100644 --- a/jabgui/src/main/java/org/jabref/gui/logging/GuiWriter.java +++ b/jabgui/src/main/java/org/jabref/gui/logging/GuiWriter.java @@ -35,4 +35,5 @@ public void flush() { @Override public void close() { } + } diff --git a/jabgui/src/main/java/org/jabref/gui/logging/LogMessages.java b/jabgui/src/main/java/org/jabref/gui/logging/LogMessages.java index 4e7db3d7113..32c19bb34df 100644 --- a/jabgui/src/main/java/org/jabref/gui/logging/LogMessages.java +++ b/jabgui/src/main/java/org/jabref/gui/logging/LogMessages.java @@ -6,8 +6,9 @@ import org.tinylog.core.LogEntry; /** - * This class is used for storing and archiving all message output of JabRef as log events. - * To listen to changes on the stored logs one can bind to the {@code messagesProperty}. + * This class is used for storing and archiving all message output of JabRef as log + * events. To listen to changes on the stored logs one can bind to the + * {@code messagesProperty}. */ public class LogMessages { @@ -33,4 +34,5 @@ public void add(LogEntry event) { public void clear() { messages.clear(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java b/jabgui/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java index 39c121f5b36..8903712dfe9 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java @@ -39,22 +39,38 @@ import com.tobiasdiez.easybind.optional.OptionalBinding; public class BibEntryTableViewModel { + private final BibEntry entry; + private final ObservableValue fieldValueFormatter; + private final Map> fieldValues = new HashMap<>(); + private final Map> specialFieldValues = new HashMap<>(); + private final EasyBinding> linkedFiles; + private final EasyBinding> linkedIdentifiers; + private final Binding> matchedGroups; + private final BibDatabaseContext bibDatabaseContext; + private final BooleanProperty hasFullTextResults = new SimpleBooleanProperty(false); + private final BooleanProperty isMatchedBySearch = new SimpleBooleanProperty(true); + private final BooleanProperty isVisibleBySearch = new SimpleBooleanProperty(true); + private final BooleanProperty isMatchedByGroup = new SimpleBooleanProperty(true); + private final BooleanProperty isVisibleByGroup = new SimpleBooleanProperty(true); - private final ObjectProperty matchCategory = new SimpleObjectProperty<>(MatchCategory.MATCHING_SEARCH_AND_GROUPS); - public BibEntryTableViewModel(BibEntry entry, BibDatabaseContext bibDatabaseContext, ObservableValue fieldValueFormatter) { + private final ObjectProperty matchCategory = new SimpleObjectProperty<>( + MatchCategory.MATCHING_SEARCH_AND_GROUPS); + + public BibEntryTableViewModel(BibEntry entry, BibDatabaseContext bibDatabaseContext, + ObservableValue fieldValueFormatter) { this.entry = entry; this.bibDatabaseContext = bibDatabaseContext; this.fieldValueFormatter = fieldValueFormatter; @@ -65,13 +81,9 @@ public BibEntryTableViewModel(BibEntry entry, BibDatabaseContext bibDatabaseCont } private static EasyBinding> createLinkedIdentifiersBinding(BibEntry entry) { - return EasyBind.combine( - entry.getFieldBinding(StandardField.URL), - entry.getFieldBinding(StandardField.DOI), - entry.getFieldBinding(StandardField.URI), - entry.getFieldBinding(StandardField.EPRINT), - entry.getFieldBinding(StandardField.ISBN), - (url, doi, uri, eprint, isbn) -> { + return EasyBind.combine(entry.getFieldBinding(StandardField.URL), entry.getFieldBinding(StandardField.DOI), + entry.getFieldBinding(StandardField.URI), entry.getFieldBinding(StandardField.EPRINT), + entry.getFieldBinding(StandardField.ISBN), (url, doi, uri, eprint, isbn) -> { Map identifiers = new HashMap<>(); url.ifPresent(value -> identifiers.put(StandardField.URL, value)); doi.ifPresent(value -> identifiers.put(StandardField.DOI, value)); @@ -86,15 +98,18 @@ public BibEntry getEntry() { return entry; } - private static Binding> createMatchedGroupsBinding(BibDatabaseContext database, BibEntry entry) { - return new UiThreadBinding<>(EasyBind.combine(entry.getFieldBinding(StandardField.GROUPS), database.getMetaData().groupsBinding(), - (_, _) -> - database.getMetaData().getGroups().map(groupTreeNode -> - groupTreeNode.getMatchingGroups(entry).stream() - .map(GroupTreeNode::getGroup) - .filter(Predicate.not(Predicate.isEqual(groupTreeNode.getGroup()))) - .collect(Collectors.toList())) - .orElse(List.of()))); + private static Binding> createMatchedGroupsBinding(BibDatabaseContext database, + BibEntry entry) { + return new UiThreadBinding<>( + EasyBind.combine(entry.getFieldBinding(StandardField.GROUPS), database.getMetaData().groupsBinding(), + (_, _) -> database.getMetaData() + .getGroups() + .map(groupTreeNode -> groupTreeNode.getMatchingGroups(entry) + .stream() + .map(GroupTreeNode::getGroup) + .filter(Predicate.not(Predicate.isEqual(groupTreeNode.getGroup()))) + .collect(Collectors.toList())) + .orElse(List.of()))); } public OptionalBinding getField(Field field) { @@ -119,17 +134,24 @@ public ObservableValue> getSpecialField(Spe Optional currentValue = this.entry.getField(field); if (value != null) { if (currentValue.isEmpty() && value.getValue().isEmpty()) { - OptionalBinding zeroValue = getField(field).flatMapOpt(_ -> field.parseValue("CLEAR_RANK").map(SpecialFieldValueViewModel::new)); + OptionalBinding zeroValue = getField(field) + .flatMapOpt(_ -> field.parseValue("CLEAR_RANK").map(SpecialFieldValueViewModel::new)); specialFieldValues.put(field, zeroValue); return zeroValue; - } else if (value.getValue().isEmpty() || !value.getValue().get().getValue().getFieldValue().equals(currentValue)) { - // specialFieldValues value and BibEntry value differ => Set specialFieldValues value to BibEntry value - value = getField(field).flatMapOpt(fieldValue -> field.parseValue(fieldValue).map(SpecialFieldValueViewModel::new)); + } + else if (value.getValue().isEmpty() + || !value.getValue().get().getValue().getFieldValue().equals(currentValue)) { + // specialFieldValues value and BibEntry value differ => Set + // specialFieldValues value to BibEntry value + value = getField(field) + .flatMapOpt(fieldValue -> field.parseValue(fieldValue).map(SpecialFieldValueViewModel::new)); specialFieldValues.put(field, value); return value; } - } else { - value = getField(field).flatMapOpt(fieldValue -> field.parseValue(fieldValue).map(SpecialFieldValueViewModel::new)); + } + else { + value = getField(field) + .flatMapOpt(fieldValue -> field.parseValue(fieldValue).map(SpecialFieldValueViewModel::new)); specialFieldValues.put(field, value); } return value; @@ -144,8 +166,7 @@ public ObservableValue getFields(OrFields fields) { ArrayList observables = new ArrayList<>(List.of(entry.getObservables())); observables.add(fieldValueFormatter); - value = Bindings.createStringBinding(() -> - fieldValueFormatter.getValue().formatFieldsValues(fields, entry), + value = Bindings.createStringBinding(() -> fieldValueFormatter.getValue().formatFieldsValues(fields, entry), observables.toArray(Observable[]::new)); fieldValues.put(fields, value); return value; @@ -192,12 +213,15 @@ public void updateMatchCategory() { if (isMatchedBySearch.get() && isMatchedByGroup.get()) { category = MatchCategory.MATCHING_SEARCH_AND_GROUPS; - } else if (isMatchedBySearch.get()) { + } + else if (isMatchedBySearch.get()) { category = MatchCategory.MATCHING_SEARCH_NOT_GROUPS; - } else if (isMatchedByGroup.get()) { + } + else if (isMatchedByGroup.get()) { category = MatchCategory.MATCHING_GROUPS_NOT_SEARCH; } matchCategory.set(category); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/CellFactory.java b/jabgui/src/main/java/org/jabref/gui/maintable/CellFactory.java index 19abeff7f00..c5ed779cbab 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/CellFactory.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/CellFactory.java @@ -40,7 +40,8 @@ public CellFactory(GuiPreferences preferences, UndoManager undoManager) { TABLE_ICONS.put(StandardField.EPRINT, icon); icon = IconTheme.JabRefIcons.DOI; - // icon.setToolTipText(Localization.lang("Open") + " DOI " + Localization.lang("web link")); + // icon.setToolTipText(Localization.lang("Open") + " DOI " + + // Localization.lang("web link")); TABLE_ICONS.put(StandardField.DOI, icon); icon = IconTheme.JabRefIcons.FILE; @@ -61,12 +62,14 @@ public CellFactory(GuiPreferences preferences, UndoManager undoManager) { TABLE_ICONS.put(fileType.getField(), icon); } - SpecialFieldViewModel relevanceViewModel = new SpecialFieldViewModel(SpecialField.RELEVANCE, preferences, undoManager); + SpecialFieldViewModel relevanceViewModel = new SpecialFieldViewModel(SpecialField.RELEVANCE, preferences, + undoManager); icon = relevanceViewModel.getIcon(); // icon.setToolTipText(relevanceViewModel.getLocalization()); TABLE_ICONS.put(SpecialField.RELEVANCE, icon); - SpecialFieldViewModel qualityViewModel = new SpecialFieldViewModel(SpecialField.QUALITY, preferences, undoManager); + SpecialFieldViewModel qualityViewModel = new SpecialFieldViewModel(SpecialField.QUALITY, preferences, + undoManager); icon = qualityViewModel.getIcon(); // icon.setToolTipText(qualityViewModel.getLocalization()); TABLE_ICONS.put(SpecialField.QUALITY, icon); @@ -78,19 +81,22 @@ public CellFactory(GuiPreferences preferences, UndoManager undoManager) { TABLE_ICONS.put(SpecialField.RANKING, icon); // Priority icon used for the menu - SpecialFieldViewModel priorityViewModel = new SpecialFieldViewModel(SpecialField.PRIORITY, preferences, undoManager); + SpecialFieldViewModel priorityViewModel = new SpecialFieldViewModel(SpecialField.PRIORITY, preferences, + undoManager); icon = priorityViewModel.getIcon(); // icon.setToolTipText(priorityViewModel.getLocalization()); TABLE_ICONS.put(SpecialField.PRIORITY, icon); // Read icon used for menu - SpecialFieldViewModel readViewModel = new SpecialFieldViewModel(SpecialField.READ_STATUS, preferences, undoManager); + SpecialFieldViewModel readViewModel = new SpecialFieldViewModel(SpecialField.READ_STATUS, preferences, + undoManager); icon = readViewModel.getIcon(); // icon.setToolTipText(readViewModel.getLocalization()); TABLE_ICONS.put(SpecialField.READ_STATUS, icon); // Print icon used for menu - SpecialFieldViewModel printedViewModel = new SpecialFieldViewModel(SpecialField.PRINTED, preferences, undoManager); + SpecialFieldViewModel printedViewModel = new SpecialFieldViewModel(SpecialField.PRINTED, preferences, + undoManager); icon = printedViewModel.getIcon(); // icon.setToolTipText(printedViewModel.getLocalization()); TABLE_ICONS.put(SpecialField.PRINTED, icon); @@ -101,9 +107,12 @@ public Node getTableIcon(Field field) { if (icon == null) { // LOGGER.info("Error: no table icon defined for type '" + field + "'."); return null; - } else { - // node should be generated for each call, as nodes can be added to the scene graph only once + } + else { + // node should be generated for each call, as nodes can be added to the scene + // graph only once return icon.getGraphicNode(); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java b/jabgui/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java index ca000f2ce28..fecc2eab1a5 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java @@ -8,13 +8,15 @@ public class ColumnPreferences { public static final double DEFAULT_COLUMN_WIDTH = 100; - public static final double ICON_COLUMN_WIDTH = 16 + 12; // add some additional space to improve appearance + + public static final double ICON_COLUMN_WIDTH = 16 + 12; // add some additional space + // to improve appearance private final ObservableList columns; + private final ObservableList columnSortOrder; - public ColumnPreferences(List columns, - List columnSortOrder) { + public ColumnPreferences(List columns, List columnSortOrder) { this.columns = FXCollections.observableArrayList(columns); this.columnSortOrder = FXCollections.observableArrayList(columnSortOrder); } @@ -36,4 +38,5 @@ public void setColumnSortOrder(List list) { columnSortOrder.clear(); columnSortOrder.addAll(list); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java b/jabgui/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java index b4aa68fd321..f558d18aef5 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java @@ -33,40 +33,40 @@ * SIDE EFFECT: Sets the "cites" field of the entry having the linked files * *
      - *
    • Mode choice A: online or offline
    • - *
    • Mode choice B: complete entry or single file (the latter is not implemented)
    • + *
    • Mode choice A: online or offline
    • + *
    • Mode choice B: complete entry or single file (the latter is not implemented)
    • *
    * * The mode is selected by the preferences whether to use Grobid or not. */ public class ExtractReferencesAction extends SimpleCommand { + private final int FILES_LIMIT = 10; private final DialogService dialogService; + private final StateManager stateManager; + private final CliPreferences preferences; + private final BibEntry entry; + private final LinkedFile linkedFile; private final BibliographyFromPdfImporter bibliographyFromPdfImporter; - public ExtractReferencesAction(DialogService dialogService, - StateManager stateManager, - CliPreferences preferences) { + public ExtractReferencesAction(DialogService dialogService, StateManager stateManager, CliPreferences preferences) { this(dialogService, stateManager, preferences, null, null); } /** - * Can be used to bind the action on a context menu in the linked file view (future work) - * + * Can be used to bind the action on a context menu in the linked file view (future + * work) * @param entry the entry to handle (can be null) * @param linkedFile the linked file (can be null) */ - private ExtractReferencesAction(@NonNull DialogService dialogService, - @NonNull StateManager stateManager, - @NonNull CliPreferences preferences, - @Nullable BibEntry entry, - @Nullable LinkedFile linkedFile) { + private ExtractReferencesAction(@NonNull DialogService dialogService, @NonNull StateManager stateManager, + @NonNull CliPreferences preferences, @Nullable BibEntry entry, @Nullable LinkedFile linkedFile) { this.dialogService = dialogService; this.stateManager = stateManager; this.preferences = preferences; @@ -75,11 +75,10 @@ private ExtractReferencesAction(@NonNull DialogService dialogService, bibliographyFromPdfImporter = new BibliographyFromPdfImporter(preferences.getCitationKeyPatternPreferences()); if (this.linkedFile == null) { - this.executable.bind( - ActionHelper.needsEntriesSelected(stateManager) - .and(ActionHelper.hasLinkedFileForSelectedEntries(stateManager)) - ); - } else { + this.executable.bind(ActionHelper.needsEntriesSelected(stateManager) + .and(ActionHelper.hasLinkedFileForSelectedEntries(stateManager))); + } + else { this.setExecutable(true); } } @@ -94,23 +93,26 @@ private void extractReferences() { List selectedEntries; if (entry == null) { selectedEntries = stateManager.getSelectedEntries(); - } else { + } + else { selectedEntries = List.of(entry); } boolean online = this.preferences.getGrobidPreferences().isGrobidEnabled(); Callable parserResultCallable; if (online) { - Optional> parserResultCallableOnline = getParserResultCallableOnline(databaseContext, selectedEntries); + Optional> parserResultCallableOnline = getParserResultCallableOnline( + databaseContext, selectedEntries); if (parserResultCallableOnline.isEmpty()) { return; } parserResultCallable = parserResultCallableOnline.get(); - } else { + } + else { parserResultCallable = getParserResultCallableOffline(databaseContext, selectedEntries); } BackgroundTask task = BackgroundTask.wrap(parserResultCallable) - .withInitialMessage(Localization.lang("Processing PDF(s)")); + .withInitialMessage(Localization.lang("Processing PDF(s)")); task.onFailure(dialogService::showErrorDialogAndWait); @@ -118,7 +120,8 @@ private void extractReferences() { String title; if (online) { title = Localization.lang("Extract References (online)"); - } else { + } + else { title = Localization.lang("Extract References (offline)"); } dialog.setTitle(title); @@ -126,12 +129,15 @@ private void extractReferences() { }); } - private @NonNull Callable getParserResultCallableOffline(BibDatabaseContext databaseContext, List selectedEntries) { + private @NonNull Callable getParserResultCallableOffline(BibDatabaseContext databaseContext, + List selectedEntries) { return () -> { BibEntry currentEntry = selectedEntries.getFirst(); - List fileList = FileUtil.getListOfLinkedFiles(selectedEntries, databaseContext.getFileDirectories(preferences.getFilePreferences())); + List fileList = FileUtil.getListOfLinkedFiles(selectedEntries, + databaseContext.getFileDirectories(preferences.getFilePreferences())); - // We need to have ParserResult handled at the importer, because it imports the meta data (library type, encoding, ...) + // We need to have ParserResult handled at the importer, because it imports + // the meta data (library type, encoding, ...) ParserResult result = bibliographyFromPdfImporter.importDatabase(fileList.getFirst()); // subsequent files are just appended to result @@ -144,7 +150,8 @@ private void extractReferences() { selectedEntriesIterator.next(); // skip first entry while (selectedEntriesIterator.hasNext()) { currentEntry = selectedEntriesIterator.next(); - fileList = FileUtil.getListOfLinkedFiles(List.of(currentEntry), databaseContext.getFileDirectories(preferences.getFilePreferences())); + fileList = FileUtil.getListOfLinkedFiles(List.of(currentEntry), + databaseContext.getFileDirectories(preferences.getFilePreferences())); fileListIterator = fileList.iterator(); extractReferences(fileListIterator, result, currentEntry); } @@ -155,7 +162,9 @@ private void extractReferences() { private void extractReferences(Iterator fileListIterator, ParserResult result, BibEntry currentEntry) { while (fileListIterator.hasNext()) { - result.getDatabase().insertEntries(bibliographyFromPdfImporter.importDatabase(fileListIterator.next()).getDatabase().getEntries()); + result.getDatabase() + .insertEntries( + bibliographyFromPdfImporter.importDatabase(fileListIterator.next()).getDatabase().getEntries()); } String cites = getCites(result.getDatabase().getEntries(), currentEntry); @@ -163,11 +172,12 @@ private void extractReferences(Iterator fileListIterator, ParserResult res } /** - * Creates the field content for the "cites" field. The field contains the citation keys of the imported entries. + * Creates the field content for the "cites" field. The field contains the citation + * keys of the imported entries. * * TODO: Move this part to logic somehow - * - * @param currentEntry used to create citation keys if the importer did not provide one from the imported entry + * @param currentEntry used to create citation keys if the importer did not provide + * one from the imported entry */ private static String getCites(List entries, BibEntry currentEntry) { StringJoiner cites = new StringJoiner(","); @@ -178,20 +188,24 @@ private static String getCites(List entries, BibEntry currentEntry) { String citationKeyToAdd; if (citationKey.isPresent()) { citationKeyToAdd = citationKey.get(); - } else { + } + else { // No key present -> generate one based on - // the citation key of the entry holding the files and - // the number of the current entry (extracted from the reference; fallback: current number of the entry (count variable)) + // the citation key of the entry holding the files and + // the number of the current entry (extracted from the reference; + // fallback: current number of the entry (count variable)) String sourceCitationKey = currentEntry.getCitationKey().orElse("unknown"); String newCitationKey; // Could happen if no author and no year is present - // We use the number of the comment field (because there is no other way to get the number reliable) + // We use the number of the comment field (because there is no other way + // to get the number reliable) Pattern pattern = Pattern.compile("^\\[(\\d+)\\]"); Matcher matcher = pattern.matcher(importedEntry.getField(StandardField.COMMENT).orElse("")); if (matcher.hasMatch()) { newCitationKey = sourceCitationKey + "-" + matcher.group(1); - } else { + } + else { newCitationKey = sourceCitationKey + "-" + count; } importedEntry.setCitationKey(newCitationKey); @@ -202,18 +216,21 @@ private static String getCites(List entries, BibEntry currentEntry) { return cites.toString(); } - private Optional> getParserResultCallableOnline(BibDatabaseContext databaseContext, List selectedEntries) { - List fileList = FileUtil.getListOfLinkedFiles(selectedEntries, databaseContext.getFileDirectories(preferences.getFilePreferences())); + private Optional> getParserResultCallableOnline(BibDatabaseContext databaseContext, + List selectedEntries) { + List fileList = FileUtil.getListOfLinkedFiles(selectedEntries, + databaseContext.getFileDirectories(preferences.getFilePreferences())); if (fileList.size() > FILES_LIMIT) { - boolean continueOpening = dialogService.showConfirmationDialogAndWait(Localization.lang("Processing a large number of files"), + boolean continueOpening = dialogService.showConfirmationDialogAndWait( + Localization.lang("Processing a large number of files"), Localization.lang("You are about to process %0 files. Continue?", fileList.size()), Localization.lang("Continue"), Localization.lang("Cancel")); if (!continueOpening) { return Optional.empty(); } } - return Optional.of(() -> new ParserResult( - new GrobidService(this.preferences.getGrobidPreferences()).processReferences(fileList, preferences.getImportFormatPreferences()) - )); + return Optional.of(() -> new ParserResult(new GrobidService(this.preferences.getGrobidPreferences()) + .processReferences(fileList, preferences.getImportFormatPreferences()))); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/MainTable.java b/jabgui/src/main/java/org/jabref/gui/maintable/MainTable.java index f95dc15be2d..e27fa0c15ee 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/MainTable.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/MainTable.java @@ -75,41 +75,55 @@ @AllowedToUseClassGetResource("JavaFX internally handles the passed URLs properly.") public class MainTable extends TableView { + private static final Logger LOGGER = LoggerFactory.getLogger(MainTable.class); - private static final PseudoClass MATCHING_SEARCH_AND_GROUPS = PseudoClass.getPseudoClass("matching-search-and-groups"); - private static final PseudoClass MATCHING_SEARCH_NOT_GROUPS = PseudoClass.getPseudoClass("matching-search-not-groups"); - private static final PseudoClass MATCHING_GROUPS_NOT_SEARCH = PseudoClass.getPseudoClass("matching-groups-not-search"); - private static final PseudoClass NOT_MATCHING_SEARCH_AND_GROUPS = PseudoClass.getPseudoClass("not-matching-search-and-groups"); + + private static final PseudoClass MATCHING_SEARCH_AND_GROUPS = PseudoClass + .getPseudoClass("matching-search-and-groups"); + + private static final PseudoClass MATCHING_SEARCH_NOT_GROUPS = PseudoClass + .getPseudoClass("matching-search-not-groups"); + + private static final PseudoClass MATCHING_GROUPS_NOT_SEARCH = PseudoClass + .getPseudoClass("matching-groups-not-search"); + + private static final PseudoClass NOT_MATCHING_SEARCH_AND_GROUPS = PseudoClass + .getPseudoClass("not-matching-search-and-groups"); private final LibraryTab libraryTab; + private final StateManager stateManager; + private final BibDatabaseContext database; + private final GuiPreferences preferences; + private final DialogService dialogService; + private final MainTableDataModel model; + private final CustomLocalDragboard localDragboard; + private final TaskExecutor taskExecutor; + private final UndoManager undoManager; + private final FilePreferences filePreferences; + private final ImportHandler importHandler; + private final ClipboardContentGenerator clipboardContentGenerator; private long lastKeyPressTime; + private String columnSearchTerm; + private boolean citationMergeMode = false; - public MainTable(MainTableDataModel model, - LibraryTab libraryTab, - LibraryTabContainer tabContainer, - BibDatabaseContext database, - GuiPreferences preferences, - DialogService dialogService, - StateManager stateManager, - KeyBindingRepository keyBindingRepository, - ClipBoardManager clipBoardManager, - BibEntryTypesManager entryTypesManager, - TaskExecutor taskExecutor, - ImportHandler importHandler) { + public MainTable(MainTableDataModel model, LibraryTab libraryTab, LibraryTabContainer tabContainer, + BibDatabaseContext database, GuiPreferences preferences, DialogService dialogService, + StateManager stateManager, KeyBindingRepository keyBindingRepository, ClipBoardManager clipBoardManager, + BibEntryTypesManager entryTypesManager, TaskExecutor taskExecutor, ImportHandler importHandler) { super(); this.libraryTab = libraryTab; this.stateManager = stateManager; @@ -121,7 +135,9 @@ public MainTable(MainTableDataModel model, this.undoManager = libraryTab.getUndoManager(); this.filePreferences = preferences.getFilePreferences(); this.importHandler = importHandler; - this.clipboardContentGenerator = new ClipboardContentGenerator(preferences.getPreviewPreferences(), preferences.getLayoutFormatterPreferences(), Injector.instantiateModelOrService(JournalAbbreviationRepository.class)); + this.clipboardContentGenerator = new ClipboardContentGenerator(preferences.getPreviewPreferences(), + preferences.getLayoutFormatterPreferences(), + Injector.instantiateModelOrService(JournalAbbreviationRepository.class)); MainTablePreferences mainTablePreferences = preferences.getMainTablePreferences(); @@ -132,66 +148,58 @@ public MainTable(MainTableDataModel model, this.getStyleClass().add("main-table"); - MainTableColumnFactory mainTableColumnFactory = new MainTableColumnFactory( - database, - preferences, - preferences.getMainTableColumnPreferences(), - undoManager, - dialogService, - stateManager, - taskExecutor); + MainTableColumnFactory mainTableColumnFactory = new MainTableColumnFactory(database, preferences, + preferences.getMainTableColumnPreferences(), undoManager, dialogService, stateManager, taskExecutor); this.getColumns().addAll(mainTableColumnFactory.createColumns()); this.getColumns().removeIf(LibraryColumn.class::isInstance); - new ViewModelTableRowFactory() - .withOnMouseClickedEvent((entry, event) -> { - if (event.getClickCount() == 2) { - libraryTab.showAndEdit(entry.getEntry()); - } - }) - .withContextMenu(entry -> RightClickMenu.create(entry, - keyBindingRepository, - libraryTab, - dialogService, - stateManager, - preferences, - undoManager, - clipBoardManager, - taskExecutor, - Injector.instantiateModelOrService(JournalAbbreviationRepository.class), - entryTypesManager, - importHandler)) - .withPseudoClass(MATCHING_SEARCH_AND_GROUPS, entry -> entry.matchCategory().isEqualTo(MatchCategory.MATCHING_SEARCH_AND_GROUPS)) - .withPseudoClass(MATCHING_SEARCH_NOT_GROUPS, entry -> entry.matchCategory().isEqualTo(MatchCategory.MATCHING_SEARCH_NOT_GROUPS)) - .withPseudoClass(MATCHING_GROUPS_NOT_SEARCH, entry -> entry.matchCategory().isEqualTo(MatchCategory.MATCHING_GROUPS_NOT_SEARCH)) - .withPseudoClass(NOT_MATCHING_SEARCH_AND_GROUPS, entry -> entry.matchCategory().isEqualTo(MatchCategory.NOT_MATCHING_SEARCH_AND_GROUPS)) - .setOnDragDetected(this::handleOnDragDetected) - .setOnDragDropped(this::handleOnDragDropped) - .setOnDragOver(this::handleOnDragOver) - .setOnDragExited(this::handleOnDragExited) - .setOnMouseDragEntered(this::handleOnDragEntered) - .install(this); + new ViewModelTableRowFactory().withOnMouseClickedEvent((entry, event) -> { + if (event.getClickCount() == 2) { + libraryTab.showAndEdit(entry.getEntry()); + } + }) + .withContextMenu(entry -> RightClickMenu.create(entry, keyBindingRepository, libraryTab, dialogService, + stateManager, preferences, undoManager, clipBoardManager, taskExecutor, + Injector.instantiateModelOrService(JournalAbbreviationRepository.class), entryTypesManager, + importHandler)) + .withPseudoClass(MATCHING_SEARCH_AND_GROUPS, + entry -> entry.matchCategory().isEqualTo(MatchCategory.MATCHING_SEARCH_AND_GROUPS)) + .withPseudoClass(MATCHING_SEARCH_NOT_GROUPS, + entry -> entry.matchCategory().isEqualTo(MatchCategory.MATCHING_SEARCH_NOT_GROUPS)) + .withPseudoClass(MATCHING_GROUPS_NOT_SEARCH, + entry -> entry.matchCategory().isEqualTo(MatchCategory.MATCHING_GROUPS_NOT_SEARCH)) + .withPseudoClass(NOT_MATCHING_SEARCH_AND_GROUPS, + entry -> entry.matchCategory().isEqualTo(MatchCategory.NOT_MATCHING_SEARCH_AND_GROUPS)) + .setOnDragDetected(this::handleOnDragDetected) + .setOnDragDropped(this::handleOnDragDropped) + .setOnDragOver(this::handleOnDragOver) + .setOnDragExited(this::handleOnDragExited) + .setOnMouseDragEntered(this::handleOnDragEntered) + .install(this); this.getSortOrder().clear(); - // force match category column to be the first sort order, (match_category column is always the first column) + // force match category column to be the first sort order, (match_category column + // is always the first column) this.getSortOrder().addFirst(getColumns().getFirst()); this.getSortOrder().addListener((ListChangeListener>) change -> { - if (!this.getSortOrder().getFirst().equals(getColumns().getFirst())) { - this.getSortOrder().addFirst(getColumns().getFirst()); - } - }); + if (!this.getSortOrder().getFirst().equals(getColumns().getFirst())) { + this.getSortOrder().addFirst(getColumns().getFirst()); + } + }); - mainTablePreferences.getColumnPreferences().getColumnSortOrder().forEach(columnModel -> - this.getColumns().stream() - .map(column -> (MainTableColumn) column) - .filter(column -> column.getModel().equals(columnModel)) - .findFirst() - .ifPresent(column -> { - LOGGER.trace("Adding sort order for col {} ", column); - this.getSortOrder().add(column); - })); + mainTablePreferences.getColumnPreferences() + .getColumnSortOrder() + .forEach(columnModel -> this.getColumns() + .stream() + .map(column -> (MainTableColumn) column) + .filter(column -> column.getModel().equals(columnModel)) + .findFirst() + .ifPresent(column -> { + LOGGER.trace("Adding sort order for col {} ", column); + this.getSortOrder().add(column); + })); if (mainTablePreferences.getResizeColumnsToFit()) { this.setColumnResizePolicy(new SmartConstrainedResizePolicy()); @@ -223,33 +231,40 @@ public MainTable(MainTableDataModel model, updatePlaceholder(placeholderBox); - database.getDatabase().getEntries().addListener((ListChangeListener) change -> updatePlaceholder(placeholderBox)); + database.getDatabase() + .getEntries() + .addListener((ListChangeListener) change -> updatePlaceholder(placeholderBox)); - this.getItems().addListener((ListChangeListener) change -> updatePlaceholder(placeholderBox)); + this.getItems() + .addListener((ListChangeListener) change -> updatePlaceholder(placeholderBox)); // Enable sorting - // Workaround for a JavaFX bug: https://bugs.openjdk.org/browse/JDK-8301761 (The sorting of the SortedList can become invalid) - // The default comparator of the SortedList does not consider the insertion index of entries that are equal according to the comparator. - // When two entries are equal based on the comparator, the entry that was inserted first should be considered smaller. + // Workaround for a JavaFX bug: https://bugs.openjdk.org/browse/JDK-8301761 (The + // sorting of the SortedList can become invalid) + // The default comparator of the SortedList does not consider the insertion index + // of entries that are equal according to the comparator. + // When two entries are equal based on the comparator, the entry that was inserted + // first should be considered smaller. this.setSortPolicy(_ -> true); - model.getEntriesFilteredAndSorted().comparatorProperty().bind( - this.comparatorProperty().map(comparator -> { - if (comparator == null) { - return null; - } - - return (entry1, entry2) -> { - int result = comparator.compare(entry1, entry2); - if (result != 0) { - return result; - } - // If the entries are equal according to the comparator, compare them by their index in the database. - // The comparison should ideally be based on the database index, but retrieving the index takes log(n). See {@link BibDatabase#indexOf}. - // Using the entry ID is also valid since IDs are monotonically increasing. - return entry1.getEntry().getId().compareTo(entry2.getEntry().getId()); - }; - }) - ); + model.getEntriesFilteredAndSorted().comparatorProperty().bind(this.comparatorProperty().map(comparator -> { + if (comparator == null) { + return null; + } + + return (entry1, entry2) -> { + int result = comparator.compare(entry1, entry2); + if (result != 0) { + return result; + } + // If the entries are equal according to the comparator, compare them by + // their index in the database. + // The comparison should ideally be based on the database index, but + // retrieving the index takes log(n). See {@link BibDatabase#indexOf}. + // Using the entry ID is also valid since IDs are monotonically + // increasing. + return entry1.getEntry().getId().compareTo(entry2.getEntry().getId()); + }; + })); // Store visual state new PersistenceVisualStateTable(this, mainTablePreferences.getColumnPreferences()).addListeners(); @@ -271,11 +286,12 @@ public MainTable(MainTableDataModel model, } /** - * This is called, if a user starts typing some characters into the keyboard with focus on main table. The {@link MainTable} will scroll to the cell with the same starting column value and typed string - * If the user presses any other special key as well, e.g. alt or shift we don't jump - * + * This is called, if a user starts typing some characters into the keyboard with + * focus on main table. The {@link MainTable} will scroll to the cell with the same + * starting column value and typed string If the user presses any other special key as + * well, e.g. alt or shift we don't jump * @param sortedColumn The sorted column in {@link MainTable} - * @param keyEvent The pressed character + * @param keyEvent The pressed character */ private void jumpToSearchKey(TableColumn sortedColumn, KeyEvent keyEvent) { if (keyEvent.isAltDown() || keyEvent.isControlDown() || keyEvent.isMetaDown() || keyEvent.isShiftDown()) { @@ -287,18 +303,20 @@ private void jumpToSearchKey(TableColumn sortedColumn if ((System.currentTimeMillis() - lastKeyPressTime) < 700) { columnSearchTerm += keyEvent.getCharacter().toLowerCase(); - } else { + } + else { columnSearchTerm = keyEvent.getCharacter().toLowerCase(); } lastKeyPressTime = System.currentTimeMillis(); - this.getItems().stream() + this.getItems() + .stream() .filter(item -> Optional.ofNullable(sortedColumn.getCellObservableValue(item).getValue()) - .map(Object::toString) - .orElse("") - .toLowerCase() - .startsWith(columnSearchTerm)) + .map(Object::toString) + .orElse("") + .toLowerCase() + .startsWith(columnSearchTerm)) .findFirst() .ifPresent(item -> { getSelectionModel().clearSelection(); @@ -312,7 +330,8 @@ public void clearAndSelect(BibEntry bibEntry) { if (citationMergeMode) { // keep original entry selected and reset citation merge mode this.citationMergeMode = false; - } else { + } + else { // select new entry getSelectionModel().clearSelection(); findEntry(bibEntry).ifPresent(entry -> { @@ -327,15 +346,16 @@ public void clearAndSelect(List bibEntries) { if (citationMergeMode) { // keep original entry selected and reset citation merge mode this.citationMergeMode = false; - } else { + } + else { // select new entries getSelectionModel().clearSelection(); List entries = bibEntries.stream() - .filter(bibEntry -> bibEntry.getCitationKey().isPresent()) - .map(bibEntry -> findEntryByCitationKey(bibEntry.getCitationKey().get())) - .filter(Optional::isPresent) - .map(Optional::get) - .toList(); + .filter(bibEntry -> bibEntry.getCitationKey().isPresent()) + .map(bibEntry -> findEntryByCitationKey(bibEntry.getCitationKey().get())) + .filter(Optional::isPresent) + .map(Optional::get) + .toList(); entries.forEach(entry -> getSelectionModel().select(entry)); if (!entries.isEmpty()) { scrollTo(entries.getFirst()); @@ -386,17 +406,20 @@ private void setupKeyBindings(KeyBindingRepository keyBindings) { EditAction pasteAction = new EditAction(StandardActions.PASTE, () -> libraryTab, stateManager, undoManager); EditAction copyAction = new EditAction(StandardActions.COPY, () -> libraryTab, stateManager, undoManager); EditAction cutAction = new EditAction(StandardActions.CUT, () -> libraryTab, stateManager, undoManager); - EditAction deleteAction = new EditAction(StandardActions.DELETE_ENTRY, () -> libraryTab, stateManager, undoManager); + EditAction deleteAction = new EditAction(StandardActions.DELETE_ENTRY, () -> libraryTab, stateManager, + undoManager); OpenUrlAction openUrlAction = new OpenUrlAction(dialogService, stateManager, preferences); - OpenSelectedEntriesFilesAction openSelectedEntriesFilesActionFileAction = new OpenSelectedEntriesFilesAction(dialogService, stateManager, preferences, taskExecutor); - MergeWithFetchedEntryAction mergeWithFetchedEntryAction = new MergeWithFetchedEntryAction(dialogService, stateManager, taskExecutor, preferences, undoManager); - LookupIdentifierAction lookupIdentifierAction = new LookupIdentifierAction<>(WebFetchers.getIdFetcherForIdentifier(DOI.class), stateManager, undoManager, dialogService, taskExecutor); + OpenSelectedEntriesFilesAction openSelectedEntriesFilesActionFileAction = new OpenSelectedEntriesFilesAction( + dialogService, stateManager, preferences, taskExecutor); + MergeWithFetchedEntryAction mergeWithFetchedEntryAction = new MergeWithFetchedEntryAction(dialogService, + stateManager, taskExecutor, preferences, undoManager); + LookupIdentifierAction lookupIdentifierAction = new LookupIdentifierAction<>( + WebFetchers.getIdFetcherForIdentifier(DOI.class), stateManager, undoManager, dialogService, + taskExecutor); this.addEventFilter(KeyEvent.KEY_PRESSED, event -> { if (event.getCode() == KeyCode.ENTER) { - getSelectedEntries().stream() - .findFirst() - .ifPresent(libraryTab::showAndEdit); + getSelectedEntries().stream().findFirst().ifPresent(libraryTab::showAndEdit); event.consume(); return; } @@ -433,7 +456,7 @@ private void setupKeyBindings(KeyBindingRepository keyBindings) { event.consume(); break; case SCROLL_TO_PREVIOUS_MATCH_CATEGORY: - scrollToPreviousMatchCategory(); + scrollToPreviousMatchCategory(); event.consume(); break; case OPEN_URL_OR_DOI: @@ -486,36 +509,47 @@ private void handleOnDragOverTableView(DragEvent event) { event.consume(); } - private void handleOnDragEntered(TableRow row, BibEntryTableViewModel entry, MouseDragEvent event) { - // Support the following gesture to select entries: click on one row -> hold mouse button -> move over other rows - // We need to select all items between the starting row and the row where the user currently hovers the mouse over - // It is not enough to just select the currently hovered row since then sometimes rows are not marked selected if the user moves to fast + private void handleOnDragEntered(TableRow row, BibEntryTableViewModel entry, + MouseDragEvent event) { + // Support the following gesture to select entries: click on one row -> hold mouse + // button -> move over other rows + // We need to select all items between the starting row and the row where the user + // currently hovers the mouse over + // It is not enough to just select the currently hovered row since then sometimes + // rows are not marked selected if the user moves to fast @SuppressWarnings("unchecked") TableRow sourceRow = (TableRow) event.getGestureSource(); getSelectionModel().selectRange(sourceRow.getIndex(), row.getIndex()); } - private void handleOnDragExited(TableRow row, BibEntryTableViewModel entry, DragEvent dragEvent) { + private void handleOnDragExited(TableRow row, BibEntryTableViewModel entry, + DragEvent dragEvent) { ControlHelper.removeDroppingPseudoClasses(row); } - private void handleOnDragDetected(TableRow row, BibEntryTableViewModel entry, MouseEvent event) { + private void handleOnDragDetected(TableRow row, BibEntryTableViewModel entry, + MouseEvent event) { // Start drag'n'drop row.startFullDrag(); - List entries = getSelectionModel().getSelectedItems().stream().map(BibEntryTableViewModel::getEntry).collect(Collectors.toList()); + List entries = getSelectionModel().getSelectedItems() + .stream() + .map(BibEntryTableViewModel::getEntry) + .collect(Collectors.toList()); ClipboardContent content; try { content = clipboardContentGenerator.generate(entries, CitationStyleOutputFormat.HTML, database); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.warn("Could not generate clipboard content. Falling back to empty clipboard", e); content = new ClipboardContent(); } // Required to be able to drop the entries inside JabRef content.put(DragAndDropDataFormats.ENTRIES, ""); - // Drag'n'drop to other tabs use COPY TransferMode, drop to group sidepane use MOVE + // Drag'n'drop to other tabs use COPY TransferMode, drop to group sidepane use + // MOVE Dragboard dragboard = startDragAndDrop(TransferMode.COPY_OR_MOVE); dragboard.setContent(content); @@ -526,7 +560,8 @@ private void handleOnDragDetected(TableRow row, BibEntry event.consume(); } - private void handleOnDragDropped(TableRow row, BibEntryTableViewModel target, DragEvent event) { + private void handleOnDragDropped(TableRow row, BibEntryTableViewModel target, + DragEvent event) { boolean success = false; if (event.getDragboard().hasFiles()) { @@ -537,9 +572,12 @@ private void handleOnDragDropped(TableRow row, BibEntryT TransferMode transferMode = event.getTransferMode(); switch (ControlHelper.getDroppingMouseLocation(row, event)) { - // Different actions depending on where the user releases the drop in the target row + // Different actions depending on where the user releases the drop in the + // target row // - Bottom + top -> import entries - case TOP, BOTTOM -> importHandler.importFilesInBackground(files, database, filePreferences, transferMode).executeWith(taskExecutor); + case TOP, BOTTOM -> + importHandler.importFilesInBackground(files, database, filePreferences, transferMode) + .executeWith(taskExecutor); // - Center -> modify entry: link files to entry case CENTER -> { BibEntry entry = target.getEntry(); @@ -560,9 +598,8 @@ private void handleOnDragDroppedTableView(DragEvent event) { if (event.getDragboard().hasFiles()) { List files = event.getDragboard().getFiles().stream().map(File::toPath).toList(); - importHandler - .importFilesInBackground(files, this.database, filePreferences, event.getTransferMode()) - .executeWith(taskExecutor); + importHandler.importFilesInBackground(files, this.database, filePreferences, event.getTransferMode()) + .executeWith(taskExecutor); success = true; } @@ -579,11 +616,10 @@ public MainTableDataModel getTableModel() { } public List getSelectedEntries() { - return getSelectionModel() - .getSelectedItems() - .stream() - .map(BibEntryTableViewModel::getEntry) - .collect(Collectors.toList()); + return getSelectionModel().getSelectedItems() + .stream() + .map(BibEntryTableViewModel::getEntry) + .collect(Collectors.toList()); } private Optional findEntry(BibEntry entry) { @@ -599,28 +635,29 @@ public void setCitationMergeMode(boolean citationMerge) { } private void updatePlaceholder(VBox placeholderBox) { - if (database.getDatabase().getEntries().isEmpty()) { - this.setPlaceholder(placeholderBox); - // [impl->req~maintable.focus~1] - requestFocus(); - } else { - this.setPlaceholder(null); - } + if (database.getDatabase().getEntries().isEmpty()) { + this.setPlaceholder(placeholderBox); + // [impl->req~maintable.focus~1] + requestFocus(); + } + else { + this.setPlaceholder(null); + } } private BibEntry addExampleEntry() { BibEntry exampleEntry = new BibEntry(StandardEntryType.Article) - .withField(StandardField.AUTHOR, "Oliver Kopp and Carl Christian Snethlage and Christoph Schwentker") - .withField(StandardField.TITLE, "JabRef: BibTeX-based literature management software") - .withField(StandardField.JOURNAL, "TUGboat") - .withField(StandardField.VOLUME, "44") - .withField(StandardField.NUMBER, "3") - .withField(StandardField.PAGES, "441--447") - .withField(StandardField.DOI, "10.47397/tb/44-3/tb138kopp-jabref") - .withField(StandardField.ISSN, "0896-3207") - .withField(StandardField.ISSUE, "138") - .withField(StandardField.YEAR, "2023") - .withChanged(true); + .withField(StandardField.AUTHOR, "Oliver Kopp and Carl Christian Snethlage and Christoph Schwentker") + .withField(StandardField.TITLE, "JabRef: BibTeX-based literature management software") + .withField(StandardField.JOURNAL, "TUGboat") + .withField(StandardField.VOLUME, "44") + .withField(StandardField.NUMBER, "3") + .withField(StandardField.PAGES, "441--447") + .withField(StandardField.DOI, "10.47397/tb/44-3/tb138kopp-jabref") + .withField(StandardField.ISSN, "0896-3207") + .withField(StandardField.ISSUE, "138") + .withField(StandardField.YEAR, "2023") + .withChanged(true); database.getDatabase().insertEntry(exampleEntry); return exampleEntry; @@ -630,9 +667,7 @@ private void importPdfs() { List fileDirectories = database.getFileDirectories(filePreferences); if (fileDirectories.isEmpty()) { - dialogService.notify( - Localization.lang("File directory is not set or does not exist.") - ); + dialogService.notify(Localization.lang("File directory is not set or does not exist.")); LibraryPropertiesAction libraryPropertiesAction = new LibraryPropertiesAction(stateManager); libraryPropertiesAction.execute(); return; @@ -641,4 +676,5 @@ private void importPdfs() { FindUnlinkedFilesAction findUnlinkedFilesAction = new FindUnlinkedFilesAction(dialogService, stateManager); findUnlinkedFilesAction.execute(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java b/jabgui/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java index d419cb90678..2fe0ba497e2 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java @@ -52,25 +52,30 @@ public class MainTableColumnFactory { public static final String STYLE_ICON_COLUMN = "column-icon"; + private static final Logger LOGGER = LoggerFactory.getLogger(MainTableColumnFactory.class); private final GuiPreferences preferences; + private final ColumnPreferences columnPreferences; + private final BibDatabaseContext database; + private final CellFactory cellFactory; + private final UndoManager undoManager; + private final DialogService dialogService; + private final TaskExecutor taskExecutor; + private final StateManager stateManager; + private final MainTableTooltip tooltip; - public MainTableColumnFactory(BibDatabaseContext database, - GuiPreferences preferences, - ColumnPreferences abstractColumnPrefs, - UndoManager undoManager, - DialogService dialogService, - StateManager stateManager, - TaskExecutor taskExecutor) { + public MainTableColumnFactory(BibDatabaseContext database, GuiPreferences preferences, + ColumnPreferences abstractColumnPrefs, UndoManager undoManager, DialogService dialogService, + StateManager stateManager, TaskExecutor taskExecutor) { this.database = Objects.requireNonNull(database); this.preferences = Objects.requireNonNull(preferences); this.columnPreferences = abstractColumnPrefs; @@ -114,8 +119,10 @@ public MainTableColumnFactory(BibDatabaseContext database, Field field = FieldFactory.parseField(column.getQualifier()); if (field instanceof SpecialField) { returnColumn = createSpecialFieldColumn(column); - } else { - LOGGER.warn("Special field type '{}' is unknown. Using normal column type.", column.getQualifier()); + } + else { + LOGGER.warn("Special field type '{}' is unknown. Using normal column type.", + column.getQualifier()); returnColumn = createFieldColumn(column, tooltip); } } @@ -146,11 +153,14 @@ public static void setExactWidth(TableColumn column, double width) { /** * Creates a column for the match category. - *

    This column is always hidden but is used for sorting the table - * in the floating mode. The order of the {@link MatchCategory} enum constants - * determines the sorting order.

    + *

    + * This column is always hidden but is used for sorting the table in the floating + * mode. The order of the {@link MatchCategory} enum constants determines the sorting + * order. + *

    */ - private TableColumn createMatchCategoryColumn(MainTableColumnModel columnModel) { + private TableColumn createMatchCategoryColumn( + MainTableColumnModel columnModel) { TableColumn column = new MainTableColumn<>(columnModel); column.setCellValueFactory(cellData -> cellData.getValue().matchCategory()); column.setSortable(true); @@ -171,9 +181,7 @@ private TableColumn createIndexColumn(MainTableC column.setStyle("-fx-alignment: CENTER-RIGHT;"); column.setCellValueFactory(cellData -> new ReadOnlyObjectWrapper<>( String.valueOf(cellData.getTableView().getItems().indexOf(cellData.getValue()) + 1))); - new ValueTableCellFactory() - .withText(text -> text) - .install(column); + new ValueTableCellFactory().withText(text -> text).install(column); column.setSortable(false); return column; } @@ -191,8 +199,8 @@ private TableColumn createIndexColumn(MainTableC column.setResizable(false); column.setCellValueFactory(cellData -> cellData.getValue().getMatchedGroups()); new ValueTableCellFactory>() - .withGraphic(this::createGroupColorRegion) - .install(column); + .withGraphic(this::createGroupColorRegion) + .install(column); column.setStyle("-fx-padding: 0 0 0 0;"); column.setSortable(true); return column; @@ -210,17 +218,15 @@ private TableColumn createIndexColumn(MainTableC column.setResizable(true); column.setCellValueFactory(cellData -> cellData.getValue().getMatchedGroups()); new ValueTableCellFactory>() - .withGraphic(this::createGroupIconRegion) - .install(column); + .withGraphic(this::createGroupIconRegion) + .install(column); column.setStyle("-fx-padding: 0 0 0 0;"); column.setSortable(true); return column; } private Node createGroupColorRegion(BibEntryTableViewModel entry, List matchedGroups) { - List groupColors = matchedGroups.stream() - .flatMap(group -> group.getColor().stream()) - .toList(); + List groupColors = matchedGroups.stream().flatMap(group -> group.getColor().stream()).toList(); if (!groupColors.isEmpty()) { HBox container = new HBox(); @@ -240,10 +246,11 @@ private Node createGroupColorRegion(BibEntryTableViewModel entry, List matchedGroups) { List groupIcons = matchedGroups.stream() - .filter(abstractGroup -> abstractGroup.getIconName().isPresent()) - .flatMap(group -> IconTheme.findIcon(group.getIconName().get(), group.getColor().orElse(IconTheme.getDefaultGroupColor())).stream() - ) - .toList(); + .filter(abstractGroup -> abstractGroup.getIconName().isPresent()) + .flatMap(group -> IconTheme + .findIcon(group.getIconName().get(), group.getColor().orElse(IconTheme.getDefaultGroupColor())) + .stream()) + .toList(); if (!groupIcons.isEmpty()) { HBox container = new HBox(); container.setSpacing(2); @@ -263,13 +271,16 @@ private Node createGroupIconRegion(BibEntryTableViewModel entry, List container.getChildren().add(groupIcon.getGraphicNode())); + groupIcons.stream() + .distinct() + .forEach(groupIcon -> container.getChildren().add(groupIcon.getGraphicNode())); String matchedGroupsString = matchedGroups.stream() - .distinct() - .map(AbstractGroup::getName) - .collect(Collectors.joining(", ")); - Tooltip tooltip = new Tooltip(Localization.lang("Entry is contained in the following groups:") + "\n" + matchedGroupsString); + .distinct() + .map(AbstractGroup::getName) + .collect(Collectors.joining(", ")); + Tooltip tooltip = new Tooltip( + Localization.lang("Entry is contained in the following groups:") + "\n" + matchedGroupsString); Tooltip.install(container, tooltip); return container; } @@ -279,45 +290,43 @@ private Node createGroupIconRegion(BibEntryTableViewModel entry, List createFieldColumn(MainTableColumnModel columnModel, MainTableTooltip tooltip) { + private TableColumn createFieldColumn(MainTableColumnModel columnModel, + MainTableTooltip tooltip) { return new FieldColumn(columnModel, tooltip); } /** * Creates a clickable icons column for DOIs, URLs, URIs and EPrints. */ - private TableColumn> createIdentifierColumn(MainTableColumnModel columnModel) { + private TableColumn> createIdentifierColumn( + MainTableColumnModel columnModel) { return new LinkedIdentifierColumn(columnModel, cellFactory, database, dialogService, preferences, stateManager); } /** * Creates a column that displays a {@link SpecialField} */ - private TableColumn> createSpecialFieldColumn(MainTableColumnModel columnModel) { + private TableColumn> createSpecialFieldColumn( + MainTableColumnModel columnModel) { return new SpecialFieldColumn(columnModel, preferences, undoManager); } /** - * Creates a column for all the linked files. Instead of creating a column for a single file type, like {@link - * #createExtraFileColumn(MainTableColumnModel)} createExtraFileColumn} does, this creates one single column collecting all file links. + * Creates a column for all the linked files. Instead of creating a column for a + * single file type, like {@link #createExtraFileColumn(MainTableColumnModel)} + * createExtraFileColumn} does, this creates one single column collecting all file + * links. */ private TableColumn> createFilesColumn(MainTableColumnModel columnModel) { - return new FileColumn(columnModel, - database, - dialogService, - preferences, - taskExecutor); + return new FileColumn(columnModel, database, dialogService, preferences, taskExecutor); } /** * Creates a column for all the linked files of a single file type. */ - private TableColumn> createExtraFileColumn(MainTableColumnModel columnModel) { - return new FileColumn(columnModel, - database, - dialogService, - preferences, - columnModel.getQualifier(), + private TableColumn> createExtraFileColumn( + MainTableColumnModel columnModel) { + return new FileColumn(columnModel, database, dialogService, preferences, columnModel.getQualifier(), taskExecutor); } @@ -327,4 +336,5 @@ private TableColumn> createExtraFileCol private TableColumn createLibraryColumn(MainTableColumnModel columnModel) { return new LibraryColumn(columnModel); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/MainTableColumnModel.java b/jabgui/src/main/java/org/jabref/gui/maintable/MainTableColumnModel.java index cc14cdb7ac1..76a7656e928 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/MainTableColumnModel.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/MainTableColumnModel.java @@ -26,31 +26,32 @@ import org.slf4j.LoggerFactory; /** - * Represents the full internal name of a column in the main table. Consists of two parts: The type of the column and a qualifier, like the - * field name to be displayed in the column. + * Represents the full internal name of a column in the main table. Consists of two parts: + * The type of the column and a qualifier, like the field name to be displayed in the + * column. */ public class MainTableColumnModel { public static final Character COLUMNS_QUALIFIER_DELIMITER = ':'; private static final Logger LOGGER = LoggerFactory.getLogger(MainTableColumnModel.class); + public enum Type { - MATCH_CATEGORY("match_category"), // Not localized, because this column is always hidden - INDEX("index", Localization.lang("Index")), - EXTRAFILE("extrafile", Localization.lang("File type")), - FILES("files", Localization.lang("Linked files")), - GROUPS("groups", Localization.lang("Groups")), - GROUP_ICONS("group_icons", Localization.lang("Group icons")), - LINKED_IDENTIFIER("linked_id", Localization.lang("Linked identifiers")), - NORMALFIELD("field"), - SPECIALFIELD("special", Localization.lang("Special")), - LIBRARY_NAME("library", Localization.lang("Library")); + MATCH_CATEGORY("match_category"), // Not localized, because this column is always + // hidden + INDEX("index", Localization.lang("Index")), EXTRAFILE("extrafile", Localization.lang("File type")), + FILES("files", Localization.lang("Linked files")), + GROUPS("groups", Localization.lang("Groups")), GROUP_ICONS("group_icons", Localization.lang("Group icons")), + LINKED_IDENTIFIER("linked_id", Localization.lang("Linked identifiers")), NORMALFIELD("field"), + SPECIALFIELD("special", Localization.lang("Special")), LIBRARY_NAME("library", Localization.lang("Library")); - public static final EnumSet ICON_COLUMNS = EnumSet.of(EXTRAFILE, FILES, GROUPS, GROUP_ICONS, LINKED_IDENTIFIER); + public static final EnumSet ICON_COLUMNS = EnumSet.of(EXTRAFILE, FILES, GROUPS, GROUP_ICONS, + LINKED_IDENTIFIER); private final String name; + private final String displayName; Type(String name) { @@ -83,25 +84,28 @@ public static Type fromString(String text) { @Override public String toString() { - return "Type{" + - "name='" + name + '\'' + - ", displayName='" + displayName + '\'' + - '}'; + return "Type{" + "name='" + name + '\'' + ", displayName='" + displayName + '\'' + '}'; } + } private final ObjectProperty typeProperty = new SimpleObjectProperty<>(); + private final StringProperty qualifierProperty = new SimpleStringProperty(); + private final DoubleProperty widthProperty = new SimpleDoubleProperty(); + private final ObjectProperty sortTypeProperty = new SimpleObjectProperty<>(); private final CliPreferences preferences; + private final UndoManager undoManager; /** - * This is used by the preferences dialog, to initialize available columns the user can add to the table. - * - * @param type the {@code MainTableColumnModel.Type} of the column, e.g. "NORMALFIELD" or "EXTRAFILE" + * This is used by the preferences dialog, to initialize available columns the user + * can add to the table. + * @param type the {@code MainTableColumnModel.Type} of the column, e.g. "NORMALFIELD" + * or "EXTRAFILE" * @param qualifier the stored qualifier of the column, e.g. "author/editor" */ public MainTableColumnModel(Type type, String qualifier) { @@ -116,15 +120,17 @@ public MainTableColumnModel(Type type, String qualifier) { if (Type.ICON_COLUMNS.contains(type)) { this.widthProperty.setValue(ColumnPreferences.ICON_COLUMN_WIDTH); - } else { + } + else { this.widthProperty.setValue(ColumnPreferences.DEFAULT_COLUMN_WIDTH); } } /** - * This is used by the preferences dialog, to initialize available basic icon columns, the user can add to the table. - * - * @param type the {@code MainTableColumnModel.Type} of the column, e.g. "GROUPS" or "LINKED_IDENTIFIER" + * This is used by the preferences dialog, to initialize available basic icon columns, + * the user can add to the table. + * @param type the {@code MainTableColumnModel.Type} of the column, e.g. "GROUPS" or + * "LINKED_IDENTIFIER" */ public MainTableColumnModel(Type type) { this(type, ""); @@ -132,10 +138,10 @@ public MainTableColumnModel(Type type) { /** * This is used by the preference migrations. - * - * @param type the {@code MainTableColumnModel.Type} of the column, e.g. "NORMALFIELD" or "GROUPS" + * @param type the {@code MainTableColumnModel.Type} of the column, e.g. "NORMALFIELD" + * or "GROUPS" * @param qualifier the stored qualifier of the column, e.g. "author/editor" - * @param width the stored width of the column + * @param width the stored width of the column */ public MainTableColumnModel(Type type, String qualifier, double width) { this(type, qualifier); @@ -154,7 +160,8 @@ public String getQualifier() { public String getName() { if (qualifierProperty.getValue().isBlank()) { return typeProperty.getValue().getName(); - } else { + } + else { return typeProperty.getValue().getName() + COLUMNS_QUALIFIER_DELIMITER + qualifierProperty.getValue(); } } @@ -163,11 +170,14 @@ public String getDisplayName() { if ((Type.ICON_COLUMNS.contains(typeProperty.getValue()) && qualifierProperty.getValue().isBlank()) || (typeProperty.getValue() == Type.INDEX)) { return typeProperty.getValue().getDisplayName(); - } else { - // In case an OrField is used, `FieldFactory.parseField` returns UnknownField, which leads to + } + else { + // In case an OrField is used, `FieldFactory.parseField` returns UnknownField, + // which leads to // "author/editor(Custom)" instead of "author/editor" in the output - return FieldsUtil.getNameWithType(FieldFactory.parseField(qualifierProperty.getValue()), preferences, undoManager); + return FieldsUtil.getNameWithType(FieldFactory.parseField(qualifierProperty.getValue()), preferences, + undoManager); } } @@ -192,18 +202,20 @@ public ObjectProperty sortTypeProperty() { } /** - * Returns a list of sort cirteria based on the fields the current column displays. - * In case it is single field, a single SortCriterion is returned. - * In case of multiple fields, for each field, there is a SortCriterion contained in the list. + * Returns a list of sort cirteria based on the fields the current column displays. In + * case it is single field, a single SortCriterion is returned. In case of multiple + * fields, for each field, there is a SortCriterion contained in the list. * - * Implementation reason: We want to have SortCriterion handle a single field, because the UI allows for handling - * "plain" fields only. + * Implementation reason: We want to have SortCriterion handle a single field, because + * the UI allows for handling "plain" fields only. */ public List getSortCriteria() { boolean descending = getSortType() == TableColumn.SortType.DESCENDING; - return FieldFactory.parseOrFields(getQualifier()).getFields().stream() - .map(field -> new SaveOrder.SortCriterion(field, descending)) - .toList(); + return FieldFactory.parseOrFields(getQualifier()) + .getFields() + .stream() + .map(field -> new SaveOrder.SortCriterion(field, descending)) + .toList(); } @Override @@ -231,15 +243,12 @@ public int hashCode() { @Override public String toString() { - return "MainTableColumnModel{" + - "qualifierProperty=" + qualifierProperty + - ", typeProperty=" + typeProperty + - '}'; + return "MainTableColumnModel{" + "qualifierProperty=" + qualifierProperty + ", typeProperty=" + typeProperty + + '}'; } /** * This creates a new {@code MainTableColumnModel} out of a given string - * * @param rawColumnName the name of the column, e.g. "field:author", or "author" * @return A new {@code MainTableColumnModel} */ @@ -250,16 +259,17 @@ public static MainTableColumnModel parse(String rawColumnName) { Type type = Type.fromString(splittedName[0]); String qualifier = ""; - if ((type == Type.NORMALFIELD) - || (type == Type.SPECIALFIELD) - || (type == Type.EXTRAFILE)) { + if ((type == Type.NORMALFIELD) || (type == Type.SPECIALFIELD) || (type == Type.EXTRAFILE)) { if (splittedName.length == 1) { - qualifier = splittedName[0]; // By default the rawColumnName is parsed as NORMALFIELD - } else { + qualifier = splittedName[0]; // By default the rawColumnName is parsed as + // NORMALFIELD + } + else { qualifier = splittedName[1]; } } return new MainTableColumnModel(type, qualifier); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java b/jabgui/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java index 643f04c505f..2d2a557dfb7 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java @@ -45,34 +45,46 @@ import static org.jabref.model.search.PostgreConstants.ENTRY_ID; public class MainTableDataModel { + private final Logger LOGGER = LoggerFactory.getLogger(MainTableDataModel.class); private final ObservableList entriesViewModel; + private final FilteredList entriesFiltered; + private final SortedList entriesFilteredAndSorted; + private final ObjectProperty fieldValueFormatter = new SimpleObjectProperty<>(); + private final GroupsPreferences groupsPreferences; + private final SearchPreferences searchPreferences; + private final NameDisplayPreferences nameDisplayPreferences; + private final BibDatabaseContext bibDatabaseContext; + private final TaskExecutor taskExecutor; + private final Subscription searchQuerySubscription; + private final Subscription searchDisplayModeSubscription; + private final Subscription selectedGroupsSubscription; + private final Subscription groupViewModeSubscription; + private final SearchIndexListener indexUpdatedListener; + private final OptionalObjectProperty searchQueryProperty; + @Nullable private final IndexManager indexManager; private Optional groupsMatcher; - public MainTableDataModel(BibDatabaseContext context, - GuiPreferences preferences, - TaskExecutor taskExecutor, - @Nullable IndexManager indexManager, - ListProperty selectedGroupsProperty, - OptionalObjectProperty searchQueryProperty, - IntegerProperty resultSizeProperty) { + public MainTableDataModel(BibDatabaseContext context, GuiPreferences preferences, TaskExecutor taskExecutor, + @Nullable IndexManager indexManager, ListProperty selectedGroupsProperty, + OptionalObjectProperty searchQueryProperty, IntegerProperty resultSizeProperty) { this.groupsPreferences = preferences.getGroupsPreferences(); this.searchPreferences = preferences.getSearchPreferences(); this.nameDisplayPreferences = preferences.getNameDisplayPreferences(); @@ -87,15 +99,21 @@ public MainTableDataModel(BibDatabaseContext context, resetFieldFormatter(); ObservableList allEntries = BindingsHelper.forUI(context.getDatabase().getEntries()); - entriesViewModel = EasyBind.mapBacked(allEntries, entry -> new BibEntryTableViewModel(entry, bibDatabaseContext, fieldValueFormatter), false); + entriesViewModel = EasyBind.mapBacked(allEntries, + entry -> new BibEntryTableViewModel(entry, bibDatabaseContext, fieldValueFormatter), false); entriesFiltered = new FilteredList<>(entriesViewModel, BibEntryTableViewModel::isVisible); - searchQuerySubscription = EasyBind.listen(searchQueryProperty, (observable, oldValue, newValue) -> updateSearchMatches(newValue)); - searchDisplayModeSubscription = EasyBind.listen(searchPreferences.searchDisplayModeProperty(), (observable, oldValue, newValue) -> updateSearchDisplayMode(newValue)); - selectedGroupsSubscription = EasyBind.listen(selectedGroupsProperty, (observable, oldValue, newValue) -> updateGroupMatches(newValue)); - groupViewModeSubscription = EasyBind.listen(preferences.getGroupsPreferences().groupViewModeProperty(), observable -> updateGroupMatches(selectedGroupsProperty.get())); - - resultSizeProperty.bind(Bindings.size(entriesFiltered.filtered(entry -> entry.matchCategory().isEqualTo(MatchCategory.MATCHING_SEARCH_AND_GROUPS).get()))); + searchQuerySubscription = EasyBind.listen(searchQueryProperty, + (observable, oldValue, newValue) -> updateSearchMatches(newValue)); + searchDisplayModeSubscription = EasyBind.listen(searchPreferences.searchDisplayModeProperty(), + (observable, oldValue, newValue) -> updateSearchDisplayMode(newValue)); + selectedGroupsSubscription = EasyBind.listen(selectedGroupsProperty, + (observable, oldValue, newValue) -> updateGroupMatches(newValue)); + groupViewModeSubscription = EasyBind.listen(preferences.getGroupsPreferences().groupViewModeProperty(), + observable -> updateGroupMatches(selectedGroupsProperty.get())); + + resultSizeProperty.bind(Bindings.size(entriesFiltered + .filtered(entry -> entry.matchCategory().isEqualTo(MatchCategory.MATCHING_SEARCH_AND_GROUPS).get()))); // We need to wrap the list since otherwise sorting in the table does not work entriesFilteredAndSorted = new SortedList<>(entriesFiltered); } @@ -104,7 +122,8 @@ private void updateSearchMatches(Optional query) { BackgroundTask.wrap(() -> { if (query.isPresent()) { setSearchMatches(indexManager.search(query.get())); - } else { + } + else { clearSearchMatches(); } }).onSuccess(result -> FilteredListProxy.refilterListReflection(entriesFiltered)).executeWith(taskExecutor); @@ -119,7 +138,8 @@ private void updateSearchMatches(Optional query) { public void refreshSearchMatches() { searchQueryProperty.getValue().ifPresent(searchQuery -> { searchQuery.getSearchFlags().remove(SearchFlags.FULLTEXT); - // There is no need to re-add the flag since the UI is unchanged and the flag will be automatically re-added. + // There is no need to re-add the flag since the UI is unchanged and the flag + // will be automatically re-added. }); } @@ -140,16 +160,19 @@ private void clearSearchMatches() { }); } - private static void updateEntrySearchMatch(BibEntryTableViewModel entry, boolean isMatched, boolean isFloatingMode) { + private static void updateEntrySearchMatch(BibEntryTableViewModel entry, boolean isMatched, + boolean isFloatingMode) { entry.isMatchedBySearch().set(isMatched); entry.updateMatchCategory(); setEntrySearchVisibility(entry, isMatched, isFloatingMode); } - private static void setEntrySearchVisibility(BibEntryTableViewModel entry, boolean isMatched, boolean isFloatingMode) { + private static void setEntrySearchVisibility(BibEntryTableViewModel entry, boolean isMatched, + boolean isFloatingMode) { if (isMatched) { entry.isVisibleBySearch().set(true); - } else { + } + else { entry.isVisibleBySearch().set(isFloatingMode); } } @@ -157,7 +180,8 @@ private static void setEntrySearchVisibility(BibEntryTableViewModel entry, boole private void updateSearchDisplayMode(SearchDisplayMode mode) { BackgroundTask.wrap(() -> { boolean isFloatingMode = mode == SearchDisplayMode.FLOAT; - entriesViewModel.forEach(entry -> setEntrySearchVisibility(entry, entry.isMatchedBySearch().get(), isFloatingMode)); + entriesViewModel + .forEach(entry -> setEntrySearchVisibility(entry, entry.isMatchedBySearch().get(), isFloatingMode)); }).onSuccess(result -> FilteredListProxy.refilterListReflection(entriesFiltered)).executeWith(taskExecutor); } @@ -166,32 +190,34 @@ private void updateGroupMatches(ObservableList groups) { groupsMatcher = createGroupMatcher(groups, groupsPreferences); boolean isInvertMode = groupsPreferences.getGroupViewMode().contains(GroupViewMode.INVERT); boolean isFloatingMode = !groupsPreferences.getGroupViewMode().contains(GroupViewMode.FILTER); - entriesViewModel.forEach(entry -> updateEntryGroupMatch(entry, groupsMatcher, isInvertMode, isFloatingMode)); + entriesViewModel + .forEach(entry -> updateEntryGroupMatch(entry, groupsMatcher, isInvertMode, isFloatingMode)); }).onSuccess(result -> FilteredListProxy.refilterListReflection(entriesFiltered)).executeWith(taskExecutor); } - private void updateEntryGroupMatch(BibEntryTableViewModel entry, Optional groupsMatcher, boolean isInvertMode, boolean isFloatingMode) { - boolean isMatched = groupsMatcher.map(matcher -> matcher.isMatch(entry.getEntry()) ^ isInvertMode) - .orElse(true); + private void updateEntryGroupMatch(BibEntryTableViewModel entry, Optional groupsMatcher, + boolean isInvertMode, boolean isFloatingMode) { + boolean isMatched = groupsMatcher.map(matcher -> matcher.isMatch(entry.getEntry()) ^ isInvertMode).orElse(true); entry.isMatchedByGroup().set(isMatched); entry.updateMatchCategory(); if (isMatched) { entry.isVisibleByGroup().set(true); - } else { + } + else { entry.isVisibleByGroup().set(isFloatingMode); } } - private static Optional createGroupMatcher(List selectedGroups, GroupsPreferences groupsPreferences) { + private static Optional createGroupMatcher(List selectedGroups, + GroupsPreferences groupsPreferences) { if ((selectedGroups == null) || selectedGroups.isEmpty()) { // No selected group, show all entries return Optional.empty(); } - final MatcherSet searchRules = MatcherSets.build( - groupsPreferences.getGroupViewMode().contains(GroupViewMode.INTERSECTION) - ? MatcherSets.MatcherType.AND - : MatcherSets.MatcherType.OR); + final MatcherSet searchRules = MatcherSets + .build(groupsPreferences.getGroupViewMode().contains(GroupViewMode.INTERSECTION) + ? MatcherSets.MatcherType.AND : MatcherSets.MatcherType.OR); for (GroupTreeNode node : selectedGroups) { searchRules.addRule(node.getSearchMatcher()); @@ -222,8 +248,8 @@ public Optional getViewModelByIndex(int index) { public Optional getViewModelByCitationKey(String citationKey) { return entriesViewModel.stream() - .filter(viewModel -> citationKey.equals(viewModel.getEntry().getCitationKey().orElse(""))) - .findFirst(); + .filter(viewModel -> citationKey.equals(viewModel.getEntry().getCitationKey().orElse(""))) + .findFirst(); } public void resetFieldFormatter() { @@ -231,6 +257,7 @@ public void resetFieldFormatter() { } class SearchIndexListener { + @Subscribe public void listen(IndexAddedOrUpdatedEvent indexAddedOrUpdatedEvent) { indexAddedOrUpdatedEvent.entries().forEach(entry -> BackgroundTask.wrap(() -> { @@ -241,19 +268,23 @@ public void listen(IndexAddedOrUpdatedEvent indexAddedOrUpdatedEvent) { boolean isMatched; if (searchQueryProperty.get().isPresent()) { SearchQuery searchQuery = searchQueryProperty.get().get(); - String newSearchExpression = "(" + ENTRY_ID + "= " + entry.getId() + ") AND (" + searchQuery.getSearchExpression() + ")"; + String newSearchExpression = "(" + ENTRY_ID + "= " + entry.getId() + ") AND (" + + searchQuery.getSearchExpression() + ")"; SearchQuery entryQuery = new SearchQuery(newSearchExpression, searchQuery.getSearchFlags()); SearchResults results = indexManager.search(entryQuery); isMatched = results.isMatched(entry); viewModel.hasFullTextResultsProperty().set(results.hasFulltextResults(entry)); - } else { + } + else { isMatched = true; viewModel.hasFullTextResultsProperty().set(false); } updateEntrySearchMatch(viewModel, isMatched, isFloatingMode); - updateEntryGroupMatch(viewModel, groupsMatcher, groupsPreferences.getGroupViewMode().contains(GroupViewMode.INVERT), !groupsPreferences.getGroupViewMode().contains(GroupViewMode.FILTER)); + updateEntryGroupMatch(viewModel, groupsMatcher, + groupsPreferences.getGroupViewMode().contains(GroupViewMode.INVERT), + !groupsPreferences.getGroupViewMode().contains(GroupViewMode.FILTER)); } return index; }).onSuccess(index -> { @@ -267,5 +298,7 @@ public void listen(IndexAddedOrUpdatedEvent indexAddedOrUpdatedEvent) { public void listen(IndexStartedEvent indexStartedEvent) { updateSearchMatches(searchQueryProperty.get()); } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/MainTableFieldValueFormatter.java b/jabgui/src/main/java/org/jabref/gui/maintable/MainTableFieldValueFormatter.java index 33841527a34..b7ed1498794 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/MainTableFieldValueFormatter.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/MainTableFieldValueFormatter.java @@ -14,21 +14,25 @@ import static org.jabref.gui.maintable.NameDisplayPreferences.DisplayStyle; public class MainTableFieldValueFormatter { + private final DisplayStyle displayStyle; + private final AbbreviationStyle abbreviationStyle; + private final BibDatabase bibDatabase; - public MainTableFieldValueFormatter(NameDisplayPreferences nameDisplayPreferences, BibDatabaseContext bibDatabaseContext) { + public MainTableFieldValueFormatter(NameDisplayPreferences nameDisplayPreferences, + BibDatabaseContext bibDatabaseContext) { this.displayStyle = nameDisplayPreferences.getDisplayStyle(); this.abbreviationStyle = nameDisplayPreferences.getAbbreviationStyle(); this.bibDatabase = bibDatabaseContext.getDatabase(); } /** - * Format fields for {@link BibEntryTableViewModel}, according to user preferences and with latex translated to - * unicode if possible. - * - * @param fields the fields argument of {@link BibEntryTableViewModel#getFields(OrFields)}. + * Format fields for {@link BibEntryTableViewModel}, according to user preferences and + * with latex translated to unicode if possible. + * @param fields the fields argument of + * {@link BibEntryTableViewModel#getFields(OrFields)}. * @param entry the BibEntry of {@link BibEntryTableViewModel}. * @return The formatted name field. */ @@ -40,7 +44,8 @@ public String formatFieldsValues(final OrFields fields, final BibEntry entry) { if (name.isPresent()) { return formatFieldWithAuthorValue(name.get()); } - } else { + } + else { Optional content = entry.getResolvedFieldOrAliasLatexFree(field, bibDatabase); if (content.isPresent()) { @@ -52,9 +57,8 @@ public String formatFieldsValues(final OrFields fields, final BibEntry entry) { } /** - * Format a name field for the table, according to user preferences and with latex expressions translated if - * possible. - * + * Format a name field for the table, according to user preferences and with latex + * expressions translated if possible. * @param nameToFormat The contents of the name field. * @return The formatted name field. */ @@ -65,21 +69,19 @@ private String formatFieldWithAuthorValue(final String nameToFormat) { AuthorList authors = AuthorList.parse(nameToFormat); - if (((displayStyle == DisplayStyle.FIRSTNAME_LASTNAME) - || (displayStyle == DisplayStyle.LASTNAME_FIRSTNAME)) + if (((displayStyle == DisplayStyle.FIRSTNAME_LASTNAME) || (displayStyle == DisplayStyle.LASTNAME_FIRSTNAME)) && (abbreviationStyle == AbbreviationStyle.LASTNAME_ONLY)) { return authors.latexFree().getAsLastNames(false); } return switch (displayStyle) { default -> nameToFormat; - case FIRSTNAME_LASTNAME -> authors.latexFree().getAsFirstLastNames( - abbreviationStyle == AbbreviationStyle.FULL, - false); - case LASTNAME_FIRSTNAME -> authors.latexFree().getAsLastFirstNames( - abbreviationStyle == AbbreviationStyle.FULL, - false); + case FIRSTNAME_LASTNAME -> + authors.latexFree().getAsFirstLastNames(abbreviationStyle == AbbreviationStyle.FULL, false); + case LASTNAME_FIRSTNAME -> + authors.latexFree().getAsLastFirstNames(abbreviationStyle == AbbreviationStyle.FULL, false); case NATBIB -> authors.latexFree().getAsNatbib(); }; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/MainTableHeaderContextMenu.java b/jabgui/src/main/java/org/jabref/gui/maintable/MainTableHeaderContextMenu.java index 7cfe366ff12..cb42a23a9a2 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/MainTableHeaderContextMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/MainTableHeaderContextMenu.java @@ -23,15 +23,17 @@ public class MainTableHeaderContextMenu extends ContextMenu { private static final int OUT_OF_BOUNDS = -1; + MainTable mainTable; + MainTableColumnFactory factory; + private final LibraryTabContainer tabContainer; + private final DialogService dialogService; - public MainTableHeaderContextMenu(MainTable mainTable, - MainTableColumnFactory factory, - LibraryTabContainer tabContainer, - DialogService dialogService) { + public MainTableHeaderContextMenu(MainTable mainTable, MainTableColumnFactory factory, + LibraryTabContainer tabContainer, DialogService dialogService) { super(); this.tabContainer = tabContainer; this.mainTable = mainTable; @@ -45,12 +47,14 @@ public MainTableHeaderContextMenu(MainTable mainTable, * Handles showing the menu in the cursors position when right-clicked. */ public void show(boolean show) { - // TODO: 20/10/2022 unknown bug where issue does not show unless parameter is passed through this method. + // TODO: 20/10/2022 unknown bug where issue does not show unless parameter is + // passed through this method. mainTable.setOnContextMenuRequested(event -> { // Display the menu if header is clicked, otherwise, remove from display. if (!(event.getTarget() instanceof StackPane) && show) { this.show(mainTable, event.getScreenX(), event.getScreenY()); - } else if (this.isShowing()) { + } + else if (this.isShowing()) { this.hide(); } event.consume(); @@ -77,7 +81,8 @@ private void constructItems() { // Remove from remaining common columns pool MainTableColumn searchCol = (MainTableColumn) column; if (isACommonColumn(searchCol)) { - commonColumns.removeIf(tableCol -> ((MainTableColumn) tableCol).getModel().equals(searchCol.getModel())); + commonColumns + .removeIf(tableCol -> ((MainTableColumn) tableCol).getModel().equals(searchCol.getModel())); } } @@ -100,7 +105,8 @@ private void constructItems() { } /** - * Creates an item for the menu constructed with the name/visibility of the table column. + * Creates an item for the menu constructed with the name/visibility of the table + * column. */ private RightClickMenuItem createMenuItem(TableColumn column, boolean isDisplaying) { // Gets display name and constructs Radio Menu Item. @@ -130,7 +136,8 @@ private int obtainIndexOfColumn(MainTableColumn searchColumn) { private void addColumn(MainTableColumn tableColumn, int index) { if ((index <= OUT_OF_BOUNDS) || (index >= mainTable.getColumns().size())) { mainTable.getColumns().add(tableColumn); - } else { + } + else { mainTable.getColumns().add(index, tableColumn); } } @@ -139,7 +146,8 @@ private void addColumn(MainTableColumn tableColumn, int index) { * Removes the column from the MainTable to remove visibility. */ private void removeColumn(MainTableColumn tableColumn) { - mainTable.getColumns().removeIf(tableCol -> ((MainTableColumn) tableCol).getModel().equals(tableColumn.getModel())); + mainTable.getColumns() + .removeIf(tableCol -> ((MainTableColumn) tableCol).getModel().equals(tableColumn.getModel())); } /** @@ -152,8 +160,9 @@ private boolean isACommonColumn(MainTableColumn tableColumn) { /** * Determines if a list of TableColumns contains the searched column. */ - private boolean isColumnInList(MainTableColumn searchColumn, List> tableColumns) { - for (TableColumn column: tableColumns) { + private boolean isColumnInList(MainTableColumn searchColumn, + List> tableColumns) { + for (TableColumn column : tableColumns) { MainTableColumnModel model = ((MainTableColumn) column).getModel(); if (model.equals(searchColumn.getModel())) { return true; @@ -163,7 +172,8 @@ private boolean isColumnInList(MainTableColumn searchColumn, List> commonColumns() { // Qualifier strings @@ -193,7 +203,7 @@ private boolean isColumnInList(MainTableColumn searchColumn, List> commonTableColumns = new ArrayList<>(); - for (MainTableColumnModel columnModel: commonColumns) { + for (MainTableColumnModel columnModel : commonColumns) { TableColumn tableColumn = factory.createColumn(columnModel); commonTableColumns.add(tableColumn); } @@ -204,7 +214,9 @@ private boolean isColumnInList(MainTableColumn searchColumn, List column, boolean isVisible) { @@ -220,7 +232,8 @@ private class RightClickMenuItem extends RadioMenuItem { if (isVisibleInTable()) { setIndex(obtainIndexOfColumn(column)); removeColumn(column); - } else { + } + else { addColumn(column, this.index); setIndex(obtainIndexOfColumn(column)); } @@ -239,5 +252,7 @@ public void setVisibleInTable(boolean visibleInTable) { public boolean isVisibleInTable() { return visibleInTable; } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/MainTablePreferences.java b/jabgui/src/main/java/org/jabref/gui/maintable/MainTablePreferences.java index f2f42a62503..8c740a3cd17 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/MainTablePreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/MainTablePreferences.java @@ -4,13 +4,15 @@ import javafx.beans.property.SimpleBooleanProperty; public class MainTablePreferences { + private final ColumnPreferences columnPreferences; + private final BooleanProperty resizeColumnsToFit = new SimpleBooleanProperty(); + private final BooleanProperty extraFileColumnsEnabled = new SimpleBooleanProperty(); - public MainTablePreferences(ColumnPreferences columnPreferences, - boolean resizeColumnsToFit, - boolean extraFileColumnsEnabled) { + public MainTablePreferences(ColumnPreferences columnPreferences, boolean resizeColumnsToFit, + boolean extraFileColumnsEnabled) { this.columnPreferences = columnPreferences; this.resizeColumnsToFit.set(resizeColumnsToFit); this.extraFileColumnsEnabled.set(extraFileColumnsEnabled); @@ -43,4 +45,5 @@ public BooleanProperty extraFileColumnsEnabledProperty() { public void setExtraFileColumnsEnabled(boolean extraFileColumnsEnabled) { this.extraFileColumnsEnabled.set(extraFileColumnsEnabled); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/MainTableTooltip.java b/jabgui/src/main/java/org/jabref/gui/maintable/MainTableTooltip.java index 74df84f2c35..5d22991ca6e 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/MainTableTooltip.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/MainTableTooltip.java @@ -15,11 +15,15 @@ public class MainTableTooltip extends Tooltip { private final PreviewViewer preview; + private final GuiPreferences preferences; + private final VBox tooltipContent = new VBox(); + private final Label fieldValueLabel = new Label(); - public MainTableTooltip(DialogService dialogService, GuiPreferences preferences, ThemeManager themeManager, TaskExecutor taskExecutor) { + public MainTableTooltip(DialogService dialogService, GuiPreferences preferences, ThemeManager themeManager, + TaskExecutor taskExecutor) { this.preferences = preferences; this.preview = new PreviewViewer(dialogService, preferences, themeManager, taskExecutor); this.tooltipContent.getChildren().addAll(fieldValueLabel, preview); @@ -32,9 +36,11 @@ public Tooltip createTooltip(BibDatabaseContext databaseContext, BibEntry entry, preview.setDatabaseContext(databaseContext); preview.setEntry(entry); this.setGraphic(tooltipContent); - } else { + } + else { this.setGraphic(fieldValueLabel); } return this; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/NameDisplayPreferences.java b/jabgui/src/main/java/org/jabref/gui/maintable/NameDisplayPreferences.java index 8ffc1b2032f..8f84c246abb 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/NameDisplayPreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/NameDisplayPreferences.java @@ -6,18 +6,22 @@ public class NameDisplayPreferences { public enum DisplayStyle { + NATBIB, AS_IS, FIRSTNAME_LASTNAME, LASTNAME_FIRSTNAME + } public enum AbbreviationStyle { + NONE, LASTNAME_ONLY, FULL + } private final ObjectProperty displayStyle = new SimpleObjectProperty<>(); + private final ObjectProperty abbreviationStyle = new SimpleObjectProperty<>(); - public NameDisplayPreferences(DisplayStyle displayStyle, - AbbreviationStyle abbreviationStyle) { + public NameDisplayPreferences(DisplayStyle displayStyle, AbbreviationStyle abbreviationStyle) { this.displayStyle.set(displayStyle); this.abbreviationStyle.set(abbreviationStyle); } @@ -45,4 +49,5 @@ public ObjectProperty abbreviationStyleProperty() { public void setAbbreviationStyle(AbbreviationStyle abbreviationStyle) { this.abbreviationStyle.set(abbreviationStyle); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfAction.java b/jabgui/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfAction.java index 96336a57574..2f24af42271 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfAction.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfAction.java @@ -20,29 +20,30 @@ import org.slf4j.LoggerFactory; /** - * Similar to {@link ExtractReferencesAction}. This action creates a new library, the other action "just" appends to the current library + * Similar to {@link ExtractReferencesAction}. This action creates a new library, the + * other action "just" appends to the current library * *
      - *
    • Mode choice A: online or offline
    • - *
    • Mode choice B: complete entry or single file (the latter is not implemented)
    • + *
    • Mode choice A: online or offline
    • + *
    • Mode choice B: complete entry or single file (the latter is not implemented)
    • *
    */ public abstract class NewLibraryFromPdfAction extends SimpleCommand { + private static final Logger LOGGER = LoggerFactory.getLogger(NewLibraryFromPdfAction.class); protected final CliPreferences preferences; private final LibraryTabContainer libraryTabContainer; + private final StateManager stateManager; + private final DialogService dialogService; + private final TaskExecutor taskExecutor; - public NewLibraryFromPdfAction( - LibraryTabContainer libraryTabContainer, - StateManager stateManager, - DialogService dialogService, - CliPreferences preferences, - TaskExecutor taskExecutor) { + public NewLibraryFromPdfAction(LibraryTabContainer libraryTabContainer, StateManager stateManager, + DialogService dialogService, CliPreferences preferences, TaskExecutor taskExecutor) { this.libraryTabContainer = libraryTabContainer; this.stateManager = stateManager; this.dialogService = dialogService; @@ -54,10 +55,13 @@ public NewLibraryFromPdfAction( public void execute() { final FileDialogConfiguration.Builder builder = new FileDialogConfiguration.Builder(); builder.withDefaultExtension(StandardFileType.PDF); - // Sensible default for the directory to start browsing is the directory of the currently opened library. The pdf storage dir seems not to be feasible, because extracting references from a PDF itself can be done by the context menu of the respective entry. + // Sensible default for the directory to start browsing is the directory of the + // currently opened library. The pdf storage dir seems not to be feasible, because + // extracting references from a PDF itself can be done by the context menu of the + // respective entry. stateManager.getActiveDatabase() - .flatMap(BibDatabaseContext::getDatabasePath) - .ifPresent(path -> builder.withInitialDirectory(path.getParent())); + .flatMap(BibDatabaseContext::getDatabasePath) + .ifPresent(path -> builder.withInitialDirectory(path.getParent())); FileDialogConfiguration fileDialogConfiguration = builder.build(); LOGGER.trace("Opening file dialog with configuration: {}", fileDialogConfiguration); @@ -66,15 +70,16 @@ public void execute() { LOGGER.trace("Selected file: {}", path); Callable parserResultCallable = getParserResultCallable(path); BackgroundTask.wrap(parserResultCallable) - .withInitialMessage(Localization.lang("Processing PDF(s)")) - .onFailure(dialogService::showErrorDialogAndWait) - .onSuccess(result -> { - LOGGER.trace("Finished processing PDF(s): {}", result); - libraryTabContainer.addTab(result.getDatabaseContext(), true); - }) - .executeWith(taskExecutor); + .withInitialMessage(Localization.lang("Processing PDF(s)")) + .onFailure(dialogService::showErrorDialogAndWait) + .onSuccess(result -> { + LOGGER.trace("Finished processing PDF(s): {}", result); + libraryTabContainer.addTab(result.getDatabaseContext(), true); + }) + .executeWith(taskExecutor); }); } protected abstract Callable getParserResultCallable(Path path); + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfActionOffline.java b/jabgui/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfActionOffline.java index e6b3180d4e7..be2dca051a8 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfActionOffline.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfActionOffline.java @@ -15,10 +15,12 @@ public class NewLibraryFromPdfActionOffline extends NewLibraryFromPdfAction { private final BibliographyFromPdfImporter bibliographyFromPdfImporter; - public NewLibraryFromPdfActionOffline(LibraryTabContainer libraryTabContainer, StateManager stateManager, DialogService dialogService, CliPreferences preferences, TaskExecutor taskExecutor) { + public NewLibraryFromPdfActionOffline(LibraryTabContainer libraryTabContainer, StateManager stateManager, + DialogService dialogService, CliPreferences preferences, TaskExecutor taskExecutor) { super(libraryTabContainer, stateManager, dialogService, preferences, taskExecutor); - // Use the importer keeping the numbers (instead of generating keys; which is the other constructor) + // Use the importer keeping the numbers (instead of generating keys; which is the + // other constructor) this.bibliographyFromPdfImporter = new BibliographyFromPdfImporter(); } @@ -26,4 +28,5 @@ public NewLibraryFromPdfActionOffline(LibraryTabContainer libraryTabContainer, S protected Callable getParserResultCallable(Path path) { return () -> bibliographyFromPdfImporter.importDatabase(path); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfActionOnline.java b/jabgui/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfActionOnline.java index 3b8c2fc0f5d..02ed2304764 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfActionOnline.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfActionOnline.java @@ -13,13 +13,15 @@ public class NewLibraryFromPdfActionOnline extends NewLibraryFromPdfAction { - public NewLibraryFromPdfActionOnline(LibraryTabContainer libraryTabContainer, StateManager stateManager, DialogService dialogService, CliPreferences preferences, TaskExecutor taskExecutor) { + public NewLibraryFromPdfActionOnline(LibraryTabContainer libraryTabContainer, StateManager stateManager, + DialogService dialogService, CliPreferences preferences, TaskExecutor taskExecutor) { super(libraryTabContainer, stateManager, dialogService, preferences, taskExecutor); } @Override protected Callable getParserResultCallable(Path path) { - return () -> new ParserResult( - new GrobidService(this.preferences.getGrobidPreferences()).processReferences(path, preferences.getImportFormatPreferences())); + return () -> new ParserResult(new GrobidService(this.preferences.getGrobidPreferences()).processReferences(path, + preferences.getImportFormatPreferences())); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/OpenFolderAction.java b/jabgui/src/main/java/org/jabref/gui/maintable/OpenFolderAction.java index 8d3e362405c..e915a386669 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/OpenFolderAction.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/OpenFolderAction.java @@ -13,26 +13,24 @@ public class OpenFolderAction extends SimpleCommand { private final DialogService dialogService; + private final StateManager stateManager; + private final GuiPreferences preferences; private final BibEntry entry; + private final LinkedFile linkedFile; + private final TaskExecutor taskExecutor; - public OpenFolderAction(DialogService dialogService, - StateManager stateManager, - GuiPreferences preferences, - TaskExecutor taskExecutor) { + public OpenFolderAction(DialogService dialogService, StateManager stateManager, GuiPreferences preferences, + TaskExecutor taskExecutor) { this(dialogService, stateManager, preferences, null, null, taskExecutor); } - public OpenFolderAction(DialogService dialogService, - StateManager stateManager, - GuiPreferences preferences, - BibEntry entry, - LinkedFile linkedFile, - TaskExecutor taskExecutor) { + public OpenFolderAction(DialogService dialogService, StateManager stateManager, GuiPreferences preferences, + BibEntry entry, LinkedFile linkedFile, TaskExecutor taskExecutor) { this.dialogService = dialogService; this.stateManager = stateManager; this.preferences = preferences; @@ -42,35 +40,31 @@ public OpenFolderAction(DialogService dialogService, if (this.linkedFile == null) { this.executable.bind(ActionHelper.isFilePresentForSelectedEntry(stateManager, preferences)); - } else { + } + else { this.setExecutable(true); } } @Override public void execute() { - stateManager.getActiveDatabase().ifPresent(databaseContext -> { - if (entry == null) { - stateManager.getSelectedEntries().stream().filter(entry -> !entry.getFiles().isEmpty()).forEach(entry -> { - LinkedFileViewModel linkedFileViewModel = new LinkedFileViewModel( - entry.getFiles().getFirst(), - entry, - databaseContext, - taskExecutor, - dialogService, - preferences); + stateManager.getActiveDatabase().ifPresent(databaseContext -> { + if (entry == null) { + stateManager.getSelectedEntries() + .stream() + .filter(entry -> !entry.getFiles().isEmpty()) + .forEach(entry -> { + LinkedFileViewModel linkedFileViewModel = new LinkedFileViewModel(entry.getFiles().getFirst(), + entry, databaseContext, taskExecutor, dialogService, preferences); linkedFileViewModel.openFolder(); }); - } else { - LinkedFileViewModel linkedFileViewModel = new LinkedFileViewModel( - linkedFile, - entry, - databaseContext, - taskExecutor, - dialogService, - preferences); - linkedFileViewModel.openFolder(); - } - }); + } + else { + LinkedFileViewModel linkedFileViewModel = new LinkedFileViewModel(linkedFile, entry, databaseContext, + taskExecutor, dialogService, preferences); + linkedFileViewModel.openFolder(); + } + }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/OpenSelectedEntriesFilesAction.java b/jabgui/src/main/java/org/jabref/gui/maintable/OpenSelectedEntriesFilesAction.java index 9056f29dc43..b0272457157 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/OpenSelectedEntriesFilesAction.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/OpenSelectedEntriesFilesAction.java @@ -16,44 +16,39 @@ public class OpenSelectedEntriesFilesAction extends SimpleCommand { private static final int FILES_LIMIT = 10; private final DialogService dialogService; + private final StateManager stateManager; + private final GuiPreferences preferences; + private final TaskExecutor taskExecutor; - public OpenSelectedEntriesFilesAction(DialogService dialogService, - StateManager stateManager, - GuiPreferences preferences, - TaskExecutor taskExecutor) { + public OpenSelectedEntriesFilesAction(DialogService dialogService, StateManager stateManager, + GuiPreferences preferences, TaskExecutor taskExecutor) { this.dialogService = dialogService; this.stateManager = stateManager; this.preferences = preferences; this.taskExecutor = taskExecutor; this.executable.bind(ActionHelper.hasLinkedFileForSelectedEntries(stateManager) - .and(ActionHelper.needsEntriesSelected(stateManager))); + .and(ActionHelper.needsEntriesSelected(stateManager))); } @Override public void execute() { stateManager.getActiveDatabase().ifPresent(databaseContext -> { - List linkedFileViewModelList = stateManager - .getSelectedEntries().stream() - .flatMap(entry -> entry.getFiles().stream() - .map(linkedFile -> new LinkedFileViewModel( - linkedFile, - entry, - databaseContext, - taskExecutor, - dialogService, - preferences))) - .toList(); + List linkedFileViewModelList = stateManager.getSelectedEntries() + .stream() + .flatMap(entry -> entry.getFiles() + .stream() + .map(linkedFile -> new LinkedFileViewModel(linkedFile, entry, databaseContext, taskExecutor, + dialogService, preferences))) + .toList(); if (linkedFileViewModelList.size() > FILES_LIMIT) { boolean continueOpening = dialogService.showConfirmationDialogAndWait( Localization.lang("Opening large number of files"), Localization.lang("You are about to open %0 files. Continue?", linkedFileViewModelList.size()), - Localization.lang("Open all linked files"), - Localization.lang("Cancel file opening") - ); + Localization.lang("Open all linked files"), Localization.lang("Cancel file opening")); if (!continueOpening) { return; } @@ -62,4 +57,5 @@ public void execute() { linkedFileViewModelList.forEach(LinkedFileViewModel::open); }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/OpenSingleExternalFileAction.java b/jabgui/src/main/java/org/jabref/gui/maintable/OpenSingleExternalFileAction.java index 641f3c17527..e72cdb3c419 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/OpenSingleExternalFileAction.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/OpenSingleExternalFileAction.java @@ -14,18 +14,20 @@ public class OpenSingleExternalFileAction extends SimpleCommand { private final DialogService dialogService; + private final GuiPreferences preferences; + private final BibEntry entry; + private final LinkedFile linkedFile; + private final TaskExecutor taskExecutor; + private final StateManager stateManager; - public OpenSingleExternalFileAction(@NonNull DialogService dialogService, - @NonNull GuiPreferences preferences, - @NonNull BibEntry entry, - @NonNull LinkedFile linkedFile, - @NonNull TaskExecutor taskExecutor, - @NonNull StateManager stateManager) { + public OpenSingleExternalFileAction(@NonNull DialogService dialogService, @NonNull GuiPreferences preferences, + @NonNull BibEntry entry, @NonNull LinkedFile linkedFile, @NonNull TaskExecutor taskExecutor, + @NonNull StateManager stateManager) { this.dialogService = dialogService; this.preferences = preferences; this.entry = entry; @@ -39,8 +41,9 @@ public OpenSingleExternalFileAction(@NonNull DialogService dialogService, @Override public void execute() { stateManager.getActiveDatabase() - .ifPresent(databaseContext -> new LinkedFileViewModel( - linkedFile, entry, databaseContext, taskExecutor, dialogService, preferences) - .open()); + .ifPresent(databaseContext -> new LinkedFileViewModel(linkedFile, entry, databaseContext, taskExecutor, + dialogService, preferences) + .open()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/OpenUrlAction.java b/jabgui/src/main/java/org/jabref/gui/maintable/OpenUrlAction.java index 48778884953..9c141546e2e 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/OpenUrlAction.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/OpenUrlAction.java @@ -20,7 +20,9 @@ public class OpenUrlAction extends SimpleCommand { private final DialogService dialogService; + private final StateManager stateManager; + private final GuiPreferences preferences; public OpenUrlAction(DialogService dialogService, StateManager stateManager, GuiPreferences preferences) { @@ -29,8 +31,7 @@ public OpenUrlAction(DialogService dialogService, StateManager stateManager, Gui this.preferences = preferences; BooleanExpression fieldIsSet = ActionHelper.isAnyFieldSetForSelectedEntry( - List.of(StandardField.URL, StandardField.DOI, StandardField.URI, StandardField.EPRINT), - stateManager); + List.of(StandardField.URL, StandardField.DOI, StandardField.URI, StandardField.EPRINT), stateManager); this.executable.bind(ActionHelper.needsEntriesSelected(1, stateManager).and(fieldIsSet)); } @@ -71,13 +72,17 @@ public void execute() { try { if (field.equals(StandardField.DOI) && preferences.getDOIPreferences().isUseCustom()) { NativeDesktop.openCustomDoi(link.get(), preferences, dialogService); - } else { - NativeDesktop.openExternalViewer(databaseContext, preferences, link.get(), field, dialogService, entry); } - } catch (IOException e) { + else { + NativeDesktop.openExternalViewer(databaseContext, preferences, link.get(), field, dialogService, + entry); + } + } + catch (IOException e) { dialogService.showErrorDialogAndWait(Localization.lang("Unable to open link."), e); } } }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java b/jabgui/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java index d31f8574212..90fb0d61d69 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java @@ -22,6 +22,7 @@ public class PersistenceVisualStateTable { private static final Logger LOGGER = LoggerFactory.getLogger(PersistenceVisualStateTable.class); protected final TableView table; + protected final ColumnPreferences preferences; public PersistenceVisualStateTable(TableView table, ColumnPreferences preferences) { @@ -31,22 +32,23 @@ public PersistenceVisualStateTable(TableView table, Colu public void addListeners() { table.getColumns().addListener((InvalidationListener) obs -> updateColumns()); - table.getSortOrder().addListener((ListChangeListener>) obs -> updateSortOrder()); + table.getSortOrder() + .addListener((ListChangeListener>) obs -> updateSortOrder()); - // As we store the ColumnModels of the MainTable, we need to add the listener to the ColumnModel properties, - // since the value is bound to the model after the listener to the column itself is called. + // As we store the ColumnModels of the MainTable, we need to add the listener to + // the ColumnModel properties, + // since the value is bound to the model after the listener to the column itself + // is called. - table.getColumns().stream() - .map(col -> ((MainTableColumn) col).getModel()) - .forEach(model -> { - model.widthProperty().addListener(obs -> updateColumns()); - model.sortTypeProperty().addListener(obs -> updateColumns()); - }); + table.getColumns().stream().map(col -> ((MainTableColumn) col).getModel()).forEach(model -> { + model.widthProperty().addListener(obs -> updateColumns()); + model.sortTypeProperty().addListener(obs -> updateColumns()); + }); } /** - * Stores shown columns, their width and their {@link TableColumn.SortType} in preferences. - * The conversion to the "real" string in the preferences is made at + * Stores shown columns, their width and their {@link TableColumn.SortType} in + * preferences. The conversion to the "real" string in the preferences is made at * {@link JabRefCliPreferences#getColumnSortTypesAsStringList(ColumnPreferences)} */ private void updateColumns() { @@ -56,10 +58,10 @@ private void updateColumns() { } /** - * Stores the SortOrder of the Table in the preferences. This includes {@link TableColumn.SortType}. - *
    - * Cannot be combined with updateColumns, because JavaFX would provide just an empty list for the sort order - * on other changes. + * Stores the SortOrder of the Table in the preferences. This includes + * {@link TableColumn.SortType}.
    + * Cannot be combined with updateColumns, because JavaFX would provide just an empty + * list for the sort order on other changes. */ private void updateSortOrder() { LOGGER.debug("Updating sort order"); @@ -68,9 +70,10 @@ private void updateSortOrder() { private List toList(List> columns) { return columns.stream() - .filter(col -> col instanceof MainTableColumn) - .map(column -> ((MainTableColumn) column).getModel()) - .filter(model -> model.getType() != MainTableColumnModel.Type.MATCH_CATEGORY) - .collect(Collectors.toList()); + .filter(col -> col instanceof MainTableColumn) + .map(column -> ((MainTableColumn) column).getModel()) + .filter(model -> model.getType() != MainTableColumnModel.Type.MATCH_CATEGORY) + .collect(Collectors.toList()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index 4de76ac469a..f9f898e8b94 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -51,64 +51,86 @@ public class RightClickMenu { - public static ContextMenu create(BibEntryTableViewModel entry, - KeyBindingRepository keyBindingRepository, - LibraryTab libraryTab, - DialogService dialogService, - StateManager stateManager, - GuiPreferences preferences, - UndoManager undoManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor, - JournalAbbreviationRepository abbreviationRepository, - BibEntryTypesManager entryTypesManager, - ImportHandler importHandler) { + public static ContextMenu create(BibEntryTableViewModel entry, KeyBindingRepository keyBindingRepository, + LibraryTab libraryTab, DialogService dialogService, StateManager stateManager, GuiPreferences preferences, + UndoManager undoManager, ClipBoardManager clipBoardManager, TaskExecutor taskExecutor, + JournalAbbreviationRepository abbreviationRepository, BibEntryTypesManager entryTypesManager, + ImportHandler importHandler) { ActionFactory factory = new ActionFactory(); ContextMenu contextMenu = new ContextMenu(); - ExtractReferencesAction extractReferencesAction = new ExtractReferencesAction(dialogService, stateManager, preferences); - // Two menu items required, because of menu item display. Action checks preference internal what to do - MenuItem extractFileReferencesOnline = factory.createMenuItem(StandardActions.EXTRACT_FILE_REFERENCES_ONLINE, extractReferencesAction); - MenuItem extractFileReferencesOffline = factory.createMenuItem(StandardActions.EXTRACT_FILE_REFERENCES_OFFLINE, extractReferencesAction); - - contextMenu.getItems().addAll( - factory.createMenuItem(StandardActions.COPY, new EditAction(StandardActions.COPY, () -> libraryTab, stateManager, undoManager)), - createCopySubMenu(factory, dialogService, stateManager, preferences, clipBoardManager, abbreviationRepository, taskExecutor), - createCopyToMenu(factory, dialogService, stateManager, preferences, libraryTab, importHandler), - factory.createMenuItem(StandardActions.PASTE, new EditAction(StandardActions.PASTE, () -> libraryTab, stateManager, undoManager)), - factory.createMenuItem(StandardActions.CUT, new EditAction(StandardActions.CUT, () -> libraryTab, stateManager, undoManager)), - factory.createMenuItem(StandardActions.MERGE_ENTRIES, new MergeEntriesAction(dialogService, stateManager, undoManager, preferences)), - factory.createMenuItem(StandardActions.DELETE_ENTRY, new EditAction(StandardActions.DELETE_ENTRY, () -> libraryTab, stateManager, undoManager)), - - new SeparatorMenuItem(), - - createSendSubMenu(factory, dialogService, stateManager, preferences, entryTypesManager, taskExecutor), - - SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.RANKING, factory, () -> libraryTab, dialogService, preferences, undoManager, stateManager), - SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.RELEVANCE, factory, () -> libraryTab, dialogService, preferences, undoManager, stateManager), - SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.QUALITY, factory, () -> libraryTab, dialogService, preferences, undoManager, stateManager), - SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.PRINTED, factory, () -> libraryTab, dialogService, preferences, undoManager, stateManager), - SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.PRIORITY, factory, () -> libraryTab, dialogService, preferences, undoManager, stateManager), - SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.READ_STATUS, factory, () -> libraryTab, dialogService, preferences, undoManager, stateManager), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.ATTACH_FILE, new AttachFileAction(libraryTab, dialogService, stateManager, preferences.getFilePreferences(), preferences.getExternalApplicationsPreferences())), - factory.createMenuItem(StandardActions.ATTACH_FILE_FROM_URL, new AttachFileFromURLAction(dialogService, stateManager, taskExecutor, preferences)), - factory.createMenuItem(StandardActions.OPEN_FOLDER, new OpenFolderAction(dialogService, stateManager, preferences, taskExecutor)), - factory.createMenuItem(StandardActions.OPEN_EXTERNAL_FILE, new OpenSelectedEntriesFilesAction(dialogService, stateManager, preferences, taskExecutor)), - extractFileReferencesOnline, - extractFileReferencesOffline, - - factory.createMenuItem(StandardActions.OPEN_URL, new OpenUrlAction(dialogService, stateManager, preferences)), - factory.createMenuItem(StandardActions.SEARCH_SHORTSCIENCE, new SearchShortScienceAction(dialogService, stateManager, preferences)), - - new SeparatorMenuItem(), - - new ChangeEntryTypeMenu(libraryTab.getSelectedEntries(), libraryTab.getBibDatabaseContext(), undoManager, entryTypesManager).asSubMenu(), - factory.createMenuItem(StandardActions.MERGE_WITH_FETCHED_ENTRY, new MergeWithFetchedEntryAction(dialogService, stateManager, taskExecutor, preferences, undoManager)), - factory.createMenuItem(StandardActions.LOOKUP_DOC_IDENTIFIER, new LookupIdentifierAction<>(WebFetchers.getIdFetcherForIdentifier(DOI.class), stateManager, undoManager, dialogService, taskExecutor)) - ); + ExtractReferencesAction extractReferencesAction = new ExtractReferencesAction(dialogService, stateManager, + preferences); + // Two menu items required, because of menu item display. Action checks preference + // internal what to do + MenuItem extractFileReferencesOnline = factory.createMenuItem(StandardActions.EXTRACT_FILE_REFERENCES_ONLINE, + extractReferencesAction); + MenuItem extractFileReferencesOffline = factory.createMenuItem(StandardActions.EXTRACT_FILE_REFERENCES_OFFLINE, + extractReferencesAction); + + contextMenu.getItems() + .addAll(factory.createMenuItem(StandardActions.COPY, + new EditAction(StandardActions.COPY, () -> libraryTab, stateManager, undoManager)), + createCopySubMenu(factory, dialogService, stateManager, preferences, clipBoardManager, + abbreviationRepository, taskExecutor), + createCopyToMenu(factory, dialogService, stateManager, preferences, libraryTab, importHandler), + factory.createMenuItem(StandardActions.PASTE, + new EditAction(StandardActions.PASTE, () -> libraryTab, stateManager, undoManager)), + factory.createMenuItem(StandardActions.CUT, + new EditAction(StandardActions.CUT, () -> libraryTab, stateManager, undoManager)), + factory.createMenuItem(StandardActions.MERGE_ENTRIES, + new MergeEntriesAction(dialogService, stateManager, undoManager, preferences)), + factory.createMenuItem(StandardActions.DELETE_ENTRY, + new EditAction(StandardActions.DELETE_ENTRY, () -> libraryTab, stateManager, undoManager)), + + new SeparatorMenuItem(), + + createSendSubMenu(factory, dialogService, stateManager, preferences, entryTypesManager, + taskExecutor), + + SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.RANKING, factory, () -> libraryTab, + dialogService, preferences, undoManager, stateManager), + SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.RELEVANCE, factory, + () -> libraryTab, dialogService, preferences, undoManager, stateManager), + SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.QUALITY, factory, + () -> libraryTab, dialogService, preferences, undoManager, stateManager), + SpecialFieldMenuItemFactory.getSpecialFieldSingleItem(SpecialField.PRINTED, factory, + () -> libraryTab, dialogService, preferences, undoManager, stateManager), + SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.PRIORITY, factory, () -> libraryTab, + dialogService, preferences, undoManager, stateManager), + SpecialFieldMenuItemFactory.createSpecialFieldMenu(SpecialField.READ_STATUS, factory, + () -> libraryTab, dialogService, preferences, undoManager, stateManager), + + new SeparatorMenuItem(), + + factory.createMenuItem(StandardActions.ATTACH_FILE, + new AttachFileAction(libraryTab, dialogService, stateManager, + preferences.getFilePreferences(), + preferences.getExternalApplicationsPreferences())), + factory.createMenuItem(StandardActions.ATTACH_FILE_FROM_URL, + new AttachFileFromURLAction(dialogService, stateManager, taskExecutor, preferences)), + factory.createMenuItem(StandardActions.OPEN_FOLDER, + new OpenFolderAction(dialogService, stateManager, preferences, taskExecutor)), + factory.createMenuItem(StandardActions.OPEN_EXTERNAL_FILE, + new OpenSelectedEntriesFilesAction(dialogService, stateManager, preferences, taskExecutor)), + extractFileReferencesOnline, extractFileReferencesOffline, + + factory.createMenuItem(StandardActions.OPEN_URL, + new OpenUrlAction(dialogService, stateManager, preferences)), + factory.createMenuItem(StandardActions.SEARCH_SHORTSCIENCE, + new SearchShortScienceAction(dialogService, stateManager, preferences)), + + new SeparatorMenuItem(), + + new ChangeEntryTypeMenu(libraryTab.getSelectedEntries(), libraryTab.getBibDatabaseContext(), + undoManager, entryTypesManager) + .asSubMenu(), + factory.createMenuItem(StandardActions.MERGE_WITH_FETCHED_ENTRY, + new MergeWithFetchedEntryAction(dialogService, stateManager, taskExecutor, preferences, + undoManager)), + factory.createMenuItem(StandardActions.LOOKUP_DOC_IDENTIFIER, + new LookupIdentifierAction<>(WebFetchers.getIdFetcherForIdentifier(DOI.class), stateManager, + undoManager, dialogService, taskExecutor))); EasyBind.subscribe(preferences.getGrobidPreferences().grobidEnabledProperty(), enabled -> { extractFileReferencesOnline.setVisible(enabled); @@ -118,22 +140,19 @@ public static ContextMenu create(BibEntryTableViewModel entry, return contextMenu; } - private static Menu createCopyToMenu(ActionFactory factory, - DialogService dialogService, - StateManager stateManager, - GuiPreferences preferences, - LibraryTab libraryTab, - ImportHandler importHandler) { + private static Menu createCopyToMenu(ActionFactory factory, DialogService dialogService, StateManager stateManager, + GuiPreferences preferences, LibraryTab libraryTab, ImportHandler importHandler) { Menu copyToMenu = factory.createMenu(StandardActions.COPY_TO); ObservableList openDatabases = stateManager.getOpenDatabases(); BibDatabaseContext sourceDatabaseContext = libraryTab.getBibDatabaseContext(); - Optional sourceDatabaseName = libraryTab - .getBibDatabaseContext().getDatabasePath().stream() - .flatMap(path -> FileUtil.getUniquePathFragment(stateManager.getAllDatabasePaths(), path).stream()) - .findFirst(); + Optional sourceDatabaseName = libraryTab.getBibDatabaseContext() + .getDatabasePath() + .stream() + .flatMap(path -> FileUtil.getUniquePathFragment(stateManager.getAllDatabasePaths(), path).stream()) + .findFirst(); if (!openDatabases.isEmpty()) { openDatabases.forEach(bibDatabaseContext -> { @@ -141,83 +160,107 @@ private static Menu createCopyToMenu(ActionFactory factory, String destinationDatabaseName = ""; if (bibDatabaseContext.getDatabasePath().isPresent()) { - Optional uniqueFilePathFragment = FileUtil.getUniquePathFragment(stateManager.getAllDatabasePaths(), bibDatabaseContext.getDatabasePath().get()); + Optional uniqueFilePathFragment = FileUtil.getUniquePathFragment( + stateManager.getAllDatabasePaths(), bibDatabaseContext.getDatabasePath().get()); if (uniqueFilePathFragment.equals(sourceDatabaseName)) { return; } if (uniqueFilePathFragment.isPresent()) { destinationDatabaseName = uniqueFilePathFragment.get(); } - } else if (bibDatabaseContext.getLocation() == DatabaseLocation.SHARED) { - destinationDatabaseName = bibDatabaseContext.getDBMSSynchronizer().getDBName() + " [" + Localization.lang("shared") + "]"; - } else { + } + else if (bibDatabaseContext.getLocation() == DatabaseLocation.SHARED) { + destinationDatabaseName = bibDatabaseContext.getDBMSSynchronizer().getDBName() + " [" + + Localization.lang("shared") + "]"; + } + else { destinationDatabaseName = destinationPath.orElse(Localization.lang("untitled")); } - copyToMenu.getItems().addAll( - factory.createCustomMenuItem( - StandardActions.COPY_TO, - new CopyTo(dialogService, stateManager, preferences.getCopyToPreferences(), importHandler, sourceDatabaseContext, bibDatabaseContext), - destinationDatabaseName - ) - ); + copyToMenu.getItems() + .addAll(factory + .createCustomMenuItem(StandardActions.COPY_TO, + new CopyTo(dialogService, stateManager, preferences.getCopyToPreferences(), + importHandler, sourceDatabaseContext, bibDatabaseContext), + destinationDatabaseName)); }); } return copyToMenu; } - private static Menu createCopySubMenu(ActionFactory factory, - DialogService dialogService, - StateManager stateManager, - GuiPreferences preferences, - ClipBoardManager clipBoardManager, - JournalAbbreviationRepository abbreviationRepository, - TaskExecutor taskExecutor) { + private static Menu createCopySubMenu(ActionFactory factory, DialogService dialogService, StateManager stateManager, + GuiPreferences preferences, ClipBoardManager clipBoardManager, + JournalAbbreviationRepository abbreviationRepository, TaskExecutor taskExecutor) { Menu copySpecialMenu = factory.createMenu(StandardActions.COPY_MORE); - copySpecialMenu.getItems().addAll( - factory.createMenuItem(StandardActions.COPY_TITLE, new CopyMoreAction(StandardActions.COPY_TITLE, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), - factory.createMenuItem(StandardActions.COPY_KEY, new CopyMoreAction(StandardActions.COPY_KEY, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), - factory.createMenuItem(StandardActions.COPY_CITE_KEY, new CopyMoreAction(StandardActions.COPY_CITE_KEY, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), - factory.createMenuItem(StandardActions.COPY_KEY_AND_TITLE, new CopyMoreAction(StandardActions.COPY_KEY_AND_TITLE, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), - factory.createMenuItem(StandardActions.COPY_KEY_AND_LINK, new CopyMoreAction(StandardActions.COPY_KEY_AND_LINK, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), - factory.createMenuItem(StandardActions.COPY_DOI, new CopyMoreAction(StandardActions.COPY_DOI, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), - factory.createMenuItem(StandardActions.COPY_DOI_URL, new CopyMoreAction(StandardActions.COPY_DOI_URL, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), - new SeparatorMenuItem() - ); - - // the submenu will behave dependent on what style is currently selected (citation/preview) + copySpecialMenu.getItems() + .addAll(factory.createMenuItem(StandardActions.COPY_TITLE, + new CopyMoreAction(StandardActions.COPY_TITLE, dialogService, stateManager, clipBoardManager, + preferences, abbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_KEY, + new CopyMoreAction(StandardActions.COPY_KEY, dialogService, stateManager, clipBoardManager, + preferences, abbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_CITE_KEY, + new CopyMoreAction(StandardActions.COPY_CITE_KEY, dialogService, stateManager, + clipBoardManager, preferences, abbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_KEY_AND_TITLE, + new CopyMoreAction(StandardActions.COPY_KEY_AND_TITLE, dialogService, stateManager, + clipBoardManager, preferences, abbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_KEY_AND_LINK, + new CopyMoreAction(StandardActions.COPY_KEY_AND_LINK, dialogService, stateManager, + clipBoardManager, preferences, abbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_DOI, + new CopyMoreAction(StandardActions.COPY_DOI, dialogService, stateManager, clipBoardManager, + preferences, abbreviationRepository)), + factory + .createMenuItem(StandardActions.COPY_DOI_URL, + new CopyMoreAction(StandardActions.COPY_DOI_URL, dialogService, stateManager, + clipBoardManager, preferences, abbreviationRepository)), + new SeparatorMenuItem()); + + // the submenu will behave dependent on what style is currently selected + // (citation/preview) PreviewPreferences previewPreferences = preferences.getPreviewPreferences(); if (previewPreferences.getSelectedPreviewLayout() instanceof CitationStylePreviewLayout) { - copySpecialMenu.getItems().addAll( - factory.createMenuItem(StandardActions.COPY_CITATION_HTML, new CopyCitationAction(CitationStyleOutputFormat.HTML, dialogService, stateManager, clipBoardManager, taskExecutor, preferences, abbreviationRepository)), - factory.createMenuItem(StandardActions.COPY_CITATION_TEXT, new CopyCitationAction(CitationStyleOutputFormat.TEXT, dialogService, stateManager, clipBoardManager, taskExecutor, preferences, abbreviationRepository)), - factory.createMenuItem(StandardActions.COPY_CITATION_MARKDOWN, new CopyCitationAction(CitationStyleOutputFormat.MARKDOWN, dialogService, stateManager, clipBoardManager, taskExecutor, preferences, abbreviationRepository))); - } else { - copySpecialMenu.getItems().add(factory.createMenuItem(StandardActions.COPY_CITATION_PREVIEW, new CopyCitationAction(CitationStyleOutputFormat.HTML, dialogService, stateManager, clipBoardManager, taskExecutor, preferences, abbreviationRepository))); + copySpecialMenu.getItems() + .addAll(factory.createMenuItem(StandardActions.COPY_CITATION_HTML, + new CopyCitationAction(CitationStyleOutputFormat.HTML, dialogService, stateManager, + clipBoardManager, taskExecutor, preferences, abbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_CITATION_TEXT, + new CopyCitationAction(CitationStyleOutputFormat.TEXT, dialogService, stateManager, + clipBoardManager, taskExecutor, preferences, abbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_CITATION_MARKDOWN, + new CopyCitationAction(CitationStyleOutputFormat.MARKDOWN, dialogService, stateManager, + clipBoardManager, taskExecutor, preferences, abbreviationRepository))); + } + else { + copySpecialMenu.getItems() + .add(factory.createMenuItem(StandardActions.COPY_CITATION_PREVIEW, + new CopyCitationAction(CitationStyleOutputFormat.HTML, dialogService, stateManager, + clipBoardManager, taskExecutor, preferences, abbreviationRepository))); } - copySpecialMenu.getItems().addAll( - new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.EXPORT_TO_CLIPBOARD, new ExportToClipboardAction(dialogService, stateManager, clipBoardManager, taskExecutor, preferences))); + copySpecialMenu.getItems() + .addAll(new SeparatorMenuItem(), + factory.createMenuItem(StandardActions.EXPORT_TO_CLIPBOARD, new ExportToClipboardAction( + dialogService, stateManager, clipBoardManager, taskExecutor, preferences))); return copySpecialMenu; } - private static Menu createSendSubMenu(ActionFactory factory, - DialogService dialogService, - StateManager stateManager, - GuiPreferences preferences, - BibEntryTypesManager entryTypesManager, - TaskExecutor taskExecutor) { + private static Menu createSendSubMenu(ActionFactory factory, DialogService dialogService, StateManager stateManager, + GuiPreferences preferences, BibEntryTypesManager entryTypesManager, TaskExecutor taskExecutor) { Menu sendMenu = factory.createMenu(StandardActions.SEND); - sendMenu.getItems().addAll( - factory.createMenuItem(StandardActions.SEND_AS_EMAIL, new SendAsStandardEmailAction(dialogService, preferences, stateManager, entryTypesManager, taskExecutor)), - factory.createMenuItem(StandardActions.SEND_TO_KINDLE, new SendAsKindleEmailAction(dialogService, preferences, stateManager, taskExecutor)), - new SeparatorMenuItem() - ); + sendMenu.getItems() + .addAll(factory.createMenuItem(StandardActions.SEND_AS_EMAIL, + new SendAsStandardEmailAction(dialogService, preferences, stateManager, entryTypesManager, + taskExecutor)), + factory.createMenuItem(StandardActions.SEND_TO_KINDLE, + new SendAsKindleEmailAction(dialogService, preferences, stateManager, taskExecutor)), + new SeparatorMenuItem()); return sendMenu; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/SearchShortScienceAction.java b/jabgui/src/main/java/org/jabref/gui/maintable/SearchShortScienceAction.java index 4b6a251c4ff..411b7cc0e17 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/SearchShortScienceAction.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/SearchShortScienceAction.java @@ -19,11 +19,15 @@ import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; public class SearchShortScienceAction extends SimpleCommand { + private final DialogService dialogService; + private final StateManager stateManager; + private final GuiPreferences preferences; - public SearchShortScienceAction(DialogService dialogService, StateManager stateManager, GuiPreferences preferences) { + public SearchShortScienceAction(DialogService dialogService, StateManager stateManager, + GuiPreferences preferences) { this.dialogService = dialogService; this.stateManager = stateManager; this.preferences = preferences; @@ -43,11 +47,14 @@ public void execute() { } ExternalLinkCreator.getShortScienceSearchURL(bibEntries.getFirst()).ifPresent(url -> { try { - NativeDesktop.openExternalViewer(databaseContext, preferences, url, StandardField.URL, dialogService, bibEntries.getFirst()); - } catch (IOException ex) { + NativeDesktop.openExternalViewer(databaseContext, preferences, url, StandardField.URL, + dialogService, bibEntries.getFirst()); + } + catch (IOException ex) { dialogService.showErrorDialogAndWait(Localization.lang("Unable to open ShortScience."), ex); } }); }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/jabgui/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index 19b5fd7bc8a..f63bd93d5fc 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -14,10 +14,12 @@ import org.slf4j.LoggerFactory; /** - * This resize policy is almost the same as {@link TableView#CONSTRAINED_RESIZE_POLICY} - * We make sure that the width of all columns sums up to the total width of the table. - * However, in contrast to {@link TableView#CONSTRAINED_RESIZE_POLICY} we size the columns initially by their preferred width. - * Although {@link TableView#CONSTRAINED_RESIZE_POLICY} is deprecated, this policy maintains a similar resizing behavior. + * This resize policy is almost the same as {@link TableView#CONSTRAINED_RESIZE_POLICY} We + * make sure that the width of all columns sums up to the total width of the table. + * However, in contrast to {@link TableView#CONSTRAINED_RESIZE_POLICY} we size the columns + * initially by their preferred width. Although + * {@link TableView#CONSTRAINED_RESIZE_POLICY} is deprecated, this policy maintains a + * similar resizing behavior. */ public class SmartConstrainedResizePolicy implements Callback { @@ -27,7 +29,8 @@ public class SmartConstrainedResizePolicy implements Callback table) { double share = col.getPrefWidth() / totalPrefWidth; double newSize = tableWidth * share; - // Just to make sure that we are staying under the total table width (due to rounding errors) + // Just to make sure that we are staying under the total table width + // (due to rounding errors) currPrefWidth += newSize; if (currPrefWidth > tableWidth) { newSize -= currPrefWidth - tableWidth; @@ -68,7 +72,8 @@ private void resize(TableColumnBase column, double delta) { Method constrainedResize = clazz.getDeclaredMethod("resize", TableColumnBase.class, double.class); constrainedResize.setAccessible(true); constrainedResize.invoke(null, column, delta); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException e) { + } + catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException e) { LOGGER.error("Could not invoke resize in TableUtil", e); } } @@ -76,22 +81,22 @@ private void resize(TableColumnBase column, double delta) { private Boolean constrainedResize(TableView.ResizeFeatures prop) { TableView table = prop.getTable(); List> visibleLeafColumns = table.getVisibleLeafColumns(); - return constrainedResize(prop, - false, - getContentWidth(table) - 2, - visibleLeafColumns); + return constrainedResize(prop, false, getContentWidth(table) - 2, visibleLeafColumns); } - private Boolean constrainedResize(TableView.ResizeFeatures prop, Boolean isFirstRun, Double contentWidth, List> visibleLeafColumns) { + private Boolean constrainedResize(TableView.ResizeFeatures prop, Boolean isFirstRun, Double contentWidth, + List> visibleLeafColumns) { // We have to use reflection since TableUtil is not visible to us try { // TODO: reflective access, should be removed Class clazz = Class.forName("javafx.scene.control.TableUtil"); - Method constrainedResize = clazz.getDeclaredMethod("constrainedResize", ResizeFeaturesBase.class, Boolean.TYPE, Double.TYPE, List.class); + Method constrainedResize = clazz.getDeclaredMethod("constrainedResize", ResizeFeaturesBase.class, + Boolean.TYPE, Double.TYPE, List.class); constrainedResize.setAccessible(true); Object returnValue = constrainedResize.invoke(null, prop, isFirstRun, contentWidth, visibleLeafColumns); return (Boolean) returnValue; - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException e) { + } + catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException e) { LOGGER.error("Could not invoke constrainedResize in TableUtil", e); return false; } @@ -103,8 +108,10 @@ private Double getContentWidth(TableView table) { Field privateStringField = TableView.class.getDeclaredField("contentWidth"); privateStringField.setAccessible(true); return (Double) privateStringField.get(table); - } catch (IllegalAccessException | NoSuchFieldException e) { + } + catch (IllegalAccessException | NoSuchFieldException e) { return 0d; } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/columns/FieldColumn.java b/jabgui/src/main/java/org/jabref/gui/maintable/columns/FieldColumn.java index 512b9906951..86dffcf9528 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/columns/FieldColumn.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/columns/FieldColumn.java @@ -21,6 +21,7 @@ public class FieldColumn extends MainTableColumn { private final OrFields fields; + private final MainTableTooltip tooltip; public FieldColumn(MainTableColumnModel model, MainTableTooltip tooltip) { @@ -31,10 +32,9 @@ public FieldColumn(MainTableColumnModel model, MainTableTooltip tooltip) { setText(getDisplayName()); setCellValueFactory(param -> getFieldValue(param.getValue())); - new ValueTableCellFactory() - .withText(text -> text) - .graphicTooltip(this::createTooltip) - .install(this); + new ValueTableCellFactory().withText(text -> text) + .graphicTooltip(this::createTooltip) + .install(this); if (fields.hasExactlyOne()) { // comparator can't parse more than one value @@ -50,7 +50,6 @@ public FieldColumn(MainTableColumnModel model, MainTableTooltip tooltip) { /** * Get the table column name to be displayed in the UI - * * @return name to be displayed. null if field is empty. */ @Override @@ -61,7 +60,8 @@ public String getDisplayName() { private ObservableValue getFieldValue(BibEntryTableViewModel entry) { if (fields.isEmpty()) { return null; - } else { + } + else { return entry.getFields(fields); } } @@ -69,4 +69,5 @@ private ObservableValue getFieldValue(BibEntryTableViewModel entry) { private Tooltip createTooltip(BibEntryTableViewModel entry, String fieldValue) { return tooltip.createTooltip(entry.getBibDatabaseContext(), entry.getEntry(), fieldValue); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/columns/FileColumn.java b/jabgui/src/main/java/org/jabref/gui/maintable/columns/FileColumn.java index e0e9a931dc4..a91d028c753 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/columns/FileColumn.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/columns/FileColumn.java @@ -33,18 +33,18 @@ public class FileColumn extends MainTableColumn> { private final DialogService dialogService; + private final BibDatabaseContext database; + private final GuiPreferences preferences; + private final TaskExecutor taskExecutor; /** * Creates a joined column for all the linked files. */ - public FileColumn(MainTableColumnModel model, - BibDatabaseContext database, - DialogService dialogService, - GuiPreferences preferences, - TaskExecutor taskExecutor) { + public FileColumn(MainTableColumnModel model, BibDatabaseContext database, DialogService dialogService, + GuiPreferences preferences, TaskExecutor taskExecutor) { super(model); this.database = Objects.requireNonNull(database); this.dialogService = dialogService; @@ -57,34 +57,25 @@ public FileColumn(MainTableColumnModel model, Tooltip.install(headerGraphic, new Tooltip(Localization.lang("Linked files"))); this.setGraphic(headerGraphic); - new ValueTableCellFactory>() - .withGraphic(this::createFileIcon) - .withTooltip(this::createFileTooltip) - .withMenu(this::createFileMenu) - .withOnMouseClickedEvent((entry, linkedFiles) -> event -> { - if ((event.getButton() == MouseButton.PRIMARY) && (linkedFiles.size() == 1)) { - // Only one linked file -> open directly - LinkedFileViewModel linkedFileViewModel = new LinkedFileViewModel(linkedFiles.getFirst(), - entry.getEntry(), - database, - taskExecutor, - dialogService, - preferences); - linkedFileViewModel.open(); - } - }) - .install(this); + new ValueTableCellFactory>().withGraphic(this::createFileIcon) + .withTooltip(this::createFileTooltip) + .withMenu(this::createFileMenu) + .withOnMouseClickedEvent((entry, linkedFiles) -> event -> { + if ((event.getButton() == MouseButton.PRIMARY) && (linkedFiles.size() == 1)) { + // Only one linked file -> open directly + LinkedFileViewModel linkedFileViewModel = new LinkedFileViewModel(linkedFiles.getFirst(), + entry.getEntry(), database, taskExecutor, dialogService, preferences); + linkedFileViewModel.open(); + } + }) + .install(this); } /** * Creates a column for all the linked files of a single file type. */ - public FileColumn(MainTableColumnModel model, - BibDatabaseContext database, - DialogService dialogService, - GuiPreferences preferences, - String fileType, - TaskExecutor taskExecutor) { + public FileColumn(MainTableColumnModel model, BibDatabaseContext database, DialogService dialogService, + GuiPreferences preferences, String fileType, TaskExecutor taskExecutor) { super(model); this.database = Objects.requireNonNull(database); this.dialogService = dialogService; @@ -93,41 +84,45 @@ public FileColumn(MainTableColumnModel model, setCommonSettings(); - this.setGraphic(ExternalFileTypes.getExternalFileTypeByName(fileType, preferences.getExternalApplicationsPreferences()) - .map(ExternalFileType::getIcon).orElse(IconTheme.JabRefIcons.FILE) - .getGraphicNode()); + this.setGraphic( + ExternalFileTypes.getExternalFileTypeByName(fileType, preferences.getExternalApplicationsPreferences()) + .map(ExternalFileType::getIcon) + .orElse(IconTheme.JabRefIcons.FILE) + .getGraphicNode()); new ValueTableCellFactory>() - .withGraphic((entry, linkedFiles) -> createFileIcon(entry, linkedFiles.stream() - .filter(linkedFile -> linkedFile.getFileType().equalsIgnoreCase(fileType)) - .collect(Collectors.toList()))) - .withOnMouseClickedEvent((entry, linkedFiles) -> event -> { - List filteredFiles = linkedFiles.stream() - .filter(linkedFile -> linkedFile.getFileType().equalsIgnoreCase(fileType)) - .toList(); - - if (event.getButton() == MouseButton.PRIMARY) { - if (filteredFiles.size() == 1) { - // Only one file - open directly - LinkedFileViewModel linkedFileViewModel = new LinkedFileViewModel(filteredFiles.getFirst(), + .withGraphic((entry, linkedFiles) -> createFileIcon(entry, + linkedFiles.stream() + .filter(linkedFile -> linkedFile.getFileType().equalsIgnoreCase(fileType)) + .collect(Collectors.toList()))) + .withOnMouseClickedEvent((entry, linkedFiles) -> event -> { + List filteredFiles = linkedFiles.stream() + .filter(linkedFile -> linkedFile.getFileType().equalsIgnoreCase(fileType)) + .toList(); + + if (event.getButton() == MouseButton.PRIMARY) { + if (filteredFiles.size() == 1) { + // Only one file - open directly + LinkedFileViewModel linkedFileViewModel = new LinkedFileViewModel(filteredFiles.getFirst(), + entry.getEntry(), database, taskExecutor, dialogService, preferences); + linkedFileViewModel.open(); + } + else if (filteredFiles.size() > 1) { + // Multiple files - show context menu to choose file + ContextMenu contextMenu = new ContextMenu(); + for (LinkedFile linkedFile : filteredFiles) { + LinkedFileViewModel linkedFileViewModel = new LinkedFileViewModel(linkedFile, entry.getEntry(), database, taskExecutor, dialogService, preferences); - linkedFileViewModel.open(); - } else if (filteredFiles.size() > 1) { - // Multiple files - show context menu to choose file - ContextMenu contextMenu = new ContextMenu(); - for (LinkedFile linkedFile : filteredFiles) { - LinkedFileViewModel linkedFileViewModel = new LinkedFileViewModel(linkedFile, - entry.getEntry(), database, taskExecutor, dialogService, preferences); - MenuItem menuItem = new MenuItem(linkedFileViewModel.getTruncatedDescriptionAndLink(), - linkedFileViewModel.getTypeIcon().getGraphicNode()); - menuItem.setOnAction(_ -> linkedFileViewModel.open()); - contextMenu.getItems().add(menuItem); - } - contextMenu.show((Node) event.getSource(), event.getScreenX(), event.getScreenY()); + MenuItem menuItem = new MenuItem(linkedFileViewModel.getTruncatedDescriptionAndLink(), + linkedFileViewModel.getTypeIcon().getGraphicNode()); + menuItem.setOnAction(_ -> linkedFileViewModel.open()); + contextMenu.getItems().add(menuItem); } + contextMenu.show((Node) event.getSource(), event.getScreenX(), event.getScreenY()); } - }) - .install(this); + } + }) + .install(this); } private void setCommonSettings() { @@ -142,9 +137,7 @@ private String createFileTooltip(List linkedFiles) { return null; } - String filePaths = linkedFiles.stream() - .map(LinkedFile::getLink) - .collect(Collectors.joining("\n")); + String filePaths = linkedFiles.stream().map(LinkedFile::getLink).collect(Collectors.joining("\n")); if (linkedFiles.size() == 1) { return Localization.lang("Open file %0", filePaths); @@ -160,12 +153,8 @@ private ContextMenu createFileMenu(BibEntryTableViewModel entry, List linke } if (linkedFiles.size() > 1) { return IconTheme.JabRefIcons.FILE_MULTIPLE.getGraphicNode(); - } else if (linkedFiles.size() == 1) { - return ExternalFileTypes.getExternalFileTypeByLinkedFile(linkedFiles.getFirst(), true, preferences.getExternalApplicationsPreferences()) - .map(ExternalFileType::getIcon) - .orElse(IconTheme.JabRefIcons.FILE) - .getGraphicNode(); - } else { + } + else if (linkedFiles.size() == 1) { + return ExternalFileTypes + .getExternalFileTypeByLinkedFile(linkedFiles.getFirst(), true, + preferences.getExternalApplicationsPreferences()) + .map(ExternalFileType::getIcon) + .orElse(IconTheme.JabRefIcons.FILE) + .getGraphicNode(); + } + else { return null; } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/columns/LibraryColumn.java b/jabgui/src/main/java/org/jabref/gui/maintable/columns/LibraryColumn.java index a02359c42b9..50d774be414 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/columns/LibraryColumn.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/columns/LibraryColumn.java @@ -13,12 +13,12 @@ public LibraryColumn(MainTableColumnModel model) { super(model); setText(Localization.lang("Library")); - new ValueTableCellFactory().withText(FileUtil::getBaseName) - .install(this); + new ValueTableCellFactory().withText(FileUtil::getBaseName).install(this); setCellValueFactory(param -> param.getValue().bibDatabasePathProperty()); } public LibraryColumn() { this(new MainTableColumnModel(Type.LIBRARY_NAME)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/columns/LinkedIdentifierColumn.java b/jabgui/src/main/java/org/jabref/gui/maintable/columns/LinkedIdentifierColumn.java index 22aa68e43f4..2e260899bbb 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/columns/LinkedIdentifierColumn.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/columns/LinkedIdentifierColumn.java @@ -32,16 +32,15 @@ public class LinkedIdentifierColumn extends MainTableColumn> { private final BibDatabaseContext database; + private final CellFactory cellFactory; + private final DialogService dialogService; + private final GuiPreferences preferences; - public LinkedIdentifierColumn(MainTableColumnModel model, - CellFactory cellFactory, - BibDatabaseContext database, - DialogService dialogService, - GuiPreferences preferences, - StateManager stateManager) { + public LinkedIdentifierColumn(MainTableColumnModel model, CellFactory cellFactory, BibDatabaseContext database, + DialogService dialogService, GuiPreferences preferences, StateManager stateManager) { super(model); this.database = database; this.cellFactory = cellFactory; @@ -56,31 +55,37 @@ public LinkedIdentifierColumn(MainTableColumnModel model, this.setResizable(false); this.setCellValueFactory(cellData -> cellData.getValue().getLinkedIdentifiers()); new ValueTableCellFactory>() - .withGraphic(this::createIdentifierGraphic) - .withTooltip(this::createIdentifierTooltip) - .withMenu(this::createIdentifierMenu) - .withOnMouseClickedEvent((entry, linkedFiles) -> event -> { - // If we only have one identifer, open directly - if ((linkedFiles.size() == 1) && (event.getButton() == MouseButton.PRIMARY)) { - new OpenUrlAction(dialogService, stateManager, preferences).execute(); - } - }) - .install(this); + .withGraphic(this::createIdentifierGraphic) + .withTooltip(this::createIdentifierTooltip) + .withMenu(this::createIdentifierMenu) + .withOnMouseClickedEvent((entry, linkedFiles) -> event -> { + // If we only have one identifer, open directly + if ((linkedFiles.size() == 1) && (event.getButton() == MouseButton.PRIMARY)) { + new OpenUrlAction(dialogService, stateManager, preferences).execute(); + } + }) + .install(this); } private Node createIdentifierGraphic(Map values) { if (values.size() > 1) { return IconTheme.JabRefIcons.LINK_VARIANT.getGraphicNode(); - } else if (values.size() == 1) { + } + else if (values.size() == 1) { return IconTheme.JabRefIcons.LINK.getGraphicNode(); - } else { + } + else { return null; } } private String createIdentifierTooltip(Map values) { StringBuilder identifiers = new StringBuilder(); - values.keySet().forEach(field -> identifiers.append(field.getDisplayName()).append(": ").append(values.get(field)).append("\n")); + values.keySet() + .forEach(field -> identifiers.append(field.getDisplayName()) + .append(": ") + .append(values.get(field)) + .append("\n")); return identifiers.toString(); } @@ -92,13 +97,15 @@ private ContextMenu createIdentifierMenu(BibEntryTableViewModel entry, Map { - MenuItem menuItem = new MenuItem(field.getDisplayName() + ": " + - ControlHelper.truncateString(values.get(field), -1, "...", ControlHelper.EllipsisPosition.CENTER), + MenuItem menuItem = new MenuItem(field.getDisplayName() + ": " + + ControlHelper.truncateString(values.get(field), -1, "...", ControlHelper.EllipsisPosition.CENTER), cellFactory.getTableIcon(field)); menuItem.setOnAction(event -> { try { - NativeDesktop.openExternalViewer(database, preferences, values.get(field), field, dialogService, entry.getEntry()); - } catch (IOException e) { + NativeDesktop.openExternalViewer(database, preferences, values.get(field), field, dialogService, + entry.getEntry()); + } + catch (IOException e) { dialogService.showErrorDialogAndWait(Localization.lang("Unable to open link."), e); } event.consume(); @@ -108,4 +115,5 @@ private ContextMenu createIdentifierMenu(BibEntryTableViewModel entry, Map extends TableColumn { public MainTableColumn(MainTableColumnModel model) { this.model = model; - BindingsHelper.bindBidirectional( - this.widthProperty(), - model.widthProperty(), + BindingsHelper.bindBidirectional(this.widthProperty(), model.widthProperty(), value -> this.setPrefWidth(model.widthProperty().getValue()), value -> model.widthProperty().setValue(this.getWidth())); - BindingsHelper.bindBidirectional( - this.sortTypeProperty(), - (ObservableValue) model.sortTypeProperty(), + BindingsHelper.bindBidirectional(this.sortTypeProperty(), (ObservableValue) model.sortTypeProperty(), value -> this.setSortType(model.sortTypeProperty().getValue()), value -> model.sortTypeProperty().setValue(this.getSortType())); } @@ -34,4 +30,5 @@ public MainTableColumnModel getModel() { public String getDisplayName() { return model.getDisplayName(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/columns/SpecialFieldColumn.java b/jabgui/src/main/java/org/jabref/gui/maintable/columns/SpecialFieldColumn.java index 27266e8f8e5..7ca4e5a628c 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/columns/SpecialFieldColumn.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/columns/SpecialFieldColumn.java @@ -37,6 +37,7 @@ public class SpecialFieldColumn extends MainTableColumn> { private final CliPreferences preferences; + private final UndoManager undoManager; public SpecialFieldColumn(MainTableColumnModel model, CliPreferences preferences, UndoManager undoManager) { @@ -56,26 +57,28 @@ public SpecialFieldColumn(MainTableColumnModel model, CliPreferences preferences MainTableColumnFactory.setExactWidth(this, SpecialFieldsPreferences.COLUMN_RANKING_WIDTH); this.setResizable(false); new OptionalValueTableCellFactory() - .withGraphic(this::createSpecialRating) - .install(this); - } else { + .withGraphic(this::createSpecialRating) + .install(this); + } + else { MainTableColumnFactory.setExactWidth(this, ColumnPreferences.ICON_COLUMN_WIDTH); this.setResizable(false); if (specialField.isSingleValueField()) { new OptionalValueTableCellFactory() - .withGraphic((entry, value) -> createSpecialFieldIcon(value, specialFieldViewModel)) - .withOnMouseClickedEvent((entry, value) -> event -> { - if (event.getButton() == MouseButton.PRIMARY) { - specialFieldViewModel.toggle(entry.getEntry()); - } - }) - .install(this); - } else { + .withGraphic((entry, value) -> createSpecialFieldIcon(value, specialFieldViewModel)) + .withOnMouseClickedEvent((entry, value) -> event -> { + if (event.getButton() == MouseButton.PRIMARY) { + specialFieldViewModel.toggle(entry.getEntry()); + } + }) + .install(this); + } + else { new OptionalValueTableCellFactory() - .withGraphic((entry, value) -> createSpecialFieldIcon(value, specialFieldViewModel)) - .withMenu((entry, value) -> createSpecialFieldMenu(entry.getEntry(), specialFieldViewModel)) - .install(this); + .withGraphic((entry, value) -> createSpecialFieldIcon(value, specialFieldViewModel)) + .withMenu((entry, value) -> createSpecialFieldMenu(entry.getEntry(), specialFieldViewModel)) + .install(this); } } @@ -83,7 +86,8 @@ public SpecialFieldColumn(MainTableColumnModel model, CliPreferences preferences if (specialField == SpecialField.RANKING) { this.setComparator(new RankingFieldComparator()); - } else { + } + else { this.setComparator(new SpecialFieldComparator()); } @@ -95,7 +99,8 @@ private Rating createSpecialRating(BibEntryTableViewModel entry, Optional - new SpecialFieldViewModel(SpecialField.RANKING, preferences, undoManager) - .setSpecialFieldValue(entry.getEntry(), SpecialFieldValue.getRating(rating.intValue()))); + EasyBind.subscribe(ranking.ratingProperty(), + rating -> new SpecialFieldViewModel(SpecialField.RANKING, preferences, undoManager) + .setSpecialFieldValue(entry.getEntry(), SpecialFieldValue.getRating(rating.intValue()))); return ranking; } @@ -119,7 +125,8 @@ private ContextMenu createSpecialFieldMenu(BibEntry entry, SpecialFieldViewModel ContextMenu contextMenu = new ContextMenu(); for (SpecialFieldValueViewModel value : specialField.getValues()) { - MenuItem menuItem = new MenuItem(value.getMenuString(), value.getIcon().map(JabRefIcon::getGraphicNode).orElse(null)); + MenuItem menuItem = new MenuItem(value.getMenuString(), + value.getIcon().map(JabRefIcon::getGraphicNode).orElse(null)); menuItem.setOnAction(event -> specialField.setSpecialFieldValue(entry, value.getValue())); contextMenu.getItems().add(menuItem); } @@ -127,13 +134,13 @@ private ContextMenu createSpecialFieldMenu(BibEntry entry, SpecialFieldViewModel return contextMenu; } - private Node createSpecialFieldIcon(Optional fieldValue, SpecialFieldViewModel specialField) { - return fieldValue.flatMap(SpecialFieldValueViewModel::getIcon) - .map(JabRefIcon::getGraphicNode) - .orElseGet(() -> { - Node node = specialField.getEmptyIcon().getGraphicNode(); - node.getStyleClass().add("empty-special-field"); - return node; - }); + private Node createSpecialFieldIcon(Optional fieldValue, + SpecialFieldViewModel specialField) { + return fieldValue.flatMap(SpecialFieldValueViewModel::getIcon).map(JabRefIcon::getGraphicNode).orElseGet(() -> { + Node node = specialField.getEmptyIcon().getGraphicNode(); + node.getStyleClass().add("empty-special-field"); + return node; + }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/menus/ChangeEntryTypeAction.java b/jabgui/src/main/java/org/jabref/gui/menus/ChangeEntryTypeAction.java index 8d7a70f3e9e..14c612a521f 100644 --- a/jabgui/src/main/java/org/jabref/gui/menus/ChangeEntryTypeAction.java +++ b/jabgui/src/main/java/org/jabref/gui/menus/ChangeEntryTypeAction.java @@ -14,7 +14,9 @@ public class ChangeEntryTypeAction extends SimpleCommand { private final EntryType type; + private final List entries; + private final UndoManager undoManager; public ChangeEntryTypeAction(EntryType type, List entries, UndoManager undoManager) { @@ -26,8 +28,8 @@ public ChangeEntryTypeAction(EntryType type, List entries, UndoManager @Override public void execute() { NamedCompound compound = new NamedCompound(Localization.lang("Change entry type")); - entries.forEach(e -> e.setType(type) - .ifPresent(change -> compound.addEdit(new UndoableChangeType(change)))); + entries.forEach(e -> e.setType(type).ifPresent(change -> compound.addEdit(new UndoableChangeType(change)))); undoManager.addEdit(compound); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/menus/ChangeEntryTypeMenu.java b/jabgui/src/main/java/org/jabref/gui/menus/ChangeEntryTypeMenu.java index 4ad7dbe3cb2..1ad44f739dd 100644 --- a/jabgui/src/main/java/org/jabref/gui/menus/ChangeEntryTypeMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/menus/ChangeEntryTypeMenu.java @@ -26,15 +26,17 @@ public class ChangeEntryTypeMenu { private final List entries; + private final BibDatabaseContext bibDatabaseContext; + private final UndoManager undoManager; + private final ActionFactory factory; + private final BibEntryTypesManager entryTypesManager; - public ChangeEntryTypeMenu(List entries, - BibDatabaseContext bibDatabaseContext, - UndoManager undoManager, - BibEntryTypesManager entryTypesManager) { + public ChangeEntryTypeMenu(List entries, BibDatabaseContext bibDatabaseContext, UndoManager undoManager, + BibEntryTypesManager entryTypesManager) { this.entries = entries; this.bibDatabaseContext = bibDatabaseContext; this.undoManager = undoManager; @@ -54,7 +56,8 @@ public Menu asSubMenu() { return menu; } - private ObservableList getMenuItems(List entries, BibDatabaseContext bibDatabaseContext, UndoManager undoManager) { + private ObservableList getMenuItems(List entries, BibDatabaseContext bibDatabaseContext, + UndoManager undoManager) { ObservableList items = FXCollections.observableArrayList(); if (bibDatabaseContext.isBiblatexMode()) { @@ -62,35 +65,31 @@ private ObservableList getMenuItems(List entries, BibDatabas items.addAll(fromEntryTypes(entryTypesManager.getAllTypes(BibDatabaseMode.BIBLATEX), entries, undoManager)); // Custom types - createSubMenu(Localization.lang("Custom"), entryTypesManager.getAllCustomTypes(BibDatabaseMode.BIBLATEX), entries, undoManager) - .ifPresent(subMenu -> - items.addAll(new SeparatorMenuItem(), - subMenu - )); - } else { + createSubMenu(Localization.lang("Custom"), entryTypesManager.getAllCustomTypes(BibDatabaseMode.BIBLATEX), + entries, undoManager) + .ifPresent(subMenu -> items.addAll(new SeparatorMenuItem(), subMenu)); + } + else { // Default BibTeX - createSubMenu(BibDatabaseMode.BIBTEX.getFormattedName(), BibtexEntryTypeDefinitions.ALL, entries, undoManager) - .ifPresent(items::add); + createSubMenu(BibDatabaseMode.BIBTEX.getFormattedName(), BibtexEntryTypeDefinitions.ALL, entries, + undoManager) + .ifPresent(items::add); // IEEETran createSubMenu("IEEETran", IEEETranEntryTypeDefinitions.ALL, entries, undoManager) - .ifPresent(subMenu -> items.addAll( - new SeparatorMenuItem(), - subMenu - )); + .ifPresent(subMenu -> items.addAll(new SeparatorMenuItem(), subMenu)); // Custom types - createSubMenu(Localization.lang("Custom"), entryTypesManager.getAllCustomTypes(BibDatabaseMode.BIBTEX), entries, undoManager) - .ifPresent(subMenu -> items.addAll( - new SeparatorMenuItem(), - subMenu - )); + createSubMenu(Localization.lang("Custom"), entryTypesManager.getAllCustomTypes(BibDatabaseMode.BIBTEX), + entries, undoManager) + .ifPresent(subMenu -> items.addAll(new SeparatorMenuItem(), subMenu)); } return items; } - private Optional
    createSubMenu(String text, List entryTypes, List entries, UndoManager undoManager) { + private Optional createSubMenu(String text, List entryTypes, List entries, + UndoManager undoManager) { Menu subMenu = null; if (!entryTypes.isEmpty()) { @@ -101,10 +100,13 @@ private Optional createSubMenu(String text, List entryTypes, return Optional.ofNullable(subMenu); } - private List fromEntryTypes(Collection types, List entries, UndoManager undoManager) { + private List fromEntryTypes(Collection types, List entries, + UndoManager undoManager) { return types.stream() - .map(BibEntryType::getType) - .map(type -> factory.createMenuItem(type::getDisplayName, new ChangeEntryTypeAction(type, entries, undoManager))) - .toList(); + .map(BibEntryType::getType) + .map(type -> factory.createMenuItem(type::getDisplayName, + new ChangeEntryTypeAction(type, entries, undoManager))) + .toList(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/BatchEntryMergeTask.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/BatchEntryMergeTask.java index b4a9cb82977..fdd85573521 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/BatchEntryMergeTask.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/BatchEntryMergeTask.java @@ -25,18 +25,21 @@ public class BatchEntryMergeTask extends BackgroundTask { private static final Logger LOGGER = LoggerFactory.getLogger(BatchEntryMergeTask.class); private final NamedCompound compoundEdit; + private final List entries; + private final MergingIdBasedFetcher fetcher; + private final UndoManager undoManager; + private final NotificationService notificationService; private int processedEntries; + private int successfulUpdates; - public BatchEntryMergeTask(List entries, - MergingIdBasedFetcher fetcher, - UndoManager undoManager, - NotificationService notificationService) { + public BatchEntryMergeTask(List entries, MergingIdBasedFetcher fetcher, UndoManager undoManager, + NotificationService notificationService) { this.entries = entries; this.fetcher = fetcher; this.undoManager = undoManager; @@ -53,31 +56,31 @@ public BatchEntryMergeTask(List entries, @Override public Void call() { - if (isCancelled()) { - notifyCancellation(); - return null; - } - - List updatedEntries = processMergeEntries(); + if (isCancelled()) { + notifyCancellation(); + return null; + } - if (isCancelled()) { - notifyCancellation(); - updateUndoManager(updatedEntries); - return null; - } + List updatedEntries = processMergeEntries(); + if (isCancelled()) { + notifyCancellation(); updateUndoManager(updatedEntries); - LOGGER.debug("Merge operation completed. Processed: {}, Successfully updated: {}", - processedEntries, successfulUpdates); - notifySuccess(successfulUpdates); return null; + } + + updateUndoManager(updatedEntries); + LOGGER.debug("Merge operation completed. Processed: {}, Successfully updated: {}", processedEntries, + successfulUpdates); + notifySuccess(successfulUpdates); + return null; } private void notifyCancellation() { - LOGGER.debug("Merge operation was cancelled. Processed: {}, Successfully updated: {}", - processedEntries, successfulUpdates); - notificationService.notify( - Localization.lang("Merge operation cancelled after updating %0 entry(s)", successfulUpdates)); + LOGGER.debug("Merge operation was cancelled. Processed: {}, Successfully updated: {}", processedEntries, + successfulUpdates); + notificationService + .notify(Localization.lang("Merge operation cancelled after updating %0 entry(s)", successfulUpdates)); } private List processMergeEntries() { @@ -96,24 +99,20 @@ private List processMergeEntries() { private Optional processSingleEntryWithProgress(BibEntry entry) { updateProgress(++processedEntries, entries.size()); - updateMessage(Localization.lang("Processing entry %0 of %1", - processedEntries, - entries.size())); + updateMessage(Localization.lang("Processing entry %0 of %1", processedEntries, entries.size())); return processSingleEntry(entry); } private Optional processSingleEntry(BibEntry entry) { LOGGER.debug("Processing entry: {}", entry); - return fetcher.fetchEntry(entry) - .filter(MergingIdBasedFetcher.FetcherResult::hasChanges) - .flatMap(result -> { - boolean changesApplied = MergeEntriesHelper.mergeEntries(result.mergedEntry(), entry, compoundEdit); - if (changesApplied) { - successfulUpdates++; - return entry.getCitationKey(); - } - return Optional.empty(); - }); + return fetcher.fetchEntry(entry).filter(MergingIdBasedFetcher.FetcherResult::hasChanges).flatMap(result -> { + boolean changesApplied = MergeEntriesHelper.mergeEntries(result.mergedEntry(), entry, compoundEdit); + if (changesApplied) { + successfulUpdates++; + return entry.getCitationKey(); + } + return Optional.empty(); + }); } private void updateUndoManager(List updatedEntries) { @@ -124,9 +123,9 @@ private void updateUndoManager(List updatedEntries) { } private void notifySuccess(int updateCount) { - String message = updateCount == 0 - ? Localization.lang("No updates found.") + String message = updateCount == 0 ? Localization.lang("No updates found.") : Localization.lang("Batch update successful. %0 entry(s) updated.", updateCount); notificationService.notify(message); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/BatchEntryMergeWithFetchedDataAction.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/BatchEntryMergeWithFetchedDataAction.java index 5fd2dcbe24f..9c212dc3943 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/BatchEntryMergeWithFetchedDataAction.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/BatchEntryMergeWithFetchedDataAction.java @@ -22,16 +22,17 @@ public class BatchEntryMergeWithFetchedDataAction extends SimpleCommand { private final StateManager stateManager; + private final UndoManager undoManager; + private final GuiPreferences preferences; + private final NotificationService notificationService; + private final TaskExecutor taskExecutor; - public BatchEntryMergeWithFetchedDataAction(StateManager stateManager, - UndoManager undoManager, - GuiPreferences preferences, - NotificationService notificationService, - TaskExecutor taskExecutor) { + public BatchEntryMergeWithFetchedDataAction(StateManager stateManager, UndoManager undoManager, + GuiPreferences preferences, NotificationService notificationService, TaskExecutor taskExecutor) { this.stateManager = stateManager; this.undoManager = undoManager; this.preferences = preferences; @@ -47,9 +48,7 @@ public void execute() { return; } - List entries = stateManager.getActiveDatabase() - .map(BibDatabaseContext::getEntries) - .orElse(List.of()); + List entries = stateManager.getActiveDatabase().map(BibDatabaseContext::getEntries).orElse(List.of()); if (entries.isEmpty()) { notificationService.notify(Localization.lang("No entries available for merging")); @@ -57,12 +56,9 @@ public void execute() { } MergingIdBasedFetcher fetcher = new MergingIdBasedFetcher(preferences.getImportFormatPreferences()); - BatchEntryMergeTask mergeTask = new BatchEntryMergeTask( - entries, - fetcher, - undoManager, - notificationService); + BatchEntryMergeTask mergeTask = new BatchEntryMergeTask(entries, fetcher, undoManager, notificationService); mergeTask.executeWith(taskExecutor); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/DiffMode.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/DiffMode.java index de05daebb10..5c07a3c637b 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/DiffMode.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/DiffMode.java @@ -4,10 +4,8 @@ public enum DiffMode { - PLAIN(Localization.lang("None")), - WORD(Localization.lang("Word by word")), - CHARACTER(Localization.lang("Character by character")), - WORD_SYMMETRIC(Localization.lang("Symmetric word by word")), + PLAIN(Localization.lang("None")), WORD(Localization.lang("Word by word")), + CHARACTER(Localization.lang("Character by character")), WORD_SYMMETRIC(Localization.lang("Symmetric word by word")), CHARACTER_SYMMETRIC(Localization.lang("Symmetric character by character")); private final String text; @@ -19,7 +17,8 @@ public enum DiffMode { public static DiffMode parse(String name) { try { return DiffMode.valueOf(name); - } catch (IllegalArgumentException e) { + } + catch (IllegalArgumentException e) { return WORD; // default } } @@ -27,4 +26,5 @@ public static DiffMode parse(String name) { public String getDisplayText() { return text; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java index 14896fd7dd5..da4ada1f2eb 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java @@ -41,21 +41,25 @@ */ public class FetchAndMergeEntry { - // All identifiers listed here should also appear at {@link org.jabref.logic.importer.CompositeIdFetcher#performSearchById} - public static List SUPPORTED_FIELDS = Arrays.asList(StandardField.DOI, StandardField.EPRINT, StandardField.ISBN); + // All identifiers listed here should also appear at {@link + // org.jabref.logic.importer.CompositeIdFetcher#performSearchById} + public static List SUPPORTED_FIELDS = Arrays.asList(StandardField.DOI, StandardField.EPRINT, + StandardField.ISBN); private static final Logger LOGGER = LoggerFactory.getLogger(FetchAndMergeEntry.class); + private final DialogService dialogService; + private final UndoManager undoManager; + private final BibDatabaseContext bibDatabaseContext; + private final TaskExecutor taskExecutor; + private final GuiPreferences preferences; - public FetchAndMergeEntry(BibDatabaseContext bibDatabaseContext, - TaskExecutor taskExecutor, - GuiPreferences preferences, - DialogService dialogService, - UndoManager undoManager) { + public FetchAndMergeEntry(BibDatabaseContext bibDatabaseContext, TaskExecutor taskExecutor, + GuiPreferences preferences, DialogService dialogService, UndoManager undoManager) { this.bibDatabaseContext = bibDatabaseContext; this.taskExecutor = taskExecutor; this.preferences = preferences; @@ -75,30 +79,44 @@ public void fetchAndMerge(BibEntry entry, List fields) { for (Field field : fields) { Optional fieldContent = entry.getField(field); if (fieldContent.isPresent()) { - Optional fetcher = WebFetchers.getIdBasedFetcherForField(field, preferences.getImportFormatPreferences()); - fetcher.ifPresent(idBasedFetcher -> BackgroundTask.wrap(() -> idBasedFetcher.performSearchById(fieldContent.get())) - .onSuccess(fetchedEntry -> { - ImportCleanup cleanup = ImportCleanup.targeting(bibDatabaseContext.getMode(), preferences.getFieldPreferences()); - String type = field.getDisplayName(); - if (fetchedEntry.isPresent()) { - cleanup.doPostCleanup(fetchedEntry.get()); - showMergeDialog(entry, fetchedEntry.get(), idBasedFetcher); - } else { - dialogService.notify(Localization.lang("Cannot get info based on given %0: %1", type, fieldContent.get())); - } - }) - .onFailure(exception -> { - LOGGER.error("Error while fetching bibliographic information", exception); - if (exception instanceof FetcherClientException) { - dialogService.showInformationDialogAndWait(Localization.lang("Fetching information using %0", idBasedFetcher.getName()), Localization.lang("No data was found for the identifier")); - } else if (exception instanceof FetcherServerException) { - dialogService.showInformationDialogAndWait(Localization.lang("Fetching information using %0", idBasedFetcher.getName()), Localization.lang("Server not available")); - } else { - dialogService.showInformationDialogAndWait(Localization.lang("Fetching information using %0", idBasedFetcher.getName()), Localization.lang("Error occurred %0", exception.getMessage())); - } - }) - .executeWith(taskExecutor)); - } else { + Optional fetcher = WebFetchers.getIdBasedFetcherForField(field, + preferences.getImportFormatPreferences()); + fetcher.ifPresent(idBasedFetcher -> BackgroundTask + .wrap(() -> idBasedFetcher.performSearchById(fieldContent.get())) + .onSuccess(fetchedEntry -> { + ImportCleanup cleanup = ImportCleanup.targeting(bibDatabaseContext.getMode(), + preferences.getFieldPreferences()); + String type = field.getDisplayName(); + if (fetchedEntry.isPresent()) { + cleanup.doPostCleanup(fetchedEntry.get()); + showMergeDialog(entry, fetchedEntry.get(), idBasedFetcher); + } + else { + dialogService.notify(Localization.lang("Cannot get info based on given %0: %1", type, + fieldContent.get())); + } + }) + .onFailure(exception -> { + LOGGER.error("Error while fetching bibliographic information", exception); + if (exception instanceof FetcherClientException) { + dialogService.showInformationDialogAndWait( + Localization.lang("Fetching information using %0", idBasedFetcher.getName()), + Localization.lang("No data was found for the identifier")); + } + else if (exception instanceof FetcherServerException) { + dialogService.showInformationDialogAndWait( + Localization.lang("Fetching information using %0", idBasedFetcher.getName()), + Localization.lang("Server not available")); + } + else { + dialogService.showInformationDialogAndWait( + Localization.lang("Fetching information using %0", idBasedFetcher.getName()), + Localization.lang("Error occurred %0", exception.getMessage())); + } + }) + .executeWith(taskExecutor)); + } + else { dialogService.notify(Localization.lang("No %0 found", field.getDisplayName())); } } @@ -110,10 +128,12 @@ private void showMergeDialog(BibEntry originalEntry, BibEntry fetchedEntry, WebF dialog.setLeftHeaderText(Localization.lang("Original entry")); dialog.setRightHeaderText(Localization.lang("Entry from %0", fetcher.getName())); dialog.autoSelectBetterFields(); - Optional mergedEntry = dialogService.showCustomDialogAndWait(dialog).map(EntriesMergeResult::mergedEntry); + Optional mergedEntry = dialogService.showCustomDialogAndWait(dialog) + .map(EntriesMergeResult::mergedEntry); if (mergedEntry.isPresent()) { - NamedCompound ce = new NamedCompound(Localization.lang("Merge entry with %0 information", fetcher.getName())); + NamedCompound ce = new NamedCompound( + Localization.lang("Merge entry with %0 information", fetcher.getName())); // Updated the original entry with the new fields Set jointFields = new TreeSet<>(Comparator.comparing(Field::getName)); @@ -137,19 +157,23 @@ private void showMergeDialog(BibEntry originalEntry, BibEntry fetchedEntry, WebF Optional originalString = originalEntry.getField(field); Optional mergedString = mergedEntry.get().getField(field); if (originalString.isEmpty() || !originalString.equals(mergedString)) { - originalEntry.setField(field, mergedString.get()); // mergedString always present + originalEntry.setField(field, mergedString.get()); // mergedString + // always present ce.addEdit(new UndoableFieldChange(originalEntry, field, originalString.orElse(null), mergedString.get())); edited = true; } } - // Remove fields which are not in the merged entry, unless they are internal fields + // Remove fields which are not in the merged entry, unless they are internal + // fields for (Field field : originalFields) { if (!jointFields.contains(field) && !FieldFactory.isInternalField(field)) { Optional originalString = originalEntry.getField(field); originalEntry.clearField(field); - ce.addEdit(new UndoableFieldChange(originalEntry, field, originalString.get(), null)); // originalString always present + ce.addEdit(new UndoableFieldChange(originalEntry, field, originalString.get(), null)); // originalString + // always + // present edited = true; } } @@ -158,29 +182,32 @@ private void showMergeDialog(BibEntry originalEntry, BibEntry fetchedEntry, WebF ce.end(); undoManager.addEdit(ce); dialogService.notify(Localization.lang("Updated entry with info from %0", fetcher.getName())); - } else { + } + else { dialogService.notify(Localization.lang("No information added")); } - } else { + } + else { dialogService.notify(Localization.lang("Canceled merging entries")); } } public void fetchAndMerge(BibEntry entry, EntryBasedFetcher fetcher) { - BackgroundTask.wrap(() -> fetcher.performSearch(entry).stream().findFirst()) - .onSuccess(fetchedEntry -> { - if (fetchedEntry.isPresent()) { - ImportCleanup cleanup = ImportCleanup.targeting(bibDatabaseContext.getMode(), preferences.getFieldPreferences()); - cleanup.doPostCleanup(fetchedEntry.get()); - showMergeDialog(entry, fetchedEntry.get(), fetcher); - } else { - dialogService.notify(Localization.lang("Could not find any bibliographic information.")); - } - }) - .onFailure(exception -> { - LOGGER.error("Error while fetching entry with {} ", fetcher.getName(), exception); - dialogService.showErrorDialogAndWait(Localization.lang("Error while fetching from %0", fetcher.getName()), exception); - }) - .executeWith(taskExecutor); + BackgroundTask.wrap(() -> fetcher.performSearch(entry).stream().findFirst()).onSuccess(fetchedEntry -> { + if (fetchedEntry.isPresent()) { + ImportCleanup cleanup = ImportCleanup.targeting(bibDatabaseContext.getMode(), + preferences.getFieldPreferences()); + cleanup.doPostCleanup(fetchedEntry.get()); + showMergeDialog(entry, fetchedEntry.get(), fetcher); + } + else { + dialogService.notify(Localization.lang("Could not find any bibliographic information.")); + } + }).onFailure(exception -> { + LOGGER.error("Error while fetching entry with {} ", fetcher.getName(), exception); + dialogService.showErrorDialogAndWait(Localization.lang("Error while fetching from %0", fetcher.getName()), + exception); + }).executeWith(taskExecutor); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/MergeDialogPreferences.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/MergeDialogPreferences.java index 15709455d69..359c870b29a 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/MergeDialogPreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/MergeDialogPreferences.java @@ -8,21 +8,25 @@ import org.jabref.gui.duplicationFinder.DuplicateResolverDialog; public class MergeDialogPreferences { + private final ObjectProperty mergeDiffMode; + private final BooleanProperty mergeShouldShowDiff; + private final BooleanProperty mergeShouldShowUnifiedDiff; + private final BooleanProperty mergeHighlightWords; + private final BooleanProperty mergeShowChangedFieldsOnly; + private final BooleanProperty mergeApplyToAllEntries; + private final ObjectProperty allEntriesDuplicateResolverDecision; - public MergeDialogPreferences(DiffMode mergeDiffMode, - boolean mergeShouldShowDiff, - boolean mergeShouldShowUnifiedDiff, - boolean mergeHighlightWords, - boolean mergeShowChangedFieldsOnly, - boolean mergeApplyToAllEntries, - DuplicateResolverDialog.DuplicateResolverResult allEntriesDuplicateResolverDecision) { + public MergeDialogPreferences(DiffMode mergeDiffMode, boolean mergeShouldShowDiff, + boolean mergeShouldShowUnifiedDiff, boolean mergeHighlightWords, boolean mergeShowChangedFieldsOnly, + boolean mergeApplyToAllEntries, + DuplicateResolverDialog.DuplicateResolverResult allEntriesDuplicateResolverDecision) { this.mergeDiffMode = new SimpleObjectProperty<>(mergeDiffMode); this.mergeShouldShowDiff = new SimpleBooleanProperty(mergeShouldShowDiff); this.mergeShouldShowUnifiedDiff = new SimpleBooleanProperty(mergeShouldShowUnifiedDiff); @@ -105,7 +109,8 @@ public void setIsMergeApplyToAllEntries(boolean applyToAllEntries) { this.mergeApplyToAllEntries.setValue(applyToAllEntries); } - public void setAllEntriesDuplicateResolverDecision(DuplicateResolverDialog.DuplicateResolverResult allEntriesDuplicateResolverDecision) { + public void setAllEntriesDuplicateResolverDecision( + DuplicateResolverDialog.DuplicateResolverResult allEntriesDuplicateResolverDecision) { this.allEntriesDuplicateResolverDecision.setValue(allEntriesDuplicateResolverDecision); } @@ -116,4 +121,5 @@ public DuplicateResolverDialog.DuplicateResolverResult getAllEntriesDuplicateRes public ObjectProperty allEntriesDuplicateResolverDecisionProperty() { return allEntriesDuplicateResolverDecision; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/MergeEntriesHelper.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/MergeEntriesHelper.java index 717921f0b6d..bc96810804b 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/MergeEntriesHelper.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/MergeEntriesHelper.java @@ -16,9 +16,9 @@ import org.slf4j.LoggerFactory; /** - * Helper class for merging bibliography entries with undo support. - * Source entry data is merged into the library entry, with longer field values preferred - * and obsolete fields removed. + * Helper class for merging bibliography entries with undo support. Source entry data is + * merged into the library entry, with longer field values preferred and obsolete fields + * removed. */ public final class MergeEntriesHelper { @@ -29,10 +29,12 @@ private MergeEntriesHelper() { /// Merges two BibEntry objects with undo support. /// - /// @param entryFromFetcher The entry containing new information (source, from the fetcher) + /// @param entryFromFetcher The entry containing new information (source, from the + /// fetcher) /// @param entryFromLibrary The entry to be updated (target, from the library) /// @param namedCompound Compound edit to collect undo information - public static boolean mergeEntries(BibEntry entryFromFetcher, BibEntry entryFromLibrary, NamedCompound namedCompound) { + public static boolean mergeEntries(BibEntry entryFromFetcher, BibEntry entryFromLibrary, + NamedCompound namedCompound) { LOGGER.debug("Entry from fetcher: {}", entryFromFetcher); LOGGER.debug("Entry from library: {}", entryFromLibrary); @@ -43,7 +45,8 @@ public static boolean mergeEntries(BibEntry entryFromFetcher, BibEntry entryFrom return typeChanged || fieldsChanged || fieldsRemoved; } - private static boolean mergeEntryType(BibEntry entryFromFetcher, BibEntry entryFromLibrary, NamedCompound namedCompound) { + private static boolean mergeEntryType(BibEntry entryFromFetcher, BibEntry entryFromLibrary, + NamedCompound namedCompound) { EntryType fetcherType = entryFromFetcher.getType(); EntryType libraryType = entryFromLibrary.getType(); @@ -56,7 +59,8 @@ private static boolean mergeEntryType(BibEntry entryFromFetcher, BibEntry entryF return false; } - private static boolean mergeFields(BibEntry entryFromFetcher, BibEntry entryFromLibrary, NamedCompound namedCompound) { + private static boolean mergeFields(BibEntry entryFromFetcher, BibEntry entryFromLibrary, + NamedCompound namedCompound) { Set allFields = new LinkedHashSet<>(); allFields.addAll(entryFromFetcher.getFields()); allFields.addAll(entryFromLibrary.getFields()); @@ -70,14 +74,16 @@ private static boolean mergeFields(BibEntry entryFromFetcher, BibEntry entryFrom if (fetcherValue.isPresent() && shouldUpdateField(fetcherValue.get(), libraryValue)) { LOGGER.debug("Updating field {}: {} -> {}", field, libraryValue.orElse(null), fetcherValue.get()); entryFromLibrary.setField(field, fetcherValue.get()); - namedCompound.addEdit(new UndoableFieldChange(entryFromLibrary, field, libraryValue.orElse(null), fetcherValue.get())); + namedCompound.addEdit(new UndoableFieldChange(entryFromLibrary, field, libraryValue.orElse(null), + fetcherValue.get())); anyFieldsChanged = true; } } return anyFieldsChanged; } - private static boolean removeFieldsNotPresentInFetcher(BibEntry entryFromFetcher, BibEntry entryFromLibrary, NamedCompound namedCompound) { + private static boolean removeFieldsNotPresentInFetcher(BibEntry entryFromFetcher, BibEntry entryFromLibrary, + NamedCompound namedCompound) { Set obsoleteFields = new LinkedHashSet<>(entryFromLibrary.getFields()); obsoleteFields.removeAll(entryFromFetcher.getFields()); @@ -100,10 +106,12 @@ private static boolean removeFieldsNotPresentInFetcher(BibEntry entryFromFetcher } private static boolean shouldUpdateField(String fetcherValue, Optional libraryValue) { - // TODO: Think of a better heuristics - better "quality" is the ultimate goal (e.g., more sensible year, better page ranges, longer abstract ...) - // This is difficult to get 100% right - // Read more at https://github.com/JabRef/jabref/issues/12549 + // TODO: Think of a better heuristics - better "quality" is the ultimate goal + // (e.g., more sensible year, better page ranges, longer abstract ...) + // This is difficult to get 100% right + // Read more at https://github.com/JabRef/jabref/issues/12549 // Currently: Only overwrite if there is nothing in the library return libraryValue.isEmpty(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/MergeWithFetchedEntryAction.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/MergeWithFetchedEntryAction.java index 743b0aac900..84810dc0a75 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/MergeWithFetchedEntryAction.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/MergeWithFetchedEntryAction.java @@ -16,16 +16,17 @@ public class MergeWithFetchedEntryAction extends SimpleCommand { private final DialogService dialogService; + private final StateManager stateManager; + private final GuiPreferences preferences; + private final UndoManager undoManager; + private final TaskExecutor taskExecutor; - public MergeWithFetchedEntryAction(DialogService dialogService, - StateManager stateManager, - TaskExecutor taskExecutor, - GuiPreferences preferences, - UndoManager undoManager) { + public MergeWithFetchedEntryAction(DialogService dialogService, StateManager stateManager, + TaskExecutor taskExecutor, GuiPreferences preferences, UndoManager undoManager) { this.dialogService = dialogService; this.stateManager = stateManager; this.taskExecutor = taskExecutor; @@ -33,7 +34,7 @@ public MergeWithFetchedEntryAction(DialogService dialogService, this.undoManager = undoManager; this.executable.bind(ActionHelper.needsEntriesSelected(1, stateManager) - .and(ActionHelper.isAnyFieldSetForSelectedEntry(FetchAndMergeEntry.SUPPORTED_FIELDS, stateManager))); + .and(ActionHelper.isAnyFieldSetForSelectedEntry(FetchAndMergeEntry.SUPPORTED_FIELDS, stateManager))); } @Override @@ -43,12 +44,18 @@ public void execute() { } if (stateManager.getSelectedEntries().size() != 1) { - dialogService.showInformationDialogAndWait( - Localization.lang("Merge entry with %0 information", new OrFields(StandardField.DOI, StandardField.ISBN, StandardField.EPRINT).getDisplayName()), - Localization.lang("This operation requires exactly one item to be selected.")); + dialogService + .showInformationDialogAndWait( + Localization.lang("Merge entry with %0 information", + new OrFields(StandardField.DOI, StandardField.ISBN, StandardField.EPRINT) + .getDisplayName()), + Localization.lang("This operation requires exactly one item to be selected.")); } BibEntry originalEntry = stateManager.getSelectedEntries().getFirst(); - new FetchAndMergeEntry(stateManager.getActiveDatabase().get(), taskExecutor, preferences, dialogService, undoManager).fetchAndMerge(originalEntry); + new FetchAndMergeEntry(stateManager.getActiveDatabase().get(), taskExecutor, preferences, dialogService, + undoManager) + .fetchAndMerge(originalEntry); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/multiwaymerge/DiffHighlighting.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/multiwaymerge/DiffHighlighting.java index f09ef59e787..48f1a29f5e5 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/multiwaymerge/DiffHighlighting.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/multiwaymerge/DiffHighlighting.java @@ -18,7 +18,9 @@ public static List generateDiffHighlighting(String baseString, String modi List baseStringSplit = Arrays.asList(baseString.split(separator)); List modifiedStringSplit = Arrays.asList(modifiedString.split(separator)); List> deltaList = DiffUtils.diff(baseStringSplit, modifiedStringSplit).getDeltas(); - List result = baseStringSplit.stream().map(text -> forUnchanged(text + separator)).collect(Collectors.toList()); + List result = baseStringSplit.stream() + .map(text -> forUnchanged(text + separator)) + .collect(Collectors.toList()); for (AbstractDelta delta : deltaList.reversed()) { int startPos = delta.getSource().getPosition(); List lines = delta.getSource().getLines(); @@ -29,7 +31,8 @@ public static List generateDiffHighlighting(String baseString, String modi result.set(startPos + offset, forRemoved(line + separator)); offset++; } - result.set(startPos + offset - 1, forRemoved(baseStringSplit.get((startPos + offset) - 1) + separator)); + result.set(startPos + offset - 1, + forRemoved(baseStringSplit.get((startPos + offset) - 1) + separator)); result.add(startPos + offset, forAdded(String.join(separator, delta.getTarget().getLines()))); break; case DELETE: @@ -39,7 +42,8 @@ public static List generateDiffHighlighting(String baseString, String modi } break; case INSERT: - result.add(delta.getSource().getPosition(), forAdded(String.join(separator, delta.getTarget().getLines()))); + result.add(delta.getSource().getPosition(), + forAdded(String.join(separator, delta.getTarget().getLines()))); break; default: break; @@ -71,4 +75,5 @@ public static Text forRemoved(String text) { node.getStyleClass().add("text-removed"); return node; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/multiwaymerge/DiffHighlightingEllipsingTextFlow.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/multiwaymerge/DiffHighlightingEllipsingTextFlow.java index f01bec88e49..20df9c22c53 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/multiwaymerge/DiffHighlightingEllipsingTextFlow.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/multiwaymerge/DiffHighlightingEllipsingTextFlow.java @@ -21,17 +21,23 @@ public class DiffHighlightingEllipsingTextFlow extends TextFlow { private final static String DEFAULT_ELLIPSIS_STRING = "..."; + private StringProperty ellipsisString; private final ObservableList allChildren = FXCollections.observableArrayList(); + private final ChangeListener sizeChangeListener = (observableValue, number, t1) -> adjustText(); + private final ListChangeListener listChangeListener = this::adjustChildren; private final String fullText; + private final EasyObservableValue comparisonString; + private final ObjectProperty diffMode; - public DiffHighlightingEllipsingTextFlow(String fullText, EasyObservableValue comparisonString, ObjectProperty diffMode) { + public DiffHighlightingEllipsingTextFlow(String fullText, EasyObservableValue comparisonString, + ObjectProperty diffMode) { this.fullText = fullText; allChildren.addListener(listChangeListener); widthProperty().addListener(sizeChangeListener); @@ -91,14 +97,17 @@ private boolean fillUntilOverflowing() { // all Texts are displayed, let's make sure all chars are as well Node lastChildAsShown = super.getChildren().getLast(); Node lastChild = allChildren.getLast(); - if (lastChildAsShown instanceof Text text && text.getText().length() < ((Text) lastChild).getText().length()) { + if (lastChildAsShown instanceof Text text + && text.getText().length() < ((Text) lastChild).getText().length()) { text.setText(((Text) lastChild).getText()); - } else { + } + else { // nothing to fill the space with return false; } } - } else { + } + else { super.getChildren().add(allChildren.get(super.getChildren().size())); } super.autosize(); @@ -137,7 +146,8 @@ public void highlightDiff() { default -> throw new UnsupportedOperationException("Not implemented " + diffMode.getValue()); }; allChildren.addAll(highlightedText); - } else { + } + else { Text text = new Text(fullText); text.getStyleClass().add("text-unchanged"); allChildren.add(text); @@ -169,4 +179,5 @@ public final StringProperty ellipsisStringProperty() { public String getFullText() { return fullText; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/multiwaymerge/MultiMergeEntriesView.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/multiwaymerge/MultiMergeEntriesView.java index 7fd0f4935f2..3ac713c94e8 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/multiwaymerge/MultiMergeEntriesView.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/multiwaymerge/MultiMergeEntriesView.java @@ -58,45 +58,61 @@ import org.slf4j.LoggerFactory; public class MultiMergeEntriesView extends BaseDialog { + public static final int ACTIVE_COLUMNS_MINIMUM = 2; private static final Logger LOGGER = LoggerFactory.getLogger(MultiMergeEntriesView.class); // LEFT - @FXML private ScrollPane leftScrollPane; - @FXML private VBox fieldHeader; + @FXML + private ScrollPane leftScrollPane; + + @FXML + private VBox fieldHeader; // CENTER - @FXML private ScrollPane topScrollPane; - @FXML private HBox supplierHeader; - @FXML private ScrollPane centerScrollPane; - @FXML private GridPane optionsGrid; + @FXML + private ScrollPane topScrollPane; + + @FXML + private HBox supplierHeader; + + @FXML + private ScrollPane centerScrollPane; + + @FXML + private GridPane optionsGrid; // RIGHT - @FXML private ScrollPane rightScrollPane; - @FXML private VBox fieldEditor; + @FXML + private ScrollPane rightScrollPane; - @FXML private Label failedSuppliers; - @FXML private ComboBox diffMode; + @FXML + private VBox fieldEditor; + + @FXML + private Label failedSuppliers; + + @FXML + private ComboBox diffMode; private final ToggleGroup headerToggleGroup = new ToggleGroup(); + private final HashMap fieldRows = new HashMap<>(); private final MultiMergeEntriesViewModel viewModel; + private final TaskExecutor taskExecutor; private final GuiPreferences preferences; - public MultiMergeEntriesView(GuiPreferences preferences, - TaskExecutor taskExecutor) { + public MultiMergeEntriesView(GuiPreferences preferences, TaskExecutor taskExecutor) { this.preferences = preferences; this.taskExecutor = taskExecutor; viewModel = new MultiMergeEntriesViewModel(); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); ButtonType mergeEntries = new ButtonType(Localization.lang("Merge entries"), ButtonBar.ButtonData.OK_DONE); this.getDialogPane().getButtonTypes().setAll(ButtonType.CANCEL, mergeEntries); @@ -112,14 +128,16 @@ public MultiMergeEntriesView(GuiPreferences preferences, } }); - viewModel.mergedEntryProperty().get().getFieldsObservable().addListener((MapChangeListener) change -> { - if (change.wasAdded() && !fieldRows.containsKey(change.getKey())) { - FieldRow fieldRow = new FieldRow( - change.getKey(), - viewModel.mergedEntryProperty().get().getFields().size() - 1); - fieldRows.put(change.getKey(), fieldRow); - } - }); + viewModel.mergedEntryProperty() + .get() + .getFieldsObservable() + .addListener((MapChangeListener) change -> { + if (change.wasAdded() && !fieldRows.containsKey(change.getKey())) { + FieldRow fieldRow = new FieldRow(change.getKey(), + viewModel.mergedEntryProperty().get().getFields().size() - 1); + fieldRows.put(change.getKey(), fieldRow); + } + }); } @FXML @@ -129,10 +147,10 @@ public void initialize() { rightScrollPane.vvalueProperty().bindBidirectional(centerScrollPane.vvalueProperty()); viewModel.failedSuppliersProperty().addListener((_, _, _) -> { - failedSuppliers.setText(viewModel.failedSuppliersProperty().get().isEmpty() ? "" : Localization.lang( - "Could not extract Metadata from: %0", - String.join(", ", viewModel.failedSuppliersProperty()))); - int activeColumns = viewModel.entriesProperty().get().size() - viewModel.failedSuppliersProperty().get().size(); + failedSuppliers.setText(viewModel.failedSuppliersProperty().get().isEmpty() ? "" : Localization + .lang("Could not extract Metadata from: %0", String.join(", ", viewModel.failedSuppliersProperty()))); + int activeColumns = viewModel.entriesProperty().get().size() + - viewModel.failedSuppliersProperty().get().size(); if (activeColumns < ACTIVE_COLUMNS_MINIMUM) { // [impl->req~ux.auto-close.merge-entries~1] close(); @@ -142,15 +160,11 @@ public void initialize() { } private void fillDiffModes() { - diffMode.setItems(FXCollections.observableList(List.of( - DiffMode.PLAIN, - DiffMode.WORD, - DiffMode.CHARACTER))); - new ViewModelListCellFactory() - .withText(DiffMode::getDisplayText) - .install(diffMode); + diffMode.setItems(FXCollections.observableList(List.of(DiffMode.PLAIN, DiffMode.WORD, DiffMode.CHARACTER))); + new ViewModelListCellFactory().withText(DiffMode::getDisplayText).install(diffMode); diffMode.setValue(preferences.getMergeDialogPreferences().getMergeDiffMode()); - EasyBind.subscribe(this.diffMode.valueProperty(), mode -> preferences.getMergeDialogPreferences().setMergeDiffMode(mode)); + EasyBind.subscribe(this.diffMode.valueProperty(), + mode -> preferences.getMergeDialogPreferences().setMergeDiffMode(mode)); } private void addColumn(MultiMergeEntriesViewModel.EntrySource entrySourceColumn) { @@ -172,7 +186,8 @@ private void addColumn(MultiMergeEntriesViewModel.EntrySource entrySourceColumn) // Copy BibEntry to column - either immediately (if available) or after loading if (!entrySourceColumn.isLoadingProperty().getValue()) { writeBibEntryToColumn(entrySourceColumn, columnIndex); - } else { + } + else { header.setDisable(true); entrySourceColumn.isLoadingProperty().addListener((_, _, newValue) -> { if (!newValue && entrySourceColumn.entryProperty().get() != null) { @@ -214,12 +229,14 @@ private ToggleButton generateEntryHeader(MultiMergeEntriesViewModel.EntrySource /** * Adds ToggleButtons for all fields that are set for this BibEntry - * * @param entrySourceColumn the entry to write - * @param columnIndex the index of the column to write this entry to + * @param columnIndex the index of the column to write this entry to */ private void writeBibEntryToColumn(MultiMergeEntriesViewModel.EntrySource entrySourceColumn, int columnIndex) { - for (Map.Entry entry : entrySourceColumn.entryProperty().get().getFieldsObservable().entrySet()) { + for (Map.Entry entry : entrySourceColumn.entryProperty() + .get() + .getFieldsObservable() + .entrySet()) { Field key = entry.getKey(); String value = entry.getValue(); Cell cell = new Cell(value, key, columnIndex); @@ -228,21 +245,22 @@ private void writeBibEntryToColumn(MultiMergeEntriesViewModel.EntrySource entryS } /** - * Set up the button that displays the name of the source so that if it is clicked, all toggles in that column are - * selected. - * + * Set up the button that displays the name of the source so that if it is clicked, + * all toggles in that column are selected. * @param sourceButton the header button to setup - * @param column the column this button is heading + * @param column the column this button is heading */ private void setupSourceButtonAction(ToggleButton sourceButton, int column) { sourceButton.selectedProperty().addListener((observable, oldValue, newValue) -> { if (newValue) { - optionsGrid.getChildrenUnmodifiable().stream() - .filter(node -> GridPane.getColumnIndex(node) == column) - .filter(HBox.class::isInstance) - .forEach(hbox -> ((HBox) hbox).getChildrenUnmodifiable().stream() - .filter(ToggleButton.class::isInstance) - .forEach(toggleButton -> ((ToggleButton) toggleButton).setSelected(true))); + optionsGrid.getChildrenUnmodifiable() + .stream() + .filter(node -> GridPane.getColumnIndex(node) == column) + .filter(HBox.class::isInstance) + .forEach(hbox -> ((HBox) hbox).getChildrenUnmodifiable() + .stream() + .filter(ToggleButton.class::isInstance) + .forEach(toggleButton -> ((ToggleButton) toggleButton).setSelected(true))); sourceButton.setSelected(true); } }); @@ -250,7 +268,6 @@ private void setupSourceButtonAction(ToggleButton sourceButton, int column) { /** * Checks if the Field can be multiline - * * @param field the field to be checked * @return true if the field may be multiline, false otherwise */ @@ -269,8 +286,9 @@ public Cell(String content, Field field, int columnIndex) { this.content = content; /* - If this is not explicitly done on the JavaFX thread, the bindings to the text fields don't work properly. - The text only shows up after one text in that same row is selected by the user. + * If this is not explicitly done on the JavaFX thread, the bindings to the + * text fields don't work properly. The text only shows up after one text in + * that same row is selected by the user. */ UiTaskExecutor.runInJavaFXThread(() -> { @@ -294,7 +312,9 @@ public Cell(String content, Field field, int columnIndex) { HBox.setHgrow(cellButton, Priority.ALWAYS); // Text - DiffHighlightingEllipsingTextFlow buttonText = new DiffHighlightingEllipsingTextFlow(content, viewModel.mergedEntryProperty().get().getFieldBinding(field).asOrdinary(), diffMode.valueProperty()); + DiffHighlightingEllipsingTextFlow buttonText = new DiffHighlightingEllipsingTextFlow(content, + viewModel.mergedEntryProperty().get().getFieldBinding(field).asOrdinary(), + diffMode.valueProperty()); buttonText.maxWidthProperty().bind(widthProperty().add(-10)); buttonText.maxHeightProperty().bind(heightProperty()); @@ -329,7 +349,8 @@ public Cell(String content, Field field, int columnIndex) { addSource(Localization.lang("From DOI"), () -> { try { return doiFetcher.performSearchById(content).get(); - } catch (FetcherException | NoSuchElementException e) { + } + catch (FetcherException | NoSuchElementException e) { LOGGER.warn("Failed to fetch BibEntry for DOI {}", content, e); return null; } @@ -342,6 +363,7 @@ public Cell(String content, Field field, int columnIndex) { public String getContent() { return content; } + } public void addSource(String title, BibEntry entry) { @@ -355,12 +377,15 @@ public void addSource(String title, Supplier supplier) { private class FieldRow { public final ToggleGroup toggleGroup = new ToggleGroup(); + private final TextInputControl fieldEditorCell; private final int rowIndex; - // Reference needs to be kept, since java garbage collection would otherwise destroy the subscription - @SuppressWarnings("FieldCanBeLocal") private EasyObservableValue fieldBinding; + // Reference needs to be kept, since java garbage collection would otherwise + // destroy the subscription + @SuppressWarnings("FieldCanBeLocal") + private EasyObservableValue fieldBinding; public FieldRow(Field field, int rowIndex) { this.rowIndex = rowIndex; @@ -370,7 +395,8 @@ public FieldRow(Field field, int rowIndex) { if (isMultiLine) { fieldEditorCell = new TextArea(); ((TextArea) fieldEditorCell).setWrapText(true); - } else { + } + else { fieldEditorCell = new TextField(); } @@ -381,8 +407,12 @@ public FieldRow(Field field, int rowIndex) { toggleGroup.selectedToggleProperty().addListener((_, _, newValue) -> { if (newValue == null) { viewModel.mergedEntryProperty().get().setField(field, ""); - } else { - viewModel.mergedEntryProperty().get().setField(field, ((DiffHighlightingEllipsingTextFlow) ((ToggleButton) newValue).getGraphic()).getFullText()); + } + else { + viewModel.mergedEntryProperty() + .get() + .setField(field, ((DiffHighlightingEllipsingTextFlow) ((ToggleButton) newValue).getGraphic()) + .getFullText()); headerToggleGroup.selectToggle(null); } }); @@ -390,26 +420,21 @@ public FieldRow(Field field, int rowIndex) { /** * Adds a row that represents this field - * * @param field the field to add to the view as a new row in the table */ private void addRow(Field field) { VBox.setVgrow(fieldEditorCell, Priority.ALWAYS); fieldBinding = viewModel.mergedEntryProperty().get().getFieldBinding(field).asOrdinary(); - BindingsHelper.bindBidirectional( - fieldEditorCell.textProperty(), - fieldBinding, - text -> { - if (text != null) { - fieldEditorCell.setText(text); - } - }, - binding -> { - if (binding != null) { - viewModel.mergedEntryProperty().get().setField(field, binding); - } - }); + BindingsHelper.bindBidirectional(fieldEditorCell.textProperty(), fieldBinding, text -> { + if (text != null) { + fieldEditorCell.setText(text); + } + }, binding -> { + if (binding != null) { + viewModel.mergedEntryProperty().get().setField(field, binding); + } + }); fieldEditorCell.setMaxHeight(Double.MAX_VALUE); VBox.setVgrow(fieldEditorCell, Priority.ALWAYS); @@ -429,5 +454,7 @@ private void addRow(Field field) { constraint.prefHeightProperty().bind(fieldEditorCell.heightProperty()); optionsGrid.getRowConstraints().add(constraint); } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/multiwaymerge/MultiMergeEntriesViewModel.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/multiwaymerge/MultiMergeEntriesViewModel.java index fe5775ad192..c63e0d73c3c 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/multiwaymerge/MultiMergeEntriesViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/multiwaymerge/MultiMergeEntriesViewModel.java @@ -31,7 +31,8 @@ public class MultiMergeEntriesViewModel extends AbstractViewModel { public void addSource(EntrySource entrySource) { if (!entrySource.isLoading.getValue()) { updateFields(entrySource.entry.get()); - } else { + } + else { entrySource.isLoading.addListener((observable, oldValue, newValue) -> { if (!newValue) { updateFields(entrySource.entry.get()); @@ -76,20 +77,21 @@ public ListProperty failedSuppliersProperty() { } public static class EntrySource { + private final StringProperty title = new SimpleStringProperty(""); + private final ObjectProperty entry = new SimpleObjectProperty<>(); + private final BooleanProperty isLoading = new SimpleBooleanProperty(false); public EntrySource(String title, Supplier entrySupplier, TaskExecutor taskExecutor) { this.title.set(title); isLoading.set(true); - BackgroundTask.wrap(entrySupplier::get) - .onSuccess(value -> { - entry.set(value); - isLoading.set(false); - }) - .executeWith(taskExecutor); + BackgroundTask.wrap(entrySupplier::get).onSuccess(value -> { + entry.set(value); + isLoading.set(false); + }).executeWith(taskExecutor); } public EntrySource(String title, BibEntry entry) { @@ -108,5 +110,7 @@ public ObjectProperty entryProperty() { public BooleanProperty isLoadingProperty() { return isLoading; } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/DiffMethod.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/DiffMethod.java index 9ce3a98fd65..b0eda9da9ee 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/DiffMethod.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/DiffMethod.java @@ -1,5 +1,7 @@ package org.jabref.gui.mergeentries.threewaymerge; public interface DiffMethod { + public String separator(); + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/EntriesMergeResult.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/EntriesMergeResult.java index 105d32e8901..27c0534e6c8 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/EntriesMergeResult.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/EntriesMergeResult.java @@ -2,7 +2,6 @@ import org.jabref.model.entry.BibEntry; -public record EntriesMergeResult( - BibEntry originalLeftEntry, BibEntry originalRightEntry, BibEntry newLeftEntry, BibEntry newRightEntry, BibEntry mergedEntry -) { +public record EntriesMergeResult(BibEntry originalLeftEntry, BibEntry originalRightEntry, BibEntry newLeftEntry, + BibEntry newRightEntry, BibEntry mergedEntry) { } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/FieldRowView.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/FieldRowView.java index 7d696e3b00b..d3b4c391e66 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/FieldRowView.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/FieldRowView.java @@ -29,21 +29,27 @@ * A controller class to control left, right and merged field values */ public class FieldRowView { + private static final Logger LOGGER = LoggerFactory.getLogger(FieldRowView.class); protected final FieldRowViewModel viewModel; protected final BooleanProperty shouldShowDiffs = new SimpleBooleanProperty(true); + private final FieldNameCell fieldNameCell; + private final FieldValueCell leftValueCell; + private final FieldValueCell rightValueCell; + private final MergedFieldCell mergedValueCell; private final ToggleGroup toggleGroup = new ToggleGroup(); private GridPane parent; - public FieldRowView(Field field, BibEntry leftEntry, BibEntry rightEntry, BibEntry mergedEntry, FieldMergerFactory fieldMergerFactory, GuiPreferences preferences, int rowIndex) { + public FieldRowView(Field field, BibEntry leftEntry, BibEntry rightEntry, BibEntry mergedEntry, + FieldMergerFactory fieldMergerFactory, GuiPreferences preferences, int rowIndex) { viewModel = new FieldRowViewModel(field, leftEntry, rightEntry, mergedEntry, fieldMergerFactory); fieldNameCell = new FieldNameCell(field.getDisplayName(), rowIndex); @@ -51,8 +57,10 @@ public FieldRowView(Field field, BibEntry leftEntry, BibEntry rightEntry, BibEnt rightValueCell = new FieldValueCell(viewModel.getRightFieldValue(), rowIndex, preferences); mergedValueCell = new MergedFieldCell(viewModel.getMergedFieldValue(), rowIndex); - // As a workaround we need to have a reference to the parent grid pane to be able to show/hide the row. - // This won't be necessary when https://bugs.openjdk.org/browse/JDK-8136901 is fixed. + // As a workaround we need to have a reference to the parent grid pane to be able + // to show/hide the row. + // This won't be necessary when https://bugs.openjdk.org/browse/JDK-8136901 is + // fixed. leftValueCell.parentProperty().addListener(e -> { if (leftValueCell.getParent() instanceof GridPane grid) { parent = grid; @@ -68,7 +76,8 @@ public FieldRowView(Field field, BibEntry leftEntry, BibEntry rightEntry, BibEnt LOGGER.debug("Field merge state is {} for field {}", fieldState, field); if (fieldState == ToggleMergeUnmergeButton.FieldState.MERGED) { viewModel.mergeFields(); - } else { + } + else { viewModel.unmergeFields(); } }); @@ -83,9 +92,11 @@ public FieldRowView(Field field, BibEntry leftEntry, BibEntry rightEntry, BibEnt EasyBind.subscribe(viewModel.selectionProperty(), selection -> { if (selection == Selection.LEFT) { toggleGroup.selectToggle(leftValueCell); - } else if (selection == Selection.RIGHT) { + } + else if (selection == Selection.RIGHT) { toggleGroup.selectToggle(rightValueCell); - } else if (selection == Selection.NONE) { + } + else if (selection == Selection.NONE) { toggleGroup.selectToggle(null); } }); @@ -93,19 +104,23 @@ public FieldRowView(Field field, BibEntry leftEntry, BibEntry rightEntry, BibEnt EasyBind.subscribe(toggleGroup.selectedToggleProperty(), selectedToggle -> { if (selectedToggle == leftValueCell) { selectLeftValue(); - } else if (selectedToggle == rightValueCell) { + } + else if (selectedToggle == rightValueCell) { selectRightValue(); - } else { + } + else { selectNone(); } }); - // Hide rightValueCell and extend leftValueCell to 2 columns when fields are merged + // Hide rightValueCell and extend leftValueCell to 2 columns when fields are + // merged EasyBind.subscribe(viewModel.isFieldsMergedProperty(), isFieldsMerged -> { if (isFieldsMerged) { rightValueCell.setVisible(false); GridPane.setColumnSpan(leftValueCell, 2); - } else { + } + else { rightValueCell.setVisible(true); GridPane.setColumnSpan(leftValueCell, 1); } @@ -156,7 +171,8 @@ public MergedFieldCell getMergedValueCell() { } public void showDiff(ShowDiffConfig diffConfig) { - if (!rightValueCell.isVisible() || StringUtil.isNullOrEmpty(viewModel.getLeftFieldValue()) || StringUtil.isNullOrEmpty(viewModel.getRightFieldValue())) { + if (!rightValueCell.isVisible() || StringUtil.isNullOrEmpty(viewModel.getLeftFieldValue()) + || StringUtil.isNullOrEmpty(viewModel.getRightFieldValue())) { return; } LOGGER.debug("Showing diffs..."); @@ -168,7 +184,8 @@ public void showDiff(ShowDiffConfig diffConfig) { if (shouldShowDiffs.get()) { if (diffConfig.diffView() == ThreeWayMergeToolbar.DiffView.UNIFIED) { new UnifiedDiffHighlighter(leftLabel, rightLabel, diffConfig.diffHighlightingMethod()).highlight(); - } else { + } + else { new SplitDiffHighlighter(leftLabel, rightLabel, diffConfig.diffHighlightingMethod()).highlight(); } } @@ -214,6 +231,9 @@ public void autoSelectBetterValue() { @Override public String toString() { - return "FieldRowView [shouldShowDiffs=" + shouldShowDiffs.get() + ", fieldNameCell=" + fieldNameCell + ", leftValueCell=" + leftValueCell + ", rightValueCell=" + rightValueCell + ", mergedValueCell=" + mergedValueCell + "]"; + return "FieldRowView [shouldShowDiffs=" + shouldShowDiffs.get() + ", fieldNameCell=" + fieldNameCell + + ", leftValueCell=" + leftValueCell + ", rightValueCell=" + rightValueCell + ", mergedValueCell=" + + mergedValueCell + "]"; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/FieldRowViewModel.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/FieldRowViewModel.java index 2cd50b5ffa6..5a568877cb7 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/FieldRowViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/FieldRowViewModel.java @@ -31,23 +31,28 @@ import org.slf4j.LoggerFactory; public class FieldRowViewModel { + public enum Selection { - LEFT, - RIGHT, + + LEFT, RIGHT, /** - * When the user types something into the merged field value and neither the left nor - * right values match it, NONE is selected - * */ + * When the user types something into the merged field value and neither the left + * nor right values match it, NONE is selected + */ NONE + } private final Logger LOGGER = LoggerFactory.getLogger(FieldRowViewModel.class); + private final BooleanProperty isFieldsMerged = new SimpleBooleanProperty(Boolean.FALSE); private final ObjectProperty selection = new SimpleObjectProperty<>(); private final StringProperty leftFieldValue = new SimpleStringProperty(""); + private final StringProperty rightFieldValue = new SimpleStringProperty(""); + private final StringProperty mergedFieldValue = new SimpleStringProperty(""); private final Field field; @@ -64,7 +69,8 @@ public enum Selection { private final CompoundEdit fieldsMergedEdit = new CompoundEdit(); - public FieldRowViewModel(Field field, BibEntry leftEntry, BibEntry rightEntry, BibEntry mergedEntry, FieldMergerFactory fieldMergerFactory) { + public FieldRowViewModel(Field field, BibEntry leftEntry, BibEntry rightEntry, BibEntry mergedEntry, + FieldMergerFactory fieldMergerFactory) { this.field = field; this.leftEntry = leftEntry; this.rightEntry = rightEntry; @@ -74,7 +80,8 @@ public FieldRowViewModel(Field field, BibEntry leftEntry, BibEntry rightEntry, B if (field.equals(InternalField.TYPE_HEADER)) { setLeftFieldValue(leftEntry.getType().getDisplayName()); setRightFieldValue(rightEntry.getType().getDisplayName()); - } else { + } + else { setLeftFieldValue(leftEntry.getField(field).orElse("")); setRightFieldValue(rightEntry.getField(field).orElse("")); } @@ -84,12 +91,14 @@ public FieldRowViewModel(Field field, BibEntry leftEntry, BibEntry rightEntry, B EasyBind.listen(mergedFieldValueProperty(), (obs, old, mergedFieldValue) -> { if (field.equals(InternalField.TYPE_HEADER)) { getMergedEntry().setType(EntryTypeFactory.parse(mergedFieldValue)); - } else { + } + else { getMergedEntry().setField(field, mergedFieldValue); } }); - hasEqualLeftAndRight = Bindings.createBooleanBinding(this::hasEqualLeftAndRightValues, leftFieldValueProperty(), rightFieldValueProperty()); + hasEqualLeftAndRight = Bindings.createBooleanBinding(this::hasEqualLeftAndRightValues, leftFieldValueProperty(), + rightFieldValueProperty()); selectNonEmptyValue(); @@ -97,7 +106,8 @@ public FieldRowViewModel(Field field, BibEntry leftEntry, BibEntry rightEntry, B LOGGER.debug("Field are merged: {}", areFieldsMerged); if (areFieldsMerged) { selectLeftValue(); - } else { + } + else { selectNonEmptyValue(); } }); @@ -114,9 +124,11 @@ public FieldRowViewModel(Field field, BibEntry leftEntry, BibEntry rightEntry, B LOGGER.debug("Merged value is {} for field {}", mergedValue, field.getDisplayName()); if (mergedValue.equals(getLeftFieldValue())) { selectLeftValue(); - } else if (getMergedFieldValue().equals(getRightFieldValue())) { + } + else if (getMergedFieldValue().equals(getRightFieldValue())) { selectRightValue(); - } else { + } + else { selectNone(); } }); @@ -129,14 +141,16 @@ public void autoSelectBetterValue() { String rightValue = getRightFieldValue(); if (StandardField.YEAR == field) { - YearFieldValuePlausibilityComparator comparator = new YearFieldValuePlausibilityComparator(); - ComparisonResult comparison = comparator.compare(leftValue, rightValue); - if (ComparisonResult.RIGHT_BETTER == comparison) { - selectRightValue(); - } else if (ComparisonResult.LEFT_BETTER == comparison) { - selectLeftValue(); - } - } else if (InternalField.TYPE_HEADER == field) { + YearFieldValuePlausibilityComparator comparator = new YearFieldValuePlausibilityComparator(); + ComparisonResult comparison = comparator.compare(leftValue, rightValue); + if (ComparisonResult.RIGHT_BETTER == comparison) { + selectRightValue(); + } + else if (ComparisonResult.LEFT_BETTER == comparison) { + selectLeftValue(); + } + } + else if (InternalField.TYPE_HEADER == field) { if (leftValue.equalsIgnoreCase(StandardEntryType.Misc.getName())) { selectRightValue(); } @@ -146,7 +160,8 @@ public void autoSelectBetterValue() { public void selectNonEmptyValue() { if (StringUtil.isNullOrEmpty(leftFieldValue.get())) { selectRightValue(); - } else { + } + else { selectLeftValue(); } } @@ -162,7 +177,8 @@ public void selectLeftValue() { public void selectRightValue() { if (isFieldsMerged()) { selectLeftValue(); - } else { + } + else { setSelection(Selection.RIGHT); } } @@ -200,7 +216,8 @@ public void mergeFields() { if (fieldsMergedEdit.canRedo()) { fieldsMergedEdit.redo(); - } else { + } + else { fieldsMergedEdit.addEdit(new MergeFieldsUndo(oldLeftFieldValue, oldRightFieldValue, mergedFields)); fieldsMergedEdit.end(); } @@ -281,8 +298,11 @@ public BibEntry getMergedEntry() { } class MergeFieldsUndo extends AbstractUndoableEdit { + private final String oldLeft; + private final String oldRight; + private final String mergedFields; MergeFieldsUndo(String oldLeft, String oldRight, String mergedFields) { @@ -304,5 +324,7 @@ public void redo() throws CannotRedoException { setLeftFieldValue(mergedFields); setRightFieldValue(mergedFields); } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/GroupDiffMode.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/GroupDiffMode.java index 0f284526c57..d7d0a70ca40 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/GroupDiffMode.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/GroupDiffMode.java @@ -5,11 +5,12 @@ public class GroupDiffMode implements DiffMethod { private final String separator; public GroupDiffMode(String separator) { - this.separator = separator; + this.separator = separator; } @Override public String separator() { return this.separator; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/MergeEntriesAction.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/MergeEntriesAction.java index 1cd6bfb887f..bbb6c6f3d6f 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/MergeEntriesAction.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/MergeEntriesAction.java @@ -16,13 +16,19 @@ import org.jabref.model.entry.field.InternalField; public class MergeEntriesAction extends SimpleCommand { + private static final int NUMBER_OF_ENTRIES_NEEDED = 2; + private final DialogService dialogService; + private final StateManager stateManager; + private final UndoManager undoManager; + private final GuiPreferences preferences; - public MergeEntriesAction(DialogService dialogService, StateManager stateManager, UndoManager undoManager, GuiPreferences preferences) { + public MergeEntriesAction(DialogService dialogService, StateManager stateManager, UndoManager undoManager, + GuiPreferences preferences) { this.dialogService = dialogService; this.stateManager = stateManager; this.undoManager = undoManager; @@ -41,8 +47,7 @@ public void execute() { List selectedEntries = stateManager.getSelectedEntries(); if (selectedEntries.size() != 2) { // Inform the user to select entries first. - dialogService.showInformationDialogAndWait( - Localization.lang("Merge entries"), + dialogService.showInformationDialogAndWait(Localization.lang("Merge entries"), Localization.lang("You have to choose exactly two entries to merge.")); return; } @@ -58,7 +63,8 @@ public void execute() { if (entryComparator.compare(one, two) <= 0) { first = one; second = two; - } else { + } + else { first = two; second = one; } @@ -73,4 +79,5 @@ public void execute() { dialogService.notify(Localization.lang("Merged entries")); }, () -> dialogService.notify(Localization.lang("Canceled merging entries"))); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/MergeEntriesDialog.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/MergeEntriesDialog.java index 587a06781bf..0d08d9a9784 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/MergeEntriesDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/MergeEntriesDialog.java @@ -9,8 +9,11 @@ import org.jabref.model.entry.BibEntry; public class MergeEntriesDialog extends BaseDialog { + private final ThreeWayMergeView threeWayMergeView; + private final BibEntry one; + private final BibEntry two; public MergeEntriesDialog(BibEntry one, BibEntry two, GuiPreferences preferences) { @@ -21,7 +24,8 @@ public MergeEntriesDialog(BibEntry one, BibEntry two, GuiPreferences preferences init(); } - public MergeEntriesDialog(BibEntry one, BibEntry two, String leftHeader, String rightHeader, GuiPreferences preferences) { + public MergeEntriesDialog(BibEntry one, BibEntry two, String leftHeader, String rightHeader, + GuiPreferences preferences) { threeWayMergeView = new ThreeWayMergeView(one, two, leftHeader, rightHeader, preferences); this.one = one; this.two = two; @@ -44,8 +48,10 @@ private void init() { this.setResultConverter(buttonType -> { threeWayMergeView.saveConfiguration(); if (buttonType.equals(replaceEntries)) { - return new EntriesMergeResult(one, two, threeWayMergeView.getLeftEntry(), threeWayMergeView.getRightEntry(), threeWayMergeView.getMergedEntry()); - } else { + return new EntriesMergeResult(one, two, threeWayMergeView.getLeftEntry(), + threeWayMergeView.getRightEntry(), threeWayMergeView.getMergedEntry()); + } + else { return null; } }); @@ -66,4 +72,5 @@ public void configureDiff(ShowDiffConfig diffConfig) { public void autoSelectBetterFields() { threeWayMergeView.autoSelectBetterFields(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/MergeTwoEntriesAction.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/MergeTwoEntriesAction.java index 4e4993b2d1b..afa5d6c0448 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/MergeTwoEntriesAction.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/MergeTwoEntriesAction.java @@ -15,11 +15,15 @@ import org.jabref.model.entry.BibEntry; public class MergeTwoEntriesAction extends SimpleCommand { + private final EntriesMergeResult entriesMergeResult; + private final StateManager stateManager; + private final UndoManager undoManager; - public MergeTwoEntriesAction(EntriesMergeResult entriesMergeResult, StateManager stateManager, UndoManager undoManager) { + public MergeTwoEntriesAction(EntriesMergeResult entriesMergeResult, StateManager stateManager, + UndoManager undoManager) { this.entriesMergeResult = entriesMergeResult; this.stateManager = stateManager; this.undoManager = undoManager; @@ -32,16 +36,19 @@ public void execute() { } BibDatabase database = stateManager.getActiveDatabase().get().getDatabase(); - List entriesToRemove = Arrays.asList(entriesMergeResult.originalLeftEntry(), entriesMergeResult.originalRightEntry()); + List entriesToRemove = Arrays.asList(entriesMergeResult.originalLeftEntry(), + entriesMergeResult.originalRightEntry()); database.insertEntry(entriesMergeResult.mergedEntry()); database.removeEntries(entriesToRemove); NamedCompound ce = new NamedCompound(Localization.lang("Merge entries")); - ce.addEdit(new UndoableInsertEntries(stateManager.getActiveDatabase().get().getDatabase(), entriesMergeResult.mergedEntry())); + ce.addEdit(new UndoableInsertEntries(stateManager.getActiveDatabase().get().getDatabase(), + entriesMergeResult.mergedEntry())); ce.addEdit(new UndoableRemoveEntries(database, entriesToRemove)); ce.end(); undoManager.addEdit(ce); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/PersonsNameFieldRowView.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/PersonsNameFieldRowView.java index e551cb552e7..13e112b29b7 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/PersonsNameFieldRowView.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/PersonsNameFieldRowView.java @@ -11,10 +11,13 @@ import org.jabref.model.entry.field.FieldProperty; public class PersonsNameFieldRowView extends FieldRowView { + private final AuthorList leftEntryNames; + private final AuthorList rightEntryNames; - public PersonsNameFieldRowView(Field field, BibEntry leftEntry, BibEntry rightEntry, BibEntry mergedEntry, FieldMergerFactory fieldMergerFactory, GuiPreferences preferences, int rowIndex) { + public PersonsNameFieldRowView(Field field, BibEntry leftEntry, BibEntry rightEntry, BibEntry mergedEntry, + FieldMergerFactory fieldMergerFactory, GuiPreferences preferences, int rowIndex) { super(field, leftEntry, rightEntry, mergedEntry, fieldMergerFactory, preferences, rowIndex); assert field.getProperties().contains(FieldProperty.PERSON_NAMES); @@ -29,7 +32,9 @@ public PersonsNameFieldRowView(Field field, BibEntry leftEntry, BibEntry rightEn } private void showPersonsNamesAreTheSameInfo() { - InfoButton infoButton = new InfoButton(Localization.lang("The %0s are the same. However, the order of field content differs", viewModel.getField().getName())); + InfoButton infoButton = new InfoButton(Localization + .lang("The %0s are the same. However, the order of field content differs", viewModel.getField().getName())); getFieldNameCell().addSideButton(infoButton); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/ShowDiffConfig.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/ShowDiffConfig.java index c0aed294544..d488263ecc6 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/ShowDiffConfig.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/ShowDiffConfig.java @@ -2,7 +2,5 @@ import org.jabref.gui.mergeentries.threewaymerge.toolbar.ThreeWayMergeToolbar.DiffView; -public record ShowDiffConfig( - DiffView diffView, - DiffMethod diffHighlightingMethod) { +public record ShowDiffConfig(DiffView diffView, DiffMethod diffHighlightingMethod) { } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/ThreeWayMergeHeaderView.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/ThreeWayMergeHeaderView.java index b4d9a713ea8..0167fbc1397 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/ThreeWayMergeHeaderView.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/ThreeWayMergeHeaderView.java @@ -8,12 +8,15 @@ import org.jabref.logic.l10n.Localization; /** - * GridPane was used instead of a Hbox because Hbox allocates more space for cells - * with longer text, but I wanted all cells to have the same width + * GridPane was used instead of a Hbox because Hbox allocates more space for cells with + * longer text, but I wanted all cells to have the same width */ public class ThreeWayMergeHeaderView extends GridPane { + public static final String DEFAULT_STYLE_CLASS = "merge-header"; + private final HeaderCell leftHeaderCell; + private final HeaderCell rightHeaderCell; private final HeaderCell mergedHeaderCell; @@ -25,19 +28,16 @@ public ThreeWayMergeHeaderView(String leftHeader, String rightHeader) { this.rightHeaderCell = new HeaderCell(rightHeader); this.mergedHeaderCell = new HeaderCell(Localization.lang("Merged entry")); - addRow(0, - new HeaderCell(""), - leftHeaderCell, - rightHeaderCell, - mergedHeaderCell - ); + addRow(0, new HeaderCell(""), leftHeaderCell, rightHeaderCell, mergedHeaderCell); setPrefHeight(Control.USE_COMPUTED_SIZE); setMaxHeight(Control.USE_PREF_SIZE); setMinHeight(Control.USE_PREF_SIZE); - // The fields grid pane is contained within a scroll pane, thus it doesn't allocate the full available width. In - // fact, it uses the available width minus the size of the scrollbar which is 8. This leads to header columns being + // The fields grid pane is contained within a scroll pane, thus it doesn't + // allocate the full available width. In + // fact, it uses the available width minus the size of the scrollbar which is 8. + // This leads to header columns being // always wider than fields columns. This hack should fix it. setPadding(new Insets(0, 8, 0, 0)); } @@ -49,4 +49,5 @@ public void setLeftHeader(String leftHeader) { public void setRightHeader(String rightHeader) { rightHeaderCell.setText(rightHeader); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/ThreeWayMergeView.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/ThreeWayMergeView.java index 059d13f0577..bce64f0e0fb 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/ThreeWayMergeView.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/ThreeWayMergeView.java @@ -20,32 +20,48 @@ import org.jabref.model.entry.field.FieldProperty; public class ThreeWayMergeView extends VBox { + public static final int GRID_COLUMN_MIN_WIDTH = 250; public static final String LEFT_DEFAULT_HEADER = Localization.lang("Left Entry"); + public static final String RIGHT_DEFAULT_HEADER = Localization.lang("Right Entry"); private final ColumnConstraints fieldNameColumnConstraints = new ColumnConstraints(150); - private final ColumnConstraints leftEntryColumnConstraints = new ColumnConstraints(GRID_COLUMN_MIN_WIDTH, 256, Double.MAX_VALUE); - private final ColumnConstraints rightEntryColumnConstraints = new ColumnConstraints(GRID_COLUMN_MIN_WIDTH, 256, Double.MAX_VALUE); - private final ColumnConstraints mergedEntryColumnConstraints = new ColumnConstraints(GRID_COLUMN_MIN_WIDTH, 256, Double.MAX_VALUE); + + private final ColumnConstraints leftEntryColumnConstraints = new ColumnConstraints(GRID_COLUMN_MIN_WIDTH, 256, + Double.MAX_VALUE); + + private final ColumnConstraints rightEntryColumnConstraints = new ColumnConstraints(GRID_COLUMN_MIN_WIDTH, 256, + Double.MAX_VALUE); + + private final ColumnConstraints mergedEntryColumnConstraints = new ColumnConstraints(GRID_COLUMN_MIN_WIDTH, 256, + Double.MAX_VALUE); + private final ThreeWayMergeToolbar toolbar; + private final ThreeWayMergeHeaderView headerView; + private final ScrollPane scrollPane; + private final GridPane mergeGridPane; private final ThreeWayMergeViewModel viewModel; + private final List fieldRows = new ArrayList<>(); private final GuiPreferences preferences; private final FieldMergerFactory fieldMergerFactory; + private final String keywordSeparator; - public ThreeWayMergeView(BibEntry leftEntry, BibEntry rightEntry, String leftHeader, String rightHeader, GuiPreferences preferences) { + public ThreeWayMergeView(BibEntry leftEntry, BibEntry rightEntry, String leftHeader, String rightHeader, + GuiPreferences preferences) { this.preferences = preferences; - viewModel = new ThreeWayMergeViewModel(new BibEntry(leftEntry), new BibEntry(rightEntry), leftHeader, rightHeader); + viewModel = new ThreeWayMergeViewModel(new BibEntry(leftEntry), new BibEntry(rightEntry), leftHeader, + rightHeader); this.fieldMergerFactory = new FieldMergerFactory(preferences.getBibEntryPreferences()); this.keywordSeparator = preferences.getBibEntryPreferences().getKeywordSeparator().toString(); @@ -91,7 +107,8 @@ private void showOrHideEqualFields() { if (row.hasEqualLeftAndRightValues()) { row.hide(); } - } else { + } + else { row.show(); } } @@ -100,22 +117,25 @@ private void showOrHideEqualFields() { private void updateDiff() { if (toolbar.shouldShowDiffs()) { for (FieldRowView row : fieldRows) { - if ("Groups".equals(row.getFieldNameCell().getText()) && (row.getLeftValueCell().getText().contains(keywordSeparator) || row.getRightValueCell().getText().contains(keywordSeparator))) { + if ("Groups".equals(row.getFieldNameCell().getText()) + && (row.getLeftValueCell().getText().contains(keywordSeparator) + || row.getRightValueCell().getText().contains(keywordSeparator))) { row.showDiff(new ShowDiffConfig(toolbar.getDiffView(), new GroupDiffMode(keywordSeparator))); - } else { + } + else { row.showDiff(new ShowDiffConfig(toolbar.getDiffView(), toolbar.getDiffHighlightingMethod())); } } - } else { + } + else { fieldRows.forEach(FieldRowView::hideDiff); } } private void initializeHeaderView() { - headerView.getColumnConstraints().addAll(fieldNameColumnConstraints, - leftEntryColumnConstraints, - rightEntryColumnConstraints, - mergedEntryColumnConstraints); + headerView.getColumnConstraints() + .addAll(fieldNameColumnConstraints, leftEntryColumnConstraints, rightEntryColumnConstraints, + mergedEntryColumnConstraints); } private void initializeScrollPane() { @@ -132,7 +152,9 @@ private void initializeColumnConstraints() { } private void initializeMergeGridPane() { - mergeGridPane.getColumnConstraints().addAll(fieldNameColumnConstraints, leftEntryColumnConstraints, rightEntryColumnConstraints, mergedEntryColumnConstraints); + mergeGridPane.getColumnConstraints() + .addAll(fieldNameColumnConstraints, leftEntryColumnConstraints, rightEntryColumnConstraints, + mergedEntryColumnConstraints); for (int fieldIndex = 0; fieldIndex < viewModel.numberOfVisibleFields(); fieldIndex++) { addRow(fieldIndex); @@ -150,9 +172,12 @@ private void addRow(int fieldIndex) { FieldRowView fieldRow; if (field.getProperties().contains(FieldProperty.PERSON_NAMES)) { - fieldRow = new PersonsNameFieldRowView(field, getLeftEntry(), getRightEntry(), getMergedEntry(), fieldMergerFactory, preferences, fieldIndex); - } else { - fieldRow = new FieldRowView(field, getLeftEntry(), getRightEntry(), getMergedEntry(), fieldMergerFactory, preferences, fieldIndex); + fieldRow = new PersonsNameFieldRowView(field, getLeftEntry(), getRightEntry(), getMergedEntry(), + fieldMergerFactory, preferences, fieldIndex); + } + else { + fieldRow = new FieldRowView(field, getLeftEntry(), getRightEntry(), getMergedEntry(), fieldMergerFactory, + preferences, fieldIndex); } fieldRows.add(fieldIndex, fieldRow); @@ -206,4 +231,5 @@ public void autoSelectBetterFields() { row.autoSelectBetterValue(); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/ThreeWayMergeViewModel.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/ThreeWayMergeViewModel.java index 80542a0d7ae..b29d6b12ced 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/ThreeWayMergeViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/ThreeWayMergeViewModel.java @@ -22,9 +22,13 @@ public class ThreeWayMergeViewModel extends AbstractViewModel { private final ObjectProperty leftEntry = new SimpleObjectProperty<>(); + private final ObjectProperty rightEntry = new SimpleObjectProperty<>(); + private final ObjectProperty mergedEntry = new SimpleObjectProperty<>(); + private final StringProperty leftHeader = new SimpleStringProperty(); + private final StringProperty rightHeader = new SimpleStringProperty(); private final ObservableList visibleFields = FXCollections.observableArrayList(); @@ -42,9 +46,8 @@ public ThreeWayMergeViewModel(BibEntry leftEntry, BibEntry rightEntry, String le mergedEntry.set(new BibEntry()); - setVisibleFields(Stream.concat( - leftEntry.getFields().stream(), - rightEntry.getFields().stream()).collect(Collectors.toSet())); + setVisibleFields(Stream.concat(leftEntry.getFields().stream(), rightEntry.getFields().stream()) + .collect(Collectors.toSet())); } public StringProperty leftHeaderProperty() { @@ -96,7 +99,8 @@ public ObservableList getVisibleFields() { } /** - * Convince method to determine the total number of fields in the union of the left and right fields. + * Convince method to determine the total number of fields in the union of the left + * and right fields. */ public int numberOfVisibleFields() { return visibleFields.size(); @@ -113,4 +117,5 @@ private void setVisibleFields(Set fields) { // Add the entry type field as the first field to display visibleFields.addFirst(InternalField.TYPE_HEADER); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/CopyFieldValueCommand.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/CopyFieldValueCommand.java index c0906c6447c..19075264ec4 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/CopyFieldValueCommand.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/CopyFieldValueCommand.java @@ -6,7 +6,9 @@ import org.jabref.gui.actions.SimpleCommand; public class CopyFieldValueCommand extends SimpleCommand { + private final String fieldValue; + private final ClipBoardManager clipBoardManager; public CopyFieldValueCommand(final String fieldValue) { @@ -19,4 +21,5 @@ public CopyFieldValueCommand(final String fieldValue) { public void execute() { clipBoardManager.setContent(fieldValue); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/FieldNameCell.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/FieldNameCell.java index bafaae86399..0cf86849de5 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/FieldNameCell.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/FieldNameCell.java @@ -9,8 +9,11 @@ * A readonly cell used to display the name of some field. */ public class FieldNameCell extends ThreeWayMergeCell { + public static final String DEFAULT_STYLE_CLASS = "field-name"; + protected final HBox actionLayout = new HBox(); + private final Label label = new Label(); private final HBox labelBox = new HBox(label); @@ -36,4 +39,5 @@ public void addSideButton(Button sideButton) { actionLayout.getChildren().clear(); actionLayout.getChildren().setAll(sideButton); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/FieldValueCell.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/FieldValueCell.java index d513ca429f6..5aa3c7d0070 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/FieldValueCell.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/FieldValueCell.java @@ -39,9 +39,11 @@ * A readonly, selectable field cell that contains the value of some field */ public class FieldValueCell extends ThreeWayMergeCell implements Toggle { + public static final Logger LOGGER = LoggerFactory.getLogger(FieldValueCell.class); public static final String DEFAULT_STYLE_CLASS = "merge-field-value"; + public static final String SELECTION_BOX_STYLE_CLASS = "selection-box"; private static final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected"); @@ -51,11 +53,15 @@ public class FieldValueCell extends ThreeWayMergeCell implements Toggle { private final ActionFactory factory; private final StyleClassedTextArea label = new StyleClassedTextArea(); + private final VirtualizedScrollPane scrollPane = new VirtualizedScrollPane<>(label); + HBox labelBox = new HBox(scrollPane); private final HBox selectionBox = new HBox(); + private final HBox actionsContainer = new HBox(); + private final FieldValueCellViewModel viewModel; public FieldValueCell(String text, int rowIndex, GuiPreferences preferences) { @@ -130,7 +136,8 @@ private Button createCopyButton() { FontIcon copyIcon = FontIcon.of(MaterialDesignC.CONTENT_COPY); copyIcon.getStyleClass().add("action-icon"); - Button copyButton = factory.createIconButton(() -> Localization.lang("Copy"), new CopyFieldValueCommand(getText())); + Button copyButton = factory.createIconButton(() -> Localization.lang("Copy"), + new CopyFieldValueCommand(getText())); copyButton.setGraphic(copyIcon); copyButton.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); copyButton.setMaxHeight(Double.MAX_VALUE); @@ -143,12 +150,15 @@ public Button createOpenLinkButton() { Node openLinkIcon = IconTheme.JabRefIcons.OPEN_LINK.getGraphicNode(); openLinkIcon.getStyleClass().add("action-icon"); - Button openLinkButton = factory.createIconButton(() -> Localization.lang("Open Link"), new OpenExternalLinkAction(getText(), preferences.getExternalApplicationsPreferences())); + Button openLinkButton = factory.createIconButton(() -> Localization.lang("Open Link"), + new OpenExternalLinkAction(getText(), preferences.getExternalApplicationsPreferences())); openLinkButton.setGraphic(openLinkIcon); openLinkButton.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); openLinkButton.setMaxHeight(Double.MAX_VALUE); - openLinkButton.visibleProperty().bind(EasyBind.map(textProperty(), input -> StringUtil.isNotBlank(input) && (URLUtil.isURL(input) || DOI.isValid(input)))); + openLinkButton.visibleProperty() + .bind(EasyBind.map(textProperty(), + input -> StringUtil.isNotBlank(input) && (URLUtil.isURL(input) || DOI.isValid(input)))); return openLinkButton; } @@ -160,11 +170,11 @@ private void initializeScrollPane() { private void preventTextSelectionViaMouseEvents() { label.addEventFilter(MouseEvent.ANY, e -> { - if ((e.getEventType() == MouseEvent.MOUSE_DRAGGED) || - (e.getEventType() == MouseEvent.DRAG_DETECTED) || - (e.getEventType() == MouseEvent.MOUSE_ENTERED)) { + if ((e.getEventType() == MouseEvent.MOUSE_DRAGGED) || (e.getEventType() == MouseEvent.DRAG_DETECTED) + || (e.getEventType() == MouseEvent.MOUSE_ENTERED)) { e.consume(); - } else if (e.getEventType() == MouseEvent.MOUSE_PRESSED) { + } + else if (e.getEventType() == MouseEvent.MOUSE_PRESSED) { if (e.getClickCount() > 1) { e.consume(); } @@ -205,4 +215,5 @@ public BooleanProperty selectedProperty() { public StyleClassedTextArea getStyleClassedLabel() { return label; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/FieldValueCellViewModel.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/FieldValueCellViewModel.java index b94a80e0d32..43bef3359ac 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/FieldValueCellViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/FieldValueCellViewModel.java @@ -9,8 +9,11 @@ import javafx.scene.control.ToggleGroup; public class FieldValueCellViewModel { + private final StringProperty fieldValue = new SimpleStringProperty(); + private final BooleanProperty selected = new SimpleBooleanProperty(FieldValueCell.class, "selected"); + private final ObjectProperty toggleGroup = new SimpleObjectProperty<>(); public FieldValueCellViewModel(String text) { @@ -52,4 +55,5 @@ public ObjectProperty toggleGroupProperty() { public void setToggleGroup(ToggleGroup toggleGroup) { this.toggleGroup.set(toggleGroup); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/HeaderCell.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/HeaderCell.java index 56dd1de1178..7f7c5497454 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/HeaderCell.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/HeaderCell.java @@ -4,10 +4,13 @@ import javafx.scene.control.Label; /** - * A readonly cell used to display the header of the ThreeWayMerge UI at the top of the layout. - * */ + * A readonly cell used to display the header of the ThreeWayMerge UI at the top of the + * layout. + */ public class HeaderCell extends ThreeWayMergeCell { + public static final String DEFAULT_STYLE_CLASS = "merge-header-cell"; + private final Label label = new Label(); public HeaderCell(String text) { @@ -25,4 +28,5 @@ private void initializeLabel() { label.textProperty().bind(textProperty()); label.setPadding(new Insets(getPadding().getTop(), getPadding().getRight(), getPadding().getBottom(), 16)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/MergedFieldCell.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/MergedFieldCell.java index 050b85e70f4..4de547b1a4e 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/MergedFieldCell.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/MergedFieldCell.java @@ -11,6 +11,7 @@ import org.fxmisc.richtext.StyleClassedTextArea; public class MergedFieldCell extends ThreeWayMergeCell { + private static final String DEFAULT_STYLE_CLASS = "merged-field"; private final StyleClassedTextArea textArea = new StyleClassedTextArea(); @@ -27,10 +28,8 @@ private void initialize() { } private void initializeTextArea() { - BindingsHelper.bindBidirectional(textArea.textProperty(), - textProperty(), - textArea::replaceText, - textProperty()::setValue); + BindingsHelper.bindBidirectional(textArea.textProperty(), textProperty(), textArea::replaceText, + textProperty()::setValue); setAlignment(Pos.CENTER); textArea.setWrapText(true); @@ -43,4 +42,5 @@ private void initializeTextArea() { MergedFieldCell.this.fireEvent(e.copyFor(e.getSource(), MergedFieldCell.this)); }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/OpenExternalLinkAction.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/OpenExternalLinkAction.java index 43e00e1ecca..0cd42f5dff0 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/OpenExternalLinkAction.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/OpenExternalLinkAction.java @@ -12,9 +12,11 @@ import org.slf4j.LoggerFactory; /** - * A command for opening DOIs and URLs. This was created primarily for simplifying {@link FieldValueCell}. + * A command for opening DOIs and URLs. This was created primarily for simplifying + * {@link FieldValueCell}. */ public class OpenExternalLinkAction extends SimpleCommand { + private final Logger LOGGER = LoggerFactory.getLogger(OpenExternalLinkAction.class); private final ExternalApplicationsPreferences externalApplicationPreferences; @@ -31,19 +33,18 @@ public void execute() { try { if (DOI.isValid(urlOrDoi)) { NativeDesktop.openBrowser( - DOI.parse(urlOrDoi) - .flatMap(DOI::getExternalURI) - .map(URI::toString) - .orElse(""), + DOI.parse(urlOrDoi).flatMap(DOI::getExternalURI).map(URI::toString).orElse(""), externalApplicationPreferences ); - } else { - NativeDesktop.openBrowser(urlOrDoi, externalApplicationPreferences - ); } - } catch (IOException e) { + else { + NativeDesktop.openBrowser(urlOrDoi, externalApplicationPreferences); + } + } + catch (IOException e) { LOGGER.warn("Cannot open the given external link '{}'", urlOrDoi, e); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/ThreeWayMergeCell.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/ThreeWayMergeCell.java index 35e7ad405a1..02bbd73ef05 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/ThreeWayMergeCell.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/ThreeWayMergeCell.java @@ -7,9 +7,13 @@ import com.tobiasdiez.easybind.EasyBind; public abstract class ThreeWayMergeCell extends HBox { + public static final String ODD_PSEUDO_CLASS = "odd"; + public static final String EVEN_PSEUDO_CLASS = "even"; + public static final int HEADER_ROW = -1; + private static final String DEFAULT_STYLE_CLASS = "field-cell"; private final ThreeWayMergeCellViewModel viewModel; @@ -18,8 +22,10 @@ public ThreeWayMergeCell(String text, int rowIndex) { getStyleClass().add(DEFAULT_STYLE_CLASS); viewModel = new ThreeWayMergeCellViewModel(text, rowIndex); - EasyBind.subscribe(viewModel.oddProperty(), isOdd -> pseudoClassStateChanged(PseudoClass.getPseudoClass(ODD_PSEUDO_CLASS), isOdd)); - EasyBind.subscribe(viewModel.evenProperty(), isEven -> pseudoClassStateChanged(PseudoClass.getPseudoClass(EVEN_PSEUDO_CLASS), isEven)); + EasyBind.subscribe(viewModel.oddProperty(), + isOdd -> pseudoClassStateChanged(PseudoClass.getPseudoClass(ODD_PSEUDO_CLASS), isOdd)); + EasyBind.subscribe(viewModel.evenProperty(), + isEven -> pseudoClassStateChanged(PseudoClass.getPseudoClass(EVEN_PSEUDO_CLASS), isEven)); } public String getText() { @@ -38,4 +44,5 @@ public void setText(String text) { public String toString() { return "ThreeWayMergeCell [getText()=" + getText() + "]"; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/ThreeWayMergeCellViewModel.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/ThreeWayMergeCellViewModel.java index a40d2b8cbdc..58c5077276a 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/ThreeWayMergeCellViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/ThreeWayMergeCellViewModel.java @@ -10,8 +10,11 @@ import static org.jabref.gui.mergeentries.threewaymerge.cell.ThreeWayMergeCell.HEADER_ROW; public class ThreeWayMergeCellViewModel { + private final StringProperty text = new SimpleStringProperty(); + private final BooleanProperty odd = new SimpleBooleanProperty(ThreeWayMergeCell.class, "odd"); + private final BooleanProperty even = new SimpleBooleanProperty(ThreeWayMergeCell.class, "even"); public ThreeWayMergeCellViewModel(String text, int rowIndex) { @@ -19,7 +22,8 @@ public ThreeWayMergeCellViewModel(String text, int rowIndex) { if (rowIndex != HEADER_ROW) { if (rowIndex % 2 == 1) { odd.setValue(true); - } else { + } + else { even.setValue(true); } } @@ -64,4 +68,5 @@ public BooleanProperty evenProperty() { public void setEven(boolean even) { this.even.set(even); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/sidebuttons/InfoButton.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/sidebuttons/InfoButton.java index 121bd1ac5ae..588f8c4585a 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/sidebuttons/InfoButton.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/sidebuttons/InfoButton.java @@ -15,7 +15,9 @@ import com.tobiasdiez.easybind.EasyBind; public class InfoButton extends Button { + private final StringProperty infoMessage = new SimpleStringProperty(); + private final ActionFactory actionFactory = new ActionFactory(); private final Action mergeAction = new Action() { @@ -47,4 +49,5 @@ public void execute() { } }, this); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/sidebuttons/ToggleMergeUnmergeButton.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/sidebuttons/ToggleMergeUnmergeButton.java index 5b1122c999a..59bfed8ad5b 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/sidebuttons/ToggleMergeUnmergeButton.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/cell/sidebuttons/ToggleMergeUnmergeButton.java @@ -17,7 +17,9 @@ import org.jabref.model.entry.field.Field; public class ToggleMergeUnmergeButton extends Button { + private final ObjectProperty fieldState = new SimpleObjectProperty<>(FieldState.UNMERGED); + private final BooleanProperty canMerge = new SimpleBooleanProperty(Boolean.TRUE); private final ActionFactory actionFactory = new ActionFactory(); @@ -65,12 +67,13 @@ public boolean canMerge() { /** * Setting {@code canMerge} to {@code false} will disable the merge/unmerge button - * */ + */ public void setCanMerge(boolean value) { canMergeProperty().set(value); } private class ToggleMergeCommand extends SimpleCommand { + private final Action mergeAction = new Action() { @Override public Optional getIcon() { @@ -100,14 +103,19 @@ public void execute() { if (fieldStateProperty().get() == FieldState.MERGED) { setFieldState(FieldState.UNMERGED); configureMergeButton(); - } else { + } + else { setFieldState(FieldState.MERGED); configureUnmergeButton(); } } + } public enum FieldState { + MERGED, UNMERGED + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/diffhighlighter/DiffHighlighter.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/diffhighlighter/DiffHighlighter.java index 77d544fb3d6..462f395f389 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/diffhighlighter/DiffHighlighter.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/diffhighlighter/DiffHighlighter.java @@ -9,12 +9,15 @@ import org.fxmisc.richtext.StyleClassedTextArea; public abstract sealed class DiffHighlighter permits SplitDiffHighlighter, UnifiedDiffHighlighter { + protected final StyleClassedTextArea sourceTextview; + protected final StyleClassedTextArea targetTextview; protected DiffMethod diffMethod; - public DiffHighlighter(StyleClassedTextArea sourceTextview, StyleClassedTextArea targetTextview, DiffMethod diffMethod) { + public DiffHighlighter(StyleClassedTextArea sourceTextview, StyleClassedTextArea targetTextview, + DiffMethod diffMethod) { Objects.requireNonNull(sourceTextview, "source text view MUST NOT be null."); Objects.requireNonNull(targetTextview, "target text view MUST NOT be null."); @@ -42,6 +45,7 @@ public String getSeparator() { } public enum BasicDiffMethod implements DiffMethod { + WORDS(" "), CHARS(""), COMMA(","); private final String separator; @@ -54,6 +58,7 @@ public enum BasicDiffMethod implements DiffMethod { public String separator() { return separator; } + } protected String join(List stringList) { @@ -61,12 +66,12 @@ protected String join(List stringList) { } enum ChangeType { + ADDITION, DELETION, CHANGE_DELETION + } - record Change( - int position, - int spanSize, - ChangeType type) { + record Change(int position, int spanSize, ChangeType type) { } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/diffhighlighter/SplitDiffHighlighter.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/diffhighlighter/SplitDiffHighlighter.java index e6285f812cd..2050c5646bc 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/diffhighlighter/SplitDiffHighlighter.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/diffhighlighter/SplitDiffHighlighter.java @@ -9,12 +9,14 @@ import org.fxmisc.richtext.StyleClassedTextArea; /** - * A diff highlighter in which changes are split between source and target text view. - * They are represented by an addition in the target text view and deletion in the source text view. + * A diff highlighter in which changes are split between source and target text view. They + * are represented by an addition in the target text view and deletion in the source text + * view. */ public final class SplitDiffHighlighter extends DiffHighlighter { - public SplitDiffHighlighter(StyleClassedTextArea sourceTextview, StyleClassedTextArea targetTextview, DiffMethod diffMethod) { + public SplitDiffHighlighter(StyleClassedTextArea sourceTextview, StyleClassedTextArea targetTextview, + DiffMethod diffMethod) { super(sourceTextview, targetTextview, diffMethod); } @@ -38,27 +40,29 @@ public void highlight() { List affectedTokensInSource = delta.getSource().getLines(); List affectedTokensInTarget = delta.getTarget().getLines(); int joinedSourceTokensLength = affectedTokensInSource.stream() - .map(String::length) - .reduce(Integer::sum) - .map(value -> value + (getSeparator().length() * (affectedTokensInSource.size() - 1))) - .orElse(0); + .map(String::length) + .reduce(Integer::sum) + .map(value -> value + (getSeparator().length() * (affectedTokensInSource.size() - 1))) + .orElse(0); int joinedTargetTokensLength = affectedTokensInTarget.stream() - .map(String::length) - .reduce(Integer::sum) - .map(value -> value + (getSeparator().length() * (affectedTokensInTarget.size() - 1))) - .orElse(0); + .map(String::length) + .reduce(Integer::sum) + .map(value -> value + (getSeparator().length() * (affectedTokensInTarget.size() - 1))) + .orElse(0); int affectedSourceTokensPositionInText = getPositionInText(affectedSourceTokensPosition, sourceTokens); int affectedTargetTokensPositionInText = getPositionInText(affectedTargetTokensPosition, targetTokens); switch (delta.getType()) { case CHANGE -> { - sourceTextview.setStyleClass(affectedSourceTokensPositionInText, affectedSourceTokensPositionInText + joinedSourceTokensLength, "deletion"); - targetTextview.setStyleClass(affectedTargetTokensPositionInText, affectedTargetTokensPositionInText + joinedTargetTokensLength, "updated"); + sourceTextview.setStyleClass(affectedSourceTokensPositionInText, + affectedSourceTokensPositionInText + joinedSourceTokensLength, "deletion"); + targetTextview.setStyleClass(affectedTargetTokensPositionInText, + affectedTargetTokensPositionInText + joinedTargetTokensLength, "updated"); } - case DELETE -> - sourceTextview.setStyleClass(affectedSourceTokensPositionInText, affectedSourceTokensPositionInText + joinedSourceTokensLength, "deletion"); - case INSERT -> - targetTextview.setStyleClass(affectedTargetTokensPositionInText, affectedTargetTokensPositionInText + joinedTargetTokensLength, "addition"); + case DELETE -> sourceTextview.setStyleClass(affectedSourceTokensPositionInText, + affectedSourceTokensPositionInText + joinedSourceTokensLength, "deletion"); + case INSERT -> targetTextview.setStyleClass(affectedTargetTokensPositionInText, + affectedTargetTokensPositionInText + joinedTargetTokensLength, "addition"); } } } @@ -66,11 +70,15 @@ public void highlight() { public int getPositionInText(int positionInTokenList, List tokenList) { if (positionInTokenList == 0) { return 0; - } else { - return tokenList.stream().limit(positionInTokenList).map(String::length) - .reduce(Integer::sum) - .map(value -> value + (getSeparator().length() * positionInTokenList)) - .orElse(0); + } + else { + return tokenList.stream() + .limit(positionInTokenList) + .map(String::length) + .reduce(Integer::sum) + .map(value -> value + (getSeparator().length() * positionInTokenList)) + .orElse(0); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/diffhighlighter/UnifiedDiffHighlighter.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/diffhighlighter/UnifiedDiffHighlighter.java index cdc8c6f5245..4f584c4acb6 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/diffhighlighter/UnifiedDiffHighlighter.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/diffhighlighter/UnifiedDiffHighlighter.java @@ -12,12 +12,14 @@ import org.fxmisc.richtext.StyleClassedTextArea; /** - * A diff highlighter in which differences of type {@link DeltaType#CHANGE} are unified and represented by an insertion - * and deletion in the target text view. Normal addition and deletion are kept as they are. + * A diff highlighter in which differences of type {@link DeltaType#CHANGE} are unified + * and represented by an insertion and deletion in the target text view. Normal addition + * and deletion are kept as they are. */ public final class UnifiedDiffHighlighter extends DiffHighlighter { - public UnifiedDiffHighlighter(StyleClassedTextArea sourceTextview, StyleClassedTextArea targetTextview, DiffMethod diffMethod) { + public UnifiedDiffHighlighter(StyleClassedTextArea sourceTextview, StyleClassedTextArea targetTextview, + DiffMethod diffMethod) { super(sourceTextview, targetTextview, diffMethod); } @@ -62,7 +64,8 @@ public void highlight() { } case INSERT -> { int insertionPoint = delta.getTarget().getPosition() + deletionCount; - changeList.add(new Change(insertionPoint, delta.getTarget().getLines().size(), ChangeType.ADDITION)); + changeList + .add(new Change(insertionPoint, delta.getTarget().getLines().size(), ChangeType.ADDITION)); } } } @@ -74,20 +77,25 @@ public void highlight() { Optional changeAtPosition = findChange(position, changeList); if (changeAtPosition.isEmpty()) { appendToTextArea(targetTextview, getSeparator() + word, "unchanged"); - } else { + } + else { Change change = changeAtPosition.get(); - List changeWords = unifiedWords.subList(change.position(), change.position() + change.spanSize()); + List changeWords = unifiedWords.subList(change.position(), + change.position() + change.spanSize()); if (change.type() == ChangeType.DELETION) { appendToTextArea(targetTextview, getSeparator() + join(changeWords), "deletion"); - } else if (change.type() == ChangeType.ADDITION) { + } + else if (change.type() == ChangeType.ADDITION) { if (changeInProgress) { appendToTextArea(targetTextview, join(changeWords), "addition"); changeInProgress = false; - } else { + } + else { appendToTextArea(targetTextview, getSeparator() + join(changeWords), "addition"); } - } else if (change.type() == ChangeType.CHANGE_DELETION) { + } + else if (change.type() == ChangeType.CHANGE_DELETION) { appendToTextArea(targetTextview, getSeparator() + join(changeWords), "deletion"); changeInProgress = true; } @@ -108,7 +116,8 @@ private void appendToTextArea(StyleClassedTextArea textArea, String text, String if (text.startsWith(getSeparator())) { textArea.append(getSeparator(), "unchanged"); textArea.append(text.substring(getSeparator().length()), styleClass); - } else { + } + else { textArea.append(text, styleClass); } } @@ -116,4 +125,5 @@ private void appendToTextArea(StyleClassedTextArea textArea, String text, String private Optional findChange(int position, List changeList) { return changeList.stream().filter(change -> change.position() == position).findAny(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/CommentMerger.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/CommentMerger.java index cccb75db27f..3d0c9b82f0e 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/CommentMerger.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/CommentMerger.java @@ -5,10 +5,12 @@ /** * A merger for the {@link StandardField#COMMENT} field - * */ + */ public class CommentMerger implements FieldMerger { + @Override public String merge(String fieldValueA, String fieldValueB) { return fieldValueA + OS.NEWLINE + fieldValueB; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/FieldMerger.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/FieldMerger.java index 7dfdb077341..18c8f6d52c1 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/FieldMerger.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/FieldMerger.java @@ -1,9 +1,12 @@ package org.jabref.gui.mergeentries.threewaymerge.fieldsmerger; /** - * This class is responsible for taking two values for some field and merging them to into one value - * */ + * This class is responsible for taking two values for some field and merging them to into + * one value + */ @FunctionalInterface public interface FieldMerger { + String merge(String fieldValueA, String fieldValueB); + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/FieldMergerFactory.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/FieldMergerFactory.java index 9af2f85be0c..979f1984cc1 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/FieldMergerFactory.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/FieldMergerFactory.java @@ -5,6 +5,7 @@ import org.jabref.model.entry.field.StandardField; public class FieldMergerFactory { + private final BibEntryPreferences bibEntryPreferences; public FieldMergerFactory(BibEntryPreferences bibEntryPreferences) { @@ -18,11 +19,14 @@ public FieldMerger create(Field field) { case StandardField.COMMENT -> new CommentMerger(); case StandardField.FILE -> new FileMerger(); case null -> throw new IllegalArgumentException("Field must not be null"); - default -> throw new IllegalArgumentException("No implementation found for merging the given field: " + field.getDisplayName()); + default -> throw new IllegalArgumentException( + "No implementation found for merging the given field: " + field.getDisplayName()); }; } public static boolean canMerge(Field field) { - return field == StandardField.GROUPS || field == StandardField.KEYWORDS || field == StandardField.COMMENT || field == StandardField.FILE; + return field == StandardField.GROUPS || field == StandardField.KEYWORDS || field == StandardField.COMMENT + || field == StandardField.FILE; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/FileMerger.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/FileMerger.java index 7e5f075f6cb..902db5e92d6 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/FileMerger.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/FileMerger.java @@ -10,17 +10,21 @@ /** * A merger for the {@link StandardField#FILE} field - * */ + */ public class FileMerger implements FieldMerger { + @Override public String merge(String filesA, String filesB) { if (StringUtil.isBlank(filesA + filesB)) { return ""; - } else if (StringUtil.isBlank(filesA)) { + } + else if (StringUtil.isBlank(filesA)) { return filesB; - } else if (StringUtil.isBlank(filesB)) { + } + else if (StringUtil.isBlank(filesB)) { return filesA; - } else { + } + else { List linkedFilesA = FileFieldParser.parse(filesA); List linkedFilesB = FileFieldParser.parse(filesB); @@ -28,4 +32,5 @@ public String merge(String filesA, String filesB) { return FileFieldWriter.getStringRepresentation(linkedFilesA); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/GroupMerger.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/GroupMerger.java index 2aa182c286a..d5079d281f2 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/GroupMerger.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/GroupMerger.java @@ -11,7 +11,7 @@ /** * A merger for the {@link StandardField#GROUPS} field - * */ + */ public class GroupMerger implements FieldMerger { private final @NonNull BibEntryPreferences bibEntryPreferences; @@ -26,12 +26,16 @@ public String merge(String groupsA, String groupsB) { if (StringUtil.isBlank(groupsA) && StringUtil.isBlank(groupsB)) { return ""; - } else if (StringUtil.isBlank(groupsA)) { + } + else if (StringUtil.isBlank(groupsA)) { return groupsB; - } else if (StringUtil.isBlank(groupsB)) { + } + else if (StringUtil.isBlank(groupsB)) { return groupsA; - } else { + } + else { return KeywordList.merge(groupsA, groupsB, delimiter).getAsString(delimiter); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/KeywordMerger.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/KeywordMerger.java index f47190ef1b5..765580c7b5e 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/KeywordMerger.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/fieldsmerger/KeywordMerger.java @@ -8,8 +8,9 @@ /** * A merger for the {@link StandardField#KEYWORDS} field - * */ + */ public class KeywordMerger implements FieldMerger { + private final BibEntryPreferences bibEntryPreferences; public KeywordMerger(BibEntryPreferences bibEntryPreferences) { @@ -22,4 +23,5 @@ public String merge(String keywordsA, String keywordsB) { Character delimiter = bibEntryPreferences.getKeywordSeparator(); return KeywordList.merge(keywordsA, keywordsB, delimiter).getAsString(delimiter); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/toolbar/ThreeWayMergeToolbar.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/toolbar/ThreeWayMergeToolbar.java index 74cc54a255d..b75a19280f2 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/toolbar/ThreeWayMergeToolbar.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/threewaymerge/toolbar/ThreeWayMergeToolbar.java @@ -27,6 +27,7 @@ import jakarta.inject.Inject; public class ThreeWayMergeToolbar extends AnchorPane { + @FXML private RadioButton highlightCharactersRadioButtons; @@ -58,19 +59,21 @@ public class ThreeWayMergeToolbar extends AnchorPane { private GuiPreferences preferences; private final ObjectProperty diffHighlightingMethod = new SimpleObjectProperty<>(); + private final BooleanProperty onlyShowChangedFields = new SimpleBooleanProperty(); + private final BooleanProperty applyToAllEntries = new SimpleBooleanProperty(); + private EasyBinding showDiff; public ThreeWayMergeToolbar() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @FXML public void initialize() { - showDiff = EasyBind.map(plainTextOrDiffComboBox.valueProperty(), plainTextOrDiff -> plainTextOrDiff == PlainTextOrDiff.Diff); + showDiff = EasyBind.map(plainTextOrDiffComboBox.valueProperty(), + plainTextOrDiff -> plainTextOrDiff == PlainTextOrDiff.Diff); plainTextOrDiffComboBox.getItems().addAll(PlainTextOrDiff.values()); plainTextOrDiffComboBox.setConverter(new StringConverter<>() { @@ -106,15 +109,18 @@ public DiffView fromString(String string) { diffHighlightingMethodToggleGroup.selectedToggleProperty().addListener(observable -> { if (diffHighlightingMethodToggleGroup.getSelectedToggle().equals(highlightCharactersRadioButtons)) { diffHighlightingMethod.set(BasicDiffMethod.CHARS); - } else { + } + else { diffHighlightingMethod.set(BasicDiffMethod.WORDS); } }); - onlyShowChangedFieldsCheck.selectedProperty().bindBidirectional(preferences.getMergeDialogPreferences().mergeShowChangedFieldOnlyProperty()); + onlyShowChangedFieldsCheck.selectedProperty() + .bindBidirectional(preferences.getMergeDialogPreferences().mergeShowChangedFieldOnlyProperty()); onlyShowChangedFields.bind(onlyShowChangedFieldsCheck.selectedProperty()); - applyToAllEntriesCheck.selectedProperty().bindBidirectional(preferences.getMergeDialogPreferences().mergeApplyToAllEntriesProperty()); + applyToAllEntriesCheck.selectedProperty() + .bindBidirectional(preferences.getMergeDialogPreferences().mergeApplyToAllEntriesProperty()); applyToAllEntries.bind(applyToAllEntriesCheck.selectedProperty()); loadSavedConfiguration(); @@ -123,20 +129,26 @@ public DiffView fromString(String string) { private void loadSavedConfiguration() { MergeDialogPreferences mergeDialogPreferences = preferences.getMergeDialogPreferences(); - PlainTextOrDiff plainTextOrDiffPreference = mergeDialogPreferences.getMergeShouldShowDiff() ? PlainTextOrDiff.Diff : PlainTextOrDiff.PLAIN_TEXT; + PlainTextOrDiff plainTextOrDiffPreference = mergeDialogPreferences.getMergeShouldShowDiff() + ? PlainTextOrDiff.Diff : PlainTextOrDiff.PLAIN_TEXT; plainTextOrDiffComboBox.getSelectionModel().select(plainTextOrDiffPreference); - DiffView diffViewPreference = mergeDialogPreferences.getMergeShouldShowUnifiedDiff() ? DiffView.UNIFIED : DiffView.SPLIT; + DiffView diffViewPreference = mergeDialogPreferences.getMergeShouldShowUnifiedDiff() ? DiffView.UNIFIED + : DiffView.SPLIT; diffViewComboBox.getSelectionModel().select(diffViewPreference); - diffHighlightingMethodToggleGroup.selectToggle(mergeDialogPreferences.getMergeHighlightWords() ? highlightWordsRadioButton : highlightCharactersRadioButtons); + diffHighlightingMethodToggleGroup.selectToggle(mergeDialogPreferences.getMergeHighlightWords() + ? highlightWordsRadioButton : highlightCharactersRadioButtons); } public void saveToolbarConfiguration() { - preferences.getMergeDialogPreferences().setMergeShouldShowDiff(plainTextOrDiffComboBox.getValue() == PlainTextOrDiff.Diff); - preferences.getMergeDialogPreferences().setMergeShouldShowUnifiedDiff(diffViewComboBox.getValue() == DiffView.UNIFIED); + preferences.getMergeDialogPreferences() + .setMergeShouldShowDiff(plainTextOrDiffComboBox.getValue() == PlainTextOrDiff.Diff); + preferences.getMergeDialogPreferences() + .setMergeShouldShowUnifiedDiff(diffViewComboBox.getValue() == DiffView.UNIFIED); - boolean highlightWordsRadioButtonValue = diffHighlightingMethodToggleGroup.getSelectedToggle().equals(highlightWordsRadioButton); + boolean highlightWordsRadioButtonValue = diffHighlightingMethodToggleGroup.getSelectedToggle() + .equals(highlightWordsRadioButton); preferences.getMergeDialogPreferences().setMergeHighlightWords(highlightWordsRadioButtonValue); } @@ -172,8 +184,8 @@ public boolean shouldHideEqualFields() { * Convenience method used to disable diff related views when diff is not selected. * *

    - * This method is required because {@link EasyBinding} class doesn't have a method to invert a boolean property, - * like {@link BooleanExpression#not()} + * This method is required because {@link EasyBinding} class doesn't have a method to + * invert a boolean property, like {@link BooleanExpression#not()} *

    */ public EasyBinding notShowDiffProperty() { @@ -205,6 +217,7 @@ public void setOnSelectRightEntryValuesButtonClicked(Runnable onClick) { } public enum PlainTextOrDiff { + PLAIN_TEXT(Localization.lang("Plain Text")), Diff(Localization.lang("Show Diff")); private final String value; @@ -224,11 +237,13 @@ public String getValue() { public static PlainTextOrDiff fromString(String str) { return Enum.valueOf(PlainTextOrDiff.class, str); } + } public enum DiffView { - UNIFIED(Localization.lang("Unified View")), - SPLIT(Localization.lang("Split View")); + + UNIFIED(Localization.lang("Unified View")), SPLIT(Localization.lang("Split View")); + private final String value; DiffView(String value) { @@ -246,5 +261,7 @@ public String getValue() { public static DiffView fromString(String str) { return Enum.valueOf(DiffView.class, str); } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryDialogTab.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryDialogTab.java index f603d084bb9..1d02d15f0e5 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryDialogTab.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryDialogTab.java @@ -1,8 +1,7 @@ package org.jabref.gui.newentry; public enum NewEntryDialogTab { - CHOOSE_ENTRY_TYPE, - ENTER_IDENTIFIER, - INTERPRET_CITATIONS, - SPECIFY_BIBTEX, + + CHOOSE_ENTRY_TYPE, ENTER_IDENTIFIER, INTERPRET_CITATIONS, SPECIFY_BIBTEX, + } diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryPreferences.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryPreferences.java index 99a75ab7904..dbfb936d1f5 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryPreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryPreferences.java @@ -10,23 +10,26 @@ import org.jabref.model.entry.types.EntryType; public class NewEntryPreferences { + private final ObjectProperty latestApproach; + private final BooleanProperty typesRecommendedExpanded; + private final BooleanProperty typesOtherExpanded; + private final BooleanProperty typesCustomExpanded; + private final ObjectProperty latestImmediateType; + private final BooleanProperty idLookupGuessing; + private final StringProperty latestIdFetcherName; + private final StringProperty latestInterpretParserName; - public NewEntryPreferences(NewEntryDialogTab approach, - boolean expandRecommended, - boolean expandOther, - boolean expandCustom, - EntryType immediateType, - boolean idLookupGuessing, - String idFetcherName, - String interpretParserName) { + public NewEntryPreferences(NewEntryDialogTab approach, boolean expandRecommended, boolean expandOther, + boolean expandCustom, EntryType immediateType, boolean idLookupGuessing, String idFetcherName, + String interpretParserName) { this.latestApproach = new SimpleObjectProperty<>(approach); this.typesRecommendedExpanded = new SimpleBooleanProperty(expandRecommended); this.typesOtherExpanded = new SimpleBooleanProperty(expandOther); @@ -132,4 +135,5 @@ public void setLatestInterpretParser(String interpretParserName) { public StringProperty latestInterpretParserProperty() { return latestInterpretParserName; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java index a6173daff6a..a0ee8dfea66 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java @@ -74,58 +74,114 @@ import jakarta.inject.Inject; public class NewEntryView extends BaseDialog { + private static final String BIBTEX_REGEX = "^@([A-Za-z]+)\\{,"; + private static final String LINE_BREAK = "\n"; private NewEntryViewModel viewModel; private final NewEntryDialogTab initialApproach; + private NewEntryDialogTab currentApproach; private final GuiPreferences guiPreferences; + private final NewEntryPreferences preferences; + private final LibraryTab libraryTab; + private final DialogService dialogService; - @Inject private StateManager stateManager; - @Inject private TaskExecutor taskExecutor; - @Inject private AiService aiService; - @Inject private FileUpdateMonitor fileUpdateMonitor; + + @Inject + private StateManager stateManager; + + @Inject + private TaskExecutor taskExecutor; + + @Inject + private AiService aiService; + + @Inject + private FileUpdateMonitor fileUpdateMonitor; private final ControlsFxVisualizer visualizer; - @FXML private ButtonType generateButtonType; + @FXML + private ButtonType generateButtonType; + private Button generateButton; - @FXML private TabPane tabs; - @FXML private Tab tabAddEntry; - @FXML private Tab tabLookupIdentifier; - @FXML private Tab tabInterpretCitations; - @FXML private Tab tabSpecifyBibtex; - - @FXML private TitledPane entryRecommendedTitle; - @FXML private TilePane entryRecommended; - @FXML private TitledPane entryOtherTitle; - @FXML private TilePane entryOther; - @FXML private TitledPane entryCustomTitle; - @FXML private TilePane entryCustom; - - @FXML private TextField idText; - @FXML private Tooltip idTextTooltip; - @FXML private Hyperlink idJumpLink; - @FXML private RadioButton idLookupGuess; - @FXML private RadioButton idLookupSpecify; - @FXML private ComboBox idFetcher; - @FXML private Label idErrorInvalidText; - @FXML private Label idErrorInvalidFetcher; - - @FXML private TextArea interpretText; - @FXML private ComboBox interpretParser; - - @FXML private TextArea bibtexText; + @FXML + private TabPane tabs; + + @FXML + private Tab tabAddEntry; + + @FXML + private Tab tabLookupIdentifier; + + @FXML + private Tab tabInterpretCitations; + + @FXML + private Tab tabSpecifyBibtex; + + @FXML + private TitledPane entryRecommendedTitle; + + @FXML + private TilePane entryRecommended; + + @FXML + private TitledPane entryOtherTitle; + + @FXML + private TilePane entryOther; + + @FXML + private TitledPane entryCustomTitle; + + @FXML + private TilePane entryCustom; + + @FXML + private TextField idText; + + @FXML + private Tooltip idTextTooltip; + + @FXML + private Hyperlink idJumpLink; + + @FXML + private RadioButton idLookupGuess; + + @FXML + private RadioButton idLookupSpecify; + + @FXML + private ComboBox idFetcher; + + @FXML + private Label idErrorInvalidText; + + @FXML + private Label idErrorInvalidFetcher; + + @FXML + private TextArea interpretText; + + @FXML + private ComboBox interpretParser; + + @FXML + private TextArea bibtexText; private BibEntry result; - public NewEntryView(NewEntryDialogTab initialApproach, GuiPreferences preferences, LibraryTab libraryTab, DialogService dialogService) { + public NewEntryView(NewEntryDialogTab initialApproach, GuiPreferences preferences, LibraryTab libraryTab, + DialogService dialogService) { this.initialApproach = initialApproach; this.currentApproach = initialApproach; @@ -167,12 +223,15 @@ private void finalizeTabs() { approach = NewEntryDialogTab.ENTER_IDENTIFIER; interpretText.setText(clipboardText); interpretText.selectAll(); - } else if (clipboardText.split(LINE_BREAK)[0].matches(BIBTEX_REGEX)) { + } + else if (clipboardText.split(LINE_BREAK)[0].matches(BIBTEX_REGEX)) { approach = NewEntryDialogTab.SPECIFY_BIBTEX; - } else { + } + else { approach = preferences.getLatestApproach(); } - } else { + } + else { approach = preferences.getLatestApproach(); } } @@ -204,17 +263,16 @@ private void finalizeTabs() { @FXML public void initialize() { - viewModel = new NewEntryViewModel(guiPreferences, libraryTab, dialogService, stateManager, (UiTaskExecutor) taskExecutor, aiService, fileUpdateMonitor); + viewModel = new NewEntryViewModel(guiPreferences, libraryTab, dialogService, stateManager, + (UiTaskExecutor) taskExecutor, aiService, fileUpdateMonitor); visualizer.setDecoration(new IconValidationDecorator()); - EasyBind.subscribe( - viewModel.executedSuccessfullyProperty(), - succeeded -> { - if (succeeded) { - onSuccessfulExecution(); - } - }); + EasyBind.subscribe(viewModel.executedSuccessfullyProperty(), succeeded -> { + if (succeeded) { + onSuccessfulExecution(); + } + }); initializeAddEntry(); initializeLookupIdentifier(); @@ -245,7 +303,8 @@ private void initializeAddEntry() { otherEntries.removeAll(recommendedEntries); otherEntries.addAll(BiblatexSoftwareEntryTypeDefinitions.ALL); otherEntries.addAll(BiblatexAPAEntryTypeDefinitions.ALL); - } else { + } + else { recommendedEntries = BibtexEntryTypeDefinitions.RECOMMENDED; otherEntries = new ArrayList<>(BiblatexEntryTypeDefinitions.ALL); otherEntries.removeAll(recommendedEntries); @@ -255,19 +314,23 @@ private void initializeAddEntry() { addEntriesToPane(entryOther, otherEntries); final BibEntryTypesManager entryTypesManager = Injector.instantiateModelOrService(BibEntryTypesManager.class); - final BibDatabaseMode customTypesDatabaseMode = isBiblatexMode ? BibDatabaseMode.BIBLATEX : BibDatabaseMode.BIBTEX; + final BibDatabaseMode customTypesDatabaseMode = isBiblatexMode ? BibDatabaseMode.BIBLATEX + : BibDatabaseMode.BIBTEX; final List customEntries = entryTypesManager.getAllCustomTypes(customTypesDatabaseMode); if (customEntries.isEmpty()) { entryCustomTitle.setVisible(false); - } else { + } + else { addEntriesToPane(entryCustom, customEntries); } } private void initializeLookupIdentifier() { - // TODO: It would be nice if this was a `TextArea`, so that users could enter multiple IDs at once. The view - // model would then iterate through all non-blank lines, passing each of them through the specified lookup - // method (each automatically independently, or all through the same fetcher). + // TODO: It would be nice if this was a `TextArea`, so that users could enter + // multiple IDs at once. The view + // model would then iterate through all non-blank lines, passing each of them + // through the specified lookup + // method (each automatically independently, or all through the same fetcher). idText.setPromptText(Localization.lang("Enter the reference identifier to search for.")); idText.textProperty().bindBidirectional(viewModel.idTextProperty()); @@ -277,7 +340,8 @@ private void initializeLookupIdentifier() { if (preferences.getIdLookupGuessing()) { idLookupGuess.selectedProperty().set(true); - } else { + } + else { idLookupSpecify.selectedProperty().set(true); } @@ -289,7 +353,8 @@ private void initializeLookupIdentifier() { viewModel.duplicateDoiValidatorStatus().validProperty().addListener((_, _, isValid) -> { if (isValid) { Tooltip.install(idText, idTextTooltip); - } else { + } + else { Tooltip.uninstall(idText, idTextTooltip); } }); @@ -302,7 +367,8 @@ private void initializeLookupIdentifier() { idLookupSpecify.setSelected(true); fetcherForIdentifier(id).ifPresent(idFetcher::setValue); }); - } else { + } + else { Platform.runLater(() -> idLookupGuess.setSelected(true)); } @@ -323,7 +389,8 @@ private void initializeLookupIdentifier() { idJumpLink.visibleProperty().bind(viewModel.duplicateDoiValidatorStatus().validProperty().not()); idErrorInvalidText.visibleProperty().bind(viewModel.idTextValidatorProperty().not()); idErrorInvalidText.managedProperty().bind(viewModel.idTextValidatorProperty().not()); - idErrorInvalidFetcher.visibleProperty().bind(idLookupSpecify.selectedProperty().and(viewModel.idFetcherValidatorProperty().not())); + idErrorInvalidFetcher.visibleProperty() + .bind(idLookupSpecify.selectedProperty().and(viewModel.idFetcherValidatorProperty().not())); idJumpLink.setOnAction(_ -> libraryTab.showAndEdit(viewModel.getDuplicateEntry())); @@ -341,22 +408,26 @@ private void initializeInterpretCitations() { } interpretParser.itemsProperty().bind(viewModel.interpretParsersProperty()); - new ViewModelListCellFactory().withText(PlainCitationParserChoice::getLocalizedName).install(interpretParser); + new ViewModelListCellFactory().withText(PlainCitationParserChoice::getLocalizedName) + .install(interpretParser); interpretParser.valueProperty().bindBidirectional(viewModel.interpretParserProperty()); - PlainCitationParserChoice initialParser = parserFromName(preferences.getLatestInterpretParser(), interpretParser.getItems()); + PlainCitationParserChoice initialParser = parserFromName(preferences.getLatestInterpretParser(), + interpretParser.getItems()); if (initialParser == null) { final PlainCitationParserChoice defaultParser = PlainCitationParserChoice.RULE_BASED; initialParser = parserFromName(defaultParser.getLocalizedName(), interpretParser.getItems()); } interpretParser.setValue(initialParser); - interpretParser.setOnAction(_ -> preferences.setLatestInterpretParser(interpretParser.getValue().getLocalizedName())); + interpretParser + .setOnAction(_ -> preferences.setLatestInterpretParser(interpretParser.getValue().getLocalizedName())); } private void initializeSpecifyBibTeX() { bibtexText.textProperty().bindBidirectional(viewModel.bibtexTextProperty()); final String clipboardText = ClipBoardManager.getContents().trim(); if (!StringUtil.isBlank(clipboardText)) { - // :TODO: Better validation would be nice here, so clipboard text is only copied over if it matches a + // :TODO: Better validation would be nice here, so clipboard text is only + // copied over if it matches a // supported Bib(La)Tex source format. bibtexText.setText(clipboardText); bibtexText.selectAll(); @@ -393,7 +464,8 @@ private void switchLookupIdentifier() { } if (generateButton != null) { - generateButton.disableProperty().bind(idErrorInvalidText.visibleProperty().or(idErrorInvalidFetcher.visibleProperty())); + generateButton.disableProperty() + .bind(idErrorInvalidText.visibleProperty().or(idErrorInvalidFetcher.visibleProperty())); generateButton.setText("Search"); } } @@ -449,8 +521,10 @@ private void onSuccessfulExecution() { } private void execute() { - // :TODO: These button text changes aren't actually visible, due to the UI thread not being able to perform the - // update before the button text is reset. The `viewModel.execute*()` and `switch*()` calls could be wrapped in + // :TODO: These button text changes aren't actually visible, due to the UI thread + // not being able to perform the + // update before the button text is reset. The `viewModel.execute*()` and + // `switch*()` calls could be wrapped in // a `Platform.runLater(...)` which would probably fix this. switch (currentApproach) { case NewEntryDialogTab.CHOOSE_ENTRY_TYPE: @@ -510,41 +584,64 @@ private static String descriptionOfEntryType(EntryType type) { } private static String descriptionOfStandardEntryType(StandardEntryType type) { - // These descriptions are taken from subsection 2.1 of the biblatex package documentation. - // Biblatex is a superset of bibtex, with more elaborate descriptions, so its documentation is preferred. - // See [https://mirrors.ibiblio.org/pub/mirrors/CTAN/macros/latex/contrib/biblatex/doc/biblatex.pdf]. + // These descriptions are taken from subsection 2.1 of the biblatex package + // documentation. + // Biblatex is a superset of bibtex, with more elaborate descriptions, so its + // documentation is preferred. + // See + // [https://mirrors.ibiblio.org/pub/mirrors/CTAN/macros/latex/contrib/biblatex/doc/biblatex.pdf]. return switch (type) { - case Article -> Localization.lang("An article in a journal, magazine, newspaper, or other periodical which forms a self-contained unit with its own title."); - case Book -> Localization.lang("A single-volume book with one or more authors where the authors share credit for the work as a whole."); + case Article -> Localization.lang( + "An article in a journal, magazine, newspaper, or other periodical which forms a self-contained unit with its own title."); + case Book -> Localization.lang( + "A single-volume book with one or more authors where the authors share credit for the work as a whole."); case Booklet -> Localization.lang("A book-like work without a formal publisher or sponsoring institution."); - case Collection -> Localization.lang("A single-volume collection with multiple, self-contained contributions by distinct authors which have their own title. The work as a whole has no overall author but it will usually have an editor."); + case Collection -> Localization.lang( + "A single-volume collection with multiple, self-contained contributions by distinct authors which have their own title. The work as a whole has no overall author but it will usually have an editor."); case Conference -> Localization.lang("A legacy alias for \"InProceedings\"."); case InBook -> Localization.lang("A part of a book which forms a self-contained unit with its own title."); - case InCollection -> Localization.lang("A contribution to a collection which forms a self-contained unit with a distinct author and title."); + case InCollection -> Localization.lang( + "A contribution to a collection which forms a self-contained unit with a distinct author and title."); case InProceedings -> Localization.lang("An article in a conference proceedings."); case Manual -> Localization.lang("Technical or other documentation, not necessarily in printed form."); - case MastersThesis -> Localization.lang("Similar to \"Thesis\" except that the type field is optional and defaults to the localised term Master's thesis."); + case MastersThesis -> Localization.lang( + "Similar to \"Thesis\" except that the type field is optional and defaults to the localised term Master's thesis."); case Misc -> Localization.lang("A fallback type for entries which do not fit into any other category."); - case PhdThesis -> Localization.lang("Similar to \"Thesis\" except that the type field is optional and defaults to the localised term PhD thesis."); - case Proceedings -> Localization.lang("A single-volume conference proceedings. This type is very similar to \"Collection\"."); - case TechReport -> Localization.lang("Similar to \"Report\" except that the type field is optional and defaults to the localised term technical report."); - case Unpublished -> Localization.lang("A work with an author and a title which has not been formally published, such as a manuscript or the script of a talk."); - case BookInBook -> Localization.lang("This type is similar to \"InBook\" but intended for works originally published as a stand-alone book."); - case InReference -> Localization.lang("An article in a work of reference. This is a more specific variant of the generic \"InCollection\" entry type."); + case PhdThesis -> Localization.lang( + "Similar to \"Thesis\" except that the type field is optional and defaults to the localised term PhD thesis."); + case Proceedings -> Localization + .lang("A single-volume conference proceedings. This type is very similar to \"Collection\"."); + case TechReport -> Localization.lang( + "Similar to \"Report\" except that the type field is optional and defaults to the localised term technical report."); + case Unpublished -> Localization.lang( + "A work with an author and a title which has not been formally published, such as a manuscript or the script of a talk."); + case BookInBook -> Localization.lang( + "This type is similar to \"InBook\" but intended for works originally published as a stand-alone book."); + case InReference -> Localization.lang( + "An article in a work of reference. This is a more specific variant of the generic \"InCollection\" entry type."); case MvBook -> Localization.lang("A multi-volume \"Book\"."); case MvCollection -> Localization.lang("A multi-volume \"Collection\"."); case MvProceedings -> Localization.lang("A multi-volume \"Proceedings\" entry."); - case MvReference -> Localization.lang("A multi-volume \"Reference\" entry. The standard styles will treat this entry type as an alias for \"MvCollection\"."); - case Online -> Localization.lang("This entry type is intended for sources such as web sites which are intrinsically online resources."); - case Reference -> Localization.lang("A single-volume work of reference such as an encyclopedia or a dictionary."); - case Report -> Localization.lang("A technical report, research report, or white paper published by a university or some other institution."); - case Set -> Localization.lang("An entry set is a group of entries which are cited as a single reference and listed as a single item in the bibliography."); - case SuppBook -> Localization.lang("Supplemental material in a \"Book\". This type is provided for elements such as prefaces, introductions, forewords, afterwords, etc. which often have a generic title only."); + case MvReference -> Localization.lang( + "A multi-volume \"Reference\" entry. The standard styles will treat this entry type as an alias for \"MvCollection\"."); + case Online -> Localization.lang( + "This entry type is intended for sources such as web sites which are intrinsically online resources."); + case Reference -> + Localization.lang("A single-volume work of reference such as an encyclopedia or a dictionary."); + case Report -> Localization.lang( + "A technical report, research report, or white paper published by a university or some other institution."); + case Set -> Localization.lang( + "An entry set is a group of entries which are cited as a single reference and listed as a single item in the bibliography."); + case SuppBook -> Localization.lang( + "Supplemental material in a \"Book\". This type is provided for elements such as prefaces, introductions, forewords, afterwords, etc. which often have a generic title only."); case SuppCollection -> Localization.lang("Supplemental material in a \"Collection\"."); - case SuppPeriodical -> Localization.lang("Supplemental material in a \"Periodical\". This type may be useful when referring to items such as regular columns, obituaries, letters to the editor, etc. which only have a generic title."); - case Thesis -> Localization.lang("A thesis written for an educational institution to satisfy the requirements for a degree."); + case SuppPeriodical -> Localization.lang( + "Supplemental material in a \"Periodical\". This type may be useful when referring to items such as regular columns, obituaries, letters to the editor, etc. which only have a generic title."); + case Thesis -> Localization + .lang("A thesis written for an educational institution to satisfy the requirements for a degree."); case WWW -> Localization.lang("An alias for \"Online\", provided for jurabib compatibility."); - case Software -> Localization.lang("Computer software. The standard styles will treat this entry type as an alias for \"Misc\"."); + case Software -> Localization + .lang("Computer software. The standard styles will treat this entry type as an alias for \"Misc\"."); case Dataset -> Localization.lang("A data set or a similar collection of (mostly) raw data."); }; } @@ -558,7 +655,8 @@ private static IdBasedFetcher fetcherFromName(String fetcherName, List parsers) { + private static PlainCitationParserChoice parserFromName(String parserName, + List parsers) { for (PlainCitationParserChoice parser : parsers) { if (parser.getLocalizedName().equals(parserName)) { return parser; @@ -575,12 +673,9 @@ private Optional extractValidIdentifierFromClipboard() { if (identifier.isPresent()) { Identifier id = identifier.get(); boolean isValid = switch (id) { - case DOI doi -> - DOI.isValid(doi.asString()); - case ISBN isbn -> - isbn.isValid(); - default -> - true; + case DOI doi -> DOI.isValid(doi.asString()); + case ISBN isbn -> isbn.isValid(); + default -> true; }; if (isValid) { return Optional.of(id); @@ -593,14 +688,15 @@ private Optional extractValidIdentifierFromClipboard() { private Optional fetcherForIdentifier(Identifier id) { for (IdBasedFetcher fetcher : idFetcher.getItems()) { - if ((id instanceof DOI && fetcher instanceof DoiFetcher) || - (id instanceof ISBN && (fetcher instanceof IsbnFetcher) || - (id instanceof ArXivIdentifier && fetcher instanceof ArXivFetcher) || - (id instanceof RFC && fetcher instanceof RfcFetcher) || - (id instanceof SSRN && fetcher instanceof DoiFetcher))) { + if ((id instanceof DOI && fetcher instanceof DoiFetcher) + || (id instanceof ISBN && (fetcher instanceof IsbnFetcher) + || (id instanceof ArXivIdentifier && fetcher instanceof ArXivFetcher) + || (id instanceof RFC && fetcher instanceof RfcFetcher) + || (id instanceof SSRN && fetcher instanceof DoiFetcher))) { return Optional.of(fetcher); } } return Optional.empty(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryViewModel.java b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryViewModel.java index b3cb2d42ad2..ce77f227dcd 100644 --- a/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/newentry/NewEntryViewModel.java @@ -60,43 +60,60 @@ public class NewEntryViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(NewEntryViewModel.class); private final GuiPreferences preferences; + private final LibraryTab libraryTab; + private final DialogService dialogService; + private final StateManager stateManager; + private final UiTaskExecutor taskExecutor; + private final AiService aiService; + private final FileUpdateMonitor fileUpdateMonitor; private final BooleanProperty executing; + private final BooleanProperty executedSuccessfully; private final StringProperty idText; + private final Validator idTextValidator; + private final Validator duplicateDoiValidator; + private final ListProperty idFetchers; + private final ObjectProperty idFetcher; + private final Validator idFetcherValidator; + private Task> idLookupWorker; private final StringProperty interpretText; + private final Validator interpretTextValidator; + private final ListProperty interpretParsers; + private final ObjectProperty interpretParser; + private Task>> interpretWorker; private final StringProperty bibtexText; + private final Validator bibtexTextValidator; + private Task>> bibtexWorker; + private final Map doiCache; + private BibEntry duplicateEntry; - public NewEntryViewModel(GuiPreferences preferences, - LibraryTab libraryTab, - DialogService dialogService, - StateManager stateManager, - UiTaskExecutor taskExecutor, - AiService aiService, - FileUpdateMonitor fileUpdateMonitor) { + public NewEntryViewModel(GuiPreferences preferences, LibraryTab libraryTab, DialogService dialogService, + StateManager stateManager, UiTaskExecutor taskExecutor, AiService aiService, + FileUpdateMonitor fileUpdateMonitor) { this.preferences = preferences; this.libraryTab = libraryTab; this.dialogService = dialogService; @@ -110,26 +127,19 @@ public NewEntryViewModel(GuiPreferences preferences, doiCache = new HashMap<>(); idText = new SimpleStringProperty(); - idTextValidator = new FunctionBasedValidator<>( - idText, - StringUtil::isNotBlank, + idTextValidator = new FunctionBasedValidator<>(idText, StringUtil::isNotBlank, ValidationMessage.error(Localization.lang("You must specify an identifier."))); - duplicateDoiValidator = new FunctionBasedValidator<>( - idText, - input -> checkDOI(input).orElse(null)); + duplicateDoiValidator = new FunctionBasedValidator<>(idText, input -> checkDOI(input).orElse(null)); idFetchers = new SimpleListProperty<>(FXCollections.observableArrayList()); - idFetchers.addAll(WebFetchers.getIdBasedFetchers(preferences.getImportFormatPreferences(), preferences.getImporterPreferences())); + idFetchers.addAll(WebFetchers.getIdBasedFetchers(preferences.getImportFormatPreferences(), + preferences.getImporterPreferences())); idFetcher = new SimpleObjectProperty<>(); - idFetcherValidator = new FunctionBasedValidator<>( - idFetcher, - Objects::nonNull, + idFetcherValidator = new FunctionBasedValidator<>(idFetcher, Objects::nonNull, ValidationMessage.error(Localization.lang("You must select an identifier type."))); idLookupWorker = null; interpretText = new SimpleStringProperty(); - interpretTextValidator = new FunctionBasedValidator<>( - interpretText, - StringUtil::isNotBlank, + interpretTextValidator = new FunctionBasedValidator<>(interpretText, StringUtil::isNotBlank, ValidationMessage.error(Localization.lang("You must specify one (or more) citations."))); interpretParsers = new SimpleListProperty<>(FXCollections.observableArrayList()); interpretParsers.addAll(PlainCitationParserChoice.values()); @@ -137,9 +147,7 @@ public NewEntryViewModel(GuiPreferences preferences, interpretWorker = null; bibtexText = new SimpleStringProperty(); - bibtexTextValidator = new FunctionBasedValidator<>( - bibtexText, - StringUtil::isNotBlank, + bibtexTextValidator = new FunctionBasedValidator<>(bibtexText, StringUtil::isNotBlank, ValidationMessage.error(Localization.lang("You must specify a Bib(La)TeX source."))); bibtexWorker = null; } @@ -148,15 +156,13 @@ public void populateDOICache() { doiCache.clear(); Optional activeDatabase = stateManager.getActiveDatabase(); - activeDatabase.map(BibDatabaseContext::getEntries) - .ifPresent(entries -> { - entries.forEach(entry -> { - entry.getField(StandardField.DOI) - .ifPresent(doi -> { - doiCache.put(doi, entry); - }); - }); - }); + activeDatabase.map(BibDatabaseContext::getEntries).ifPresent(entries -> { + entries.forEach(entry -> { + entry.getField(StandardField.DOI).ifPresent(doi -> { + doiCache.put(doi, entry); + }); + }); + }); } public Optional checkDOI(String doiInput) { @@ -236,6 +242,7 @@ public ReadOnlyBooleanProperty bibtexTextValidatorProperty() { } private class WorkerLookupId extends Task> { + @Override protected Optional call() throws FetcherException { final String text = idText.getValue(); @@ -247,9 +254,11 @@ protected Optional call() throws FetcherException { return fetcher.performSearchById(text); } + } private class WorkerLookupTypedId extends Task> { + @Override protected Optional call() throws FetcherException { final String text = idText.getValue(); @@ -262,6 +271,7 @@ protected Optional call() throws FetcherException { return fetcher.performSearchById(text); } + } public void executeLookupIdentifier(boolean searchComposite) { @@ -270,7 +280,8 @@ public void executeLookupIdentifier(boolean searchComposite) { cancel(); if (searchComposite) { idLookupWorker = new WorkerLookupId(); - } else { + } + else { idLookupWorker = new WorkerLookupTypedId(); } @@ -283,34 +294,26 @@ public void executeLookupIdentifier(boolean searchComposite) { final String dialogTitle = Localization.lang("Failed to lookup identifier"); if (exception instanceof FetcherClientException) { - dialogService.showInformationDialogAndWait( - dialogTitle, - Localization.lang( - "Bibliographic data could not be retrieved.\n" + - "This is likely due to an issue with your input, or your network connection.\n" + - "Check your provided identifier (and identifier type), and try again.\n" + - "%0", - exceptionMessage)); - } else if (exception instanceof FetcherServerException) { - dialogService.showInformationDialogAndWait( - dialogTitle, - Localization.lang( - "Bibliographic data could not be retrieved.\n" + - "This is likely due to an issue being experienced by the server.\n" + - "Try again later.\n" + - "%0", - exceptionMessage)); - } else { - dialogService.showInformationDialogAndWait( - dialogTitle, - Localization.lang( - "Bibliographic data could not be retrieved.\n" + - "The following error was encountered:\n" + - "%0", + dialogService.showInformationDialogAndWait(dialogTitle, + Localization.lang("Bibliographic data could not be retrieved.\n" + + "This is likely due to an issue with your input, or your network connection.\n" + + "Check your provided identifier (and identifier type), and try again.\n" + "%0", exceptionMessage)); } + else if (exception instanceof FetcherServerException) { + dialogService.showInformationDialogAndWait(dialogTitle, + Localization.lang("Bibliographic data could not be retrieved.\n" + + "This is likely due to an issue being experienced by the server.\n" + + "Try again later.\n" + "%0", exceptionMessage)); + } + else { + dialogService.showInformationDialogAndWait(dialogTitle, + Localization.lang("Bibliographic data could not be retrieved.\n" + + "The following error was encountered:\n" + "%0", exceptionMessage)); + } - LOGGER.error("An exception occurred with the '{}' fetcher when resolving '{}'.", fetcherName, textString, exception); + LOGGER.error("An exception occurred with the '{}' fetcher when resolving '{}'.", fetcherName, textString, + exception); executing.set(false); }); @@ -319,23 +322,14 @@ public void executeLookupIdentifier(boolean searchComposite) { final Optional result = idLookupWorker.getValue(); if (result.isEmpty()) { - dialogService.showWarningDialogAndWait( - Localization.lang("Invalid result returned"), - Localization.lang( - "An unknown error has occurred.\n" + - "This entry may need to be added manually.")); + dialogService.showWarningDialogAndWait(Localization.lang("Invalid result returned"), Localization + .lang("An unknown error has occurred.\n" + "This entry may need to be added manually.")); executing.set(false); return; } - final ImportHandler handler = new ImportHandler( - libraryTab.getBibDatabaseContext(), - preferences, - fileUpdateMonitor, - libraryTab.getUndoManager(), - stateManager, - dialogService, - taskExecutor); + final ImportHandler handler = new ImportHandler(libraryTab.getBibDatabaseContext(), preferences, + fileUpdateMonitor, libraryTab.getUndoManager(), stateManager, dialogService, taskExecutor); handler.importEntryWithDuplicateCheck(libraryTab.getBibDatabaseContext(), result.get()); executedSuccessfully.set(true); @@ -346,6 +340,7 @@ public void executeLookupIdentifier(boolean searchComposite) { } private class WorkerInterpretCitations extends Task>> { + @Override protected Optional> call() throws FetcherException { final String text = interpretText.getValue(); @@ -358,8 +353,10 @@ protected Optional> call() throws FetcherException { final PlainCitationParser parser = switch (parserChoice) { case PlainCitationParserChoice.RULE_BASED -> new RuleBasedPlainCitationParser(); - case PlainCitationParserChoice.GROBID -> new GrobidPlainCitationParser(preferences.getGrobidPreferences(), preferences.getImportFormatPreferences()); - case PlainCitationParserChoice.LLM -> new LlmPlainCitationParser(aiService.getTemplatesService(), preferences.getImportFormatPreferences(), aiService.getChatLanguageModel()); + case PlainCitationParserChoice.GROBID -> new GrobidPlainCitationParser( + preferences.getGrobidPreferences(), preferences.getImportFormatPreferences()); + case PlainCitationParserChoice.LLM -> new LlmPlainCitationParser(aiService.getTemplatesService(), + preferences.getImportFormatPreferences(), aiService.getChatLanguageModel()); }; final SeveralPlainCitationParser setParser = new SeveralPlainCitationParser(parser); @@ -370,6 +367,7 @@ protected Optional> call() throws FetcherException { } return Optional.of(entries); } + } public void executeInterpretCitations() { @@ -386,21 +384,15 @@ public void executeInterpretCitations() { final String dialogTitle = Localization.lang("Failed to interpret citations"); if (exception instanceof FetcherException) { - dialogService.showInformationDialogAndWait( - dialogTitle, - Localization.lang( - "Failed to interpret citations.\n" + - "The following error was encountered:\n" + - "%0", - exceptionMessage)); - } else { - dialogService.showInformationDialogAndWait( - dialogTitle, + dialogService.showInformationDialogAndWait(dialogTitle, Localization.lang( - "The following error occurred:\n" + - "%0", + "Failed to interpret citations.\n" + "The following error was encountered:\n" + "%0", exceptionMessage)); } + else { + dialogService.showInformationDialogAndWait(dialogTitle, + Localization.lang("The following error occurred:\n" + "%0", exceptionMessage)); + } LOGGER.error("An exception occurred with the '{}' parser.", parserName, exception); @@ -411,24 +403,15 @@ public void executeInterpretCitations() { final Optional> result = interpretWorker.getValue(); if (result.isEmpty()) { - dialogService.showWarningDialogAndWait( - Localization.lang("Invalid result"), - Localization.lang( - "An unknown error has occurred.\n" + - "Entries may need to be added manually.")); + dialogService.showWarningDialogAndWait(Localization.lang("Invalid result"), Localization + .lang("An unknown error has occurred.\n" + "Entries may need to be added manually.")); LOGGER.error("An invalid result was returned when parsing citations."); executing.set(false); return; } - final ImportHandler handler = new ImportHandler( - libraryTab.getBibDatabaseContext(), - preferences, - fileUpdateMonitor, - libraryTab.getUndoManager(), - stateManager, - dialogService, - taskExecutor); + final ImportHandler handler = new ImportHandler(libraryTab.getBibDatabaseContext(), preferences, + fileUpdateMonitor, libraryTab.getUndoManager(), stateManager, dialogService, taskExecutor); handler.importEntriesWithDuplicateCheck(libraryTab.getBibDatabaseContext(), result.get()); executedSuccessfully.set(true); @@ -439,6 +422,7 @@ public void executeInterpretCitations() { } private class WorkerSpecifyBibtex extends Task>> { + @Override protected Optional> call() throws ParseException { final String text = bibtexText.getValue(); @@ -456,6 +440,7 @@ protected Optional> call() throws ParseException { } return Optional.of(entries); } + } public void executeSpecifyBibtex() { @@ -471,21 +456,15 @@ public void executeSpecifyBibtex() { final String dialogTitle = Localization.lang("Failed to parse Bib(La)Tex"); if (exception instanceof ParseException) { - dialogService.showInformationDialogAndWait( - dialogTitle, - Localization.lang( - "Failed to parse entries.\n" + - "The following error was encountered:\n" + - "%0", - exceptionMessage)); - } else { - dialogService.showInformationDialogAndWait( - dialogTitle, + dialogService.showInformationDialogAndWait(dialogTitle, Localization.lang( - "The following error occurred:\n" + - "%0", + "Failed to parse entries.\n" + "The following error was encountered:\n" + "%0", exceptionMessage)); } + else { + dialogService.showInformationDialogAndWait(dialogTitle, + Localization.lang("The following error occurred:\n" + "%0", exceptionMessage)); + } LOGGER.error("An exception occurred when parsing Bib(La)Tex entries.", exception); @@ -496,24 +475,15 @@ public void executeSpecifyBibtex() { final Optional> result = bibtexWorker.getValue(); if (result.isEmpty()) { - dialogService.showWarningDialogAndWait( - Localization.lang("Invalid result"), - Localization.lang( - "An unknown error has occurred.\n" + - "Entries may need to be added manually.")); + dialogService.showWarningDialogAndWait(Localization.lang("Invalid result"), Localization + .lang("An unknown error has occurred.\n" + "Entries may need to be added manually.")); LOGGER.error("An invalid result was returned when parsing Bib(La)Tex entries."); executing.set(false); return; } - final ImportHandler handler = new ImportHandler( - libraryTab.getBibDatabaseContext(), - preferences, - fileUpdateMonitor, - libraryTab.getUndoManager(), - stateManager, - dialogService, - taskExecutor); + final ImportHandler handler = new ImportHandler(libraryTab.getBibDatabaseContext(), preferences, + fileUpdateMonitor, libraryTab.getUndoManager(), stateManager, dialogService, taskExecutor); handler.importEntriesWithDuplicateCheck(libraryTab.getBibDatabaseContext(), result.get()); executedSuccessfully.set(true); @@ -534,4 +504,5 @@ public void cancel() { bibtexWorker.cancel(); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/openoffice/AdvancedCiteDialogView.java b/jabgui/src/main/java/org/jabref/gui/openoffice/AdvancedCiteDialogView.java index 18029ee5f40..7075df8e423 100644 --- a/jabgui/src/main/java/org/jabref/gui/openoffice/AdvancedCiteDialogView.java +++ b/jabgui/src/main/java/org/jabref/gui/openoffice/AdvancedCiteDialogView.java @@ -13,16 +13,22 @@ public class AdvancedCiteDialogView extends BaseDialog { - @FXML private TextField pageInfo; - @FXML private RadioButton inPar; - @FXML private RadioButton inText; - @FXML private ToggleGroup citeToggleGroup; + @FXML + private TextField pageInfo; + + @FXML + private RadioButton inPar; + + @FXML + private RadioButton inText; + + @FXML + private ToggleGroup citeToggleGroup; + private AdvancedCiteDialogViewModel viewModel; public AdvancedCiteDialogView() { - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); setResultConverter(btn -> { if (btn == ButtonType.OK) { @@ -42,4 +48,5 @@ private void initialize() { inText.selectedProperty().bindBidirectional(viewModel.citeInTextProperty()); pageInfo.textProperty().bindBidirectional(viewModel.pageInfoProperty()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/openoffice/AdvancedCiteDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/openoffice/AdvancedCiteDialogViewModel.java index c62abfeccec..0f36320ef2a 100644 --- a/jabgui/src/main/java/org/jabref/gui/openoffice/AdvancedCiteDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/openoffice/AdvancedCiteDialogViewModel.java @@ -8,7 +8,9 @@ public class AdvancedCiteDialogViewModel { private final StringProperty pageInfo = new SimpleStringProperty(""); + private final BooleanProperty citeInPar = new SimpleBooleanProperty(); + private final BooleanProperty citeInText = new SimpleBooleanProperty(); public StringProperty pageInfoProperty() { @@ -22,4 +24,5 @@ public BooleanProperty citeInParProperty() { public BooleanProperty citeInTextProperty() { return citeInText; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/openoffice/Bootstrap.java b/jabgui/src/main/java/org/jabref/gui/openoffice/Bootstrap.java index 89cc4672f70..0299f002033 100644 --- a/jabgui/src/main/java/org/jabref/gui/openoffice/Bootstrap.java +++ b/jabgui/src/main/java/org/jabref/gui/openoffice/Bootstrap.java @@ -52,32 +52,33 @@ import com.sun.star.uno.UnoRuntime; import com.sun.star.uno.XComponentContext; -/** Bootstrap offers functionality to obtain a context or simply - * a service manager. - * The service manager can create a few basic services, whose implementations are: - *
      - *
    • com.sun.star.comp.loader.JavaLoader
    • - *
    • com.sun.star.comp.urlresolver.UrlResolver
    • - *
    • com.sun.star.comp.bridgefactory.BridgeFactory
    • - *
    • com.sun.star.comp.connections.Connector
    • - *
    • com.sun.star.comp.connections.Acceptor
    • - *
    • com.sun.star.comp.servicemanager.ServiceManager
    • - *
    - * - * Other services can be inserted into the service manager by - * using its XSet interface: - *
    -  *     XSet xSet = UnoRuntime.queryInterface( XSet.class, aMultiComponentFactory );
    -  *     // insert the service manager
    -  *     xSet.insert( aSingleComponentFactory );
    -  * 
    -*/ +/** + * Bootstrap offers functionality to obtain a context or simply a service manager. The + * service manager can create a few basic services, whose implementations are: + *
      + *
    • com.sun.star.comp.loader.JavaLoader
    • + *
    • com.sun.star.comp.urlresolver.UrlResolver
    • + *
    • com.sun.star.comp.bridgefactory.BridgeFactory
    • + *
    • com.sun.star.comp.connections.Connector
    • + *
    • com.sun.star.comp.connections.Acceptor
    • + *
    • com.sun.star.comp.servicemanager.ServiceManager
    • + *
    + * + * Other services can be inserted into the service manager by using its XSet interface: + *
    + *     XSet xSet = UnoRuntime.queryInterface( XSet.class, aMultiComponentFactory );
    + *     // insert the service manager
    + *     xSet.insert( aSingleComponentFactory );
    + * 
    + */ public class Bootstrap { private static final Random RANDOM_PIPE_NAME = new Random(); + private static boolean M_LOADED_JUH = false; - private static void insertBasicFactories(XSet xSet, XImplementationLoader xImpLoader) throws CannotActivateFactoryException, ElementExistException { + private static void insertBasicFactories(XSet xSet, XImplementationLoader xImpLoader) + throws CannotActivateFactoryException, ElementExistException { // insert the factory of the loader xSet.insert(xImpLoader.activate("com.sun.star.comp.loader.JavaLoader", null, null, null)); @@ -95,10 +96,9 @@ private static void insertBasicFactories(XSet xSet, XImplementationLoader xImpLo } /** - * Returns an array of default commandline options to start bootstrapped - * instance of soffice with. You may use it in connection with bootstrap - * method for example like this: - *
    +     * Returns an array of default commandline options to start bootstrapped instance of
    +     * soffice with. You may use it in connection with bootstrap method for example like
    +     * this: 
          *     List list = Arrays.asList( Bootstrap.getDefaultOptions() );
          *     list.remove("--nologo");
          *     list.remove("--nodefault");
    @@ -106,39 +106,41 @@ private static void insertBasicFactories(XSet xSet, XImplementationLoader xImpLo
          *
          *     Bootstrap.bootstrap( list.toArray( new String[list.size()] );
          * 
    - * * @return an array of default commandline options * @see #bootstrap(String[]) * @since LibreOffice 5.1 */ public static String[] getDefaultOptions() { - return new String[] {"--nologo", "--nodefault", "--norestore", "--nolockcheck"}; + return new String[] { "--nologo", "--nodefault", "--norestore", "--nolockcheck" }; } /** * backwards compatibility stub. - * - * @param context_entries the hash table contains mappings of entry names (type string) to context entries (type class ComponentContextEntry). + * @param context_entries the hash table contains mappings of entry names (type + * string) to context entries (type class ComponentContextEntry). * @return a new context. * @throws Exception if things go awry. */ - public static XComponentContext createInitialComponentContext(Hashtable context_entries) throws Exception { + public static XComponentContext createInitialComponentContext(Hashtable context_entries) + throws Exception { return createInitialComponentContext((Map) context_entries); } /** - * Bootstraps an initial component context with service manager and basic jurt components inserted. - * - * @param context_entries the hash table contains mappings of entry names (type string) to context entries (type class ComponentContextEntry). + * Bootstraps an initial component context with service manager and basic jurt + * components inserted. + * @param context_entries the hash table contains mappings of entry names (type + * string) to context entries (type class ComponentContextEntry). * @return a new context. * @throws Exception if things go awry. */ - public static XComponentContext createInitialComponentContext(Map context_entries) throws Exception { + public static XComponentContext createInitialComponentContext(Map context_entries) + throws Exception { ServiceManager xSMgr = new ServiceManager(); XImplementationLoader xImpLoader = UnoRuntime.queryInterface(XImplementationLoader.class, new JavaLoader()); XInitialization xInit = UnoRuntime.queryInterface(XInitialization.class, xImpLoader); - Object[] args = new Object[] {xSMgr}; + Object[] args = new Object[] { xSMgr }; xInit.initialize(args); // initial component context @@ -163,21 +165,19 @@ public static XComponentContext createInitialComponentContext(Map * See also UNOIDL com.sun.star.lang.ServiceManager. - * * @return a freshly bootstrapped service manager * @throws Exception if things go awry. */ public static XMultiServiceFactory createSimpleServiceManager() throws Exception { - return UnoRuntime.queryInterface(XMultiServiceFactory.class, createInitialComponentContext((Map) null).getServiceManager()); + return UnoRuntime.queryInterface(XMultiServiceFactory.class, + createInitialComponentContext((Map) null).getServiceManager()); } /** * Bootstraps the initial component context from a native UNO installation. - * * @return a freshly bootstrapped component context. *

    - * See also - * cppuhelper/defaultBootstrap_InitialComponentContext(). + * See also cppuhelper/defaultBootstrap_InitialComponentContext(). * @throws Exception if things go awry. */ public static XComponentContext defaultBootstrap_InitialComponentContext() { @@ -186,28 +186,27 @@ public static XComponentContext defaultBootstrap_InitialComponentContext() { /** * Backwards compatibility stub. - * - * @param ini_file ini_file (may be null: uno.rc besides cppuhelper lib) + * @param ini_file ini_file (may be null: uno.rc besides cppuhelper lib) * @param bootstrap_parameters bootstrap parameters (maybe null) * @return a freshly bootstrapped component context. * @throws Exception if things go awry. */ - public static XComponentContext defaultBootstrap_InitialComponentContext(String ini_file, Hashtable bootstrap_parameters) { + public static XComponentContext defaultBootstrap_InitialComponentContext(String ini_file, + Hashtable bootstrap_parameters) { return defaultBootstrap_InitialComponentContext(ini_file, (Map) bootstrap_parameters); } /** * Bootstraps the initial component context from a native UNO installation. *

    - * See also - * cppuhelper/defaultBootstrap_InitialComponentContext(). - * - * @param ini_file ini_file (may be null: uno.rc besides cppuhelper lib) + * See also cppuhelper/defaultBootstrap_InitialComponentContext(). + * @param ini_file ini_file (may be null: uno.rc besides cppuhelper lib) * @param bootstrap_parameters bootstrap parameters (maybe null) * @return a freshly bootstrapped component context. * @throws Exception if things go awry. */ - public static XComponentContext defaultBootstrap_InitialComponentContext(String ini_file, Map bootstrap_parameters) { + public static XComponentContext defaultBootstrap_InitialComponentContext(String ini_file, + Map bootstrap_parameters) { // jni convenience: easier to iterate over array than calling Hashtable String pairs[] = null; if (null != bootstrap_parameters) { @@ -233,45 +232,50 @@ public static XComponentContext defaultBootstrap_InitialComponentContext(String boolean disable_dynloading = false; try { System.loadLibrary("lo-bootstrap"); - } catch (UnsatisfiedLinkError e) { + } + catch (UnsatisfiedLinkError e) { disable_dynloading = true; } if (!disable_dynloading) { NativeLibraryLoader.loadLibrary(Bootstrap.class.getClassLoader(), "juh"); } - } else { + } + else { NativeLibraryLoader.loadLibrary(Bootstrap.class.getClassLoader(), "juh"); } M_LOADED_JUH = true; } - return UnoRuntime.queryInterface(XComponentContext.class, cppuhelper_bootstrap(ini_file, pairs, Bootstrap.class.getClassLoader())); + return UnoRuntime.queryInterface(XComponentContext.class, + cppuhelper_bootstrap(ini_file, pairs, Bootstrap.class.getClassLoader())); } - private static native Object cppuhelper_bootstrap(String ini_file, String bootstrap_parameters[], ClassLoader loader); + private static native Object cppuhelper_bootstrap(String ini_file, String bootstrap_parameters[], + ClassLoader loader); /** * Bootstraps the component context from a UNO installation. - * * @return a bootstrapped component context. * @throws BootstrapException if things go awry. * @since UDK 3.1.0 */ - public static XComponentContext bootstrap(Path ooPath) throws BootstrapException, IOException, InterruptedException { + public static XComponentContext bootstrap(Path ooPath) + throws BootstrapException, IOException, InterruptedException { String[] defaultArgArray = getDefaultOptions(); return bootstrap(defaultArgArray, ooPath); } /** * Bootstraps the component context from a UNO installation. - * - * @param argArray an array of strings - commandline options to start instance of soffice with + * @param argArray an array of strings - commandline options to start instance of + * soffice with * @return a bootstrapped component context. * @throws BootstrapException if things go awry. * @see #getDefaultOptions() * @since LibreOffice 5.1 */ - public static XComponentContext bootstrap(String[] argArray, Path path) throws BootstrapException, IOException, InterruptedException { + public static XComponentContext bootstrap(String[] argArray, Path path) + throws BootstrapException, IOException, InterruptedException { XComponentContext xContext = null; @@ -283,7 +287,8 @@ public static XComponentContext bootstrap(String[] argArray, Path path) throws B } // create call with arguments - // We need a socket, pipe does not work. https://api.libreoffice.org/examples/examples.html + // We need a socket, pipe does not work. + // https://api.libreoffice.org/examples/examples.html String[] cmdArray = new String[argArray.length + 2]; cmdArray[0] = path.toAbsolutePath().toString(); cmdArray[1] = "--accept=socket,host=localhost,port=2083" + ";urp;"; @@ -308,7 +313,7 @@ public static XComponentContext bootstrap(String[] argArray, Path path) throws B String sConnect = "uno:socket,host=localhost,port=2083" + ";urp;StarOffice.ComponentContext"; // wait until office is started - for (int i = 0; ; ++i) { + for (int i = 0;; ++i) { try { // try to connect to office Object context = xUrlResolver.resolve(sConnect); @@ -317,7 +322,8 @@ public static XComponentContext bootstrap(String[] argArray, Path path) throws B throw new BootstrapException("no component context!"); } break; - } catch (NoConnectException ex) { + } + catch (NoConnectException ex) { // Wait 500 ms, then try to connect again, but do not wait // longer than 5 min (= 600 * 500 ms) total: if (i == 600) { @@ -326,7 +332,8 @@ public static XComponentContext bootstrap(String[] argArray, Path path) throws B Thread.sleep(500); } } - } catch (Exception e) { + } + catch (Exception e) { throw new BootstrapException(e); } @@ -340,19 +347,21 @@ public void run() { try { BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); - for (; ; ) { + for (;;) { String s = r.readLine(); if (s == null) { break; } out.println(prefix + s); } - } catch (IOException e) { + } + catch (IOException e) { e.printStackTrace(System.err); } } }.start(); } + } // vim:set shiftwidth=4 softtabstop=4 expandtab: diff --git a/jabgui/src/main/java/org/jabref/gui/openoffice/CSLStyleSelectViewModel.java b/jabgui/src/main/java/org/jabref/gui/openoffice/CSLStyleSelectViewModel.java index 55a5bfda77c..04eaeb92f11 100644 --- a/jabgui/src/main/java/org/jabref/gui/openoffice/CSLStyleSelectViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/openoffice/CSLStyleSelectViewModel.java @@ -9,9 +9,13 @@ import org.jabref.logic.l10n.Localization; public class CSLStyleSelectViewModel { + private final CitationStylePreviewLayout layout; + private final StringProperty nameProperty = new SimpleStringProperty(); + private final StringProperty pathProperty = new SimpleStringProperty(); + private final BooleanProperty internalStyleProperty = new SimpleBooleanProperty(); public CSLStyleSelectViewModel(CitationStylePreviewLayout layout) { @@ -19,7 +23,8 @@ public CSLStyleSelectViewModel(CitationStylePreviewLayout layout) { this.nameProperty.set(layout.getDisplayName()); if (layout.getCitationStyle().isInternalStyle()) { this.pathProperty.set(Localization.lang("Internal style")); - } else { + } + else { this.pathProperty.set(layout.getFilePath()); } this.internalStyleProperty.set(layout.getCitationStyle().isInternalStyle()); @@ -40,4 +45,5 @@ public BooleanProperty internalStyleProperty() { public CitationStylePreviewLayout getLayout() { return layout; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/openoffice/CitationEntryViewModel.java b/jabgui/src/main/java/org/jabref/gui/openoffice/CitationEntryViewModel.java index c29f24c8424..36402e542a6 100644 --- a/jabgui/src/main/java/org/jabref/gui/openoffice/CitationEntryViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/openoffice/CitationEntryViewModel.java @@ -8,7 +8,9 @@ public class CitationEntryViewModel { private final StringProperty citation = new SimpleStringProperty(""); + private final StringProperty extraInformation = new SimpleStringProperty(""); + private final String refMarkName; public CitationEntryViewModel(String refMarkName, String citation, String extraInfo) { @@ -36,4 +38,5 @@ public StringProperty extraInformationProperty() { public void setExtraInfo(String extraInfo) { extraInformation.setValue(extraInfo); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/openoffice/DetectOpenOfficeInstallation.java b/jabgui/src/main/java/org/jabref/gui/openoffice/DetectOpenOfficeInstallation.java index 65cdd357de9..19d82fd4302 100644 --- a/jabgui/src/main/java/org/jabref/gui/openoffice/DetectOpenOfficeInstallation.java +++ b/jabgui/src/main/java/org/jabref/gui/openoffice/DetectOpenOfficeInstallation.java @@ -20,6 +20,7 @@ public class DetectOpenOfficeInstallation { private final OpenOfficePreferences openOfficePreferences; + private final DialogService dialogService; public DetectOpenOfficeInstallation(OpenOfficePreferences openOfficePreferences, DialogService dialogService) { @@ -32,11 +33,12 @@ public boolean isExecutablePathDefined() { } public Optional selectInstallationPath() { - dialogService.showInformationDialogAndWait(Localization.lang("Could not find OpenOffice/LibreOffice installation"), - Localization.lang("Unable to autodetect OpenOffice/LibreOffice installation. Please choose the installation directory manually.")); + dialogService.showInformationDialogAndWait( + Localization.lang("Could not find OpenOffice/LibreOffice installation"), Localization.lang( + "Unable to autodetect OpenOffice/LibreOffice installation. Please choose the installation directory manually.")); DirectoryDialogConfiguration dirDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(NativeDesktop.get().getApplicationDirectory()) - .build(); + .withInitialDirectory(NativeDesktop.get().getApplicationDirectory()) + .build(); return dialogService.showDirectorySelectionDialog(dirDialogConfiguration); } @@ -57,9 +59,11 @@ public boolean setOpenOfficePreferences(Path installDir) { if (OS.WINDOWS) { execPath = FileUtil.find(OpenOfficePreferences.WINDOWS_EXECUTABLE, installDir); - } else if (OS.OS_X) { + } + else if (OS.OS_X) { execPath = FileUtil.find(OpenOfficePreferences.OSX_EXECUTABLE, installDir); - } else if (OS.LINUX) { + } + else if (OS.LINUX) { execPath = FileUtil.find(OpenOfficePreferences.LINUX_EXECUTABLE, installDir); } @@ -80,11 +84,10 @@ public Optional chooseAmongInstallations(List installDirs) { return Optional.of(installDirs.getFirst().toAbsolutePath()); } - return dialogService.showChoiceDialogAndWait( - Localization.lang("Choose OpenOffice/LibreOffice executable"), + return dialogService.showChoiceDialogAndWait(Localization.lang("Choose OpenOffice/LibreOffice executable"), Localization.lang("Found more than one OpenOffice/LibreOffice executable.") + "\n" + Localization.lang("Please choose which one to connect to:"), - Localization.lang("Use selected instance"), - installDirs); + Localization.lang("Use selected instance"), installDirs); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/openoffice/JStyleSelectViewModel.java b/jabgui/src/main/java/org/jabref/gui/openoffice/JStyleSelectViewModel.java index 72365c7572b..8bd626eb3c8 100644 --- a/jabgui/src/main/java/org/jabref/gui/openoffice/JStyleSelectViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/openoffice/JStyleSelectViewModel.java @@ -14,10 +14,16 @@ public class JStyleSelectViewModel { private final StringProperty name = new SimpleStringProperty(""); + private final StringProperty journals = new SimpleStringProperty(""); + private final StringProperty file = new SimpleStringProperty(""); - private final ObjectProperty icon = new SimpleObjectProperty<>(IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()); + + private final ObjectProperty icon = new SimpleObjectProperty<>( + IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()); + private final JStyle jStyle; + private final BooleanProperty internalStyle = new SimpleBooleanProperty(); public JStyleSelectViewModel(String name, String journals, String file, JStyle jStyle) { @@ -55,4 +61,5 @@ public BooleanProperty internalStyleProperty() { public String getStylePath() { return jStyle.getPath(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogView.java b/jabgui/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogView.java index 986c971c9d0..6a4bb00ed8c 100644 --- a/jabgui/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogView.java +++ b/jabgui/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogView.java @@ -22,24 +22,29 @@ public class ManageCitationsDialogView extends BaseDialog { private static final String HTML_BOLD_END_TAG = ""; + private static final String HTML_BOLD_START_TAG = ""; private final OOBibBase ooBase; - @FXML private TableView citationsTableView; - @FXML private TableColumn citation; - @FXML private TableColumn extraInfo; + @FXML + private TableView citationsTableView; - @Inject private DialogService dialogService; + @FXML + private TableColumn citation; + + @FXML + private TableColumn extraInfo; + + @Inject + private DialogService dialogService; private ManageCitationsDialogViewModel viewModel; public ManageCitationsDialogView(OOBibBase ooBase) { this.ooBase = ooBase; - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); setResultConverter(btn -> { if (btn == ButtonType.OK) { @@ -65,15 +70,16 @@ private void initialize() { citationsTableView.itemsProperty().bindBidirectional(viewModel.citationsProperty()); - extraInfo.setOnEditCommit((CellEditEvent cell) -> - cell.getRowValue().setExtraInfo(cell.getNewValue())); + extraInfo.setOnEditCommit((CellEditEvent cell) -> cell.getRowValue() + .setExtraInfo(cell.getNewValue())); extraInfo.setCellFactory(TextFieldTableCell.forTableColumn()); } private Node getText(String citationContext) { String inBetween = StringUtil.substringBetween(citationContext, HTML_BOLD_START_TAG, HTML_BOLD_END_TAG); String start = citationContext.substring(0, citationContext.indexOf(HTML_BOLD_START_TAG)); - String end = citationContext.substring(citationContext.lastIndexOf(HTML_BOLD_END_TAG) + HTML_BOLD_END_TAG.length()); + String end = citationContext + .substring(citationContext.lastIndexOf(HTML_BOLD_END_TAG) + HTML_BOLD_END_TAG.length()); Text startText = new Text(start); Text inBetweenText = new Text(inBetween); @@ -86,4 +92,5 @@ private Node getText(String citationContext) { public boolean isOkToShowThisDialog() { return viewModel != null && !viewModel.failedToGetCitationEntries; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogViewModel.java index 697212fde54..696a8239ce3 100644 --- a/jabgui/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogViewModel.java @@ -14,8 +14,12 @@ public class ManageCitationsDialogViewModel { public final boolean failedToGetCitationEntries; - private final ListProperty citations = new SimpleListProperty<>(FXCollections.observableArrayList()); + + private final ListProperty citations = new SimpleListProperty<>( + FXCollections.observableArrayList()); + private final OOBibBase ooBase; + private final DialogService dialogService; public ManageCitationsDialogViewModel(OOBibBase ooBase, DialogService dialogService) { @@ -35,12 +39,14 @@ public ManageCitationsDialogViewModel(OOBibBase ooBase, DialogService dialogServ } public void storeSettings() { - List citationEntries = citations.stream().map(CitationEntryViewModel::toCitationEntry).collect(Collectors.toList()); + List citationEntries = citations.stream() + .map(CitationEntryViewModel::toCitationEntry) + .collect(Collectors.toList()); ooBase.guiActionApplyCitationEntries(citationEntries); } public ListProperty citationsProperty() { return citations; } -} +} diff --git a/jabgui/src/main/java/org/jabref/gui/openoffice/ModifyCSLBibliographyPropertiesDialogView.java b/jabgui/src/main/java/org/jabref/gui/openoffice/ModifyCSLBibliographyPropertiesDialogView.java index 30177ea3fe7..5c4713e7dcb 100644 --- a/jabgui/src/main/java/org/jabref/gui/openoffice/ModifyCSLBibliographyPropertiesDialogView.java +++ b/jabgui/src/main/java/org/jabref/gui/openoffice/ModifyCSLBibliographyPropertiesDialogView.java @@ -15,9 +15,14 @@ public class ModifyCSLBibliographyPropertiesDialogView extends BaseDialog { - @FXML private TextField titleField; - @FXML private ComboBox headerFormats; - @FXML private ComboBox bodyFormats; + @FXML + private TextField titleField; + + @FXML + private ComboBox headerFormats; + + @FXML + private ComboBox bodyFormats; private final ModifyCSLBibliographyPropertiesDialogViewModel viewModel; @@ -28,9 +33,7 @@ public ModifyCSLBibliographyPropertiesDialogView(OpenOfficePreferences openOffic this.initModality(Modality.NONE); this.setResizable(false); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); setResultConverter(buttonType -> { if (buttonType == ButtonType.OK) { @@ -44,16 +47,13 @@ public ModifyCSLBibliographyPropertiesDialogView(OpenOfficePreferences openOffic public void initialize() { titleField.textProperty().bindBidirectional(viewModel.cslBibliographyTitleProperty()); - new ViewModelListCellFactory() - .withText(format -> format) - .install(headerFormats); + new ViewModelListCellFactory().withText(format -> format).install(headerFormats); headerFormats.itemsProperty().bind(viewModel.headerFormatListProperty()); headerFormats.valueProperty().bindBidirectional(viewModel.cslBibliographySelectedHeaderFormatProperty()); - new ViewModelListCellFactory() - .withText(format -> format) - .install(bodyFormats); + new ViewModelListCellFactory().withText(format -> format).install(bodyFormats); bodyFormats.itemsProperty().bind(viewModel.bodyFormatListProperty()); bodyFormats.valueProperty().bindBidirectional(viewModel.cslBibliographySelectedBodyFormatProperty()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/openoffice/ModifyCSLBibliographyPropertiesDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/openoffice/ModifyCSLBibliographyPropertiesDialogViewModel.java index 53c08365c7f..c5d854fb626 100644 --- a/jabgui/src/main/java/org/jabref/gui/openoffice/ModifyCSLBibliographyPropertiesDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/openoffice/ModifyCSLBibliographyPropertiesDialogViewModel.java @@ -12,12 +12,16 @@ public class ModifyCSLBibliographyPropertiesDialogViewModel { private final StringProperty cslBibliographyTitle = new SimpleStringProperty(); + private final StringProperty cslBibliographySelectedHeaderFormat = new SimpleStringProperty(); + private final StringProperty cslBibliographySelectedBodyFormat = new SimpleStringProperty(); - private final ReadOnlyListProperty headerFormatListProperty = - new ReadOnlyListWrapper<>(FXCollections.observableArrayList(CSLFormatUtils.BIBLIOGRAPHY_TITLE_FORMATS)); - private final ReadOnlyListProperty bodyFormatListProperty = - new ReadOnlyListWrapper<>(FXCollections.observableArrayList(CSLFormatUtils.BIBLIOGRAPHY_BODY_FORMATS)); + + private final ReadOnlyListProperty headerFormatListProperty = new ReadOnlyListWrapper<>( + FXCollections.observableArrayList(CSLFormatUtils.BIBLIOGRAPHY_TITLE_FORMATS)); + + private final ReadOnlyListProperty bodyFormatListProperty = new ReadOnlyListWrapper<>( + FXCollections.observableArrayList(CSLFormatUtils.BIBLIOGRAPHY_BODY_FORMATS)); private final OpenOfficePreferences openOfficePreferences; @@ -54,4 +58,5 @@ public StringProperty cslBibliographySelectedBodyFormatProperty() { public ReadOnlyListProperty bodyFormatListProperty() { return bodyFormatListProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/openoffice/OOBibBase.java b/jabgui/src/main/java/org/jabref/gui/openoffice/OOBibBase.java index 483eb775220..fe073d2c035 100644 --- a/jabgui/src/main/java/org/jabref/gui/openoffice/OOBibBase.java +++ b/jabgui/src/main/java/org/jabref/gui/openoffice/OOBibBase.java @@ -62,7 +62,8 @@ import org.slf4j.LoggerFactory; /** - * Class for manipulating the Bibliography of the currently started document in OpenOffice. + * Class for manipulating the Bibliography of the currently started document in + * OpenOffice. */ public class OOBibBase { @@ -75,12 +76,11 @@ public class OOBibBase { private final OpenOfficePreferences openOfficePreferences; private CSLCitationOOAdapter cslCitationOOAdapter; + private CSLUpdateBibliography cslUpdateBibliography; public OOBibBase(Path loPath, DialogService dialogService, OpenOfficePreferences openOfficePreferences) - throws - BootstrapException, - CreationException, IOException, InterruptedException { + throws BootstrapException, CreationException, IOException, InterruptedException { this.dialogService = dialogService; this.connection = new OOBibBaseConnect(loPath, dialogService); @@ -91,38 +91,42 @@ private void initializeCitationAdapter(XTextDocument doc) throws WrappedTargetEx if (cslCitationOOAdapter == null) { StateManager stateManager = Injector.instantiateModelOrService(StateManager.class); Supplier> databasesSupplier = stateManager::getOpenDatabases; - cslCitationOOAdapter = new CSLCitationOOAdapter(doc, databasesSupplier, openOfficePreferences, Injector.instantiateModelOrService(BibEntryTypesManager.class)); + cslCitationOOAdapter = new CSLCitationOOAdapter(doc, databasesSupplier, openOfficePreferences, + Injector.instantiateModelOrService(BibEntryTypesManager.class)); cslUpdateBibliography = new CSLUpdateBibliography(); } } - public void guiActionSelectDocument(boolean autoSelectForSingle) throws WrappedTargetException, NoSuchElementException { + public void guiActionSelectDocument(boolean autoSelectForSingle) + throws WrappedTargetException, NoSuchElementException { final String errorTitle = Localization.lang("Problem connecting"); try { connection.selectDocument(autoSelectForSingle); - } catch (NoDocumentFoundException ex) { + } + catch (NoDocumentFoundException ex) { OOError.from(ex).showErrorDialog(dialogService); - } catch (DisposedException ex) { + } + catch (DisposedException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); - } catch (WrappedTargetException - | IndexOutOfBoundsException - | NoSuchElementException ex) { + } + catch (WrappedTargetException | IndexOutOfBoundsException | NoSuchElementException ex) { LOGGER.warn("Problem connecting", ex); OOError.fromMisc(ex).setTitle(errorTitle).showErrorDialog(dialogService); } if (isConnectedToDocument()) { initializeCitationAdapter(this.getXTextDocument().get()); - dialogService.notify(Localization.lang("Connected to document") + ": " - + this.getCurrentDocumentTitle().orElse("")); + dialogService + .notify(Localization.lang("Connected to document") + ": " + this.getCurrentDocumentTitle().orElse("")); } } /** * A simple test for document availability. *

    - * See also `isDocumentConnectionMissing` for a test actually attempting to use the connection. + * See also `isDocumentConnectionMissing` for a test actually attempting to use the + * connection. */ public boolean isConnectedToDocument() { return this.connection.isConnectedToDocument(); @@ -149,11 +153,12 @@ public Optional getCurrentDocumentTitle() { return this.connection.getCurrentDocumentTitle(); } - /* ****************************************************** + /* + * ****************************************************** * - * Tools to collect and show precondition test results + * Tools to collect and show precondition test results * - * ******************************************************/ + ******************************************************/ void showDialog(OOError err) { err.showErrorDialog(dialogService); @@ -165,12 +170,13 @@ void showDialog(String errorTitle, OOError err) { OOVoidResult collectResults(String errorTitle, List> results) { String msg = results.stream() - .filter(OOVoidResult::isError) - .map(e -> e.getError().getLocalizedMessage()) - .collect(Collectors.joining("\n\n")); + .filter(OOVoidResult::isError) + .map(e -> e.getError().getLocalizedMessage()) + .collect(Collectors.joining("\n\n")); if (msg.isEmpty()) { return OOVoidResult.ok(); - } else { + } + else { return OOVoidResult.error(new OOError(errorTitle, msg)); } } @@ -204,11 +210,10 @@ OOResult getUserCursorForTextInsertion(XTextDocument doc, Objects.requireNonNull(cursor); try { cursor.getStart(); - } catch (com.sun.star.uno.RuntimeException ex) { - String msg = - Localization.lang("Please move the cursor" - + " to the location for the new citation.") + "\n" - + Localization.lang("I cannot insert to the cursor's current location."); + } + catch (com.sun.star.uno.RuntimeException ex) { + String msg = Localization.lang("Please move the cursor" + " to the location for the new citation.") + "\n" + + Localization.lang("I cannot insert to the cursor's current location."); return OOResult.error(new OOError(errorTitle, msg, ex)); } return OOResult.ok(cursor); @@ -218,12 +223,9 @@ OOResult getUserCursorForTextInsertion(XTextDocument doc, * This may move the view cursor. */ OOResult getFunctionalTextViewCursor(XTextDocument doc, String errorTitle) { - String messageOnFailureToObtain = - Localization.lang("Please move the cursor into the document text.") - + "\n" - + Localization.lang("To get the visual positions of your citations" - + " I need to move the cursor around," - + " but could not get it."); + String messageOnFailureToObtain = Localization.lang("Please move the cursor into the document text.") + "\n" + + Localization.lang("To get the visual positions of your citations" + + " I need to move the cursor around," + " but could not get it."); OOResult result = FunctionalTextViewCursor.get(doc); if (result.isError()) { LOGGER.warn(result.getError()); @@ -236,14 +238,13 @@ private static OOVoidResult checkRangeOverlaps(XTextDocument doc, OOFro boolean requireSeparation = false; int maxReportedOverlaps = 10; try { - return frontend.checkRangeOverlaps(doc, - new ArrayList<>(), - requireSeparation, - maxReportedOverlaps) - .mapError(OOError::from); - } catch (NoDocumentException ex) { + return frontend.checkRangeOverlaps(doc, new ArrayList<>(), requireSeparation, maxReportedOverlaps) + .mapError(OOError::from); + } + catch (NoDocumentException ex) { return OOVoidResult.error(OOError.from(ex).setTitle(errorTitle)); - } catch (WrappedTargetException ex) { + } + catch (WrappedTargetException ex) { return OOVoidResult.error(OOError.fromMisc(ex).setTitle(errorTitle)); } } @@ -257,28 +258,28 @@ private static OOVoidResult checkRangeOverlapsWithCursor(XTextDocument boolean requireSeparation = false; OOVoidResult res; try { - res = frontend.checkRangeOverlapsWithCursor(doc, - userRanges, - requireSeparation); - } catch (NoDocumentException ex) { + res = frontend.checkRangeOverlapsWithCursor(doc, userRanges, requireSeparation); + } + catch (NoDocumentException ex) { return OOVoidResult.error(OOError.from(ex).setTitle(errorTitle)); - } catch (WrappedTargetException ex) { + } + catch (WrappedTargetException ex) { return OOVoidResult.error(OOError.fromMisc(ex).setTitle(errorTitle)); } if (res.isError()) { final String xtitle = Localization.lang("The cursor is in a protected area."); - return OOVoidResult.error( - new OOError(xtitle, xtitle + "\n" + res.getError().getLocalizedMessage() + "\n")); + return OOVoidResult.error(new OOError(xtitle, xtitle + "\n" + res.getError().getLocalizedMessage() + "\n")); } return res.mapError(OOError::from); } - /* ****************************************************** + /* + * ****************************************************** * * Tests for preconditions. * - * ******************************************************/ + ******************************************************/ private static OOVoidResult checkIfOpenOfficeIsRecordingChanges(XTextDocument doc) { String errorTitle = Localization.lang("Recording and/or Recorded changes"); @@ -288,24 +289,23 @@ private static OOVoidResult checkIfOpenOfficeIsRecordingChanges(XTextDo if (recordingChanges || (nRedlines > 0)) { String msg = ""; if (recordingChanges) { - msg += Localization.lang("Cannot work with" - + " [Edit]/[Track Changes]/[Record] turned on."); + msg += Localization.lang("Cannot work with" + " [Edit]/[Track Changes]/[Record] turned on."); } if (nRedlines > 0) { if (recordingChanges) { msg += "\n"; } - msg += Localization.lang("Changes by JabRef" - + " could result in unexpected interactions with" + msg += Localization.lang("Changes by JabRef" + " could result in unexpected interactions with" + " recorded changes."); msg += "\n"; msg += Localization.lang("Use [Edit]/[Track Changes]/[Manage] to resolve them first."); } return OOVoidResult.error(new OOError(errorTitle, msg)); } - } catch (WrappedTargetException ex) { - String msg = Localization.lang("Error while checking if Writer" - + " is recording changes or has recorded changes."); + } + catch (WrappedTargetException ex) { + String msg = Localization + .lang("Error while checking if Writer" + " is recording changes or has recorded changes."); return OOVoidResult.error(new OOError(errorTitle, msg, ex)); } return OOVoidResult.ok(); @@ -314,7 +314,8 @@ private static OOVoidResult checkIfOpenOfficeIsRecordingChanges(XTextDo OOVoidResult styleIsRequired(OOStyle style) { if (style == null) { return OOVoidResult.error(OOError.noValidStyleSelected()); - } else { + } + else { return OOVoidResult.ok(); } } @@ -323,28 +324,29 @@ OOResult getFrontend(XTextDocument doc) { final String errorTitle = "Unable to get frontend"; try { return OOResult.ok(new OOFrontend(doc)); - } catch (NoDocumentException ex) { + } + catch (NoDocumentException ex) { return OOResult.error(OOError.from(ex).setTitle(errorTitle)); - } catch (WrappedTargetException - | RuntimeException ex) { + } + catch (WrappedTargetException | RuntimeException ex) { return OOResult.error(OOError.fromMisc(ex).setTitle(errorTitle)); } } - OOVoidResult databaseIsRequired(List databases, - Supplier fun) { + OOVoidResult databaseIsRequired(List databases, Supplier fun) { if (databases == null || databases.isEmpty()) { return OOVoidResult.error(fun.get()); - } else { + } + else { return OOVoidResult.ok(); } } - OOVoidResult selectedBibEntryIsRequired(List entries, - Supplier fun) { + OOVoidResult selectedBibEntryIsRequired(List entries, Supplier fun) { if (entries == null || entries.isEmpty()) { return OOVoidResult.error(fun.get()); - } else { + } + else { return OOVoidResult.ok(); } } @@ -352,56 +354,37 @@ OOVoidResult selectedBibEntryIsRequired(List entries, /* * Checks existence and also checks if it is not an internal name. */ - private OOVoidResult checkStyleExistsInTheDocument(String familyName, - String styleName, - XTextDocument doc, - String labelInJstyleFile, - String pathToStyleFile) - throws - WrappedTargetException { + private OOVoidResult checkStyleExistsInTheDocument(String familyName, String styleName, XTextDocument doc, + String labelInJstyleFile, String pathToStyleFile) throws WrappedTargetException { Optional internalName = UnoStyle.getInternalNameOfStyle(doc, familyName, styleName); if (internalName.isEmpty()) { - String msg = - switch (familyName) { - case UnoStyle.PARAGRAPH_STYLES -> Localization.lang("The %0 paragraph style '%1' is missing from the document", - labelInJstyleFile, - styleName); - case UnoStyle.CHARACTER_STYLES -> Localization.lang("The %0 character style '%1' is missing from the document", - labelInJstyleFile, - styleName); - default -> throw new IllegalArgumentException("Expected " + UnoStyle.CHARACTER_STYLES - + " or " + UnoStyle.PARAGRAPH_STYLES - + " for familyName"); - } - + "\n" - + Localization.lang("Please create it in the document or change in the file:") - + "\n" - + pathToStyleFile; + String msg = switch (familyName) { + case UnoStyle.PARAGRAPH_STYLES -> Localization + .lang("The %0 paragraph style '%1' is missing from the document", labelInJstyleFile, styleName); + case UnoStyle.CHARACTER_STYLES -> Localization + .lang("The %0 character style '%1' is missing from the document", labelInJstyleFile, styleName); + default -> throw new IllegalArgumentException("Expected " + UnoStyle.CHARACTER_STYLES + " or " + + UnoStyle.PARAGRAPH_STYLES + " for familyName"); + } + "\n" + Localization.lang("Please create it in the document or change in the file:") + "\n" + + pathToStyleFile; return OOVoidResult.error(new OOError("StyleIsNotKnown", msg)); } if (!internalName.get().equals(styleName)) { - String msg = - switch (familyName) { - case UnoStyle.PARAGRAPH_STYLES -> Localization.lang("The %0 paragraph style '%1' is a display name for '%2'.", - labelInJstyleFile, - styleName, - internalName.get()); - case UnoStyle.CHARACTER_STYLES -> Localization.lang("The %0 character style '%1' is a display name for '%2'.", - labelInJstyleFile, - styleName, - internalName.get()); - default -> throw new IllegalArgumentException("Expected " + UnoStyle.CHARACTER_STYLES - + " or " + UnoStyle.PARAGRAPH_STYLES - + " for familyName"); - } - + "\n" - + Localization.lang("Please use the latter in the style file below" - + " to avoid localization problems.") - + "\n" - + pathToStyleFile; + String msg = switch (familyName) { + case UnoStyle.PARAGRAPH_STYLES -> + Localization.lang("The %0 paragraph style '%1' is a display name for '%2'.", labelInJstyleFile, + styleName, internalName.get()); + case UnoStyle.CHARACTER_STYLES -> + Localization.lang("The %0 character style '%1' is a display name for '%2'.", labelInJstyleFile, + styleName, internalName.get()); + default -> throw new IllegalArgumentException("Expected " + UnoStyle.CHARACTER_STYLES + " or " + + UnoStyle.PARAGRAPH_STYLES + " for familyName"); + } + "\n" + Localization + .lang("Please use the latter in the style file below" + " to avoid localization problems.") + "\n" + + pathToStyleFile; return OOVoidResult.error(new OOError("StyleNameIsNotInternal", msg)); } return OOVoidResult.ok(); @@ -412,37 +395,30 @@ public OOVoidResult checkStylesExistInTheDocument(JStyle jStyle, XTextD List> results = new ArrayList<>(); try { - results.add(checkStyleExistsInTheDocument(UnoStyle.PARAGRAPH_STYLES, - jStyle.getReferenceHeaderParagraphFormat(), - doc, - "ReferenceHeaderParagraphFormat", - pathToStyleFile)); - results.add(checkStyleExistsInTheDocument(UnoStyle.PARAGRAPH_STYLES, - jStyle.getReferenceParagraphFormat(), - doc, - "ReferenceParagraphFormat", - pathToStyleFile)); + results.add( + checkStyleExistsInTheDocument(UnoStyle.PARAGRAPH_STYLES, jStyle.getReferenceHeaderParagraphFormat(), + doc, "ReferenceHeaderParagraphFormat", pathToStyleFile)); + results.add(checkStyleExistsInTheDocument(UnoStyle.PARAGRAPH_STYLES, jStyle.getReferenceParagraphFormat(), + doc, "ReferenceParagraphFormat", pathToStyleFile)); if (jStyle.isFormatCitations()) { results.add(checkStyleExistsInTheDocument(UnoStyle.CHARACTER_STYLES, - jStyle.getCitationCharacterFormat(), - doc, - "CitationCharacterFormat", - pathToStyleFile)); + jStyle.getCitationCharacterFormat(), doc, "CitationCharacterFormat", pathToStyleFile)); } - } catch (WrappedTargetException ex) { - results.add(OOVoidResult.error(new OOError("Other error in checkStyleExistsInTheDocument", - ex.getMessage(), - ex))); + } + catch (WrappedTargetException ex) { + results.add(OOVoidResult + .error(new OOError("Other error in checkStyleExistsInTheDocument", ex.getMessage(), ex))); } return collectResults("checkStyleExistsInTheDocument failed", results); } - /* ****************************************************** + /* + * ****************************************************** * * ManageCitationsDialogView * - * ******************************************************/ + ******************************************************/ public Optional> guiActionGetCitationEntries() { final Optional> FAIL = Optional.empty(); final String errorTitle = Localization.lang("Problem collecting citations"); @@ -461,13 +437,16 @@ public Optional> guiActionGetCitationEntries() { try { return Optional.of(ManageCitations.getCitationEntries(doc)); - } catch (NoDocumentException ex) { + } + catch (NoDocumentException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); return FAIL; - } catch (DisposedException ex) { + } + catch (DisposedException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); return FAIL; - } catch (WrappedTargetException ex) { + } + catch (WrappedTargetException ex) { LOGGER.warn(errorTitle, ex); OOError.fromMisc(ex).setTitle(errorTitle).showErrorDialog(dialogService); return FAIL; @@ -479,18 +458,22 @@ public Optional> guiActionGetCitationEntries() { *

    * Does not change presentation. *

    - * Note: we use no undo context here, because only DocumentConnection.setUserDefinedStringPropertyValue() is called, and Undo in LO will not undo that. + * Note: we use no undo context here, because only + * DocumentConnection.setUserDefinedStringPropertyValue() is called, and Undo in LO + * will not undo that. *

    - * GUI: "Manage citations" dialog "OK" button. Called from: ManageCitationsDialogViewModel.storeSettings + * GUI: "Manage citations" dialog "OK" button. Called from: + * ManageCitationsDialogViewModel.storeSettings * *

    * Currently the only editable part is pageInfo. *

    - * Since the only call to applyCitationEntries() only changes pageInfo w.r.t those returned by getCitationEntries(), we can do with the following restrictions: + * Since the only call to applyCitationEntries() only changes pageInfo w.r.t those + * returned by getCitationEntries(), we can do with the following restrictions: *

      - *
    • Missing pageInfo means no action.
    • - *
    • Missing CitationEntry means no action (no attempt to remove - * citation from the text).
    • + *
    • Missing pageInfo means no action.
    • + *
    • Missing CitationEntry means no action (no attempt to remove citation from the + * text).
    • *
    */ public void guiActionApplyCitationEntries(List citationEntries) { @@ -505,14 +488,15 @@ public void guiActionApplyCitationEntries(List citationEntries) { try { ManageCitations.applyCitationEntries(doc, citationEntries); - } catch (NoDocumentException ex) { + } + catch (NoDocumentException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); - } catch (DisposedException ex) { + } + catch (DisposedException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); - } catch (PropertyVetoException - | IllegalTypeException - | WrappedTargetException - | com.sun.star.lang.IllegalArgumentException ex) { + } + catch (PropertyVetoException | IllegalTypeException | WrappedTargetException + | com.sun.star.lang.IllegalArgumentException ex) { LOGGER.warn(errorTitle, ex); OOError.fromMisc(ex).setTitle(errorTitle).showErrorDialog(dialogService); } @@ -524,30 +508,28 @@ public void guiActionApplyCitationEntries(List citationEntries) { * Uses LO undo context "Insert citation". *

    * Note: Undo does not remove or reestablish custom properties. - * - * @param entries The entries to cite. - * @param bibDatabaseContext The database the entries belong to (all of them). Used when creating the citation mark. - *

    - * Consistency: for each entry in {@code entries}: looking it up in {@code syncOptions.get().databases} (if present) should yield {@code database}. - * @param style The bibliography style we are using. - * @param citationType Indicates whether it is an in-text citation, a citation in parenthesis or an invisible citation. - * @param pageInfo A single page-info for these entries. Attributed to the last entry. - * @param syncOptions Indicates whether in-text citations should be refreshed in the document. Optional.empty() indicates no refresh. Otherwise provides options for refreshing the reference list. + * @param entries The entries to cite. + * @param bibDatabaseContext The database the entries belong to (all of them). Used + * when creating the citation mark. + *

    + * Consistency: for each entry in {@code entries}: looking it up in + * {@code syncOptions.get().databases} (if present) should yield {@code database}. + * @param style The bibliography style we are using. + * @param citationType Indicates whether it is an in-text citation, a citation in + * parenthesis or an invisible citation. + * @param pageInfo A single page-info for these entries. Attributed to the last entry. + * @param syncOptions Indicates whether in-text citations should be refreshed in the + * document. Optional.empty() indicates no refresh. Otherwise provides options for + * refreshing the reference list. */ - public void guiActionInsertEntry(List entries, - BibDatabaseContext bibDatabaseContext, - BibEntryTypesManager bibEntryTypesManager, - OOStyle style, - CitationType citationType, - String pageInfo, - Optional syncOptions) { + public void guiActionInsertEntry(List entries, BibDatabaseContext bibDatabaseContext, + BibEntryTypesManager bibEntryTypesManager, OOStyle style, CitationType citationType, String pageInfo, + Optional syncOptions) { final String errorTitle = "Could not insert citation"; OOResult odoc = getXTextDocument(); - if (testDialog(errorTitle, - odoc.asVoidResult(), - styleIsRequired(style), + if (testDialog(errorTitle, odoc.asVoidResult(), styleIsRequired(style), selectedBibEntryIsRequired(entries, OOError::noEntriesSelectedForCitation))) { return; } @@ -568,8 +550,7 @@ public void guiActionInsertEntry(List entries, } if (style instanceof JStyle jStyle) { - if (testDialog(errorTitle, - checkStylesExistInTheDocument(jStyle, doc), + if (testDialog(errorTitle, checkStylesExistInTheDocument(jStyle, doc), checkIfOpenOfficeIsRecordingChanges(doc))) { return; } @@ -581,8 +562,9 @@ public void guiActionInsertEntry(List entries, OOResult fcursor = null; if (syncOptions.isPresent()) { fcursor = getFunctionalTextViewCursor(doc, errorTitle); - if (testDialog(errorTitle, fcursor.asVoidResult()) || testDialog(databaseIsRequired(syncOptions.get().databases, - OOError::noDataBaseIsOpenForSyncingAfterCitation))) { + if (testDialog(errorTitle, fcursor.asVoidResult()) + || testDialog(databaseIsRequired(syncOptions.get().databases, + OOError::noDataBaseIsOpenForSyncingAfterCitation))) { return; } } @@ -595,59 +577,63 @@ public void guiActionInsertEntry(List entries, if (style instanceof CitationStyle citationStyle) { // Handle insertion of CSL Style citations try { - // Lock document controllers - disable refresh during the process (avoids document flicker during writing) + // Lock document controllers - disable refresh during the process + // (avoids document flicker during writing) // MUST always be paired with an unlockControllers() call doc.lockControllers(); if (citationType == CitationType.AUTHORYEAR_PAR) { // "Cite" button - cslCitationOOAdapter.insertCitation(cursor.get(), citationStyle, entries, bibDatabaseContext, bibEntryTypesManager); - } else if (citationType == CitationType.AUTHORYEAR_INTEXT) { + cslCitationOOAdapter.insertCitation(cursor.get(), citationStyle, entries, bibDatabaseContext, + bibEntryTypesManager); + } + else if (citationType == CitationType.AUTHORYEAR_INTEXT) { // "Cite in-text" button - cslCitationOOAdapter.insertInTextCitation(cursor.get(), citationStyle, entries, bibDatabaseContext, bibEntryTypesManager); - } else if (citationType == CitationType.INVISIBLE_CIT) { + cslCitationOOAdapter.insertInTextCitation(cursor.get(), citationStyle, entries, + bibDatabaseContext, bibEntryTypesManager); + } + else if (citationType == CitationType.INVISIBLE_CIT) { // "Insert empty citation" cslCitationOOAdapter.insertEmptyCitation(cursor.get(), citationStyle, entries); } - // If "Automatically sync bibliography when inserting citations" is enabled + // If "Automatically sync bibliography when inserting citations" is + // enabled if (citationStyle.hasBibliography()) { syncOptions.ifPresent(options -> guiActionUpdateDocument(options.databases, citationStyle)); } - } finally { + } + finally { // Release controller lock doc.unlockControllers(); } - } else if (style instanceof JStyle jStyle) { + } + else if (style instanceof JStyle jStyle) { // Handle insertion of JStyle citations - EditInsert.insertCitationGroup(doc, - frontend.get(), - cursor.get(), - entries, - bibDatabaseContext.getDatabase(), - jStyle, - citationType, - pageInfo); + EditInsert.insertCitationGroup(doc, frontend.get(), cursor.get(), entries, + bibDatabaseContext.getDatabase(), jStyle, citationType, pageInfo); if (syncOptions.isPresent()) { Update.resyncDocument(doc, jStyle, fcursor.get(), syncOptions.get()); } } - } catch (NoDocumentException ex) { + } + catch (NoDocumentException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); - } catch (DisposedException ex) { + } + catch (DisposedException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); - } catch (CreationException - | WrappedTargetException - | PropertyVetoException - | IllegalTypeException - | NotRemoveableException ex) { + } + catch (CreationException | WrappedTargetException | PropertyVetoException | IllegalTypeException + | NotRemoveableException ex) { LOGGER.warn("Could not insert entry", ex); OOError.fromMisc(ex).setTitle(errorTitle).showErrorDialog(dialogService); - } catch (com.sun.star.uno.Exception e) { + } + catch (com.sun.star.uno.Exception e) { throw new RuntimeException(e); - } finally { + } + finally { UnoUndo.leaveUndoContext(doc); } } @@ -660,9 +646,7 @@ public void guiActionMergeCitationGroups(List databases, OOStyle st if (style instanceof JStyle jStyle) { OOResult odoc = getXTextDocument(); - if (testDialog(errorTitle, - odoc.asVoidResult(), - styleIsRequired(jStyle), + if (testDialog(errorTitle, odoc.asVoidResult(), styleIsRequired(jStyle), databaseIsRequired(databases, OOError::noDataBaseIsOpen))) { return; } @@ -670,9 +654,7 @@ public void guiActionMergeCitationGroups(List databases, OOStyle st OOResult fcursor = getFunctionalTextViewCursor(doc, errorTitle); - if (testDialog(errorTitle, - fcursor.asVoidResult(), - checkStylesExistInTheDocument(jStyle, doc), + if (testDialog(errorTitle, fcursor.asVoidResult(), checkStylesExistInTheDocument(jStyle, doc), checkIfOpenOfficeIsRecordingChanges(doc))) { return; } @@ -687,19 +669,19 @@ public void guiActionMergeCitationGroups(List databases, OOStyle st Update.SyncOptions syncOptions = new Update.SyncOptions(databases); Update.resyncDocument(doc, jStyle, fcursor.get(), syncOptions); } - } catch (NoDocumentException ex) { + } + catch (NoDocumentException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); - } catch (DisposedException ex) { + } + catch (DisposedException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); - } catch (CreationException - | IllegalTypeException - | NotRemoveableException - | PropertyVetoException - | WrappedTargetException - | com.sun.star.lang.IllegalArgumentException ex) { + } + catch (CreationException | IllegalTypeException | NotRemoveableException | PropertyVetoException + | WrappedTargetException | com.sun.star.lang.IllegalArgumentException ex) { LOGGER.warn("Problem combining cite markers", ex); OOError.fromMisc(ex).setTitle(errorTitle).showErrorDialog(dialogService); - } finally { + } + finally { UnoUndo.leaveUndoContext(doc); fcursor.get().restore(doc); } @@ -709,16 +691,15 @@ public void guiActionMergeCitationGroups(List databases, OOStyle st /** * GUI action "Separate citations". *

    - * Do the opposite of MergeCitationGroups. Combined markers are split, with a space inserted between. + * Do the opposite of MergeCitationGroups. Combined markers are split, with a space + * inserted between. */ public void guiActionSeparateCitations(List databases, OOStyle style) { final String errorTitle = Localization.lang("Problem during separating cite markers"); if (style instanceof JStyle jStyle) { OOResult odoc = getXTextDocument(); - if (testDialog(errorTitle, - odoc.asVoidResult(), - styleIsRequired(jStyle), + if (testDialog(errorTitle, odoc.asVoidResult(), styleIsRequired(jStyle), databaseIsRequired(databases, OOError::noDataBaseIsOpen))) { return; } @@ -726,9 +707,7 @@ public void guiActionSeparateCitations(List databases, OOStyle styl XTextDocument doc = odoc.get(); OOResult fcursor = getFunctionalTextViewCursor(doc, errorTitle); - if (testDialog(errorTitle, - fcursor.asVoidResult(), - checkStylesExistInTheDocument(jStyle, doc), + if (testDialog(errorTitle, fcursor.asVoidResult(), checkStylesExistInTheDocument(jStyle, doc), checkIfOpenOfficeIsRecordingChanges(doc))) { return; } @@ -743,19 +722,19 @@ public void guiActionSeparateCitations(List databases, OOStyle styl Update.SyncOptions syncOptions = new Update.SyncOptions(databases); Update.resyncDocument(doc, jStyle, fcursor.get(), syncOptions); } - } catch (NoDocumentException ex) { + } + catch (NoDocumentException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); - } catch (DisposedException ex) { + } + catch (DisposedException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); - } catch (CreationException - | IllegalTypeException - | NotRemoveableException - | PropertyVetoException - | WrappedTargetException - | com.sun.star.lang.IllegalArgumentException ex) { + } + catch (CreationException | IllegalTypeException | NotRemoveableException | PropertyVetoException + | WrappedTargetException | com.sun.star.lang.IllegalArgumentException ex) { LOGGER.warn("Problem during separating cite markers", ex); OOError.fromMisc(ex).setTitle(errorTitle).showErrorDialog(dialogService); - } finally { + } + finally { UnoUndo.leaveUndoContext(doc); fcursor.get().restore(doc); } @@ -766,16 +745,15 @@ public void guiActionSeparateCitations(List databases, OOStyle styl * GUI action for "Export cited" *

    * Does not refresh the bibliography. - * - * @param returnPartialResult If there are some unresolved keys, shall we return an otherwise nonempty result, or Optional.empty()? + * @param returnPartialResult If there are some unresolved keys, shall we return an + * otherwise nonempty result, or Optional.empty()? */ public Optional exportCitedHelper(List databases, boolean returnPartialResult) { final Optional FAIL = Optional.empty(); final String errorTitle = Localization.lang("Unable to generate new library"); OOResult odoc = getXTextDocument(); - if (testDialog(errorTitle, - odoc.asVoidResult(), + if (testDialog(errorTitle, odoc.asVoidResult(), databaseIsRequired(databases, OOError::noDataBaseIsOpenForExport))) { return FAIL; } @@ -787,44 +765,43 @@ public Optional exportCitedHelper(List databases, bool try { UnoUndo.enterUndoContext(doc, "Changes during \"Export cited\""); result = ExportCited.generateDatabase(doc, databases); - } finally { + } + finally { // There should be no changes, thus no Undo entry should appear // in LibreOffice. UnoUndo.leaveUndoContext(doc); } if (!result.newDatabase.hasEntries()) { - dialogService.showErrorDialogAndWait( - Localization.lang("Unable to generate new library"), - Localization.lang("Your OpenOffice/LibreOffice document references" - + " no citation keys" + dialogService.showErrorDialogAndWait(Localization.lang("Unable to generate new library"), + Localization.lang("Your OpenOffice/LibreOffice document references" + " no citation keys" + " which could also be found in your current library.")); return FAIL; } List unresolvedKeys = result.unresolvedKeys; if (!unresolvedKeys.isEmpty()) { - dialogService.showErrorDialogAndWait( - Localization.lang("Unable to generate new library"), - Localization.lang("Your OpenOffice/LibreOffice document references" - + " at least %0 citation keys" - + " which could not be found in your current library." - + " Some of these are %1.", - String.valueOf(unresolvedKeys.size()), - String.join(", ", unresolvedKeys))); + dialogService + .showErrorDialogAndWait(Localization.lang("Unable to generate new library"), Localization.lang( + "Your OpenOffice/LibreOffice document references" + " at least %0 citation keys" + + " which could not be found in your current library." + " Some of these are %1.", + String.valueOf(unresolvedKeys.size()), String.join(", ", unresolvedKeys))); if (returnPartialResult) { return Optional.of(result.newDatabase); - } else { + } + else { return FAIL; } } return Optional.of(result.newDatabase); - } catch (NoDocumentException ex) { + } + catch (NoDocumentException ex) { OOError.from(ex).showErrorDialog(dialogService); - } catch (DisposedException ex) { + } + catch (DisposedException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); - } catch (WrappedTargetException - | com.sun.star.lang.IllegalArgumentException ex) { + } + catch (WrappedTargetException | com.sun.star.lang.IllegalArgumentException ex) { LOGGER.warn("Problem generating new database.", ex); OOError.fromMisc(ex).setTitle(errorTitle).showErrorDialog(dialogService); } @@ -833,9 +810,8 @@ public Optional exportCitedHelper(List databases, bool /** * GUI action, refreshes citation markers and bibliography. - * * @param databases Must have at least one. - * @param style Style. + * @param style Style. */ public void guiActionUpdateDocument(List databases, OOStyle style) { final String errorTitle = Localization.lang("Unable to synchronize bibliography"); @@ -843,9 +819,7 @@ public void guiActionUpdateDocument(List databases, OOStyle style) try { OOResult odoc = getXTextDocument(); - if (testDialog(errorTitle, - odoc.asVoidResult(), - styleIsRequired(jStyle))) { + if (testDialog(errorTitle, odoc.asVoidResult(), styleIsRequired(jStyle))) { return; } @@ -853,9 +827,7 @@ public void guiActionUpdateDocument(List databases, OOStyle style) OOResult fcursor = getFunctionalTextViewCursor(doc, errorTitle); - if (testDialog(errorTitle, - fcursor.asVoidResult(), - checkStylesExistInTheDocument(jStyle, doc), + if (testDialog(errorTitle, fcursor.asVoidResult(), checkStylesExistInTheDocument(jStyle, doc), checkIfOpenOfficeIsRecordingChanges(doc))) { return; } @@ -870,34 +842,35 @@ public void guiActionUpdateDocument(List databases, OOStyle style) UnoUndo.enterUndoContext(doc, "Refresh bibliography"); Update.SyncOptions syncOptions = new Update.SyncOptions(databases); - syncOptions - .setUpdateBibliography(true) - .setAlwaysAddCitedOnPages(openOfficePreferences.getAlwaysAddCitedOnPages()); + syncOptions.setUpdateBibliography(true) + .setAlwaysAddCitedOnPages(openOfficePreferences.getAlwaysAddCitedOnPages()); unresolvedKeys = Update.synchronizeDocument(doc, frontend, jStyle, fcursor.get(), syncOptions); - } finally { + } + finally { UnoUndo.leaveUndoContext(doc); fcursor.get().restore(doc); } if (!unresolvedKeys.isEmpty()) { - String msg = Localization.lang( - "Your OpenOffice/LibreOffice document references the citation key '%0'," - + " which could not be found in your current library.", - unresolvedKeys.getFirst()); + String msg = Localization + .lang("Your OpenOffice/LibreOffice document references the citation key '%0'," + + " which could not be found in your current library.", unresolvedKeys.getFirst()); dialogService.showErrorDialogAndWait(errorTitle, msg); } - } catch (NoDocumentException ex) { + } + catch (NoDocumentException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); - } catch (DisposedException ex) { + } + catch (DisposedException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); - } catch (CreationException - | WrappedTargetException - | com.sun.star.lang.IllegalArgumentException ex) { + } + catch (CreationException | WrappedTargetException | com.sun.star.lang.IllegalArgumentException ex) { LOGGER.warn("Could not update JStyle bibliography", ex); OOError.fromMisc(ex).setTitle(errorTitle).showErrorDialog(dialogService); } - } else if (style instanceof CitationStyle citationStyle) { + } + else if (style instanceof CitationStyle citationStyle) { if (!citationStyle.hasBibliography()) { return; } @@ -911,9 +884,7 @@ public void guiActionUpdateDocument(List databases, OOStyle style) OOResult fcursor = getFunctionalTextViewCursor(doc, errorTitle); - if (testDialog(errorTitle, - fcursor.asVoidResult(), - checkIfOpenOfficeIsRecordingChanges(doc))) { + if (testDialog(errorTitle, fcursor.asVoidResult(), checkIfOpenOfficeIsRecordingChanges(doc))) { return; } @@ -932,10 +903,8 @@ public void guiActionUpdateDocument(List databases, OOStyle style) // If no entries are cited, show a message and return if (citedEntries.isEmpty()) { - dialogService.showInformationDialogAndWait( - Localization.lang("Bibliography"), - Localization.lang("No cited entries found in the document.") - ); + dialogService.showInformationDialogAndWait(Localization.lang("Bibliography"), + Localization.lang("No cited entries found in the document.")); return; } @@ -943,27 +912,29 @@ public void guiActionUpdateDocument(List databases, OOStyle style) BibDatabase bibDatabase = new BibDatabase(citedEntries); BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(bibDatabase); - // Lock document controllers - disable refresh during the process (avoids document flicker during writing) + // Lock document controllers - disable refresh during the process + // (avoids document flicker during writing) // MUST always be paired with an unlockControllers() call doc.lockControllers(); - cslUpdateBibliography.rebuildCSLBibliography(doc, cslCitationOOAdapter, citedEntries, citationStyle, bibDatabaseContext, Injector.instantiateModelOrService(BibEntryTypesManager.class)); - } catch (NoDocumentException - | NoSuchElementException - | PropertyVetoException - | UnknownPropertyException e) { + cslUpdateBibliography.rebuildCSLBibliography(doc, cslCitationOOAdapter, citedEntries, citationStyle, + bibDatabaseContext, Injector.instantiateModelOrService(BibEntryTypesManager.class)); + } + catch (NoDocumentException | NoSuchElementException | PropertyVetoException + | UnknownPropertyException e) { throw new RuntimeException(e); - } finally { + } + finally { doc.unlockControllers(); UnoUndo.leaveUndoContext(doc); fcursor.get().restore(doc); } - } catch (CreationException - | WrappedTargetException - | com.sun.star.lang.IllegalArgumentException ex) { + } + catch (CreationException | WrappedTargetException | com.sun.star.lang.IllegalArgumentException ex) { LOGGER.warn("Could not update CSL bibliography", ex); OOError.fromMisc(ex).setTitle(errorTitle).showErrorDialog(dialogService); } } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/openoffice/OOBibBaseConnect.java b/jabgui/src/main/java/org/jabref/gui/openoffice/OOBibBaseConnect.java index 333c9ed3d49..6ef9d94b4ae 100644 --- a/jabgui/src/main/java/org/jabref/gui/openoffice/OOBibBaseConnect.java +++ b/jabgui/src/main/java/org/jabref/gui/openoffice/OOBibBaseConnect.java @@ -41,6 +41,7 @@ public class OOBibBaseConnect { private static final Logger LOGGER = LoggerFactory.getLogger(OOBibBaseConnect.class); private final DialogService dialogService; + private final XDesktop xDesktop; /** @@ -51,18 +52,14 @@ public class OOBibBaseConnect { private XTextDocument xTextDocument; public OOBibBaseConnect(Path loPath, DialogService dialogService) - throws - BootstrapException, - CreationException, IOException, InterruptedException { + throws BootstrapException, CreationException, IOException, InterruptedException { this.dialogService = dialogService; this.xDesktop = simpleBootstrap(loPath); } private XDesktop simpleBootstrap(Path loPath) - throws - CreationException, - BootstrapException, IOException, InterruptedException { + throws CreationException, BootstrapException, IOException, InterruptedException { // Get the office component context: XComponentContext context = Bootstrap.bootstrap(loPath); @@ -73,7 +70,8 @@ private XDesktop simpleBootstrap(Path loPath) Object desktop; try { desktop = sem.createInstanceWithContext("com.sun.star.frame.Desktop", context); - } catch (com.sun.star.uno.Exception e) { + } + catch (com.sun.star.uno.Exception e) { throw new CreationException(e.getMessage()); } return UnoCast.cast(XDesktop.class, desktop).get(); @@ -86,8 +84,7 @@ public static void closeOfficeConnection() { try { // get the bridge factory from the local service manager XBridgeFactory bridgeFactory = queryInterface(XBridgeFactory.class, - Bootstrap.createSimpleServiceManager() - .createInstance("com.sun.star.bridge.BridgeFactory")); + Bootstrap.createSimpleServiceManager().createInstance("com.sun.star.bridge.BridgeFactory")); if (bridgeFactory != null) { for (XBridge bridge : bridgeFactory.getExistingBridges()) { @@ -95,15 +92,14 @@ public static void closeOfficeConnection() { queryInterface(XComponent.class, bridge).dispose(); } } - } catch (com.sun.star.uno.Exception ex) { + } + catch (com.sun.star.uno.Exception ex) { LOGGER.error("Exception disposing office process connection bridge", ex); } } private static List getTextDocuments(XDesktop desktop) - throws - NoSuchElementException, - WrappedTargetException { + throws NoSuchElementException, WrappedTargetException { List result = new ArrayList<>(); @@ -121,15 +117,14 @@ private static List getTextDocuments(XDesktop desktop) /** * Run a dialog allowing the user to choose among the documents in `list`. - * * @return Null if no document was selected. Otherwise the document selected. */ - private static XTextDocument selectDocumentDialog(List list, - DialogService dialogService) { + private static XTextDocument selectDocumentDialog(List list, DialogService dialogService) { class DocumentTitleViewModel { private final XTextDocument xTextDocument; + private final String description; public DocumentTitleViewModel(XTextDocument xTextDocument) { @@ -145,25 +140,21 @@ public XTextDocument getXtextDocument() { public String toString() { return description; } + } List viewModel = list.stream() - .map(DocumentTitleViewModel::new) - .collect(Collectors.toList()); + .map(DocumentTitleViewModel::new) + .collect(Collectors.toList()); // This whole method is part of a background task when // auto-detecting instances, so we need to show dialog in FX // thread - Optional selectedDocument = - dialogService - .showChoiceDialogAndWait(Localization.lang("Select document"), - Localization.lang("Found documents:"), - Localization.lang("Use selected document"), - viewModel); - - return selectedDocument - .map(DocumentTitleViewModel::getXtextDocument) - .orElse(null); + Optional selectedDocument = dialogService.showChoiceDialogAndWait( + Localization.lang("Select document"), Localization.lang("Found documents:"), + Localization.lang("Use selected document"), viewModel); + + return selectedDocument.map(DocumentTitleViewModel::getXtextDocument).orElse(null); } /** @@ -171,27 +162,28 @@ public String toString() { *

    * Assumes we have already connected to LibreOffice or OpenOffice. *

    - * If there is a single document to choose from, selects that. If there are more than one, shows selection dialog. If there are none, throws NoDocumentFoundException + * If there is a single document to choose from, selects that. If there are more than + * one, shows selection dialog. If there are none, throws NoDocumentFoundException *

    - * After successful selection connects to the selected document and extracts some frequently used parts (starting points for managing its content). + * After successful selection connects to the selected document and extracts some + * frequently used parts (starting points for managing its content). *

    - * Finally initializes this.xTextDocument with the selected document and parts extracted. + * Finally initializes this.xTextDocument with the selected document and parts + * extracted. */ public void selectDocument(boolean autoSelectForSingle) - throws - NoDocumentFoundException, - NoSuchElementException, - WrappedTargetException { + throws NoDocumentFoundException, NoSuchElementException, WrappedTargetException { XTextDocument selected; List textDocumentList = getTextDocuments(this.xDesktop); if (textDocumentList.isEmpty()) { throw new NoDocumentFoundException("No Writer documents found"); - } else if ((textDocumentList.size() == 1) && autoSelectForSingle) { + } + else if ((textDocumentList.size() == 1) && autoSelectForSingle) { selected = textDocumentList.getFirst(); // Get the only one - } else { // Bring up a dialog - selected = OOBibBaseConnect.selectDocumentDialog(textDocumentList, - this.dialogService); + } + else { // Bring up a dialog + selected = OOBibBaseConnect.selectDocumentDialog(textDocumentList, this.dialogService); } if (selected == null) { @@ -211,7 +203,8 @@ private void forgetDocument() { /** * A simple test for document availability. *

    - * See also `isDocumentConnectionMissing` for a test actually attempting to use teh connection. + * See also `isDocumentConnectionMissing` for a test actually attempting to use teh + * connection. */ public boolean isConnectedToDocument() { return this.xTextDocument != null; @@ -237,9 +230,7 @@ public boolean isDocumentConnectionMissing() { /** * Either return a valid XTextDocument or throw NoDocumentException. */ - public XTextDocument getXTextDocumentOrThrow() - throws - NoDocumentException { + public XTextDocument getXTextDocumentOrThrow() throws NoDocumentException { if (isDocumentConnectionMissing()) { throw new NoDocumentException("Not connected to document"); } @@ -259,8 +250,10 @@ public OOResult getXTextDocument() { public Optional getCurrentDocumentTitle() { if (isDocumentConnectionMissing()) { return Optional.empty(); - } else { + } + else { return UnoTextDocument.getFrameTitle(this.xTextDocument); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/openoffice/OOError.java b/jabgui/src/main/java/org/jabref/gui/openoffice/OOError.java index c8ad7ef53aa..244458b4b11 100644 --- a/jabgui/src/main/java/org/jabref/gui/openoffice/OOError.java +++ b/jabgui/src/main/java/org/jabref/gui/openoffice/OOError.java @@ -40,49 +40,35 @@ public void showErrorDialog(DialogService dialogService) { */ public static OOError from(JabRefException err) { - return new OOError( - Localization.lang("JabRefException"), - err.getLocalizedMessage(), - err); + return new OOError(Localization.lang("JabRefException"), err.getLocalizedMessage(), err); } // For DisposedException public static OOError from(DisposedException err) { - return new OOError( - Localization.lang("Connection lost"), + return new OOError(Localization.lang("Connection lost"), Localization.lang("Connection to OpenOffice/LibreOffice has been lost." - + " Please make sure OpenOffice/LibreOffice is running," - + " and try to reconnect."), + + " Please make sure OpenOffice/LibreOffice is running," + " and try to reconnect."), err); } // For NoDocumentException public static OOError from(NoDocumentException err) { - return new OOError( - Localization.lang("Not connected to document"), - Localization.lang("Not connected to any Writer document." - + " Please make sure a document is open," - + " and use the 'Select Writer document' button" - + " to connect to it."), + return new OOError(Localization.lang("Not connected to document"), + Localization.lang("Not connected to any Writer document." + " Please make sure a document is open," + + " and use the 'Select Writer document' button" + " to connect to it."), err); } // For NoDocumentFoundException public static OOError from(NoDocumentFoundException err) { - return new OOError( - Localization.lang("No Writer documents found"), - Localization.lang("Could not connect to any Writer document." - + " Please make sure a document is open" - + " before using the 'Select Writer document' button" - + " to connect to it."), + return new OOError(Localization.lang("No Writer documents found"), + Localization.lang("Could not connect to any Writer document." + " Please make sure a document is open" + + " before using the 'Select Writer document' button" + " to connect to it."), err); } public static OOError fromMisc(Exception err) { - return new OOError( - "Exception", - err.getMessage(), - err); + return new OOError("Exception", err.getMessage(), err); } /* @@ -91,51 +77,42 @@ public static OOError fromMisc(Exception err) { // noDataBaseIsOpenForCiting public static OOError noDataBaseIsOpenForCiting() { - return new OOError( - Localization.lang("No database"), - Localization.lang("No bibliography database is open for citation.") - + "\n" + return new OOError(Localization.lang("No database"), + Localization.lang("No bibliography database is open for citation.") + "\n" + Localization.lang("Open one before citing.")); } public static OOError noDataBaseIsOpenForSyncingAfterCitation() { - return new OOError( - Localization.lang("No database"), - Localization.lang("No database is open for updating citation markers after citing.") - + "\n" + return new OOError(Localization.lang("No database"), + Localization.lang("No database is open for updating citation markers after citing.") + "\n" + Localization.lang("Open one before citing.")); } // noDataBaseIsOpenForExport public static OOError noDataBaseIsOpenForExport() { - return new OOError( - Localization.lang("No database is open"), + return new OOError(Localization.lang("No database is open"), Localization.lang("We need a database to export from. Open one.")); } // noDataBaseIsOpenForExport public static OOError noDataBaseIsOpen() { - return new OOError( - Localization.lang("No database is open"), + return new OOError(Localization.lang("No database is open"), Localization.lang("This operation requires a bibliography database.")); } // noValidStyleSelected public static OOError noValidStyleSelected() { return new OOError(Localization.lang("No valid style file defined"), - Localization.lang("No bibliography style is selected for citation.") - + "\n" - + Localization.lang("Select one before citing.") - + "\n" - + Localization.lang("You must select either a valid style file," - + " or use one of the default styles.")); + Localization.lang("No bibliography style is selected for citation.") + "\n" + + Localization.lang("Select one before citing.") + "\n" + Localization + .lang("You must select either a valid style file," + " or use one of the default styles.")); } // noEntriesSelectedForCitation public static OOError noEntriesSelectedForCitation() { return new OOError(Localization.lang("No entries selected for citation"), - Localization.lang("No bibliography entries are selected for citation.") - + "\n" + Localization.lang("No bibliography entries are selected for citation.") + "\n" + Localization.lang("Select some before citing.")); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java b/jabgui/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java index 5ee9823a118..29f255d3383 100644 --- a/jabgui/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java +++ b/jabgui/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java @@ -79,59 +79,84 @@ public class OpenOfficePanel { private static final Logger LOGGER = LoggerFactory.getLogger(OpenOfficePanel.class); + private final DialogService dialogService; private final Button connect; + private final Button manualConnect; + private final Button selectDocument; + private final Button setStyleFile = new Button(Localization.lang("Select style")); + private final Button pushEntries = new Button(Localization.lang("Cite")); + private final Button pushEntriesInt = new Button(Localization.lang("Cite in-text")); + private final Button pushEntriesEmpty = new Button(Localization.lang("Insert empty citation")); + private final Button pushEntriesAdvanced = new Button(Localization.lang("Cite special")); + private final Button update; + private final Button merge = new Button(Localization.lang("Merge citations")); + private final Button unmerge = new Button(Localization.lang("Separate citations")); + private final Button manageCitations = new Button(Localization.lang("Manage citations")); + private final Button exportCitations = new Button(Localization.lang("Export cited")); + private final Button modifyBibliographyProperties = new Button(Localization.lang("Bibliography properties")); + private final Button settingsB = new Button(Localization.lang("Settings")); + private final Button help; + private final VBox vbox = new VBox(); private final GuiPreferences preferences; + private final OpenOfficePreferences openOfficePreferences; + private final CitationKeyPatternPreferences citationKeyPatternPreferences; + private final StateManager stateManager; + private final ClipBoardManager clipBoardManager; + private final UndoManager undoManager; + private final UiTaskExecutor taskExecutor; + private final AiService aiService; + private final JStyleLoader jStyleLoader; + private final CSLStyleLoader cslStyleLoader; + private final LibraryTabContainer tabContainer; + private final FileUpdateMonitor fileUpdateMonitor; + private final BibEntryTypesManager entryTypesManager; + private OOBibBase ooBase; + private OOStyle currentStyle; private final SimpleObjectProperty currentStyleProperty; - public OpenOfficePanel(LibraryTabContainer tabContainer, - GuiPreferences preferences, - OpenOfficePreferences openOfficePreferences, - ExternalApplicationsPreferences externalApplicationsPreferences, - LayoutFormatterPreferences layoutFormatterPreferences, - CitationKeyPatternPreferences citationKeyPatternPreferences, - JournalAbbreviationRepository abbreviationRepository, - UiTaskExecutor taskExecutor, - DialogService dialogService, - AiService aiService, - StateManager stateManager, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager, - ClipBoardManager clipBoardManager, - UndoManager undoManager) { + public OpenOfficePanel(LibraryTabContainer tabContainer, GuiPreferences preferences, + OpenOfficePreferences openOfficePreferences, + ExternalApplicationsPreferences externalApplicationsPreferences, + LayoutFormatterPreferences layoutFormatterPreferences, + CitationKeyPatternPreferences citationKeyPatternPreferences, + JournalAbbreviationRepository abbreviationRepository, UiTaskExecutor taskExecutor, + DialogService dialogService, AiService aiService, StateManager stateManager, + FileUpdateMonitor fileUpdateMonitor, BibEntryTypesManager entryTypesManager, + ClipBoardManager clipBoardManager, UndoManager undoManager) { this.tabContainer = tabContainer; this.fileUpdateMonitor = fileUpdateMonitor; this.entryTypesManager = entryTypesManager; @@ -148,10 +173,7 @@ public OpenOfficePanel(LibraryTabContainer tabContainer, this.currentStyleProperty = new SimpleObjectProperty<>(currentStyle); - jStyleLoader = new JStyleLoader( - openOfficePreferences, - layoutFormatterPreferences, - abbreviationRepository); + jStyleLoader = new JStyleLoader(openOfficePreferences, layoutFormatterPreferences, abbreviationRepository); cslStyleLoader = new CSLStyleLoader(openOfficePreferences); @@ -167,7 +189,8 @@ public OpenOfficePanel(LibraryTabContainer tabContainer, manualConnect.setTooltip(new Tooltip(Localization.lang("Manual connect"))); manualConnect.setMaxWidth(Double.MAX_VALUE); - help = factory.createIconButton(StandardActions.HELP, new HelpAction(HelpFile.OPENOFFICE_LIBREOFFICE, dialogService, externalApplicationsPreferences)); + help = factory.createIconButton(StandardActions.HELP, + new HelpAction(HelpFile.OPENOFFICE_LIBREOFFICE, dialogService, externalApplicationsPreferences)); help.setMaxWidth(Double.MAX_VALUE); selectDocument = new Button(); @@ -187,7 +210,8 @@ public Node getContent() { return vbox; } - /* Note: the style may still be null on return. + /* + * Note: the style may still be null on return. * * Return true if failed. In this case the dialog is already shown. */ @@ -198,20 +222,22 @@ private boolean getOrUpdateTheStyle(String title) { if (currentStyle == null) { currentStyle = openOfficePreferences.getCurrentStyle(); currentStyleProperty.set(currentStyle); - } else { + } + else { if (currentStyle instanceof JStyle jStyle) { try { jStyle = jStyleLoader.getUsedJstyle(); jStyle.ensureUpToDate(); - } catch (IOException ex) { + } + catch (IOException ex) { LOGGER.warn("Unable to reload style file '{}'", jStyle.getPath(), ex); - String msg = Localization.lang("Unable to reload style file") - + "'" + jStyle.getPath() + "'" - + "\n" + ex.getMessage(); + String msg = Localization.lang("Unable to reload style file") + "'" + jStyle.getPath() + "'" + "\n" + + ex.getMessage(); new OOError(title, msg, ex).showErrorDialog(dialogService); return FAIL; } - } else { + } + else { // CSL Styles don't need to be updated return PASS; } @@ -227,8 +253,8 @@ private void initPanel() { selectDocument.setOnAction(_ -> { try { ooBase.guiActionSelectDocument(false); - } catch (WrappedTargetException - | NoSuchElementException ex) { + } + catch (WrappedTargetException | NoSuchElementException ex) { throw new RuntimeException(ex); } }); @@ -236,23 +262,24 @@ private void initPanel() { setStyleFile.setMaxWidth(Double.MAX_VALUE); setStyleFile.setOnAction(_ -> { StyleSelectDialogView styleDialog = new StyleSelectDialogView(cslStyleLoader, jStyleLoader); - dialogService.showCustomDialogAndWait(styleDialog) - .ifPresent(selectedStyle -> { - currentStyle = selectedStyle; - currentStyleProperty.set(currentStyle); - - if (currentStyle instanceof JStyle jStyle) { - try { - jStyle.ensureUpToDate(); - } catch (IOException e) { - LOGGER.warn("Unable to reload style file '{}'", jStyle.getPath(), e); - } - dialogService.notify(Localization.lang("Currently selected JStyle: '%0'", jStyle.getName())); - } else if (currentStyle instanceof CitationStyle cslStyle) { - dialogService.notify(Localization.lang("Currently selected CSL Style: '%0'", cslStyle.getName())); - } - updateButtonAvailability(); - }); + dialogService.showCustomDialogAndWait(styleDialog).ifPresent(selectedStyle -> { + currentStyle = selectedStyle; + currentStyleProperty.set(currentStyle); + + if (currentStyle instanceof JStyle jStyle) { + try { + jStyle.ensureUpToDate(); + } + catch (IOException e) { + LOGGER.warn("Unable to reload style file '{}'", jStyle.getPath(), e); + } + dialogService.notify(Localization.lang("Currently selected JStyle: '%0'", jStyle.getName())); + } + else if (currentStyle instanceof CitationStyle cslStyle) { + dialogService.notify(Localization.lang("Currently selected CSL Style: '%0'", cslStyle.getName())); + } + updateButtonAvailability(); + }); }); pushEntries.setTooltip(new Tooltip(Localization.lang("Cite selected entries between parenthesis"))); @@ -261,7 +288,8 @@ private void initPanel() { pushEntriesInt.setTooltip(new Tooltip(Localization.lang("Cite selected entries with in-text citation"))); pushEntriesInt.setOnAction(_ -> pushEntries(CitationType.AUTHORYEAR_INTEXT, false)); pushEntriesInt.setMaxWidth(Double.MAX_VALUE); - pushEntriesEmpty.setTooltip(new Tooltip(Localization.lang("Insert a citation without text (the entry will appear in the reference list)"))); + pushEntriesEmpty.setTooltip(new Tooltip( + Localization.lang("Insert a citation without text (the entry will appear in the reference list)"))); pushEntriesEmpty.setOnAction(_ -> pushEntries(CitationType.INVISIBLE_CIT, false)); pushEntriesEmpty.setMaxWidth(Double.MAX_VALUE); pushEntriesAdvanced.setTooltip(new Tooltip(Localization.lang("Cite selected entries with extra information"))); @@ -280,7 +308,8 @@ private void initPanel() { }); merge.setMaxWidth(Double.MAX_VALUE); - merge.setTooltip(new Tooltip(Localization.lang("Combine pairs of citations that are separated by spaces only"))); + merge + .setTooltip(new Tooltip(Localization.lang("Combine pairs of citations that are separated by spaces only"))); merge.setOnAction(_ -> ooBase.guiActionMergeCitationGroups(getBaseList(), currentStyle)); unmerge.setMaxWidth(Double.MAX_VALUE); @@ -325,7 +354,8 @@ private void initPanel() { } private void modifyBibliographyProperties() { - ModifyCSLBibliographyPropertiesDialogView modifyBibliographyPropertiesDialogView = new ModifyCSLBibliographyPropertiesDialogView(openOfficePreferences); + ModifyCSLBibliographyPropertiesDialogView modifyBibliographyPropertiesDialogView = new ModifyCSLBibliographyPropertiesDialogView( + openOfficePreferences); dialogService.showCustomDialog(modifyBibliographyPropertiesDialogView); } @@ -335,17 +365,8 @@ private void exportEntries() { Optional newDatabase = ooBase.exportCitedHelper(databases, returnPartialResult); if (newDatabase.isPresent()) { BibDatabaseContext databaseContext = new BibDatabaseContext(newDatabase.get()); - LibraryTab libraryTab = LibraryTab.createLibraryTab( - databaseContext, - tabContainer, - dialogService, - aiService, - preferences, - stateManager, - fileUpdateMonitor, - entryTypesManager, - undoManager, - clipBoardManager, + LibraryTab libraryTab = LibraryTab.createLibraryTab(databaseContext, tabContainer, dialogService, aiService, + preferences, stateManager, fileUpdateMonitor, entryTypesManager, undoManager, clipBoardManager, taskExecutor); tabContainer.addTab(libraryTab, true); } @@ -357,21 +378,23 @@ private List getBaseList() { for (BibDatabaseContext database : stateManager.getOpenDatabases()) { databases.add(database.getDatabase()); } - } else { - databases.add(stateManager.getActiveDatabase() - .map(BibDatabaseContext::getDatabase) - .orElse(new BibDatabase())); + } + else { + databases + .add(stateManager.getActiveDatabase().map(BibDatabaseContext::getDatabase).orElse(new BibDatabase())); } return databases; } private void connectAutomatically() { - DetectOpenOfficeInstallation officeInstallation = new DetectOpenOfficeInstallation(openOfficePreferences, dialogService); + DetectOpenOfficeInstallation officeInstallation = new DetectOpenOfficeInstallation(openOfficePreferences, + dialogService); if (officeInstallation.isExecutablePathDefined()) { connect(); - } else { + } + else { Task> taskConnectIfInstalled = new Task<>() { @Override protected List call() { @@ -385,37 +408,51 @@ protected List call() { officeInstallation.selectInstallationPath().ifPresent(installations::add); } Optional chosenInstallationDirectory = officeInstallation.chooseAmongInstallations(installations); - if (chosenInstallationDirectory.isPresent() && officeInstallation.setOpenOfficePreferences(chosenInstallationDirectory.get())) { + if (chosenInstallationDirectory.isPresent() + && officeInstallation.setOpenOfficePreferences(chosenInstallationDirectory.get())) { connect(); } }); - taskConnectIfInstalled.setOnFailed(_ -> dialogService.showErrorDialogAndWait(Localization.lang("Autodetection failed"), Localization.lang("Autodetection failed"), taskConnectIfInstalled.getException())); + taskConnectIfInstalled + .setOnFailed(_ -> dialogService.showErrorDialogAndWait(Localization.lang("Autodetection failed"), + Localization.lang("Autodetection failed"), taskConnectIfInstalled.getException())); - dialogService.showProgressDialog(Localization.lang("Autodetecting paths..."), Localization.lang("Autodetecting paths..."), taskConnectIfInstalled); + dialogService.showProgressDialog(Localization.lang("Autodetecting paths..."), + Localization.lang("Autodetecting paths..."), taskConnectIfInstalled); taskExecutor.execute(taskConnectIfInstalled); } } private void connectManually() { - DirectoryDialogConfiguration fileDialogConfiguration = new DirectoryDialogConfiguration.Builder().withInitialDirectory(System.getProperty("user.home")).build(); + DirectoryDialogConfiguration fileDialogConfiguration = new DirectoryDialogConfiguration.Builder() + .withInitialDirectory(System.getProperty("user.home")) + .build(); Optional selectedPath = dialogService.showDirectorySelectionDialog(fileDialogConfiguration); - DetectOpenOfficeInstallation officeInstallation = new DetectOpenOfficeInstallation(openOfficePreferences, dialogService); + DetectOpenOfficeInstallation officeInstallation = new DetectOpenOfficeInstallation(openOfficePreferences, + dialogService); if (selectedPath.isPresent()) { BackgroundTask.wrap(() -> officeInstallation.setOpenOfficePreferences(selectedPath.get())) - .withInitialMessage("Searching for executable") - .onFailure(dialogService::showErrorDialogAndWait).onSuccess(value -> { - if (value) { - connect(); - } else { - dialogService.showErrorDialogAndWait(Localization.lang("Could not connect to running OpenOffice/LibreOffice."), Localization.lang("If connecting manually, please verify program and library paths.")); - } - }) - .executeWith(taskExecutor); - } else { - dialogService.showErrorDialogAndWait(Localization.lang("Could not connect to running OpenOffice/LibreOffice."), Localization.lang("If connecting manually, please verify program and library paths.")); + .withInitialMessage("Searching for executable") + .onFailure(dialogService::showErrorDialogAndWait) + .onSuccess(value -> { + if (value) { + connect(); + } + else { + dialogService.showErrorDialogAndWait( + Localization.lang("Could not connect to running OpenOffice/LibreOffice."), + Localization.lang("If connecting manually, please verify program and library paths.")); + } + }) + .executeWith(taskExecutor); + } + else { + dialogService.showErrorDialogAndWait( + Localization.lang("Could not connect to running OpenOffice/LibreOffice."), + Localization.lang("If connecting manually, please verify program and library paths.")); } } @@ -426,7 +463,8 @@ private void updateButtonAvailability() { boolean canCite = isConnectedToDocument && hasStyle && hasDatabase; boolean canRefreshDocument = isConnectedToDocument && hasStyle; boolean cslStyleSelected = currentStyle instanceof CitationStyle; - boolean canGenerateBibliography = (currentStyle instanceof JStyle) || (currentStyle instanceof CitationStyle citationStyle && citationStyle.hasBibliography()); + boolean canGenerateBibliography = (currentStyle instanceof JStyle) + || (currentStyle instanceof CitationStyle citationStyle && citationStyle.hasBibliography()); selectDocument.setDisable(!isConnectedToDocument); @@ -459,8 +497,8 @@ protected OOBibBase call() throws BootstrapException, CreationException, IOExcep try { ooBase.guiActionSelectDocument(true); - } catch (WrappedTargetException - | NoSuchElementException e) { + } + catch (WrappedTargetException | NoSuchElementException e) { throw new RuntimeException(e); } @@ -475,50 +513,55 @@ protected OOBibBase call() throws BootstrapException, CreationException, IOExcep case UnsatisfiedLinkError unsatisfiedLinkError -> { LOGGER.warn("Could not connect to running OpenOffice/LibreOffice", unsatisfiedLinkError); - dialogService.showErrorDialogAndWait(Localization.lang("Unable to connect. One possible reason is that JabRef " - + "and OpenOffice/LibreOffice are not both running in either 32 bit mode or 64 bit mode.")); + dialogService.showErrorDialogAndWait(Localization + .lang("Unable to connect. One possible reason is that JabRef " + + "and OpenOffice/LibreOffice are not both running in either 32 bit mode or 64 bit mode.")); } case IOException ioException -> { LOGGER.warn("Could not connect to running OpenOffice/LibreOffice", ioException); - dialogService.showErrorDialogAndWait(Localization.lang("Could not connect to running OpenOffice/LibreOffice."), - Localization.lang("Could not connect to running OpenOffice/LibreOffice.") + dialogService.showErrorDialogAndWait( + Localization.lang("Could not connect to running OpenOffice/LibreOffice."), + Localization.lang("Could not connect to running OpenOffice/LibreOffice.") + "\n" + + Localization + .lang("Make sure you have installed OpenOffice/LibreOffice with Java support.") + "\n" - + Localization.lang("Make sure you have installed OpenOffice/LibreOffice with Java support.") + "\n" - + Localization.lang("If connecting manually, please verify program and library paths.") + "\n" + "\n" + Localization.lang("Error message:"), + + Localization + .lang("If connecting manually, please verify program and library paths.") + + "\n" + "\n" + Localization.lang("Error message:"), ex); } case BootstrapException bootstrapEx -> { LOGGER.error("Exception boostrap cause", bootstrapEx.getTargetException()); dialogService.showErrorDialogAndWait("Bootstrap error", bootstrapEx.getTargetException()); } - case null, - default -> - dialogService.showErrorDialogAndWait(Localization.lang("Autodetection failed"), Localization.lang("Autodetection failed"), ex); + case null, default -> dialogService.showErrorDialogAndWait(Localization.lang("Autodetection failed"), + Localization.lang("Autodetection failed"), ex); } }); - dialogService.showProgressDialog(Localization.lang("Autodetecting paths..."), Localization.lang("Autodetecting paths..."), connectTask); + dialogService.showProgressDialog(Localization.lang("Autodetecting paths..."), + Localization.lang("Autodetecting paths..."), connectTask); taskExecutor.execute(connectTask); } - private OOBibBase createBibBase(Path loPath) throws BootstrapException, CreationException, IOException, InterruptedException { + private OOBibBase createBibBase(Path loPath) + throws BootstrapException, CreationException, IOException, InterruptedException { return new OOBibBase(loPath, dialogService, openOfficePreferences); } /** - * Given the withText and inParenthesis options, return the corresponding citationType. - * - * @param withText False means invisible citation (no text). - * @param inParenthesis True means "(Au and Thor 2000)". False means "Au and Thor (2000)". + * Given the withText and inParenthesis options, return the corresponding + * citationType. + * @param withText False means invisible citation (no text). + * @param inParenthesis True means "(Au and Thor 2000)". False means "Au and Thor + * (2000)". */ private static CitationType citationTypeFromOptions(boolean withText, boolean inParenthesis) { if (!withText) { return CitationType.INVISIBLE_CIT; } - return inParenthesis - ? CitationType.AUTHORYEAR_PAR - : CitationType.AUTHORYEAR_INTEXT; + return inParenthesis ? CitationType.AUTHORYEAR_PAR : CitationType.AUTHORYEAR_INTEXT; } private void pushEntries(CitationType citationType, boolean addPageInfo) { @@ -527,9 +570,7 @@ private void pushEntries(CitationType citationType, boolean addPageInfo) { final Optional activeDatabase = stateManager.getActiveDatabase(); if (activeDatabase.isEmpty() || (activeDatabase.get().getDatabase() == null)) { - OOError.noDataBaseIsOpenForCiting() - .setTitle(errorDialogTitle) - .showErrorDialog(dialogService); + OOError.noDataBaseIsOpenForCiting().setTitle(errorDialogTitle).showErrorDialog(dialogService); return; } @@ -538,9 +579,7 @@ private void pushEntries(CitationType citationType, boolean addPageInfo) { List entries = stateManager.getSelectedEntries(); if (entries.isEmpty()) { - OOError.noEntriesSelectedForCitation() - .setTitle(errorDialogTitle) - .showErrorDialog(dialogService); + OOError.noEntriesSelectedForCitation().setTitle(errorDialogTitle).showErrorDialog(dialogService); return; } @@ -552,14 +591,16 @@ private void pushEntries(CitationType citationType, boolean addPageInfo) { if (addPageInfo) { boolean withText = citationType.withText(); - Optional citeDialogViewModel = dialogService.showCustomDialogAndWait(new AdvancedCiteDialogView()); + Optional citeDialogViewModel = dialogService + .showCustomDialogAndWait(new AdvancedCiteDialogView()); if (citeDialogViewModel.isPresent()) { AdvancedCiteDialogViewModel model = citeDialogViewModel.get(); if (!model.pageInfoProperty().getValue().isEmpty()) { pageInfo = model.pageInfoProperty().getValue(); } citationType = citationTypeFromOptions(withText, model.citeInParProperty().getValue()); - } else { + } + else { // user canceled return; } @@ -570,29 +611,24 @@ private void pushEntries(CitationType citationType, boolean addPageInfo) { return; } - Optional syncOptions = - openOfficePreferences.getSyncWhenCiting() - ? Optional.of(new Update.SyncOptions(getBaseList())) - : Optional.empty(); + Optional syncOptions = openOfficePreferences.getSyncWhenCiting() + ? Optional.of(new Update.SyncOptions(getBaseList())) : Optional.empty(); - // Sync options are non-null only when "Automatically sync bibliography when inserting citations" is enabled + // Sync options are non-null only when "Automatically sync bibliography when + // inserting citations" is enabled if (syncOptions.isPresent() && openOfficePreferences.getSyncWhenCiting()) { syncOptions.get().setUpdateBibliography(true); } - ooBase.guiActionInsertEntry(entries, - bibDatabaseContext, - entryTypesManager, - currentStyle, - citationType, - pageInfo, - syncOptions); + ooBase.guiActionInsertEntry(entries, bibDatabaseContext, entryTypesManager, currentStyle, citationType, + pageInfo, syncOptions); } /** - * Check that all entries in the list have citation keys, if not ask if they should be generated - * + * Check that all entries in the list have citation keys, if not ask if they should be + * generated * @param entries A list of entries to be checked - * @return true if all entries have citation keys, if it so may be after generating them + * @return true if all entries have citation keys, if it so may be after generating + * them */ private boolean checkThatEntriesHaveKeys(List entries) { // Check if there are empty keys @@ -613,8 +649,7 @@ private boolean checkThatEntriesHaveKeys(List entries) { // Ask if keys should be generated boolean citePressed = dialogService.showConfirmationDialogAndWait(Localization.lang("Cite"), Localization.lang("Cannot cite entries without citation keys. Generate keys now?"), - Localization.lang("Generate keys"), - Localization.lang("Cancel")); + Localization.lang("Generate keys"), Localization.lang("Cancel")); Optional databaseContext = stateManager.getActiveDatabase(); if (citePressed && databaseContext.isPresent()) { @@ -624,8 +659,8 @@ private boolean checkThatEntriesHaveKeys(List entries) { if (entry.getCitationKey().isEmpty()) { // Generate key new CitationKeyGenerator(databaseContext.get(), citationKeyPatternPreferences) - .generateAndSetKey(entry) - .ifPresent(change -> undoCompound.addEdit(new UndoableKeyChange(change))); + .generateAndSetKey(entry) + .ifPresent(change -> undoCompound.addEdit(new UndoableKeyChange(change))); } } undoCompound.end(); @@ -633,8 +668,10 @@ private boolean checkThatEntriesHaveKeys(List entries) { undoManager.addEdit(undoCompound); // Now every entry has a key return true; - } else { - // No, we canceled (or there is no panel to get the database from, highly unlikely) + } + else { + // No, we canceled (or there is no panel to get the database from, highly + // unlikely) return false; } } @@ -642,16 +679,19 @@ private boolean checkThatEntriesHaveKeys(List entries) { private ContextMenu createSettingsPopup() { ContextMenu contextMenu = new ContextMenu(); - CheckMenuItem autoSync = new CheckMenuItem(Localization.lang("Automatically sync bibliography when inserting citations")); + CheckMenuItem autoSync = new CheckMenuItem( + Localization.lang("Automatically sync bibliography when inserting citations")); autoSync.selectedProperty().set(openOfficePreferences.getSyncWhenCiting()); CheckMenuItem addSpaceAfter = new CheckMenuItem(Localization.lang("Add space after citation")); addSpaceAfter.selectedProperty().set(openOfficePreferences.getAddSpaceAfter()); addSpaceAfter.setOnAction(_ -> openOfficePreferences.setAddSpaceAfter(addSpaceAfter.isSelected())); - CheckMenuItem alwaysAddCitedOnPagesText = new CheckMenuItem(Localization.lang("Automatically add \"Cited on pages...\" at the end of bibliographic entries")); + CheckMenuItem alwaysAddCitedOnPagesText = new CheckMenuItem( + Localization.lang("Automatically add \"Cited on pages...\" at the end of bibliographic entries")); alwaysAddCitedOnPagesText.selectedProperty().set(openOfficePreferences.getAlwaysAddCitedOnPages()); - alwaysAddCitedOnPagesText.setOnAction(_ -> openOfficePreferences.setAlwaysAddCitedOnPages(alwaysAddCitedOnPagesText.isSelected())); + alwaysAddCitedOnPagesText + .setOnAction(_ -> openOfficePreferences.setAlwaysAddCitedOnPages(alwaysAddCitedOnPagesText.isSelected())); EasyBind.listen(currentStyleProperty, (_, _, newValue) -> { switch (newValue) { @@ -660,15 +700,17 @@ private ContextMenu createSettingsPopup() { contextMenu.getItems().add(1, alwaysAddCitedOnPagesText); } } - case CitationStyle _ -> - contextMenu.getItems().remove(alwaysAddCitedOnPagesText); - default -> { } + case CitationStyle _ -> contextMenu.getItems().remove(alwaysAddCitedOnPagesText); + default -> { + } } }); ToggleGroup toggleGroup = new ToggleGroup(); - RadioMenuItem useActiveBase = new RadioMenuItem(Localization.lang("Look up BibTeX entries in the active tab only")); - RadioMenuItem useAllBases = new RadioMenuItem(Localization.lang("Look up BibTeX entries in all open libraries")); + RadioMenuItem useActiveBase = new RadioMenuItem( + Localization.lang("Look up BibTeX entries in the active tab only")); + RadioMenuItem useAllBases = new RadioMenuItem( + Localization.lang("Look up BibTeX entries in all open libraries")); useActiveBase.setToggleGroup(toggleGroup); useAllBases.setToggleGroup(toggleGroup); @@ -676,7 +718,8 @@ private ContextMenu createSettingsPopup() { if (openOfficePreferences.getUseAllDatabases()) { useAllBases.setSelected(true); - } else { + } + else { useActiveBase.setSelected(true); } @@ -688,14 +731,9 @@ private ContextMenu createSettingsPopup() { dialogService.notify(Localization.lang("Cleared connection settings")); }); - contextMenu.getItems().addAll( - autoSync, - addSpaceAfter, - new SeparatorMenuItem(), - useActiveBase, - useAllBases, - new SeparatorMenuItem(), - clearConnectionSettings); + contextMenu.getItems() + .addAll(autoSync, addSpaceAfter, new SeparatorMenuItem(), useActiveBase, useAllBases, + new SeparatorMenuItem(), clearConnectionSettings); if (currentStyle instanceof JStyle) { contextMenu.getItems().add(1, alwaysAddCitedOnPagesText); @@ -703,4 +741,5 @@ private ContextMenu createSettingsPopup() { return contextMenu; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java b/jabgui/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java index d0277a77d3b..8bbd3725a81 100644 --- a/jabgui/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java +++ b/jabgui/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java @@ -50,46 +50,90 @@ public class StyleSelectDialogView extends BaseDialog { private final MenuItem edit = new MenuItem(Localization.lang("Edit")); + private final MenuItem reload = new MenuItem(Localization.lang("Reload")); private final CSLStyleLoader cslStyleLoader; + private final JStyleLoader jStyleLoader; - @FXML private Tab cslStyleTab; - @FXML private Tab jStyleTab; + @FXML + private Tab cslStyleTab; + + @FXML + private Tab jStyleTab; // CSL Styles TableView - @FXML private TableView cslStylesTable; - @FXML private TableColumn cslNameColumn; - @FXML private TableColumn cslPathColumn; - @FXML private TableColumn cslDeleteColumn; + @FXML + private TableView cslStylesTable; + + @FXML + private TableColumn cslNameColumn; + + @FXML + private TableColumn cslPathColumn; + + @FXML + private TableColumn cslDeleteColumn; // JStyles TableView - @FXML private TableView jStylesTable; - @FXML private TableColumn jStyleNameColumn; - @FXML private TableColumn jStyleJournalColumn; - @FXML private TableColumn jStyleFileColumn; - @FXML private TableColumn jStyleDeleteColumn; + @FXML + private TableView jStylesTable; + + @FXML + private TableColumn jStyleNameColumn; + + @FXML + private TableColumn jStyleJournalColumn; + + @FXML + private TableColumn jStyleFileColumn; + + @FXML + private TableColumn jStyleDeleteColumn; + + @FXML + private Button addCslButton; + + @FXML + private Button addJStyleButton; - @FXML private Button addCslButton; - @FXML private Button addJStyleButton; + @FXML + private VBox cslPreviewBox; - @FXML private VBox cslPreviewBox; - @FXML private VBox jStylePreviewBox; + @FXML + private VBox jStylePreviewBox; private final AtomicBoolean initialScrollPerformed = new AtomicBoolean(false); - @FXML private CustomTextField searchBox; - @FXML private TabPane tabPane; - @FXML private Label currentStyleNameLabel; - @Inject private GuiPreferences preferences; - @Inject private DialogService dialogService; - @Inject private ThemeManager themeManager; - @Inject private TaskExecutor taskExecutor; - @Inject private BibEntryTypesManager bibEntryTypesManager; + @FXML + private CustomTextField searchBox; + + @FXML + private TabPane tabPane; + + @FXML + private Label currentStyleNameLabel; + + @Inject + private GuiPreferences preferences; + + @Inject + private DialogService dialogService; + + @Inject + private ThemeManager themeManager; + + @Inject + private TaskExecutor taskExecutor; + + @Inject + private BibEntryTypesManager bibEntryTypesManager; private StyleSelectDialogViewModel viewModel; + private PreviewViewer previewArticle; + private PreviewViewer previewBook; /** @@ -100,9 +144,7 @@ public StyleSelectDialogView(CSLStyleLoader cslStyleLoader, JStyleLoader jStyleL this.cslStyleLoader = cslStyleLoader; this.jStyleLoader = jStyleLoader; - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); setResultConverter(button -> { if (button == ButtonType.OK) { @@ -116,20 +158,26 @@ public StyleSelectDialogView(CSLStyleLoader cslStyleLoader, JStyleLoader jStyleL @FXML private void initialize() { - viewModel = new StyleSelectDialogViewModel(dialogService, cslStyleLoader, jStyleLoader, preferences, taskExecutor, bibEntryTypesManager); + viewModel = new StyleSelectDialogViewModel(dialogService, cslStyleLoader, jStyleLoader, preferences, + taskExecutor, bibEntryTypesManager); setupCslStylesTab(); setupJStylesTab(); - OOStyle currentStyle = preferences.getOpenOfficePreferences(Injector.instantiateModelOrService(JournalAbbreviationRepository.class)).getCurrentStyle(); + OOStyle currentStyle = preferences + .getOpenOfficePreferences(Injector.instantiateModelOrService(JournalAbbreviationRepository.class)) + .getCurrentStyle(); if (currentStyle instanceof CitationStyle) { tabPane.getSelectionModel().select(cslStyleTab); - } else { + } + else { tabPane.getSelectionModel().select(jStyleTab); } viewModel.setSelectedTab(tabPane.getSelectionModel().getSelectedItem()); - tabPane.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> viewModel.setSelectedTab(newValue)); + tabPane.getSelectionModel() + .selectedItemProperty() + .addListener((_, _, newValue) -> viewModel.setSelectedTab(newValue)); updateCurrentStyleLabel(); addCslButton.setGraphic(IconTheme.JabRefIcons.ADD.getGraphicNode()); @@ -144,26 +192,24 @@ private void setupCslStylesTab() { cslDeleteColumn.setCellValueFactory(cellData -> cellData.getValue().internalStyleProperty()); new ValueTableCellFactory() - .withGraphic(internalStyle -> internalStyle ? null : IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) - .withOnMouseClickedEvent(item -> evt -> { - CSLStyleSelectViewModel selectedStyle = cslStylesTable.getSelectionModel().getSelectedItem(); - if (selectedStyle != null) { - viewModel.deleteCslStyle(selectedStyle.getLayout().getCitationStyle()); - } - }) - .withTooltip(item -> Localization.lang("Remove style")) - .install(cslDeleteColumn); - - new ViewModelTableRowFactory() - .withOnMouseClickedEvent((item, event) -> { - if (event.getClickCount() == 2) { - viewModel.selectedCslLayoutProperty().set(item.getLayout()); - viewModel.storeStylePreferences(); - this.setResult(viewModel.getSelectedStyle()); - this.close(); - } - }) - .install(cslStylesTable); + .withGraphic(internalStyle -> internalStyle ? null : IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) + .withOnMouseClickedEvent(item -> evt -> { + CSLStyleSelectViewModel selectedStyle = cslStylesTable.getSelectionModel().getSelectedItem(); + if (selectedStyle != null) { + viewModel.deleteCslStyle(selectedStyle.getLayout().getCitationStyle()); + } + }) + .withTooltip(item -> Localization.lang("Remove style")) + .install(cslDeleteColumn); + + new ViewModelTableRowFactory().withOnMouseClickedEvent((item, event) -> { + if (event.getClickCount() == 2) { + viewModel.selectedCslLayoutProperty().set(item.getLayout()); + viewModel.storeStylePreferences(); + this.setResult(viewModel.getSelectedStyle()); + this.close(); + } + }).install(cslStylesTable); cslStylesTable.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> { if (newValue != null) { @@ -183,7 +229,9 @@ private void setupCslStylesTab() { viewModel.getAvailableCslLayouts().addListener((ListChangeListener) c -> { updateCslStylesTable(); if (c.next() && c.wasAdded() && !initialScrollPerformed.get()) { - Platform.runLater(this::scrollToCurrentStyle); // taking care of slight delay in table population + Platform.runLater(this::scrollToCurrentStyle); // taking care of slight + // delay in table + // population } }); @@ -198,29 +246,27 @@ private void setupJStylesTab() { jStyleDeleteColumn.setCellValueFactory(cellData -> cellData.getValue().internalStyleProperty()); new ValueTableCellFactory() - .withGraphic(internalStyle -> internalStyle ? null : IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) - .withOnMouseClickedEvent(item -> evt -> viewModel.deleteJStyle()) - .withTooltip(item -> Localization.lang("Remove style")) - .install(jStyleDeleteColumn); + .withGraphic(internalStyle -> internalStyle ? null : IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) + .withOnMouseClickedEvent(item -> evt -> viewModel.deleteJStyle()) + .withTooltip(item -> Localization.lang("Remove style")) + .install(jStyleDeleteColumn); edit.setOnAction(e -> viewModel.editJStyle()); - new ViewModelTableRowFactory() - .withOnMouseClickedEvent((item, event) -> { - if (event.getClickCount() == 2) { - viewModel.selectedJStyleProperty().setValue(item); - viewModel.storeStylePreferences(); - this.setResult(viewModel.getSelectedStyle()); - this.close(); - } - }) - .withContextMenu(item -> createContextMenu()) - .install(jStylesTable); + new ViewModelTableRowFactory().withOnMouseClickedEvent((item, event) -> { + if (event.getClickCount() == 2) { + viewModel.selectedJStyleProperty().setValue(item); + viewModel.storeStylePreferences(); + this.setResult(viewModel.getSelectedStyle()); + this.close(); + } + }).withContextMenu(item -> createContextMenu()).install(jStylesTable); jStylesTable.getSelectionModel().selectedItemProperty().addListener((_, oldValue, newValue) -> { if (newValue == null) { viewModel.selectedJStyleProperty().setValue(oldValue); - } else { + } + else { viewModel.selectedJStyleProperty().setValue(newValue); } }); @@ -239,8 +285,10 @@ private void setupJStylesTab() { EasyBind.subscribe(viewModel.selectedJStyleProperty(), style -> { if (viewModel.getSelectedStyle() instanceof JStyle) { jStylesTable.getSelectionModel().select(style); - previewArticle.setLayout(new TextBasedPreviewLayout(style.getJStyle().getReferenceFormat(StandardEntryType.Article))); - previewBook.setLayout(new TextBasedPreviewLayout(style.getJStyle().getReferenceFormat(StandardEntryType.Book))); + previewArticle.setLayout( + new TextBasedPreviewLayout(style.getJStyle().getReferenceFormat(StandardEntryType.Article))); + previewBook.setLayout( + new TextBasedPreviewLayout(style.getJStyle().getReferenceFormat(StandardEntryType.Book))); } }); } @@ -289,8 +337,9 @@ private void addJStyleFile() { } /** - * When Select Style dialog is first opened, there is a slight delay in population of CSL styles table. - * This function scrolls to the last selected style, while taking care of the delay. + * When Select Style dialog is first opened, there is a slight delay in population of + * CSL styles table. This function scrolls to the last selected style, while taking + * care of the delay. */ private void onDialogShown(DialogEvent event) { if (!cslStylesTable.getItems().isEmpty()) { @@ -303,7 +352,9 @@ private void scrollToCurrentStyle() { return; // Scroll has already been performed, exit early } - OOStyle currentStyle = preferences.getOpenOfficePreferences(Injector.instantiateModelOrService(JournalAbbreviationRepository.class)).getCurrentStyle(); + OOStyle currentStyle = preferences + .getOpenOfficePreferences(Injector.instantiateModelOrService(JournalAbbreviationRepository.class)) + .getCurrentStyle(); if (currentStyle instanceof CitationStyle currentCitationStyle) { for (int i = 0; i < cslStylesTable.getItems().size(); i++) { CSLStyleSelectViewModel item = cslStylesTable.getItems().get(i); @@ -315,4 +366,5 @@ private void scrollToCurrentStyle() { } } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java index 0e9438e08b5..35cc5620598 100644 --- a/jabgui/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java @@ -44,29 +44,34 @@ public class StyleSelectDialogViewModel { private final DialogService dialogService; private final CSLStyleLoader cslStyleLoader; + private final JStyleLoader jStyleLoader; private final ExternalApplicationsPreferences externalApplicationsPreferences; + private final FilePreferences filePreferences; + private final OpenOfficePreferences openOfficePreferences; private final BibEntryTypesManager bibEntryTypesManager; private final ObjectProperty selectedTab = new SimpleObjectProperty<>(); - private final ListProperty jStyles = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty jStyles = new SimpleListProperty<>( + FXCollections.observableArrayList()); + private final ObjectProperty selectedJStyle = new SimpleObjectProperty<>(); private final ObservableList availableCslLayouts = FXCollections.observableArrayList(); + private final ObjectProperty selectedCslLayoutProperty = new SimpleObjectProperty<>(); - private final FilteredList filteredAvailableCslLayouts = new FilteredList<>(availableCslLayouts); - - public StyleSelectDialogViewModel(DialogService dialogService, - CSLStyleLoader cslStyleLoader, - JStyleLoader jStyleLoader, - GuiPreferences preferences, - TaskExecutor taskExecutor, - BibEntryTypesManager bibEntryTypesManager) { + + private final FilteredList filteredAvailableCslLayouts = new FilteredList<>( + availableCslLayouts); + + public StyleSelectDialogViewModel(DialogService dialogService, CSLStyleLoader cslStyleLoader, + JStyleLoader jStyleLoader, GuiPreferences preferences, TaskExecutor taskExecutor, + BibEntryTypesManager bibEntryTypesManager) { this.dialogService = dialogService; this.cslStyleLoader = cslStyleLoader; @@ -74,7 +79,8 @@ public StyleSelectDialogViewModel(DialogService dialogService, this.externalApplicationsPreferences = preferences.getExternalApplicationsPreferences(); this.filePreferences = preferences.getFilePreferences(); - this.openOfficePreferences = preferences.getOpenOfficePreferences(Injector.instantiateModelOrService(JournalAbbreviationRepository.class)); + this.openOfficePreferences = preferences + .getOpenOfficePreferences(Injector.instantiateModelOrService(JournalAbbreviationRepository.class)); this.bibEntryTypesManager = bibEntryTypesManager; @@ -86,31 +92,31 @@ public StyleSelectDialogViewModel(DialogService dialogService, selectedJStyle.setValue(getJStyleOrDefault(jStyle.getPath())); } - BackgroundTask.wrap(CSLStyleLoader::getStyles) - .onSuccess(styles -> { - List layouts = styles.stream() - .map(style -> new CitationStylePreviewLayout(style, bibEntryTypesManager)) - .toList(); - availableCslLayouts.setAll(layouts); - - if (currentStyle instanceof CitationStyle citationStyle) { - // Find the matching style - first try exact path match for external styles - Optional matchingLayout = availableCslLayouts.stream() - .filter(layout -> layout.getFilePath().equals(citationStyle.getFilePath())) - .findFirst(); - - // If not found, match by name (for internal style) - if (matchingLayout.isEmpty()) { - matchingLayout = availableCslLayouts.stream() - .filter(layout -> layout.getDisplayName().equals(citationStyle.getTitle())) - .findFirst(); - } - - selectedCslLayoutProperty.set(matchingLayout.orElse(availableCslLayouts.getFirst())); - } - }) - .onFailure(ex -> dialogService.showErrorDialogAndWait("Error discovering citation styles", ex)) - .executeWith(taskExecutor); + BackgroundTask.wrap(CSLStyleLoader::getStyles).onSuccess(styles -> { + List layouts = styles.stream() + .map(style -> new CitationStylePreviewLayout(style, bibEntryTypesManager)) + .toList(); + availableCslLayouts.setAll(layouts); + + if (currentStyle instanceof CitationStyle citationStyle) { + // Find the matching style - first try exact path match for external + // styles + Optional matchingLayout = availableCslLayouts.stream() + .filter(layout -> layout.getFilePath().equals(citationStyle.getFilePath())) + .findFirst(); + + // If not found, match by name (for internal style) + if (matchingLayout.isEmpty()) { + matchingLayout = availableCslLayouts.stream() + .filter(layout -> layout.getDisplayName().equals(citationStyle.getTitle())) + .findFirst(); + } + + selectedCslLayoutProperty.set(matchingLayout.orElse(availableCslLayouts.getFirst())); + } + }) + .onFailure(ex -> dialogService.showErrorDialogAndWait("Error discovering citation styles", ex)) + .executeWith(taskExecutor); } // region - general methods @@ -137,7 +143,8 @@ public OOStyle getSelectedStyle() { if (selectedJStyle.get() != null) { return selectedJStyle.get().getJStyle(); } - } else if ("CSL Styles".equals(tabText)) { + } + else if ("CSL Styles".equals(tabText)) { if (selectedCslLayoutProperty.get() != null) { return selectedCslLayoutProperty.get().getCitationStyle(); } @@ -149,10 +156,10 @@ public OOStyle getSelectedStyle() { public void storeStylePreferences() { // save external jstyles List externalJStyles = jStyles.stream() - .map(this::toJStyle) - .filter(style -> !style.isInternalStyle()) - .map(JStyle::getPath) - .toList(); + .map(this::toJStyle) + .filter(style -> !style.isInternalStyle()) + .map(JStyle::getPath) + .toList(); openOfficePreferences.setExternalJStyles(externalJStyles); @@ -160,7 +167,8 @@ public void storeStylePreferences() { OOStyle selectedStyle = getSelectedStyle(); openOfficePreferences.setCurrentStyle(selectedStyle); - // Handle backward-compatibility with pure JStyle (formerly OOBibStyle) preferences ("ooBibliographyStyleFile") + // Handle backward-compatibility with pure JStyle (formerly OOBibStyle) + // preferences ("ooBibliographyStyleFile") if (selectedStyle instanceof JStyle jStyle) { openOfficePreferences.setCurrentJStyle(jStyle.getPath()); } @@ -179,8 +187,8 @@ public ObjectProperty selectedCslLayoutProperty() { } public void setAvailableCslLayoutsFilter(String searchTerm) { - filteredAvailableCslLayouts.setPredicate(layout -> - searchTerm.isEmpty() || layout.getDisplayName().toLowerCase().contains(searchTerm.toLowerCase())); + filteredAvailableCslLayouts.setPredicate(layout -> searchTerm.isEmpty() + || layout.getDisplayName().toLowerCase().contains(searchTerm.toLowerCase())); } /** @@ -188,10 +196,12 @@ public void setAvailableCslLayoutsFilter(String searchTerm) { */ public void addCslStyleFile() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(Localization.lang("%0 file", StandardFileType.CITATION_STYLE.getName()), StandardFileType.CITATION_STYLE) - .withDefaultExtension(Localization.lang("%0 file", StandardFileType.CITATION_STYLE.getName()), StandardFileType.CITATION_STYLE) - .withInitialDirectory(filePreferences.getWorkingDirectory()) - .build(); + .addExtensionFilter(Localization.lang("%0 file", StandardFileType.CITATION_STYLE.getName()), + StandardFileType.CITATION_STYLE) + .withDefaultExtension(Localization.lang("%0 file", StandardFileType.CITATION_STYLE.getName()), + StandardFileType.CITATION_STYLE) + .withInitialDirectory(filePreferences.getWorkingDirectory()) + .build(); Optional path = dialogService.showFileOpenDialog(fileDialogConfiguration); @@ -203,14 +213,14 @@ public void addCslStyleFile() { List allStyles = CSLStyleLoader.getStyles(); List updatedLayouts = allStyles.stream() - .map(style -> new CitationStylePreviewLayout(style, bibEntryTypesManager)) - .toList(); + .map(style -> new CitationStylePreviewLayout(style, bibEntryTypesManager)) + .toList(); availableCslLayouts.setAll(updatedLayouts); Optional newLayoutOptional = updatedLayouts.stream() - .filter(layout -> layout.getFilePath().equals(stylePath)) - .findFirst(); + .filter(layout -> layout.getFilePath().equals(stylePath)) + .findFirst(); if (newLayoutOptional.isPresent()) { CitationStylePreviewLayout newLayout = newLayoutOptional.get(); @@ -218,59 +228,55 @@ public void addCslStyleFile() { openOfficePreferences.setCurrentStyle(newStyle); - dialogService.showInformationDialogAndWait( - Localization.lang("Style added"), - Localization.lang("The CSL style has been added successfully.") - ); - } else { - dialogService.showErrorDialogAndWait( - Localization.lang("Style not found"), - Localization.lang("The CSL style was added but could not be found in the list.") - ); + dialogService.showInformationDialogAndWait(Localization.lang("Style added"), + Localization.lang("The CSL style has been added successfully.")); + } + else { + dialogService.showErrorDialogAndWait(Localization.lang("Style not found"), + Localization.lang("The CSL style was added but could not be found in the list.")); } - } else { - dialogService.showErrorDialogAndWait( - Localization.lang("Invalid style selected"), - Localization.lang("You must select a valid CSL style file.") - ); + } + else { + dialogService.showErrorDialogAndWait(Localization.lang("Invalid style selected"), + Localization.lang("You must select a valid CSL style file.")); } }); } public void deleteCslStyle(CitationStyle style) { - boolean deleteConfirmed = dialogService.showConfirmationDialogAndWait( - Localization.lang("Delete style"), + boolean deleteConfirmed = dialogService.showConfirmationDialogAndWait(Localization.lang("Delete style"), Localization.lang("Are you sure you want to delete the style '%0'?", style.getTitle()), - Localization.lang("Delete"), - Localization.lang("Cancel")); + Localization.lang("Delete"), Localization.lang("Cancel")); if (deleteConfirmed) { if (cslStyleLoader.removeStyle(style)) { Optional layoutToRemove = availableCslLayouts.stream() - .filter(layout -> layout.getFilePath().equals(style.getFilePath())) - .findFirst(); + .filter(layout -> layout.getFilePath().equals(style.getFilePath())) + .findFirst(); layoutToRemove.ifPresent(availableCslLayouts::remove); // If the deleted style was the current selection, select another style - if (selectedCslLayoutProperty.get() != null && - selectedCslLayoutProperty.get().getFilePath().equals(style.getFilePath())) { + if (selectedCslLayoutProperty.get() != null + && selectedCslLayoutProperty.get().getFilePath().equals(style.getFilePath())) { if (!availableCslLayouts.isEmpty()) { selectedCslLayoutProperty.set(availableCslLayouts.getFirst()); - } else { + } + else { selectedCslLayoutProperty.set(null); } } - // Update the currently set style to default (ieee) if it was the deleted one - if (openOfficePreferences.getCurrentStyle() instanceof CitationStyle currentStyle && - currentStyle.getFilePath().equals(style.getFilePath())) { + // Update the currently set style to default (ieee) if it was the deleted + // one + if (openOfficePreferences.getCurrentStyle() instanceof CitationStyle currentStyle + && currentStyle.getFilePath().equals(style.getFilePath())) { openOfficePreferences.setCurrentStyle(CSLStyleLoader.getDefaultStyle()); } - } else { - dialogService.showErrorDialogAndWait( - Localization.lang("Cannot delete style"), - Localization.lang("Could not delete style. It might be an internal style that cannot be removed.")); + } + else { + dialogService.showErrorDialogAndWait(Localization.lang("Cannot delete style"), Localization + .lang("Could not delete style. It might be an internal style that cannot be removed.")); } } } @@ -280,7 +286,8 @@ public void deleteCslStyle(CitationStyle style) { // region - jstyle-specific methods public JStyleSelectViewModel fromJStyle(JStyle style) { - return new JStyleSelectViewModel(style.getName(), String.join(", ", style.getJournals()), style.isInternalStyle() ? Localization.lang("Internal style") : style.getPath(), style); + return new JStyleSelectViewModel(style.getName(), String.join(", ", style.getJournals()), + style.isInternalStyle() ? Localization.lang("Internal style") : style.getPath(), style); } public JStyle toJStyle(JStyleSelectViewModel item) { @@ -300,33 +307,43 @@ public List loadJStyles() { } private JStyleSelectViewModel getJStyleOrDefault(String stylePath) { - return jStyles.stream().filter(style -> style.getStylePath().equals(stylePath)).findFirst().orElse(jStyles.getFirst()); + return jStyles.stream() + .filter(style -> style.getStylePath().equals(stylePath)) + .findFirst() + .orElse(jStyles.getFirst()); } public void addJStyleFile() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(Localization.lang("%0 file", StandardFileType.JSTYLE.getName()), StandardFileType.JSTYLE) - .withDefaultExtension(Localization.lang("%0 file", StandardFileType.JSTYLE.getName()), StandardFileType.JSTYLE) - .withInitialDirectory(filePreferences.getWorkingDirectory()) - .build(); + .addExtensionFilter(Localization.lang("%0 file", StandardFileType.JSTYLE.getName()), + StandardFileType.JSTYLE) + .withDefaultExtension(Localization.lang("%0 file", StandardFileType.JSTYLE.getName()), + StandardFileType.JSTYLE) + .withInitialDirectory(filePreferences.getWorkingDirectory()) + .build(); Optional path = dialogService.showFileOpenDialog(fileDialogConfiguration); path.map(Path::toAbsolutePath).ifPresent(stylePath -> { if (jStyleLoader.addStyleIfValid(stylePath)) { openOfficePreferences.setCurrentJStyle(stylePath.toString()); jStyles.setAll(loadJStyles()); selectedJStyle.setValue(getJStyleOrDefault(stylePath.toString())); - } else { - dialogService.showErrorDialogAndWait(Localization.lang("Invalid style selected"), Localization.lang("You must select a valid style file. Your style is probably missing a line for the type \"default\".")); + } + else { + dialogService.showErrorDialogAndWait(Localization.lang("Invalid style selected"), Localization.lang( + "You must select a valid style file. Your style is probably missing a line for the type \"default\".")); } }); } public void editJStyle() { JStyle jStyle = selectedJStyle.getValue().getJStyle(); - Optional type = ExternalFileTypes.getExternalFileTypeByExt("jstyle", externalApplicationsPreferences); + Optional type = ExternalFileTypes.getExternalFileTypeByExt("jstyle", + externalApplicationsPreferences); try { - NativeDesktop.openExternalFileAnyFormat(new BibDatabaseContext(), externalApplicationsPreferences, filePreferences, jStyle.getPath(), type); - } catch (IOException e) { + NativeDesktop.openExternalFileAnyFormat(new BibDatabaseContext(), externalApplicationsPreferences, + filePreferences, jStyle.getPath(), type); + } + catch (IOException e) { dialogService.showErrorDialogAndWait(e); } } @@ -339,4 +356,5 @@ public void deleteJStyle() { } // endregion + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/AbstractPreferenceTabView.java b/jabgui/src/main/java/org/jabref/gui/preferences/AbstractPreferenceTabView.java index 0cca01f40ce..858b7a92ab5 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/AbstractPreferenceTabView.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/AbstractPreferenceTabView.java @@ -10,11 +10,17 @@ import jakarta.inject.Inject; -public abstract class AbstractPreferenceTabView extends VBox implements PreferencesTab { +public abstract class AbstractPreferenceTabView extends VBox + implements PreferencesTab { - @Inject protected TaskExecutor taskExecutor; - @Inject protected DialogService dialogService; - @Inject protected GuiPreferences preferences; + @Inject + protected TaskExecutor taskExecutor; + + @Inject + protected DialogService dialogService; + + @Inject + protected GuiPreferences preferences; protected T viewModel; @@ -42,4 +48,5 @@ public boolean validateSettings() { public List getRestartWarnings() { return viewModel.getRestartWarnings(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/GuiPreferences.java b/jabgui/src/main/java/org/jabref/gui/preferences/GuiPreferences.java index 6e1faaded08..0dd94d9945b 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/GuiPreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/GuiPreferences.java @@ -21,6 +21,7 @@ import org.jabref.logic.push.PushToApplicationPreferences; public interface GuiPreferences extends CliPreferences { + CopyToPreferences getCopyToPreferences(); EntryEditorPreferences getEntryEditorPreferences(); @@ -58,4 +59,5 @@ public interface GuiPreferences extends CliPreferences { KeyBindingRepository getKeyBindingRepository(); NewEntryPreferences getNewEntryPreferences(); + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java b/jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java index c82ee3c79d3..768237accfc 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java @@ -83,87 +83,134 @@ public class JabRefGuiPreferences extends JabRefCliPreferences implements GuiPre // Public because needed for pref migration public static final String AUTOCOMPLETER_COMPLETE_FIELDS = "autoCompleteFields"; + public static final String MAIN_FONT_SIZE = "mainFontSize"; // region Preview - public for pref migrations public static final String PREVIEW_STYLE = "previewStyle"; + public static final String CYCLE_PREVIEW_POS = "cyclePreviewPos"; + public static final String CYCLE_PREVIEW = "cyclePreview"; + public static final String PREVIEW_AS_TAB = "previewAsTab"; + public static final String PREVIEW_IN_ENTRY_TABLE_TOOLTIP = "previewInEntryTableTooltip"; + public static final String PREVIEW_BST_LAYOUT_PATHS = "previewBstLayoutPaths"; + // endregion // region column names // public because of migration - // Variable names have changed to ensure backward compatibility with pre 5.0 releases of JabRef + // Variable names have changed to ensure backward compatibility with pre 5.0 releases + // of JabRef // Pre 5.1: columnNames, columnWidths, columnSortTypes, columnSortOrder public static final String COLUMN_NAMES = "mainTableColumnNames"; + public static final String COLUMN_WIDTHS = "mainTableColumnWidths"; + public static final String COLUMN_SORT_TYPES = "mainTableColumnSortTypes"; + public static final String COLUMN_SORT_ORDER = "mainTableColumnSortOrder"; + // endregion // region keybindings - public because needed for pref migration public static final String BIND_NAMES = "bindNames"; + public static final String BINDINGS = "bindings"; + // endregion private static final Logger LOGGER = LoggerFactory.getLogger(JabRefGuiPreferences.class); // region core GUI preferences private static final String MAIN_WINDOW_POS_X = "mainWindowPosX"; + private static final String MAIN_WINDOW_POS_Y = "mainWindowPosY"; + private static final String MAIN_WINDOW_WIDTH = "mainWindowSizeX"; + private static final String MAIN_WINDOW_HEIGHT = "mainWindowSizeY"; + private static final String WINDOW_MAXIMISED = "windowMaximised"; + private static final String SIDE_PANE_WIDTH = "sidePaneWidthFX"; + private static final String SIDE_PANE_COMPONENT_PREFERRED_POSITIONS = "sidePaneComponentPreferredPositions"; + private static final String SIDE_PANE_COMPONENT_NAMES = "sidePaneComponentNames"; + // endregion // region main table, main table columns, save columns private static final String AUTO_RESIZE_MODE = "autoResizeMode"; + private static final String EXTRA_FILE_COLUMNS = "extraFileColumns"; private static final String SEARCH_DIALOG_COLUMN_WIDTHS = "searchTableColumnWidths"; + private static final String SEARCH_DIALOG_COLUMN_SORT_TYPES = "searchDialogColumnSortTypes"; + private static final String SEARCH_DIALOG_COLUMN_SORT_ORDER = "searchDalogColumnSortOrder"; + // endregion // region NameDisplayPreferences private static final String NAMES_LAST_ONLY = "namesLastOnly"; + private static final String ABBR_AUTHOR_NAMES = "abbrAuthorNames"; + private static final String NAMES_NATBIB = "namesNatbib"; + private static final String NAMES_FIRST_LAST = "namesFf"; + private static final String NAMES_AS_IS = "namesAsIs"; + // endregion // region ExternalApplicationsPreferences private static final String EXTERNAL_FILE_TYPES = "externalFileTypes"; + private static final String CONSOLE_COMMAND = "consoleCommand"; + private static final String USE_DEFAULT_CONSOLE_APPLICATION = "useDefaultConsoleApplication"; + private static final String USE_DEFAULT_FILE_BROWSER_APPLICATION = "userDefaultFileBrowserApplication"; + private static final String EMAIL_SUBJECT = "emailSubject"; + private static final String KINDLE_EMAIL = "kindleEmail"; + private static final String OPEN_FOLDERS_OF_ATTACHED_FILES = "openFoldersOfAttachedFiles"; + private static final String FILE_BROWSER_COMMAND = "fileBrowserCommand"; + // endregion // region workspace private static final String THEME = "fxTheme"; + private static final String THEME_SYNC_OS = "themeSyncOs"; + private static final String OPEN_LAST_EDITED = "openLastEdited"; + private static final String OVERRIDE_DEFAULT_FONT_SIZE = "overrideDefaultFontSize"; + private static final String SHOW_ADVANCED_HINTS = "showAdvancedHints"; + private static final String CONFIRM_DELETE = "confirmDelete"; + private static final String CONFIRM_HIDE_TAB_BAR = "confirmHideTabBar"; + // endregion private static final String ENTRY_EDITOR_HEIGHT = "entryEditorHeightFX"; /** - * Holds the horizontal divider position of the preview view when it is shown inside the entry editor + * Holds the horizontal divider position of the preview view when it is shown inside + * the entry editor */ private static final String ENTRY_EDITOR_PREVIEW_DIVIDER_POS = "entryEditorPreviewDividerPos"; @@ -171,70 +218,115 @@ public class JabRefGuiPreferences extends JabRefCliPreferences implements GuiPre // region Auto completion private static final String AUTO_COMPLETE = "autoComplete"; + private static final String AUTOCOMPLETER_FIRSTNAME_MODE = "autoCompFirstNameMode"; + private static final String AUTOCOMPLETER_LAST_FIRST = "autoCompLF"; + private static final String AUTOCOMPLETER_FIRST_LAST = "autoCompFF"; + // endregion // region SidePanePreferences private static final String SELECTED_FETCHER_INDEX = "selectedFetcherIndex"; + private static final String WEB_SEARCH_VISIBLE = "webSearchVisible"; + private static final String OO_SHOW_PANEL = "showOOPanel"; + private static final String GROUP_SIDEPANE_VISIBLE = "groupSidepaneVisible"; + // endregion // region GroupsPreferences private static final String AUTO_ASSIGN_GROUP = "autoAssignGroup"; + private static final String DISPLAY_GROUP_COUNT = "displayGroupCount"; + private static final String GROUP_VIEW_INTERSECTION = "groupIntersection"; + private static final String GROUP_VIEW_FILTER = "groupFilter"; + private static final String GROUP_VIEW_INVERT = "groupInvert"; + private static final String DEFAULT_HIERARCHICAL_CONTEXT = "defaultHierarchicalContext"; + // endregion // region specialFieldsPreferences private static final String SPECIALFIELDSENABLED = "specialFieldsEnabled"; + // endregion private static final String SELECTED_SLR_CATALOGS = "selectedSlrCatalogs"; + private static final String UNLINKED_FILES_SELECTED_EXTENSION = "unlinkedFilesSelectedExtension"; + private static final String UNLINKED_FILES_SELECTED_DATE_RANGE = "unlinkedFilesSelectedDateRange"; + private static final String UNLINKED_FILES_SELECTED_SORT = "unlinkedFilesSelectedSort"; private static final String INCLUDE_CROSS_REFERENCES = "includeCrossReferences"; + private static final String ASK_FOR_INCLUDING_CROSS_REFERENCES = "askForIncludingCrossReferences"; // region NewEntryPreferences private static final String CREATE_ENTRY_APPROACH = "latestApproach"; + private static final String CREATE_ENTRY_EXPAND_RECOMMENDED = "typesRecommendedExpanded"; + private static final String CREATE_ENTRY_EXPAND_OTHER = "typesOtherExpanded"; + private static final String CREATE_ENTRY_EXPAND_CUSTOM = "typesCustomExpanded"; + private static final String CREATE_ENTRY_IMMEDIATE_TYPE = "latestImmediateType"; + private static final String CREATE_ENTRY_ID_LOOKUP_GUESSING = "idLookupGuessing"; + private static final String CREATE_ENTRY_ID_FETCHER_NAME = "latestIdFetcherName"; + private static final String CREATE_ENTRY_INTERPRET_PARSER_NAME = "latestInterpretParserName"; + // endregion private static JabRefGuiPreferences singleton; private EntryEditorPreferences entryEditorPreferences; + private MergeDialogPreferences mergeDialogPreferences; + private AutoCompletePreferences autoCompletePreferences; + private CoreGuiPreferences coreGuiPreferences; + private WorkspacePreferences workspacePreferences; + private UnlinkedFilesDialogPreferences unlinkedFilesDialogPreferences; + private ExternalApplicationsPreferences externalApplicationsPreferences; + private SidePanePreferences sidePanePreferences; + private GroupsPreferences groupsPreferences; + private SpecialFieldsPreferences specialFieldsPreferences; + private PreviewPreferences previewPreferences; + private PushToApplicationPreferences pushToApplicationPreferences; + private NameDisplayPreferences nameDisplayPreferences; + private MainTablePreferences mainTablePreferences; + private ColumnPreferences mainTableColumnPreferences; + private ColumnPreferences searchDialogColumnPreferences; + private KeyBindingRepository keyBindingRepository; + private CopyToPreferences copyToPreferences; + private NewEntryPreferences newEntryPreferences; private JabRefGuiPreferences() { @@ -252,15 +344,21 @@ private JabRefGuiPreferences() { defaults.put(MERGE_ENTRIES_HIGHLIGHT_WORDS, Boolean.TRUE); defaults.put(MERGE_SHOW_ONLY_CHANGED_FIELDS, Boolean.FALSE); defaults.put(MERGE_APPLY_TO_ALL_ENTRIES, Boolean.FALSE); - defaults.put(DUPLICATE_RESOLVER_DECISION_RESULT_ALL_ENTRIES, DuplicateResolverDialog.DuplicateResolverResult.BREAK.name()); + defaults.put(DUPLICATE_RESOLVER_DECISION_RESULT_ALL_ENTRIES, + DuplicateResolverDialog.DuplicateResolverResult.BREAK.name()); // endregion // region autoCompletePreferences defaults.put(AUTO_COMPLETE, Boolean.FALSE); defaults.put(AUTOCOMPLETER_FIRSTNAME_MODE, AutoCompleteFirstNameMode.BOTH.name()); - defaults.put(AUTOCOMPLETER_FIRST_LAST, Boolean.FALSE); // "Autocomplete names in 'Firstname Lastname' format only" - defaults.put(AUTOCOMPLETER_LAST_FIRST, Boolean.FALSE); // "Autocomplete names in 'Lastname, Firstname' format only" - defaults.put(AUTOCOMPLETER_COMPLETE_FIELDS, "author;editor;title;journal;publisher;keywords;crossref;related;entryset"); + defaults.put(AUTOCOMPLETER_FIRST_LAST, Boolean.FALSE); // "Autocomplete names in + // 'Firstname Lastname' + // format only" + defaults.put(AUTOCOMPLETER_LAST_FIRST, Boolean.FALSE); // "Autocomplete names in + // 'Lastname, Firstname' + // format only" + defaults.put(AUTOCOMPLETER_COMPLETE_FIELDS, + "author;editor;title;journal;publisher;keywords;crossref;related;entryset"); // endregion // region workspace @@ -287,7 +385,8 @@ private JabRefGuiPreferences() { if (OS.WINDOWS) { defaults.put(OPEN_FOLDERS_OF_ATTACHED_FILES, Boolean.TRUE); - } else { + } + else { defaults.put(OPEN_FOLDERS_OF_ATTACHED_FILES, Boolean.FALSE); } @@ -296,7 +395,8 @@ private JabRefGuiPreferences() { if (OS.WINDOWS) { defaults.put(CONSOLE_COMMAND, "C:\\Program Files\\ConEmu\\ConEmu64.exe /single /dir \"%DIR\""); defaults.put(FILE_BROWSER_COMMAND, "explorer.exe /select, \"%DIR\""); - } else { + } + else { defaults.put(CONSOLE_COMMAND, ""); defaults.put(FILE_BROWSER_COMMAND, ""); } @@ -325,25 +425,24 @@ private JabRefGuiPreferences() { defaults.put(CYCLE_PREVIEW_POS, 0); defaults.put(PREVIEW_AS_TAB, Boolean.FALSE); defaults.put(PREVIEW_IN_ENTRY_TABLE_TOOLTIP, Boolean.FALSE); - defaults.put(PREVIEW_STYLE, - "" + - "\\bibtextype\\begin{citationkey} (\\citationkey)\\end{citationkey}__NEWLINE__" + - "\\begin{author}

    \\format[Authors(LastFirst, FullName,Sep= / ,LastSep= / ),HTMLChars]{\\author}\\end{author}__NEWLINE__" + - "\\begin{editor & !author}

    \\format[Authors(LastFirst,FullName,Sep= / ,LastSep= / ),HTMLChars]{\\editor} (\\format[IfPlural(Eds.,Ed.)]{\\editor})\\end{editor & !author}__NEWLINE__" + - "\\begin{title}
    \\format[HTMLChars]{\\title} \\end{title}__NEWLINE__" + - "
    \\begin{date}\\date\\end{date}\\begin{edition}, \\edition. edition\\end{edition}__NEWLINE__" + - "\\begin{editor & author}

    \\format[Authors(LastFirst,FullName,Sep= / ,LastSep= / ),HTMLChars]{\\editor} (\\format[IfPlural(Eds.,Ed.)]{\\editor})\\end{editor & author}__NEWLINE__" + - "\\begin{booktitle}
    \\format[HTMLChars]{\\booktitle}\\end{booktitle}__NEWLINE__" + - "\\begin{chapter} \\format[HTMLChars]{\\chapter}
    \\end{chapter}" + - "\\begin{editor & !author}
    \\end{editor & !author}\\begin{!editor}
    \\end{!editor}\\begin{journal}
    \\format[HTMLChars]{\\journal} \\end{journal} \\begin{volume}, Vol. \\volume\\end{volume}\\begin{series}
    \\format[HTMLChars]{\\series}\\end{series}\\begin{number}, No. \\format[HTMLChars]{\\number}\\end{number}__NEWLINE__" + - "\\begin{school} \\format[HTMLChars]{\\school}, \\end{school}__NEWLINE__" + - "\\begin{institution} \\format[HTMLChars]{\\institution}, \\end{institution}__NEWLINE__" + - "\\begin{publisher}
    \\format[HTMLChars]{\\publisher}\\end{publisher}\\begin{location}: \\format[HTMLChars]{\\location} \\end{location}__NEWLINE__" + - "\\begin{pages}
    p. \\format[FormatPagesForHTML]{\\pages}\\end{pages}__NEWLINE__" + - "\\begin{abstract}

    Abstract: \\format[HTMLChars]{\\abstract} \\end{abstract}__NEWLINE__" + - "\\begin{owncitation}

    Own citation: \\format[HTMLChars]{\\owncitation} \\end{owncitation}__NEWLINE__" + - "\\begin{comment}

    Comment: \\format[Markdown,HTMLChars(keepCurlyBraces)]{\\comment}\\end{comment}__NEWLINE__" + - "
    __NEWLINE__"); + defaults.put(PREVIEW_STYLE, "" + + "\\bibtextype\\begin{citationkey} (\\citationkey)\\end{citationkey}__NEWLINE__" + + "\\begin{author}

    \\format[Authors(LastFirst, FullName,Sep= / ,LastSep= / ),HTMLChars]{\\author}\\end{author}__NEWLINE__" + + "\\begin{editor & !author}

    \\format[Authors(LastFirst,FullName,Sep= / ,LastSep= / ),HTMLChars]{\\editor} (\\format[IfPlural(Eds.,Ed.)]{\\editor})\\end{editor & !author}__NEWLINE__" + + "\\begin{title}
    \\format[HTMLChars]{\\title} \\end{title}__NEWLINE__" + + "
    \\begin{date}\\date\\end{date}\\begin{edition}, \\edition. edition\\end{edition}__NEWLINE__" + + "\\begin{editor & author}

    \\format[Authors(LastFirst,FullName,Sep= / ,LastSep= / ),HTMLChars]{\\editor} (\\format[IfPlural(Eds.,Ed.)]{\\editor})\\end{editor & author}__NEWLINE__" + + "\\begin{booktitle}
    \\format[HTMLChars]{\\booktitle}\\end{booktitle}__NEWLINE__" + + "\\begin{chapter} \\format[HTMLChars]{\\chapter}
    \\end{chapter}" + + "\\begin{editor & !author}
    \\end{editor & !author}\\begin{!editor}
    \\end{!editor}\\begin{journal}
    \\format[HTMLChars]{\\journal} \\end{journal} \\begin{volume}, Vol. \\volume\\end{volume}\\begin{series}
    \\format[HTMLChars]{\\series}\\end{series}\\begin{number}, No. \\format[HTMLChars]{\\number}\\end{number}__NEWLINE__" + + "\\begin{school} \\format[HTMLChars]{\\school}, \\end{school}__NEWLINE__" + + "\\begin{institution} \\format[HTMLChars]{\\institution}, \\end{institution}__NEWLINE__" + + "\\begin{publisher}
    \\format[HTMLChars]{\\publisher}\\end{publisher}\\begin{location}: \\format[HTMLChars]{\\location} \\end{location}__NEWLINE__" + + "\\begin{pages}
    p. \\format[FormatPagesForHTML]{\\pages}\\end{pages}__NEWLINE__" + + "\\begin{abstract}

    Abstract: \\format[HTMLChars]{\\abstract} \\end{abstract}__NEWLINE__" + + "\\begin{owncitation}

    Own citation: \\format[HTMLChars]{\\owncitation} \\end{owncitation}__NEWLINE__" + + "\\begin{comment}

    Comment: \\format[Markdown,HTMLChars(keepCurlyBraces)]{\\comment}\\end{comment}__NEWLINE__" + + "
    __NEWLINE__"); // endregion // region NameDisplayPreferences @@ -356,7 +455,8 @@ private JabRefGuiPreferences() { // region: Main table, main table column, and search dialog column preferences defaults.put(EXTRA_FILE_COLUMNS, Boolean.FALSE); - defaults.put(COLUMN_NAMES, "groups;group_icons;files;linked_id;field:citationkey;field:entrytype;field:author/editor;field:title;field:year;field:journal/booktitle;special:ranking;special:readstatus;special:priority"); + defaults.put(COLUMN_NAMES, + "groups;group_icons;files;linked_id;field:citationkey;field:entrytype;field:author/editor;field:title;field:year;field:journal/booktitle;special:ranking;special:readstatus;special:priority"); defaults.put(COLUMN_WIDTHS, "28;40;28;28;100;75;300;470;60;130;50;50;50"); defaults.put(SIDE_PANE_COMPONENT_NAMES, ""); @@ -379,7 +479,8 @@ private JabRefGuiPreferences() { defaults.put(INCLUDE_CROSS_REFERENCES, Boolean.FALSE); // region NewEntryUnifierPreferences - defaults.put(CREATE_ENTRY_APPROACH, List.of(NewEntryDialogTab.values()).indexOf(NewEntryDialogTab.CHOOSE_ENTRY_TYPE)); + defaults.put(CREATE_ENTRY_APPROACH, + List.of(NewEntryDialogTab.values()).indexOf(NewEntryDialogTab.CHOOSE_ENTRY_TYPE)); defaults.put(CREATE_ENTRY_EXPAND_RECOMMENDED, true); defaults.put(CREATE_ENTRY_EXPAND_OTHER, false); defaults.put(CREATE_ENTRY_EXPAND_CUSTOM, true); @@ -392,8 +493,9 @@ private JabRefGuiPreferences() { /** * @deprecated Never ever add a call to this method. There should be only one caller. - * All other usages should get the preferences passed (or injected). - * The JabRef team leaves the {@code @deprecated} annotation to have IntelliJ listing this method with a strike-through. + * All other usages should get the preferences passed (or injected). The JabRef team + * leaves the {@code @deprecated} annotation to have IntelliJ listing this method with + * a strike-through. */ @Deprecated public static JabRefGuiPreferences getInstance() { @@ -407,13 +509,13 @@ public CopyToPreferences getCopyToPreferences() { if (copyToPreferences != null) { return copyToPreferences; } - copyToPreferences = new CopyToPreferences( - getBoolean(ASK_FOR_INCLUDING_CROSS_REFERENCES), - getBoolean(INCLUDE_CROSS_REFERENCES) - ); + copyToPreferences = new CopyToPreferences(getBoolean(ASK_FOR_INCLUDING_CROSS_REFERENCES), + getBoolean(INCLUDE_CROSS_REFERENCES)); - EasyBind.listen(copyToPreferences.shouldAskForIncludingCrossReferencesProperty(), (obs, oldValue, newValue) -> putBoolean(ASK_FOR_INCLUDING_CROSS_REFERENCES, newValue)); - EasyBind.listen(copyToPreferences.shouldIncludeCrossReferencesProperty(), (obs, oldValue, newValue) -> putBoolean(INCLUDE_CROSS_REFERENCES, newValue)); + EasyBind.listen(copyToPreferences.shouldAskForIncludingCrossReferencesProperty(), + (obs, oldValue, newValue) -> putBoolean(ASK_FOR_INCLUDING_CROSS_REFERENCES, newValue)); + EasyBind.listen(copyToPreferences.shouldIncludeCrossReferencesProperty(), + (obs, oldValue, newValue) -> putBoolean(INCLUDE_CROSS_REFERENCES, newValue)); return copyToPreferences; } @@ -424,47 +526,51 @@ public EntryEditorPreferences getEntryEditorPreferences() { return entryEditorPreferences; } - entryEditorPreferences = new EntryEditorPreferences( - getEntryEditorTabs(), - getDefaultEntryEditorTabs(), - getBoolean(AUTO_OPEN_FORM), - getBoolean(SHOW_RECOMMENDATIONS), - getBoolean(SHOW_AI_SUMMARY), - getBoolean(SHOW_AI_CHAT), - getBoolean(SHOW_LATEX_CITATIONS), - getBoolean(SMART_FILE_ANNOTATIONS), - getBoolean(DEFAULT_SHOW_SOURCE), - getBoolean(VALIDATE_IN_ENTRY_EDITOR), - getBoolean(ALLOW_INTEGER_EDITION_BIBTEX), - getBoolean(AUTOLINK_FILES_ENABLED), - EntryEditorPreferences.JournalPopupEnabled.fromString(get(JOURNAL_POPUP)), - getBoolean(SHOW_SCITE_TAB), - getBoolean(SHOW_USER_COMMENTS_FIELDS), - getDouble(ENTRY_EDITOR_PREVIEW_DIVIDER_POS)); + entryEditorPreferences = new EntryEditorPreferences(getEntryEditorTabs(), getDefaultEntryEditorTabs(), + getBoolean(AUTO_OPEN_FORM), getBoolean(SHOW_RECOMMENDATIONS), getBoolean(SHOW_AI_SUMMARY), + getBoolean(SHOW_AI_CHAT), getBoolean(SHOW_LATEX_CITATIONS), getBoolean(SMART_FILE_ANNOTATIONS), + getBoolean(DEFAULT_SHOW_SOURCE), getBoolean(VALIDATE_IN_ENTRY_EDITOR), + getBoolean(ALLOW_INTEGER_EDITION_BIBTEX), getBoolean(AUTOLINK_FILES_ENABLED), + EntryEditorPreferences.JournalPopupEnabled.fromString(get(JOURNAL_POPUP)), getBoolean(SHOW_SCITE_TAB), + getBoolean(SHOW_USER_COMMENTS_FIELDS), getDouble(ENTRY_EDITOR_PREVIEW_DIVIDER_POS)); EasyBind.listen(entryEditorPreferences.entryEditorTabs(), (_, _, newValue) -> storeEntryEditorTabs(newValue)); // defaultEntryEditorTabs are read-only - EasyBind.listen(entryEditorPreferences.shouldOpenOnNewEntryProperty(), (_, _, newValue) -> putBoolean(AUTO_OPEN_FORM, newValue)); - EasyBind.listen(entryEditorPreferences.shouldShowRecommendationsTabProperty(), (_, _, newValue) -> putBoolean(SHOW_RECOMMENDATIONS, newValue)); - EasyBind.listen(entryEditorPreferences.shouldShowAiSummaryTabProperty(), (_, _, newValue) -> putBoolean(SHOW_AI_SUMMARY, newValue)); - EasyBind.listen(entryEditorPreferences.shouldShowAiChatTabProperty(), (_, _, newValue) -> putBoolean(SHOW_AI_CHAT, newValue)); - EasyBind.listen(entryEditorPreferences.shouldShowLatexCitationsTabProperty(), (_, _, newValue) -> putBoolean(SHOW_LATEX_CITATIONS, newValue)); - EasyBind.listen(entryEditorPreferences.shouldShowFileAnnotationsTabProperty(), (_, _, newValue) -> putBoolean(SMART_FILE_ANNOTATIONS, newValue)); - EasyBind.listen(entryEditorPreferences.showSourceTabByDefaultProperty(), (_, _, newValue) -> putBoolean(DEFAULT_SHOW_SOURCE, newValue)); - EasyBind.listen(entryEditorPreferences.enableValidationProperty(), (_, _, newValue) -> putBoolean(VALIDATE_IN_ENTRY_EDITOR, newValue)); - EasyBind.listen(entryEditorPreferences.allowIntegerEditionBibtexProperty(), (_, _, newValue) -> putBoolean(ALLOW_INTEGER_EDITION_BIBTEX, newValue)); - EasyBind.listen(entryEditorPreferences.autoLinkEnabledProperty(), (_, _, newValue) -> putBoolean(AUTOLINK_FILES_ENABLED, newValue)); - EasyBind.listen(entryEditorPreferences.enableJournalPopupProperty(), (_, _, newValue) -> put(JOURNAL_POPUP, newValue.toString())); - EasyBind.listen(entryEditorPreferences.shouldShowLSciteTabProperty(), (_, _, newValue) -> putBoolean(SHOW_SCITE_TAB, newValue)); - EasyBind.listen(entryEditorPreferences.showUserCommentsFieldsProperty(), (_, _, newValue) -> putBoolean(SHOW_USER_COMMENTS_FIELDS, newValue)); - EasyBind.listen(entryEditorPreferences.previewWidthDividerPositionProperty(), (_, _, newValue) -> putDouble(ENTRY_EDITOR_PREVIEW_DIVIDER_POS, newValue.doubleValue())); + EasyBind.listen(entryEditorPreferences.shouldOpenOnNewEntryProperty(), + (_, _, newValue) -> putBoolean(AUTO_OPEN_FORM, newValue)); + EasyBind.listen(entryEditorPreferences.shouldShowRecommendationsTabProperty(), + (_, _, newValue) -> putBoolean(SHOW_RECOMMENDATIONS, newValue)); + EasyBind.listen(entryEditorPreferences.shouldShowAiSummaryTabProperty(), + (_, _, newValue) -> putBoolean(SHOW_AI_SUMMARY, newValue)); + EasyBind.listen(entryEditorPreferences.shouldShowAiChatTabProperty(), + (_, _, newValue) -> putBoolean(SHOW_AI_CHAT, newValue)); + EasyBind.listen(entryEditorPreferences.shouldShowLatexCitationsTabProperty(), + (_, _, newValue) -> putBoolean(SHOW_LATEX_CITATIONS, newValue)); + EasyBind.listen(entryEditorPreferences.shouldShowFileAnnotationsTabProperty(), + (_, _, newValue) -> putBoolean(SMART_FILE_ANNOTATIONS, newValue)); + EasyBind.listen(entryEditorPreferences.showSourceTabByDefaultProperty(), + (_, _, newValue) -> putBoolean(DEFAULT_SHOW_SOURCE, newValue)); + EasyBind.listen(entryEditorPreferences.enableValidationProperty(), + (_, _, newValue) -> putBoolean(VALIDATE_IN_ENTRY_EDITOR, newValue)); + EasyBind.listen(entryEditorPreferences.allowIntegerEditionBibtexProperty(), + (_, _, newValue) -> putBoolean(ALLOW_INTEGER_EDITION_BIBTEX, newValue)); + EasyBind.listen(entryEditorPreferences.autoLinkEnabledProperty(), + (_, _, newValue) -> putBoolean(AUTOLINK_FILES_ENABLED, newValue)); + EasyBind.listen(entryEditorPreferences.enableJournalPopupProperty(), + (_, _, newValue) -> put(JOURNAL_POPUP, newValue.toString())); + EasyBind.listen(entryEditorPreferences.shouldShowLSciteTabProperty(), + (_, _, newValue) -> putBoolean(SHOW_SCITE_TAB, newValue)); + EasyBind.listen(entryEditorPreferences.showUserCommentsFieldsProperty(), + (_, _, newValue) -> putBoolean(SHOW_USER_COMMENTS_FIELDS, newValue)); + EasyBind.listen(entryEditorPreferences.previewWidthDividerPositionProperty(), + (_, _, newValue) -> putDouble(ENTRY_EDITOR_PREVIEW_DIVIDER_POS, newValue.doubleValue())); return entryEditorPreferences; } /** * Get a Map of defined tab names to default tab fields. - * - * @return A map of the currently defined tabs in the entry editor from scratch to cache + * @return A map of the currently defined tabs in the entry editor from scratch to + * cache */ private Map> getEntryEditorTabs() { Map> tabs = new LinkedHashMap<>(); @@ -485,16 +591,15 @@ private Map> getEntryEditorTabs() { /** * Stores the defined tabs and corresponding fields in the preferences. - * - * @param customTabs a map of tab names and the corresponding set of fields to be displayed in + * @param customTabs a map of tab names and the corresponding set of fields to be + * displayed in */ private void storeEntryEditorTabs(Map> customTabs) { String[] names = customTabs.keySet().toArray(String[]::new); - String[] fields = customTabs.values().stream() - .map(set -> set.stream() - .map(Field::getName) - .collect(Collectors.joining(STRINGLIST_DELIMITER.toString()))) - .toArray(String[]::new); + String[] fields = customTabs.values() + .stream() + .map(set -> set.stream().map(Field::getName).collect(Collectors.joining(STRINGLIST_DELIMITER.toString()))) + .toArray(String[]::new); for (int i = 0; i < customTabs.size(); i++) { put(CUSTOM_TAB_NAME + i, names[i]); @@ -520,7 +625,8 @@ private SequencedMap> getDefaultEntryEditorTabs() { break; } - customTabsMap.put(name, FieldFactory.parseFieldList((String) defaults.get(CUSTOM_TAB_FIELDS + "_def" + defNumber))); + customTabsMap.put(name, + FieldFactory.parseFieldList((String) defaults.get(CUSTOM_TAB_FIELDS + "_def" + defNumber))); defNumber++; } return customTabsMap; @@ -533,23 +639,26 @@ public MergeDialogPreferences getMergeDialogPreferences() { return mergeDialogPreferences; } - mergeDialogPreferences = new MergeDialogPreferences( - DiffMode.parse(get(MERGE_ENTRIES_DIFF_MODE)), - getBoolean(MERGE_ENTRIES_SHOULD_SHOW_DIFF), - getBoolean(MERGE_ENTRIES_SHOULD_SHOW_UNIFIED_DIFF), - getBoolean(MERGE_ENTRIES_HIGHLIGHT_WORDS), - getBoolean(MERGE_SHOW_ONLY_CHANGED_FIELDS), - getBoolean(MERGE_APPLY_TO_ALL_ENTRIES), - DuplicateResolverDialog.DuplicateResolverResult.parse(get(DUPLICATE_RESOLVER_DECISION_RESULT_ALL_ENTRIES)) - ); - - EasyBind.listen(mergeDialogPreferences.mergeDiffModeProperty(), (obs, oldValue, newValue) -> put(MERGE_ENTRIES_DIFF_MODE, newValue.name())); - EasyBind.listen(mergeDialogPreferences.mergeShouldShowDiffProperty(), (obs, oldValue, newValue) -> putBoolean(MERGE_ENTRIES_SHOULD_SHOW_DIFF, newValue)); - EasyBind.listen(mergeDialogPreferences.mergeShouldShowUnifiedDiffProperty(), (obs, oldValue, newValue) -> putBoolean(MERGE_ENTRIES_SHOULD_SHOW_UNIFIED_DIFF, newValue)); - EasyBind.listen(mergeDialogPreferences.mergeHighlightWordsProperty(), (obs, oldValue, newValue) -> putBoolean(MERGE_ENTRIES_HIGHLIGHT_WORDS, newValue)); - EasyBind.listen(mergeDialogPreferences.mergeShowChangedFieldOnlyProperty(), (obs, oldValue, newValue) -> putBoolean(MERGE_SHOW_ONLY_CHANGED_FIELDS, newValue)); - EasyBind.listen(mergeDialogPreferences.mergeApplyToAllEntriesProperty(), (obs, oldValue, newValue) -> putBoolean(MERGE_APPLY_TO_ALL_ENTRIES, newValue)); - EasyBind.listen(mergeDialogPreferences.allEntriesDuplicateResolverDecisionProperty(), (obs, oldValue, newValue) -> put(DUPLICATE_RESOLVER_DECISION_RESULT_ALL_ENTRIES, newValue.name())); + mergeDialogPreferences = new MergeDialogPreferences(DiffMode.parse(get(MERGE_ENTRIES_DIFF_MODE)), + getBoolean(MERGE_ENTRIES_SHOULD_SHOW_DIFF), getBoolean(MERGE_ENTRIES_SHOULD_SHOW_UNIFIED_DIFF), + getBoolean(MERGE_ENTRIES_HIGHLIGHT_WORDS), getBoolean(MERGE_SHOW_ONLY_CHANGED_FIELDS), + getBoolean(MERGE_APPLY_TO_ALL_ENTRIES), DuplicateResolverDialog.DuplicateResolverResult + .parse(get(DUPLICATE_RESOLVER_DECISION_RESULT_ALL_ENTRIES))); + + EasyBind.listen(mergeDialogPreferences.mergeDiffModeProperty(), + (obs, oldValue, newValue) -> put(MERGE_ENTRIES_DIFF_MODE, newValue.name())); + EasyBind.listen(mergeDialogPreferences.mergeShouldShowDiffProperty(), + (obs, oldValue, newValue) -> putBoolean(MERGE_ENTRIES_SHOULD_SHOW_DIFF, newValue)); + EasyBind.listen(mergeDialogPreferences.mergeShouldShowUnifiedDiffProperty(), + (obs, oldValue, newValue) -> putBoolean(MERGE_ENTRIES_SHOULD_SHOW_UNIFIED_DIFF, newValue)); + EasyBind.listen(mergeDialogPreferences.mergeHighlightWordsProperty(), + (obs, oldValue, newValue) -> putBoolean(MERGE_ENTRIES_HIGHLIGHT_WORDS, newValue)); + EasyBind.listen(mergeDialogPreferences.mergeShowChangedFieldOnlyProperty(), + (obs, oldValue, newValue) -> putBoolean(MERGE_SHOW_ONLY_CHANGED_FIELDS, newValue)); + EasyBind.listen(mergeDialogPreferences.mergeApplyToAllEntriesProperty(), + (obs, oldValue, newValue) -> putBoolean(MERGE_APPLY_TO_ALL_ENTRIES, newValue)); + EasyBind.listen(mergeDialogPreferences.allEntriesDuplicateResolverDecisionProperty(), + (obs, oldValue, newValue) -> put(DUPLICATE_RESOLVER_DECISION_RESULT_ALL_ENTRIES, newValue.name())); return mergeDialogPreferences; } @@ -563,31 +672,37 @@ public AutoCompletePreferences getAutoCompletePreferences() { AutoCompletePreferences.NameFormat nameFormat = AutoCompletePreferences.NameFormat.BOTH; if (getBoolean(AUTOCOMPLETER_LAST_FIRST)) { nameFormat = AutoCompletePreferences.NameFormat.LAST_FIRST; - } else if (getBoolean(AUTOCOMPLETER_FIRST_LAST)) { + } + else if (getBoolean(AUTOCOMPLETER_FIRST_LAST)) { nameFormat = AutoCompletePreferences.NameFormat.FIRST_LAST; } - autoCompletePreferences = new AutoCompletePreferences( - getBoolean(AUTO_COMPLETE), - AutoCompleteFirstNameMode.parse(get(AUTOCOMPLETER_FIRSTNAME_MODE)), - nameFormat, - getStringList(AUTOCOMPLETER_COMPLETE_FIELDS).stream().map(FieldFactory::parseField).collect(Collectors.toSet()) - ); - - EasyBind.listen(autoCompletePreferences.autoCompleteProperty(), (obs, oldValue, newValue) -> putBoolean(AUTO_COMPLETE, newValue)); - EasyBind.listen(autoCompletePreferences.firstNameModeProperty(), (obs, oldValue, newValue) -> put(AUTOCOMPLETER_FIRSTNAME_MODE, newValue.name())); - autoCompletePreferences.getCompleteFields().addListener((SetChangeListener) c -> - putStringList(AUTOCOMPLETER_COMPLETE_FIELDS, autoCompletePreferences.getCompleteFields().stream() - .map(Field::getName) - .collect(Collectors.toList()))); + autoCompletePreferences = new AutoCompletePreferences(getBoolean(AUTO_COMPLETE), + AutoCompleteFirstNameMode.parse(get(AUTOCOMPLETER_FIRSTNAME_MODE)), nameFormat, + getStringList(AUTOCOMPLETER_COMPLETE_FIELDS).stream() + .map(FieldFactory::parseField) + .collect(Collectors.toSet())); + + EasyBind.listen(autoCompletePreferences.autoCompleteProperty(), + (obs, oldValue, newValue) -> putBoolean(AUTO_COMPLETE, newValue)); + EasyBind.listen(autoCompletePreferences.firstNameModeProperty(), + (obs, oldValue, newValue) -> put(AUTOCOMPLETER_FIRSTNAME_MODE, newValue.name())); + autoCompletePreferences.getCompleteFields() + .addListener((SetChangeListener) c -> putStringList(AUTOCOMPLETER_COMPLETE_FIELDS, + autoCompletePreferences.getCompleteFields() + .stream() + .map(Field::getName) + .collect(Collectors.toList()))); EasyBind.listen(autoCompletePreferences.nameFormatProperty(), (obs, oldValue, newValue) -> { if (autoCompletePreferences.getNameFormat() == AutoCompletePreferences.NameFormat.BOTH) { putBoolean(AUTOCOMPLETER_LAST_FIRST, false); putBoolean(AUTOCOMPLETER_FIRST_LAST, false); - } else if (autoCompletePreferences.getNameFormat() == AutoCompletePreferences.NameFormat.LAST_FIRST) { + } + else if (autoCompletePreferences.getNameFormat() == AutoCompletePreferences.NameFormat.LAST_FIRST) { putBoolean(AUTOCOMPLETER_LAST_FIRST, true); putBoolean(AUTOCOMPLETER_FIRST_LAST, false); - } else { + } + else { putBoolean(AUTOCOMPLETER_LAST_FIRST, false); putBoolean(AUTOCOMPLETER_FIRST_LAST, true); } @@ -602,22 +717,24 @@ public CoreGuiPreferences getGuiPreferences() { return coreGuiPreferences; } - coreGuiPreferences = new CoreGuiPreferences( - getDouble(MAIN_WINDOW_POS_X), - getDouble(MAIN_WINDOW_POS_Y), - getDouble(MAIN_WINDOW_WIDTH), - getDouble(MAIN_WINDOW_HEIGHT), - getBoolean(WINDOW_MAXIMISED), - getDouble(SIDE_PANE_WIDTH), - getDouble(ENTRY_EDITOR_HEIGHT)); - - EasyBind.listen(coreGuiPreferences.positionXProperty(), (_, _, newValue) -> putDouble(MAIN_WINDOW_POS_X, newValue.doubleValue())); - EasyBind.listen(coreGuiPreferences.positionYProperty(), (_, _, newValue) -> putDouble(MAIN_WINDOW_POS_Y, newValue.doubleValue())); - EasyBind.listen(coreGuiPreferences.sizeXProperty(), (_, _, newValue) -> putDouble(MAIN_WINDOW_WIDTH, newValue.doubleValue())); - EasyBind.listen(coreGuiPreferences.sizeYProperty(), (_, _, newValue) -> putDouble(MAIN_WINDOW_HEIGHT, newValue.doubleValue())); - EasyBind.listen(coreGuiPreferences.windowMaximisedProperty(), (_, _, newValue) -> putBoolean(WINDOW_MAXIMISED, newValue)); - EasyBind.listen(coreGuiPreferences.horizontalDividerPositionProperty(), (_, _, newValue) -> putDouble(SIDE_PANE_WIDTH, newValue.doubleValue())); - EasyBind.listen(coreGuiPreferences.getVerticalDividerPositionProperty(), (_, _, newValue) -> putDouble(ENTRY_EDITOR_HEIGHT, newValue.doubleValue())); + coreGuiPreferences = new CoreGuiPreferences(getDouble(MAIN_WINDOW_POS_X), getDouble(MAIN_WINDOW_POS_Y), + getDouble(MAIN_WINDOW_WIDTH), getDouble(MAIN_WINDOW_HEIGHT), getBoolean(WINDOW_MAXIMISED), + getDouble(SIDE_PANE_WIDTH), getDouble(ENTRY_EDITOR_HEIGHT)); + + EasyBind.listen(coreGuiPreferences.positionXProperty(), + (_, _, newValue) -> putDouble(MAIN_WINDOW_POS_X, newValue.doubleValue())); + EasyBind.listen(coreGuiPreferences.positionYProperty(), + (_, _, newValue) -> putDouble(MAIN_WINDOW_POS_Y, newValue.doubleValue())); + EasyBind.listen(coreGuiPreferences.sizeXProperty(), + (_, _, newValue) -> putDouble(MAIN_WINDOW_WIDTH, newValue.doubleValue())); + EasyBind.listen(coreGuiPreferences.sizeYProperty(), + (_, _, newValue) -> putDouble(MAIN_WINDOW_HEIGHT, newValue.doubleValue())); + EasyBind.listen(coreGuiPreferences.windowMaximisedProperty(), + (_, _, newValue) -> putBoolean(WINDOW_MAXIMISED, newValue)); + EasyBind.listen(coreGuiPreferences.horizontalDividerPositionProperty(), + (_, _, newValue) -> putDouble(SIDE_PANE_WIDTH, newValue.doubleValue())); + EasyBind.listen(coreGuiPreferences.getVerticalDividerPositionProperty(), + (_, _, newValue) -> putDouble(ENTRY_EDITOR_HEIGHT, newValue.doubleValue())); return coreGuiPreferences; } @@ -630,19 +747,11 @@ public WorkspacePreferences getWorkspacePreferences() { return workspacePreferences; } - workspacePreferences = new WorkspacePreferences( - getLanguage(), - getBoolean(OVERRIDE_DEFAULT_FONT_SIZE), - getInt(MAIN_FONT_SIZE), - (Integer) defaults.get(MAIN_FONT_SIZE), - new Theme(get(THEME)), - getBoolean(THEME_SYNC_OS), - getBoolean(OPEN_LAST_EDITED), - getBoolean(SHOW_ADVANCED_HINTS), - getBoolean(WARN_ABOUT_DUPLICATES_IN_INSPECTION), - getBoolean(CONFIRM_DELETE), - getBoolean(CONFIRM_HIDE_TAB_BAR), - getStringList(SELECTED_SLR_CATALOGS)); + workspacePreferences = new WorkspacePreferences(getLanguage(), getBoolean(OVERRIDE_DEFAULT_FONT_SIZE), + getInt(MAIN_FONT_SIZE), (Integer) defaults.get(MAIN_FONT_SIZE), new Theme(get(THEME)), + getBoolean(THEME_SYNC_OS), getBoolean(OPEN_LAST_EDITED), getBoolean(SHOW_ADVANCED_HINTS), + getBoolean(WARN_ABOUT_DUPLICATES_IN_INSPECTION), getBoolean(CONFIRM_DELETE), + getBoolean(CONFIRM_HIDE_TAB_BAR), getStringList(SELECTED_SLR_CATALOGS)); EasyBind.listen(workspacePreferences.languageProperty(), (obs, oldValue, newValue) -> { put(LANGUAGE, newValue.getId()); @@ -652,17 +761,27 @@ public WorkspacePreferences getWorkspacePreferences() { } }); - EasyBind.listen(workspacePreferences.shouldOverrideDefaultFontSizeProperty(), (obs, oldValue, newValue) -> putBoolean(OVERRIDE_DEFAULT_FONT_SIZE, newValue)); - EasyBind.listen(workspacePreferences.mainFontSizeProperty(), (obs, oldValue, newValue) -> putInt(MAIN_FONT_SIZE, newValue)); - EasyBind.listen(workspacePreferences.themeProperty(), (obs, oldValue, newValue) -> put(THEME, newValue.getName())); - EasyBind.listen(workspacePreferences.themeSyncOsProperty(), (obs, oldValue, newValue) -> putBoolean(THEME_SYNC_OS, newValue)); - EasyBind.listen(workspacePreferences.openLastEditedProperty(), (obs, oldValue, newValue) -> putBoolean(OPEN_LAST_EDITED, newValue)); - EasyBind.listen(workspacePreferences.showAdvancedHintsProperty(), (obs, oldValue, newValue) -> putBoolean(SHOW_ADVANCED_HINTS, newValue)); - EasyBind.listen(workspacePreferences.warnAboutDuplicatesInInspectionProperty(), (obs, oldValue, newValue) -> putBoolean(WARN_ABOUT_DUPLICATES_IN_INSPECTION, newValue)); - EasyBind.listen(workspacePreferences.confirmDeleteProperty(), (obs, oldValue, newValue) -> putBoolean(CONFIRM_DELETE, newValue)); - EasyBind.listen(workspacePreferences.hideTabBarProperty(), (obs, oldValue, newValue) -> putBoolean(CONFIRM_HIDE_TAB_BAR, newValue)); - workspacePreferences.getSelectedSlrCatalogs().addListener((ListChangeListener) change -> - putStringList(SELECTED_SLR_CATALOGS, workspacePreferences.getSelectedSlrCatalogs())); + EasyBind.listen(workspacePreferences.shouldOverrideDefaultFontSizeProperty(), + (obs, oldValue, newValue) -> putBoolean(OVERRIDE_DEFAULT_FONT_SIZE, newValue)); + EasyBind.listen(workspacePreferences.mainFontSizeProperty(), + (obs, oldValue, newValue) -> putInt(MAIN_FONT_SIZE, newValue)); + EasyBind.listen(workspacePreferences.themeProperty(), + (obs, oldValue, newValue) -> put(THEME, newValue.getName())); + EasyBind.listen(workspacePreferences.themeSyncOsProperty(), + (obs, oldValue, newValue) -> putBoolean(THEME_SYNC_OS, newValue)); + EasyBind.listen(workspacePreferences.openLastEditedProperty(), + (obs, oldValue, newValue) -> putBoolean(OPEN_LAST_EDITED, newValue)); + EasyBind.listen(workspacePreferences.showAdvancedHintsProperty(), + (obs, oldValue, newValue) -> putBoolean(SHOW_ADVANCED_HINTS, newValue)); + EasyBind.listen(workspacePreferences.warnAboutDuplicatesInInspectionProperty(), + (obs, oldValue, newValue) -> putBoolean(WARN_ABOUT_DUPLICATES_IN_INSPECTION, newValue)); + EasyBind.listen(workspacePreferences.confirmDeleteProperty(), + (obs, oldValue, newValue) -> putBoolean(CONFIRM_DELETE, newValue)); + EasyBind.listen(workspacePreferences.hideTabBarProperty(), + (obs, oldValue, newValue) -> putBoolean(CONFIRM_HIDE_TAB_BAR, newValue)); + workspacePreferences.getSelectedSlrCatalogs() + .addListener((ListChangeListener) change -> putStringList(SELECTED_SLR_CATALOGS, + workspacePreferences.getSelectedSlrCatalogs())); return workspacePreferences; } @@ -672,15 +791,16 @@ public UnlinkedFilesDialogPreferences getUnlinkedFilesDialogPreferences() { return unlinkedFilesDialogPreferences; } - unlinkedFilesDialogPreferences = new UnlinkedFilesDialogPreferences( - get(UNLINKED_FILES_SELECTED_EXTENSION), + unlinkedFilesDialogPreferences = new UnlinkedFilesDialogPreferences(get(UNLINKED_FILES_SELECTED_EXTENSION), DateRange.parse(get(UNLINKED_FILES_SELECTED_DATE_RANGE)), - ExternalFileSorter.parse(get(UNLINKED_FILES_SELECTED_SORT)) - ); + ExternalFileSorter.parse(get(UNLINKED_FILES_SELECTED_SORT))); - EasyBind.listen(unlinkedFilesDialogPreferences.unlinkedFilesSelectedExtensionProperty(), (obs, oldValue, newValue) -> put(UNLINKED_FILES_SELECTED_EXTENSION, newValue)); - EasyBind.listen(unlinkedFilesDialogPreferences.unlinkedFilesSelectedDateRangeProperty(), (obs, oldValue, newValue) -> put(UNLINKED_FILES_SELECTED_DATE_RANGE, newValue.name())); - EasyBind.listen(unlinkedFilesDialogPreferences.unlinkedFilesSelectedSortProperty(), (obs, oldValue, newValue) -> put(UNLINKED_FILES_SELECTED_SORT, newValue.name())); + EasyBind.listen(unlinkedFilesDialogPreferences.unlinkedFilesSelectedExtensionProperty(), + (obs, oldValue, newValue) -> put(UNLINKED_FILES_SELECTED_EXTENSION, newValue)); + EasyBind.listen(unlinkedFilesDialogPreferences.unlinkedFilesSelectedDateRangeProperty(), + (obs, oldValue, newValue) -> put(UNLINKED_FILES_SELECTED_DATE_RANGE, newValue.name())); + EasyBind.listen(unlinkedFilesDialogPreferences.unlinkedFilesSelectedSortProperty(), + (obs, oldValue, newValue) -> put(UNLINKED_FILES_SELECTED_SORT, newValue.name())); return unlinkedFilesDialogPreferences; } @@ -692,16 +812,16 @@ public SidePanePreferences getSidePanePreferences() { return sidePanePreferences; } - sidePanePreferences = new SidePanePreferences( - getVisibleSidePanes(), - getSidePanePreferredPositions(), + sidePanePreferences = new SidePanePreferences(getVisibleSidePanes(), getSidePanePreferredPositions(), getInt(SELECTED_FETCHER_INDEX)); - sidePanePreferences.visiblePanes().addListener((InvalidationListener) listener -> - storeVisibleSidePanes(sidePanePreferences.visiblePanes())); - sidePanePreferences.getPreferredPositions().addListener((InvalidationListener) listener -> - storeSidePanePreferredPositions(sidePanePreferences.getPreferredPositions())); - EasyBind.listen(sidePanePreferences.webSearchFetcherSelectedProperty(), (obs, oldValue, newValue) -> putInt(SELECTED_FETCHER_INDEX, newValue)); + sidePanePreferences.visiblePanes() + .addListener((InvalidationListener) listener -> storeVisibleSidePanes(sidePanePreferences.visiblePanes())); + sidePanePreferences.getPreferredPositions() + .addListener((InvalidationListener) listener -> storeSidePanePreferredPositions( + sidePanePreferences.getPreferredPositions())); + EasyBind.listen(sidePanePreferences.webSearchFetcherSelectedProperty(), + (obs, oldValue, newValue) -> putInt(SELECTED_FETCHER_INDEX, newValue)); return sidePanePreferences; } @@ -737,9 +857,11 @@ private Map getSidePanePreferredPositions() { try { SidePaneType type = Enum.valueOf(SidePaneType.class, name); preferredPositions.put(type, Integer.parseInt(componentPositions.get(i))); - } catch (NumberFormatException e) { + } + catch (NumberFormatException e) { LOGGER.debug("Invalid number format for side pane component '{}'", name, e); - } catch (IllegalArgumentException e) { + } + catch (IllegalArgumentException e) { LOGGER.debug("Following component is not a side pane: '{}'", name, e); } } @@ -749,13 +871,12 @@ private Map getSidePanePreferredPositions() { private void storeSidePanePreferredPositions(Map preferredPositions) { // Split the map into a pair of parallel String lists suitable for storage - List names = preferredPositions.keySet().stream() - .map(Enum::toString) - .collect(Collectors.toList()); + List names = preferredPositions.keySet().stream().map(Enum::toString).collect(Collectors.toList()); - List positions = preferredPositions.values().stream() - .map(integer -> Integer.toString(integer)) - .collect(Collectors.toList()); + List positions = preferredPositions.values() + .stream() + .map(integer -> Integer.toString(integer)) + .collect(Collectors.toList()); putStringList(SIDE_PANE_COMPONENT_NAMES, names); putStringList(SIDE_PANE_COMPONENT_PREFERRED_POSITIONS, positions); @@ -768,28 +889,31 @@ public ExternalApplicationsPreferences getExternalApplicationsPreferences() { return externalApplicationsPreferences; } - externalApplicationsPreferences = new ExternalApplicationsPreferences( - get(EMAIL_SUBJECT), - getBoolean(OPEN_FOLDERS_OF_ATTACHED_FILES), - ExternalFileTypes.fromString(get(EXTERNAL_FILE_TYPES)), + externalApplicationsPreferences = new ExternalApplicationsPreferences(get(EMAIL_SUBJECT), + getBoolean(OPEN_FOLDERS_OF_ATTACHED_FILES), ExternalFileTypes.fromString(get(EXTERNAL_FILE_TYPES)), !getBoolean(USE_DEFAULT_CONSOLE_APPLICATION), // mind the ! - get(CONSOLE_COMMAND), - !getBoolean(USE_DEFAULT_FILE_BROWSER_APPLICATION), // mind the ! - get(FILE_BROWSER_COMMAND), - get(KINDLE_EMAIL)); + get(CONSOLE_COMMAND), !getBoolean(USE_DEFAULT_FILE_BROWSER_APPLICATION), // mind + // the + // ! + get(FILE_BROWSER_COMMAND), get(KINDLE_EMAIL)); EasyBind.listen(externalApplicationsPreferences.eMailSubjectProperty(), (obs, oldValue, newValue) -> put(EMAIL_SUBJECT, newValue)); EasyBind.listen(externalApplicationsPreferences.autoOpenEmailAttachmentsFolderProperty(), (obs, oldValue, newValue) -> putBoolean(OPEN_FOLDERS_OF_ATTACHED_FILES, newValue)); EasyBind.listen(externalApplicationsPreferences.useCustomTerminalProperty(), - (obs, oldValue, newValue) -> putBoolean(USE_DEFAULT_CONSOLE_APPLICATION, !newValue)); // mind the ! - externalApplicationsPreferences.getExternalFileTypes().addListener((SetChangeListener) c -> - put(EXTERNAL_FILE_TYPES, ExternalFileTypes.toStringList(externalApplicationsPreferences.getExternalFileTypes()))); + (obs, oldValue, newValue) -> putBoolean(USE_DEFAULT_CONSOLE_APPLICATION, !newValue)); // mind + // the + // ! + externalApplicationsPreferences.getExternalFileTypes() + .addListener((SetChangeListener) c -> put(EXTERNAL_FILE_TYPES, + ExternalFileTypes.toStringList(externalApplicationsPreferences.getExternalFileTypes()))); EasyBind.listen(externalApplicationsPreferences.customTerminalCommandProperty(), (obs, oldValue, newValue) -> put(CONSOLE_COMMAND, newValue)); EasyBind.listen(externalApplicationsPreferences.useCustomFileBrowserProperty(), - (obs, oldValue, newValue) -> putBoolean(USE_DEFAULT_FILE_BROWSER_APPLICATION, !newValue)); // mind the ! + (obs, oldValue, newValue) -> putBoolean(USE_DEFAULT_FILE_BROWSER_APPLICATION, !newValue)); // mind + // the + // ! EasyBind.listen(externalApplicationsPreferences.customFileBrowserCommandProperty(), (obs, oldValue, newValue) -> put(FILE_BROWSER_COMMAND, newValue)); EasyBind.listen(externalApplicationsPreferences.kindleEmailProperty(), @@ -803,23 +927,22 @@ public GroupsPreferences getGroupsPreferences() { return groupsPreferences; } - groupsPreferences = new GroupsPreferences( - getBoolean(GROUP_VIEW_INTERSECTION), - getBoolean(GROUP_VIEW_FILTER), - getBoolean(GROUP_VIEW_INVERT), - getBoolean(AUTO_ASSIGN_GROUP), - getBoolean(DISPLAY_GROUP_COUNT), - GroupHierarchyType.valueOf(get(DEFAULT_HIERARCHICAL_CONTEXT)) - ); + groupsPreferences = new GroupsPreferences(getBoolean(GROUP_VIEW_INTERSECTION), getBoolean(GROUP_VIEW_FILTER), + getBoolean(GROUP_VIEW_INVERT), getBoolean(AUTO_ASSIGN_GROUP), getBoolean(DISPLAY_GROUP_COUNT), + GroupHierarchyType.valueOf(get(DEFAULT_HIERARCHICAL_CONTEXT))); groupsPreferences.groupViewModeProperty().addListener((SetChangeListener) change -> { - putBoolean(GROUP_VIEW_INTERSECTION, groupsPreferences.groupViewModeProperty().contains(GroupViewMode.INTERSECTION)); + putBoolean(GROUP_VIEW_INTERSECTION, + groupsPreferences.groupViewModeProperty().contains(GroupViewMode.INTERSECTION)); putBoolean(GROUP_VIEW_FILTER, groupsPreferences.groupViewModeProperty().contains(GroupViewMode.FILTER)); putBoolean(GROUP_VIEW_INVERT, groupsPreferences.groupViewModeProperty().contains(GroupViewMode.INVERT)); }); - EasyBind.listen(groupsPreferences.autoAssignGroupProperty(), (obs, oldValue, newValue) -> putBoolean(AUTO_ASSIGN_GROUP, newValue)); - EasyBind.listen(groupsPreferences.displayGroupCountProperty(), (obs, oldValue, newValue) -> putBoolean(DISPLAY_GROUP_COUNT, newValue)); - EasyBind.listen(groupsPreferences.defaultHierarchicalContextProperty(), (obs, oldValue, newValue) -> put(DEFAULT_HIERARCHICAL_CONTEXT, newValue.name())); + EasyBind.listen(groupsPreferences.autoAssignGroupProperty(), + (obs, oldValue, newValue) -> putBoolean(AUTO_ASSIGN_GROUP, newValue)); + EasyBind.listen(groupsPreferences.displayGroupCountProperty(), + (obs, oldValue, newValue) -> putBoolean(DISPLAY_GROUP_COUNT, newValue)); + EasyBind.listen(groupsPreferences.defaultHierarchicalContextProperty(), + (obs, oldValue, newValue) -> put(DEFAULT_HIERARCHICAL_CONTEXT, newValue.name())); return groupsPreferences; } @@ -831,7 +954,8 @@ public SpecialFieldsPreferences getSpecialFieldsPreferences() { specialFieldsPreferences = new SpecialFieldsPreferences(getBoolean(SPECIALFIELDSENABLED)); - EasyBind.listen(specialFieldsPreferences.specialFieldsEnabledProperty(), (obs, oldValue, newValue) -> putBoolean(SPECIALFIELDSENABLED, newValue)); + EasyBind.listen(specialFieldsPreferences.specialFieldsEnabledProperty(), + (obs, oldValue, newValue) -> putBoolean(SPECIALFIELDSENABLED, newValue)); return specialFieldsPreferences; } @@ -845,83 +969,81 @@ public PreviewPreferences getPreviewPreferences() { String style = get(PREVIEW_STYLE); List layouts = getPreviewLayouts(style); - this.previewPreferences = new PreviewPreferences( - layouts, - getPreviewCyclePosition(layouts), - new TextBasedPreviewLayout( - style, - getLayoutFormatterPreferences(), + this.previewPreferences = new PreviewPreferences(layouts, getPreviewCyclePosition(layouts), + new TextBasedPreviewLayout(style, getLayoutFormatterPreferences(), Injector.instantiateModelOrService(JournalAbbreviationRepository.class)), - (String) defaults.get(PREVIEW_STYLE), - getBoolean(PREVIEW_AS_TAB), + (String) defaults.get(PREVIEW_STYLE), getBoolean(PREVIEW_AS_TAB), getBoolean(PREVIEW_IN_ENTRY_TABLE_TOOLTIP), - getStringList(PREVIEW_BST_LAYOUT_PATHS).stream() - .map(Path::of) - .collect(Collectors.toList()) - ); - - previewPreferences.getLayoutCycle().addListener((InvalidationListener) c -> storePreviewLayouts(previewPreferences.getLayoutCycle())); - EasyBind.listen(previewPreferences.layoutCyclePositionProperty(), (obs, oldValue, newValue) -> putInt(CYCLE_PREVIEW_POS, newValue)); - EasyBind.listen(previewPreferences.customPreviewLayoutProperty(), (obs, oldValue, newValue) -> put(PREVIEW_STYLE, newValue.getText())); - EasyBind.listen(previewPreferences.showPreviewAsExtraTabProperty(), (obs, oldValue, newValue) -> putBoolean(PREVIEW_AS_TAB, newValue)); - EasyBind.listen(previewPreferences.showPreviewEntryTableTooltip(), (obs, oldValue, newValue) -> putBoolean(PREVIEW_IN_ENTRY_TABLE_TOOLTIP, newValue)); - previewPreferences.getBstPreviewLayoutPaths().addListener((InvalidationListener) c -> storeBstPaths(previewPreferences.getBstPreviewLayoutPaths())); + getStringList(PREVIEW_BST_LAYOUT_PATHS).stream().map(Path::of).collect(Collectors.toList())); + + previewPreferences.getLayoutCycle() + .addListener((InvalidationListener) c -> storePreviewLayouts(previewPreferences.getLayoutCycle())); + EasyBind.listen(previewPreferences.layoutCyclePositionProperty(), + (obs, oldValue, newValue) -> putInt(CYCLE_PREVIEW_POS, newValue)); + EasyBind.listen(previewPreferences.customPreviewLayoutProperty(), + (obs, oldValue, newValue) -> put(PREVIEW_STYLE, newValue.getText())); + EasyBind.listen(previewPreferences.showPreviewAsExtraTabProperty(), + (obs, oldValue, newValue) -> putBoolean(PREVIEW_AS_TAB, newValue)); + EasyBind.listen(previewPreferences.showPreviewEntryTableTooltip(), + (obs, oldValue, newValue) -> putBoolean(PREVIEW_IN_ENTRY_TABLE_TOOLTIP, newValue)); + previewPreferences.getBstPreviewLayoutPaths() + .addListener((InvalidationListener) c -> storeBstPaths(previewPreferences.getBstPreviewLayoutPaths())); return this.previewPreferences; } private void storeBstPaths(List bstPaths) { - putStringList(PREVIEW_BST_LAYOUT_PATHS, bstPaths.stream().map(Path::toAbsolutePath).map(Path::toString).toList()); + putStringList(PREVIEW_BST_LAYOUT_PATHS, + bstPaths.stream().map(Path::toAbsolutePath).map(Path::toString).toList()); } private List getPreviewLayouts(String style) { List cycle = getStringList(CYCLE_PREVIEW); - // For backwards compatibility always add at least the default preview to the cycle + // For backwards compatibility always add at least the default preview to the + // cycle if (cycle.isEmpty()) { cycle.add("Preview"); } - return cycle.stream() - .map(layout -> { - if (CSLStyleUtils.isCitationStyleFile(layout)) { - BibEntryTypesManager entryTypesManager = Injector.instantiateModelOrService(BibEntryTypesManager.class); - return CSLStyleUtils.createCitationStyleFromFile(layout) - .map(file -> (PreviewLayout) new CitationStylePreviewLayout(file, entryTypesManager)) - .orElse(null); - } - if (BstPreviewLayout.isBstStyleFile(layout)) { - return getStringList(PREVIEW_BST_LAYOUT_PATHS).stream() - .filter(path -> path.endsWith(layout)).map(Path::of) - .map(BstPreviewLayout::new) - .findFirst() - .orElse(null); - } else { - return new TextBasedPreviewLayout( - style, - getLayoutFormatterPreferences(), - Injector.instantiateModelOrService(JournalAbbreviationRepository.class)); - } - }).filter(Objects::nonNull) - .collect(Collectors.toList()); + return cycle.stream().map(layout -> { + if (CSLStyleUtils.isCitationStyleFile(layout)) { + BibEntryTypesManager entryTypesManager = Injector.instantiateModelOrService(BibEntryTypesManager.class); + return CSLStyleUtils.createCitationStyleFromFile(layout) + .map(file -> (PreviewLayout) new CitationStylePreviewLayout(file, entryTypesManager)) + .orElse(null); + } + if (BstPreviewLayout.isBstStyleFile(layout)) { + return getStringList(PREVIEW_BST_LAYOUT_PATHS).stream() + .filter(path -> path.endsWith(layout)) + .map(Path::of) + .map(BstPreviewLayout::new) + .findFirst() + .orElse(null); + } + else { + return new TextBasedPreviewLayout(style, getLayoutFormatterPreferences(), + Injector.instantiateModelOrService(JournalAbbreviationRepository.class)); + } + }).filter(Objects::nonNull).collect(Collectors.toList()); } private void storePreviewLayouts(ObservableList previewCycle) { - putStringList(CYCLE_PREVIEW, previewCycle.stream() - .map(layout -> { - if (layout instanceof CitationStylePreviewLayout citationStyleLayout) { - return citationStyleLayout.getFilePath(); - } else { - return layout.getDisplayName(); - } - }).toList() - ); + putStringList(CYCLE_PREVIEW, previewCycle.stream().map(layout -> { + if (layout instanceof CitationStylePreviewLayout citationStyleLayout) { + return citationStyleLayout.getFilePath(); + } + else { + return layout.getDisplayName(); + } + }).toList()); } private int getPreviewCyclePosition(List layouts) { int storedCyclePos = getInt(CYCLE_PREVIEW_POS); if (storedCyclePos < layouts.size()) { return storedCyclePos; - } else { + } + else { return 0; // fallback if stored position is no longer valid } } @@ -935,9 +1057,7 @@ public NameDisplayPreferences getNameDisplayPreferences() { return nameDisplayPreferences; } - nameDisplayPreferences = new NameDisplayPreferences( - getNameDisplayStyle(), - getNameAbbreviationStyle()); + nameDisplayPreferences = new NameDisplayPreferences(getNameDisplayStyle(), getNameAbbreviationStyle()); EasyBind.listen(nameDisplayPreferences.displayStyleProperty(), (obs, oldValue, newValue) -> { putBoolean(NAMES_NATBIB, newValue == NameDisplayPreferences.DisplayStyle.NATBIB); @@ -956,7 +1076,8 @@ private NameDisplayPreferences.AbbreviationStyle getNameAbbreviationStyle() { NameDisplayPreferences.AbbreviationStyle abbreviationStyle = NameDisplayPreferences.AbbreviationStyle.NONE; // default if (getBoolean(ABBR_AUTHOR_NAMES)) { abbreviationStyle = NameDisplayPreferences.AbbreviationStyle.FULL; - } else if (getBoolean(NAMES_LAST_ONLY)) { + } + else if (getBoolean(NAMES_LAST_ONLY)) { abbreviationStyle = NameDisplayPreferences.AbbreviationStyle.LASTNAME_ONLY; } return abbreviationStyle; @@ -966,9 +1087,11 @@ private NameDisplayPreferences.DisplayStyle getNameDisplayStyle() { NameDisplayPreferences.DisplayStyle displayStyle = NameDisplayPreferences.DisplayStyle.LASTNAME_FIRSTNAME; // default if (getBoolean(NAMES_NATBIB)) { displayStyle = NameDisplayPreferences.DisplayStyle.NATBIB; - } else if (getBoolean(NAMES_AS_IS)) { + } + else if (getBoolean(NAMES_AS_IS)) { displayStyle = NameDisplayPreferences.DisplayStyle.AS_IS; - } else if (getBoolean(NAMES_FIRST_LAST)) { + } + else if (getBoolean(NAMES_FIRST_LAST)) { displayStyle = NameDisplayPreferences.DisplayStyle.FIRSTNAME_LASTNAME; } return displayStyle; @@ -983,9 +1106,7 @@ public MainTablePreferences getMainTablePreferences() { return mainTablePreferences; } - mainTablePreferences = new MainTablePreferences( - getMainTableColumnPreferences(), - getBoolean(AUTO_RESIZE_MODE), + mainTablePreferences = new MainTablePreferences(getMainTableColumnPreferences(), getBoolean(AUTO_RESIZE_MODE), getBoolean(EXTRA_FILE_COLUMNS)); EasyBind.listen(mainTablePreferences.resizeColumnsToFitProperty(), @@ -1001,7 +1122,8 @@ public ColumnPreferences getMainTableColumnPreferences() { return mainTableColumnPreferences; } - List columns = getColumns(COLUMN_NAMES, COLUMN_WIDTHS, COLUMN_SORT_TYPES, ColumnPreferences.DEFAULT_COLUMN_WIDTH); + List columns = getColumns(COLUMN_NAMES, COLUMN_WIDTHS, COLUMN_SORT_TYPES, + ColumnPreferences.DEFAULT_COLUMN_WIDTH); List columnSortOrder = getColumnSortOrder(COLUMN_SORT_ORDER, columns); mainTableColumnPreferences = new ColumnPreferences(columns, columnSortOrder); @@ -1010,8 +1132,9 @@ public ColumnPreferences getMainTableColumnPreferences() { putStringList(COLUMN_WIDTHS, getColumnWidthsAsStringList(mainTableColumnPreferences)); putStringList(COLUMN_SORT_TYPES, getColumnSortTypesAsStringList(mainTableColumnPreferences)); }); - mainTableColumnPreferences.getColumnSortOrder().addListener((InvalidationListener) change -> - putStringList(COLUMN_SORT_ORDER, getColumnSortOrderAsStringList(mainTableColumnPreferences))); + mainTableColumnPreferences.getColumnSortOrder() + .addListener((InvalidationListener) change -> putStringList(COLUMN_SORT_ORDER, + getColumnSortOrderAsStringList(mainTableColumnPreferences))); return mainTableColumnPreferences; } @@ -1021,40 +1144,44 @@ public ColumnPreferences getSearchDialogColumnPreferences() { return searchDialogColumnPreferences; } - List columns = getColumns(COLUMN_NAMES, SEARCH_DIALOG_COLUMN_WIDTHS, SEARCH_DIALOG_COLUMN_SORT_TYPES, ColumnPreferences.DEFAULT_COLUMN_WIDTH); + List columns = getColumns(COLUMN_NAMES, SEARCH_DIALOG_COLUMN_WIDTHS, + SEARCH_DIALOG_COLUMN_SORT_TYPES, ColumnPreferences.DEFAULT_COLUMN_WIDTH); List columnSortOrder = getColumnSortOrder(SEARCH_DIALOG_COLUMN_SORT_ORDER, columns); searchDialogColumnPreferences = new ColumnPreferences(columns, columnSortOrder); searchDialogColumnPreferences.getColumns().addListener((InvalidationListener) change -> { // MainTable and SearchResultTable use the same set of columnNames - // putStringList(SEARCH_DIALOG_COLUMN_NAMES, getColumnNamesAsStringList(columnPreferences)); + // putStringList(SEARCH_DIALOG_COLUMN_NAMES, + // getColumnNamesAsStringList(columnPreferences)); putStringList(SEARCH_DIALOG_COLUMN_WIDTHS, getColumnWidthsAsStringList(searchDialogColumnPreferences)); - putStringList(SEARCH_DIALOG_COLUMN_SORT_TYPES, getColumnSortTypesAsStringList(searchDialogColumnPreferences)); + putStringList(SEARCH_DIALOG_COLUMN_SORT_TYPES, + getColumnSortTypesAsStringList(searchDialogColumnPreferences)); }); - searchDialogColumnPreferences.getColumnSortOrder().addListener((InvalidationListener) change -> - putStringList(SEARCH_DIALOG_COLUMN_SORT_ORDER, getColumnSortOrderAsStringList(searchDialogColumnPreferences))); + searchDialogColumnPreferences.getColumnSortOrder() + .addListener((InvalidationListener) change -> putStringList(SEARCH_DIALOG_COLUMN_SORT_ORDER, + getColumnSortOrderAsStringList(searchDialogColumnPreferences))); return searchDialogColumnPreferences; } // --- Generic column handling --- @SuppressWarnings("SameParameterValue") - private List getColumns(String columnNamesList, String columnWidthList, String sortTypeList, double defaultWidth) { + private List getColumns(String columnNamesList, String columnWidthList, String sortTypeList, + double defaultWidth) { List columnNames = getStringList(columnNamesList); - List columnWidths = getStringList(columnWidthList) - .stream() - .map(string -> { - try { - return Double.parseDouble(string); - } catch (NumberFormatException e) { - LOGGER.error("Exception while parsing column widths. Choosing default.", e); - return defaultWidth; - } - }).toList(); - - List columnSortTypes = getStringList(sortTypeList) - .stream() - .map(TableColumn.SortType::valueOf).toList(); + List columnWidths = getStringList(columnWidthList).stream().map(string -> { + try { + return Double.parseDouble(string); + } + catch (NumberFormatException e) { + LOGGER.error("Exception while parsing column widths. Choosing default.", e); + return defaultWidth; + } + }).toList(); + + List columnSortTypes = getStringList(sortTypeList).stream() + .map(TableColumn.SortType::valueOf) + .toList(); List columns = new ArrayList<>(); for (int i = 0; i < columnNames.size(); i++) { @@ -1073,47 +1200,50 @@ private List getColumns(String columnNamesList, String col return columns; } - private List getColumnSortOrder(String sortOrderList, List tableColumns) { + private List getColumnSortOrder(String sortOrderList, + List tableColumns) { List columnsOrdered = new ArrayList<>(); - getStringList(sortOrderList).forEach(columnName -> tableColumns.stream().filter(column -> column.getName().equals(columnName)) - .findFirst() - .ifPresent(columnsOrdered::add)); + getStringList(sortOrderList).forEach(columnName -> tableColumns.stream() + .filter(column -> column.getName().equals(columnName)) + .findFirst() + .ifPresent(columnsOrdered::add)); return columnsOrdered; } private static List getColumnNamesAsStringList(ColumnPreferences columnPreferences) { - return columnPreferences.getColumns().stream() - .map(MainTableColumnModel::getName) - .toList(); + return columnPreferences.getColumns().stream().map(MainTableColumnModel::getName).toList(); } private static List getColumnWidthsAsStringList(ColumnPreferences columnPreferences) { - return columnPreferences.getColumns().stream() - .map(column -> column.widthProperty().getValue().toString()) - .toList(); + return columnPreferences.getColumns() + .stream() + .map(column -> column.widthProperty().getValue().toString()) + .toList(); } private static List getColumnSortTypesAsStringList(ColumnPreferences columnPreferences) { - return columnPreferences.getColumns().stream() - .map(column -> column.sortTypeProperty().getValue().toString()) - .toList(); + return columnPreferences.getColumns() + .stream() + .map(column -> column.sortTypeProperty().getValue().toString()) + .toList(); } private static List getColumnSortOrderAsStringList(ColumnPreferences columnPreferences) { - return columnPreferences.getColumnSortOrder().stream() - .map(MainTableColumnModel::getName) - .collect(Collectors.toList()); + return columnPreferences.getColumnSortOrder() + .stream() + .map(MainTableColumnModel::getName) + .collect(Collectors.toList()); } // endregion /** - * For the export configuration, generates the SelfContainedSaveOrder having the reference to TABLE resolved. + * For the export configuration, generates the SelfContainedSaveOrder having the + * reference to TABLE resolved. */ private SelfContainedSaveOrder getSelfContainedTableSaveOrder() { List sortOrder = getMainTableColumnPreferences().getColumnSortOrder(); - return new SelfContainedSaveOrder( - SaveOrder.OrderType.SPECIFIED, + return new SelfContainedSaveOrder(SaveOrder.OrderType.SPECIFIED, sortOrder.stream().flatMap(model -> model.getSortCriteria().stream()).toList()); } @@ -1121,17 +1251,13 @@ private SelfContainedSaveOrder getSelfContainedTableSaveOrder() { public SelfContainedSaveConfiguration getSelfContainedExportConfiguration() { SaveOrder exportSaveOrder = getExportSaveOrder(); SelfContainedSaveOrder saveOrder = switch (exportSaveOrder.getOrderType()) { - case TABLE -> - this.getSelfContainedTableSaveOrder(); - case SPECIFIED -> - SelfContainedSaveOrder.of(exportSaveOrder); - case ORIGINAL -> - SaveOrder.getDefaultSaveOrder(); + case TABLE -> this.getSelfContainedTableSaveOrder(); + case SPECIFIED -> SelfContainedSaveOrder.of(exportSaveOrder); + case ORIGINAL -> SaveOrder.getDefaultSaveOrder(); }; - return new SelfContainedSaveConfiguration( - saveOrder, false, BibDatabaseWriter.SaveType.WITH_JABREF_META_DATA, getLibraryPreferences() - .shouldAlwaysReformatOnSave()); + return new SelfContainedSaveConfiguration(saveOrder, false, BibDatabaseWriter.SaveType.WITH_JABREF_META_DATA, + getLibraryPreferences().shouldAlwaysReformatOnSave()); } @Override @@ -1158,8 +1284,7 @@ public NewEntryPreferences getNewEntryPreferences() { final int approachIndex = getInt(CREATE_ENTRY_APPROACH); NewEntryDialogTab approach = NewEntryDialogTab.values().length > approachIndex - ? NewEntryDialogTab.values()[approachIndex] - : NewEntryDialogTab.values()[0]; + ? NewEntryDialogTab.values()[approachIndex] : NewEntryDialogTab.values()[0]; final String immediateTypeName = get(CREATE_ENTRY_IMMEDIATE_TYPE); EntryType immediateType = StandardEntryType.Article; @@ -1170,24 +1295,27 @@ public NewEntryPreferences getNewEntryPreferences() { } } - newEntryPreferences = new NewEntryPreferences( - approach, - getBoolean(CREATE_ENTRY_EXPAND_RECOMMENDED), - getBoolean(CREATE_ENTRY_EXPAND_OTHER), - getBoolean(CREATE_ENTRY_EXPAND_CUSTOM), - immediateType, - getBoolean(CREATE_ENTRY_ID_LOOKUP_GUESSING), - get(CREATE_ENTRY_ID_FETCHER_NAME), - get(CREATE_ENTRY_INTERPRET_PARSER_NAME)); - - EasyBind.listen(newEntryPreferences.latestApproachProperty(), (_, _, newValue) -> putInt(CREATE_ENTRY_APPROACH, List.of(NewEntryDialogTab.values()).indexOf(newValue))); - EasyBind.listen(newEntryPreferences.typesRecommendedExpandedProperty(), (_, _, newValue) -> putBoolean(CREATE_ENTRY_EXPAND_RECOMMENDED, newValue)); - EasyBind.listen(newEntryPreferences.typesOtherExpandedProperty(), (_, _, newValue) -> putBoolean(CREATE_ENTRY_EXPAND_OTHER, newValue)); - EasyBind.listen(newEntryPreferences.typesCustomExpandedProperty(), (_, _, newValue) -> putBoolean(CREATE_ENTRY_EXPAND_CUSTOM, newValue)); - EasyBind.listen(newEntryPreferences.latestImmediateTypeProperty(), (_, _, newValue) -> put(CREATE_ENTRY_IMMEDIATE_TYPE, newValue.getDisplayName())); - EasyBind.listen(newEntryPreferences.idLookupGuessingProperty(), (_, _, newValue) -> putBoolean(CREATE_ENTRY_ID_LOOKUP_GUESSING, newValue)); - EasyBind.listen(newEntryPreferences.latestIdFetcherProperty(), (_, _, newValue) -> put(CREATE_ENTRY_ID_FETCHER_NAME, newValue)); - EasyBind.listen(newEntryPreferences.latestInterpretParserProperty(), (_, _, newValue) -> put(CREATE_ENTRY_INTERPRET_PARSER_NAME, newValue)); + newEntryPreferences = new NewEntryPreferences(approach, getBoolean(CREATE_ENTRY_EXPAND_RECOMMENDED), + getBoolean(CREATE_ENTRY_EXPAND_OTHER), getBoolean(CREATE_ENTRY_EXPAND_CUSTOM), immediateType, + getBoolean(CREATE_ENTRY_ID_LOOKUP_GUESSING), get(CREATE_ENTRY_ID_FETCHER_NAME), + get(CREATE_ENTRY_INTERPRET_PARSER_NAME)); + + EasyBind.listen(newEntryPreferences.latestApproachProperty(), (_, _, newValue) -> putInt(CREATE_ENTRY_APPROACH, + List.of(NewEntryDialogTab.values()).indexOf(newValue))); + EasyBind.listen(newEntryPreferences.typesRecommendedExpandedProperty(), + (_, _, newValue) -> putBoolean(CREATE_ENTRY_EXPAND_RECOMMENDED, newValue)); + EasyBind.listen(newEntryPreferences.typesOtherExpandedProperty(), + (_, _, newValue) -> putBoolean(CREATE_ENTRY_EXPAND_OTHER, newValue)); + EasyBind.listen(newEntryPreferences.typesCustomExpandedProperty(), + (_, _, newValue) -> putBoolean(CREATE_ENTRY_EXPAND_CUSTOM, newValue)); + EasyBind.listen(newEntryPreferences.latestImmediateTypeProperty(), + (_, _, newValue) -> put(CREATE_ENTRY_IMMEDIATE_TYPE, newValue.getDisplayName())); + EasyBind.listen(newEntryPreferences.idLookupGuessingProperty(), + (_, _, newValue) -> putBoolean(CREATE_ENTRY_ID_LOOKUP_GUESSING, newValue)); + EasyBind.listen(newEntryPreferences.latestIdFetcherProperty(), + (_, _, newValue) -> put(CREATE_ENTRY_ID_FETCHER_NAME, newValue)); + EasyBind.listen(newEntryPreferences.latestInterpretParserProperty(), + (_, _, newValue) -> put(CREATE_ENTRY_INTERPRET_PARSER_NAME, newValue)); return newEntryPreferences; } @@ -1204,4 +1332,5 @@ protected Path getDefaultPath() { protected boolean moveToTrashSupported() { return NativeDesktop.get().moveToTrashSupported(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/PreferenceTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/PreferenceTabViewModel.java index 393db8e1c03..5b4f364b2f7 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/PreferenceTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/PreferenceTabViewModel.java @@ -5,40 +5,38 @@ public interface PreferenceTabViewModel { /** - * This method is called when the dialog is opened, or if it is made - * visible after being hidden. The tab should update all its values. + * This method is called when the dialog is opened, or if it is made visible after + * being hidden. The tab should update all its values. * - * This is the ONLY PLACE to set values for the fields in the tab. It - * is ILLEGAL to set values only at construction time, because the dialog - * will be reused and updated. + * This is the ONLY PLACE to set values for the fields in the tab. It is ILLEGAL to + * set values only at construction time, because the dialog will be reused and + * updated. */ void setValues(); /** - * This method is called before the {@link #storeSettings()} method, - * to check if there are illegal settings in the tab, or if is ready - * to be closed. - * If the tab is *not* ready, it should display a message to the user - * informing about the illegal setting. + * This method is called before the {@link #storeSettings()} method, to check if there + * are illegal settings in the tab, or if is ready to be closed. If the tab is *not* + * ready, it should display a message to the user informing about the illegal setting. */ default boolean validateSettings() { return true; } /** - * This method is called when the user presses OK in the - * Preferences dialog. Implementing classes must make sure all - * settings presented get stored in PreferencesService. + * This method is called when the user presses OK in the Preferences dialog. + * Implementing classes must make sure all settings presented get stored in + * PreferencesService. */ void storeSettings(); /** - * This method should be called after storing the preferences, to - * collect the properties, which require a restart of JabRef to load - * + * This method should be called after storing the preferences, to collect the + * properties, which require a restart of JabRef to load * @return The messages for the changed properties (e.g. "Changed language: English") */ default List getRestartWarnings() { return List.of(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesDialogView.java b/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesDialogView.java index 617954e645e..fa4a8441b9a 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesDialogView.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesDialogView.java @@ -25,37 +25,52 @@ import org.controlsfx.control.textfield.CustomTextField; /** - * Preferences dialog. Contains a TabbedPane, and tabs will be defined in separate classes. Tabs MUST implement the - * PreferencesTab interface, since this dialog will call the storeSettings() method of all tabs when the user presses - * ok. + * Preferences dialog. Contains a TabbedPane, and tabs will be defined in separate + * classes. Tabs MUST implement the PreferencesTab interface, since this dialog will call + * the storeSettings() method of all tabs when the user presses ok. */ public class PreferencesDialogView extends BaseDialog { public static final String DIALOG_TITLE = Localization.lang("JabRef preferences"); - @FXML private CustomTextField searchBox; - @FXML private ListView preferenceTabList; - @FXML private ScrollPane preferencesContainer; - @FXML private ButtonType saveButton; - @FXML private ToggleButton memoryStickMode; - @Inject private DialogService dialogService; - @Inject private GuiPreferences preferences; - @Inject private ThemeManager themeManager; + @FXML + private CustomTextField searchBox; + + @FXML + private ListView preferenceTabList; + + @FXML + private ScrollPane preferencesContainer; + + @FXML + private ButtonType saveButton; + + @FXML + private ToggleButton memoryStickMode; + + @Inject + private DialogService dialogService; + + @Inject + private GuiPreferences preferences; + + @Inject + private ThemeManager themeManager; private PreferencesDialogViewModel viewModel; + private final Class preferencesTabToSelectClass; public PreferencesDialogView(Class preferencesTabToSelectClass) { this.setTitle(DIALOG_TITLE); this.preferencesTabToSelectClass = preferencesTabToSelectClass; - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); ControlHelper.setAction(saveButton, getDialogPane(), event -> savePreferencesAndCloseDialog()); - // Stop the default button from firing when the user hits enter within the search box + // Stop the default button from firing when the user hits enter within the search + // box searchBox.setOnKeyPressed(event -> { if (event.getCode() == KeyCode.ENTER) { event.consume(); @@ -97,24 +112,25 @@ private void initialize() { preferencesContainer.setContent(preferencesTab.getBuilder()); preferencesTab.prefWidthProperty().bind(preferencesContainer.widthProperty().subtract(10d)); preferencesTab.getStyleClass().add("preferencesTab"); - } else { + } + else { preferencesContainer.setContent(null); } }); if (this.preferencesTabToSelectClass != null) { Optional tabToSelectIfExist = preferenceTabList.getItems() - .stream() - .filter(prefTab -> prefTab.getClass().equals(preferencesTabToSelectClass)) - .findFirst(); - tabToSelectIfExist.ifPresent(preferencesTab -> preferenceTabList.getSelectionModel().select(preferencesTab)); - } else { + .stream() + .filter(prefTab -> prefTab.getClass().equals(preferencesTabToSelectClass)) + .findFirst(); + tabToSelectIfExist + .ifPresent(preferencesTab -> preferenceTabList.getSelectionModel().select(preferencesTab)); + } + else { preferenceTabList.getSelectionModel().selectFirst(); } - new ViewModelListCellFactory() - .withText(PreferencesTab::getTabName) - .install(preferenceTabList); + new ViewModelListCellFactory().withText(PreferencesTab::getTabName).install(preferenceTabList); memoryStickMode.selectedProperty().bindBidirectional(viewModel.getMemoryStickProperty()); @@ -153,4 +169,5 @@ void showAllPreferences() { void resetPreferences() { viewModel.resetPreferences(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java index 5836422f225..2002f61a658 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java @@ -52,41 +52,25 @@ public class PreferencesDialogViewModel extends AbstractViewModel { private final SimpleBooleanProperty memoryStickProperty = new SimpleBooleanProperty(); private final DialogService dialogService; + private final GuiPreferences preferences; + private final ObservableList preferenceTabs; public PreferencesDialogViewModel(DialogService dialogService, GuiPreferences preferences) { this.dialogService = dialogService; this.preferences = preferences; - // This enables passing unsaved preference values from the AI tab to the "web search" tab. + // This enables passing unsaved preference values from the AI tab to the "web + // search" tab. AiTab aiTab = new AiTab(); - preferenceTabs = FXCollections.observableArrayList( - new GeneralTab(), - new KeyBindingsTab(), - new GroupsTab(), - new WebSearchTab(aiTab.aiEnabledProperty()), - aiTab, - new EntryTab(), - new TableTab(), - new PreviewTab(), - new EntryEditorTab(), - new CustomEntryTypesTab(), - new CitationKeyPatternTab(), - new LinkedFilesTab(), - new ExportTab(), - new AutoCompletionTab(), - new ProtectedTermsTab(), - new ExternalTab(), - new ExternalFileTypesTab(), - new JournalAbbreviationsTab(), - new NameFormatterTab(), - new XmpPrivacyTab(), - new CustomImporterTab(), - new CustomExporterTab(), - new NetworkTab() - ); + preferenceTabs = FXCollections.observableArrayList(new GeneralTab(), new KeyBindingsTab(), new GroupsTab(), + new WebSearchTab(aiTab.aiEnabledProperty()), aiTab, new EntryTab(), new TableTab(), new PreviewTab(), + new EntryEditorTab(), new CustomEntryTypesTab(), new CitationKeyPatternTab(), new LinkedFilesTab(), + new ExportTab(), new AutoCompletionTab(), new ProtectedTermsTab(), new ExternalTab(), + new ExternalFileTypesTab(), new JournalAbbreviationsTab(), new NameFormatterTab(), new XmpPrivacyTab(), + new CustomImporterTab(), new CustomExporterTab(), new NetworkTab()); } public ObservableList getPreferenceTabs() { @@ -95,43 +79,44 @@ public ObservableList getPreferenceTabs() { public void importPreferences() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.XML) - .withDefaultExtension(StandardFileType.XML) - .withInitialDirectory(preferences.getInternalPreferences().getLastPreferencesExportPath()).build(); - - dialogService.showFileOpenDialog(fileDialogConfiguration) - .ifPresent(file -> { - try { - preferences.importPreferences(file); - setValues(); - - dialogService.showWarningDialogAndWait(Localization.lang("Import preferences"), - Localization.lang("You must restart JabRef for this to come into effect.")); - } catch (JabRefException ex) { - LOGGER.error("Error while importing preferences", ex); - dialogService.showErrorDialogAndWait(Localization.lang("Import preferences"), ex); - } - }); + .addExtensionFilter(StandardFileType.XML) + .withDefaultExtension(StandardFileType.XML) + .withInitialDirectory(preferences.getInternalPreferences().getLastPreferencesExportPath()) + .build(); + + dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(file -> { + try { + preferences.importPreferences(file); + setValues(); + + dialogService.showWarningDialogAndWait(Localization.lang("Import preferences"), + Localization.lang("You must restart JabRef for this to come into effect.")); + } + catch (JabRefException ex) { + LOGGER.error("Error while importing preferences", ex); + dialogService.showErrorDialogAndWait(Localization.lang("Import preferences"), ex); + } + }); } public void exportPreferences() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.XML) - .withDefaultExtension(StandardFileType.XML) - .withInitialDirectory(preferences.getInternalPreferences().getLastPreferencesExportPath()) - .build(); - - dialogService.showFileSaveDialog(fileDialogConfiguration) - .ifPresent(exportFile -> { - try { - storeAllSettings(); - preferences.exportPreferences(exportFile); - preferences.getInternalPreferences().setLastPreferencesExportPath(exportFile); - } catch (JabRefException ex) { - LOGGER.warn(ex.getMessage(), ex); - dialogService.showErrorDialogAndWait(Localization.lang("Export preferences"), ex); - } - }); + .addExtensionFilter(StandardFileType.XML) + .withDefaultExtension(StandardFileType.XML) + .withInitialDirectory(preferences.getInternalPreferences().getLastPreferencesExportPath()) + .build(); + + dialogService.showFileSaveDialog(fileDialogConfiguration).ifPresent(exportFile -> { + try { + storeAllSettings(); + preferences.exportPreferences(exportFile); + preferences.getInternalPreferences().setLastPreferencesExportPath(exportFile); + } + catch (JabRefException ex) { + LOGGER.warn(ex.getMessage(), ex); + dialogService.showErrorDialogAndWait(Localization.lang("Export preferences"), ex); + } + }); } public void showPreferences() { @@ -142,14 +127,14 @@ public void resetPreferences() { boolean resetPreferencesConfirmed = dialogService.showConfirmationDialogAndWait( Localization.lang("Reset preferences"), Localization.lang("Are you sure you want to reset all settings to default values?"), - Localization.lang("Reset preferences"), - Localization.lang("Cancel")); + Localization.lang("Reset preferences"), Localization.lang("Cancel")); if (resetPreferencesConfirmed) { try { preferences.clear(); dialogService.showWarningDialogAndWait(Localization.lang("Reset preferences"), Localization.lang("You must restart JabRef for this to come into effect.")); - } catch (BackingStoreException ex) { + } + catch (BackingStoreException ex) { LOGGER.error("Error while resetting preferences", ex); dialogService.showErrorDialogAndWait(Localization.lang("Reset preferences"), ex); } @@ -185,8 +170,7 @@ public void storeAllSettings() { if (!restartWarnings.isEmpty()) { dialogService.showWarningDialogAndWait(Localization.lang("Restart required"), - String.join(",\n", restartWarnings) - + "\n\n" + String.join(",\n", restartWarnings) + "\n\n" + Localization.lang("You must restart JabRef for this to come into effect.")); } @@ -208,4 +192,5 @@ public void setValues() { public BooleanProperty getMemoryStickProperty() { return memoryStickProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesFilter.java b/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesFilter.java index 8c27486f55d..a41f6ec4c77 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesFilter.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesFilter.java @@ -21,27 +21,33 @@ public List getPreferenceOptions() { Map defaults = new HashMap<>(preferences.getDefaults()); Map prefs = preferences.getPreferences(); - return prefs.entrySet().stream() - .map(entry -> new PreferenceOption(entry.getKey(), entry.getValue(), defaults.get(entry.getKey()))) - .collect(Collectors.toList()); + return prefs.entrySet() + .stream() + .map(entry -> new PreferenceOption(entry.getKey(), entry.getValue(), defaults.get(entry.getKey()))) + .collect(Collectors.toList()); } public List getDeviatingPreferences() { return getPreferenceOptions().stream() - .filter(PreferenceOption::isChanged) - .sorted() - .collect(Collectors.toList()); + .filter(PreferenceOption::isChanged) + .sorted() + .collect(Collectors.toList()); } public enum PreferenceType { + BOOLEAN, INTEGER, STRING + } public static class PreferenceOption implements Comparable { private final String key; + private final Object value; + private final Optional defaultValue; + private final PreferenceType type; public PreferenceOption(String key, Object value, Object defaultValue) { @@ -58,9 +64,11 @@ public PreferenceOption(String key, Object value, Object defaultValue) { private PreferenceType getType(Object value) { if (value instanceof Boolean) { return PreferenceType.BOOLEAN; - } else if (value instanceof Integer) { + } + else if (value instanceof Integer) { return PreferenceType.INTEGER; - } else { + } + else { return PreferenceType.STRING; } } @@ -98,5 +106,7 @@ public PreferenceType getType() { public int compareTo(PreferenceOption o) { return Objects.compare(this.key, o.key, String::compareTo); } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesFilterDialog.java b/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesFilterDialog.java index 60c96af5c1e..a71ef52c5dc 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesFilterDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesFilterDialog.java @@ -25,26 +25,41 @@ public class PreferencesFilterDialog extends BaseDialog { private final PreferencesFilter preferencesFilter; + private final ObservableList preferenceOptions; + private final FilteredList filteredOptions; - @FXML private TableView table; - @FXML private TableColumn columnType; - @FXML private TableColumn columnKey; - @FXML private TableColumn columnValue; - @FXML private TableColumn columnDefaultValue; - @FXML private CheckBox showOnlyDeviatingPreferenceOptions; - @FXML private Label count; - @FXML private TextField searchField; + @FXML + private TableView table; + + @FXML + private TableColumn columnType; + + @FXML + private TableColumn columnKey; + + @FXML + private TableColumn columnValue; + + @FXML + private TableColumn columnDefaultValue; + + @FXML + private CheckBox showOnlyDeviatingPreferenceOptions; + + @FXML + private Label count; + + @FXML + private TextField searchField; public PreferencesFilterDialog(PreferencesFilter preferencesFilter) { this.preferencesFilter = Objects.requireNonNull(preferencesFilter); this.preferenceOptions = FXCollections.observableArrayList(); this.filteredOptions = new FilteredList<>(this.preferenceOptions); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); this.setTitle(Localization.lang("Preferences")); } @@ -62,7 +77,8 @@ private void initialize() { columnType.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(data.getValue().getType())); columnKey.setCellValueFactory(data -> new ReadOnlyStringWrapper(data.getValue().getKey())); columnValue.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(data.getValue().getValue())); - columnDefaultValue.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(data.getValue().getDefaultValue().orElse(""))); + columnDefaultValue + .setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(data.getValue().getDefaultValue().orElse(""))); table.setItems(filteredOptions); count.textProperty().bind(Bindings.size(table.getItems()).asString("(%d)")); updateModel(); @@ -71,8 +87,10 @@ private void initialize() { private void updateModel() { if (showOnlyDeviatingPreferenceOptions.isSelected()) { preferenceOptions.setAll(preferencesFilter.getDeviatingPreferences()); - } else { + } + else { preferenceOptions.setAll(preferencesFilter.getPreferenceOptions()); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesSearchHandler.java b/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesSearchHandler.java index 689b303f883..ea9d5f44687 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesSearchHandler.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesSearchHandler.java @@ -18,19 +18,21 @@ import com.google.common.collect.ArrayListMultimap; /** - * This class is responsible for filtering and searching inside the preferences view based on a search query. + * This class is responsible for filtering and searching inside the preferences view based + * on a search query. */ class PreferencesSearchHandler { private static final PseudoClass CONTROL_HIGHLIGHT = PseudoClass.getPseudoClass("search-highlight"); private final List preferenceTabs; + private final ListProperty filteredPreferenceTabs; + private final ArrayListMultimap preferenceTabsControls; /** * Initializes the PreferencesSearchHandler with the given list of preference tabs. - * * @param preferenceTabs The list of preference tabs. */ PreferencesSearchHandler(List preferenceTabs) { @@ -40,9 +42,8 @@ class PreferencesSearchHandler { } /** - * Filters the preference tabs based on the provided search query. - * Highlights matching controls within tabs. - * + * Filters the preference tabs based on the provided search query. Highlights matching + * controls within tabs. * @param query The search query to filter tabs. */ public void filterTabs(String query) { @@ -56,15 +57,15 @@ public void filterTabs(String query) { String searchQuery = query.toLowerCase(Locale.ROOT); List matchedTabs = preferenceTabs.stream() - .filter(tab -> tabMatchesQuery(tab, searchQuery)) - .collect(Collectors.toList()); + .filter(tab -> tabMatchesQuery(tab, searchQuery)) + .collect(Collectors.toList()); filteredPreferenceTabs.setAll(matchedTabs); } /** - * Checks if a tab matches the given search query either by its name or by its controls. - * + * Checks if a tab matches the given search query either by its name or by its + * controls. * @param tab The preferences tab to check. * @param query The search query. * @return True if the tab matches the query. @@ -72,11 +73,12 @@ public void filterTabs(String query) { private boolean tabMatchesQuery(PreferencesTab tab, String query) { boolean tabNameMatches = tab.getTabName().toLowerCase(Locale.ROOT).contains(query); - boolean controlMatches = preferenceTabsControls.get(tab).stream() - .filter(control -> controlMatchesQuery(control, query)) - .peek(this::highlightControl) - .findAny() - .isPresent(); + boolean controlMatches = preferenceTabsControls.get(tab) + .stream() + .filter(control -> controlMatchesQuery(control, query)) + .peek(this::highlightControl) + .findAny() + .isPresent(); return tabNameMatches || controlMatches; } @@ -86,13 +88,15 @@ private boolean tabMatchesQuery(PreferencesTab tab, String query) { *

    * Matching criteria based on control type: *

      - *
    • Labeled (e.g., Label, Button): Matches if its text contains the query (case-insensitive).
    • - *
    • ComboBox: Matches if any item (converted to string) contains the query (case-insensitive).
    • - *
    • TextField: Matches if its content contains the query (case-insensitive).
    • + *
    • Labeled (e.g., Label, Button): Matches if its text contains the query + * (case-insensitive).
    • + *
    • ComboBox: Matches if any item (converted to string) contains the query + * (case-insensitive).
    • + *
    • TextField: Matches if its content contains the query + * (case-insensitive).
    • *
    - * * @param control The control to check. - * @param query The search query. + * @param query The search query. * @return true if the control contains the query, otherwise false. */ private boolean controlMatchesQuery(Control control, String query) { @@ -101,9 +105,10 @@ private boolean controlMatchesQuery(Control control, String query) { } if (control instanceof ComboBox comboBox && !comboBox.getItems().isEmpty()) { - return comboBox.getItems().stream() - .map(Object::toString) - .anyMatch(item -> item.toLowerCase(Locale.ROOT).contains(query)); + return comboBox.getItems() + .stream() + .map(Object::toString) + .anyMatch(item -> item.toLowerCase(Locale.ROOT).contains(query)); } if (control instanceof TextField textField && textField.getText() != null) { @@ -115,7 +120,6 @@ private boolean controlMatchesQuery(Control control, String query) { /** * Highlights the given control to indicate a match. - * * @param control The control to highlight. */ private void highlightControl(Control control) { @@ -138,7 +142,6 @@ private void resetSearch() { /** * Provides the property representing the filtered list of preferences tabs. - * * @return The filtered preference tabs as a ListProperty. */ protected ListProperty filteredPreferenceTabsProperty() { @@ -147,7 +150,6 @@ protected ListProperty filteredPreferenceTabsProperty() { /** * Builds a map of controls for each preferences tab. - * * @param tabs The list of preferences tabs. * @return A map of preferences tabs to their controls. */ @@ -159,7 +161,6 @@ private ArrayListMultimap getPreferenceTabsControlsMap( /** * Recursively scans nodes and collects all controls. - * * @param node The current node being scanned. * @param controlMap Map storing tabs and their corresponding controls. * @param tab The PreferencesTab associated with the current node. @@ -167,8 +168,10 @@ private ArrayListMultimap getPreferenceTabsControlsMap( private void scanControls(Node node, ArrayListMultimap controlMap, PreferencesTab tab) { if (node instanceof Control control) { controlMap.put(tab, control); - } else if (node instanceof Parent parent) { + } + else if (node instanceof Parent parent) { parent.getChildrenUnmodifiable().forEach(child -> scanControls(child, controlMap, tab)); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesTab.java index 8dfdc3c0210..04aa56d65a3 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/PreferencesTab.java @@ -15,36 +15,35 @@ public interface PreferencesTab { /** * Should return the localized identifier to use for the tab. - * - * @return Identifier for the tab (for instance "General", "Appearance" or "External Files"). + * @return Identifier for the tab (for instance "General", "Appearance" or "External + * Files"). */ String getTabName(); /** - * This method is called when the dialog is opened, or if it is made - * visible after being hidden. This calls the appropriate method in the - * ViewModel. + * This method is called when the dialog is opened, or if it is made visible after + * being hidden. This calls the appropriate method in the ViewModel. */ void setValues(); /** - * This method is called when the user presses OK in the Preferences - * dialog. This calls the appropriate method in the ViewModel. + * This method is called when the user presses OK in the Preferences dialog. This + * calls the appropriate method in the ViewModel. */ void storeSettings(); /** - * This method is called before the {@link #storeSettings()} method, - * to check if there are illegal settings in the tab, or if is ready - * to be closed. This calls the appropriate method in the ViewModel. + * This method is called before the {@link #storeSettings()} method, to check if there + * are illegal settings in the tab, or if is ready to be closed. This calls the + * appropriate method in the ViewModel. */ boolean validateSettings(); /** - * This method should be called after storing the preferences, This - * calls the appropriate method in the ViewModel. - * + * This method should be called after storing the preferences, This calls the + * appropriate method in the ViewModel. * @return The messages for the changed properties (e.g. "Changed language: English") */ List getRestartWarnings(); + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/ShowPreferencesAction.java b/jabgui/src/main/java/org/jabref/gui/preferences/ShowPreferencesAction.java index 0ba021be858..f556faf2e84 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/ShowPreferencesAction.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/ShowPreferencesAction.java @@ -7,6 +7,7 @@ public class ShowPreferencesAction extends SimpleCommand { private final LibraryTabContainer tabContainer; + private final Class preferencesTabToSelectClass; private final DialogService dialogService; @@ -15,7 +16,8 @@ public ShowPreferencesAction(LibraryTabContainer tabContainer, DialogService dia this(tabContainer, null, dialogService); } - public ShowPreferencesAction(LibraryTabContainer tabContainer, Class preferencesTabToSelectClass, DialogService dialogService) { + public ShowPreferencesAction(LibraryTabContainer tabContainer, + Class preferencesTabToSelectClass, DialogService dialogService) { this.tabContainer = tabContainer; this.preferencesTabToSelectClass = preferencesTabToSelectClass; this.dialogService = dialogService; @@ -26,4 +28,5 @@ public void execute() { dialogService.showCustomDialogAndWait(new PreferencesDialogView(preferencesTabToSelectClass)); tabContainer.refresh(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/ai/AiTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/ai/AiTab.java index 1f708435c3f..36d1a8ec96e 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/ai/AiTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/ai/AiTab.java @@ -34,58 +34,123 @@ import org.controlsfx.control.textfield.CustomPasswordField; public class AiTab extends AbstractPreferenceTabView implements PreferencesTab { + private static final String HUGGING_FACE_CHAT_MODEL_PROMPT = "TinyLlama/TinyLlama_v1.1 (or any other model name)"; + private static final String GPT_4_ALL_CHAT_MODEL_PROMPT = "Phi-3.1-mini (or any other local model name from GPT4All)"; - @FXML private CheckBox enableAi; - @FXML private CheckBox autoGenerateEmbeddings; - @FXML private CheckBox autoGenerateSummaries; - - @FXML private ComboBox aiProviderComboBox; - @FXML private ComboBox chatModelComboBox; - @FXML private CustomPasswordField apiKeyTextField; - - @FXML private CheckBox customizeExpertSettingsCheckbox; - @FXML private VBox expertSettingsPane; - - @FXML private TextField apiBaseUrlTextField; - @FXML private SearchableComboBox embeddingModelComboBox; - @FXML private TextField temperatureTextField; - @FXML private IntegerInputField contextWindowSizeTextField; - @FXML private IntegerInputField documentSplitterChunkSizeTextField; - @FXML private IntegerInputField documentSplitterOverlapSizeTextField; - @FXML private IntegerInputField ragMaxResultsCountTextField; - @FXML private TextField ragMinScoreTextField; - - @FXML private TabPane templatesTabPane; - @FXML private Tab systemMessageForChattingTab; - @FXML private Tab userMessageForChattingTab; - @FXML private Tab summarizationChunkSystemMessageTab; - @FXML private Tab summarizationChunkUserMessageTab; - @FXML private Tab summarizationCombineSystemMessageTab; - @FXML private Tab summarizationCombineUserMessageTab; - @FXML private Tab citationParsingSystemMessageTab; - @FXML private Tab citationParsingUserMessageTab; - - @FXML private TextArea systemMessageTextArea; - @FXML private TextArea userMessageTextArea; - @FXML private TextArea summarizationChunkSystemMessageTextArea; - @FXML private TextArea summarizationChunkUserMessageTextArea; - @FXML private TextArea summarizationCombineSystemMessageTextArea; - @FXML private TextArea summarizationCombineUserMessageTextArea; - @FXML private TextArea citationParsingSystemMessageTextArea; - @FXML private TextArea citationParsingUserMessageTextArea; - - @FXML private Button generalSettingsHelp; - @FXML private Button expertSettingsHelp; - @FXML private Button templatesHelp; + @FXML + private CheckBox enableAi; + + @FXML + private CheckBox autoGenerateEmbeddings; + + @FXML + private CheckBox autoGenerateSummaries; + + @FXML + private ComboBox aiProviderComboBox; + + @FXML + private ComboBox chatModelComboBox; + + @FXML + private CustomPasswordField apiKeyTextField; + + @FXML + private CheckBox customizeExpertSettingsCheckbox; + + @FXML + private VBox expertSettingsPane; + + @FXML + private TextField apiBaseUrlTextField; + + @FXML + private SearchableComboBox embeddingModelComboBox; + + @FXML + private TextField temperatureTextField; + + @FXML + private IntegerInputField contextWindowSizeTextField; + + @FXML + private IntegerInputField documentSplitterChunkSizeTextField; + + @FXML + private IntegerInputField documentSplitterOverlapSizeTextField; + + @FXML + private IntegerInputField ragMaxResultsCountTextField; + + @FXML + private TextField ragMinScoreTextField; + + @FXML + private TabPane templatesTabPane; + + @FXML + private Tab systemMessageForChattingTab; + + @FXML + private Tab userMessageForChattingTab; + + @FXML + private Tab summarizationChunkSystemMessageTab; + + @FXML + private Tab summarizationChunkUserMessageTab; + + @FXML + private Tab summarizationCombineSystemMessageTab; + + @FXML + private Tab summarizationCombineUserMessageTab; + + @FXML + private Tab citationParsingSystemMessageTab; + + @FXML + private Tab citationParsingUserMessageTab; + + @FXML + private TextArea systemMessageTextArea; + + @FXML + private TextArea userMessageTextArea; + + @FXML + private TextArea summarizationChunkSystemMessageTextArea; + + @FXML + private TextArea summarizationChunkUserMessageTextArea; + + @FXML + private TextArea summarizationCombineSystemMessageTextArea; + + @FXML + private TextArea summarizationCombineUserMessageTextArea; + + @FXML + private TextArea citationParsingSystemMessageTextArea; + + @FXML + private TextArea citationParsingUserMessageTextArea; + + @FXML + private Button generalSettingsHelp; + + @FXML + private Button expertSettingsHelp; + + @FXML + private Button templatesHelp; private final ControlsFxVisualizer visualizer = new ControlsFxVisualizer(); public AiTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } public void initialize() { @@ -103,22 +168,36 @@ public void initialize() { private void initializeHelp() { ActionFactory actionFactory = new ActionFactory(); - actionFactory.configureIconButton(StandardActions.HELP, new HelpAction(HelpFile.AI_GENERAL_SETTINGS, dialogService, preferences.getExternalApplicationsPreferences()), generalSettingsHelp); - actionFactory.configureIconButton(StandardActions.HELP, new HelpAction(HelpFile.AI_EXPERT_SETTINGS, dialogService, preferences.getExternalApplicationsPreferences()), expertSettingsHelp); - actionFactory.configureIconButton(StandardActions.HELP, new HelpAction(HelpFile.AI_TEMPLATES, dialogService, preferences.getExternalApplicationsPreferences()), templatesHelp); + actionFactory.configureIconButton(StandardActions.HELP, new HelpAction(HelpFile.AI_GENERAL_SETTINGS, + dialogService, preferences.getExternalApplicationsPreferences()), generalSettingsHelp); + actionFactory.configureIconButton(StandardActions.HELP, new HelpAction(HelpFile.AI_EXPERT_SETTINGS, + dialogService, preferences.getExternalApplicationsPreferences()), expertSettingsHelp); + actionFactory.configureIconButton(StandardActions.HELP, + new HelpAction(HelpFile.AI_TEMPLATES, dialogService, preferences.getExternalApplicationsPreferences()), + templatesHelp); } private void initializeTemplates() { - systemMessageTextArea.textProperty().bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.CHATTING_SYSTEM_MESSAGE)); - userMessageTextArea.textProperty().bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.CHATTING_USER_MESSAGE)); - summarizationChunkSystemMessageTextArea.textProperty().bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.SUMMARIZATION_CHUNK_SYSTEM_MESSAGE)); - summarizationChunkUserMessageTextArea.textProperty().bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.SUMMARIZATION_CHUNK_USER_MESSAGE)); - summarizationCombineSystemMessageTextArea.textProperty().bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.SUMMARIZATION_COMBINE_SYSTEM_MESSAGE)); - summarizationCombineUserMessageTextArea.textProperty().bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.SUMMARIZATION_COMBINE_USER_MESSAGE)); - citationParsingSystemMessageTextArea.textProperty().bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.CITATION_PARSING_SYSTEM_MESSAGE)); - citationParsingUserMessageTextArea.textProperty().bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.CITATION_PARSING_USER_MESSAGE)); - - templatesTabPane.getSelectionModel().selectedItemProperty().addListener(_ -> viewModel.selectedTemplateProperty().set(getAiTemplate())); + systemMessageTextArea.textProperty() + .bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.CHATTING_SYSTEM_MESSAGE)); + userMessageTextArea.textProperty() + .bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.CHATTING_USER_MESSAGE)); + summarizationChunkSystemMessageTextArea.textProperty() + .bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.SUMMARIZATION_CHUNK_SYSTEM_MESSAGE)); + summarizationChunkUserMessageTextArea.textProperty() + .bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.SUMMARIZATION_CHUNK_USER_MESSAGE)); + summarizationCombineSystemMessageTextArea.textProperty() + .bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.SUMMARIZATION_COMBINE_SYSTEM_MESSAGE)); + summarizationCombineUserMessageTextArea.textProperty() + .bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.SUMMARIZATION_COMBINE_USER_MESSAGE)); + citationParsingSystemMessageTextArea.textProperty() + .bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.CITATION_PARSING_SYSTEM_MESSAGE)); + citationParsingUserMessageTextArea.textProperty() + .bindBidirectional(viewModel.getTemplateSources().get(AiTemplate.CITATION_PARSING_USER_MESSAGE)); + + templatesTabPane.getSelectionModel() + .selectedItemProperty() + .addListener(_ -> viewModel.selectedTemplateProperty().set(getAiTemplate())); } private void initializeValidations() { @@ -130,73 +209,85 @@ private void initializeValidations() { visualizer.initVisualization(viewModel.getTemperatureTypeValidationStatus(), temperatureTextField); visualizer.initVisualization(viewModel.getTemperatureRangeValidationStatus(), temperatureTextField); visualizer.initVisualization(viewModel.getMessageWindowSizeValidationStatus(), contextWindowSizeTextField); - visualizer.initVisualization(viewModel.getDocumentSplitterChunkSizeValidationStatus(), documentSplitterChunkSizeTextField); - visualizer.initVisualization(viewModel.getDocumentSplitterOverlapSizeValidationStatus(), documentSplitterOverlapSizeTextField); - visualizer.initVisualization(viewModel.getRagMaxResultsCountValidationStatus(), ragMaxResultsCountTextField); + visualizer.initVisualization(viewModel.getDocumentSplitterChunkSizeValidationStatus(), + documentSplitterChunkSizeTextField); + visualizer.initVisualization(viewModel.getDocumentSplitterOverlapSizeValidationStatus(), + documentSplitterOverlapSizeTextField); + visualizer.initVisualization(viewModel.getRagMaxResultsCountValidationStatus(), + ragMaxResultsCountTextField); visualizer.initVisualization(viewModel.getRagMinScoreTypeValidationStatus(), ragMinScoreTextField); visualizer.initVisualization(viewModel.getRagMinScoreRangeValidationStatus(), ragMinScoreTextField); }); } private void initializeExpertSettings() { - customizeExpertSettingsCheckbox.selectedProperty().bindBidirectional(viewModel.customizeExpertSettingsProperty()); + customizeExpertSettingsCheckbox.selectedProperty() + .bindBidirectional(viewModel.customizeExpertSettingsProperty()); customizeExpertSettingsCheckbox.disableProperty().bind(viewModel.disableBasicSettingsProperty()); expertSettingsPane.visibleProperty().bind(customizeExpertSettingsCheckbox.selectedProperty()); expertSettingsPane.managedProperty().bind(customizeExpertSettingsCheckbox.selectedProperty()); - new ViewModelListCellFactory() - .withText(EmbeddingModel::fullInfo) - .install(embeddingModelComboBox); + new ViewModelListCellFactory().withText(EmbeddingModel::fullInfo) + .install(embeddingModelComboBox); embeddingModelComboBox.setItems(viewModel.embeddingModelsProperty()); embeddingModelComboBox.valueProperty().bindBidirectional(viewModel.selectedEmbeddingModelProperty()); embeddingModelComboBox.disableProperty().bind(viewModel.disableExpertSettingsProperty()); apiBaseUrlTextField.textProperty().bindBidirectional(viewModel.apiBaseUrlProperty()); - viewModel.disableExpertSettingsProperty().addListener((observable, oldValue, newValue) -> - apiBaseUrlTextField.setDisable(newValue || viewModel.disableApiBaseUrlProperty().get()) - ); + viewModel.disableExpertSettingsProperty() + .addListener((observable, oldValue, newValue) -> apiBaseUrlTextField + .setDisable(newValue || viewModel.disableApiBaseUrlProperty().get())); - viewModel.disableApiBaseUrlProperty().addListener((observable, oldValue, newValue) -> - apiBaseUrlTextField.setDisable(newValue || viewModel.disableExpertSettingsProperty().get()) - ); + viewModel.disableApiBaseUrlProperty() + .addListener((observable, oldValue, newValue) -> apiBaseUrlTextField + .setDisable(newValue || viewModel.disableExpertSettingsProperty().get())); - // bindBidirectional doesn't work well with number input fields ({@link IntegerInputField}, {@link DoubleInputField}), + // bindBidirectional doesn't work well with number input fields ({@link + // IntegerInputField}, {@link DoubleInputField}), // so they are expanded into `addListener` calls. - contextWindowSizeTextField.valueProperty().addListener((observable, oldValue, newValue) -> - viewModel.contextWindowSizeProperty().set(newValue == null ? 0 : newValue)); + contextWindowSizeTextField.valueProperty() + .addListener((observable, oldValue, newValue) -> viewModel.contextWindowSizeProperty() + .set(newValue == null ? 0 : newValue)); - viewModel.contextWindowSizeProperty().addListener((observable, oldValue, newValue) -> - contextWindowSizeTextField.valueProperty().set(newValue == null ? 0 : newValue.intValue())); + viewModel.contextWindowSizeProperty() + .addListener((observable, oldValue, newValue) -> contextWindowSizeTextField.valueProperty() + .set(newValue == null ? 0 : newValue.intValue())); contextWindowSizeTextField.disableProperty().bind(viewModel.disableExpertSettingsProperty()); temperatureTextField.textProperty().bindBidirectional(viewModel.temperatureProperty()); temperatureTextField.disableProperty().bind(viewModel.disableExpertSettingsProperty()); - documentSplitterChunkSizeTextField.valueProperty().addListener((observable, oldValue, newValue) -> - viewModel.documentSplitterChunkSizeProperty().set(newValue == null ? 0 : newValue)); + documentSplitterChunkSizeTextField.valueProperty() + .addListener((observable, oldValue, newValue) -> viewModel.documentSplitterChunkSizeProperty() + .set(newValue == null ? 0 : newValue)); - viewModel.documentSplitterChunkSizeProperty().addListener((observable, oldValue, newValue) -> - documentSplitterChunkSizeTextField.valueProperty().set(newValue == null ? 0 : newValue.intValue())); + viewModel.documentSplitterChunkSizeProperty() + .addListener((observable, oldValue, newValue) -> documentSplitterChunkSizeTextField.valueProperty() + .set(newValue == null ? 0 : newValue.intValue())); documentSplitterChunkSizeTextField.disableProperty().bind(viewModel.disableExpertSettingsProperty()); - documentSplitterOverlapSizeTextField.valueProperty().addListener((observable, oldValue, newValue) -> - viewModel.documentSplitterOverlapSizeProperty().set(newValue == null ? 0 : newValue)); + documentSplitterOverlapSizeTextField.valueProperty() + .addListener((observable, oldValue, newValue) -> viewModel.documentSplitterOverlapSizeProperty() + .set(newValue == null ? 0 : newValue)); - viewModel.documentSplitterOverlapSizeProperty().addListener((observable, oldValue, newValue) -> - documentSplitterOverlapSizeTextField.valueProperty().set(newValue == null ? 0 : newValue.intValue())); + viewModel.documentSplitterOverlapSizeProperty() + .addListener((observable, oldValue, newValue) -> documentSplitterOverlapSizeTextField.valueProperty() + .set(newValue == null ? 0 : newValue.intValue())); documentSplitterOverlapSizeTextField.disableProperty().bind(viewModel.disableExpertSettingsProperty()); - ragMaxResultsCountTextField.valueProperty().addListener((observable, oldValue, newValue) -> - viewModel.ragMaxResultsCountProperty().set(newValue == null ? 0 : newValue)); + ragMaxResultsCountTextField.valueProperty() + .addListener((observable, oldValue, newValue) -> viewModel.ragMaxResultsCountProperty() + .set(newValue == null ? 0 : newValue)); - viewModel.ragMaxResultsCountProperty().addListener((observable, oldValue, newValue) -> - ragMaxResultsCountTextField.valueProperty().set(newValue == null ? 0 : newValue.intValue())); + viewModel.ragMaxResultsCountProperty() + .addListener((observable, oldValue, newValue) -> ragMaxResultsCountTextField.valueProperty() + .set(newValue == null ? 0 : newValue.intValue())); ragMaxResultsCountTextField.disableProperty().bind(viewModel.disableExpertSettingsProperty()); @@ -207,18 +298,13 @@ private void initializeExpertSettings() { private void initializeApiKey() { apiKeyTextField.textProperty().bindBidirectional(viewModel.apiKeyProperty()); // Disable if GPT4ALL is selected - apiKeyTextField.disableProperty().bind( - Bindings.or( - viewModel.disableBasicSettingsProperty(), - aiProviderComboBox.valueProperty().isEqualTo(AiProvider.GPT4ALL) - ) - ); + apiKeyTextField.disableProperty() + .bind(Bindings.or(viewModel.disableBasicSettingsProperty(), + aiProviderComboBox.valueProperty().isEqualTo(AiProvider.GPT4ALL))); } private void initializeChatModel() { - new ViewModelListCellFactory() - .withText(text -> text) - .install(chatModelComboBox); + new ViewModelListCellFactory().withText(text -> text).install(chatModelComboBox); chatModelComboBox.itemsProperty().bind(viewModel.chatModelsProperty()); chatModelComboBox.valueProperty().bindBidirectional(viewModel.selectedChatModelProperty()); chatModelComboBox.disableProperty().bind(viewModel.disableBasicSettingsProperty()); @@ -234,9 +320,7 @@ private void initializeChatModel() { } private void initializeAiProvider() { - new ViewModelListCellFactory() - .withText(AiProvider::toString) - .install(aiProviderComboBox); + new ViewModelListCellFactory().withText(AiProvider::toString).install(aiProviderComboBox); aiProviderComboBox.itemsProperty().bind(viewModel.aiProvidersProperty()); aiProviderComboBox.valueProperty().bindBidirectional(viewModel.selectedAiProviderProperty()); aiProviderComboBox.disableProperty().bind(viewModel.disableBasicSettingsProperty()); @@ -278,22 +362,30 @@ public Optional getAiTemplate() { Tab selectedTab = templatesTabPane.getSelectionModel().getSelectedItem(); if (selectedTab == systemMessageForChattingTab) { return Optional.of(AiTemplate.CHATTING_SYSTEM_MESSAGE); - } else if (selectedTab == userMessageForChattingTab) { + } + else if (selectedTab == userMessageForChattingTab) { return Optional.of(AiTemplate.CHATTING_USER_MESSAGE); - } else if (selectedTab == summarizationChunkSystemMessageTab) { + } + else if (selectedTab == summarizationChunkSystemMessageTab) { return Optional.of(AiTemplate.SUMMARIZATION_CHUNK_SYSTEM_MESSAGE); - } else if (selectedTab == summarizationChunkUserMessageTab) { + } + else if (selectedTab == summarizationChunkUserMessageTab) { return Optional.of(AiTemplate.SUMMARIZATION_CHUNK_USER_MESSAGE); - } else if (selectedTab == summarizationCombineSystemMessageTab) { + } + else if (selectedTab == summarizationCombineSystemMessageTab) { return Optional.of(AiTemplate.SUMMARIZATION_COMBINE_SYSTEM_MESSAGE); - } else if (selectedTab == summarizationCombineUserMessageTab) { + } + else if (selectedTab == summarizationCombineUserMessageTab) { return Optional.of(AiTemplate.SUMMARIZATION_COMBINE_USER_MESSAGE); - } else if (selectedTab == citationParsingSystemMessageTab) { + } + else if (selectedTab == citationParsingSystemMessageTab) { return Optional.of(AiTemplate.CITATION_PARSING_SYSTEM_MESSAGE); - } else if (selectedTab == citationParsingUserMessageTab) { + } + else if (selectedTab == citationParsingUserMessageTab) { return Optional.of(AiTemplate.CITATION_PARSING_USER_MESSAGE); } return Optional.empty(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java index 87e62180be7..c754298080c 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java @@ -37,88 +37,132 @@ import de.saxsys.mvvmfx.utils.validation.Validator; public class AiTabViewModel implements PreferenceTabViewModel { + private final Locale oldLocale; private final BooleanProperty enableAi = new SimpleBooleanProperty(); + private final BooleanProperty autoGenerateEmbeddings = new SimpleBooleanProperty(); + private final BooleanProperty disableAutoGenerateEmbeddings = new SimpleBooleanProperty(); + private final BooleanProperty autoGenerateSummaries = new SimpleBooleanProperty(); + private final BooleanProperty disableAutoGenerateSummaries = new SimpleBooleanProperty(); - private final ListProperty aiProvidersList = - new SimpleListProperty<>(FXCollections.observableArrayList(AiProvider.values())); + private final ListProperty aiProvidersList = new SimpleListProperty<>( + FXCollections.observableArrayList(AiProvider.values())); + private final ObjectProperty selectedAiProvider = new SimpleObjectProperty<>(); - private final ListProperty chatModelsList = - new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty chatModelsList = new SimpleListProperty<>(FXCollections.observableArrayList()); private final StringProperty currentChatModel = new SimpleStringProperty(); private final StringProperty openAiChatModel = new SimpleStringProperty(); + private final StringProperty mistralAiChatModel = new SimpleStringProperty(); + private final StringProperty geminiChatModel = new SimpleStringProperty(); + private final StringProperty huggingFaceChatModel = new SimpleStringProperty(); + private final StringProperty gpt4AllChatModel = new SimpleStringProperty(); private final StringProperty currentApiKey = new SimpleStringProperty(); private final StringProperty openAiApiKey = new SimpleStringProperty(); + private final StringProperty mistralAiApiKey = new SimpleStringProperty(); + private final StringProperty geminiAiApiKey = new SimpleStringProperty(); + private final StringProperty huggingFaceApiKey = new SimpleStringProperty(); + private final StringProperty gpt4AllApiKey = new SimpleStringProperty(); private final BooleanProperty customizeExpertSettings = new SimpleBooleanProperty(); - private final ListProperty embeddingModelsList = - new SimpleListProperty<>(FXCollections.observableArrayList(EmbeddingModel.values())); + private final ListProperty embeddingModelsList = new SimpleListProperty<>( + FXCollections.observableArrayList(EmbeddingModel.values())); + private final ObjectProperty selectedEmbeddingModel = new SimpleObjectProperty<>(); private final StringProperty currentApiBaseUrl = new SimpleStringProperty(); - private final BooleanProperty disableApiBaseUrl = new SimpleBooleanProperty(true); // {@link HuggingFaceChatModel} and {@link GoogleAiGeminiChatModel} doesn't support setting API base URL + + private final BooleanProperty disableApiBaseUrl = new SimpleBooleanProperty(true); // {@link + // HuggingFaceChatModel} + // and + // {@link + // GoogleAiGeminiChatModel} + // doesn't + // support + // setting + // API + // base + // URL private final StringProperty openAiApiBaseUrl = new SimpleStringProperty(); + private final StringProperty mistralAiApiBaseUrl = new SimpleStringProperty(); + private final StringProperty geminiApiBaseUrl = new SimpleStringProperty(); + private final StringProperty huggingFaceApiBaseUrl = new SimpleStringProperty(); + private final StringProperty gpt4AllApiBaseUrl = new SimpleStringProperty(); - private final Map templateSources = Map.of( - AiTemplate.CHATTING_SYSTEM_MESSAGE, new SimpleStringProperty(), - AiTemplate.CHATTING_USER_MESSAGE, new SimpleStringProperty(), + private final Map templateSources = Map.of(AiTemplate.CHATTING_SYSTEM_MESSAGE, + new SimpleStringProperty(), AiTemplate.CHATTING_USER_MESSAGE, new SimpleStringProperty(), AiTemplate.SUMMARIZATION_CHUNK_SYSTEM_MESSAGE, new SimpleStringProperty(), AiTemplate.SUMMARIZATION_CHUNK_USER_MESSAGE, new SimpleStringProperty(), AiTemplate.SUMMARIZATION_COMBINE_SYSTEM_MESSAGE, new SimpleStringProperty(), AiTemplate.SUMMARIZATION_COMBINE_USER_MESSAGE, new SimpleStringProperty(), AiTemplate.CITATION_PARSING_SYSTEM_MESSAGE, new SimpleStringProperty(), - AiTemplate.CITATION_PARSING_USER_MESSAGE, new SimpleStringProperty() - ); + AiTemplate.CITATION_PARSING_USER_MESSAGE, new SimpleStringProperty()); private final OptionalObjectProperty selectedTemplate = OptionalObjectProperty.empty(); private final StringProperty temperature = new SimpleStringProperty(); + private final IntegerProperty contextWindowSize = new SimpleIntegerProperty(); + private final IntegerProperty documentSplitterChunkSize = new SimpleIntegerProperty(); + private final IntegerProperty documentSplitterOverlapSize = new SimpleIntegerProperty(); + private final IntegerProperty ragMaxResultsCount = new SimpleIntegerProperty(); + private final StringProperty ragMinScore = new SimpleStringProperty(); private final BooleanProperty disableBasicSettings = new SimpleBooleanProperty(true); + private final BooleanProperty disableExpertSettings = new SimpleBooleanProperty(true); private final AiPreferences aiPreferences; private final Validator apiKeyValidator; + private final Validator chatModelValidator; + private final Validator apiBaseUrlValidator; + private final Validator embeddingModelValidator; + private final Validator temperatureTypeValidator; + private final Validator temperatureRangeValidator; + private final Validator contextWindowSizeValidator; + private final Validator documentSplitterChunkSizeValidator; + private final Validator documentSplitterOverlapSizeValidator; + private final Validator ragMaxResultsCountValidator; + private final Validator ragMinScoreTypeValidator; + private final Validator ragMinScoreRangeValidator; public AiTabViewModel(CliPreferences preferences) { @@ -131,18 +175,20 @@ public AiTabViewModel(CliPreferences preferences) { disableExpertSettings.set(!newValue || !customizeExpertSettings.get()); }); - this.customizeExpertSettings.addListener((_, _, newValue) -> - disableExpertSettings.set(!newValue || !enableAi.get()) - ); + this.customizeExpertSettings + .addListener((_, _, newValue) -> disableExpertSettings.set(!newValue || !enableAi.get())); this.selectedAiProvider.addListener((_, oldValue, newValue) -> { List models = AiDefaultPreferences.getAvailableModels(newValue); disableApiBaseUrl.set(newValue == AiProvider.HUGGING_FACE || newValue == AiProvider.GEMINI); - // When we setAll on Hugging Face, models are empty, and currentChatModel become null. - // It becomes null because currentChatModel is bound to combobox, and this combobox becomes empty. - // For some reason, custom edited value in the combobox will be erased, so we need to store the old value. + // When we setAll on Hugging Face, models are empty, and currentChatModel + // become null. + // It becomes null because currentChatModel is bound to combobox, and this + // combobox becomes empty. + // For some reason, custom edited value in the combobox will be erased, so we + // need to store the old value. String oldChatModel = currentChatModel.get(); chatModelsList.setAll(models); @@ -168,7 +214,7 @@ public AiTabViewModel(CliPreferences preferences) { huggingFaceApiKey.set(currentApiKey.get()); huggingFaceApiBaseUrl.set(currentApiBaseUrl.get()); } - case GPT4ALL-> { + case GPT4ALL -> { gpt4AllChatModel.set(oldChatModel); gpt4AllApiKey.set(currentApiKey.get()); gpt4AllApiBaseUrl.set(currentApiBaseUrl.get()); @@ -241,64 +287,50 @@ public AiTabViewModel(CliPreferences preferences) { } }); - this.apiKeyValidator = new FunctionBasedValidator<>( - currentApiKey, - token -> !StringUtil.isBlank(token), + this.apiKeyValidator = new FunctionBasedValidator<>(currentApiKey, token -> !StringUtil.isBlank(token), ValidationMessage.error(Localization.lang("An API key has to be provided"))); - this.chatModelValidator = new FunctionBasedValidator<>( - currentChatModel, + this.chatModelValidator = new FunctionBasedValidator<>(currentChatModel, chatModel -> !StringUtil.isBlank(chatModel), ValidationMessage.error(Localization.lang("Chat model has to be provided"))); - this.apiBaseUrlValidator = new FunctionBasedValidator<>( - currentApiBaseUrl, - token -> !StringUtil.isBlank(token), + this.apiBaseUrlValidator = new FunctionBasedValidator<>(currentApiBaseUrl, token -> !StringUtil.isBlank(token), ValidationMessage.error(Localization.lang("API base URL has to be provided"))); - this.embeddingModelValidator = new FunctionBasedValidator<>( - selectedEmbeddingModel, - Objects::nonNull, + this.embeddingModelValidator = new FunctionBasedValidator<>(selectedEmbeddingModel, Objects::nonNull, ValidationMessage.error(Localization.lang("Embedding model has to be provided"))); - this.temperatureTypeValidator = new FunctionBasedValidator<>( - temperature, + this.temperatureTypeValidator = new FunctionBasedValidator<>(temperature, temp -> LocalizedNumbers.stringToDouble(temp).isPresent(), ValidationMessage.error(Localization.lang("Temperature must be a number"))); - // Source: https://platform.openai.com/docs/api-reference/chat/create#chat-create-temperature - this.temperatureRangeValidator = new FunctionBasedValidator<>( - temperature, + // Source: + // https://platform.openai.com/docs/api-reference/chat/create#chat-create-temperature + this.temperatureRangeValidator = new FunctionBasedValidator<>(temperature, temp -> LocalizedNumbers.stringToDouble(temp).map(t -> t >= 0 && t <= 2).orElse(false), ValidationMessage.error(Localization.lang("Temperature must be between 0 and 2"))); - this.contextWindowSizeValidator = new FunctionBasedValidator<>( - contextWindowSize, - size -> size.intValue() > 0, + this.contextWindowSizeValidator = new FunctionBasedValidator<>(contextWindowSize, size -> size.intValue() > 0, ValidationMessage.error(Localization.lang("Context window size must be greater than 0"))); - this.documentSplitterChunkSizeValidator = new FunctionBasedValidator<>( - documentSplitterChunkSize, + this.documentSplitterChunkSizeValidator = new FunctionBasedValidator<>(documentSplitterChunkSize, size -> size.intValue() > 0, ValidationMessage.error(Localization.lang("Document splitter chunk size must be greater than 0"))); - this.documentSplitterOverlapSizeValidator = new FunctionBasedValidator<>( - documentSplitterOverlapSize, + this.documentSplitterOverlapSizeValidator = new FunctionBasedValidator<>(documentSplitterOverlapSize, size -> size.intValue() > 0 && size.intValue() < documentSplitterChunkSize.get(), - ValidationMessage.error(Localization.lang("Document splitter overlap size must be greater than 0 and less than chunk size"))); + ValidationMessage.error(Localization + .lang("Document splitter overlap size must be greater than 0 and less than chunk size"))); - this.ragMaxResultsCountValidator = new FunctionBasedValidator<>( - ragMaxResultsCount, + this.ragMaxResultsCountValidator = new FunctionBasedValidator<>(ragMaxResultsCount, count -> count.intValue() > 0, ValidationMessage.error(Localization.lang("RAG max results count must be greater than 0"))); - this.ragMinScoreTypeValidator = new FunctionBasedValidator<>( - ragMinScore, + this.ragMinScoreTypeValidator = new FunctionBasedValidator<>(ragMinScore, minScore -> LocalizedNumbers.stringToDouble(minScore).isPresent(), ValidationMessage.error(Localization.lang("RAG minimum score must be a number"))); - this.ragMinScoreRangeValidator = new FunctionBasedValidator<>( - ragMinScore, + this.ragMinScoreRangeValidator = new FunctionBasedValidator<>(ragMinScore, minScore -> LocalizedNumbers.stringToDouble(minScore).map(s -> s > 0 && s < 1).orElse(false), ValidationMessage.error(Localization.lang("RAG minimum score must be greater than 0 and less than 1"))); } @@ -333,8 +365,8 @@ public void setValues() { selectedEmbeddingModel.setValue(aiPreferences.getEmbeddingModel()); - Arrays.stream(AiTemplate.values()).forEach(template -> - templateSources.get(template).set(aiPreferences.getTemplate(template))); + Arrays.stream(AiTemplate.values()) + .forEach(template -> templateSources.get(template).set(aiPreferences.getTemplate(template))); temperature.setValue(LocalizedNumbers.doubleToString(aiPreferences.getTemperature())); contextWindowSize.setValue(aiPreferences.getContextWindowSize()); @@ -359,10 +391,14 @@ public void storeSettings() { aiPreferences.setGpt4AllChatModel(gpt4AllChatModel.get() == null ? "" : gpt4AllChatModel.get()); aiPreferences.storeAiApiKeyInKeyring(AiProvider.OPEN_AI, openAiApiKey.get() == null ? "" : openAiApiKey.get()); - aiPreferences.storeAiApiKeyInKeyring(AiProvider.MISTRAL_AI, mistralAiApiKey.get() == null ? "" : mistralAiApiKey.get()); - aiPreferences.storeAiApiKeyInKeyring(AiProvider.GEMINI, geminiAiApiKey.get() == null ? "" : geminiAiApiKey.get()); - aiPreferences.storeAiApiKeyInKeyring(AiProvider.HUGGING_FACE, huggingFaceApiKey.get() == null ? "" : huggingFaceApiKey.get()); - aiPreferences.storeAiApiKeyInKeyring(AiProvider.GPT4ALL, gpt4AllApiKey.get() == null ? "" : gpt4AllApiKey.get()); + aiPreferences.storeAiApiKeyInKeyring(AiProvider.MISTRAL_AI, + mistralAiApiKey.get() == null ? "" : mistralAiApiKey.get()); + aiPreferences.storeAiApiKeyInKeyring(AiProvider.GEMINI, + geminiAiApiKey.get() == null ? "" : geminiAiApiKey.get()); + aiPreferences.storeAiApiKeyInKeyring(AiProvider.HUGGING_FACE, + huggingFaceApiKey.get() == null ? "" : huggingFaceApiKey.get()); + aiPreferences.storeAiApiKeyInKeyring(AiProvider.GPT4ALL, + gpt4AllApiKey.get() == null ? "" : gpt4AllApiKey.get()); // We notify in all cases without a real check if something was changed aiPreferences.apiKeyUpdated(); @@ -376,10 +412,11 @@ public void storeSettings() { aiPreferences.setHuggingFaceApiBaseUrl(huggingFaceApiBaseUrl.get() == null ? "" : huggingFaceApiBaseUrl.get()); aiPreferences.setGpt4AllApiBaseUrl(gpt4AllApiBaseUrl.get() == null ? "" : gpt4AllApiBaseUrl.get()); - Arrays.stream(AiTemplate.values()).forEach(template -> - aiPreferences.setTemplate(template, templateSources.get(template).get())); + Arrays.stream(AiTemplate.values()) + .forEach(template -> aiPreferences.setTemplate(template, templateSources.get(template).get())); - // We already check the correctness of temperature and RAG minimum score in validators, so we don't need to check it here. + // We already check the correctness of temperature and RAG minimum score in + // validators, so we don't need to check it here. aiPreferences.setTemperature(LocalizedNumbers.stringToDouble(oldLocale, temperature.get()).get()); aiPreferences.setContextWindowSize(contextWindowSize.get()); aiPreferences.setDocumentSplitterChunkSize(documentSplitterChunkSize.get()); @@ -392,7 +429,8 @@ public void resetExpertSettings() { String resetApiBaseUrl = selectedAiProvider.get().getApiUrl(); currentApiBaseUrl.set(resetApiBaseUrl); - contextWindowSize.set(AiDefaultPreferences.getContextWindowSize(selectedAiProvider.get(), currentChatModel.get())); + contextWindowSize + .set(AiDefaultPreferences.getContextWindowSize(selectedAiProvider.get(), currentChatModel.get())); temperature.set(LocalizedNumbers.doubleToString(AiDefaultPreferences.TEMPERATURE)); documentSplitterChunkSize.set(AiDefaultPreferences.DOCUMENT_SPLITTER_CHUNK_SIZE); @@ -402,8 +440,8 @@ public void resetExpertSettings() { } public void resetTemplates() { - Arrays.stream(AiTemplate.values()).forEach(template -> - templateSources.get(template).set(AiDefaultPreferences.TEMPLATES.get(template))); + Arrays.stream(AiTemplate.values()) + .forEach(template -> templateSources.get(template).set(AiDefaultPreferences.TEMPLATES.get(template))); } public void resetCurrentTemplate() { @@ -418,7 +456,8 @@ public boolean validateSettings() { if (enableAi.get()) { if (customizeExpertSettings.get()) { return validateBasicSettings() && validateExpertSettings(); - } else { + } + else { return validateBasicSettings(); } } @@ -427,27 +466,19 @@ public boolean validateSettings() { } public boolean validateBasicSettings() { - List validators = List.of( - chatModelValidator - // apiKeyValidator -- skipped, it will generate warning, but the preferences should be able to save. + List validators = List.of(chatModelValidator + // apiKeyValidator -- skipped, it will generate warning, but the preferences + // should be able to save. ); return validators.stream().map(Validator::getValidationStatus).allMatch(ValidationStatus::isValid); } public boolean validateExpertSettings() { - List validators = List.of( - apiBaseUrlValidator, - embeddingModelValidator, - temperatureTypeValidator, - temperatureRangeValidator, - contextWindowSizeValidator, - documentSplitterChunkSizeValidator, - documentSplitterOverlapSizeValidator, - ragMaxResultsCountValidator, - ragMinScoreTypeValidator, - ragMinScoreRangeValidator - ); + List validators = List.of(apiBaseUrlValidator, embeddingModelValidator, temperatureTypeValidator, + temperatureRangeValidator, contextWindowSizeValidator, documentSplitterChunkSizeValidator, + documentSplitterOverlapSizeValidator, ragMaxResultsCountValidator, ragMinScoreTypeValidator, + ragMinScoreRangeValidator); return validators.stream().map(Validator::getValidationStatus).allMatch(ValidationStatus::isValid); } @@ -599,4 +630,5 @@ public ValidationStatus getRagMinScoreTypeValidationStatus() { public ValidationStatus getRagMinScoreRangeValidationStatus() { return ragMinScoreRangeValidator.getValidationStatus(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/autocompletion/AutoCompletionTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/autocompletion/AutoCompletionTab.java index d8837eb0443..da1caa2fb51 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/autocompletion/AutoCompletionTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/autocompletion/AutoCompletionTab.java @@ -19,21 +19,35 @@ import com.dlsc.gemsfx.TagsField; public class AutoCompletionTab extends AbstractPreferenceTabView implements PreferencesTab { + private static final PseudoClass FOCUSED = PseudoClass.getPseudoClass("focused"); - @FXML private CheckBox enableAutoComplete; - @FXML private TagsField autoCompleteFields; - @FXML private RadioButton autoCompleteFirstLast; - @FXML private RadioButton autoCompleteLastFirst; - @FXML private RadioButton autoCompleteBoth; - @FXML private RadioButton firstNameModeAbbreviated; - @FXML private RadioButton firstNameModeFull; - @FXML private RadioButton firstNameModeBoth; + @FXML + private CheckBox enableAutoComplete; + + @FXML + private TagsField autoCompleteFields; + + @FXML + private RadioButton autoCompleteFirstLast; + + @FXML + private RadioButton autoCompleteLastFirst; + + @FXML + private RadioButton autoCompleteBoth; + + @FXML + private RadioButton firstNameModeAbbreviated; + + @FXML + private RadioButton firstNameModeFull; + + @FXML + private RadioButton firstNameModeBoth; public AutoCompletionTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -63,7 +77,10 @@ private void setupTagsFiled() { autoCompleteFields.setOnMouseClicked(event -> autoCompleteFields.getEditor().requestFocus()); autoCompleteFields.getEditor().getStyleClass().clear(); autoCompleteFields.getEditor().getStyleClass().add("tags-field-editor"); - autoCompleteFields.getEditor().focusedProperty().addListener((observable, oldValue, newValue) -> autoCompleteFields.pseudoClassStateChanged(FOCUSED, newValue)); + autoCompleteFields.getEditor() + .focusedProperty() + .addListener( + (observable, oldValue, newValue) -> autoCompleteFields.pseudoClassStateChanged(FOCUSED, newValue)); } private Node createTag(Field field) { @@ -74,4 +91,5 @@ private Node createTag(Field field) { tagLabel.setContentDisplay(ContentDisplay.RIGHT); return tagLabel; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/autocompletion/AutoCompletionTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/autocompletion/AutoCompletionTabViewModel.java index 125b1a7b830..dbd8add7387 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/autocompletion/AutoCompletionTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/autocompletion/AutoCompletionTabViewModel.java @@ -21,12 +21,20 @@ public class AutoCompletionTabViewModel implements PreferenceTabViewModel { private final BooleanProperty enableAutoCompleteProperty = new SimpleBooleanProperty(); - private final ListProperty autoCompleteFieldsProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + + private final ListProperty autoCompleteFieldsProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); + private final BooleanProperty autoCompleteFirstLastProperty = new SimpleBooleanProperty(); + private final BooleanProperty autoCompleteLastFirstProperty = new SimpleBooleanProperty(); + private final BooleanProperty autoCompleteBothProperty = new SimpleBooleanProperty(); + private final BooleanProperty firstNameModeAbbreviatedProperty = new SimpleBooleanProperty(); + private final BooleanProperty firstNameModeFullProperty = new SimpleBooleanProperty(); + private final BooleanProperty firstNameModeBothProperty = new SimpleBooleanProperty(); private final AutoCompletePreferences autoCompletePreferences; @@ -40,12 +48,15 @@ public AutoCompletionTabViewModel(AutoCompletePreferences autoCompletePreference @Override public void setValues() { enableAutoCompleteProperty.setValue(autoCompletePreferences.shouldAutoComplete()); - autoCompleteFieldsProperty.setValue(FXCollections.observableArrayList(autoCompletePreferences.getCompleteFields())); + autoCompleteFieldsProperty + .setValue(FXCollections.observableArrayList(autoCompletePreferences.getCompleteFields())); if (autoCompletePreferences.getNameFormat() == AutoCompletePreferences.NameFormat.FIRST_LAST) { autoCompleteFirstLastProperty.setValue(true); - } else if (autoCompletePreferences.getNameFormat() == AutoCompletePreferences.NameFormat.LAST_FIRST) { + } + else if (autoCompletePreferences.getNameFormat() == AutoCompletePreferences.NameFormat.LAST_FIRST) { autoCompleteLastFirstProperty.setValue(true); - } else { + } + else { autoCompleteBothProperty.setValue(true); } @@ -62,24 +73,29 @@ public void storeSettings() { if (autoCompleteBothProperty.getValue()) { autoCompletePreferences.setNameFormat(AutoCompletePreferences.NameFormat.BOTH); - } else if (autoCompleteFirstLastProperty.getValue()) { + } + else if (autoCompleteFirstLastProperty.getValue()) { autoCompletePreferences.setNameFormat(AutoCompletePreferences.NameFormat.FIRST_LAST); - } else if (autoCompleteLastFirstProperty.getValue()) { + } + else if (autoCompleteLastFirstProperty.getValue()) { autoCompletePreferences.setNameFormat(AutoCompletePreferences.NameFormat.LAST_FIRST); } if (firstNameModeBothProperty.getValue()) { autoCompletePreferences.setFirstNameMode(AutoCompleteFirstNameMode.BOTH); - } else if (firstNameModeAbbreviatedProperty.getValue()) { + } + else if (firstNameModeAbbreviatedProperty.getValue()) { autoCompletePreferences.setFirstNameMode(AutoCompleteFirstNameMode.ONLY_ABBREVIATED); - } else if (firstNameModeFullProperty.getValue()) { + } + else if (firstNameModeFullProperty.getValue()) { autoCompletePreferences.setFirstNameMode(AutoCompleteFirstNameMode.ONLY_FULL); } if (autoCompletePreferences.shouldAutoComplete() != enableAutoCompleteProperty.getValue()) { if (enableAutoCompleteProperty.getValue()) { restartWarnings.add(Localization.lang("Auto complete enabled.")); - } else { + } + else { restartWarnings.add(Localization.lang("Auto complete disabled.")); } } @@ -140,8 +156,10 @@ public Field fromString(String string) { } public List getSuggestions(String request) { - return FieldFactory.getAllFieldsWithOutInternal().stream() - .filter(field -> field.getDisplayName().toLowerCase().contains(request.toLowerCase())) - .collect(Collectors.toList()); + return FieldFactory.getAllFieldsWithOutInternal() + .stream() + .filter(field -> field.getDisplayName().toLowerCase().contains(request.toLowerCase())) + .collect(Collectors.toList()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/citationkeypattern/CitationKeyPatternTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/citationkeypattern/CitationKeyPatternTab.java index dac2489ca0e..2a8b3b2eaef 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/citationkeypattern/CitationKeyPatternTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/citationkeypattern/CitationKeyPatternTab.java @@ -19,25 +19,47 @@ import com.airhacks.afterburner.injection.Injector; import com.airhacks.afterburner.views.ViewLoader; -public class CitationKeyPatternTab extends AbstractPreferenceTabView implements PreferencesTab { - - @FXML private CheckBox overwriteAllow; - @FXML private CheckBox overwriteWarning; - @FXML private CheckBox generateOnSave; - @FXML private CheckBox generateNewKeyOnImport; - @FXML private RadioButton letterStartA; - @FXML private RadioButton letterStartB; - @FXML private RadioButton letterAlwaysAdd; - @FXML private TextField keyPatternRegex; - @FXML private TextField keyPatternReplacement; - @FXML private TextField unwantedCharacters; - @FXML private Button keyPatternHelp; - @FXML private CitationKeyPatternsPanel bibtexKeyPatternTable; +public class CitationKeyPatternTab extends AbstractPreferenceTabView + implements PreferencesTab { + + @FXML + private CheckBox overwriteAllow; + + @FXML + private CheckBox overwriteWarning; + + @FXML + private CheckBox generateOnSave; + + @FXML + private CheckBox generateNewKeyOnImport; + + @FXML + private RadioButton letterStartA; + + @FXML + private RadioButton letterStartB; + + @FXML + private RadioButton letterAlwaysAdd; + + @FXML + private TextField keyPatternRegex; + + @FXML + private TextField keyPatternReplacement; + + @FXML + private TextField unwantedCharacters; + + @FXML + private Button keyPatternHelp; + + @FXML + private CitationKeyPatternsPanel bibtexKeyPatternTable; public CitationKeyPatternTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -46,7 +68,8 @@ public String getTabName() { } public void initialize() { - this.viewModel = new CitationKeyPatternTabViewModel(preferences.getCitationKeyPatternPreferences(), preferences.getImporterPreferences()); + this.viewModel = new CitationKeyPatternTabViewModel(preferences.getCitationKeyPatternPreferences(), + preferences.getImporterPreferences()); overwriteAllow.selectedProperty().bindBidirectional(viewModel.overwriteAllowProperty()); overwriteWarning.selectedProperty().bindBidirectional(viewModel.overwriteWarningProperty()); @@ -63,7 +86,10 @@ public void initialize() { bibtexKeyPatternTable.defaultKeyPatternProperty().bindBidirectional(viewModel.defaultKeyPatternProperty()); ActionFactory actionFactory = new ActionFactory(); - actionFactory.configureIconButton(StandardActions.HELP_KEY_PATTERNS, new HelpAction(HelpFile.CITATION_KEY_PATTERN, dialogService, preferences.getExternalApplicationsPreferences()), keyPatternHelp); + actionFactory.configureIconButton(StandardActions.HELP_KEY_PATTERNS, + new HelpAction(HelpFile.CITATION_KEY_PATTERN, dialogService, + preferences.getExternalApplicationsPreferences()), + keyPatternHelp); } @Override @@ -84,4 +110,5 @@ public void storeSettings() { public void resetAllKeyPatterns() { bibtexKeyPatternTable.resetAll(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/citationkeypattern/CitationKeyPatternTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/citationkeypattern/CitationKeyPatternTabViewModel.java index 2ccfdf85897..9640ceaabfe 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/citationkeypattern/CitationKeyPatternTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/citationkeypattern/CitationKeyPatternTabViewModel.java @@ -20,33 +20,49 @@ public class CitationKeyPatternTabViewModel implements PreferenceTabViewModel { private final BooleanProperty overwriteAllowProperty = new SimpleBooleanProperty(); + private final BooleanProperty overwriteWarningProperty = new SimpleBooleanProperty(); + private final BooleanProperty generateOnSaveProperty = new SimpleBooleanProperty(); + private final BooleanProperty generateKeyOnImportProperty = new SimpleBooleanProperty(); + private final BooleanProperty letterStartAProperty = new SimpleBooleanProperty(); + private final BooleanProperty letterStartBProperty = new SimpleBooleanProperty(); + private final BooleanProperty letterAlwaysAddProperty = new SimpleBooleanProperty(); + private final StringProperty keyPatternRegexProperty = new SimpleStringProperty(); + private final StringProperty keyPatternReplacementProperty = new SimpleStringProperty(); + private final StringProperty unwantedCharactersProperty = new SimpleStringProperty(); - // The list and the default properties are being overwritten by the bound properties of the tableView, but to - // prevent an NPE on storing the preferences before lazy-loading of the setValues, they need to be initialized. - private final ListProperty patternListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + // The list and the default properties are being overwritten by the bound properties + // of the tableView, but to + // prevent an NPE on storing the preferences before lazy-loading of the setValues, + // they need to be initialized. + private final ListProperty patternListProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); + private final ObjectProperty defaultKeyPatternProperty = new SimpleObjectProperty<>( new CitationKeyPatternsPanelItemModel(new CitationKeyPatternsPanelViewModel.DefaultEntryType(), "")); private final CitationKeyPatternPreferences keyPatternPreferences; /** - * The preference for whether to use the key generator on import is different from how it is configured. - * In the UI, there is no better place to put the option than the Citation Key Generator tab. - * However, shifting the preference to {@link CitationKeyPatternPreferences} would break the abstraction or hierarchy. - * Hence, we keep the preference in {@link ImporterPreferences}, but for the UI, we initialize it here. + * The preference for whether to use the key generator on import is different from how + * it is configured. In the UI, there is no better place to put the option than the + * Citation Key Generator tab. However, shifting the preference to + * {@link CitationKeyPatternPreferences} would break the abstraction or hierarchy. + * Hence, we keep the preference in {@link ImporterPreferences}, but for the UI, we + * initialize it here. */ private final ImporterPreferences importerPreferences; - public CitationKeyPatternTabViewModel(CitationKeyPatternPreferences keyPatternPreferences, ImporterPreferences importerPreferences) { + public CitationKeyPatternTabViewModel(CitationKeyPatternPreferences keyPatternPreferences, + ImporterPreferences importerPreferences) { this.keyPatternPreferences = keyPatternPreferences; this.importerPreferences = importerPreferences; } @@ -58,17 +74,17 @@ public void setValues() { generateOnSaveProperty.setValue(keyPatternPreferences.shouldGenerateCiteKeysBeforeSaving()); generateKeyOnImportProperty.setValue(importerPreferences.shouldGenerateNewKeyOnImport()); - if (keyPatternPreferences.getKeySuffix() - == CitationKeyPatternPreferences.KeySuffix.ALWAYS) { + if (keyPatternPreferences.getKeySuffix() == CitationKeyPatternPreferences.KeySuffix.ALWAYS) { letterAlwaysAddProperty.setValue(true); letterStartAProperty.setValue(false); letterStartBProperty.setValue(false); - } else if (keyPatternPreferences.getKeySuffix() - == CitationKeyPatternPreferences.KeySuffix.SECOND_WITH_A) { + } + else if (keyPatternPreferences.getKeySuffix() == CitationKeyPatternPreferences.KeySuffix.SECOND_WITH_A) { letterAlwaysAddProperty.setValue(false); letterStartAProperty.setValue(true); letterStartBProperty.setValue(false); - } else { + } + else { letterAlwaysAddProperty.setValue(false); letterStartAProperty.setValue(false); letterStartBProperty.setValue(true); @@ -81,8 +97,8 @@ public void setValues() { @Override public void storeSettings() { - GlobalCitationKeyPatterns newKeyPattern = - new GlobalCitationKeyPatterns(keyPatternPreferences.getKeyPatterns().getDefaultValue()); + GlobalCitationKeyPatterns newKeyPattern = new GlobalCitationKeyPatterns( + keyPatternPreferences.getKeyPatterns().getDefaultValue()); patternListProperty.forEach(item -> { String patternString = item.getPattern(); if (!"default".equals(item.getEntryType().getName())) { @@ -93,7 +109,8 @@ public void storeSettings() { }); if (!defaultKeyPatternProperty.getValue().getPattern().trim().isEmpty()) { - // we do not trim the value at the assignment to enable users to have spaces at the beginning and + // we do not trim the value at the assignment to enable users to have spaces + // at the beginning and // at the end of the pattern newKeyPattern.setDefaultValue(defaultKeyPatternProperty.getValue().getPattern()); } @@ -102,7 +119,8 @@ public void storeSettings() { if (letterStartAProperty.getValue()) { keySuffix = CitationKeyPatternPreferences.KeySuffix.SECOND_WITH_A; - } else if (letterStartBProperty.getValue()) { + } + else if (letterStartBProperty.getValue()) { keySuffix = CitationKeyPatternPreferences.KeySuffix.SECOND_WITH_B; } @@ -164,4 +182,5 @@ public ObjectProperty defaultKeyPatternProper public StringProperty unwantedCharactersProperty() { return unwantedCharactersProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypeViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypeViewModel.java index 45dbb9ad796..b29f5c323af 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypeViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypeViewModel.java @@ -6,11 +6,13 @@ import org.jabref.model.entry.field.Field; /** - * This class is required to check whether a delete button should be displayed at {@link org.jabref.gui.preferences.customentrytypes.CustomEntryTypesTab#setupEntryTypesTable()} + * This class is required to check whether a delete button should be displayed at + * {@link org.jabref.gui.preferences.customentrytypes.CustomEntryTypesTab#setupEntryTypesTable()} */ public class CustomEntryTypeViewModel extends EntryTypeViewModel { public CustomEntryTypeViewModel(BibEntryType entryType, Predicate isMultiline) { super(entryType, isMultiline); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTab.java index 832cde6eb81..c985458b4ee 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTab.java @@ -41,31 +41,54 @@ import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; import jakarta.inject.Inject; -public class CustomEntryTypesTab extends AbstractPreferenceTabView implements PreferencesTab { - - @FXML private TableView entryTypesTable; - @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 TableColumn fieldTypeMultilineColumn; - @FXML private ComboBox addNewField; - @FXML private Button addNewEntryTypeButton; - @FXML private Button addNewFieldButton; - - @Inject private StateManager stateManager; +public class CustomEntryTypesTab extends AbstractPreferenceTabView + implements PreferencesTab { + + @FXML + private TableView entryTypesTable; + + @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 TableColumn fieldTypeMultilineColumn; + + @FXML + private ComboBox addNewField; + + @FXML + private Button addNewEntryTypeButton; + + @FXML + private Button addNewFieldButton; + + @Inject + private StateManager stateManager; private final ControlsFxVisualizer visualizer = new ControlsFxVisualizer(); private CustomLocalDragboard localDragboard; public CustomEntryTypesTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -74,8 +97,9 @@ public String getTabName() { } public void initialize() { - BibDatabaseMode mode = stateManager.getActiveDatabase().map(BibDatabaseContext::getMode) - .orElse(preferences.getLibraryPreferences().getDefaultBibDatabaseMode()); + BibDatabaseMode mode = stateManager.getActiveDatabase() + .map(BibDatabaseContext::getMode) + .orElse(preferences.getLibraryPreferences().getDefaultBibDatabaseMode()); BibEntryTypesManager entryTypesRepository = preferences.getCustomEntryTypesRepository(); this.viewModel = new CustomEntryTypesTabViewModel(mode, entryTypesRepository, dialogService, preferences); @@ -87,7 +111,11 @@ public void initialize() { setupFieldsTable(); addNewEntryTypeButton.disableProperty().bind(viewModel.entryTypeValidationStatus().validProperty().not()); - addNewFieldButton.disableProperty().bind(viewModel.fieldValidationStatus().validProperty().not().or(viewModel.selectedEntryTypeProperty().isNull())); + addNewFieldButton.disableProperty() + .bind(viewModel.fieldValidationStatus() + .validProperty() + .not() + .or(viewModel.selectedEntryTypeProperty().isNull())); Platform.runLater(() -> { visualizer.initVisualization(viewModel.entryTypeValidationStatus(), addNewEntryType, true); @@ -96,39 +124,41 @@ public void initialize() { } private void setupEntryTypesTable() { - // Table View must be editable, otherwise the change of the Radiobuttons does not propagate the commit event + // Table View must be editable, otherwise the change of the Radiobuttons does not + // propagate the commit event fields.setEditable(true); - entryTypColumn.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue().entryType().get().getType().getDisplayName())); + entryTypColumn.setCellValueFactory(cellData -> new ReadOnlyStringWrapper( + cellData.getValue().entryType().get().getType().getDisplayName())); entryTypesTable.setItems(viewModel.entryTypes()); entryTypesTable.getSelectionModel().selectFirst(); entryTypeActionsColumn.setSortable(false); entryTypeActionsColumn.setReorderable(false); - entryTypeActionsColumn.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue().entryType().get().getType().getDisplayName())); - new ValueTableCellFactory() - .withGraphic((type, _) -> { - if (type instanceof CustomEntryTypeViewModel) { - return IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode(); - } else { - return null; - } - }) - .withTooltip((type, name) -> { - if (type instanceof CustomEntryTypeViewModel) { - return Localization.lang("Remove entry type") + " " + name; - } else { - return null; - } - }) - .withOnMouseClickedEvent((type, _) -> { - if (type instanceof CustomEntryTypeViewModel) { - return _ -> viewModel.removeEntryType(entryTypesTable.getSelectionModel().getSelectedItem()); - } else { - return _ -> { - }; - } - }) - .install(entryTypeActionsColumn); + entryTypeActionsColumn.setCellValueFactory(cellData -> new ReadOnlyStringWrapper( + cellData.getValue().entryType().get().getType().getDisplayName())); + new ValueTableCellFactory().withGraphic((type, _) -> { + if (type instanceof CustomEntryTypeViewModel) { + return IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode(); + } + else { + return null; + } + }).withTooltip((type, name) -> { + if (type instanceof CustomEntryTypeViewModel) { + return Localization.lang("Remove entry type") + " " + name; + } + else { + return null; + } + }).withOnMouseClickedEvent((type, _) -> { + if (type instanceof CustomEntryTypeViewModel) { + return _ -> viewModel.removeEntryType(entryTypesTable.getSelectionModel().getSelectedItem()); + } + else { + return _ -> { + }; + } + }).install(entryTypeActionsColumn); viewModel.selectedEntryTypeProperty().bind(entryTypesTable.getSelectionModel().selectedItemProperty()); viewModel.entryTypeToAddProperty().bindBidirectional(addNewEntryType.textProperty()); @@ -137,7 +167,8 @@ private void setupEntryTypesTable() { if (type != null) { ObservableList items = type.fields(); fields.setItems(items); - } else { + } + else { fields.setItems(null); } }); @@ -160,12 +191,16 @@ private void setupFieldsTable() { String currentDisplayName = fieldViewModel.displayNameProperty().getValue(); EntryTypeViewModel selectedEntryType = viewModel.selectedEntryTypeProperty().get(); ObservableList entryFields = selectedEntryType.fields(); - // The first predicate will check if the user input the original field name or doesn't edit anything after double click - boolean fieldExists = !newDisplayName.equals(currentDisplayName) && viewModel.displayNameExists(newDisplayName); + // The first predicate will check if the user input the original field name or + // doesn't edit anything after double click + boolean fieldExists = !newDisplayName.equals(currentDisplayName) + && viewModel.displayNameExists(newDisplayName); if (fieldExists) { - dialogService.notify(Localization.lang("Unable to change field name. \"%0\" already in use.", newDisplayName)); + dialogService + .notify(Localization.lang("Unable to change field name. \"%0\" already in use.", newDisplayName)); event.getTableView().edit(-1, null); - } else { + } + else { fieldViewModel.displayNameProperty().setValue(newDisplayName); } event.getTableView().refresh(); @@ -185,25 +220,26 @@ private void setupFieldsTable() { fieldTypeActionColumn.setCellValueFactory(cellData -> cellData.getValue().displayNameProperty()); new ValueTableCellFactory() - .withGraphic(_ -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) - .withTooltip(name -> Localization.lang("Remove field %0 from currently selected entry type", name)) - .withOnMouseClickedEvent(_ -> _ -> viewModel.removeField(fields.getSelectionModel().getSelectedItem())) - .install(fieldTypeActionColumn); - - new ViewModelTableRowFactory() - .setOnDragDetected(this::handleOnDragDetected) - .setOnDragDropped(this::handleOnDragDropped) - .setOnDragOver(this::handleOnDragOver) - .setOnDragExited(this::handleOnDragExited) - .install(fields); + .withGraphic(_ -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) + .withTooltip(name -> Localization.lang("Remove field %0 from currently selected entry type", name)) + .withOnMouseClickedEvent(_ -> _ -> viewModel.removeField(fields.getSelectionModel().getSelectedItem())) + .install(fieldTypeActionColumn); + + new ViewModelTableRowFactory().setOnDragDetected(this::handleOnDragDetected) + .setOnDragDropped(this::handleOnDragDropped) + .setOnDragOver(this::handleOnDragOver) + .setOnDragExited(this::handleOnDragExited) + .install(fields); addNewField.setItems(viewModel.fieldsForAdding()); addNewField.setConverter(FieldsUtil.FIELD_STRING_CONVERTER); viewModel.newFieldToAddProperty().bindBidirectional(addNewField.valueProperty()); - // The valueProperty() of addNewField ComboBox needs to be updated by typing text in the ComboBox textfield, + // The valueProperty() of addNewField ComboBox needs to be updated by typing text + // in the ComboBox textfield, // since the enabled/disabled state of addNewFieldButton won't update otherwise - EasyBind.subscribe(addNewField.getEditor().textProperty(), text -> addNewField.setValue(FieldsUtil.FIELD_STRING_CONVERTER.fromString(text))); + EasyBind.subscribe(addNewField.getEditor().textProperty(), + text -> addNewField.setValue(FieldsUtil.FIELD_STRING_CONVERTER.fromString(text))); } private void makeRotatedColumnHeader(TableColumn column, String text) { @@ -215,16 +251,15 @@ private void makeRotatedColumnHeader(TableColumn column, String text) { column.getStyleClass().add("rotated"); } - private void handleOnDragOver(TableRow row, FieldViewModel originalItem, DragEvent - event) { - if ((event.getGestureSource() != originalItem) && event.getDragboard().hasContent(DragAndDropDataFormats.FIELD)) { + private void handleOnDragOver(TableRow row, FieldViewModel originalItem, DragEvent event) { + if ((event.getGestureSource() != originalItem) + && event.getDragboard().hasContent(DragAndDropDataFormats.FIELD)) { event.acceptTransferModes(TransferMode.MOVE); ControlHelper.setDroppingPseudoClasses(row, event); } } - private void handleOnDragDetected(TableRow row, FieldViewModel fieldViewModel, MouseEvent - event) { + private void handleOnDragDetected(TableRow row, FieldViewModel fieldViewModel, MouseEvent event) { row.startFullDrag(); FieldViewModel field = fields.getSelectionModel().getSelectedItem(); @@ -243,8 +278,10 @@ private void handleOnDragDropped(TableRow row, FieldViewModel or if (row.isEmpty()) { fields.getItems().add(field); - } else { - // decide based on drop position whether to add the element before or after + } + else { + // decide based on drop position whether to add the element before or + // after int offset = event.getY() > (row.getHeight() / 2) ? 1 : 0; fields.getItems().add(row.getIndex() + offset, field); } @@ -273,7 +310,8 @@ void addNewField() { void resetEntryTypes() { boolean reset = dialogService.showConfirmationDialogAndWait( Localization.lang("Reset entry types and fields to defaults"), - Localization.lang("This will reset all entry types to their default values and remove all custom entry types"), + Localization + .lang("This will reset all entry types to their default values and remove all custom entry types"), Localization.lang("Reset to default")); if (reset) { viewModel.resetAllCustomEntryTypes(); @@ -283,4 +321,5 @@ void resetEntryTypes() { entryTypesTable.refresh(); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTabViewModel.java index eab2fc87d62..2ae775c4b18 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTabViewModel.java @@ -40,28 +40,39 @@ public class CustomEntryTypesTabViewModel implements PreferenceTabViewModel { - private final ObservableList fieldsForAdding = FXCollections.observableArrayList(FieldFactory.getStandardFieldsWithCitationKey()); + private final ObservableList fieldsForAdding = FXCollections + .observableArrayList(FieldFactory.getStandardFieldsWithCitationKey()); + private final ObjectProperty selectedEntryType = new SimpleObjectProperty<>(); + private final StringProperty entryTypeToAdd = new SimpleStringProperty(""); + private final ObjectProperty newFieldToAdd = new SimpleObjectProperty<>(); - private final ObservableList entryTypesWithFields = FXCollections.observableArrayList(extractor -> new Observable[]{extractor.entryType(), extractor.fields()}); + + private final ObservableList entryTypesWithFields = FXCollections + .observableArrayList(extractor -> new Observable[] { extractor.entryType(), extractor.fields() }); + private final List entryTypesToDelete = new ArrayList<>(); private final CliPreferences preferences; + private final BibEntryTypesManager entryTypesManager; + private final DialogService dialogService; + private final BibDatabaseMode bibDatabaseMode; private final Validator entryTypeValidator; + private final Validator fieldValidator; + private final Set multiLineFields = new HashSet<>(); - Predicate isMultiline = field -> this.multiLineFields.contains(field) || field.getProperties().contains(FieldProperty.MULTILINE_TEXT); + Predicate isMultiline = field -> this.multiLineFields.contains(field) + || field.getProperties().contains(FieldProperty.MULTILINE_TEXT); - public CustomEntryTypesTabViewModel(BibDatabaseMode mode, - BibEntryTypesManager entryTypesManager, - DialogService dialogService, - CliPreferences preferences) { + public CustomEntryTypesTabViewModel(BibDatabaseMode mode, BibEntryTypesManager entryTypesManager, + DialogService dialogService, CliPreferences preferences) { this.preferences = preferences; this.entryTypesManager = entryTypesManager; this.dialogService = dialogService; @@ -69,12 +80,9 @@ public CustomEntryTypesTabViewModel(BibDatabaseMode mode, this.multiLineFields.addAll(preferences.getFieldPreferences().getNonWrappableFields()); - entryTypeValidator = new FunctionBasedValidator<>( - entryTypeToAdd, - StringUtil::isNotBlank, + entryTypeValidator = new FunctionBasedValidator<>(entryTypeToAdd, StringUtil::isNotBlank, ValidationMessage.error(Localization.lang("Entry type cannot be empty. Please enter a name."))); - fieldValidator = new FunctionBasedValidator<>( - newFieldToAdd, + fieldValidator = new FunctionBasedValidator<>(newFieldToAdd, input -> (input != null) && StringUtil.isNotBlank(input.getDisplayName()), ValidationMessage.error(Localization.lang("Field cannot be empty. Please enter a name."))); } @@ -90,7 +98,8 @@ public void setValues() { EntryTypeViewModel viewModel; if (entryTypesManager.isCustomType(entryType, bibDatabaseMode)) { viewModel = new CustomEntryTypeViewModel(entryType, isMultiline); - } else { + } + else { viewModel = new EntryTypeViewModel(entryType, isMultiline); } this.entryTypesWithFields.add(viewModel); @@ -108,17 +117,19 @@ public void storeSettings() { // Collect multilineFields for storage in preferences later multilineFields.addAll(allFields.stream() - .filter(FieldViewModel::isMultiline) - .map(model -> model.toField(newPlainType)) - .toList()); + .filter(FieldViewModel::isMultiline) + .map(model -> model.toField(newPlainType)) + .toList()); List required = allFields.stream() - .filter(FieldViewModel::isRequired) - .map(model -> model.toField(newPlainType)) - .map(OrFields::new) - .collect(Collectors.toList()); + .filter(FieldViewModel::isRequired) + .map(model -> model.toField(newPlainType)) + .map(OrFields::new) + .collect(Collectors.toList()); - List fields = allFields.stream().map(model -> model.toBibField(newPlainType)).collect(Collectors.toList()); + List fields = allFields.stream() + .map(model -> model.toBibField(newPlainType)) + .collect(Collectors.toList()); BibEntryType newType = new BibEntryType(newPlainType, fields, required); @@ -153,23 +164,20 @@ public void addNewField() { boolean fieldExists = displayNameExists(field.getDisplayName()); if (fieldExists) { - dialogService.showWarningDialogAndWait( - Localization.lang("Duplicate fields"), - Localization.lang("Warning: You added field \"%0\" twice. Only one will be kept.", field.getDisplayName())); - } else { - this.selectedEntryType.getValue().addField(new FieldViewModel( - field, - FieldViewModel.Mandatory.REQUIRED, - FieldPriority.IMPORTANT, - false)); + dialogService.showWarningDialogAndWait(Localization.lang("Duplicate fields"), Localization + .lang("Warning: You added field \"%0\" twice. Only one will be kept.", field.getDisplayName())); + } + else { + this.selectedEntryType.getValue() + .addField(new FieldViewModel(field, FieldViewModel.Mandatory.REQUIRED, FieldPriority.IMPORTANT, false)); } newFieldToAddProperty().setValue(null); } public boolean displayNameExists(String displayName) { ObservableList entryFields = this.selectedEntryType.getValue().fields(); - return entryFields.stream().anyMatch(fieldViewModel -> - fieldViewModel.displayNameProperty().getValue().equals(displayName)); + return entryFields.stream() + .anyMatch(fieldViewModel -> fieldViewModel.displayNameProperty().getValue().equals(displayName)); } public void removeField(FieldViewModel focusedItem) { @@ -208,4 +216,5 @@ public ValidationStatus entryTypeValidationStatus() { public ValidationStatus fieldValidationStatus() { return fieldValidator.getValidationStatus(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/EntryTypeViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/EntryTypeViewModel.java index 56525a2877b..f557051544e 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/EntryTypeViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/EntryTypeViewModel.java @@ -18,16 +18,17 @@ public class EntryTypeViewModel { private final ObjectProperty entryType = new SimpleObjectProperty<>(); + private final ObservableList fields; public EntryTypeViewModel(BibEntryType entryType, Predicate isMultiline) { this.entryType.set(entryType); List allFieldsForType = entryType.getAllBibFields() - .stream().map(bibField -> new FieldViewModel(bibField.field(), - entryType.isRequired(bibField.field()) ? Mandatory.REQUIRED : Mandatory.OPTIONAL, - bibField.priority(), - isMultiline.test(bibField.field()))) - .collect(Collectors.toList()); + .stream() + .map(bibField -> new FieldViewModel(bibField.field(), + entryType.isRequired(bibField.field()) ? Mandatory.REQUIRED : Mandatory.OPTIONAL, + bibField.priority(), isMultiline.test(bibField.field()))) + .collect(Collectors.toList()); fields = FXCollections.observableArrayList(allFieldsForType); } @@ -67,4 +68,5 @@ public void removeField(FieldViewModel focusedItem) { public String toString() { return "CustomEntryTypeViewModel [entryType=" + entryType + ", fields=" + fields + "]"; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/FieldViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/FieldViewModel.java index ae987a27652..deef052f12e 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/FieldViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/customentrytypes/FieldViewModel.java @@ -18,14 +18,14 @@ public class FieldViewModel { private final StringProperty displayName = new SimpleStringProperty(""); + private final BooleanProperty required = new SimpleBooleanProperty(); + private final BooleanProperty multiline = new SimpleBooleanProperty(); + private final ObjectProperty priorityProperty = new SimpleObjectProperty<>(); - public FieldViewModel(Field field, - Mandatory required, - FieldPriority priorityProperty, - boolean multiline) { + public FieldViewModel(Field field, Mandatory required, FieldPriority priorityProperty, boolean multiline) { this.displayName.setValue(field.getDisplayName()); this.required.setValue(required == Mandatory.REQUIRED); this.priorityProperty.setValue(priorityProperty); @@ -58,7 +58,8 @@ public FieldPriority getPriority() { public Field toField(EntryType type) { // If the field name is known by JabRef, JabRef's casing will win. - // If the field is not known by JabRef (UnknownField), the new casing will be taken. + // If the field is not known by JabRef (UnknownField), the new casing will be + // taken. Field field = FieldFactory.parseField(type, displayName.getValue()); if (multiline.getValue()) { field.getProperties().add(FieldProperty.MULTILINE_TEXT); @@ -76,8 +77,8 @@ public String toString() { } public enum Mandatory { - REQUIRED(Localization.lang("Required")), - OPTIONAL(Localization.lang("Optional")); + + REQUIRED(Localization.lang("Required")), OPTIONAL(Localization.lang("Optional")); private final String name; @@ -88,5 +89,7 @@ public enum Mandatory { public String getDisplayName() { return name; } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/customexporter/CustomExporterTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/customexporter/CustomExporterTab.java index f5b409f6002..df337c762b5 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/customexporter/CustomExporterTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/customexporter/CustomExporterTab.java @@ -15,15 +15,20 @@ public class CustomExporterTab extends AbstractPreferenceTabView implements PreferencesTab { - @FXML private TableView exporterTable; - @FXML private TableColumn nameColumn; - @FXML private TableColumn layoutColumn; - @FXML private TableColumn extensionColumn; + @FXML + private TableView exporterTable; + + @FXML + private TableColumn nameColumn; + + @FXML + private TableColumn layoutColumn; + + @FXML + private TableColumn extensionColumn; public CustomExporterTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -37,7 +42,8 @@ private void initialize() { exporterTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); exporterTable.itemsProperty().bind(viewModel.exportersProperty()); - EasyBind.bindContent(viewModel.selectedExportersProperty(), exporterTable.getSelectionModel().getSelectedItems()); + EasyBind.bindContent(viewModel.selectedExportersProperty(), + exporterTable.getSelectionModel().getSelectedItems()); nameColumn.setCellValueFactory(cellData -> cellData.getValue().name()); layoutColumn.setCellValueFactory(cellData -> cellData.getValue().layoutFileName()); extensionColumn.setCellValueFactory(cellData -> cellData.getValue().extension()); @@ -57,4 +63,5 @@ private void modify() { private void remove() { viewModel.removeExporters(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/customexporter/CustomExporterTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/customexporter/CustomExporterTabViewModel.java index e0db96a69bf..45e6f3109e3 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/customexporter/CustomExporterTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/customexporter/CustomExporterTabViewModel.java @@ -17,10 +17,14 @@ public class CustomExporterTabViewModel implements PreferenceTabViewModel { - private final ListProperty exporters = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ListProperty selectedExporters = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty exporters = new SimpleListProperty<>( + FXCollections.observableArrayList()); + + private final ListProperty selectedExporters = new SimpleListProperty<>( + FXCollections.observableArrayList()); private final CliPreferences preferences; + private final DialogService dialogService; public CustomExporterTabViewModel(CliPreferences preferences, DialogService dialogService) { @@ -40,8 +44,8 @@ public void setValues() { @Override public void storeSettings() { List exportersLogic = exporters.stream() - .map(ExporterViewModel::getLogic) - .collect(Collectors.toList()); + .map(ExporterViewModel::getLogic) + .collect(Collectors.toList()); preferences.getExportPreferences().setCustomExporters(exportersLogic); } @@ -78,4 +82,5 @@ public ListProperty selectedExportersProperty() { public ListProperty exportersProperty() { return exporters; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/customimporter/CustomImporterTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/customimporter/CustomImporterTab.java index 7a05fcdc9db..0b1860ae1d5 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/customimporter/CustomImporterTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/customimporter/CustomImporterTab.java @@ -18,16 +18,23 @@ public class CustomImporterTab extends AbstractPreferenceTabView implements PreferencesTab { - @FXML private TableView importerTable; - @FXML private TableColumn nameColumn; - @FXML private TableColumn classColumn; - @FXML private TableColumn basePathColumn; - @FXML private Button addButton; + @FXML + private TableView importerTable; + + @FXML + private TableColumn nameColumn; + + @FXML + private TableColumn classColumn; + + @FXML + private TableColumn basePathColumn; + + @FXML + private Button addButton; public CustomImporterTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -41,17 +48,16 @@ private void initialize() { importerTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); importerTable.itemsProperty().bind(viewModel.importersProperty()); - EasyBind.bindContent(viewModel.selectedImportersProperty(), importerTable.getSelectionModel().getSelectedItems()); + EasyBind.bindContent(viewModel.selectedImportersProperty(), + importerTable.getSelectionModel().getSelectedItems()); nameColumn.setCellValueFactory(cellData -> cellData.getValue().name()); classColumn.setCellValueFactory(cellData -> cellData.getValue().className()); basePathColumn.setCellValueFactory(cellData -> cellData.getValue().basePath()); - new ViewModelTableRowFactory() - .withTooltip(importer -> importer.getLogic().getDescription()) - .install(importerTable); + new ViewModelTableRowFactory().withTooltip(importer -> importer.getLogic().getDescription()) + .install(importerTable); - addButton.setTooltip(new Tooltip( - Localization.lang("Add a (compiled) custom Importer class from a class path.") - + "\n" + Localization.lang("The path need not be on the classpath of JabRef."))); + addButton.setTooltip(new Tooltip(Localization.lang("Add a (compiled) custom Importer class from a class path.") + + "\n" + Localization.lang("The path need not be on the classpath of JabRef."))); } @FXML @@ -63,4 +69,5 @@ private void add() { private void remove() { viewModel.removeSelectedImporter(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/customimporter/CustomImporterTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/customimporter/CustomImporterTabViewModel.java index 7bdfc1d01c4..4078a21b3e1 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/customimporter/CustomImporterTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/customimporter/CustomImporterTabViewModel.java @@ -29,10 +29,14 @@ public class CustomImporterTabViewModel implements PreferenceTabViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(CustomImporterTabViewModel.class); - private final ListProperty importers = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ListProperty selectedImporters = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty importers = new SimpleListProperty<>( + FXCollections.observableArrayList()); + + private final ListProperty selectedImporters = new SimpleListProperty<>( + FXCollections.observableArrayList()); private final CliPreferences preferences; + private final DialogService dialogService; public CustomImporterTabViewModel(CliPreferences preferences, DialogService dialogService) { @@ -51,16 +55,14 @@ public void setValues() { @Override public void storeSettings() { - preferences.getImporterPreferences().setCustomImporters(importers.stream() - .map(ImporterViewModel::getLogic) - .collect(Collectors.toSet())); + preferences.getImporterPreferences() + .setCustomImporters(importers.stream().map(ImporterViewModel::getLogic).collect(Collectors.toSet())); } /** * Converts a path relative to a base-path into a class name. - * * @param basePath base path - * @param path path that includes base-path as a prefix + * @param path path that includes base-path as a prefix * @return class name */ private static String pathToClass(String basePath, Path path) { @@ -77,53 +79,62 @@ private static String pathToClass(String basePath, Path path) { public void addImporter() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.CLASS, StandardFileType.JAR, StandardFileType.ZIP) - .withDefaultExtension(StandardFileType.CLASS) - .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) - .build(); + .addExtensionFilter(StandardFileType.CLASS, StandardFileType.JAR, StandardFileType.ZIP) + .withDefaultExtension(StandardFileType.CLASS) + .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) + .build(); Optional selectedFile = dialogService.showFileOpenDialog(fileDialogConfiguration); if (selectedFile.isPresent() && (selectedFile.get().getParent() != null)) { boolean isArchive = FileUtil.getFileExtension(selectedFile.get()) - .filter(extension -> "jar".equalsIgnoreCase(extension) || "zip".equalsIgnoreCase(extension)) - .isPresent(); + .filter(extension -> "jar".equalsIgnoreCase(extension) || "zip".equalsIgnoreCase(extension)) + .isPresent(); if (isArchive) { try { - Optional selectedFileInArchive = dialogService.showFileOpenFromArchiveDialog(selectedFile.get()); + Optional selectedFileInArchive = dialogService + .showFileOpenFromArchiveDialog(selectedFile.get()); if (selectedFileInArchive.isPresent()) { - String className = selectedFileInArchive.get().toString().substring(0, selectedFileInArchive.get().toString().lastIndexOf('.')).replace( - "/", "."); - CustomImporter importer = new CustomImporter(selectedFile.get().toAbsolutePath().toString(), className); + String className = selectedFileInArchive.get() + .toString() + .substring(0, selectedFileInArchive.get().toString().lastIndexOf('.')) + .replace("/", "."); + CustomImporter importer = new CustomImporter(selectedFile.get().toAbsolutePath().toString(), + className); importers.add(new ImporterViewModel(importer)); } - } catch (IOException exc) { + } + catch (IOException exc) { LOGGER.error("Could not open ZIP-archive.", exc); - dialogService.showErrorDialogAndWait( - Localization.lang("Could not open %0", selectedFile.get().toString()) + "\n" - + Localization.lang("Have you chosen the correct package path?"), - exc); - } catch (ImportException exc) { + dialogService + .showErrorDialogAndWait(Localization.lang("Could not open %0", selectedFile.get().toString()) + + "\n" + Localization.lang("Have you chosen the correct package path?"), exc); + } + catch (ImportException exc) { LOGGER.error("Could not instantiate importer", exc); - dialogService.showErrorDialogAndWait( - Localization.lang("Could not instantiate %0 %1", "importer"), + dialogService.showErrorDialogAndWait(Localization.lang("Could not instantiate %0 %1", "importer"), exc); } - } else { + } + else { try { String basePath = selectedFile.get().getParent().toString(); String className = pathToClass(basePath, selectedFile.get()); CustomImporter importer = new CustomImporter(basePath, className); importers.add(new ImporterViewModel(importer)); - } catch (Exception exc) { + } + catch (Exception exc) { LOGGER.error("Could not instantiate importer", exc); - dialogService.showErrorDialogAndWait(Localization.lang("Could not instantiate %0", selectedFile.get().toString()), exc); - } catch (NoClassDefFoundError exc) { + dialogService.showErrorDialogAndWait( + Localization.lang("Could not instantiate %0", selectedFile.get().toString()), exc); + } + catch (NoClassDefFoundError exc) { LOGGER.error("Could not find class while instantiating importer", exc); dialogService.showErrorDialogAndWait( - Localization.lang("Could not instantiate %0. Have you chosen the correct package path?", selectedFile.get().toString()), + Localization.lang("Could not instantiate %0. Have you chosen the correct package path?", + selectedFile.get().toString()), exc); } } @@ -141,4 +152,5 @@ public ListProperty selectedImportersProperty() { public ListProperty importersProperty() { return importers; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/entry/EntryTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/entry/EntryTab.java index 072d2e6e27b..15d84d52479 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/entry/EntryTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/entry/EntryTab.java @@ -28,27 +28,41 @@ import com.dlsc.gemsfx.TagsField; public class EntryTab extends AbstractPreferenceTabView implements PreferencesTab { + private static final PseudoClass FOCUSED = PseudoClass.getPseudoClass("focused"); - @FXML private TextField keywordSeparator; + @FXML + private TextField keywordSeparator; + + @FXML + private CheckBox resolveStrings; + + @FXML + private TagsField resolvableTagsField; + + @FXML + private TagsField nonWrappableTagsField; - @FXML private CheckBox resolveStrings; + @FXML + private CheckBox markOwner; - @FXML private TagsField resolvableTagsField; - @FXML private TagsField nonWrappableTagsField; + @FXML + private TextField markOwnerName; - @FXML private CheckBox markOwner; - @FXML private TextField markOwnerName; - @FXML private CheckBox markOwnerOverwrite; - @FXML private Button markOwnerHelp; + @FXML + private CheckBox markOwnerOverwrite; - @FXML private CheckBox addCreationDate; - @FXML private CheckBox addModificationDate; + @FXML + private Button markOwnerHelp; + + @FXML + private CheckBox addCreationDate; + + @FXML + private CheckBox addModificationDate; public EntryTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } public void initialize() { @@ -56,7 +70,8 @@ public void initialize() { keywordSeparator.textProperty().bindBidirectional(viewModel.keywordSeparatorProperty()); - // Use TextFormatter to limit the length of the Input of keywordSeparator to 1 character only. + // Use TextFormatter to limit the length of the Input of keywordSeparator to 1 + // character only. UnaryOperator singleCharacterFilter = change -> { if (change.getControlNewText().length() <= 1) { return change; @@ -84,7 +99,9 @@ public void initialize() { addModificationDate.selectedProperty().bindBidirectional(viewModel.addModificationDateProperty()); ActionFactory actionFactory = new ActionFactory(); - actionFactory.configureIconButton(StandardActions.HELP, new HelpAction(HelpFile.OWNER, dialogService, preferences.getExternalApplicationsPreferences()), markOwnerHelp); + actionFactory.configureIconButton(StandardActions.HELP, + new HelpAction(HelpFile.OWNER, dialogService, preferences.getExternalApplicationsPreferences()), + markOwnerHelp); } private void setupTagsField(TagsField tagsField) { @@ -98,7 +115,9 @@ private void setupTagsField(TagsField tagsField) { tagsField.setOnMouseClicked(event -> tagsField.getEditor().requestFocus()); tagsField.getEditor().getStyleClass().clear(); tagsField.getEditor().getStyleClass().add("tags-field-editor"); - tagsField.getEditor().focusedProperty().addListener((_, _, newValue) -> tagsField.pseudoClassStateChanged(FOCUSED, newValue)); + tagsField.getEditor() + .focusedProperty() + .addListener((_, _, newValue) -> tagsField.pseudoClassStateChanged(FOCUSED, newValue)); tagsField.getEditor().setOnKeyReleased(event -> { if (event.getCode() == KeyCode.ENTER) { @@ -121,4 +140,5 @@ private Node createTag(TagsField tagsField, Field field) { public String getTabName() { return Localization.lang("Entry"); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/entry/EntryTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/entry/EntryTabViewModel.java index f8c0fc1e049..a4cb8a74160 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/entry/EntryTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/entry/EntryTabViewModel.java @@ -27,18 +27,28 @@ public class EntryTabViewModel implements PreferenceTabViewModel { private final BooleanProperty resolveStringsProperty = new SimpleBooleanProperty(); - private final ListProperty resolvableTagsFieldProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ListProperty nonWrappableTagsFieldProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty resolvableTagsFieldProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); + + private final ListProperty nonWrappableTagsFieldProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); private final BooleanProperty markOwnerProperty = new SimpleBooleanProperty(); + private final StringProperty markOwnerNameProperty = new SimpleStringProperty(""); + private final BooleanProperty markOwnerOverwriteProperty = new SimpleBooleanProperty(); + private final BooleanProperty addCreationDateProperty = new SimpleBooleanProperty(); + private final BooleanProperty addModificationDateProperty = new SimpleBooleanProperty(); private final FieldPreferences fieldPreferences; + private final BibEntryPreferences bibEntryPreferences; + private final OwnerPreferences ownerPreferences; + private final TimestampPreferences timestampPreferences; public EntryTabViewModel(CliPreferences preferences) { @@ -54,7 +64,8 @@ public void setValues() { resolveStringsProperty.setValue(fieldPreferences.shouldResolveStrings()); resolvableTagsFieldProperty.setValue(FXCollections.observableArrayList(fieldPreferences.getResolvableFields())); - nonWrappableTagsFieldProperty.setValue(FXCollections.observableArrayList(fieldPreferences.getNonWrappableFields())); + nonWrappableTagsFieldProperty + .setValue(FXCollections.observableArrayList(fieldPreferences.getNonWrappableFields())); markOwnerProperty.setValue(ownerPreferences.isUseOwner()); markOwnerNameProperty.setValue(ownerPreferences.getDefaultOwner()); @@ -134,9 +145,10 @@ public Field fromString(String string) { } public List getSuggestions(String request) { - List suggestions = FieldFactory.getAllFieldsWithOutInternal().stream() - .filter(field -> field.getDisplayName().toLowerCase().contains(request.toLowerCase())) - .collect(Collectors.toList()); + List suggestions = FieldFactory.getAllFieldsWithOutInternal() + .stream() + .filter(field -> field.getDisplayName().toLowerCase().contains(request.toLowerCase())) + .collect(Collectors.toList()); Field requestedField = FieldFactory.parseField(request.trim()); if (!suggestions.contains(requestedField)) { @@ -145,4 +157,5 @@ public List getSuggestions(String request) { return suggestions; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/entryeditor/EntryEditorTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/entryeditor/EntryEditorTab.java index e4d41f6b348..7f9e2ca29f1 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/entryeditor/EntryEditorTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/entryeditor/EntryEditorTab.java @@ -17,28 +17,56 @@ public class EntryEditorTab extends AbstractPreferenceTabView implements PreferencesTab { - @FXML private CheckBox openOnNewEntry; - @FXML private CheckBox defaultSource; - @FXML private CheckBox enableRelatedArticlesTab; - @FXML private CheckBox enableAiSummaryTab; - @FXML private CheckBox enableAiChatTab; - @FXML private CheckBox acceptRecommendations; - @FXML private CheckBox enableLatexCitationsTab; - @FXML private CheckBox smartFileAnnotationsTab; - @FXML private CheckBox enableValidation; - @FXML private CheckBox allowIntegerEdition; - @FXML private CheckBox journalPopupEnabled; - @FXML private CheckBox autoLinkFilesEnabled; - @FXML private CheckBox enableSciteTab; - @FXML private CheckBox showUserCommentsField; - - @FXML private Button generalFieldsHelp; - @FXML private TextArea fieldsTextArea; + @FXML + private CheckBox openOnNewEntry; + + @FXML + private CheckBox defaultSource; + + @FXML + private CheckBox enableRelatedArticlesTab; + + @FXML + private CheckBox enableAiSummaryTab; + + @FXML + private CheckBox enableAiChatTab; + + @FXML + private CheckBox acceptRecommendations; + + @FXML + private CheckBox enableLatexCitationsTab; + + @FXML + private CheckBox smartFileAnnotationsTab; + + @FXML + private CheckBox enableValidation; + + @FXML + private CheckBox allowIntegerEdition; + + @FXML + private CheckBox journalPopupEnabled; + + @FXML + private CheckBox autoLinkFilesEnabled; + + @FXML + private CheckBox enableSciteTab; + + @FXML + private CheckBox showUserCommentsField; + + @FXML + private Button generalFieldsHelp; + + @FXML + private TextArea fieldsTextArea; public EntryEditorTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -67,11 +95,13 @@ public void initialize() { fieldsTextArea.textProperty().bindBidirectional(viewModel.fieldsProperty()); ActionFactory actionFactory = new ActionFactory(); - actionFactory.configureIconButton(StandardActions.HELP, new HelpAction(HelpFile.GENERAL_FIELDS, dialogService, preferences.getExternalApplicationsPreferences()), generalFieldsHelp); + actionFactory.configureIconButton(StandardActions.HELP, new HelpAction(HelpFile.GENERAL_FIELDS, dialogService, + preferences.getExternalApplicationsPreferences()), generalFieldsHelp); } @FXML void resetToDefaults() { viewModel.resetToDefaults(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/entryeditor/EntryEditorTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/entryeditor/EntryEditorTabViewModel.java index fc992cd9ed0..3439eef0648 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/entryeditor/EntryEditorTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/entryeditor/EntryEditorTabViewModel.java @@ -22,17 +22,29 @@ public class EntryEditorTabViewModel implements PreferenceTabViewModel { private final BooleanProperty openOnNewEntryProperty = new SimpleBooleanProperty(); + private final BooleanProperty defaultSourceProperty = new SimpleBooleanProperty(); + private final BooleanProperty enableRelatedArticlesTabProperty = new SimpleBooleanProperty(); + private final BooleanProperty enableAiSummaryTabProperty = new SimpleBooleanProperty(); + private final BooleanProperty enableAiChatTabProperty = new SimpleBooleanProperty(); + private final BooleanProperty acceptRecommendationsProperty = new SimpleBooleanProperty(); + private final BooleanProperty enableLatexCitationsTabProperty = new SimpleBooleanProperty(); + private final BooleanProperty smartFileAnnotationsTabProperty = new SimpleBooleanProperty(); + private final BooleanProperty enableValidationProperty = new SimpleBooleanProperty(); + private final BooleanProperty allowIntegerEditionProperty = new SimpleBooleanProperty(); + private final BooleanProperty journalPopupProperty = new SimpleBooleanProperty(); + private final BooleanProperty autoLinkEnabledProperty = new SimpleBooleanProperty(); + private final BooleanProperty enableSciteTabProperty = new SimpleBooleanProperty(); private final BooleanProperty showUserCommentsProperty = new SimpleBooleanProperty(); @@ -40,8 +52,11 @@ public class EntryEditorTabViewModel implements PreferenceTabViewModel { private final StringProperty fieldsProperty = new SimpleStringProperty(); private final DialogService dialogService; + private final GuiPreferences preferences; + private final EntryEditorPreferences entryEditorPreferences; + private final MrDlibPreferences mrDlibPreferences; public EntryEditorTabViewModel(DialogService dialogService, GuiPreferences preferences) { @@ -54,7 +69,7 @@ public EntryEditorTabViewModel(DialogService dialogService, GuiPreferences prefe @Override public void setValues() { // ToDo: Include CustomizeGeneralFieldsDialog in PreferencesDialog - // Therefore yet unused: entryEditorPreferences.getEntryEditorTabList(); + // Therefore yet unused: entryEditorPreferences.getEntryEditorTabList(); openOnNewEntryProperty.setValue(entryEditorPreferences.shouldOpenOnNewEntry()); defaultSourceProperty.setValue(entryEditorPreferences.showSourceTabByDefault()); @@ -66,7 +81,8 @@ public void setValues() { smartFileAnnotationsTabProperty.setValue(entryEditorPreferences.shouldShowFileAnnotationsTab()); enableValidationProperty.setValue(entryEditorPreferences.shouldEnableValidation()); allowIntegerEditionProperty.setValue(entryEditorPreferences.shouldAllowIntegerEditionBibtex()); - journalPopupProperty.setValue(entryEditorPreferences.shouldEnableJournalPopup() == EntryEditorPreferences.JournalPopupEnabled.ENABLED); + journalPopupProperty.setValue(entryEditorPreferences + .shouldEnableJournalPopup() == EntryEditorPreferences.JournalPopupEnabled.ENABLED); autoLinkEnabledProperty.setValue(entryEditorPreferences.autoLinkFilesEnabled()); enableSciteTabProperty.setValue(entryEditorPreferences.shouldShowSciteTab()); showUserCommentsProperty.setValue(entryEditorPreferences.shouldShowUserCommentsFields()); @@ -104,9 +120,9 @@ public void storeSettings() { entryEditorPreferences.setShowSourceTabByDefault(defaultSourceProperty.getValue()); entryEditorPreferences.setEnableValidation(enableValidationProperty.getValue()); entryEditorPreferences.setAllowIntegerEditionBibtex(allowIntegerEditionProperty.getValue()); - entryEditorPreferences.setEnableJournalPopup(journalPopupProperty.getValue() - ? EntryEditorPreferences.JournalPopupEnabled.ENABLED - : EntryEditorPreferences.JournalPopupEnabled.DISABLED); + entryEditorPreferences + .setEnableJournalPopup(journalPopupProperty.getValue() ? EntryEditorPreferences.JournalPopupEnabled.ENABLED + : EntryEditorPreferences.JournalPopupEnabled.DISABLED); // entryEditorPreferences.setDividerPosition(); entryEditorPreferences.setAutoLinkFilesEnabled(autoLinkEnabledProperty.getValue()); entryEditorPreferences.setShouldShowSciteTab(enableSciteTabProperty.getValue()); @@ -118,21 +134,23 @@ public void storeSettings() { for (String line : lines) { String[] parts = line.split(":"); if (parts.length != 2) { - dialogService.showInformationDialogAndWait( - Localization.lang("Error"), + dialogService.showInformationDialogAndWait(Localization.lang("Error"), Localization.lang("Each line must be of the following form: 'tab:field1;field2;...;fieldN'.")); return; } - // Use literal string of unwanted characters specified below as opposed to exporting characters - // from preferences because the list of allowable characters in this particular differs - // i.e. ';' character is allowed in this window, but it's on the list of unwanted chars in preferences + // Use literal string of unwanted characters specified below as opposed to + // exporting characters + // from preferences because the list of allowable characters in this + // particular differs + // i.e. ';' character is allowed in this window, but it's on the list of + // unwanted chars in preferences String unwantedChars = "#{}()~,^&-\"'`ʹ\\"; String testString = CitationKeyGenerator.cleanKey(parts[1], unwantedChars); if (!testString.equals(parts[1])) { - dialogService.showInformationDialogAndWait( - Localization.lang("Error"), - Localization.lang("Field names are not allowed to contain white spaces or certain characters (%0).", + dialogService.showInformationDialogAndWait(Localization.lang("Error"), + Localization.lang( + "Field names are not allowed to contain white spaces or certain characters (%0).", "# { } ( ) ~ , ^ & - \" ' ` ʹ \\")); return; } @@ -202,4 +220,5 @@ public BooleanProperty enableSciteTabProperty() { public BooleanProperty showUserCommentsProperty() { return this.showUserCommentsProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/export/ExportTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/export/ExportTab.java index 3e99e615340..a3a4667ece2 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/export/ExportTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/export/ExportTab.java @@ -10,12 +10,12 @@ import com.airhacks.afterburner.views.ViewLoader; public class ExportTab extends AbstractPreferenceTabView implements PreferencesTab { - @FXML private SaveOrderConfigPanel exportOrderPanel; + + @FXML + private SaveOrderConfigPanel exportOrderPanel; public ExportTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -33,4 +33,5 @@ public void initialize() { exportOrderPanel.sortCriteriaProperty().bindBidirectional(viewModel.sortCriteriaProperty()); exportOrderPanel.setCriteriaLimit(3); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/export/ExportTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/export/ExportTabViewModel.java index 00717280eaf..12389a4b8e0 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/export/ExportTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/export/ExportTabViewModel.java @@ -21,10 +21,16 @@ public class ExportTabViewModel implements PreferenceTabViewModel { // SaveOrderConfigPanel private final BooleanProperty exportInOriginalProperty = new SimpleBooleanProperty(); + private final BooleanProperty exportInTableOrderProperty = new SimpleBooleanProperty(); + private final BooleanProperty exportInSpecifiedOrderProperty = new SimpleBooleanProperty(); - private final ListProperty sortableFieldsProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ListProperty sortCriteriaProperty = new SimpleListProperty<>(FXCollections.observableArrayList(new ArrayList<>())); + + private final ListProperty sortableFieldsProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); + + private final ListProperty sortCriteriaProperty = new SimpleListProperty<>( + FXCollections.observableArrayList(new ArrayList<>())); private final ExportPreferences exportPreferences; @@ -40,9 +46,8 @@ public void setValues() { case ORIGINAL -> exportInOriginalProperty.setValue(true); case TABLE -> exportInTableOrderProperty.setValue(true); } - sortCriteriaProperty.addAll(exportSaveOrder.getSortCriteria().stream() - .map(SortCriterionViewModel::new) - .toList()); + sortCriteriaProperty + .addAll(exportSaveOrder.getSortCriteria().stream().map(SortCriterionViewModel::new).toList()); Set fields = FieldFactory.getAllFieldsWithOutInternal(); fields.add(InternalField.INTERNAL_ALL_FIELD); @@ -55,7 +60,8 @@ public void setValues() { @Override public void storeSettings() { SaveOrder newSaveOrder = new SaveOrder( - SaveOrder.OrderType.fromBooleans(exportInSpecifiedOrderProperty.getValue(), exportInOriginalProperty.getValue()), + SaveOrder.OrderType.fromBooleans(exportInSpecifiedOrderProperty.getValue(), + exportInOriginalProperty.getValue()), sortCriteriaProperty.stream().map(SortCriterionViewModel::getCriterion).toList()); exportPreferences.setExportSaveOrder(newSaveOrder); } @@ -79,4 +85,5 @@ public ListProperty sortableFieldsProperty() { public ListProperty sortCriteriaProperty() { return sortCriteriaProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/external/ExternalTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/external/ExternalTab.java index 7f3c34c368b..15ab722e829 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/external/ExternalTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/external/ExternalTab.java @@ -23,26 +23,46 @@ public class ExternalTab extends AbstractPreferenceTabView implements PreferencesTab { - @FXML private TextField eMailReferenceSubject; - @FXML private CheckBox autoOpenAttachedFolders; - @FXML private ComboBox pushToApplicationCombo; - @FXML private TextField citeCommand; - @FXML private Button autolinkExternalHelp; - - @FXML private CheckBox useCustomTerminal; - @FXML private TextField customTerminalCommand; - @FXML private Button customTerminalBrowse; - @FXML private CheckBox useCustomFileBrowser; - @FXML private TextField customFileBrowserCommand; - @FXML private Button customFileBrowserBrowse; - @FXML private TextField kindleEmail; + @FXML + private TextField eMailReferenceSubject; + + @FXML + private CheckBox autoOpenAttachedFolders; + + @FXML + private ComboBox pushToApplicationCombo; + + @FXML + private TextField citeCommand; + + @FXML + private Button autolinkExternalHelp; + + @FXML + private CheckBox useCustomTerminal; + + @FXML + private TextField customTerminalCommand; + + @FXML + private Button customTerminalBrowse; + + @FXML + private CheckBox useCustomFileBrowser; + + @FXML + private TextField customFileBrowserCommand; + + @FXML + private Button customFileBrowserBrowse; + + @FXML + private TextField kindleEmail; private final ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer(); public ExternalTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -53,10 +73,9 @@ public String getTabName() { public void initialize() { this.viewModel = new ExternalTabViewModel(dialogService, preferences); - new ViewModelListCellFactory() - .withText(GuiPushToApplication::getDisplayName) - .withIcon(GuiPushToApplication::getApplicationIcon) - .install(pushToApplicationCombo); + new ViewModelListCellFactory().withText(GuiPushToApplication::getDisplayName) + .withIcon(GuiPushToApplication::getApplicationIcon) + .install(pushToApplicationCombo); eMailReferenceSubject.textProperty().bindBidirectional(viewModel.eMailReferenceSubjectProperty()); autoOpenAttachedFolders.selectedProperty().bindBidirectional(viewModel.autoOpenAttachedFoldersProperty()); @@ -80,11 +99,15 @@ public void initialize() { validationVisualizer.setDecoration(new IconValidationDecorator()); Platform.runLater(() -> { validationVisualizer.initVisualization(viewModel.terminalCommandValidationStatus(), customTerminalCommand); - validationVisualizer.initVisualization(viewModel.fileBrowserCommandValidationStatus(), customFileBrowserCommand); + validationVisualizer.initVisualization(viewModel.fileBrowserCommandValidationStatus(), + customFileBrowserCommand); }); ActionFactory actionFactory = new ActionFactory(); - actionFactory.configureIconButton(StandardActions.HELP_PUSH_TO_APPLICATION, new HelpAction(HelpFile.PUSH_TO_APPLICATION, dialogService, preferences.getExternalApplicationsPreferences()), autolinkExternalHelp); + actionFactory.configureIconButton(StandardActions.HELP_PUSH_TO_APPLICATION, + new HelpAction(HelpFile.PUSH_TO_APPLICATION, dialogService, + preferences.getExternalApplicationsPreferences()), + autolinkExternalHelp); } @FXML @@ -106,4 +129,5 @@ void useFileBrowserSpecialCommandBrowse() { void resetCiteCommandToDefault() { viewModel.resetCiteCommandToDefault(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/external/ExternalTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/external/ExternalTabViewModel.java index cc00d452bdd..33065d46b48 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/external/ExternalTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/external/ExternalTabViewModel.java @@ -37,26 +37,39 @@ public class ExternalTabViewModel implements PreferenceTabViewModel { private final StringProperty eMailReferenceSubjectProperty = new SimpleStringProperty(""); + private final BooleanProperty autoOpenAttachedFoldersProperty = new SimpleBooleanProperty(); + private final ListProperty pushToApplicationsListProperty = new SimpleListProperty<>(); + private final ObjectProperty selectedPushToApplicationProperty = new SimpleObjectProperty<>(); + private final StringProperty citeCommandProperty = new SimpleStringProperty(""); + private final BooleanProperty useCustomTerminalProperty = new SimpleBooleanProperty(); + private final StringProperty customTerminalCommandProperty = new SimpleStringProperty(""); + private final BooleanProperty useCustomFileBrowserProperty = new SimpleBooleanProperty(); + private final StringProperty customFileBrowserCommandProperty = new SimpleStringProperty(""); + private final StringProperty kindleEmailProperty = new SimpleStringProperty(""); private final Validator terminalCommandValidator; + private final Validator fileBrowserCommandValidator; private final DialogService dialogService; + private final GuiPreferences preferences; private final FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder().build(); private final ExternalApplicationsPreferences initialExternalApplicationPreferences; + private final PushToApplicationPreferences initialPushToApplicationPreferences; + private final PushToApplicationPreferences workingPushToApplicationPreferences; public ExternalTabViewModel(DialogService dialogService, GuiPreferences preferences) { @@ -70,22 +83,17 @@ public ExternalTabViewModel(DialogService dialogService, GuiPreferences preferen initialPushToApplicationPreferences.getEmacsArguments(), initialPushToApplicationPreferences.getVimServer(), initialPushToApplicationPreferences.getCiteCommand(), - initialPushToApplicationPreferences.getDefaultCiteCommand() - ); + initialPushToApplicationPreferences.getDefaultCiteCommand()); - terminalCommandValidator = new FunctionBasedValidator<>( - customTerminalCommandProperty, + terminalCommandValidator = new FunctionBasedValidator<>(customTerminalCommandProperty, input -> !StringUtil.isNullOrEmpty(input), - ValidationMessage.error("%s > %s %n %n %s".formatted( - Localization.lang("External programs"), + ValidationMessage.error("%s > %s %n %n %s".formatted(Localization.lang("External programs"), Localization.lang("Custom applications"), Localization.lang("Please specify a terminal application.")))); - fileBrowserCommandValidator = new FunctionBasedValidator<>( - customFileBrowserCommandProperty, + fileBrowserCommandValidator = new FunctionBasedValidator<>(customFileBrowserCommandProperty, input -> !StringUtil.isNullOrEmpty(input), - ValidationMessage.error("%s > %s %n %n %s".formatted( - Localization.lang("External programs"), + ValidationMessage.error("%s > %s %n %n %s".formatted(Localization.lang("External programs"), Localization.lang("Custom applications"), Localization.lang("Please specify a file browser.")))); } @@ -93,13 +101,15 @@ public ExternalTabViewModel(DialogService dialogService, GuiPreferences preferen @Override public void setValues() { eMailReferenceSubjectProperty.setValue(initialExternalApplicationPreferences.getEmailSubject()); - autoOpenAttachedFoldersProperty.setValue(initialExternalApplicationPreferences.shouldAutoOpenEmailAttachmentsFolder()); + autoOpenAttachedFoldersProperty + .setValue(initialExternalApplicationPreferences.shouldAutoOpenEmailAttachmentsFolder()); - pushToApplicationsListProperty.setValue( - FXCollections.observableArrayList(GuiPushToApplications.getAllGUIApplications(dialogService, preferences.getPushToApplicationPreferences()))); - selectedPushToApplicationProperty.setValue( - GuiPushToApplications.getGUIApplicationByName(initialPushToApplicationPreferences.getActiveApplicationName(), dialogService, preferences.getPushToApplicationPreferences()) - .orElseGet(() -> new GuiPushToEmacs(dialogService, preferences.getPushToApplicationPreferences()))); + pushToApplicationsListProperty.setValue(FXCollections.observableArrayList(GuiPushToApplications + .getAllGUIApplications(dialogService, preferences.getPushToApplicationPreferences()))); + selectedPushToApplicationProperty.setValue(GuiPushToApplications + .getGUIApplicationByName(initialPushToApplicationPreferences.getActiveApplicationName(), dialogService, + preferences.getPushToApplicationPreferences()) + .orElseGet(() -> new GuiPushToEmacs(dialogService, preferences.getPushToApplicationPreferences()))); citeCommandProperty.setValue(initialPushToApplicationPreferences.getCiteCommand().toString()); @@ -151,8 +161,8 @@ public boolean validateSettings() { ValidationStatus validationStatus = validator.getValidationStatus(); if (!validationStatus.isValid()) { - validationStatus.getHighestMessage().ifPresent(message -> - dialogService.showErrorDialogAndWait(message.getMessage())); + validationStatus.getHighestMessage() + .ifPresent(message -> dialogService.showErrorDialogAndWait(message.getMessage())); return false; } return true; @@ -160,31 +170,30 @@ public boolean validateSettings() { public void pushToApplicationSettings() { GuiPushToApplication selectedApplication = selectedPushToApplicationProperty.getValue(); - GuiPushToApplicationSettings settings = selectedApplication.getSettings(selectedApplication, dialogService, preferences.getFilePreferences(), workingPushToApplicationPreferences); + GuiPushToApplicationSettings settings = selectedApplication.getSettings(selectedApplication, dialogService, + preferences.getFilePreferences(), workingPushToApplicationPreferences); DialogPane dialogPane = new DialogPane(); dialogPane.setContent(settings.getSettingsPane()); - dialogService.showCustomDialogAndWait( - Localization.lang("Application settings"), - dialogPane, - ButtonType.OK, ButtonType.CANCEL) - .ifPresent(btn -> { - if (btn == ButtonType.OK) { - settings.storeSettings(); - } - } - ); + dialogService + .showCustomDialogAndWait(Localization.lang("Application settings"), dialogPane, ButtonType.OK, + ButtonType.CANCEL) + .ifPresent(btn -> { + if (btn == ButtonType.OK) { + settings.storeSettings(); + } + }); } public void customTerminalBrowse() { dialogService.showFileOpenDialog(fileDialogConfiguration) - .ifPresent(file -> customTerminalCommandProperty.setValue(file.toAbsolutePath().toString())); + .ifPresent(file -> customTerminalCommandProperty.setValue(file.toAbsolutePath().toString())); } public void customFileBrowserBrowse() { dialogService.showFileOpenDialog(fileDialogConfiguration) - .ifPresent(file -> customFileBrowserCommandProperty.setValue(file.toAbsolutePath().toString())); + .ifPresent(file -> customFileBrowserCommandProperty.setValue(file.toAbsolutePath().toString())); } // EMail @@ -234,6 +243,8 @@ public StringProperty customFileBrowserCommandProperty() { } public void resetCiteCommandToDefault() { - this.citeCommandProperty.setValue(preferences.getPushToApplicationPreferences().getDefaultCiteCommand().toString()); + this.citeCommandProperty + .setValue(preferences.getPushToApplicationPreferences().getDefaultCiteCommand().toString()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/EditExternalFileTypeEntryDialog.java b/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/EditExternalFileTypeEntryDialog.java index 44cab5420aa..26fd78c2c68 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/EditExternalFileTypeEntryDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/EditExternalFileTypeEntryDialog.java @@ -23,33 +23,55 @@ public class EditExternalFileTypeEntryDialog extends BaseDialog { - @FXML private RadioButton defaultApplication; - @FXML private ToggleGroup applicationToggleGroup; - @FXML private TextField extension; - @FXML private TextField name; - @FXML private TextField mimeType; - @FXML private RadioButton customApplication; - @FXML private TextField selectedApplication; - @FXML private Button btnBrowse; - @FXML private Label icon; - @Inject private DialogService dialogService; - - private final FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder().withInitialDirectory(NativeDesktop.get().getApplicationDirectory()).build(); + @FXML + private RadioButton defaultApplication; + + @FXML + private ToggleGroup applicationToggleGroup; + + @FXML + private TextField extension; + + @FXML + private TextField name; + + @FXML + private TextField mimeType; + + @FXML + private RadioButton customApplication; + + @FXML + private TextField selectedApplication; + + @FXML + private Button btnBrowse; + + @FXML + private Label icon; + + @Inject + private DialogService dialogService; + + private final FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() + .withInitialDirectory(NativeDesktop.get().getApplicationDirectory()) + .build(); + private final ExternalFileTypeItemViewModel item; private final ObservableList fileTypes; + private EditExternalFileTypeViewModel viewModel; private final ControlsFxVisualizer visualizer = new ControlsFxVisualizer(); - public EditExternalFileTypeEntryDialog(ExternalFileTypeItemViewModel item, String dialogTitle, ObservableList fileTypes) { + public EditExternalFileTypeEntryDialog(ExternalFileTypeItemViewModel item, String dialogTitle, + ObservableList fileTypes) { this.item = item; this.fileTypes = fileTypes; this.setTitle(dialogTitle); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); getDialogPane().getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL); @@ -89,6 +111,8 @@ public void initialize() { @FXML private void openFileChooser(ActionEvent event) { - dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(path -> viewModel.selectedApplicationProperty().setValue(path.toAbsolutePath().toString())); + dialogService.showFileOpenDialog(fileDialogConfiguration) + .ifPresent(path -> viewModel.selectedApplicationProperty().setValue(path.toAbsolutePath().toString())); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/EditExternalFileTypeViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/EditExternalFileTypeViewModel.java index e56aa5e5114..41f23f71101 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/EditExternalFileTypeViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/EditExternalFileTypeViewModel.java @@ -17,23 +17,39 @@ import de.saxsys.mvvmfx.utils.validation.Validator; public class EditExternalFileTypeViewModel { + private final ExternalFileTypeItemViewModel fileTypeViewModel; + private final StringProperty nameProperty = new SimpleStringProperty(""); + private final StringProperty mimeTypeProperty = new SimpleStringProperty(""); + private final StringProperty extensionProperty = new SimpleStringProperty(""); + private final StringProperty selectedApplicationProperty = new SimpleStringProperty(""); + private final BooleanProperty defaultApplicationSelectedProperty = new SimpleBooleanProperty(false); + private final BooleanProperty customApplicationSelectedProperty = new SimpleBooleanProperty(false); + private final ObservableList fileTypes; + private final String originalExtension; + private final String originalName; + private final String originalMimeType; + private CompositeValidator extensionValidator; + private CompositeValidator nameValidator; + private CompositeValidator mimeTypeValidator; + private CompositeValidator validator; - public EditExternalFileTypeViewModel(ExternalFileTypeItemViewModel fileTypeViewModel, ObservableList fileTypes) { + public EditExternalFileTypeViewModel(ExternalFileTypeItemViewModel fileTypeViewModel, + ObservableList fileTypes) { this.fileTypeViewModel = fileTypeViewModel; this.fileTypes = fileTypes; this.originalExtension = fileTypeViewModel.extensionProperty().getValue(); @@ -45,7 +61,8 @@ public EditExternalFileTypeViewModel(ExternalFileTypeItemViewModel fileTypeViewM if (fileTypeViewModel.applicationProperty().getValue().isEmpty()) { defaultApplicationSelectedProperty.setValue(true); - } else { + } + else { customApplicationSelectedProperty.setValue(true); selectedApplicationProperty.setValue(fileTypeViewModel.applicationProperty().getValue()); } @@ -57,68 +74,52 @@ private void setupValidation() { validator = new CompositeValidator(); extensionValidator = new CompositeValidator(); - Validator extensionisNotBlankValidator = new FunctionBasedValidator<>( - extensionProperty, - StringUtil::isNotBlank, - ValidationMessage.error(Localization.lang("Please enter a name for the extension.")) - ); - Validator sameExtensionValidator = new FunctionBasedValidator<>( - extensionProperty, - extension -> { - for (ExternalFileTypeItemViewModel fileTypeItem : fileTypes) { - if (extension.equalsIgnoreCase(fileTypeItem.extensionProperty().get()) && !extension.equalsIgnoreCase(originalExtension)) { - return false; - } - } - return true; - }, - ValidationMessage.error(Localization.lang("There already exists an external file type with the same extension")) - ); + Validator extensionisNotBlankValidator = new FunctionBasedValidator<>(extensionProperty, StringUtil::isNotBlank, + ValidationMessage.error(Localization.lang("Please enter a name for the extension."))); + Validator sameExtensionValidator = new FunctionBasedValidator<>(extensionProperty, extension -> { + for (ExternalFileTypeItemViewModel fileTypeItem : fileTypes) { + if (extension.equalsIgnoreCase(fileTypeItem.extensionProperty().get()) + && !extension.equalsIgnoreCase(originalExtension)) { + return false; + } + } + return true; + }, ValidationMessage + .error(Localization.lang("There already exists an external file type with the same extension"))); extensionValidator.addValidators(sameExtensionValidator, extensionisNotBlankValidator); nameValidator = new CompositeValidator(); - Validator sameNameValidator = new FunctionBasedValidator<>( - nameProperty, - name -> { - for (ExternalFileTypeItemViewModel fileTypeItem : fileTypes) { - if (name.equalsIgnoreCase(fileTypeItem.nameProperty().get()) && !name.equalsIgnoreCase(originalName)) { - return false; - } - } - return true; - }, - ValidationMessage.error(Localization.lang("There already exists an external file type with the same name")) - ); - - Validator nameIsNotBlankValidator = new FunctionBasedValidator<>( - nameProperty, - StringUtil::isNotBlank, - ValidationMessage.error(Localization.lang("Please enter a name.")) - ); + Validator sameNameValidator = new FunctionBasedValidator<>(nameProperty, name -> { + for (ExternalFileTypeItemViewModel fileTypeItem : fileTypes) { + if (name.equalsIgnoreCase(fileTypeItem.nameProperty().get()) && !name.equalsIgnoreCase(originalName)) { + return false; + } + } + return true; + }, ValidationMessage.error(Localization.lang("There already exists an external file type with the same name"))); + + Validator nameIsNotBlankValidator = new FunctionBasedValidator<>(nameProperty, StringUtil::isNotBlank, + ValidationMessage.error(Localization.lang("Please enter a name."))); nameValidator.addValidators(sameNameValidator, nameIsNotBlankValidator); mimeTypeValidator = new CompositeValidator(); - Validator mimeTypeIsNotBlankValidator = new FunctionBasedValidator<>( - mimeTypeProperty, - StringUtil::isNotBlank, - ValidationMessage.error(Localization.lang("Please enter a name for the MIME type.")) - ); - - Validator sameMimeTypeValidator = new FunctionBasedValidator<>( - mimeTypeProperty, - mimeType -> { - for (ExternalFileTypeItemViewModel fileTypeItem : fileTypes) { - if (mimeType.equalsIgnoreCase(fileTypeItem.mimetypeProperty().get()) && !mimeType.equalsIgnoreCase(originalMimeType)) { - return false; - } - } - return true; - }, - ValidationMessage.error(Localization.lang("There already exists an external file type with the same MIME type")) - ); + Validator mimeTypeIsNotBlankValidator = new FunctionBasedValidator<>(mimeTypeProperty, StringUtil::isNotBlank, + ValidationMessage.error(Localization.lang("Please enter a name for the MIME type."))); + + Validator sameMimeTypeValidator = new FunctionBasedValidator<>(mimeTypeProperty, mimeType -> { + for (ExternalFileTypeItemViewModel fileTypeItem : fileTypes) { + if (mimeType.equalsIgnoreCase(fileTypeItem.mimetypeProperty().get()) + && !mimeType.equalsIgnoreCase(originalMimeType)) { + return false; + } + } + return true; + }, ValidationMessage + .error(Localization.lang("There already exists an external file type with the same MIME type"))); mimeTypeValidator.addValidators(sameMimeTypeValidator, mimeTypeIsNotBlankValidator); - validator.addValidators(extensionValidator, sameExtensionValidator, nameValidator, sameNameValidator, mimeTypeValidator, sameMimeTypeValidator); + validator.addValidators(extensionValidator, sameExtensionValidator, nameValidator, sameNameValidator, + mimeTypeValidator, sameMimeTypeValidator); } public ValidationStatus validationStatus() { @@ -176,18 +177,22 @@ public void storeSettings() { String ext = extensionProperty.getValue().trim(); if (!ext.isEmpty() && (ext.charAt(0) == '.')) { fileTypeViewModel.extensionProperty().setValue(ext.substring(1)); - } else { + } + else { fileTypeViewModel.extensionProperty().setValue(ext); } String application = selectedApplicationProperty.getValue().trim(); - // store application as empty if the "Default" option is selected, or if the application name is empty: + // store application as empty if the "Default" option is selected, or if the + // application name is empty: if (defaultApplicationSelectedProperty.getValue() || application.isEmpty()) { fileTypeViewModel.applicationProperty().setValue(""); selectedApplicationProperty.setValue(""); - } else { + } + else { fileTypeViewModel.applicationProperty().setValue(application); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypeItemViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypeItemViewModel.java index da149d6e516..5b09ec13e24 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypeItemViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypeItemViewModel.java @@ -11,10 +11,15 @@ import org.jabref.gui.icon.JabRefIcon; public class ExternalFileTypeItemViewModel { + private final ObjectProperty icon = new SimpleObjectProperty<>(); + private final StringProperty name = new SimpleStringProperty(); + private final StringProperty extension = new SimpleStringProperty(); + private final StringProperty mimetype = new SimpleStringProperty(); + private final StringProperty application = new SimpleStringProperty(); public ExternalFileTypeItemViewModel(ExternalFileType fileType) { @@ -57,12 +62,8 @@ public StringProperty applicationProperty() { } public ExternalFileType toExternalFileType() { - return new CustomExternalFileType( - this.name.get(), - this.extension.get(), - this.mimetype.get(), - this.application.get(), - this.icon.get().name(), - this.icon.get()); + return new CustomExternalFileType(this.name.get(), this.extension.get(), this.mimetype.get(), + this.application.get(), this.icon.get().name(), this.icon.get()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypesTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypesTab.java index 1529d07d92c..cfe0013a177 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypesTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypesTab.java @@ -17,21 +17,35 @@ /** * Editor for external file types. */ -public class ExternalFileTypesTab extends AbstractPreferenceTabView implements PreferencesTab { +public class ExternalFileTypesTab extends AbstractPreferenceTabView + implements PreferencesTab { - @FXML private TableColumn fileTypesTableIconColumn; - @FXML private TableColumn fileTypesTableNameColumn; - @FXML private TableColumn fileTypesTableExtensionColumn; - @FXML private TableColumn fileTypesTableMimeTypeColumn; - @FXML private TableColumn fileTypesTableApplicationColumn; - @FXML private TableColumn fileTypesTableEditColumn; - @FXML private TableColumn fileTypesTableDeleteColumn; - @FXML private TableView fileTypesTable; + @FXML + private TableColumn fileTypesTableIconColumn; + + @FXML + private TableColumn fileTypesTableNameColumn; + + @FXML + private TableColumn fileTypesTableExtensionColumn; + + @FXML + private TableColumn fileTypesTableMimeTypeColumn; + + @FXML + private TableColumn fileTypesTableApplicationColumn; + + @FXML + private TableColumn fileTypesTableEditColumn; + + @FXML + private TableColumn fileTypesTableDeleteColumn; + + @FXML + private TableView fileTypesTable; public ExternalFileTypesTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -46,44 +60,38 @@ public void initialize() { fileTypesTable.setItems(viewModel.getFileTypes()); fileTypesTableIconColumn.setCellValueFactory(cellData -> cellData.getValue().iconProperty()); - new ValueTableCellFactory() - .withGraphic(JabRefIcon::getGraphicNode) - .install(fileTypesTableIconColumn); + new ValueTableCellFactory().withGraphic(JabRefIcon::getGraphicNode) + .install(fileTypesTableIconColumn); fileTypesTableNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); - new ValueTableCellFactory() - .withText(name -> name) - .install(fileTypesTableNameColumn); + new ValueTableCellFactory().withText(name -> name) + .install(fileTypesTableNameColumn); fileTypesTableExtensionColumn.setCellValueFactory(cellData -> cellData.getValue().extensionProperty()); - new ValueTableCellFactory() - .withText(extension -> extension) - .install(fileTypesTableExtensionColumn); + new ValueTableCellFactory().withText(extension -> extension) + .install(fileTypesTableExtensionColumn); fileTypesTableMimeTypeColumn.setCellValueFactory(cellData -> cellData.getValue().mimetypeProperty()); - new ValueTableCellFactory() - .withText(mimetype -> mimetype) - .install(fileTypesTableMimeTypeColumn); + new ValueTableCellFactory().withText(mimetype -> mimetype) + .install(fileTypesTableMimeTypeColumn); fileTypesTableApplicationColumn.setCellValueFactory(cellData -> cellData.getValue().applicationProperty()); - new ValueTableCellFactory() - .withText(extension -> extension) - .install(fileTypesTableApplicationColumn); + new ValueTableCellFactory().withText(extension -> extension) + .install(fileTypesTableApplicationColumn); fileTypesTableEditColumn.setCellValueFactory(data -> BindingsHelper.constantOf(true)); fileTypesTableDeleteColumn.setCellValueFactory(data -> BindingsHelper.constantOf(true)); - new ValueTableCellFactory() - .withGraphic(JabRefIcon::getGraphicNode) - .install(fileTypesTableIconColumn); + new ValueTableCellFactory().withGraphic(JabRefIcon::getGraphicNode) + .install(fileTypesTableIconColumn); new ValueTableCellFactory() - .withGraphic(none -> IconTheme.JabRefIcons.EDIT.getGraphicNode()) - .withOnMouseClickedEvent((type, none) -> event -> editType(type)) - .install(fileTypesTableEditColumn); + .withGraphic(none -> IconTheme.JabRefIcons.EDIT.getGraphicNode()) + .withOnMouseClickedEvent((type, none) -> event -> editType(type)) + .install(fileTypesTableEditColumn); new ValueTableCellFactory() - .withGraphic(none -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) - .withOnMouseClickedEvent((type, none) -> event -> viewModel.remove(type)) - .install(fileTypesTableDeleteColumn); + .withGraphic(none -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) + .withOnMouseClickedEvent((type, none) -> event -> viewModel.remove(type)) + .install(fileTypesTableDeleteColumn); } private void editType(ExternalFileTypeItemViewModel type) { @@ -105,4 +113,5 @@ private void addNewType() { private void resetToDefault() { viewModel.resetToDefaults(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypesTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypesTabViewModel.java index a228faf6d85..7940a3ac4d5 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypesTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypesTabViewModel.java @@ -20,12 +20,15 @@ public class ExternalFileTypesTabViewModel implements PreferenceTabViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(ExternalFileTypesTabViewModel.class); + private final ObservableList fileTypes = FXCollections.observableArrayList(); private final ExternalApplicationsPreferences externalApplicationsPreferences; + private final DialogService dialogService; - public ExternalFileTypesTabViewModel(ExternalApplicationsPreferences externalApplicationsPreferences, DialogService dialogService) { + public ExternalFileTypesTabViewModel(ExternalApplicationsPreferences externalApplicationsPreferences, + DialogService dialogService) { this.externalApplicationsPreferences = externalApplicationsPreferences; this.dialogService = dialogService; } @@ -33,28 +36,33 @@ public ExternalFileTypesTabViewModel(ExternalApplicationsPreferences externalApp @Override public void setValues() { fileTypes.clear(); - fileTypes.addAll(externalApplicationsPreferences.getExternalFileTypes().stream() - .map(ExternalFileTypeItemViewModel::new) - .toList()); + fileTypes.addAll(externalApplicationsPreferences.getExternalFileTypes() + .stream() + .map(ExternalFileTypeItemViewModel::new) + .toList()); fileTypes.sort(Comparator.comparing(ExternalFileTypeItemViewModel::getName)); } public void storeSettings() { Set saveList = new HashSet<>(); - fileTypes.stream().map(ExternalFileTypeItemViewModel::toExternalFileType) - .forEach(type -> ExternalFileTypes.getDefaultExternalFileTypes().stream() - .filter(type::equals).findAny() - .ifPresentOrElse(saveList::add, () -> saveList.add(type))); + fileTypes.stream() + .map(ExternalFileTypeItemViewModel::toExternalFileType) + .forEach(type -> ExternalFileTypes.getDefaultExternalFileTypes() + .stream() + .filter(type::equals) + .findAny() + .ifPresentOrElse(saveList::add, () -> saveList.add(type))); externalApplicationsPreferences.getExternalFileTypes().clear(); externalApplicationsPreferences.getExternalFileTypes().addAll(saveList); } public void resetToDefaults() { - fileTypes.setAll(ExternalFileTypes.getDefaultExternalFileTypes().stream() - .map(ExternalFileTypeItemViewModel::new) - .toList()); + fileTypes.setAll(ExternalFileTypes.getDefaultExternalFileTypes() + .stream() + .map(ExternalFileTypeItemViewModel::new) + .toList()); fileTypes.sort(Comparator.comparing(ExternalFileTypeItemViewModel::getName)); } @@ -87,7 +95,8 @@ public boolean edit(ExternalFileTypeItemViewModel type) { LOGGER.warn("One of the fields is empty or invalid. Not saving."); return false; } - } else if (!isValidExternalFileType(typeToModify)) { + } + else if (!isValidExternalFileType(typeToModify)) { return false; } @@ -115,7 +124,8 @@ public boolean isValidExternalFileType(ExternalFileTypeItemViewModel item) { } private boolean hasEmptyValue(ExternalFileTypeItemViewModel item) { - return item.getName().isEmpty() || item.extensionProperty().get().isEmpty() || item.mimetypeProperty().get().isEmpty(); + return item.getName().isEmpty() || item.extensionProperty().get().isEmpty() + || item.mimetypeProperty().get().isEmpty(); } private boolean isUniqueExtension(ExternalFileTypeItemViewModel item) { @@ -128,4 +138,5 @@ private boolean isUniqueExtension(ExternalFileTypeItemViewModel item) { } return true; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTab.java index 28a224ba2ca..47a4b04179e 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTab.java @@ -35,50 +35,98 @@ public class GeneralTab extends AbstractPreferenceTabView implements PreferencesTab { - @FXML private ComboBox language; - @FXML private ComboBox theme; - @FXML private CheckBox themeSyncOs; - @FXML private TextField customThemePath; - @FXML private Button customThemeBrowse; - @FXML private CheckBox fontOverride; - @FXML private Spinner fontSize; - @FXML private CheckBox openLastStartup; - @FXML private CheckBox showAdvancedHints; - @FXML private CheckBox inspectionWarningDuplicate; - - @FXML private CheckBox confirmDelete; - @FXML private CheckBox shouldAskForIncludingCrossReferences; - @FXML private CheckBox confirmHideTabBar; - @FXML private ComboBox biblatexMode; - @FXML private CheckBox alwaysReformatBib; - @FXML private CheckBox autosaveLocalLibraries; - @FXML private Button autosaveLocalLibrariesHelp; - @FXML private CheckBox createBackup; - @FXML private TextField backupDirectory; - @FXML private CheckBox remoteServer; - @FXML private TextField remotePort; - @FXML private CheckBox enableHttpServer; - @FXML private TextField httpServerPort; - @FXML private Button remoteHelp; - @Inject private FileUpdateMonitor fileUpdateMonitor; - @Inject private BibEntryTypesManager entryTypesManager; + @FXML + private ComboBox language; + + @FXML + private ComboBox theme; + + @FXML + private CheckBox themeSyncOs; + + @FXML + private TextField customThemePath; + + @FXML + private Button customThemeBrowse; + + @FXML + private CheckBox fontOverride; + + @FXML + private Spinner fontSize; + + @FXML + private CheckBox openLastStartup; + + @FXML + private CheckBox showAdvancedHints; + + @FXML + private CheckBox inspectionWarningDuplicate; + + @FXML + private CheckBox confirmDelete; + + @FXML + private CheckBox shouldAskForIncludingCrossReferences; + + @FXML + private CheckBox confirmHideTabBar; + + @FXML + private ComboBox biblatexMode; + + @FXML + private CheckBox alwaysReformatBib; + + @FXML + private CheckBox autosaveLocalLibraries; + + @FXML + private Button autosaveLocalLibrariesHelp; + + @FXML + private CheckBox createBackup; + + @FXML + private TextField backupDirectory; + + @FXML + private CheckBox remoteServer; + + @FXML + private TextField remotePort; + + @FXML + private CheckBox enableHttpServer; + + @FXML + private TextField httpServerPort; + + @FXML + private Button remoteHelp; + + @Inject + private FileUpdateMonitor fileUpdateMonitor; + + @Inject + private BibEntryTypesManager entryTypesManager; private final ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer(); - // The fontSizeFormatter formats the input given to the fontSize spinner so that non valid values cannot be entered. - private final TextFormatter fontSizeFormatter = new TextFormatter<>(new IntegerStringConverter(), 9, - c -> { - if (Pattern.matches("\\d*", c.getText())) { - return c; - } - c.setText("0"); - return c; - }); + // The fontSizeFormatter formats the input given to the fontSize spinner so that non + // valid values cannot be entered. + private final TextFormatter fontSizeFormatter = new TextFormatter<>(new IntegerStringConverter(), 9, c -> { + if (Pattern.matches("\\d*", c.getText())) { + return c; + } + c.setText("0"); + return c; + }); public GeneralTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -89,9 +137,7 @@ public String getTabName() { public void initialize() { this.viewModel = new GeneralTabViewModel(dialogService, preferences, fileUpdateMonitor); - new ViewModelListCellFactory() - .withText(Language::getDisplayName) - .install(language); + new ViewModelListCellFactory().withText(Language::getDisplayName).install(language); language.itemsProperty().bind(viewModel.languagesListProperty()); language.valueProperty().bindBidirectional(viewModel.selectedLanguageProperty()); @@ -104,9 +150,7 @@ public void initialize() { fontSize.getEditor().textProperty().bindBidirectional(viewModel.fontSizeProperty()); fontSize.getEditor().setTextFormatter(fontSizeFormatter); - new ViewModelListCellFactory() - .withText(ThemeTypes::getDisplayName) - .install(theme); + new ViewModelListCellFactory().withText(ThemeTypes::getDisplayName).install(theme); theme.itemsProperty().bind(viewModel.themesListProperty()); theme.valueProperty().bindBidirectional(viewModel.selectedThemeProperty()); themeSyncOs.selectedProperty().bindBidirectional(viewModel.themeSyncOsProperty()); @@ -123,20 +167,24 @@ public void initialize() { showAdvancedHints.selectedProperty().bindBidirectional(viewModel.showAdvancedHintsProperty()); inspectionWarningDuplicate.selectedProperty().bindBidirectional(viewModel.inspectionWarningDuplicateProperty()); confirmDelete.selectedProperty().bindBidirectional(viewModel.confirmDeleteProperty()); - shouldAskForIncludingCrossReferences.selectedProperty().bindBidirectional(viewModel.shouldAskForIncludingCrossReferences()); + shouldAskForIncludingCrossReferences.selectedProperty() + .bindBidirectional(viewModel.shouldAskForIncludingCrossReferences()); confirmHideTabBar.selectedProperty().bindBidirectional(viewModel.confirmHideTabBarProperty()); - new ViewModelListCellFactory() - .withText(BibDatabaseMode::getFormattedName) - .install(biblatexMode); + new ViewModelListCellFactory().withText(BibDatabaseMode::getFormattedName) + .install(biblatexMode); biblatexMode.itemsProperty().bind(viewModel.biblatexModeListProperty()); biblatexMode.valueProperty().bindBidirectional(viewModel.selectedBiblatexModeProperty()); alwaysReformatBib.selectedProperty().bindBidirectional(viewModel.alwaysReformatBibProperty()); autosaveLocalLibraries.selectedProperty().bindBidirectional(viewModel.autosaveLocalLibrariesProperty()); ActionFactory actionFactory = new ActionFactory(); - actionFactory.configureIconButton(StandardActions.HELP, new HelpAction(HelpFile.AUTOSAVE, dialogService, preferences.getExternalApplicationsPreferences()), autosaveLocalLibrariesHelp); - actionFactory.configureIconButton(StandardActions.HELP, new HelpAction(HelpFile.REMOTE, dialogService, preferences.getExternalApplicationsPreferences()), remoteHelp); + actionFactory.configureIconButton(StandardActions.HELP, + new HelpAction(HelpFile.AUTOSAVE, dialogService, preferences.getExternalApplicationsPreferences()), + autosaveLocalLibrariesHelp); + actionFactory.configureIconButton(StandardActions.HELP, + new HelpAction(HelpFile.REMOTE, dialogService, preferences.getExternalApplicationsPreferences()), + remoteHelp); createBackup.selectedProperty().bindBidirectional(viewModel.createBackupProperty()); backupDirectory.textProperty().bindBidirectional(viewModel.backupDirectoryProperty()); @@ -171,4 +219,5 @@ public void backupFileDirBrowse() { public void openBrowser() { viewModel.openBrowser(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java index fb753d849c8..bf7d857c678 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java @@ -57,17 +57,19 @@ public class GeneralTabViewModel implements PreferenceTabViewModel { - protected static SpinnerValueFactory fontSizeValueFactory = - new SpinnerValueFactory.IntegerSpinnerValueFactory(9, Integer.MAX_VALUE); + protected static SpinnerValueFactory fontSizeValueFactory = new SpinnerValueFactory.IntegerSpinnerValueFactory( + 9, Integer.MAX_VALUE); private static final Logger LOGGER = LoggerFactory.getLogger(GeneralTabViewModel.class); - private final ReadOnlyListProperty languagesListProperty = - new ReadOnlyListWrapper<>(FXCollections.observableArrayList(Language.getSorted())); + private final ReadOnlyListProperty languagesListProperty = new ReadOnlyListWrapper<>( + FXCollections.observableArrayList(Language.getSorted())); + private final ObjectProperty selectedLanguageProperty = new SimpleObjectProperty<>(); - private final ReadOnlyListProperty themesListProperty = - new ReadOnlyListWrapper<>(FXCollections.observableArrayList(ThemeTypes.values())); + private final ReadOnlyListProperty themesListProperty = new ReadOnlyListWrapper<>( + FXCollections.observableArrayList(ThemeTypes.values())); + private final ObjectProperty selectedThemeProperty = new SimpleObjectProperty<>(); private final BooleanProperty themeSyncOsProperty = new SimpleBooleanProperty(); @@ -76,46 +78,69 @@ public class GeneralTabViewModel implements PreferenceTabViewModel { private final StringProperty customPathToThemeProperty = new SimpleStringProperty(""); private final BooleanProperty fontOverrideProperty = new SimpleBooleanProperty(); + private final StringProperty fontSizeProperty = new SimpleStringProperty(); private final BooleanProperty openLastStartupProperty = new SimpleBooleanProperty(); + private final BooleanProperty showAdvancedHintsProperty = new SimpleBooleanProperty(); + private final BooleanProperty inspectionWarningDuplicateProperty = new SimpleBooleanProperty(); + private final BooleanProperty confirmDeleteProperty = new SimpleBooleanProperty(); + private final BooleanProperty shouldAskForIncludingCrossReferencesProperty = new SimpleBooleanProperty(); + private final BooleanProperty hideTabBarProperty = new SimpleBooleanProperty(); private final ListProperty bibliographyModeListProperty = new SimpleListProperty<>(); + private final ObjectProperty selectedBiblatexModeProperty = new SimpleObjectProperty<>(); private final BooleanProperty alwaysReformatBibProperty = new SimpleBooleanProperty(); + private final BooleanProperty autosaveLocalLibraries = new SimpleBooleanProperty(); private final BooleanProperty createBackupProperty = new SimpleBooleanProperty(); + private final StringProperty backupDirectoryProperty = new SimpleStringProperty(""); private final DialogService dialogService; + private final GuiPreferences preferences; + private final WorkspacePreferences workspacePreferences; + private final LibraryPreferences libraryPreferences; + private final FilePreferences filePreferences; + private final RemotePreferences remotePreferences; private final Validator fontSizeValidator; + private final Validator customPathToThemeValidator; private final List restartWarning = new ArrayList<>(); + private final BooleanProperty remoteServerProperty = new SimpleBooleanProperty(); + private final StringProperty remotePortProperty = new SimpleStringProperty(""); + private final Validator remotePortValidator; + private final BooleanProperty enableHttpServerProperty = new SimpleBooleanProperty(); + private final StringProperty httpPortProperty = new SimpleStringProperty(""); + private final Validator httpPortValidator; + private final TrustStoreManager trustStoreManager; private final FileUpdateMonitor fileUpdateMonitor; - public GeneralTabViewModel(DialogService dialogService, GuiPreferences preferences, FileUpdateMonitor fileUpdateMonitor) { + public GeneralTabViewModel(DialogService dialogService, GuiPreferences preferences, + FileUpdateMonitor fileUpdateMonitor) { this.dialogService = dialogService; this.preferences = preferences; this.workspacePreferences = preferences.getWorkspacePreferences(); @@ -124,53 +149,43 @@ public GeneralTabViewModel(DialogService dialogService, GuiPreferences preferenc this.remotePreferences = preferences.getRemotePreferences(); this.fileUpdateMonitor = fileUpdateMonitor; - fontSizeValidator = new FunctionBasedValidator<>( - fontSizeProperty, - input -> { - try { - return Integer.parseInt(fontSizeProperty().getValue()) > 8; - } catch (NumberFormatException ex) { - return false; - } - }, - ValidationMessage.error("%s > %s %n %n %s".formatted( - Localization.lang("General"), - Localization.lang("Font settings"), - Localization.lang("You must enter an integer value higher than 8.")))); - - customPathToThemeValidator = new FunctionBasedValidator<>( - customPathToThemeProperty, + fontSizeValidator = new FunctionBasedValidator<>(fontSizeProperty, input -> { + try { + return Integer.parseInt(fontSizeProperty().getValue()) > 8; + } + catch (NumberFormatException ex) { + return false; + } + }, ValidationMessage + .error("%s > %s %n %n %s".formatted(Localization.lang("General"), Localization.lang("Font settings"), + Localization.lang("You must enter an integer value higher than 8.")))); + + customPathToThemeValidator = new FunctionBasedValidator<>(customPathToThemeProperty, input -> !StringUtil.isNullOrEmpty(input), - ValidationMessage.error("%s > %s %n %n %s".formatted( - Localization.lang("General"), - Localization.lang("Visual theme"), - Localization.lang("Please specify a css theme file.")))); - - remotePortValidator = new FunctionBasedValidator<>( - remotePortProperty, - input -> { - try { - int portNumber = Integer.parseInt(remotePortProperty().getValue()); - return RemoteUtil.isUserPort(portNumber); - } catch (NumberFormatException ex) { - return false; - } - }, - ValidationMessage.error("%s > %s %n %n %s".formatted( - Localization.lang("Network"), - Localization.lang("Remote operation"), - Localization.lang("You must enter an integer value in the interval 1025-65535")))); - httpPortValidator = new FunctionBasedValidator<>( - httpPortProperty, - input -> { - try { - int portNumber = Integer.parseInt(httpPortProperty().getValue()); - return RemoteUtil.isUserPort(portNumber); - } catch (NumberFormatException ex) { - return false; - } - }, - ValidationMessage.error("%s".formatted(Localization.lang("You must enter an integer value in the interval 1025-65535")))); + ValidationMessage.error("%s > %s %n %n %s".formatted(Localization.lang("General"), + Localization.lang("Visual theme"), Localization.lang("Please specify a css theme file.")))); + + remotePortValidator = new FunctionBasedValidator<>(remotePortProperty, input -> { + try { + int portNumber = Integer.parseInt(remotePortProperty().getValue()); + return RemoteUtil.isUserPort(portNumber); + } + catch (NumberFormatException ex) { + return false; + } + }, ValidationMessage + .error("%s > %s %n %n %s".formatted(Localization.lang("Network"), Localization.lang("Remote operation"), + Localization.lang("You must enter an integer value in the interval 1025-65535")))); + httpPortValidator = new FunctionBasedValidator<>(httpPortProperty, input -> { + try { + int portNumber = Integer.parseInt(httpPortProperty().getValue()); + return RemoteUtil.isUserPort(portNumber); + } + catch (NumberFormatException ex) { + return false; + } + }, ValidationMessage + .error("%s".formatted(Localization.lang("You must enter an integer value in the interval 1025-65535")))); this.trustStoreManager = new TrustStoreManager(Path.of(preferences.getSSLPreferences().getTruststorePath())); } @@ -186,13 +201,13 @@ public ValidationStatus httpPortValidationStatus() { public void setValues() { selectedLanguageProperty.setValue(workspacePreferences.getLanguage()); - // The light theme is in fact the absence of any theme modifying 'base.css'. Another embedded theme like - // 'dark.css', stored in the classpath, can be introduced in {@link org.jabref.gui.theme.Theme}. + // The light theme is in fact the absence of any theme modifying 'base.css'. + // Another embedded theme like + // 'dark.css', stored in the classpath, can be introduced in {@link + // org.jabref.gui.theme.Theme}. switch (workspacePreferences.getTheme().getType()) { - case DEFAULT -> - selectedThemeProperty.setValue(ThemeTypes.LIGHT); - case EMBEDDED -> - selectedThemeProperty.setValue(ThemeTypes.DARK); + case DEFAULT -> selectedThemeProperty.setValue(ThemeTypes.LIGHT); + case EMBEDDED -> selectedThemeProperty.setValue(ThemeTypes.DARK); case CUSTOM -> { selectedThemeProperty.setValue(ThemeTypes.CUSTOM); customPathToThemeProperty.setValue(workspacePreferences.getTheme().getName()); @@ -208,7 +223,8 @@ public void setValues() { inspectionWarningDuplicateProperty.setValue(workspacePreferences.shouldWarnAboutDuplicatesInInspection()); confirmDeleteProperty.setValue(workspacePreferences.shouldConfirmDelete()); - shouldAskForIncludingCrossReferencesProperty.setValue(preferences.getCopyToPreferences().getShouldAskForIncludingCrossReferences()); + shouldAskForIncludingCrossReferencesProperty + .setValue(preferences.getCopyToPreferences().getShouldAskForIncludingCrossReferences()); hideTabBarProperty.setValue(workspacePreferences.shouldHideTabBar()); bibliographyModeListProperty.setValue(FXCollections.observableArrayList(BibDatabaseMode.values())); @@ -240,12 +256,9 @@ public void storeSettings() { workspacePreferences.setMainFontSize(Integer.parseInt(fontSizeProperty.getValue())); switch (selectedThemeProperty.get()) { - case LIGHT -> - workspacePreferences.setTheme(Theme.light()); - case DARK -> - workspacePreferences.setTheme(Theme.dark()); - case CUSTOM -> - workspacePreferences.setTheme(Theme.custom(customPathToThemeProperty.getValue())); + case LIGHT -> workspacePreferences.setTheme(Theme.light()); + case DARK -> workspacePreferences.setTheme(Theme.dark()); + case CUSTOM -> workspacePreferences.setTheme(Theme.custom(customPathToThemeProperty.getValue())); } workspacePreferences.setThemeSyncOs(themeSyncOsProperty.getValue()); @@ -254,7 +267,8 @@ public void storeSettings() { workspacePreferences.setWarnAboutDuplicatesInInspection(inspectionWarningDuplicateProperty.getValue()); workspacePreferences.setConfirmDelete(confirmDeleteProperty.getValue()); - preferences.getCopyToPreferences().setShouldAskForIncludingCrossReferences(shouldAskForIncludingCrossReferencesProperty.getValue()); + preferences.getCopyToPreferences() + .setShouldAskForIncludingCrossReferences(shouldAskForIncludingCrossReferencesProperty.getValue()); workspacePreferences.setHideTabBar(confirmHideTabBarProperty().getValue()); libraryPreferences.setDefaultBibDatabaseMode(selectedBiblatexModeProperty.getValue()); @@ -278,15 +292,16 @@ public void storeSettings() { }); UiMessageHandler uiMessageHandler = Injector.instantiateModelOrService(UiMessageHandler.class); - RemoteListenerServerManager remoteListenerServerManager = Injector.instantiateModelOrService(RemoteListenerServerManager.class); + RemoteListenerServerManager remoteListenerServerManager = Injector + .instantiateModelOrService(RemoteListenerServerManager.class); // stop in all cases, because the port might have changed remoteListenerServerManager.stop(); if (remoteServerProperty.getValue()) { remotePreferences.setUseRemoteServer(true); - remoteListenerServerManager.openAndStart( - new CLIMessageHandler(uiMessageHandler, preferences), + remoteListenerServerManager.openAndStart(new CLIMessageHandler(uiMessageHandler, preferences), remotePreferences.getPort()); - } else { + } + else { remotePreferences.setUseRemoteServer(false); remoteListenerServerManager.stop(); } @@ -304,7 +319,8 @@ public void storeSettings() { remotePreferences.setEnableHttpServer(true); URI uri = remotePreferences.getHttpServerUri(); httpServerManager.start(Injector.instantiateModelOrService(StateManager.class), uri); - } else { + } + else { remotePreferences.setEnableHttpServer(false); httpServerManager.stop(); } @@ -342,8 +358,8 @@ public boolean validateSettings() { ValidationStatus validationStatus = validator.getValidationStatus(); if (!validationStatus.isValid()) { - validationStatus.getHighestMessage().ifPresent(message -> - dialogService.showErrorDialogAndWait(message.getMessage())); + validationStatus.getHighestMessage() + .ifPresent(message -> dialogService.showErrorDialogAndWait(message.getMessage())); return false; } return true; @@ -379,16 +395,18 @@ public StringProperty customPathToThemeProperty() { } public void importCSSFile() { - String fileDir = customPathToThemeProperty.getValue().isEmpty() ? preferences.getInternalPreferences().getLastPreferencesExportPath().toString() + String fileDir = customPathToThemeProperty.getValue().isEmpty() + ? preferences.getInternalPreferences().getLastPreferencesExportPath().toString() : customPathToThemeProperty.getValue(); FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.CSS) - .withDefaultExtension(StandardFileType.CSS) - .withInitialDirectory(fileDir).build(); + .addExtensionFilter(StandardFileType.CSS) + .withDefaultExtension(StandardFileType.CSS) + .withInitialDirectory(fileDir) + .build(); - dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(file -> - customPathToThemeProperty.setValue(file.toAbsolutePath().toString())); + dialogService.showFileOpenDialog(fileDialogConfiguration) + .ifPresent(file -> customPathToThemeProperty.setValue(file.toAbsolutePath().toString())); } public BooleanProperty fontOverrideProperty() { @@ -448,10 +466,11 @@ public StringProperty backupDirectoryProperty() { } public void backupFileDirBrowse() { - DirectoryDialogConfiguration dirDialogConfiguration = - new DirectoryDialogConfiguration.Builder().withInitialDirectory(Path.of(backupDirectoryProperty().getValue())).build(); + DirectoryDialogConfiguration dirDialogConfiguration = new DirectoryDialogConfiguration.Builder() + .withInitialDirectory(Path.of(backupDirectoryProperty().getValue())) + .build(); dialogService.showDirectorySelectionDialog(dirDialogConfiguration) - .ifPresent(dir -> backupDirectoryProperty.setValue(dir.toString())); + .ifPresent(dir -> backupDirectoryProperty.setValue(dir.toString())); } public BooleanProperty remoteServerProperty() { @@ -474,7 +493,8 @@ public void openBrowser() { String url = "https://themes.jabref.org"; try { NativeDesktop.openBrowser(url, preferences.getExternalApplicationsPreferences()); - } catch (IOException e) { + } + catch (IOException e) { dialogService.showErrorDialogAndWait(Localization.lang("Could not open website."), e); } } @@ -482,8 +502,10 @@ public void openBrowser() { private Optional getPortAsInt(String value) { try { return Optional.of(Integer.parseInt(value)); - } catch (NumberFormatException ex) { + } + catch (NumberFormatException ex) { return Optional.empty(); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/groups/GroupsTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/groups/GroupsTab.java index cf6ada3c627..4fe44188b4e 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/groups/GroupsTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/groups/GroupsTab.java @@ -12,15 +12,20 @@ public class GroupsTab extends AbstractPreferenceTabView implements PreferencesTab { - @FXML private RadioButton groupViewModeIntersection; - @FXML private RadioButton groupViewModeUnion; - @FXML private CheckBox autoAssignGroup; - @FXML private CheckBox displayGroupCount; + @FXML + private RadioButton groupViewModeIntersection; + + @FXML + private RadioButton groupViewModeUnion; + + @FXML + private CheckBox autoAssignGroup; + + @FXML + private CheckBox displayGroupCount; public GroupsTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -36,4 +41,5 @@ public void initialize() { autoAssignGroup.selectedProperty().bindBidirectional(viewModel.autoAssignGroupProperty()); displayGroupCount.selectedProperty().bindBidirectional(viewModel.displayGroupCount()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/groups/GroupsTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/groups/GroupsTabViewModel.java index 0d1d095108f..51946aeffd9 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/groups/GroupsTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/groups/GroupsTabViewModel.java @@ -10,8 +10,11 @@ public class GroupsTabViewModel implements PreferenceTabViewModel { private final BooleanProperty groupViewModeIntersectionProperty = new SimpleBooleanProperty(); + private final BooleanProperty groupViewModeUnionProperty = new SimpleBooleanProperty(); + private final BooleanProperty autoAssignGroupProperty = new SimpleBooleanProperty(); + private final BooleanProperty displayGroupCountProperty = new SimpleBooleanProperty(); private final GroupsPreferences groupsPreferences; @@ -25,7 +28,8 @@ public void setValues() { if (groupsPreferences.getGroupViewMode().contains(GroupViewMode.INTERSECTION)) { groupViewModeIntersectionProperty.setValue(true); groupViewModeUnionProperty.setValue(false); - } else { + } + else { groupViewModeIntersectionProperty.setValue(false); groupViewModeUnionProperty.setValue(true); } @@ -55,4 +59,5 @@ public BooleanProperty autoAssignGroupProperty() { public BooleanProperty displayGroupCount() { return displayGroupCountProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/journals/AbbreviationViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/journals/AbbreviationViewModel.java index 1c13439cf6b..b6e795e3a71 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/journals/AbbreviationViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/journals/AbbreviationViewModel.java @@ -11,13 +11,16 @@ import org.jabref.logic.journals.Abbreviation; /** - * This class provides a view model for abbreviation objects which can also define placeholder objects of abbreviations. - * This is indicated by using the {@code pseudoAbbreviation} property. + * This class provides a view model for abbreviation objects which can also define + * placeholder objects of abbreviations. This is indicated by using the + * {@code pseudoAbbreviation} property. */ public class AbbreviationViewModel { private final StringProperty name = new SimpleStringProperty(""); + private final StringProperty abbreviation = new SimpleStringProperty(""); + private final StringProperty shortestUniqueAbbreviation = new SimpleStringProperty(""); // Used when a "null" abbreviation object is added @@ -31,10 +34,12 @@ public AbbreviationViewModel(Abbreviation abbreviationObject) { // the view model stores the "real" values, not the default fallback String shortestUniqueAbbreviationOfAbbreviation = abbreviationObject.getShortestUniqueAbbreviation(); - boolean shortestUniqueAbbreviationIsDefaultValue = shortestUniqueAbbreviationOfAbbreviation.equals(abbreviationObject.getAbbreviation()); + boolean shortestUniqueAbbreviationIsDefaultValue = shortestUniqueAbbreviationOfAbbreviation + .equals(abbreviationObject.getAbbreviation()); if (shortestUniqueAbbreviationIsDefaultValue) { this.shortestUniqueAbbreviation.set(""); - } else { + } + else { this.shortestUniqueAbbreviation.setValue(shortestUniqueAbbreviationOfAbbreviation); } } @@ -97,8 +102,7 @@ public boolean equals(Object o) { return false; } AbbreviationViewModel that = (AbbreviationViewModel) o; - return getName().equals(that.getName()) - && getAbbreviation().equals(that.getAbbreviation()) + return getName().equals(that.getName()) && getAbbreviation().equals(that.getAbbreviation()) && getShortestUniqueAbbreviation().equals(that.getShortestUniqueAbbreviation()) && (isPseudoAbbreviation() == that.isPseudoAbbreviation()); } @@ -110,8 +114,9 @@ public int hashCode() { public boolean containsCaseIndependent(String searchTerm) { searchTerm = searchTerm.toLowerCase(Locale.ROOT); - return this.abbreviation.get().toLowerCase(Locale.ROOT).contains(searchTerm) || - this.name.get().toLowerCase(Locale.ROOT).contains(searchTerm) || - this.shortestUniqueAbbreviation.get().toLowerCase(Locale.ROOT).contains(searchTerm); + return this.abbreviation.get().toLowerCase(Locale.ROOT).contains(searchTerm) + || this.name.get().toLowerCase(Locale.ROOT).contains(searchTerm) + || this.shortestUniqueAbbreviation.get().toLowerCase(Locale.ROOT).contains(searchTerm); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/journals/AbbreviationsFileViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/journals/AbbreviationsFileViewModel.java index 88ecb96d240..dde46656946 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/journals/AbbreviationsFileViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/journals/AbbreviationsFileViewModel.java @@ -20,15 +20,19 @@ import org.jabref.logic.journals.JournalAbbreviationLoader; /** - * This class provides a model for abbreviation files. It actually doesn't save the files as objects but rather saves - * their paths. This also allows to specify pseudo files as placeholder objects. + * This class provides a model for abbreviation files. It actually doesn't save the files + * as objects but rather saves their paths. This also allows to specify pseudo files as + * placeholder objects. */ public class AbbreviationsFileViewModel { private final SimpleListProperty abbreviations = new SimpleListProperty<>( FXCollections.observableArrayList()); + private final ReadOnlyBooleanProperty isBuiltInList; + private final String name; + private final Optional path; public AbbreviationsFileViewModel(Path filePath) { @@ -38,9 +42,10 @@ public AbbreviationsFileViewModel(Path filePath) { } /** - * This constructor should only be called to create a pseudo abbreviation file for built in lists. This means it is - * a placeholder and its path will be null meaning it has no place on the filesystem. Its isPseudoFile property - * will therefore be set to true. + * This constructor should only be called to create a pseudo abbreviation file for + * built in lists. This means it is a placeholder and its path will be null meaning it + * has no place on the filesystem. Its isPseudoFile property will therefore be set to + * true. */ public AbbreviationsFileViewModel(List abbreviations, String name) { this.abbreviations.addAll(abbreviations); @@ -51,24 +56,26 @@ public AbbreviationsFileViewModel(List abbreviations, Str public void readAbbreviations() throws IOException { if (path.isPresent()) { - Collection abbreviationList = JournalAbbreviationLoader.readAbbreviationsFromCsvFile(path.get()); + Collection abbreviationList = JournalAbbreviationLoader + .readAbbreviationsFromCsvFile(path.get()); abbreviationList.forEach(abbreviation -> abbreviations.addAll(new AbbreviationViewModel(abbreviation))); - } else { + } + else { throw new FileNotFoundException(); } } /** - * This method will write all abbreviations of this abbreviation file to the file on the file system. - * It essentially will check if the current file is a builtin list and if not it will call - * {@link AbbreviationWriter#writeOrCreate}. + * This method will write all abbreviations of this abbreviation file to the file on + * the file system. It essentially will check if the current file is a builtin list + * and if not it will call {@link AbbreviationWriter#writeOrCreate}. */ public void writeOrCreate() throws IOException { if (!isBuiltInList.get()) { - List actualAbbreviations = - abbreviations.stream().filter(abb -> !abb.isPseudoAbbreviation()) - .map(AbbreviationViewModel::getAbbreviationObject) - .collect(Collectors.toList()); + List actualAbbreviations = abbreviations.stream() + .filter(abb -> !abb.isPseudoAbbreviation()) + .map(AbbreviationViewModel::getAbbreviationObject) + .collect(Collectors.toList()); AbbreviationWriter.writeOrCreate(path.get(), actualAbbreviations); } } @@ -103,8 +110,10 @@ public int hashCode() { public boolean equals(Object obj) { if (obj instanceof AbbreviationsFileViewModel model) { return Objects.equals(this.name, model.name); - } else { + } + else { return false; } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java index 5bfa504edf5..2c8389754f3 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java @@ -37,47 +37,66 @@ import org.controlsfx.control.textfield.CustomTextField; /** - * This class controls the user interface of the journal abbreviations dialog. The UI elements and their layout are - * defined in the FXML file. + * This class controls the user interface of the journal abbreviations dialog. The UI + * elements and their layout are defined in the FXML file. */ -public class JournalAbbreviationsTab extends AbstractPreferenceTabView implements PreferencesTab { +public class JournalAbbreviationsTab extends AbstractPreferenceTabView + implements PreferencesTab { - @FXML private Label loadingLabel; - @FXML private ProgressIndicator progressIndicator; + @FXML + private Label loadingLabel; + + @FXML + private ProgressIndicator progressIndicator; + + @FXML + private TableView journalAbbreviationsTable; + + @FXML + private TableColumn journalTableNameColumn; + + @FXML + private TableColumn journalTableAbbreviationColumn; - @FXML private TableView journalAbbreviationsTable; - @FXML private TableColumn journalTableNameColumn; - @FXML private TableColumn journalTableAbbreviationColumn; - @FXML private TableColumn journalTableShortestUniqueAbbreviationColumn; - @FXML private TableColumn actionsColumn; + @FXML + private TableColumn journalTableShortestUniqueAbbreviationColumn; + + @FXML + private TableColumn actionsColumn; private FilteredList filteredAbbreviations; - @FXML private ComboBox journalFilesBox; - @FXML private Button addAbbreviationButton; - @FXML private Button removeAbbreviationListButton; + @FXML + private ComboBox journalFilesBox; + + @FXML + private Button addAbbreviationButton; - @FXML private CustomTextField searchBox; - @FXML private CheckBox useFJournal; + @FXML + private Button removeAbbreviationListButton; - @Inject private TaskExecutor taskExecutor; - @Inject private JournalAbbreviationRepository abbreviationRepository; + @FXML + private CustomTextField searchBox; + + @FXML + private CheckBox useFJournal; + + @Inject + private TaskExecutor taskExecutor; + + @Inject + private JournalAbbreviationRepository abbreviationRepository; private Timeline invalidateSearch; public JournalAbbreviationsTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @FXML private void initialize() { - viewModel = new JournalAbbreviationsTabViewModel( - preferences.getJournalAbbreviationPreferences(), - dialogService, - taskExecutor, - abbreviationRepository); + viewModel = new JournalAbbreviationsTabViewModel(preferences.getJournalAbbreviationPreferences(), dialogService, + taskExecutor, abbreviationRepository); filteredAbbreviations = new FilteredList<>(viewModel.abbreviationsProperty()); @@ -96,31 +115,33 @@ private void setUpTable() { journalTableAbbreviationColumn.setCellValueFactory(cellData -> cellData.getValue().abbreviationProperty()); journalTableAbbreviationColumn.setCellFactory(TextFieldTableCell.forTableColumn()); - journalTableShortestUniqueAbbreviationColumn.setCellValueFactory(cellData -> cellData.getValue().shortestUniqueAbbreviationProperty()); + journalTableShortestUniqueAbbreviationColumn + .setCellValueFactory(cellData -> cellData.getValue().shortestUniqueAbbreviationProperty()); journalTableShortestUniqueAbbreviationColumn.setCellFactory(TextFieldTableCell.forTableColumn()); actionsColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); new ValueTableCellFactory() - .withGraphic(name -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) - .withTooltip(name -> Localization.lang("Remove journal '%0'", name)) - .withDisableExpression(item -> viewModel.isEditableAndRemovableProperty().not()) - .withVisibleExpression(item -> viewModel.isEditableAndRemovableProperty()) - .withOnMouseClickedEvent(item -> evt -> - viewModel.removeAbbreviation(journalAbbreviationsTable.getFocusModel().getFocusedItem())) - .install(actionsColumn); + .withGraphic(name -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) + .withTooltip(name -> Localization.lang("Remove journal '%0'", name)) + .withDisableExpression(item -> viewModel.isEditableAndRemovableProperty().not()) + .withVisibleExpression(item -> viewModel.isEditableAndRemovableProperty()) + .withOnMouseClickedEvent(item -> evt -> viewModel + .removeAbbreviation(journalAbbreviationsTable.getFocusModel().getFocusedItem())) + .install(actionsColumn); } private void setBindings() { journalAbbreviationsTable.setItems(filteredAbbreviations); - EasyBind.subscribe(journalAbbreviationsTable.getSelectionModel().selectedItemProperty(), newValue -> - viewModel.currentAbbreviationProperty().set(newValue)); - EasyBind.subscribe(viewModel.currentAbbreviationProperty(), newValue -> - journalAbbreviationsTable.getSelectionModel().select(newValue)); + EasyBind.subscribe(journalAbbreviationsTable.getSelectionModel().selectedItemProperty(), + newValue -> viewModel.currentAbbreviationProperty().set(newValue)); + EasyBind.subscribe(viewModel.currentAbbreviationProperty(), + newValue -> journalAbbreviationsTable.getSelectionModel().select(newValue)); journalTableNameColumn.editableProperty().bind(viewModel.isAbbreviationEditableAndRemovable()); journalTableAbbreviationColumn.editableProperty().bind(viewModel.isAbbreviationEditableAndRemovable()); - journalTableShortestUniqueAbbreviationColumn.editableProperty().bind(viewModel.isAbbreviationEditableAndRemovable()); + journalTableShortestUniqueAbbreviationColumn.editableProperty() + .bind(viewModel.isAbbreviationEditableAndRemovable()); removeAbbreviationListButton.disableProperty().bind(viewModel.isFileRemovableProperty().not()); journalFilesBox.itemsProperty().bindBidirectional(viewModel.journalFilesProperty()); @@ -131,8 +152,9 @@ private void setBindings() { loadingLabel.visibleProperty().bind(viewModel.isLoadingProperty()); progressIndicator.visibleProperty().bind(viewModel.isLoadingProperty()); - searchBox.textProperty().addListener((observable, previousText, searchTerm) -> - filteredAbbreviations.setPredicate(abbreviation -> searchTerm.isEmpty() || abbreviation.containsCaseIndependent(searchTerm))); + searchBox.textProperty() + .addListener((observable, previousText, searchTerm) -> filteredAbbreviations.setPredicate( + abbreviation -> searchTerm.isEmpty() || abbreviation.containsCaseIndependent(searchTerm))); useFJournal.selectedProperty().bindBidirectional(viewModel.useFJournalProperty()); } @@ -141,16 +163,15 @@ private void setAnimations() { ObjectProperty flashingColor = new SimpleObjectProperty<>(Color.TRANSPARENT); StringProperty flashingColorStringProperty = ColorUtil.createFlashingColorStringProperty(flashingColor); - searchBox.styleProperty().bind( - new SimpleStringProperty("-fx-control-inner-background: ").concat(flashingColorStringProperty).concat(";") - ); + searchBox.styleProperty() + .bind(new SimpleStringProperty("-fx-control-inner-background: ").concat(flashingColorStringProperty) + .concat(";")); invalidateSearch = new Timeline( new KeyFrame(Duration.seconds(0), new KeyValue(flashingColor, Color.TRANSPARENT, Interpolator.LINEAR)), new KeyFrame(Duration.seconds(0.25), new KeyValue(flashingColor, Color.RED, Interpolator.LINEAR)), new KeyFrame(Duration.seconds(0.25), new KeyValue(searchBox.textProperty(), "", Interpolator.DISCRETE)), - new KeyFrame(Duration.seconds(0.25), (ActionEvent event) -> addAbbreviationActions()), - new KeyFrame(Duration.seconds(0.5), new KeyValue(flashingColor, Color.TRANSPARENT, Interpolator.LINEAR)) - ); + new KeyFrame(Duration.seconds(0.25), (ActionEvent event) -> addAbbreviationActions()), new KeyFrame( + Duration.seconds(0.5), new KeyValue(flashingColor, Color.TRANSPARENT, Interpolator.LINEAR))); } @FXML @@ -172,7 +193,8 @@ private void removeList() { private void addAbbreviation() { if (!searchBox.getText().isEmpty()) { invalidateSearch.play(); - } else { + } + else { addAbbreviationActions(); } } @@ -185,8 +207,7 @@ private void addAbbreviationActions() { @FXML private void editAbbreviation() { - journalAbbreviationsTable.edit( - journalAbbreviationsTable.getSelectionModel().getSelectedIndex(), + journalAbbreviationsTable.edit(journalAbbreviationsTable.getSelectionModel().getSelectedIndex(), journalTableNameColumn); } @@ -201,4 +222,5 @@ private void selectNewAbbreviation() { public String getTabName() { return Localization.lang("Journal abbreviations"); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTabViewModel.java index 7b95ac28244..962ebf5a91e 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTabViewModel.java @@ -30,37 +30,49 @@ import org.slf4j.LoggerFactory; /** - * This class provides a model for managing journal abbreviation lists. It provides all necessary methods to create, - * modify or delete journal abbreviations and files. To visualize the model one can bind the properties to UI elements. + * This class provides a model for managing journal abbreviation lists. It provides all + * necessary methods to create, modify or delete journal abbreviations and files. To + * visualize the model one can bind the properties to UI elements. */ public class JournalAbbreviationsTabViewModel implements PreferenceTabViewModel { private final Logger LOGGER = LoggerFactory.getLogger(JournalAbbreviationsTabViewModel.class); - private final SimpleListProperty journalFiles = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final SimpleListProperty abbreviations = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final SimpleListProperty journalFiles = new SimpleListProperty<>( + FXCollections.observableArrayList()); + + private final SimpleListProperty abbreviations = new SimpleListProperty<>( + FXCollections.observableArrayList()); + private final SimpleIntegerProperty abbreviationsCount = new SimpleIntegerProperty(); private final SimpleObjectProperty currentFile = new SimpleObjectProperty<>(); + private final SimpleObjectProperty currentAbbreviation = new SimpleObjectProperty<>(); private final SimpleBooleanProperty isFileRemovable = new SimpleBooleanProperty(); + private final SimpleBooleanProperty isLoading = new SimpleBooleanProperty(false); + private final SimpleBooleanProperty isEditableAndRemovable = new SimpleBooleanProperty(false); + private final SimpleBooleanProperty isAbbreviationEditableAndRemovable = new SimpleBooleanProperty(false); + private final SimpleBooleanProperty useFJournal = new SimpleBooleanProperty(true); private final DialogService dialogService; + private final TaskExecutor taskExecutor; private final JournalAbbreviationPreferences abbreviationsPreferences; + private final JournalAbbreviationRepository journalAbbreviationRepository; + private boolean shouldWriteLists; public JournalAbbreviationsTabViewModel(JournalAbbreviationPreferences abbreviationsPreferences, - DialogService dialogService, - TaskExecutor taskExecutor, - JournalAbbreviationRepository journalAbbreviationRepository) { + DialogService dialogService, TaskExecutor taskExecutor, + JournalAbbreviationRepository journalAbbreviationRepository) { this.dialogService = Objects.requireNonNull(dialogService); this.taskExecutor = Objects.requireNonNull(taskExecutor); this.journalAbbreviationRepository = Objects.requireNonNull(journalAbbreviationRepository); @@ -84,12 +96,14 @@ public JournalAbbreviationsTabViewModel(JournalAbbreviationPreferences abbreviat if (!abbreviations.isEmpty()) { currentAbbreviation.set(abbreviations.getLast()); } - } else { + } + else { isFileRemovable.set(false); if (journalFiles.isEmpty()) { currentAbbreviation.set(null); abbreviations.clear(); - } else { + } + else { currentFile.set(journalFiles.getFirst()); } } @@ -123,9 +137,11 @@ public void createFileObjects() { } /** - * This will set the {@code currentFile} property to the {@link AbbreviationsFileViewModel} object that was added to - * the {@code journalFiles} list property lastly. If there are no files in the list property this method will do - * nothing as the {@code currentFile} property is already {@code null}. + * This will set the {@code currentFile} property to the + * {@link AbbreviationsFileViewModel} object that was added to the + * {@code journalFiles} list property lastly. If there are no files in the list + * property this method will do nothing as the {@code currentFile} property is already + * {@code null}. */ public void selectLastJournalFile() { if (!journalFiles.isEmpty()) { @@ -134,41 +150,42 @@ public void selectLastJournalFile() { } /** - * This will load the built in abbreviation files and add it to the list of journal abbreviation files. + * This will load the built in abbreviation files and add it to the list of journal + * abbreviation files. */ public void addBuiltInList() { - BackgroundTask - .wrap(journalAbbreviationRepository::getAllLoaded) - .onRunning(() -> isLoading.setValue(true)) - .onSuccess(result -> { - isLoading.setValue(false); - List builtInViewModels = result.stream() - .map(AbbreviationViewModel::new) - .collect(Collectors.toList()); - journalFiles.add(new AbbreviationsFileViewModel(builtInViewModels, Localization.lang("JabRef built in list"))); - selectLastJournalFile(); - }) - .onFailure(dialogService::showErrorDialogAndWait) - .executeWith(taskExecutor); + BackgroundTask.wrap(journalAbbreviationRepository::getAllLoaded) + .onRunning(() -> isLoading.setValue(true)) + .onSuccess(result -> { + isLoading.setValue(false); + List builtInViewModels = result.stream() + .map(AbbreviationViewModel::new) + .collect(Collectors.toList()); + journalFiles + .add(new AbbreviationsFileViewModel(builtInViewModels, Localization.lang("JabRef built in list"))); + selectLastJournalFile(); + }) + .onFailure(dialogService::showErrorDialogAndWait) + .executeWith(taskExecutor); } /** - * This method shall be used to add a new journal abbreviation file to the set of journal abbreviation files. It - * basically just calls the {@link #openFile(Path)}} method. + * This method shall be used to add a new journal abbreviation file to the set of + * journal abbreviation files. It basically just calls the {@link #openFile(Path)}} + * method. */ public void addNewFile() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.CSV) - .build(); + .addExtensionFilter(StandardFileType.CSV) + .build(); dialogService.showFileSaveDialog(fileDialogConfiguration).ifPresent(this::openFile); } /** - * Checks whether the file exists or if a new file should be opened. The file will be added to the set of journal - * abbreviation files. If the file also exists its abbreviations will be read and written to the abbreviations - * property. - * + * Checks whether the file exists or if a new file should be opened. The file will be + * added to the set of journal abbreviation files. If the file also exists its + * abbreviations will be read and written to the abbreviations property. * @param filePath path to the file */ private void openFile(Path filePath) { @@ -181,7 +198,8 @@ private void openFile(Path filePath) { if (abbreviationsFile.exists()) { try { abbreviationsFile.readAbbreviations(); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.debug("Could not read abbreviations file", e); } } @@ -190,17 +208,19 @@ private void openFile(Path filePath) { public void openFile() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.CSV) - .build(); + .addExtensionFilter(StandardFileType.CSV) + .build(); dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(this::openFile); } /** - * This method removes the currently selected file from the set of journal abbreviation files. This will not remove - * existing files from the file system. The {@code activeFile} property will always change to the previous file in - * the {@code journalFiles} list property, except the first file is selected. If so the next file will be selected - * except if there are no more files than the {@code activeFile} property will be set to {@code null}. + * This method removes the currently selected file from the set of journal + * abbreviation files. This will not remove existing files from the file system. The + * {@code activeFile} property will always change to the previous file in the + * {@code journalFiles} list property, except the first file is selected. If so the + * next file will be selected except if there are no more files than the + * {@code activeFile} property will be set to {@code null}. */ public void removeCurrentFile() { if (isFileRemovable.get()) { @@ -212,15 +232,17 @@ public void removeCurrentFile() { } /** - * Method to add a new abbreviation to the abbreviations list property. It also sets the currentAbbreviation - * property to the new abbreviation. + * Method to add a new abbreviation to the abbreviations list property. It also sets + * the currentAbbreviation property to the new abbreviation. */ public void addAbbreviation(Abbreviation abbreviationObject) { AbbreviationViewModel abbreviationViewModel = new AbbreviationViewModel(abbreviationObject); if (abbreviations.contains(abbreviationViewModel)) { dialogService.showErrorDialogAndWait(Localization.lang("Duplicated Journal Abbreviation"), - Localization.lang("Abbreviation '%0' for journal '%1' already defined.", abbreviationObject.getAbbreviation(), abbreviationObject.getName())); - } else { + Localization.lang("Abbreviation '%0' for journal '%1' already defined.", + abbreviationObject.getAbbreviation(), abbreviationObject.getName())); + } + else { abbreviations.add(abbreviationViewModel); currentAbbreviation.set(abbreviationViewModel); shouldWriteLists = true; @@ -228,9 +250,7 @@ public void addAbbreviation(Abbreviation abbreviationObject) { } public void addAbbreviation() { - addAbbreviation(new Abbreviation( - Localization.lang("Name"), - Localization.lang("Abbreviation"), + addAbbreviation(new Abbreviation(Localization.lang("Name"), Localization.lang("Abbreviation"), Localization.lang("Shortest unique abbreviation"))); } @@ -243,11 +263,14 @@ void editAbbreviation(Abbreviation abbreviationObject) { if (abbreviations.contains(abbViewModel)) { if (abbViewModel.equals(currentAbbreviation.get())) { setCurrentAbbreviationNameAndAbbreviationIfValid(abbreviationObject); - } else { + } + else { dialogService.showErrorDialogAndWait(Localization.lang("Duplicated Journal Abbreviation"), - Localization.lang("Abbreviation '%0' for journal '%1' already defined.", abbreviationObject.getAbbreviation(), abbreviationObject.getName())); + Localization.lang("Abbreviation '%0' for journal '%1' already defined.", + abbreviationObject.getAbbreviation(), abbreviationObject.getName())); } - } else { + } + else { setCurrentAbbreviationNameAndAbbreviationIfValid(abbreviationObject); } } @@ -267,25 +290,29 @@ private void setCurrentAbbreviationNameAndAbbreviationIfValid(Abbreviation abbre abbreviationViewModel.setAbbreviation(abbreviationObject.getAbbreviation()); if (abbreviationObject.isDefaultShortestUniqueAbbreviation()) { abbreviationViewModel.setShortestUniqueAbbreviation(""); - } else { + } + else { abbreviationViewModel.setShortestUniqueAbbreviation(abbreviationObject.getShortestUniqueAbbreviation()); } shouldWriteLists = true; } /** - * Method to delete the abbreviation set in the currentAbbreviation property. The currentAbbreviationProperty will - * be set to the previous or next abbreviation in the abbreviations property if applicable. Else it will be set to - * {@code null} if there are no abbreviations left. + * Method to delete the abbreviation set in the currentAbbreviation property. The + * currentAbbreviationProperty will be set to the previous or next abbreviation in the + * abbreviations property if applicable. Else it will be set to {@code null} if there + * are no abbreviations left. */ public void deleteAbbreviation() { if ((currentAbbreviation.get() != null) && !currentAbbreviation.get().isPseudoAbbreviation()) { int index = abbreviations.indexOf(currentAbbreviation.get()); if (index > 1) { currentAbbreviation.set(abbreviations.get(index - 1)); - } else if ((index + 1) < abbreviationsCount.get()) { + } + else if ((index + 1) < abbreviationsCount.get()) { currentAbbreviation.set(abbreviations.get(index + 1)); - } else { + } + else { currentAbbreviation.set(null); } abbreviations.remove(index); @@ -305,47 +332,47 @@ public void removeAbbreviation(AbbreviationViewModel abbreviation) { } /** - * Calls the {@link AbbreviationsFileViewModel#writeOrCreate()} method for each file in the journalFiles property - * which will overwrite the existing files with the content of the abbreviations property of the AbbreviationsFile. - * Non-existing files will be created. + * Calls the {@link AbbreviationsFileViewModel#writeOrCreate()} method for each file + * in the journalFiles property which will overwrite the existing files with the + * content of the abbreviations property of the AbbreviationsFile. Non-existing files + * will be created. */ public void saveJournalAbbreviationFiles() { journalFiles.forEach(file -> { try { file.writeOrCreate(); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.debug("Error during writing journal CSV", e); } }); } /** - * This method first saves all external files to its internal list, then writes all abbreviations to their files and - * finally updates the abbreviations auto complete. + * This method first saves all external files to its internal list, then writes all + * abbreviations to their files and finally updates the abbreviations auto complete. */ @Override public void storeSettings() { - BackgroundTask - .wrap(() -> { - List journalStringList = journalFiles.stream() - .filter(path -> !path.isBuiltInListProperty().get()) - .filter(path -> path.getAbsolutePath().isPresent()) - .map(path -> path.getAbsolutePath().get().toAbsolutePath().toString()) - .collect(Collectors.toList()); - - abbreviationsPreferences.setExternalJournalLists(journalStringList); - abbreviationsPreferences.setUseFJournalField(useFJournal.get()); - - if (shouldWriteLists) { - saveJournalAbbreviationFiles(); - shouldWriteLists = false; - } - }) - .onSuccess(success -> Injector.setModelOrService( - JournalAbbreviationRepository.class, - JournalAbbreviationLoader.loadRepository(abbreviationsPreferences))) - .onFailure(exception -> LOGGER.error("Failed to store journal preferences.", exception)) - .executeWith(taskExecutor); + BackgroundTask.wrap(() -> { + List journalStringList = journalFiles.stream() + .filter(path -> !path.isBuiltInListProperty().get()) + .filter(path -> path.getAbsolutePath().isPresent()) + .map(path -> path.getAbsolutePath().get().toAbsolutePath().toString()) + .collect(Collectors.toList()); + + abbreviationsPreferences.setExternalJournalLists(journalStringList); + abbreviationsPreferences.setUseFJournalField(useFJournal.get()); + + if (shouldWriteLists) { + saveJournalAbbreviationFiles(); + shouldWriteLists = false; + } + }) + .onSuccess(success -> Injector.setModelOrService(JournalAbbreviationRepository.class, + JournalAbbreviationLoader.loadRepository(abbreviationsPreferences))) + .onFailure(exception -> LOGGER.error("Failed to store journal preferences.", exception)) + .executeWith(taskExecutor); } public SimpleBooleanProperty isLoadingProperty() { @@ -387,4 +414,5 @@ public SimpleBooleanProperty isFileRemovableProperty() { public SimpleBooleanProperty useFJournalProperty() { return useFJournal; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingViewModel.java index 5caa9ca82e3..e6ff7d8da87 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingViewModel.java @@ -18,19 +18,23 @@ import com.google.common.base.CaseFormat; /** - * This class represents a view model for objects of the KeyBinding - * class. It has two properties representing the localized name of an - * action and its key bind. It can also represent a key binding category - * instead of a key bind itself. + * This class represents a view model for objects of the KeyBinding class. It has two + * properties representing the localized name of an action and its key bind. It can also + * represent a key binding category instead of a key bind itself. * */ public class KeyBindingViewModel { private KeyBinding keyBinding = null; + private String realBinding = ""; + private final ObservableList children = FXCollections.observableArrayList(); + private final KeyBindingRepository keyBindingRepository; + private final SimpleStringProperty displayName = new SimpleStringProperty(); + private final SimpleStringProperty shownBinding = new SimpleStringProperty(); private final KeyBindingCategory category; @@ -87,9 +91,8 @@ public boolean isCategory() { } /** - * Sets a a new key bind to this objects key binding object if - * the given key event is a valid combination of keys. - * + * Sets a a new key bind to this objects key binding object if the given key event is + * a valid combination of keys. * @param evt as KeyEvent * @return true if the KeyEvent is a valid binding, false else */ @@ -128,7 +131,8 @@ public boolean setNewBinding(KeyEvent evt) { } /** - * This method will reset the key bind of this models KeyBinding object to it's default bind + * This method will reset the key bind of this models KeyBinding object to it's + * default bind */ public void resetToDefault() { if (!isCategory()) { @@ -153,4 +157,5 @@ public Optional getResetIcon() { public Optional getClearIcon() { return isCategory() ? Optional.empty() : Optional.of(IconTheme.JabRefIcons.CLEANUP_ENTRIES); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingsTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingsTab.java index 42d03093d78..dee9f8ae38c 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingsTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingsTab.java @@ -32,20 +32,32 @@ public class KeyBindingsTab extends AbstractPreferenceTabView implements PreferencesTab { - @FXML private CustomTextField searchBox; - @FXML private TreeTableView keyBindingsTable; - @FXML private TreeTableColumn actionColumn; - @FXML private TreeTableColumn shortcutColumn; - @FXML private TreeTableColumn resetColumn; - @FXML private TreeTableColumn clearColumn; - @FXML private MenuButton presetsButton; + @FXML + private CustomTextField searchBox; + + @FXML + private TreeTableView keyBindingsTable; + + @FXML + private TreeTableColumn actionColumn; + + @FXML + private TreeTableColumn shortcutColumn; - @Inject private KeyBindingRepository keyBindingRepository; + @FXML + private TreeTableColumn resetColumn; + + @FXML + private TreeTableColumn clearColumn; + + @FXML + private MenuButton presetsButton; + + @Inject + private KeyBindingRepository keyBindingRepository; public KeyBindingsTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -58,26 +70,24 @@ private void initialize() { viewModel = new KeyBindingsTabViewModel(keyBindingRepository, dialogService, preferences); keyBindingsTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); - viewModel.selectedKeyBindingProperty().bind( - EasyBind.wrapNullable(keyBindingsTable.selectionModelProperty()) - .mapObservable(SelectionModel::selectedItemProperty) - .mapObservable(TreeItem::valueProperty) - ); + viewModel.selectedKeyBindingProperty() + .bind(EasyBind.wrapNullable(keyBindingsTable.selectionModelProperty()) + .mapObservable(SelectionModel::selectedItemProperty) + .mapObservable(TreeItem::valueProperty)); keyBindingsTable.setOnKeyPressed(viewModel::setNewBindingForCurrent); - keyBindingsTable.rootProperty().bind( - EasyBind.map(viewModel.rootKeyBindingProperty(), - keybinding -> new RecursiveTreeItem<>(keybinding, KeyBindingViewModel::getChildren)) - ); + keyBindingsTable.rootProperty() + .bind(EasyBind.map(viewModel.rootKeyBindingProperty(), + keybinding -> new RecursiveTreeItem<>(keybinding, KeyBindingViewModel::getChildren))); actionColumn.setCellValueFactory(cellData -> cellData.getValue().getValue().nameProperty()); shortcutColumn.setCellValueFactory(cellData -> cellData.getValue().getValue().shownBindingProperty()); new ViewModelTreeTableCellFactory() - .withGraphic(keyBinding -> keyBinding.getResetIcon().map(JabRefIcon::getGraphicNode).orElse(null)) - .withOnMouseClickedEvent(keyBinding -> evt -> keyBinding.resetToDefault()) - .install(resetColumn); + .withGraphic(keyBinding -> keyBinding.getResetIcon().map(JabRefIcon::getGraphicNode).orElse(null)) + .withOnMouseClickedEvent(keyBinding -> evt -> keyBinding.resetToDefault()) + .install(resetColumn); new ViewModelTreeTableCellFactory() - .withGraphic(keyBinding -> keyBinding.getClearIcon().map(JabRefIcon::getGraphicNode).orElse(null)) - .withOnMouseClickedEvent(keyBinding -> evt -> keyBinding.clear()) - .install(clearColumn); + .withGraphic(keyBinding -> keyBinding.getClearIcon().map(JabRefIcon::getGraphicNode).orElse(null)) + .withOnMouseClickedEvent(keyBinding -> evt -> keyBinding.clear()) + .install(clearColumn); viewModel.keyBindingPresets().forEach(preset -> presetsButton.getItems().add(createMenuItem(preset))); @@ -89,9 +99,9 @@ private void initialize() { ObjectProperty flashingColor = new SimpleObjectProperty<>(Color.TRANSPARENT); StringProperty flashingColorStringProperty = ColorUtil.createFlashingColorStringProperty(flashingColor); - searchBox.styleProperty().bind( - new SimpleStringProperty("-fx-control-inner-background: ").concat(flashingColorStringProperty).concat(";") - ); + searchBox.styleProperty() + .bind(new SimpleStringProperty("-fx-control-inner-background: ").concat(flashingColorStringProperty) + .concat(";")); searchBox.setPromptText(Localization.lang("Search...")); searchBox.setLeft(IconTheme.JabRefIcons.SEARCH.getGraphicNode()); @@ -123,4 +133,5 @@ private void setCategoriesExpanded(boolean expanded) { child.setExpanded(expanded); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingsTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingsTabViewModel.java index ad451dba57e..eebe54dcbfd 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingsTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingsTabViewModel.java @@ -30,16 +30,22 @@ public class KeyBindingsTabViewModel implements PreferenceTabViewModel { private final KeyBindingRepository keyBindingRepository; + private final GuiPreferences preferences; + private final OptionalObjectProperty selectedKeyBinding = OptionalObjectProperty.empty(); + private final ObjectProperty rootKeyBinding = new SimpleObjectProperty<>(); - private final ListProperty keyBindingPresets = new SimpleListProperty<>(FXCollections.observableArrayList()); + + private final ListProperty keyBindingPresets = new SimpleListProperty<>( + FXCollections.observableArrayList()); private final DialogService dialogService; private final List restartWarning = new ArrayList<>(); - public KeyBindingsTabViewModel(KeyBindingRepository keyBindingRepository, DialogService dialogService, GuiPreferences preferences) { + public KeyBindingsTabViewModel(KeyBindingRepository keyBindingRepository, DialogService dialogService, + GuiPreferences preferences) { this.keyBindingRepository = new KeyBindingRepository(keyBindingRepository.getKeyBindings()); this.dialogService = Objects.requireNonNull(dialogService); this.preferences = Objects.requireNonNull(preferences); @@ -49,7 +55,8 @@ public KeyBindingsTabViewModel(KeyBindingRepository keyBindingRepository, Dialog } /** - * Read all keybindings from the keybinding repository and create table keybinding models for them + * Read all keybindings from the keybinding repository and create table keybinding + * models for them */ @Override public void setValues() { @@ -69,7 +76,8 @@ public void filterValues(String term) { return; } if (matchesSearchTerm(keyBinding, term.toLowerCase())) { - KeyBindingViewModel keyBindViewModel = new KeyBindingViewModel(keyBindingRepository, keyBinding, bind); + KeyBindingViewModel keyBindViewModel = new KeyBindingViewModel(keyBindingRepository, keyBinding, + bind); categoryItem.getChildren().add(keyBindViewModel); } }); @@ -80,19 +88,23 @@ public void filterValues(String term) { } /** - * Searches for the term in the keybinding's localization, category, or key combination - * + * Searches for the term in the keybinding's localization, category, or key + * combination * @param keyBinding keybinding to search in - * @param searchTerm term to search for + * @param searchTerm term to search for * @return true if the term is found in the keybinding */ private boolean matchesSearchTerm(KeyBinding keyBinding, String searchTerm) { - if (keyBinding.getLocalization().toLowerCase().contains(searchTerm) || - keyBinding.getCategory().getName().toLowerCase().contains(searchTerm)) { + if (keyBinding.getLocalization().toLowerCase().contains(searchTerm) + || keyBinding.getCategory().getName().toLowerCase().contains(searchTerm)) { return true; } if (keyBindingRepository.getKeyCombination(keyBinding).isPresent()) { - return keyBindingRepository.getKeyCombination(keyBinding).get().toString().toLowerCase().contains(searchTerm); + return keyBindingRepository.getKeyCombination(keyBinding) + .get() + .toString() + .toLowerCase() + .contains(searchTerm); } return false; } @@ -124,13 +136,15 @@ public void resetToDefault() { String title = Localization.lang("Resetting all keyboard shortcuts"); String content = Localization.lang("All keyboard shortcuts will be reset to their defaults."); ButtonType resetButtonType = new ButtonType("Reset", ButtonBar.ButtonData.OK_DONE); - dialogService.showCustomButtonDialogAndWait(Alert.AlertType.INFORMATION, title, content, resetButtonType, - ButtonType.CANCEL).ifPresent(response -> { - if (response == resetButtonType) { - keyBindingRepository.resetToDefault(); - setValues(); - } - }); + dialogService + .showCustomButtonDialogAndWait(Alert.AlertType.INFORMATION, title, content, resetButtonType, + ButtonType.CANCEL) + .ifPresent(response -> { + if (response == resetButtonType) { + keyBindingRepository.resetToDefault(); + setValues(); + } + }); } public void loadPreset(KeyBindingPreset preset) { @@ -162,4 +176,5 @@ public ObjectProperty rootKeyBindingProperty() { public KeyBindingRepository getKeyBindingRepository() { return keyBindingRepository; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/presets/BashKeyBindingPreset.java b/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/presets/BashKeyBindingPreset.java index 72265447c39..d8041832e16 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/presets/BashKeyBindingPreset.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/presets/BashKeyBindingPreset.java @@ -37,4 +37,5 @@ public Map getKeyBindings() { return keyBindings; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/presets/KeyBindingPreset.java b/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/presets/KeyBindingPreset.java index 4eae173e8de..fbe1fea0150 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/presets/KeyBindingPreset.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/presets/KeyBindingPreset.java @@ -5,7 +5,9 @@ import org.jabref.gui.keyboard.KeyBinding; public interface KeyBindingPreset { + String getName(); Map getKeyBindings(); + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/presets/NewEntryBindingPreset.java b/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/presets/NewEntryBindingPreset.java index 191ec3b7074..e85164aaa42 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/presets/NewEntryBindingPreset.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/keybindings/presets/NewEntryBindingPreset.java @@ -37,4 +37,5 @@ public Map getKeyBindings() { return keyBindings; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTab.java index f67aba3c420..24c6f814672 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTab.java @@ -23,31 +23,58 @@ public class LinkedFilesTab extends AbstractPreferenceTabView implements PreferencesTab { - @FXML private TextField mainFileDirectory; - @FXML private RadioButton useMainFileDirectory; - @FXML private RadioButton useBibLocationAsPrimary; - @FXML private Button browseDirectory; - @FXML private Button autolinkRegexHelp; - @FXML private RadioButton autolinkFileStartsBibtex; - @FXML private RadioButton autolinkFileExactBibtex; - @FXML private RadioButton autolinkUseRegex; - @FXML private RadioButton openFileExplorerInFilesDirectory; - @FXML private RadioButton openFileExplorerInLastDirectory; - @FXML private TextField autolinkRegexKey; - - @FXML private CheckBox fulltextIndex; - - @FXML private ComboBox fileNamePattern; - @FXML private TextField fileDirectoryPattern; - @FXML private CheckBox confirmLinkedFileDelete; - @FXML private CheckBox moveToTrash; + @FXML + private TextField mainFileDirectory; + + @FXML + private RadioButton useMainFileDirectory; + + @FXML + private RadioButton useBibLocationAsPrimary; + + @FXML + private Button browseDirectory; + + @FXML + private Button autolinkRegexHelp; + + @FXML + private RadioButton autolinkFileStartsBibtex; + + @FXML + private RadioButton autolinkFileExactBibtex; + + @FXML + private RadioButton autolinkUseRegex; + + @FXML + private RadioButton openFileExplorerInFilesDirectory; + + @FXML + private RadioButton openFileExplorerInLastDirectory; + + @FXML + private TextField autolinkRegexKey; + + @FXML + private CheckBox fulltextIndex; + + @FXML + private ComboBox fileNamePattern; + + @FXML + private TextField fileDirectoryPattern; + + @FXML + private CheckBox confirmLinkedFileDelete; + + @FXML + private CheckBox moveToTrash; private final ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer(); public LinkedFilesTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -77,17 +104,23 @@ public void initialize() { fileNamePattern.itemsProperty().bind(viewModel.defaultFileNamePatternsProperty()); fileDirectoryPattern.textProperty().bindBidirectional(viewModel.fileDirectoryPatternProperty()); confirmLinkedFileDelete.selectedProperty().bindBidirectional(viewModel.confirmLinkedFileDeleteProperty()); - openFileExplorerInFilesDirectory.selectedProperty().bindBidirectional(viewModel.openFileExplorerInFilesDirectoryProperty()); - openFileExplorerInLastDirectory.selectedProperty().bindBidirectional(viewModel.openFileExplorerInLastDirectoryProperty()); + openFileExplorerInFilesDirectory.selectedProperty() + .bindBidirectional(viewModel.openFileExplorerInFilesDirectoryProperty()); + openFileExplorerInLastDirectory.selectedProperty() + .bindBidirectional(viewModel.openFileExplorerInLastDirectoryProperty()); ActionFactory actionFactory = new ActionFactory(); - actionFactory.configureIconButton(StandardActions.HELP_REGEX_SEARCH, new HelpAction(HelpFile.REGEX_SEARCH, dialogService, preferences.getExternalApplicationsPreferences()), autolinkRegexHelp); + actionFactory.configureIconButton(StandardActions.HELP_REGEX_SEARCH, + new HelpAction(HelpFile.REGEX_SEARCH, dialogService, preferences.getExternalApplicationsPreferences()), + autolinkRegexHelp); validationVisualizer.setDecoration(new IconValidationDecorator()); - Platform.runLater(() -> validationVisualizer.initVisualization(viewModel.mainFileDirValidationStatus(), mainFileDirectory)); + Platform.runLater(() -> validationVisualizer.initVisualization(viewModel.mainFileDirValidationStatus(), + mainFileDirectory)); } public void mainFileDirBrowse() { viewModel.mainFileDirBrowse(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTabViewModel.java index 3b83c2c7f0b..672659c8bc3 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTabViewModel.java @@ -28,26 +28,42 @@ public class LinkedFilesTabViewModel implements PreferenceTabViewModel { private final StringProperty mainFileDirectoryProperty = new SimpleStringProperty(""); + private final BooleanProperty useMainFileDirectoryProperty = new SimpleBooleanProperty(); + private final BooleanProperty useBibLocationAsPrimaryProperty = new SimpleBooleanProperty(); + private final BooleanProperty autolinkFileStartsBibtexProperty = new SimpleBooleanProperty(); + private final BooleanProperty autolinkFileExactBibtexProperty = new SimpleBooleanProperty(); + private final BooleanProperty autolinkUseRegexProperty = new SimpleBooleanProperty(); + private final StringProperty autolinkRegexKeyProperty = new SimpleStringProperty(""); - private final ListProperty defaultFileNamePatternsProperty = - new SimpleListProperty<>(FXCollections.observableArrayList(FilePreferences.DEFAULT_FILENAME_PATTERNS)); + + private final ListProperty defaultFileNamePatternsProperty = new SimpleListProperty<>( + FXCollections.observableArrayList(FilePreferences.DEFAULT_FILENAME_PATTERNS)); + private final BooleanProperty fulltextIndex = new SimpleBooleanProperty(); + private final StringProperty fileNamePatternProperty = new SimpleStringProperty(); + private final StringProperty fileDirectoryPatternProperty = new SimpleStringProperty(); + private final BooleanProperty confirmLinkedFileDeleteProperty = new SimpleBooleanProperty(); + private final BooleanProperty moveToTrashProperty = new SimpleBooleanProperty(); + private final BooleanProperty openFileExplorerInFilesDirectory = new SimpleBooleanProperty(); + private final BooleanProperty openFileExplorerInLastDirectory = new SimpleBooleanProperty(); private final Validator mainFileDirValidator; private final DialogService dialogService; + private final FilePreferences filePreferences; + private final AutoLinkPreferences autoLinkPreferences; public LinkedFilesTabViewModel(DialogService dialogService, CliPreferences preferences) { @@ -55,24 +71,21 @@ public LinkedFilesTabViewModel(DialogService dialogService, CliPreferences prefe this.filePreferences = preferences.getFilePreferences(); this.autoLinkPreferences = preferences.getAutoLinkPreferences(); - mainFileDirValidator = new FunctionBasedValidator<>( - mainFileDirectoryProperty, - mainDirectoryPath -> { - ValidationMessage error = ValidationMessage.error( - Localization.lang("Main file directory '%0' not found.\nCheck the tab \"Linked files\".", mainDirectoryPath) - ); - try { - Path path = Path.of(mainDirectoryPath); - if (!(Files.exists(path) && Files.isDirectory(path))) { - return error; - } - } catch (InvalidPathException ex) { - return error; - } - // main directory is valid - return null; + mainFileDirValidator = new FunctionBasedValidator<>(mainFileDirectoryProperty, mainDirectoryPath -> { + ValidationMessage error = ValidationMessage.error(Localization + .lang("Main file directory '%0' not found.\nCheck the tab \"Linked files\".", mainDirectoryPath)); + try { + Path path = Path.of(mainDirectoryPath); + if (!(Files.exists(path) && Files.isDirectory(path))) { + return error; } - ); + } + catch (InvalidPathException ex) { + return error; + } + // main directory is valid + return null; + }); } @Override @@ -113,9 +126,11 @@ public void storeSettings() { // Autolink preferences if (autolinkFileStartsBibtexProperty.getValue()) { autoLinkPreferences.setCitationKeyDependency(AutoLinkPreferences.CitationKeyDependency.START); - } else if (autolinkFileExactBibtexProperty.getValue()) { + } + else if (autolinkFileExactBibtexProperty.getValue()) { autoLinkPreferences.setCitationKeyDependency(AutoLinkPreferences.CitationKeyDependency.EXACT); - } else if (autolinkUseRegexProperty.getValue()) { + } + else if (autolinkUseRegexProperty.getValue()) { autoLinkPreferences.setCitationKeyDependency(AutoLinkPreferences.CitationKeyDependency.REGEX); } @@ -132,18 +147,19 @@ ValidationStatus mainFileDirValidationStatus() { public boolean validateSettings() { ValidationStatus validationStatus = mainFileDirValidationStatus(); if (!validationStatus.isValid() && useMainFileDirectoryProperty().get()) { - validationStatus.getHighestMessage().ifPresent(message -> - dialogService.showErrorDialogAndWait(message.getMessage())); + validationStatus.getHighestMessage() + .ifPresent(message -> dialogService.showErrorDialogAndWait(message.getMessage())); return false; } return true; } public void mainFileDirBrowse() { - DirectoryDialogConfiguration dirDialogConfiguration = - new DirectoryDialogConfiguration.Builder().withInitialDirectory(Path.of(mainFileDirectoryProperty.getValue())).build(); + DirectoryDialogConfiguration dirDialogConfiguration = new DirectoryDialogConfiguration.Builder() + .withInitialDirectory(Path.of(mainFileDirectoryProperty.getValue())) + .build(); dialogService.showDirectorySelectionDialog(dirDialogConfiguration) - .ifPresent(f -> mainFileDirectoryProperty.setValue(f.toString())); + .ifPresent(f -> mainFileDirectoryProperty.setValue(f.toString())); } // External file links @@ -206,5 +222,5 @@ public BooleanProperty openFileExplorerInFilesDirectoryProperty() { public BooleanProperty openFileExplorerInLastDirectoryProperty() { return openFileExplorerInLastDirectory; } -} +} diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterItemModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterItemModel.java index 91a3761b8e2..a85bafced00 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterItemModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterItemModel.java @@ -6,7 +6,9 @@ import org.jabref.logic.layout.format.NameFormatter; public class NameFormatterItemModel { + private final StringProperty name = new SimpleStringProperty(""); + private final StringProperty format = new SimpleStringProperty(""); NameFormatterItemModel() { @@ -50,4 +52,5 @@ public StringProperty formatProperty() { public String toString() { return "[" + name.getValue() + "," + format.getValue() + "]"; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterTab.java index 50582e5a859..2c0b3cd2df8 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterTab.java @@ -23,18 +23,29 @@ public class NameFormatterTab extends AbstractPreferenceTabView implements PreferencesTab { - @FXML private TableView formatterList; - @FXML private TableColumn formatterNameColumn; - @FXML private TableColumn formatterStringColumn; - @FXML private TableColumn actionsColumn; - @FXML private TextField addFormatterName; - @FXML private TextField addFormatterString; - @FXML private Button formatterHelp; + @FXML + private TableView formatterList; + + @FXML + private TableColumn formatterNameColumn; + + @FXML + private TableColumn formatterStringColumn; + + @FXML + private TableColumn actionsColumn; + + @FXML + private TextField addFormatterName; + + @FXML + private TextField addFormatterString; + + @FXML + private Button formatterHelp; public NameFormatterTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -50,28 +61,28 @@ public void initialize() { formatterNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); formatterNameColumn.setCellFactory(TextFieldTableCell.forTableColumn()); formatterNameColumn.setEditable(true); - formatterNameColumn.setOnEditCommit( - (TableColumn.CellEditEvent event) -> - event.getRowValue().setName(event.getNewValue())); + formatterNameColumn + .setOnEditCommit((TableColumn.CellEditEvent event) -> event.getRowValue() + .setName(event.getNewValue())); formatterStringColumn.setSortable(true); formatterStringColumn.setReorderable(false); formatterStringColumn.setCellValueFactory(cellData -> cellData.getValue().formatProperty()); formatterStringColumn.setCellFactory(TextFieldTableCell.forTableColumn()); formatterStringColumn.setEditable(true); - formatterStringColumn.setOnEditCommit( - (TableColumn.CellEditEvent event) -> - event.getRowValue().setFormat(event.getNewValue())); + formatterStringColumn + .setOnEditCommit((TableColumn.CellEditEvent event) -> event.getRowValue() + .setFormat(event.getNewValue())); actionsColumn.setSortable(false); actionsColumn.setReorderable(false); actionsColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); new ValueTableCellFactory() - .withGraphic(name -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) - .withTooltip(name -> Localization.lang("Remove formatter '%0'", name)) - .withOnMouseClickedEvent(item -> evt -> - viewModel.removeFormatter(formatterList.getFocusModel().getFocusedItem())) - .install(actionsColumn); + .withGraphic(name -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) + .withTooltip(name -> Localization.lang("Remove formatter '%0'", name)) + .withOnMouseClickedEvent( + item -> evt -> viewModel.removeFormatter(formatterList.getFocusModel().getFocusedItem())) + .install(actionsColumn); formatterList.addEventFilter(KeyEvent.KEY_PRESSED, event -> { if (event.getCode() == KeyCode.DELETE) { @@ -102,10 +113,14 @@ public void initialize() { }); ActionFactory actionFactory = new ActionFactory(); - actionFactory.configureIconButton(StandardActions.HELP_NAME_FORMATTER, new HelpAction(HelpFile.CUSTOM_EXPORTS_NAME_FORMATTER, dialogService, preferences.getExternalApplicationsPreferences()), formatterHelp); + actionFactory.configureIconButton(StandardActions.HELP_NAME_FORMATTER, + new HelpAction(HelpFile.CUSTOM_EXPORTS_NAME_FORMATTER, dialogService, + preferences.getExternalApplicationsPreferences()), + formatterHelp); } public void addFormatter() { viewModel.addFormatter(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterTabViewModel.java index 4efae9eea8e..c0672076f8b 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterTabViewModel.java @@ -15,8 +15,11 @@ public class NameFormatterTabViewModel implements PreferenceTabViewModel { - private final ListProperty formatterListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty formatterListProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); + private final StringProperty addFormatterNameProperty = new SimpleStringProperty(); + private final StringProperty addFormatterStringProperty = new SimpleStringProperty(); private final NameFormatterPreferences nameFormatterPreferences; @@ -34,7 +37,8 @@ public void setValues() { for (int i = 0; i < names.size(); i++) { if (i < formats.size()) { formatterListProperty.add(new NameFormatterItemModel(names.get(i), formats.get(i))); - } else { + } + else { formatterListProperty.add(new NameFormatterItemModel(names.get(i))); } } @@ -56,10 +60,10 @@ public void storeSettings() { } public void addFormatter() { - if (!StringUtil.isNullOrEmpty(addFormatterNameProperty.getValue()) && - !StringUtil.isNullOrEmpty(addFormatterStringProperty.getValue())) { - formatterListProperty.add(new NameFormatterItemModel( - addFormatterNameProperty.getValue(), addFormatterStringProperty.getValue())); + if (!StringUtil.isNullOrEmpty(addFormatterNameProperty.getValue()) + && !StringUtil.isNullOrEmpty(addFormatterStringProperty.getValue())) { + formatterListProperty.add(new NameFormatterItemModel(addFormatterNameProperty.getValue(), + addFormatterStringProperty.getValue())); addFormatterNameProperty.setValue(""); addFormatterStringProperty.setValue(""); @@ -81,4 +85,5 @@ public StringProperty addFormatterNameProperty() { public StringProperty addFormatterStringProperty() { return addFormatterStringProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/network/CustomCertificateViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/network/CustomCertificateViewModel.java index af25206afd1..8658ee7c55b 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/network/CustomCertificateViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/network/CustomCertificateViewModel.java @@ -14,16 +14,25 @@ import org.jabref.logic.net.ssl.SSLCertificate; public class CustomCertificateViewModel extends AbstractViewModel { + private final StringProperty serialNumberProperty = new SimpleStringProperty(""); + private final StringProperty issuerProperty = new SimpleStringProperty(""); + private final ObjectProperty validFromProperty = new SimpleObjectProperty<>(); + private final ObjectProperty validToProperty = new SimpleObjectProperty<>(); + private final StringProperty signatureAlgorithmProperty = new SimpleStringProperty(""); + private final StringProperty versionProperty = new SimpleStringProperty(""); + private final StringProperty thumbprintProperty = new SimpleStringProperty(""); + private final StringProperty pathProperty = new SimpleStringProperty(""); - public CustomCertificateViewModel(String thumbprint, String serialNumber, String issuer, LocalDate validFrom, LocalDate validTo, String sigAlgorithm, String version) { + public CustomCertificateViewModel(String thumbprint, String serialNumber, String issuer, LocalDate validFrom, + LocalDate validTo, String sigAlgorithm, String version) { serialNumberProperty.setValue(serialNumber); issuerProperty.setValue(issuer); validFromProperty.setValue(validFrom); @@ -80,7 +89,8 @@ public StringProperty pathProperty() { public Optional getPath() { if (pathProperty.getValue() == null || pathProperty.getValue().isEmpty()) { return Optional.empty(); - } else { + } + else { return Optional.of(pathProperty.getValue()); } } @@ -103,13 +113,9 @@ public String getSignatureAlgorithm() { } public static CustomCertificateViewModel fromSSLCertificate(SSLCertificate sslCertificate) { - return new CustomCertificateViewModel( - sslCertificate.getSHA256Thumbprint(), - sslCertificate.getSerialNumber(), - sslCertificate.getIssuer(), - sslCertificate.getValidFrom(), - sslCertificate.getValidTo(), - sslCertificate.getSignatureAlgorithm(), - sslCertificate.getVersion().toString()); + return new CustomCertificateViewModel(sslCertificate.getSHA256Thumbprint(), sslCertificate.getSerialNumber(), + sslCertificate.getIssuer(), sslCertificate.getValidFrom(), sslCertificate.getValidTo(), + sslCertificate.getSignatureAlgorithm(), sslCertificate.getVersion().toString()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/network/NetworkTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/network/NetworkTab.java index 232324c4095..90513ab8ecc 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/network/NetworkTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/network/NetworkTab.java @@ -30,39 +30,82 @@ import org.controlsfx.control.textfield.CustomPasswordField; public class NetworkTab extends AbstractPreferenceTabView implements PreferencesTab { - @FXML private CheckBox versionCheck; - @FXML private CheckBox proxyUse; - @FXML private Label proxyHostnameLabel; - @FXML private TextField proxyHostname; - @FXML private Label proxyPortLabel; - @FXML private TextField proxyPort; - @FXML private CheckBox proxyUseAuthentication; - @FXML private Label proxyUsernameLabel; - @FXML private TextField proxyUsername; - @FXML private Label proxyPasswordLabel; - @FXML private CustomPasswordField proxyPassword; - @FXML private Button checkConnectionButton; - @FXML private CheckBox proxyPersistPassword; - @FXML private SplitPane persistentTooltipWrapper; // The disabled persistPassword control does not show tooltips - - @FXML private TableView customCertificatesTable; - @FXML private TableColumn certIssuer; - @FXML private TableColumn certSerialNumber; - @FXML private TableColumn certSignatureAlgorithm; - @FXML private TableColumn certValidFrom; - @FXML private TableColumn certValidTo; - @FXML private TableColumn certVersion; - @FXML private TableColumn actionsColumn; + + @FXML + private CheckBox versionCheck; + + @FXML + private CheckBox proxyUse; + + @FXML + private Label proxyHostnameLabel; + + @FXML + private TextField proxyHostname; + + @FXML + private Label proxyPortLabel; + + @FXML + private TextField proxyPort; + + @FXML + private CheckBox proxyUseAuthentication; + + @FXML + private Label proxyUsernameLabel; + + @FXML + private TextField proxyUsername; + + @FXML + private Label proxyPasswordLabel; + + @FXML + private CustomPasswordField proxyPassword; + + @FXML + private Button checkConnectionButton; + + @FXML + private CheckBox proxyPersistPassword; + + @FXML + private SplitPane persistentTooltipWrapper; // The disabled persistPassword control + // does not show tooltips + + @FXML + private TableView customCertificatesTable; + + @FXML + private TableColumn certIssuer; + + @FXML + private TableColumn certSerialNumber; + + @FXML + private TableColumn certSignatureAlgorithm; + + @FXML + private TableColumn certValidFrom; + + @FXML + private TableColumn certValidTo; + + @FXML + private TableColumn certVersion; + + @FXML + private TableColumn actionsColumn; private String proxyPasswordText = ""; + private int proxyPasswordCaretPosition = 0; private final ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer(); public NetworkTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -85,7 +128,8 @@ public void initialize() { proxyUseAuthentication.selectedProperty().bindBidirectional(viewModel.proxyUseAuthenticationProperty()); proxyUseAuthentication.disableProperty().bind(proxyUse.selectedProperty().not()); - BooleanBinding proxyCustomAndAuthentication = proxyUse.selectedProperty().and(proxyUseAuthentication.selectedProperty()); + BooleanBinding proxyCustomAndAuthentication = proxyUse.selectedProperty() + .and(proxyUseAuthentication.selectedProperty()); proxyUsernameLabel.disableProperty().bind(proxyCustomAndAuthentication.not()); proxyUsername.textProperty().bindBidirectional(viewModel.proxyUsernameProperty()); proxyUsername.disableProperty().bind(proxyCustomAndAuthentication.not()); @@ -93,12 +137,13 @@ public void initialize() { proxyPassword.textProperty().bindBidirectional(viewModel.proxyPasswordProperty()); proxyPassword.disableProperty().bind(proxyCustomAndAuthentication.not()); proxyPersistPassword.selectedProperty().bindBidirectional(viewModel.proxyPersistPasswordProperty()); - proxyPersistPassword.disableProperty().bind( - proxyCustomAndAuthentication.and(viewModel.passwordPersistAvailable()).not()); + proxyPersistPassword.disableProperty() + .bind(proxyCustomAndAuthentication.and(viewModel.passwordPersistAvailable()).not()); EasyBind.subscribe(viewModel.passwordPersistAvailable(), available -> { if (!available) { persistentTooltipWrapper.setTooltip(new Tooltip(Localization.lang("Credential store not available."))); - } else { + } + else { persistentTooltipWrapper.setTooltip(null); } }); @@ -128,10 +173,11 @@ public void initialize() { actionsColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getThumbprint())); new ValueTableCellFactory() - .withGraphic(name -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) - .withTooltip(name -> Localization.lang("Remove formatter '%0'", name)) - .withOnMouseClickedEvent(thumbprint -> evt -> viewModel.customCertificateListProperty().removeIf(cert -> cert.getThumbprint().equals(thumbprint))) - .install(actionsColumn); + .withGraphic(name -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) + .withTooltip(name -> Localization.lang("Remove formatter '%0'", name)) + .withOnMouseClickedEvent(thumbprint -> evt -> viewModel.customCertificateListProperty() + .removeIf(cert -> cert.getThumbprint().equals(thumbprint))) + .install(actionsColumn); } private String formatDate(LocalDate localDate) { @@ -168,4 +214,5 @@ void checkConnection() { void addCertificateFile() { viewModel.addCertificateFile(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/network/NetworkTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/network/NetworkTabViewModel.java index 3db20e5be94..63bc2cc5bb2 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/network/NetworkTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/network/NetworkTabViewModel.java @@ -40,81 +40,77 @@ import kong.unirest.core.UnirestException; public class NetworkTabViewModel implements PreferenceTabViewModel { + private final BooleanProperty versionCheckProperty = new SimpleBooleanProperty(); + private final BooleanProperty proxyUseProperty = new SimpleBooleanProperty(); + private final StringProperty proxyHostnameProperty = new SimpleStringProperty(""); + private final StringProperty proxyPortProperty = new SimpleStringProperty(""); + private final BooleanProperty proxyUseAuthenticationProperty = new SimpleBooleanProperty(); + private final StringProperty proxyUsernameProperty = new SimpleStringProperty(""); + private final StringProperty proxyPasswordProperty = new SimpleStringProperty(""); + private final BooleanProperty proxyPersistPasswordProperty = new SimpleBooleanProperty(); + private final BooleanProperty passwordPersistAvailable = new SimpleBooleanProperty(); - private final ListProperty customCertificateListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + + private final ListProperty customCertificateListProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); private final Validator proxyHostnameValidator; + private final Validator proxyPortValidator; + private final Validator proxyUsernameValidator; + private final Validator proxyPasswordValidator; private final DialogService dialogService; - private final CliPreferences preferences; + private final CliPreferences preferences; private final ProxyPreferences proxyPreferences; + private final ProxyPreferences backupProxyPreferences; + private final InternalPreferences internalPreferences; private final TrustStoreManager trustStoreManager; private final AtomicBoolean sslCertificatesChanged = new AtomicBoolean(false); - public NetworkTabViewModel(DialogService dialogService, - CliPreferences preferences) { + public NetworkTabViewModel(DialogService dialogService, CliPreferences preferences) { this.dialogService = dialogService; this.preferences = preferences; this.proxyPreferences = preferences.getProxyPreferences(); this.internalPreferences = preferences.getInternalPreferences(); - backupProxyPreferences = new ProxyPreferences( - proxyPreferences.shouldUseProxy(), - proxyPreferences.getHostname(), - proxyPreferences.getPort(), - proxyPreferences.shouldUseAuthentication(), - proxyPreferences.getUsername(), - proxyPreferences.getPassword(), - proxyPreferences.shouldPersistPassword()); - - proxyHostnameValidator = new FunctionBasedValidator<>( - proxyHostnameProperty, + backupProxyPreferences = new ProxyPreferences(proxyPreferences.shouldUseProxy(), proxyPreferences.getHostname(), + proxyPreferences.getPort(), proxyPreferences.shouldUseAuthentication(), proxyPreferences.getUsername(), + proxyPreferences.getPassword(), proxyPreferences.shouldPersistPassword()); + + proxyHostnameValidator = new FunctionBasedValidator<>(proxyHostnameProperty, input -> !StringUtil.isNullOrEmpty(input), - ValidationMessage.error("%s > %s %n %n %s".formatted( - Localization.lang("Network"), - Localization.lang("Proxy configuration"), - Localization.lang("Please specify a hostname")))); - - proxyPortValidator = new FunctionBasedValidator<>( - proxyPortProperty, - input -> getPortAsInt(input).isPresent(), - ValidationMessage.error("%s > %s %n %n %s".formatted( - Localization.lang("Network"), - Localization.lang("Proxy configuration"), - Localization.lang("Please specify a port")))); - - proxyUsernameValidator = new FunctionBasedValidator<>( - proxyUsernameProperty, + ValidationMessage.error("%s > %s %n %n %s".formatted(Localization.lang("Network"), + Localization.lang("Proxy configuration"), Localization.lang("Please specify a hostname")))); + + proxyPortValidator = new FunctionBasedValidator<>(proxyPortProperty, input -> getPortAsInt(input).isPresent(), + ValidationMessage.error("%s > %s %n %n %s".formatted(Localization.lang("Network"), + Localization.lang("Proxy configuration"), Localization.lang("Please specify a port")))); + + proxyUsernameValidator = new FunctionBasedValidator<>(proxyUsernameProperty, input -> !StringUtil.isNullOrEmpty(input), - ValidationMessage.error("%s > %s %n %n %s".formatted( - Localization.lang("Network"), - Localization.lang("Proxy configuration"), - Localization.lang("Please specify a username")))); - - proxyPasswordValidator = new FunctionBasedValidator<>( - proxyPasswordProperty, - input -> !input.isBlank(), - ValidationMessage.error("%s > %s %n %n %s".formatted( - Localization.lang("Network"), - Localization.lang("Proxy configuration"), - Localization.lang("Please specify a password")))); + ValidationMessage.error("%s > %s %n %n %s".formatted(Localization.lang("Network"), + Localization.lang("Proxy configuration"), Localization.lang("Please specify a username")))); + + proxyPasswordValidator = new FunctionBasedValidator<>(proxyPasswordProperty, input -> !input.isBlank(), + ValidationMessage.error("%s > %s %n %n %s".formatted(Localization.lang("Network"), + Localization.lang("Proxy configuration"), Localization.lang("Please specify a password")))); this.trustStoreManager = new TrustStoreManager(Path.of(preferences.getSSLPreferences().getTruststorePath())); } @@ -140,15 +136,18 @@ private void setProxyValues() { private void setSSLValues() { customCertificateListProperty.clear(); - trustStoreManager.getCustomCertificates().forEach(cert -> customCertificateListProperty.add(CustomCertificateViewModel.fromSSLCertificate(cert))); + trustStoreManager.getCustomCertificates() + .forEach(cert -> customCertificateListProperty.add(CustomCertificateViewModel.fromSSLCertificate(cert))); customCertificateListProperty.addListener((ListChangeListener) c -> { sslCertificatesChanged.set(true); while (c.next()) { if (c.wasAdded()) { CustomCertificateViewModel certificate = c.getAddedSubList().getFirst(); - certificate.getPath().ifPresent(path -> trustStoreManager + certificate.getPath() + .ifPresent(path -> trustStoreManager .addCertificate(formatCustomAlias(certificate.getThumbprint()), Path.of(path))); - } else if (c.wasRemoved()) { + } + else if (c.wasRemoved()) { CustomCertificateViewModel certificate = c.getRemoved().getFirst(); trustStoreManager.deleteCertificate(formatCustomAlias(certificate.getThumbprint())); } @@ -164,7 +163,13 @@ public void storeSettings() { proxyPreferences.setPort(proxyPortProperty.getValue().trim()); proxyPreferences.setUseAuthentication(proxyUseAuthenticationProperty.getValue()); proxyPreferences.setUsername(proxyUsernameProperty.getValue().trim()); - proxyPreferences.setPersistPassword(proxyPersistPasswordProperty.getValue()); // Set before the password to actually persist + proxyPreferences.setPersistPassword(proxyPersistPasswordProperty.getValue()); // Set + // before + // the + // password + // to + // actually + // persist proxyPreferences.setPassword(proxyPasswordProperty.getValue()); ProxyRegisterer.register(proxyPreferences); @@ -174,7 +179,8 @@ public void storeSettings() { private Optional getPortAsInt(String value) { try { return Optional.of(Integer.parseInt(value)); - } catch (NumberFormatException ex) { + } + catch (NumberFormatException ex) { return Optional.empty(); } } @@ -211,15 +217,17 @@ public boolean validateSettings() { ValidationStatus validationStatus = validator.getValidationStatus(); if (!validationStatus.isValid()) { - validationStatus.getHighestMessage().ifPresent(message -> - dialogService.showErrorDialogAndWait(message.getMessage())); + validationStatus.getHighestMessage() + .ifPresent(message -> dialogService.showErrorDialogAndWait(message.getMessage())); return false; } return true; } /** - * Check the connection by using the given url. Used for validating the http proxy. The checking result will be appear when request finished. The checking result could be either success or fail, if fail, the cause will be displayed. + * Check the connection by using the given url. Used for validating the http proxy. + * The checking result will be appear when request finished. The checking result could + * be either success or fail, if fail, the cause will be displayed. */ public void checkConnection() { final String connectionSuccessText = Localization.lang("Connection successful!"); @@ -228,27 +236,26 @@ public void checkConnection() { final String testUrl = "http://jabref.org"; - ProxyRegisterer.register(new ProxyPreferences( - proxyUseProperty.getValue(), - proxyHostnameProperty.getValue().trim(), - proxyPortProperty.getValue().trim(), - proxyUseAuthenticationProperty.getValue(), - proxyUsernameProperty.getValue().trim(), - proxyPasswordProperty.getValue(), - proxyPersistPasswordProperty.getValue() - )); + ProxyRegisterer.register(new ProxyPreferences(proxyUseProperty.getValue(), + proxyHostnameProperty.getValue().trim(), proxyPortProperty.getValue().trim(), + proxyUseAuthenticationProperty.getValue(), proxyUsernameProperty.getValue().trim(), + proxyPasswordProperty.getValue(), proxyPersistPasswordProperty.getValue())); URLDownload urlDownload; try { urlDownload = new URLDownload(testUrl); if (urlDownload.canBeReached()) { dialogService.showInformationDialogAndWait(dialogTitle, connectionSuccessText); - } else { + } + else { dialogService.showErrorDialogAndWait(dialogTitle, connectionFailedText); } - } catch (MalformedURLException e) { - // Why would that happen? Because one of developers inserted a failing url in testUrl... - } catch (UnirestException e) { + } + catch (MalformedURLException e) { + // Why would that happen? Because one of developers inserted a failing url in + // testUrl... + } + catch (UnirestException e) { dialogService.showErrorDialogAndWait(dialogTitle, connectionFailedText); } @@ -259,7 +266,8 @@ public void checkConnection() { public List getRestartWarnings() { if (sslCertificatesChanged.get()) { return List.of(Localization.lang("SSL configuration changed")); - } else { + } + else { return List.of(); } } @@ -306,22 +314,27 @@ public ListProperty customCertificateListProperty() public void addCertificateFile() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(new FileChooser.ExtensionFilter(Localization.lang("SSL certificate file"), "*.crt", "*.cer")) - .withDefaultExtension(Localization.lang("SSL certificate file"), StandardFileType.CER) - .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) - .build(); - - dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(certPath -> SSLCertificate.fromPath(certPath).ifPresent(sslCertificate -> { - if (!trustStoreManager.certificateExists(formatCustomAlias(sslCertificate.getSHA256Thumbprint()))) { - customCertificateListProperty.add(CustomCertificateViewModel.fromSSLCertificate(sslCertificate) - .setPath(certPath.toAbsolutePath().toString())); - } else { - dialogService.showWarningDialogAndWait(Localization.lang("Duplicate Certificates"), Localization.lang("You already added this certificate")); - } - })); + .addExtensionFilter( + new FileChooser.ExtensionFilter(Localization.lang("SSL certificate file"), "*.crt", "*.cer")) + .withDefaultExtension(Localization.lang("SSL certificate file"), StandardFileType.CER) + .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) + .build(); + + dialogService.showFileOpenDialog(fileDialogConfiguration) + .ifPresent(certPath -> SSLCertificate.fromPath(certPath).ifPresent(sslCertificate -> { + if (!trustStoreManager.certificateExists(formatCustomAlias(sslCertificate.getSHA256Thumbprint()))) { + customCertificateListProperty.add(CustomCertificateViewModel.fromSSLCertificate(sslCertificate) + .setPath(certPath.toAbsolutePath().toString())); + } + else { + dialogService.showWarningDialogAndWait(Localization.lang("Duplicate Certificates"), + Localization.lang("You already added this certificate")); + } + })); } private String formatCustomAlias(String thumbprint) { return "%s[custom]".formatted(thumbprint); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTab.java index d27d8a9dd3d..6b9d9f8a5ef 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTab.java @@ -49,34 +49,61 @@ public class PreviewTab extends AbstractPreferenceTabView implements PreferencesTab { - @FXML private CheckBox showAsTabCheckBox; - @FXML private CheckBox showPreviewTooltipCheckBox; - @FXML private ListView availableListView; - @FXML private ListView chosenListView; - @FXML private Button toRightButton; - @FXML private Button toLeftButton; - @FXML private Button sortUpButton; - @FXML private Button sortDownButton; - @FXML private Label readOnlyLabel; - @FXML private Button resetDefaultButton; - @FXML private Tab previewTab; - @FXML private CodeArea editArea; - @FXML private CustomTextField searchBox; - - @Inject private StateManager stateManager; - @Inject private ThemeManager themeManager; + @FXML + private CheckBox showAsTabCheckBox; + + @FXML + private CheckBox showPreviewTooltipCheckBox; + + @FXML + private ListView availableListView; + + @FXML + private ListView chosenListView; + + @FXML + private Button toRightButton; + + @FXML + private Button toLeftButton; + + @FXML + private Button sortUpButton; + + @FXML + private Button sortDownButton; + + @FXML + private Label readOnlyLabel; + + @FXML + private Button resetDefaultButton; + + @FXML + private Tab previewTab; + + @FXML + private CodeArea editArea; + + @FXML + private CustomTextField searchBox; + + @Inject + private StateManager stateManager; + + @Inject + private ThemeManager themeManager; private final ContextMenu contextMenu = new ContextMenu(); private long lastKeyPressTime; + private String listSearchTerm; private final ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer(); public PreviewTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } private class EditAction extends SimpleCommand { @@ -91,18 +118,15 @@ public EditAction(StandardActions command) { public void execute() { if (editArea != null) { switch (command) { - case COPY -> - editArea.copy(); - case CUT -> - editArea.cut(); - case PASTE -> - editArea.paste(); - case SELECT_ALL -> - editArea.selectAll(); + case COPY -> editArea.copy(); + case CUT -> editArea.cut(); + case PASTE -> editArea.paste(); + case SELECT_ALL -> editArea.selectAll(); } editArea.requestFocus(); } } + } @Override @@ -113,16 +137,17 @@ public String getTabName() { @FXML private void selectBstFile(ActionEvent event) { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.BST) - .withDefaultExtension(StandardFileType.BST) - .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) - .build(); + .addExtensionFilter(StandardFileType.BST) + .withDefaultExtension(StandardFileType.BST) + .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) + .build(); dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(bstFile -> viewModel.addBstStyle(bstFile)); } public void initialize() { - this.viewModel = new PreviewTabViewModel(dialogService, preferences.getPreviewPreferences(), taskExecutor, stateManager); + this.viewModel = new PreviewTabViewModel(dialogService, preferences.getPreviewPreferences(), taskExecutor, + stateManager); lastKeyPressTime = System.currentTimeMillis(); showAsTabCheckBox.selectedProperty().bindBidirectional(viewModel.showAsExtraTabProperty()); @@ -132,74 +157,85 @@ public void initialize() { searchBox.setLeft(IconTheme.JabRefIcons.SEARCH.getGraphicNode()); ActionFactory factory = new ActionFactory(); - contextMenu.getItems().addAll( - factory.createMenuItem(StandardActions.CUT, new EditAction(StandardActions.CUT)), - factory.createMenuItem(StandardActions.COPY, new EditAction(StandardActions.COPY)), - factory.createMenuItem(StandardActions.PASTE, new EditAction(StandardActions.PASTE)), - factory.createMenuItem(StandardActions.SELECT_ALL, new EditAction(StandardActions.SELECT_ALL)) - ); + contextMenu.getItems() + .addAll(factory.createMenuItem(StandardActions.CUT, new EditAction(StandardActions.CUT)), + factory.createMenuItem(StandardActions.COPY, new EditAction(StandardActions.COPY)), + factory.createMenuItem(StandardActions.PASTE, new EditAction(StandardActions.PASTE)), + factory.createMenuItem(StandardActions.SELECT_ALL, new EditAction(StandardActions.SELECT_ALL))); contextMenu.getItems().forEach(item -> item.setGraphic(null)); contextMenu.getStyleClass().add("context-menu"); availableListView.setItems(viewModel.getFilteredAvailableLayouts()); viewModel.availableSelectionModelProperty().setValue(availableListView.getSelectionModel()); - new ViewModelListCellFactory() - .withText(PreviewLayout::getDisplayName) - .install(availableListView); + new ViewModelListCellFactory().withText(PreviewLayout::getDisplayName) + .install(availableListView); availableListView.setOnDragOver(this::dragOver); availableListView.setOnDragDetected(this::dragDetectedInAvailable); availableListView.setOnDragDropped(event -> dragDropped(viewModel.availableListProperty(), event)); availableListView.setOnKeyTyped(event -> jumpToSearchKey(availableListView, event)); availableListView.setOnMouseClicked(this::mouseClickedAvailable); availableListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); - availableListView.selectionModelProperty().getValue().selectedItemProperty().addListener((observable, oldValue, newValue) -> - viewModel.setPreviewLayout(newValue)); + availableListView.selectionModelProperty() + .getValue() + .selectedItemProperty() + .addListener((observable, oldValue, newValue) -> viewModel.setPreviewLayout(newValue)); chosenListView.itemsProperty().bindBidirectional(viewModel.chosenListProperty()); viewModel.chosenSelectionModelProperty().setValue(chosenListView.getSelectionModel()); - new ViewModelListCellFactory() - .withText(PreviewLayout::getDisplayName) - .setOnDragDropped(this::dragDroppedInChosenCell) - .install(chosenListView); + new ViewModelListCellFactory().withText(PreviewLayout::getDisplayName) + .setOnDragDropped(this::dragDroppedInChosenCell) + .install(chosenListView); chosenListView.setOnDragOver(this::dragOver); chosenListView.setOnDragDetected(this::dragDetectedInChosen); chosenListView.setOnDragDropped(event -> dragDropped(viewModel.chosenListProperty(), event)); chosenListView.setOnKeyTyped(event -> jumpToSearchKey(chosenListView, event)); chosenListView.setOnMouseClicked(this::mouseClickedChosen); chosenListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); - chosenListView.selectionModelProperty().getValue().selectedItemProperty().addListener((observable, oldValue, newValue) -> - viewModel.setPreviewLayout(newValue)); - - toRightButton.disableProperty().bind(viewModel.availableSelectionModelProperty().getValue().selectedItemProperty().isNull()); - toLeftButton.disableProperty().bind(viewModel.chosenSelectionModelProperty().getValue().selectedItemProperty().isNull()); - sortUpButton.disableProperty().bind(viewModel.chosenSelectionModelProperty().getValue().selectedItemProperty().isNull()); - sortDownButton.disableProperty().bind(viewModel.chosenSelectionModelProperty().getValue().selectedItemProperty().isNull()); + chosenListView.selectionModelProperty() + .getValue() + .selectedItemProperty() + .addListener((observable, oldValue, newValue) -> viewModel.setPreviewLayout(newValue)); + + toRightButton.disableProperty() + .bind(viewModel.availableSelectionModelProperty().getValue().selectedItemProperty().isNull()); + toLeftButton.disableProperty() + .bind(viewModel.chosenSelectionModelProperty().getValue().selectedItemProperty().isNull()); + sortUpButton.disableProperty() + .bind(viewModel.chosenSelectionModelProperty().getValue().selectedItemProperty().isNull()); + sortDownButton.disableProperty() + .bind(viewModel.chosenSelectionModelProperty().getValue().selectedItemProperty().isNull()); PreviewViewer previewViewer = new PreviewViewer(dialogService, preferences, themeManager, taskExecutor); previewViewer.setDatabaseContext(new BibDatabaseContext()); previewViewer.setEntry(TestEntry.getTestEntry()); EasyBind.subscribe(viewModel.selectedLayoutProperty(), previewViewer::setLayout); - previewViewer.visibleProperty().bind(viewModel.chosenSelectionModelProperty().getValue().selectedItemProperty().isNotNull() - .or(viewModel.availableSelectionModelProperty().getValue().selectedItemProperty().isNotNull())); + previewViewer.visibleProperty() + .bind(viewModel.chosenSelectionModelProperty() + .getValue() + .selectedItemProperty() + .isNotNull() + .or(viewModel.availableSelectionModelProperty().getValue().selectedItemProperty().isNotNull())); previewTab.setContent(previewViewer); editArea.clear(); editArea.setParagraphGraphicFactory(LineNumberFactory.get(editArea)); editArea.setContextMenu(contextMenu); - editArea.visibleProperty().bind(viewModel.chosenSelectionModelProperty().getValue().selectedItemProperty().isNotNull() - .or(viewModel.availableSelectionModelProperty().getValue().selectedItemProperty().isNotNull())); - - BindingsHelper.bindBidirectional( - editArea.textProperty(), - viewModel.sourceTextProperty(), - newSourceText -> editArea.replaceText(newSourceText), - newEditText -> { + editArea.visibleProperty() + .bind(viewModel.chosenSelectionModelProperty() + .getValue() + .selectedItemProperty() + .isNotNull() + .or(viewModel.availableSelectionModelProperty().getValue().selectedItemProperty().isNotNull())); + + BindingsHelper.bindBidirectional(editArea.textProperty(), viewModel.sourceTextProperty(), + newSourceText -> editArea.replaceText(newSourceText), newEditText -> { viewModel.sourceTextProperty().setValue(newEditText); viewModel.refreshPreview(); }); - editArea.textProperty().addListener((obs, oldValue, newValue) -> - editArea.setStyleSpans(0, viewModel.computeHighlighting(newValue))); + editArea.textProperty() + .addListener( + (obs, oldValue, newValue) -> editArea.setStyleSpans(0, viewModel.computeHighlighting(newValue))); editArea.focusedProperty().addListener((observable, oldValue, newValue) -> { if (!newValue) { @@ -207,7 +243,8 @@ public void initialize() { } }); - searchBox.textProperty().addListener((observable, previousText, searchTerm) -> viewModel.setAvailableFilter(searchTerm)); + searchBox.textProperty() + .addListener((observable, previousText, searchTerm) -> viewModel.setAvailableFilter(searchTerm)); readOnlyLabel.visibleProperty().bind(viewModel.selectedIsEditableProperty().not()); resetDefaultButton.disableProperty().bind(viewModel.selectedIsEditableProperty().not()); @@ -216,14 +253,15 @@ public void initialize() { editArea.editableProperty().bind(viewModel.selectedIsEditableProperty()); validationVisualizer.setDecoration(new IconValidationDecorator()); - Platform.runLater(() -> validationVisualizer.initVisualization(viewModel.chosenListValidationStatus(), chosenListView)); + Platform.runLater( + () -> validationVisualizer.initVisualization(viewModel.chosenListValidationStatus(), chosenListView)); } /** - * This is called, if a user starts typing some characters into the keyboard with focus on one ListView. The - * ListView will scroll to the next cell with the name of the PreviewLayout fitting those characters. - * - * @param list The ListView currently focused + * This is called, if a user starts typing some characters into the keyboard with + * focus on one ListView. The ListView will scroll to the next cell with the name of + * the PreviewLayout fitting those characters. + * @param list The ListView currently focused * @param keypressed The pressed character */ @@ -234,14 +272,18 @@ private void jumpToSearchKey(ListView list, KeyEvent keypressed) if ((System.currentTimeMillis() - lastKeyPressTime) < 1000) { listSearchTerm += keypressed.getCharacter().toLowerCase(); - } else { + } + else { listSearchTerm = keypressed.getCharacter().toLowerCase(); } lastKeyPressTime = System.currentTimeMillis(); - list.getItems().stream().filter(item -> item.getDisplayName().toLowerCase().startsWith(listSearchTerm)) - .findFirst().ifPresent(list::scrollTo); + list.getItems() + .stream() + .filter(item -> item.getDisplayName().toLowerCase().startsWith(listSearchTerm)) + .findFirst() + .ifPresent(list::scrollTo); } private void dragOver(DragEvent event) { @@ -249,19 +291,23 @@ private void dragOver(DragEvent event) { } private void dragDetectedInAvailable(MouseEvent event) { - List selectedLayouts = new ArrayList<>(viewModel.availableSelectionModelProperty().getValue().getSelectedItems()); + List selectedLayouts = new ArrayList<>( + viewModel.availableSelectionModelProperty().getValue().getSelectedItems()); if (!selectedLayouts.isEmpty()) { Dragboard dragboard = startDragAndDrop(TransferMode.MOVE); - viewModel.dragDetected(viewModel.availableListProperty(), viewModel.availableSelectionModelProperty(), selectedLayouts, dragboard); + viewModel.dragDetected(viewModel.availableListProperty(), viewModel.availableSelectionModelProperty(), + selectedLayouts, dragboard); } event.consume(); } private void dragDetectedInChosen(MouseEvent event) { - List selectedLayouts = new ArrayList<>(viewModel.chosenSelectionModelProperty().getValue().getSelectedItems()); + List selectedLayouts = new ArrayList<>( + viewModel.chosenSelectionModelProperty().getValue().getSelectedItems()); if (!selectedLayouts.isEmpty()) { Dragboard dragboard = startDragAndDrop(TransferMode.MOVE); - viewModel.dragDetected(viewModel.chosenListProperty(), viewModel.chosenSelectionModelProperty(), selectedLayouts, dragboard); + viewModel.dragDetected(viewModel.chosenListProperty(), viewModel.chosenSelectionModelProperty(), + selectedLayouts, dragboard); } event.consume(); } @@ -311,4 +357,5 @@ private void mouseClickedChosen(MouseEvent event) { event.consume(); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java index d0c999ae7fa..be9b3c7d63b 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java @@ -65,34 +65,48 @@ public class PreviewTabViewModel implements PreferenceTabViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(PreviewTabViewModel.class); private final BooleanProperty showAsExtraTabProperty = new SimpleBooleanProperty(false); + private final BooleanProperty showPreviewInEntryTableTooltip = new SimpleBooleanProperty(false); - private final ListProperty availableListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ObjectProperty> availableSelectionModelProperty = new SimpleObjectProperty<>(new NoSelectionModel<>()); - private final FilteredList filteredAvailableLayouts = new FilteredList<>(this.availableListProperty()); - private final ListProperty chosenListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ObjectProperty> chosenSelectionModelProperty = new SimpleObjectProperty<>(new NoSelectionModel<>()); + private final ListProperty availableListProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); + + private final ObjectProperty> availableSelectionModelProperty = new SimpleObjectProperty<>( + new NoSelectionModel<>()); + + private final FilteredList filteredAvailableLayouts = new FilteredList<>( + this.availableListProperty()); + + private final ListProperty chosenListProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); + + private final ObjectProperty> chosenSelectionModelProperty = new SimpleObjectProperty<>( + new NoSelectionModel<>()); private final ListProperty bstStylesPaths = new SimpleListProperty<>(FXCollections.observableArrayList()); private final BooleanProperty selectedIsEditableProperty = new SimpleBooleanProperty(false); + private final ObjectProperty selectedLayoutProperty = new SimpleObjectProperty<>(); + private final StringProperty sourceTextProperty = new SimpleStringProperty(""); private final DialogService dialogService; + private final PreviewPreferences previewPreferences; + private final TaskExecutor taskExecutor; private final Validator chosenListValidator; private final CustomLocalDragboard localDragboard; + private ListProperty dragSourceList = null; + private ObjectProperty> dragSourceSelectionModel = null; - public PreviewTabViewModel(DialogService dialogService, - PreviewPreferences previewPreferences, - TaskExecutor taskExecutor, - StateManager stateManager) { + public PreviewTabViewModel(DialogService dialogService, PreviewPreferences previewPreferences, + TaskExecutor taskExecutor, StateManager stateManager) { this.dialogService = dialogService; this.taskExecutor = taskExecutor; this.localDragboard = stateManager.getLocalDragboard(); @@ -104,16 +118,10 @@ public PreviewTabViewModel(DialogService dialogService, } }); - chosenListValidator = new FunctionBasedValidator<>( - chosenListProperty, + chosenListValidator = new FunctionBasedValidator<>(chosenListProperty, input -> !chosenListProperty.getValue().isEmpty(), - ValidationMessage.error("%s > %s %n %n %s".formatted( - Localization.lang("Entry preview"), - Localization.lang("Selected"), - Localization.lang("Selected Layouts can not be empty") - ) - ) - ); + ValidationMessage.error("%s > %s %n %n %s".formatted(Localization.lang("Entry preview"), + Localization.lang("Selected"), Localization.lang("Selected Layouts can not be empty")))); } @Override @@ -131,17 +139,18 @@ public void setValues() { BibEntryTypesManager entryTypesManager = Injector.instantiateModelOrService(BibEntryTypesManager.class); BackgroundTask.wrap(CSLStyleLoader::getStyles) - .onSuccess(styles -> styles.stream() - .map(style -> new CitationStylePreviewLayout(style, entryTypesManager)) - .filter(style -> chosenListProperty.getValue().filtered(item -> - item.getName().equals(style.getName())).isEmpty()) - .sorted(Comparator.comparing(PreviewLayout::getName)) - .forEach(availableListProperty::add)) - .onFailure(ex -> { - LOGGER.error("Something went wrong while adding the discovered CitationStyles to the list.", ex); - dialogService.showErrorDialogAndWait(Localization.lang("Error adding discovered CitationStyles"), ex); - }) - .executeWith(taskExecutor); + .onSuccess(styles -> styles.stream() + .map(style -> new CitationStylePreviewLayout(style, entryTypesManager)) + .filter(style -> chosenListProperty.getValue() + .filtered(item -> item.getName().equals(style.getName())) + .isEmpty()) + .sorted(Comparator.comparing(PreviewLayout::getName)) + .forEach(availableListProperty::add)) + .onFailure(ex -> { + LOGGER.error("Something went wrong while adding the discovered CitationStyles to the list.", ex); + dialogService.showErrorDialogAndWait(Localization.lang("Error adding discovered CitationStyles"), ex); + }) + .executeWith(taskExecutor); bstStylesPaths.clear(); bstStylesPaths.addAll(previewPreferences.getBstPreviewLayoutPaths()); bstStylesPaths.forEach(path -> { @@ -159,11 +168,12 @@ public void setPreviewLayout(PreviewLayout selectedLayout) { try { selectedLayoutProperty.setValue(selectedLayout); - } catch (StringIndexOutOfBoundsException exception) { + } + catch (StringIndexOutOfBoundsException exception) { LOGGER.warn("Parsing error.", exception); - dialogService.showErrorDialogAndWait( - Localization.lang("Parsing error"), - Localization.lang("Parsing error") + ": " + Localization.lang("illegal backslash expression"), exception); + dialogService.showErrorDialogAndWait(Localization.lang("Parsing error"), + Localization.lang("Parsing error") + ": " + Localization.lang("illegal backslash expression"), + exception); } boolean isEditingAllowed = selectedLayout instanceof TextBasedPreviewLayout; @@ -181,11 +191,15 @@ public void refreshPreview() { } private PreviewLayout findLayoutByName(String name) { - return availableListProperty.getValue().stream().filter(layout -> layout.getName().equals(name)) - .findAny() - .orElse(chosenListProperty.getValue().stream().filter(layout -> layout.getName().equals(name)) - .findAny() - .orElse(null)); + return availableListProperty.getValue() + .stream() + .filter(layout -> layout.getName().equals(name)) + .findAny() + .orElse(chosenListProperty.getValue() + .stream() + .filter(layout -> layout.getName().equals(name)) + .findAny() + .orElse(null)); } /** @@ -210,8 +224,8 @@ public void storeSettings() { previewPreferences.setBstPreviewLayoutPaths(bstStylesPaths); if (!chosenSelectionModelProperty.getValue().getSelectedItems().isEmpty()) { - previewPreferences.setLayoutCyclePosition(chosenListProperty.getValue().indexOf( - chosenSelectionModelProperty.getValue().getSelectedItems().getFirst())); + previewPreferences.setLayoutCyclePosition(chosenListProperty.getValue() + .indexOf(chosenSelectionModelProperty.getValue().getSelectedItems().getFirst())); } } @@ -224,8 +238,8 @@ public boolean validateSettings() { ValidationStatus validationStatus = chosenListValidationStatus(); if (!validationStatus.isValid()) { if (validationStatus.getHighestMessage().isPresent()) { - validationStatus.getHighestMessage().ifPresent(message -> - dialogService.showErrorDialogAndWait(message.getMessage())); + validationStatus.getHighestMessage() + .ifPresent(message -> dialogService.showErrorDialogAndWait(message.getMessage())); } return false; } @@ -299,20 +313,20 @@ public void resetDefaultLayout() { } /** - * XML-Syntax-Highlighting for RichTextFX-Codearea created by (c) Carlos Martins (github: - * @cemartins) + * XML-Syntax-Highlighting for RichTextFX-Codearea created by (c) Carlos Martins + * (github: @cemartins) *

    - * License: BSD-2-Clause + * License: + * BSD-2-Clause *

    - * See also - * https://github.com/FXMisc/RichTextFX/blob/master/richtextfx-demos/README.md#xml-editor - * + * See also https://github.com/FXMisc/RichTextFX/blob/master/richtextfx-demos/README.md#xml-editor * @param text to parse and highlight * @return highlighted span for codeArea */ public StyleSpans> computeHighlighting(String text) { - final Pattern XML_TAG = Pattern.compile("(?(]*)(\\h*/?>))" - + "|(?)"); + final Pattern XML_TAG = Pattern + .compile("(?(]*)(\\h*/?>))" + "|(?)"); final Pattern ATTRIBUTES = Pattern.compile("(\\w+\\h*)(=)(\\h*\"[^\"]+\")"); final int GROUP_OPEN_BRACKET = 2; @@ -330,12 +344,15 @@ public StyleSpans> computeHighlighting(String text) { spansBuilder.add(List.of(), matcher.start() - lastKeywordEnd); if (matcher.group("COMMENT") != null) { spansBuilder.add(Set.of("comment"), matcher.end() - matcher.start()); - } else { + } + else { if (matcher.group("ELEMENT") != null) { String attributesText = matcher.group(GROUP_ATTRIBUTES_SECTION); - spansBuilder.add(Set.of("tagmark"), matcher.end(GROUP_OPEN_BRACKET) - matcher.start(GROUP_OPEN_BRACKET)); - spansBuilder.add(Set.of("anytag"), matcher.end(GROUP_ELEMENT_NAME) - matcher.end(GROUP_OPEN_BRACKET)); + spansBuilder.add(Set.of("tagmark"), + matcher.end(GROUP_OPEN_BRACKET) - matcher.start(GROUP_OPEN_BRACKET)); + spansBuilder.add(Set.of("anytag"), + matcher.end(GROUP_ELEMENT_NAME) - matcher.end(GROUP_OPEN_BRACKET)); if (!attributesText.isEmpty()) { lastKeywordEnd = 0; @@ -343,9 +360,12 @@ public StyleSpans> computeHighlighting(String text) { Matcher attributesMatcher = ATTRIBUTES.matcher(attributesText); while (attributesMatcher.find()) { spansBuilder.add(List.of(), attributesMatcher.start() - lastKeywordEnd); - spansBuilder.add(Set.of("attribute"), attributesMatcher.end(GROUP_ATTRIBUTE_NAME) - attributesMatcher.start(GROUP_ATTRIBUTE_NAME)); - spansBuilder.add(Set.of("tagmark"), attributesMatcher.end(GROUP_EQUAL_SYMBOL) - attributesMatcher.end(GROUP_ATTRIBUTE_NAME)); - spansBuilder.add(Set.of("avalue"), attributesMatcher.end(GROUP_ATTRIBUTE_VALUE) - attributesMatcher.end(GROUP_EQUAL_SYMBOL)); + spansBuilder.add(Set.of("attribute"), attributesMatcher.end(GROUP_ATTRIBUTE_NAME) + - attributesMatcher.start(GROUP_ATTRIBUTE_NAME)); + spansBuilder.add(Set.of("tagmark"), attributesMatcher.end(GROUP_EQUAL_SYMBOL) + - attributesMatcher.end(GROUP_ATTRIBUTE_NAME)); + spansBuilder.add(Set.of("avalue"), attributesMatcher.end(GROUP_ATTRIBUTE_VALUE) + - attributesMatcher.end(GROUP_EQUAL_SYMBOL)); lastKeywordEnd = attributesMatcher.end(); } if (attributesText.length() > lastKeywordEnd) { @@ -370,7 +390,9 @@ public void dragOver(DragEvent event) { } } - public void dragDetected(ListProperty sourceList, ObjectProperty> sourceSelectionModel, List selectedLayouts, Dragboard dragboard) { + public void dragDetected(ListProperty sourceList, + ObjectProperty> sourceSelectionModel, + List selectedLayouts, Dragboard dragboard) { ClipboardContent content = new ClipboardContent(); content.put(DragAndDropDataFormats.PREVIEWLAYOUTS, ""); dragboard.setContent(content); @@ -380,8 +402,8 @@ public void dragDetected(ListProperty sourceList, ObjectProperty< } /** - * This is called, when the user drops some PreviewLayouts either in the availableListView or in the empty space of chosenListView - * + * This is called, when the user drops some PreviewLayouts either in the + * availableListView or in the empty space of chosenListView * @param targetList either availableListView or chosenListView */ @@ -406,8 +428,8 @@ public boolean dragDropped(ListProperty targetList, Dragboard dra } /** - * This is called, when the user drops some PreviewLayouts on another cell in chosenListView to sort them - * + * This is called, when the user drops some PreviewLayouts on another cell in + * chosenListView to sort them * @param targetLayout the Layout, the user drops a layout on */ @@ -420,7 +442,8 @@ public boolean dragDroppedInChosenCell(PreviewLayout targetLayout, Dragboard dra chosenSelectionModelProperty.getValue().clearSelection(); int targetId = chosenListProperty.getValue().indexOf(targetLayout); - // see https://stackoverflow.com/questions/28603224/sort-tableview-with-drag-and-drop-rows + // see + // https://stackoverflow.com/questions/28603224/sort-tableview-with-drag-and-drop-rows int onSelectedDelta = 0; while (draggedSelectedLayouts.contains(targetLayout)) { onSelectedDelta = 1; @@ -437,7 +460,8 @@ public boolean dragDroppedInChosenCell(PreviewLayout targetLayout, Dragboard dra if (targetLayout != null) { targetId = chosenListProperty.getValue().indexOf(targetLayout) + onSelectedDelta; - } else if (targetId != 0) { + } + else if (targetId != 0) { targetId = chosenListProperty.getValue().size(); } @@ -469,9 +493,8 @@ public FilteredList getFilteredAvailableLayouts() { } public void setAvailableFilter(String searchTerm) { - this.filteredAvailableLayouts.setPredicate( - preview -> searchTerm.isEmpty() - || preview.containsCaseIndependent(searchTerm)); + this.filteredAvailableLayouts + .setPredicate(preview -> searchTerm.isEmpty() || preview.containsCaseIndependent(searchTerm)); } public ObjectProperty> availableSelectionModelProperty() { @@ -504,4 +527,5 @@ public void addBstStyle(Path bstFile) { availableListProperty().add(bstPreviewLayout); chosenListProperty().add(bstPreviewLayout); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/protectedterms/NewProtectedTermsFileDialog.java b/jabgui/src/main/java/org/jabref/gui/preferences/protectedterms/NewProtectedTermsFileDialog.java index 5989cbc6792..2ab3adafdf5 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/protectedterms/NewProtectedTermsFileDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/protectedterms/NewProtectedTermsFileDialog.java @@ -21,38 +21,36 @@ public class NewProtectedTermsFileDialog extends BaseDialog { private final TextField newFile = new TextField(); + private final DialogService dialogService; - public NewProtectedTermsFileDialog(List termsLists, DialogService dialogService, FilePreferences filePreferences) { + public NewProtectedTermsFileDialog(List termsLists, DialogService dialogService, + FilePreferences filePreferences) { this.dialogService = dialogService; this.setTitle(Localization.lang("New protected terms file")); FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(Localization.lang("Protected terms file"), StandardFileType.TERMS) - .withDefaultExtension(Localization.lang("Protected terms file"), StandardFileType.TERMS) - .withInitialDirectory(filePreferences.getWorkingDirectory()) - .build(); + .addExtensionFilter(Localization.lang("Protected terms file"), StandardFileType.TERMS) + .withDefaultExtension(Localization.lang("Protected terms file"), StandardFileType.TERMS) + .withInitialDirectory(filePreferences.getWorkingDirectory()) + .build(); Button browse = new Button(Localization.lang("Browse")); browse.setOnAction(event -> this.dialogService.showFileSaveDialog(fileDialogConfiguration) - .ifPresent(file -> newFile.setText(file.toAbsolutePath().toString()))); + .ifPresent(file -> newFile.setText(file.toAbsolutePath().toString()))); TextField newDescription = new TextField(); - VBox container = new VBox(10, - new VBox(5, new Label(Localization.lang("Description")), newDescription), - new VBox(5, new Label(Localization.lang("File")), new HBox(10, newFile, browse)) - ); + VBox container = new VBox(10, new VBox(5, new Label(Localization.lang("Description")), newDescription), + new VBox(5, new Label(Localization.lang("File")), new HBox(10, newFile, browse))); getDialogPane().setContent(container); - getDialogPane().getButtonTypes().setAll( - ButtonType.OK, - ButtonType.CANCEL - ); + getDialogPane().getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL); setResultConverter(button -> { if (button == ButtonType.OK) { - ProtectedTermsList newList = new ProtectedTermsList(newDescription.getText(), new ArrayList<>(), newFile.getText(), false); + ProtectedTermsList newList = new ProtectedTermsList(newDescription.getText(), new ArrayList<>(), + newFile.getText(), false); newList.setEnabled(true); newList.createAndWriteHeading(newDescription.getText()); termsLists.add(new ProtectedTermsListItemModel(newList)); @@ -60,4 +58,5 @@ public NewProtectedTermsFileDialog(List termsLists, return null; }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsListItemModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsListItemModel.java index ad06b94b55c..741b8fc111f 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsListItemModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsListItemModel.java @@ -12,6 +12,7 @@ public class ProtectedTermsListItemModel { private final ProtectedTermsList termsList; + private final BooleanProperty enabledProperty = new SimpleBooleanProperty(); public ProtectedTermsListItemModel(ProtectedTermsList termsList) { @@ -39,4 +40,5 @@ public ReadOnlyBooleanProperty internalProperty() { public BooleanProperty enabledProperty() { return enabledProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsTab.java index 733dcec512f..8647a05ae01 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsTab.java @@ -27,19 +27,30 @@ * Dialog for managing term list files. */ public class ProtectedTermsTab extends AbstractPreferenceTabView implements PreferencesTab { - @FXML private TableView filesTable; - @FXML private TableColumn filesTableEnabledColumn; - @FXML private TableColumn filesTableDescriptionColumn; - @FXML private TableColumn filesTableFileColumn; - @FXML private TableColumn filesTableEditColumn; - @FXML private TableColumn filesTableDeleteColumn; - @Inject private ProtectedTermsLoader termsLoader; + @FXML + private TableView filesTable; + + @FXML + private TableColumn filesTableEnabledColumn; + + @FXML + private TableColumn filesTableDescriptionColumn; + + @FXML + private TableColumn filesTableFileColumn; + + @FXML + private TableColumn filesTableEditColumn; + + @FXML + private TableColumn filesTableDeleteColumn; + + @Inject + private ProtectedTermsLoader termsLoader; public ProtectedTermsTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -51,36 +62,37 @@ public String getTabName() { public void initialize() { viewModel = new ProtectedTermsTabViewModel(termsLoader, dialogService, preferences); - new ViewModelTableRowFactory() - .withContextMenu(this::createContextMenu) - .install(filesTable); + new ViewModelTableRowFactory().withContextMenu(this::createContextMenu) + .install(filesTable); filesTableEnabledColumn.setCellFactory(CheckBoxTableCell.forTableColumn(filesTableEnabledColumn)); filesTableEnabledColumn.setCellValueFactory(data -> data.getValue().enabledProperty()); - filesTableDescriptionColumn.setCellValueFactory(data -> BindingsHelper.constantOf(data.getValue().getTermsList().getDescription())); + filesTableDescriptionColumn + .setCellValueFactory(data -> BindingsHelper.constantOf(data.getValue().getTermsList().getDescription())); filesTableFileColumn.setCellValueFactory(data -> { ProtectedTermsList list = data.getValue().getTermsList(); if (list.isInternalList()) { return BindingsHelper.constantOf(Localization.lang("Internal list")); - } else { + } + else { return BindingsHelper.constantOf(list.getLocation()); } }); filesTableEditColumn.setCellValueFactory(data -> data.getValue().internalProperty().not()); new ValueTableCellFactory() - .withGraphic(none -> IconTheme.JabRefIcons.EDIT.getGraphicNode()) - .withVisibleExpression(ReadOnlyBooleanWrapper::new) - .withOnMouseClickedEvent((item, none) -> event -> viewModel.edit(item)) - .install(filesTableEditColumn); + .withGraphic(none -> IconTheme.JabRefIcons.EDIT.getGraphicNode()) + .withVisibleExpression(ReadOnlyBooleanWrapper::new) + .withOnMouseClickedEvent((item, none) -> event -> viewModel.edit(item)) + .install(filesTableEditColumn); filesTableDeleteColumn.setCellValueFactory(data -> data.getValue().internalProperty().not()); new ValueTableCellFactory() - .withGraphic(none -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) - .withVisibleExpression(ReadOnlyBooleanWrapper::new) - .withTooltip(none -> Localization.lang("Remove protected terms file")) - .withOnMouseClickedEvent((item, none) -> event -> viewModel.removeList(item)) - .install(filesTableDeleteColumn); + .withGraphic(none -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) + .withVisibleExpression(ReadOnlyBooleanWrapper::new) + .withTooltip(none -> Localization.lang("Remove protected terms file")) + .withOnMouseClickedEvent((item, none) -> event -> viewModel.removeList(item)) + .install(filesTableDeleteColumn); filesTable.itemsProperty().set(viewModel.termsFilesProperty()); } @@ -88,12 +100,15 @@ public void initialize() { private ContextMenu createContextMenu(ProtectedTermsListItemModel file) { ActionFactory factory = new ActionFactory(); ContextMenu contextMenu = new ContextMenu(); - contextMenu.getItems().addAll( - factory.createMenuItem(StandardActions.EDIT_LIST, new ProtectedTermsTab.ContextAction(StandardActions.EDIT_LIST, file)), - factory.createMenuItem(StandardActions.VIEW_LIST, new ProtectedTermsTab.ContextAction(StandardActions.VIEW_LIST, file)), - factory.createMenuItem(StandardActions.REMOVE_LIST, new ProtectedTermsTab.ContextAction(StandardActions.REMOVE_LIST, file)), - factory.createMenuItem(StandardActions.RELOAD_LIST, new ProtectedTermsTab.ContextAction(StandardActions.RELOAD_LIST, file)) - ); + contextMenu.getItems() + .addAll(factory.createMenuItem(StandardActions.EDIT_LIST, + new ProtectedTermsTab.ContextAction(StandardActions.EDIT_LIST, file)), + factory.createMenuItem(StandardActions.VIEW_LIST, + new ProtectedTermsTab.ContextAction(StandardActions.VIEW_LIST, file)), + factory.createMenuItem(StandardActions.REMOVE_LIST, + new ProtectedTermsTab.ContextAction(StandardActions.REMOVE_LIST, file)), + factory.createMenuItem(StandardActions.RELOAD_LIST, + new ProtectedTermsTab.ContextAction(StandardActions.RELOAD_LIST, file))); contextMenu.getItems().forEach(item -> item.setGraphic(null)); contextMenu.getStyleClass().add("context-menu"); @@ -113,17 +128,17 @@ private void createNewFile() { private class ContextAction extends SimpleCommand { private final StandardActions command; + private final ProtectedTermsListItemModel itemModel; public ContextAction(StandardActions command, ProtectedTermsListItemModel itemModel) { this.command = command; this.itemModel = itemModel; - this.executable.bind(BindingsHelper.constantOf( - switch (command) { - case EDIT_LIST, REMOVE_LIST, RELOAD_LIST -> !itemModel.getTermsList().isInternalList(); - default -> true; - })); + this.executable.bind(BindingsHelper.constantOf(switch (command) { + case EDIT_LIST, REMOVE_LIST, RELOAD_LIST -> !itemModel.getTermsList().isInternalList(); + default -> true; + })); } @Override @@ -135,5 +150,7 @@ public void execute() { case RELOAD_LIST -> viewModel.reloadList(itemModel); } } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsTabViewModel.java index da345cd066d..5c48c7a84eb 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsTabViewModel.java @@ -35,18 +35,24 @@ import org.slf4j.LoggerFactory; public class ProtectedTermsTabViewModel implements PreferenceTabViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(ProtectedTermsTabViewModel.class); private final ProtectedTermsLoader termsLoader; - private final ListProperty termsFilesProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + + private final ListProperty termsFilesProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); + private final DialogService dialogService; + private final ExternalApplicationsPreferences externalApplicationsPreferences; + private final FilePreferences filePreferences; + private final ProtectedTermsPreferences protectedTermsPreferences; - public ProtectedTermsTabViewModel(ProtectedTermsLoader termsLoader, - DialogService dialogService, - GuiPreferences preferences) { + public ProtectedTermsTabViewModel(ProtectedTermsLoader termsLoader, DialogService dialogService, + GuiPreferences preferences) { this.termsLoader = termsLoader; this.dialogService = dialogService; this.externalApplicationsPreferences = preferences.getExternalApplicationsPreferences(); @@ -57,7 +63,8 @@ public ProtectedTermsTabViewModel(ProtectedTermsLoader termsLoader, @Override public void setValues() { termsFilesProperty.clear(); - termsFilesProperty.addAll(termsLoader.getProtectedTermsLists().stream().map(ProtectedTermsListItemModel::new).toList()); + termsFilesProperty + .addAll(termsLoader.getProtectedTermsLists().stream().map(ProtectedTermsListItemModel::new).toList()); } @Override @@ -67,18 +74,23 @@ public void storeSettings() { List enabledInternalList = new ArrayList<>(); List disabledInternalList = new ArrayList<>(); - for (ProtectedTermsList list : termsFilesProperty.getValue().stream() - .map(ProtectedTermsListItemModel::getTermsList).toList()) { + for (ProtectedTermsList list : termsFilesProperty.getValue() + .stream() + .map(ProtectedTermsListItemModel::getTermsList) + .toList()) { if (list.isInternalList()) { if (list.isEnabled()) { enabledInternalList.add(list.getLocation()); - } else { + } + else { disabledInternalList.add(list.getLocation()); } - } else { + } + else { if (list.isEnabled()) { enabledExternalList.add(list.getLocation()); - } else { + } + else { disabledExternalList.add(list.getLocation()); } } @@ -94,24 +106,24 @@ public void storeSettings() { public void addFile() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(Localization.lang("Protected terms file"), StandardFileType.TERMS) - .withDefaultExtension(Localization.lang("Protected terms file"), StandardFileType.TERMS) - .withInitialDirectory(filePreferences.getWorkingDirectory()) - .build(); - - dialogService.showFileOpenDialog(fileDialogConfiguration) - .ifPresent(file -> { - Path fileName = file.toAbsolutePath(); - termsFilesProperty.add(new ProtectedTermsListItemModel(ProtectedTermsLoader.readProtectedTermsListFromFile(fileName, true))); - }); + .addExtensionFilter(Localization.lang("Protected terms file"), StandardFileType.TERMS) + .withDefaultExtension(Localization.lang("Protected terms file"), StandardFileType.TERMS) + .withInitialDirectory(filePreferences.getWorkingDirectory()) + .build(); + + dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(file -> { + Path fileName = file.toAbsolutePath(); + termsFilesProperty.add(new ProtectedTermsListItemModel( + ProtectedTermsLoader.readProtectedTermsListFromFile(fileName, true))); + }); } public void removeList(ProtectedTermsListItemModel itemModel) { ProtectedTermsList list = itemModel.getTermsList(); - if (!list.isInternalList() && dialogService.showConfirmationDialogAndWait(Localization.lang("Remove protected terms file"), - Localization.lang("Are you sure you want to remove the protected terms file?"), - Localization.lang("Remove protected terms file"), - Localization.lang("Cancel"))) { + if (!list.isInternalList() + && dialogService.showConfirmationDialogAndWait(Localization.lang("Remove protected terms file"), + Localization.lang("Are you sure you want to remove the protected terms file?"), + Localization.lang("Remove protected terms file"), Localization.lang("Cancel"))) { itemModel.enabledProperty().setValue(false); if (!termsFilesProperty.remove(itemModel)) { LOGGER.info("Problem removing protected terms file"); @@ -120,19 +132,21 @@ public void removeList(ProtectedTermsListItemModel itemModel) { } public void createNewFile() { - dialogService.showCustomDialogAndWait(new NewProtectedTermsFileDialog(termsFilesProperty, dialogService, filePreferences)); + dialogService.showCustomDialogAndWait( + new NewProtectedTermsFileDialog(termsFilesProperty, dialogService, filePreferences)); } public void edit(ProtectedTermsListItemModel file) { Optional termsFileType = OptionalUtil.orElse( ExternalFileTypes.getExternalFileTypeByExt("terms", externalApplicationsPreferences), - ExternalFileTypes.getExternalFileTypeByExt("txt", externalApplicationsPreferences) - ); + ExternalFileTypes.getExternalFileTypeByExt("txt", externalApplicationsPreferences)); String fileName = file.getTermsList().getLocation(); try { - NativeDesktop.openExternalFileAnyFormat(new BibDatabaseContext(), externalApplicationsPreferences, filePreferences, fileName, termsFileType); - } catch (IOException e) { + NativeDesktop.openExternalFileAnyFormat(new BibDatabaseContext(), externalApplicationsPreferences, + filePreferences, fileName, termsFileType); + } + catch (IOException e) { LOGGER.warn("Problem open protected terms file editor", e); } } @@ -150,16 +164,19 @@ public void displayContent(ProtectedTermsListItemModel itemModel) { DialogPane dialogPane = new DialogPane(); dialogPane.setContent(scrollPane); - dialogService.showCustomDialogAndWait(list.getDescription() + " - " + list.getLocation(), dialogPane, ButtonType.OK); + dialogService.showCustomDialogAndWait(list.getDescription() + " - " + list.getLocation(), dialogPane, + ButtonType.OK); } public void reloadList(ProtectedTermsListItemModel oldItemModel) { ProtectedTermsList oldList = oldItemModel.getTermsList(); - ProtectedTermsList newList = ProtectedTermsLoader.readProtectedTermsListFromFile(Path.of(oldList.getLocation()), oldList.isEnabled()); + ProtectedTermsList newList = ProtectedTermsLoader.readProtectedTermsListFromFile(Path.of(oldList.getLocation()), + oldList.isEnabled()); int index = termsFilesProperty.indexOf(oldItemModel); if (index >= 0) { termsFilesProperty.set(index, new ProtectedTermsListItemModel(newList)); - } else { + } + else { LOGGER.warn("Problem reloading protected terms file {}.", oldList.getLocation()); } } @@ -167,4 +184,5 @@ public void reloadList(ProtectedTermsListItemModel oldItemModel) { public ListProperty termsFilesProperty() { return termsFilesProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/table/TableTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/table/TableTab.java index 37ea2359c39..d08f44b5209 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/table/TableTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/table/TableTab.java @@ -29,29 +29,55 @@ public class TableTab extends AbstractPreferenceTabView implements PreferencesTab { - @FXML private TableView columnsList; - @FXML private TableColumn nameColumn; - @FXML private TableColumn actionsColumn; - @FXML private ComboBox addColumnName; - @FXML private CheckBox specialFieldsEnable; - @FXML private Button specialFieldsHelp; - @FXML private CheckBox extraFileColumnsEnable; - @FXML private CheckBox autoResizeColumns; - - @FXML private RadioButton namesNatbib; - @FXML private RadioButton nameAsIs; - @FXML private RadioButton nameFirstLast; - @FXML private RadioButton nameLastFirst; - @FXML private RadioButton abbreviationDisabled; - @FXML private RadioButton abbreviationEnabled; - @FXML private RadioButton abbreviationLastNameOnly; + @FXML + private TableView columnsList; + + @FXML + private TableColumn nameColumn; + + @FXML + private TableColumn actionsColumn; + + @FXML + private ComboBox addColumnName; + + @FXML + private CheckBox specialFieldsEnable; + + @FXML + private Button specialFieldsHelp; + + @FXML + private CheckBox extraFileColumnsEnable; + + @FXML + private CheckBox autoResizeColumns; + + @FXML + private RadioButton namesNatbib; + + @FXML + private RadioButton nameAsIs; + + @FXML + private RadioButton nameFirstLast; + + @FXML + private RadioButton nameLastFirst; + + @FXML + private RadioButton abbreviationDisabled; + + @FXML + private RadioButton abbreviationEnabled; + + @FXML + private RadioButton abbreviationLastNameOnly; private final ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer(); public TableTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -66,26 +92,25 @@ public void initialize() { setupBindings(); ActionFactory actionFactory = new ActionFactory(); - actionFactory.configureIconButton(StandardActions.HELP_SPECIAL_FIELDS, new HelpAction(HelpFile.SPECIAL_FIELDS, dialogService, preferences.getExternalApplicationsPreferences()), specialFieldsHelp); + actionFactory.configureIconButton(StandardActions.HELP_SPECIAL_FIELDS, new HelpAction(HelpFile.SPECIAL_FIELDS, + dialogService, preferences.getExternalApplicationsPreferences()), specialFieldsHelp); } private void setupTable() { nameColumn.setSortable(false); nameColumn.setReorderable(false); nameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); - new ValueTableCellFactory() - .withText(name -> name) - .install(nameColumn); + new ValueTableCellFactory().withText(name -> name).install(nameColumn); actionsColumn.setSortable(false); actionsColumn.setReorderable(false); actionsColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); new ValueTableCellFactory() - .withGraphic(item -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) - .withTooltip(name -> Localization.lang("Remove column") + " " + name) - .withOnMouseClickedEvent(item -> evt -> - viewModel.removeColumn(columnsList.getFocusModel().getFocusedItem())) - .install(actionsColumn); + .withGraphic(item -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) + .withTooltip(name -> Localization.lang("Remove column") + " " + name) + .withOnMouseClickedEvent( + item -> evt -> viewModel.removeColumn(columnsList.getFocusModel().getFocusedItem())) + .install(actionsColumn); viewModel.selectedColumnModelProperty().setValue(columnsList.getSelectionModel()); columnsList.addEventFilter(KeyEvent.KEY_PRESSED, event -> { @@ -97,9 +122,8 @@ private void setupTable() { columnsList.itemsProperty().bind(viewModel.columnsListProperty()); - new ViewModelListCellFactory() - .withText(MainTableColumnModel::getDisplayName) - .install(addColumnName); + new ViewModelListCellFactory().withText(MainTableColumnModel::getDisplayName) + .install(addColumnName); addColumnName.itemsProperty().bind(viewModel.availableColumnsProperty()); addColumnName.valueProperty().bindBidirectional(viewModel.addColumnProperty()); addColumnName.setConverter(TableTabViewModel.columnNameStringConverter); @@ -111,7 +135,8 @@ private void setupTable() { }); validationVisualizer.setDecoration(new IconValidationDecorator()); - Platform.runLater(() -> validationVisualizer.initVisualization(viewModel.columnsListValidationStatus(), columnsList)); + Platform.runLater( + () -> validationVisualizer.initVisualization(viewModel.columnsListValidationStatus(), columnsList)); } private void setupBindings() { @@ -147,4 +172,5 @@ public void sortColumnDown() { public void addColumn() { viewModel.insertColumnInList(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/table/TableTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/table/TableTabViewModel.java index 12ae933265e..33172c74e5c 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/table/TableTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/table/TableTabViewModel.java @@ -42,7 +42,8 @@ public class TableTabViewModel implements PreferenceTabViewModel { public String toString(MainTableColumnModel object) { if (object != null) { return object.getName(); - } else { + } + else { return ""; } } @@ -53,30 +54,49 @@ public MainTableColumnModel fromString(String string) { } }; - private final ListProperty columnsListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ObjectProperty> selectedColumnModelProperty = new SimpleObjectProperty<>(new NoSelectionModel<>()); - private final ListProperty availableColumnsProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty columnsListProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); + + private final ObjectProperty> selectedColumnModelProperty = new SimpleObjectProperty<>( + new NoSelectionModel<>()); + + private final ListProperty availableColumnsProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); + private final ObjectProperty addColumnProperty = new SimpleObjectProperty<>(); + private final BooleanProperty specialFieldsEnabledProperty = new SimpleBooleanProperty(); + private final BooleanProperty extraFileColumnsEnabledProperty = new SimpleBooleanProperty(); + private final BooleanProperty autoResizeColumnsProperty = new SimpleBooleanProperty(); private final BooleanProperty namesNatbibProperty = new SimpleBooleanProperty(); + private final BooleanProperty nameAsIsProperty = new SimpleBooleanProperty(); + private final BooleanProperty nameFirstLastProperty = new SimpleBooleanProperty(); + private final BooleanProperty nameLastFirstProperty = new SimpleBooleanProperty(); + private final BooleanProperty abbreviationDisabledProperty = new SimpleBooleanProperty(); + private final BooleanProperty abbreviationEnabledProperty = new SimpleBooleanProperty(); + private final BooleanProperty abbreviationLastNameOnlyProperty = new SimpleBooleanProperty(); private final Validator columnsNotEmptyValidator; private final DialogService dialogService; + private final GuiPreferences preferences; private ColumnPreferences initialColumnPreferences; + private final SpecialFieldsPreferences specialFieldsPreferences; + private final NameDisplayPreferences nameDisplayPreferences; + private final MainTablePreferences mainTablePreferences; public TableTabViewModel(DialogService dialogService, GuiPreferences preferences) { @@ -89,7 +109,8 @@ public TableTabViewModel(DialogService dialogService, GuiPreferences preferences specialFieldsEnabledProperty.addListener((observable, oldValue, newValue) -> { if (newValue) { insertSpecialFieldColumns(); - } else { + } + else { removeSpecialFieldColumns(); } }); @@ -97,18 +118,15 @@ public TableTabViewModel(DialogService dialogService, GuiPreferences preferences extraFileColumnsEnabledProperty.addListener((observable, oldValue, newValue) -> { if (newValue) { insertExtraFileColumns(); - } else { + } + else { removeExtraFileColumns(); } }); - columnsNotEmptyValidator = new FunctionBasedValidator<>( - columnsListProperty, - list -> !list.isEmpty(), - ValidationMessage.error("%s > %s %n %n %s".formatted( - Localization.lang("Entry table columns"), - Localization.lang("Columns"), - Localization.lang("List must not be empty.")))); + columnsNotEmptyValidator = new FunctionBasedValidator<>(columnsListProperty, list -> !list.isEmpty(), + ValidationMessage.error("%s > %s %n %n %s".formatted(Localization.lang("Entry table columns"), + Localization.lang("Columns"), Localization.lang("List must not be empty.")))); } @Override @@ -122,8 +140,7 @@ public void setValues() { fillColumnList(); availableColumnsProperty.clear(); - availableColumnsProperty.addAll( - new MainTableColumnModel(MainTableColumnModel.Type.INDEX), + availableColumnsProperty.addAll(new MainTableColumnModel(MainTableColumnModel.Type.INDEX), new MainTableColumnModel(MainTableColumnModel.Type.LINKED_IDENTIFIER), new MainTableColumnModel(MainTableColumnModel.Type.GROUPS), new MainTableColumnModel(MainTableColumnModel.Type.GROUP_ICONS), @@ -132,13 +149,13 @@ public void setValues() { new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, StandardField.OWNER.getName()), new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, StandardField.GROUPS.getName()), new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, InternalField.KEY_FIELD.getName()), - new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, InternalField.TYPE_HEADER.getName()) - ); + new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, InternalField.TYPE_HEADER.getName())); - EnumSet.allOf(StandardField.class).stream() - .map(Field::getName) - .map(name -> new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, name)) - .forEach(item -> availableColumnsProperty.getValue().add(item)); + EnumSet.allOf(StandardField.class) + .stream() + .map(Field::getName) + .map(name -> new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, name)) + .forEach(item -> availableColumnsProperty.getValue().add(item)); if (specialFieldsEnabledProperty.getValue()) { insertSpecialFieldColumns(); @@ -170,22 +187,26 @@ public void fillColumnList() { } private void insertSpecialFieldColumns() { - EnumSet.allOf(SpecialField.class).stream() - .map(Field::getName) - .map(name -> new MainTableColumnModel(MainTableColumnModel.Type.SPECIALFIELD, name)) - .forEach(item -> availableColumnsProperty.getValue().addFirst(item)); + EnumSet.allOf(SpecialField.class) + .stream() + .map(Field::getName) + .map(name -> new MainTableColumnModel(MainTableColumnModel.Type.SPECIALFIELD, name)) + .forEach(item -> availableColumnsProperty.getValue().addFirst(item)); } private void removeSpecialFieldColumns() { columnsListProperty.getValue().removeIf(column -> column.getType() == MainTableColumnModel.Type.SPECIALFIELD); - availableColumnsProperty.getValue().removeIf(column -> column.getType() == MainTableColumnModel.Type.SPECIALFIELD); + availableColumnsProperty.getValue() + .removeIf(column -> column.getType() == MainTableColumnModel.Type.SPECIALFIELD); } private void insertExtraFileColumns() { - preferences.getExternalApplicationsPreferences().getExternalFileTypes().stream() - .map(ExternalFileType::getName) - .map(name -> new MainTableColumnModel(MainTableColumnModel.Type.EXTRAFILE, name)) - .forEach(item -> availableColumnsProperty.getValue().add(item)); + preferences.getExternalApplicationsPreferences() + .getExternalFileTypes() + .stream() + .map(ExternalFileType::getName) + .map(name -> new MainTableColumnModel(MainTableColumnModel.Type.EXTRAFILE, name)) + .forEach(item -> availableColumnsProperty.getValue().add(item)); } private void removeExtraFileColumns() { @@ -198,7 +219,11 @@ public void insertColumnInList() { return; } - if (columnsListProperty.getValue().stream().filter(item -> item.equals(addColumnProperty.getValue())).findAny().isEmpty()) { + if (columnsListProperty.getValue() + .stream() + .filter(item -> item.equals(addColumnProperty.getValue())) + .findAny() + .isEmpty()) { columnsListProperty.add(addColumnProperty.getValue()); addColumnProperty.setValue(null); } @@ -242,19 +267,24 @@ public void storeSettings() { if (nameLastFirstProperty.getValue()) { nameDisplayPreferences.setDisplayStyle(DisplayStyle.LASTNAME_FIRSTNAME); - } else if (namesNatbibProperty.getValue()) { + } + else if (namesNatbibProperty.getValue()) { nameDisplayPreferences.setDisplayStyle(DisplayStyle.NATBIB); - } else if (nameAsIsProperty.getValue()) { + } + else if (nameAsIsProperty.getValue()) { nameDisplayPreferences.setDisplayStyle(DisplayStyle.AS_IS); - } else if (nameFirstLastProperty.getValue()) { + } + else if (nameFirstLastProperty.getValue()) { nameDisplayPreferences.setDisplayStyle(DisplayStyle.FIRSTNAME_LASTNAME); } if (abbreviationDisabledProperty.getValue()) { nameDisplayPreferences.setAbbreviationStyle(AbbreviationStyle.NONE); - } else if (abbreviationEnabledProperty.getValue()) { + } + else if (abbreviationEnabledProperty.getValue()) { nameDisplayPreferences.setAbbreviationStyle(AbbreviationStyle.FULL); - } else if (abbreviationLastNameOnlyProperty.getValue()) { + } + else if (abbreviationLastNameOnlyProperty.getValue()) { nameDisplayPreferences.setAbbreviationStyle(AbbreviationStyle.LASTNAME_ONLY); } } @@ -267,8 +297,8 @@ ValidationStatus columnsListValidationStatus() { public boolean validateSettings() { ValidationStatus validationStatus = columnsListValidationStatus(); if (!validationStatus.isValid()) { - validationStatus.getHighestMessage().ifPresent(message -> - dialogService.showErrorDialogAndWait(message.getMessage())); + validationStatus.getHighestMessage() + .ifPresent(message -> dialogService.showErrorDialogAndWait(message.getMessage())); return false; } return true; @@ -329,4 +359,5 @@ public BooleanProperty abbreviationEnabledProperty() { public BooleanProperty abbreviationLastNameOnlyProperty() { return abbreviationLastNameOnlyProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.java index 93ff6b414dc..90ee1fef1a4 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.java @@ -30,41 +30,79 @@ public class WebSearchTab extends AbstractPreferenceTabView implements PreferencesTab { - @FXML private CheckBox enableWebSearch; - @FXML private CheckBox warnAboutDuplicatesOnImport; - @FXML private CheckBox downloadLinkedOnlineFiles; - @FXML private CheckBox keepDownloadUrl; - @FXML private CheckBox addImportedEntries; - @FXML private TextField addImportedEntriesGroupName; - @FXML private ComboBox defaultPlainCitationParser; - @FXML private TextField citationsRelationStoreTTL; - - @FXML private CheckBox useCustomDOI; - @FXML private TextField useCustomDOIName; - - @FXML private CheckBox grobidEnabled; - @FXML private TextField grobidURL; - - @FXML private TableView apiKeySelectorTable; - @FXML private TableColumn apiKeyName; - @FXML private TableColumn customApiKey; - @FXML private TableColumn useCustomApiKey; - @FXML private Button testCustomApiKey; - - @FXML private CheckBox persistApiKeys; - @FXML private SplitPane persistentTooltipWrapper; // The disabled persistApiKeys control does not show tooltips - @FXML private TableView catalogTable; - @FXML private TableColumn catalogEnabledColumn; - @FXML private TableColumn catalogColumn; + @FXML + private CheckBox enableWebSearch; + + @FXML + private CheckBox warnAboutDuplicatesOnImport; + + @FXML + private CheckBox downloadLinkedOnlineFiles; + + @FXML + private CheckBox keepDownloadUrl; + + @FXML + private CheckBox addImportedEntries; + + @FXML + private TextField addImportedEntriesGroupName; + + @FXML + private ComboBox defaultPlainCitationParser; + + @FXML + private TextField citationsRelationStoreTTL; + + @FXML + private CheckBox useCustomDOI; + + @FXML + private TextField useCustomDOIName; + + @FXML + private CheckBox grobidEnabled; + + @FXML + private TextField grobidURL; + + @FXML + private TableView apiKeySelectorTable; + + @FXML + private TableColumn apiKeyName; + + @FXML + private TableColumn customApiKey; + + @FXML + private TableColumn useCustomApiKey; + + @FXML + private Button testCustomApiKey; + + @FXML + private CheckBox persistApiKeys; + + @FXML + private SplitPane persistentTooltipWrapper; // The disabled persistApiKeys control + // does not show tooltips + + @FXML + private TableView catalogTable; + + @FXML + private TableColumn catalogEnabledColumn; + + @FXML + private TableColumn catalogColumn; private final ReadOnlyBooleanProperty refAiEnabled; public WebSearchTab(ReadOnlyBooleanProperty refAiEnabled) { this.refAiEnabled = refAiEnabled; - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -76,7 +114,8 @@ public void initialize() { this.viewModel = new WebSearchTabViewModel(preferences, dialogService, refAiEnabled); enableWebSearch.selectedProperty().bindBidirectional(viewModel.enableWebSearchProperty()); - warnAboutDuplicatesOnImport.selectedProperty().bindBidirectional(viewModel.warnAboutDuplicatesOnImportProperty()); + warnAboutDuplicatesOnImport.selectedProperty() + .bindBidirectional(viewModel.warnAboutDuplicatesOnImportProperty()); downloadLinkedOnlineFiles.selectedProperty().bindBidirectional(viewModel.shouldDownloadLinkedOnlineFiles()); keepDownloadUrl.selectedProperty().bindBidirectional(viewModel.shouldKeepDownloadUrl()); @@ -84,30 +123,26 @@ public void initialize() { addImportedEntriesGroupName.textProperty().bindBidirectional(viewModel.getAddImportedEntriesGroupName()); addImportedEntriesGroupName.disableProperty().bind(addImportedEntries.selectedProperty().not()); - new ViewModelListCellFactory() - .withText(PlainCitationParserChoice::getLocalizedName) - .install(defaultPlainCitationParser); + new ViewModelListCellFactory().withText(PlainCitationParserChoice::getLocalizedName) + .install(defaultPlainCitationParser); defaultPlainCitationParser.itemsProperty().bind(viewModel.plainCitationParsers()); defaultPlainCitationParser.valueProperty().bindBidirectional(viewModel.defaultPlainCitationParserProperty()); - viewModel.citationsRelationsStoreTTLProperty() - .addListener((_, _, newValue) -> { - if (newValue != null && !newValue.toString().equals(citationsRelationStoreTTL.getText())) { - citationsRelationStoreTTL.setText(newValue.toString()); - } - }); - citationsRelationStoreTTL - .textProperty() - .addListener((_, _, newValue) -> { - if (StringUtil.isBlank(newValue)) { - return; - } - if (!newValue.matches("\\d*")) { - citationsRelationStoreTTL.setText(newValue.replaceAll("\\D", "")); - return; - } - viewModel.citationsRelationsStoreTTLProperty().set(Integer.parseInt(newValue)); - }); + viewModel.citationsRelationsStoreTTLProperty().addListener((_, _, newValue) -> { + if (newValue != null && !newValue.toString().equals(citationsRelationStoreTTL.getText())) { + citationsRelationStoreTTL.setText(newValue.toString()); + } + }); + citationsRelationStoreTTL.textProperty().addListener((_, _, newValue) -> { + if (StringUtil.isBlank(newValue)) { + return; + } + if (!newValue.matches("\\d*")) { + citationsRelationStoreTTL.setText(newValue.replaceAll("\\D", "")); + return; + } + viewModel.citationsRelationsStoreTTLProperty().set(Integer.parseInt(newValue)); + }); grobidEnabled.selectedProperty().bindBidirectional(viewModel.grobidEnabledProperty()); grobidURL.textProperty().bindBidirectional(viewModel.grobidURLProperty()); @@ -117,13 +152,11 @@ public void initialize() { useCustomDOIName.textProperty().bindBidirectional(viewModel.useCustomDOINameProperty()); useCustomDOIName.disableProperty().bind(useCustomDOI.selectedProperty().not()); - new ViewModelTableRowFactory() - .withOnMouseClickedEvent((entry, event) -> { - if (event.getButton() == MouseButton.PRIMARY) { - entry.setEnabled(!entry.isEnabled()); - } - }) - .install(catalogTable); + new ViewModelTableRowFactory().withOnMouseClickedEvent((entry, event) -> { + if (event.getButton() == MouseButton.PRIMARY) { + entry.setEnabled(!entry.isEnabled()); + } + }).install(catalogTable); catalogColumn.setReorderable(false); catalogColumn.setCellFactory(TextFieldTableCell.forTableColumn()); @@ -139,8 +172,7 @@ public void initialize() { testCustomApiKey.setDisable(true); - new ViewModelTableRowFactory() - .install(apiKeySelectorTable); + new ViewModelTableRowFactory().install(apiKeySelectorTable); apiKeySelectorTable.getSelectionModel().selectedItemProperty().addListener((_, oldValue, newValue) -> { if (oldValue != null) { @@ -174,7 +206,8 @@ public void initialize() { EasyBind.subscribe(viewModel.apiKeyPersistAvailable(), available -> { if (!available) { persistentTooltipWrapper.setTooltip(new Tooltip(Localization.lang("Credential store not available."))); - } else { + } + else { persistentTooltipWrapper.setTooltip(null); } }); @@ -199,4 +232,5 @@ private void updateFetcherApiKey(FetcherApiKey apiKey) { void checkCustomApiKey() { viewModel.checkCustomApiKey(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java index f5785fd2933..c10c2ff8f29 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java @@ -42,44 +42,64 @@ import kong.unirest.core.UnirestException; public class WebSearchTabViewModel implements PreferenceTabViewModel { + private final BooleanProperty enableWebSearchProperty = new SimpleBooleanProperty(); + private final BooleanProperty warnAboutDuplicatesOnImportProperty = new SimpleBooleanProperty(); + private final BooleanProperty shouldDownloadLinkedOnlineFiles = new SimpleBooleanProperty(); + private final BooleanProperty shouldkeepDownloadUrl = new SimpleBooleanProperty(); - private final ListProperty plainCitationParsers = - new SimpleListProperty<>(FXCollections.observableArrayList(PlainCitationParserChoice.values())); + private final ListProperty plainCitationParsers = new SimpleListProperty<>( + FXCollections.observableArrayList(PlainCitationParserChoice.values())); + private final ObjectProperty defaultPlainCitationParser = new SimpleObjectProperty<>(); private final IntegerProperty citationsRelationStoreTTL = new SimpleIntegerProperty(); private final BooleanProperty addImportedEntries = new SimpleBooleanProperty(); + private final StringProperty addImportedEntriesGroupName = new SimpleStringProperty(""); private final BooleanProperty useCustomDOIProperty = new SimpleBooleanProperty(); + private final StringProperty useCustomDOINameProperty = new SimpleStringProperty(""); private final ObservableList catalogs = FXCollections.observableArrayList(); + private final BooleanProperty grobidEnabledProperty = new SimpleBooleanProperty(); + private final StringProperty grobidURLProperty = new SimpleStringProperty(""); private final ObservableList apiKeys = FXCollections.observableArrayList(); + private final ObjectProperty selectedApiKeyProperty = new SimpleObjectProperty<>(); + private final BooleanProperty apikeyPersistProperty = new SimpleBooleanProperty(); + private final BooleanProperty apikeyPersistAvailableProperty = new SimpleBooleanProperty(); private final DialogService dialogService; + private final CliPreferences preferences; + private final DOIPreferences doiPreferences; + private final GrobidPreferences grobidPreferences; + private final ImporterPreferences importerPreferences; + private final FilePreferences filePreferences; + private final ImportFormatPreferences importFormatPreferences; + private final LibraryPreferences libraryPreferences; private final ReadOnlyBooleanProperty refAiEnabled; - public WebSearchTabViewModel(CliPreferences preferences, DialogService dialogService, ReadOnlyBooleanProperty refAiEnabled) { + public WebSearchTabViewModel(CliPreferences preferences, DialogService dialogService, + ReadOnlyBooleanProperty refAiEnabled) { this.dialogService = dialogService; this.preferences = preferences; this.importerPreferences = preferences.getImporterPreferences(); @@ -102,7 +122,8 @@ private void setupPlainCitationParsers(CliPreferences preferences) { refAiEnabled.addListener((observable, oldValue, newValue) -> { if (newValue) { plainCitationParsers.add(PlainCitationParserChoice.LLM); - } else { + } + else { PlainCitationParserChoice oldChoice = defaultPlainCitationParser.get(); plainCitationParsers.remove(PlainCitationParserChoice.LLM); @@ -120,7 +141,8 @@ private void setupPlainCitationParsers(CliPreferences preferences) { grobidEnabledProperty.addListener((observable, oldValue, newValue) -> { if (newValue) { plainCitationParsers.add(PlainCitationParserChoice.GROBID); - } else { + } + else { PlainCitationParserChoice oldChoice = defaultPlainCitationParser.get(); plainCitationParsers.remove(PlainCitationParserChoice.GROBID); @@ -149,21 +171,23 @@ public void setValues() { grobidEnabledProperty.setValue(grobidPreferences.isGrobidEnabled()); grobidURLProperty.setValue(grobidPreferences.getGrobidURL()); - apiKeys.setAll(preferences.getImporterPreferences().getApiKeys().stream() - .map(apiKey -> new FetcherApiKey(apiKey.getName(), apiKey.shouldUse(), apiKey.getKey())) - .toList()); + apiKeys.setAll(preferences.getImporterPreferences() + .getApiKeys() + .stream() + .map(apiKey -> new FetcherApiKey(apiKey.getName(), apiKey.shouldUse(), apiKey.getKey())) + .toList()); apikeyPersistAvailableProperty.setValue(OS.isKeyringAvailable()); apikeyPersistProperty.setValue(preferences.getImporterPreferences().shouldPersistCustomKeys()); catalogs.addAll(WebFetchers.getSearchBasedFetchers(importFormatPreferences, importerPreferences) - .stream() - .map(SearchBasedFetcher::getName) - .filter(name -> !CompositeSearchBasedFetcher.FETCHER_NAME.equals(name)) - .map(name -> { - boolean enabled = importerPreferences.getCatalogs().contains(name); - return new StudyCatalogItem(name, enabled); - }) - .toList()); + .stream() + .map(SearchBasedFetcher::getName) + .filter(name -> !CompositeSearchBasedFetcher.FETCHER_NAME.equals(name)) + .map(name -> { + boolean enabled = importerPreferences.getCatalogs().contains(name); + return new StudyCatalogItem(name, enabled); + }) + .toList()); } @Override @@ -173,9 +197,11 @@ public void storeSettings() { filePreferences.setDownloadLinkedFiles(shouldDownloadLinkedOnlineFiles.getValue()); filePreferences.setKeepDownloadUrl(shouldkeepDownloadUrl.getValue()); libraryPreferences.setAddImportedEntries(addImportedEntries.getValue()); - if (addImportedEntriesGroupName.getValue().isEmpty() || addImportedEntriesGroupName.getValue().startsWith(" ")) { + if (addImportedEntriesGroupName.getValue().isEmpty() + || addImportedEntriesGroupName.getValue().startsWith(" ")) { libraryPreferences.setAddImportedEntriesGroupName(Localization.lang("Imported entries")); - } else { + } + else { libraryPreferences.setAddImportedEntriesGroupName(addImportedEntriesGroupName.getValue()); } importerPreferences.setDefaultPlainCitationParser(defaultPlainCitationParser.getValue()); @@ -186,11 +212,10 @@ public void storeSettings() { grobidPreferences.setGrobidURL(grobidURLProperty.getValue()); doiPreferences.setUseCustom(useCustomDOIProperty.get()); doiPreferences.setDefaultBaseURI(useCustomDOINameProperty.getValue().trim()); - importerPreferences.setCatalogs( - FXCollections.observableList(catalogs.stream() - .filter(StudyCatalogItem::isEnabled) - .map(StudyCatalogItem::getName) - .collect(Collectors.toList()))); + importerPreferences.setCatalogs(FXCollections.observableList(catalogs.stream() + .filter(StudyCatalogItem::isEnabled) + .map(StudyCatalogItem::getName) + .collect(Collectors.toList()))); importerPreferences.setPersistCustomKeys(apikeyPersistProperty.get()); preferences.getImporterPreferences().getApiKeys().clear(); if (apikeyPersistAvailableProperty.get()) { @@ -273,25 +298,21 @@ public IntegerProperty citationsRelationsStoreTTLProperty() { public void checkCustomApiKey() { final String apiKeyName = selectedApiKeyProperty.get().getName(); - final Optional fetcherOpt = - WebFetchers.getCustomizableKeyFetchers( - preferences.getImportFormatPreferences(), - preferences.getImporterPreferences()) - .stream() - .filter(fetcher -> fetcher.getName().equals(apiKeyName)) - .findFirst(); + final Optional fetcherOpt = WebFetchers + .getCustomizableKeyFetchers(preferences.getImportFormatPreferences(), preferences.getImporterPreferences()) + .stream() + .filter(fetcher -> fetcher.getName().equals(apiKeyName)) + .findFirst(); if (fetcherOpt.isEmpty()) { - dialogService.showErrorDialogAndWait( - Localization.lang("Check %0 API Key Setting", apiKeyName), + dialogService.showErrorDialogAndWait(Localization.lang("Check %0 API Key Setting", apiKeyName), Localization.lang("Fetcher unknown!")); return; } final String testUrlWithoutApiKey = fetcherOpt.get().getTestUrl(); if (testUrlWithoutApiKey == null) { - dialogService.showWarningDialogAndWait( - Localization.lang("Check %0 API Key Setting", apiKeyName), + dialogService.showWarningDialogAndWait(Localization.lang("Check %0 API Key Setting", apiKeyName), Localization.lang("Fetcher cannot be tested!")); return; } @@ -303,20 +324,26 @@ public void checkCustomApiKey() { URLDownload urlDownload; try { urlDownload = new URLDownload(testUrlWithoutApiKey + apiKey); - // The HEAD request cannot be used because its response is not 200 (maybe 404 or 596...). + // The HEAD request cannot be used because its response is not 200 (maybe + // 404 or 596...). int statusCode = ((HttpURLConnection) urlDownload.getSource().openConnection()).getResponseCode(); keyValid = (statusCode >= 200) && (statusCode < 300); - } catch (IOException | UnirestException e) { + } + catch (IOException | UnirestException e) { keyValid = false; } - } else { + } + else { keyValid = false; } if (keyValid) { - dialogService.showInformationDialogAndWait(Localization.lang("Check %0 API Key Setting", apiKeyName), Localization.lang("Connection successful!")); - } else { - dialogService.showErrorDialogAndWait(Localization.lang("Check %0 API Key Setting", apiKeyName), Localization.lang("Connection failed!")); + dialogService.showInformationDialogAndWait(Localization.lang("Check %0 API Key Setting", apiKeyName), + Localization.lang("Connection successful!")); + } + else { + dialogService.showErrorDialogAndWait(Localization.lang("Check %0 API Key Setting", apiKeyName), + Localization.lang("Connection failed!")); } } @@ -324,4 +351,5 @@ public void checkCustomApiKey() { public boolean validateSettings() { return getCatalogs().stream().anyMatch(StudyCatalogItem::isEnabled); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/xmp/XmpPrivacyTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/xmp/XmpPrivacyTab.java index ec486b606fb..1009c265d8e 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/xmp/XmpPrivacyTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/xmp/XmpPrivacyTab.java @@ -30,22 +30,34 @@ public class XmpPrivacyTab extends AbstractPreferenceTabView implements PreferencesTab { - @FXML private CheckBox enableXmpFilter; - @FXML private TableView filterList; - @FXML private TableColumn fieldColumn; - @FXML private TableColumn actionsColumn; - @FXML private ComboBox addFieldName; - @FXML private Button addField; + @FXML + private CheckBox enableXmpFilter; - @Inject private CliPreferences preferences; - @Inject private UndoManager undoManager; + @FXML + private TableView filterList; + + @FXML + private TableColumn fieldColumn; + + @FXML + private TableColumn actionsColumn; + + @FXML + private ComboBox addFieldName; + + @FXML + private Button addField; + + @Inject + private CliPreferences preferences; + + @Inject + private UndoManager undoManager; private final ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer(); public XmpPrivacyTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -65,18 +77,17 @@ public void initialize() { fieldColumn.setReorderable(false); fieldColumn.setCellValueFactory(cellData -> BindingsHelper.constantOf(cellData.getValue())); new ValueTableCellFactory() - .withText(item -> FieldsUtil.getNameWithType(item, preferences, undoManager)) - .install(fieldColumn); + .withText(item -> FieldsUtil.getNameWithType(item, preferences, undoManager)) + .install(fieldColumn); actionsColumn.setSortable(false); actionsColumn.setReorderable(false); actionsColumn.setCellValueFactory(cellData -> BindingsHelper.constantOf(cellData.getValue())); new ValueTableCellFactory() - .withGraphic(item -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) - .withTooltip(item -> Localization.lang("Remove") + " " + item.getName()) - .withOnMouseClickedEvent( - item -> evt -> viewModel.removeFilter(filterList.getFocusModel().getFocusedItem())) - .install(actionsColumn); + .withGraphic(item -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) + .withTooltip(item -> Localization.lang("Remove") + " " + item.getName()) + .withOnMouseClickedEvent(item -> evt -> viewModel.removeFilter(filterList.getFocusModel().getFocusedItem())) + .install(actionsColumn); filterList.addEventFilter(KeyEvent.KEY_PRESSED, event -> { if (event.getCode() == KeyCode.DELETE) { @@ -89,8 +100,8 @@ public void initialize() { addFieldName.setEditable(true); new ViewModelListCellFactory() - .withText(item -> FieldsUtil.getNameWithType(item, preferences, undoManager)) - .install(addFieldName); + .withText(item -> FieldsUtil.getNameWithType(item, preferences, undoManager)) + .install(addFieldName); addFieldName.itemsProperty().bind(viewModel.availableFieldsProperty()); addFieldName.valueProperty().bindBidirectional(viewModel.addFieldNameProperty()); addFieldName.setConverter(FieldsUtil.FIELD_STRING_CONVERTER); @@ -102,10 +113,12 @@ public void initialize() { }); validationVisualizer.setDecoration(new IconValidationDecorator()); - Platform.runLater(() -> validationVisualizer.initVisualization(viewModel.xmpFilterListValidationStatus(), filterList)); + Platform.runLater( + () -> validationVisualizer.initVisualization(viewModel.xmpFilterListValidationStatus(), filterList)); } public void addField() { viewModel.addField(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/xmp/XmpPrivacyTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/xmp/XmpPrivacyTabViewModel.java index dd9d7cb0b79..bc746327df4 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/xmp/XmpPrivacyTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/xmp/XmpPrivacyTabViewModel.java @@ -25,11 +25,17 @@ public class XmpPrivacyTabViewModel implements PreferenceTabViewModel { private final BooleanProperty xmpFilterEnabledProperty = new SimpleBooleanProperty(); - private final ListProperty xmpFilterListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ListProperty availableFieldsProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + + private final ListProperty xmpFilterListProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); + + private final ListProperty availableFieldsProperty = new SimpleListProperty<>( + FXCollections.observableArrayList()); + private final ObjectProperty addFieldProperty = new SimpleObjectProperty<>(); private final DialogService dialogService; + private final XmpPreferences xmpPreferences; private final Validator xmpFilterListValidator; @@ -38,13 +44,9 @@ public class XmpPrivacyTabViewModel implements PreferenceTabViewModel { this.dialogService = dialogService; this.xmpPreferences = xmpPreferences; - xmpFilterListValidator = new FunctionBasedValidator<>( - xmpFilterListProperty, - input -> !input.isEmpty(), - ValidationMessage.error("%s > %s %n %n %s".formatted( - Localization.lang("XMP metadata"), - Localization.lang("Filter List"), - Localization.lang("List must not be empty.")))); + xmpFilterListValidator = new FunctionBasedValidator<>(xmpFilterListProperty, input -> !input.isEmpty(), + ValidationMessage.error("%s > %s %n %n %s".formatted(Localization.lang("XMP metadata"), + Localization.lang("Filter List"), Localization.lang("List must not be empty.")))); } @Override @@ -71,7 +73,11 @@ public void addField() { return; } - if (xmpFilterListProperty.getValue().stream().filter(item -> item.equals(addFieldProperty.getValue())).findAny().isEmpty()) { + if (xmpFilterListProperty.getValue() + .stream() + .filter(item -> item.equals(addFieldProperty.getValue())) + .findAny() + .isEmpty()) { xmpFilterListProperty.add(addFieldProperty.getValue()); addFieldProperty.setValue(null); } @@ -89,8 +95,8 @@ public ValidationStatus xmpFilterListValidationStatus() { public boolean validateSettings() { ValidationStatus validationStatus = xmpFilterListValidationStatus(); if (xmpFilterEnabledProperty.getValue() && !validationStatus.isValid()) { - validationStatus.getHighestMessage().ifPresent(message -> - dialogService.showErrorDialogAndWait(message.getMessage())); + validationStatus.getHighestMessage() + .ifPresent(message -> dialogService.showErrorDialogAndWait(message.getMessage())); return false; } return true; @@ -111,4 +117,5 @@ public ListProperty availableFieldsProperty() { public ObjectProperty addFieldNameProperty() { return addFieldProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preview/ClipboardContentGenerator.java b/jabgui/src/main/java/org/jabref/gui/preview/ClipboardContentGenerator.java index ce5a61f5def..dbe432ce7e6 100644 --- a/jabgui/src/main/java/org/jabref/gui/preview/ClipboardContentGenerator.java +++ b/jabgui/src/main/java/org/jabref/gui/preview/ClipboardContentGenerator.java @@ -28,18 +28,21 @@ public class ClipboardContentGenerator { private PreviewPreferences previewPreferences; + private final LayoutFormatterPreferences layoutFormatterPreferences; + private final JournalAbbreviationRepository abbreviationRepository; public ClipboardContentGenerator(PreviewPreferences previewPreferences, - LayoutFormatterPreferences layoutFormatterPreferences, - JournalAbbreviationRepository abbreviationRepository) { + LayoutFormatterPreferences layoutFormatterPreferences, + JournalAbbreviationRepository abbreviationRepository) { this.previewPreferences = previewPreferences; this.layoutFormatterPreferences = layoutFormatterPreferences; this.abbreviationRepository = abbreviationRepository; } - public ClipboardContent generate(List selectedEntries, CitationStyleOutputFormat outputFormat, BibDatabaseContext bibDatabaseContext) throws IOException { + public ClipboardContent generate(List selectedEntries, CitationStyleOutputFormat outputFormat, + BibDatabaseContext bibDatabaseContext) throws IOException { List citations = generateCitations(selectedEntries, outputFormat, bibDatabaseContext); if (previewPreferences.getSelectedPreviewLayout() instanceof CitationStylePreviewLayout) { // if it's generated by a citation style take care of each output format @@ -48,14 +51,17 @@ public ClipboardContent generate(List selectedEntries, CitationStyleOu case TEXT -> processText(citations); case MARKDOWN -> processMarkdown(citations); }; - } else { + } + else { // if it is not a citation style take care of the preview return processPreview(citations); } } - private List generateCitations(List selectedEntries, CitationStyleOutputFormat outputFormat, BibDatabaseContext bibDatabaseContext) throws IOException { - // This worker stored the style as filename. The CSLAdapter and the CitationStyleCache store the source of the + private List generateCitations(List selectedEntries, CitationStyleOutputFormat outputFormat, + BibDatabaseContext bibDatabaseContext) throws IOException { + // This worker stored the style as filename. The CSLAdapter and the + // CitationStyleCache store the source of the // style. Therefore, we extract the style source from the file. String styleSource = null; PreviewLayout previewLayout = previewPreferences.getSelectedPreviewLayout(); @@ -65,20 +71,19 @@ private List generateCitations(List selectedEntries, CitationS } if (styleSource != null) { - return CitationStyleGenerator.generateBibliography( - selectedEntries, - styleSource, - outputFormat, - bibDatabaseContext, - Injector.instantiateModelOrService(BibEntryTypesManager.class)); - } else { + return CitationStyleGenerator.generateBibliography(selectedEntries, styleSource, outputFormat, + bibDatabaseContext, Injector.instantiateModelOrService(BibEntryTypesManager.class)); + } + else { return generateTextBasedPreviewLayoutCitations(selectedEntries, bibDatabaseContext); } } /** - * Generates a plain text string out of the preview (based on {@link org.jabref.logic.layout.TextBasedPreviewLayout} or {@link org.jabref.logic.bst.BstPreviewLayout}) - * and copies it additionally to the html to the clipboard (WYSIWYG Editors use the HTML, plain text editors the text) + * Generates a plain text string out of the preview (based on + * {@link org.jabref.logic.layout.TextBasedPreviewLayout} or + * {@link org.jabref.logic.bst.BstPreviewLayout}) and copies it additionally to the + * html to the clipboard (WYSIWYG Editors use the HTML, plain text editors the text) */ @VisibleForTesting static ClipboardContent processPreview(List citations) { @@ -99,22 +104,18 @@ static ClipboardContent processText(List citations) { } /** - * Inserts each citation into a HTML body and copies it to the clipboard. - * The given preview is based on {@link org.jabref.logic.citationstyle.CitationStylePreviewLayout}. + * Inserts each citation into a HTML body and copies it to the clipboard. The given + * preview is based on + * {@link org.jabref.logic.citationstyle.CitationStylePreviewLayout}. */ @VisibleForTesting static ClipboardContent processHtml(List citations) { - String result = "" + OS.NEWLINE + - "" + OS.NEWLINE + - " " + OS.NEWLINE + - " " + OS.NEWLINE + - " " + OS.NEWLINE + - " " + OS.NEWLINE + OS.NEWLINE; + String result = "" + OS.NEWLINE + "" + OS.NEWLINE + " " + OS.NEWLINE + + " " + OS.NEWLINE + " " + OS.NEWLINE + " " + OS.NEWLINE + + OS.NEWLINE; result += String.join(CitationStyleOutputFormat.HTML.getLineSeparator(), citations); - result += OS.NEWLINE + - " " + OS.NEWLINE + - "" + OS.NEWLINE; + result += OS.NEWLINE + " " + OS.NEWLINE + "" + OS.NEWLINE; ClipboardContent content = new ClipboardContent(); content.putString(result); @@ -123,22 +124,16 @@ static ClipboardContent processHtml(List citations) { } /** - * Insert each citation into HTML. - * convert HTML to markdown using flexmark. - */ + * Insert each citation into HTML. convert HTML to markdown using flexmark. + */ @VisibleForTesting static ClipboardContent processMarkdown(List citations) { - String result = "" + OS.NEWLINE + - "" + OS.NEWLINE + - " " + OS.NEWLINE + - " " + OS.NEWLINE + - " " + OS.NEWLINE + - " " + OS.NEWLINE + OS.NEWLINE; + String result = "" + OS.NEWLINE + "" + OS.NEWLINE + " " + OS.NEWLINE + + " " + OS.NEWLINE + " " + OS.NEWLINE + " " + OS.NEWLINE + + OS.NEWLINE; result += String.join(CitationStyleOutputFormat.HTML.getLineSeparator(), citations); - result += OS.NEWLINE + - " " + OS.NEWLINE + - "" + OS.NEWLINE; + result += OS.NEWLINE + " " + OS.NEWLINE + "" + OS.NEWLINE; FlexmarkHtmlConverter converter = FlexmarkHtmlConverter.builder().build(); String markdown = converter.convert(result); @@ -148,14 +143,17 @@ static ClipboardContent processMarkdown(List citations) { return content; } - private List generateTextBasedPreviewLayoutCitations(List selectedEntries, BibDatabaseContext bibDatabaseContext) throws IOException { + private List generateTextBasedPreviewLayoutCitations(List selectedEntries, + BibDatabaseContext bibDatabaseContext) throws IOException { TextBasedPreviewLayout customPreviewLayout = previewPreferences.getCustomPreviewLayout(); Reader customLayoutReader = Reader.of(customPreviewLayout.getText().replace("__NEWLINE__", "\n")); - Layout layout = new LayoutHelper(customLayoutReader, layoutFormatterPreferences, abbreviationRepository).getLayoutFromText(); + Layout layout = new LayoutHelper(customLayoutReader, layoutFormatterPreferences, abbreviationRepository) + .getLayoutFromText(); List citations = new ArrayList<>(selectedEntries.size()); for (BibEntry entry : selectedEntries) { citations.add(layout.doLayout(entry, bibDatabaseContext.getDatabase())); } return citations; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preview/CopyCitationAction.java b/jabgui/src/main/java/org/jabref/gui/preview/CopyCitationAction.java index e8364ca1a3f..59d0851c4db 100644 --- a/jabgui/src/main/java/org/jabref/gui/preview/CopyCitationAction.java +++ b/jabgui/src/main/java/org/jabref/gui/preview/CopyCitationAction.java @@ -22,35 +22,38 @@ import org.slf4j.LoggerFactory; /** - * Copies the selected entries and formats them with the selected citation style (or preview), then it is copied to the clipboard. This worker cannot be reused. + * Copies the selected entries and formats them with the selected citation style (or + * preview), then it is copied to the clipboard. This worker cannot be reused. */ public class CopyCitationAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(CopyCitationAction.class); private final List selectedEntries; + private final CitationStyleOutputFormat outputFormat; private final StateManager stateManager; + private final DialogService dialogService; + private final ClipBoardManager clipBoardManager; + private final TaskExecutor taskExecutor; + private final ClipboardContentGenerator clipboardContentGenerator; - public CopyCitationAction(CitationStyleOutputFormat outputFormat, - DialogService dialogService, - StateManager stateManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor, - GuiPreferences preferences, - JournalAbbreviationRepository abbreviationRepository) { + public CopyCitationAction(CitationStyleOutputFormat outputFormat, DialogService dialogService, + StateManager stateManager, ClipBoardManager clipBoardManager, TaskExecutor taskExecutor, + GuiPreferences preferences, JournalAbbreviationRepository abbreviationRepository) { this.outputFormat = outputFormat; this.dialogService = dialogService; this.stateManager = stateManager; this.selectedEntries = stateManager.getSelectedEntries(); this.clipBoardManager = clipBoardManager; this.taskExecutor = taskExecutor; - this.clipboardContentGenerator = new ClipboardContentGenerator(preferences.getPreviewPreferences(), preferences.getLayoutFormatterPreferences(), abbreviationRepository); + this.clipboardContentGenerator = new ClipboardContentGenerator(preferences.getPreviewPreferences(), + preferences.getLayoutFormatterPreferences(), abbreviationRepository); this.executable.bind(ActionHelper.needsEntriesSelected(stateManager)); } @@ -58,17 +61,19 @@ public CopyCitationAction(CitationStyleOutputFormat outputFormat, @Override public void execute() { BackgroundTask.wrap(this::generateCitations) - .onFailure(ex -> LOGGER.error("Error while copying citations to the clipboard", ex)) - .onSuccess(this::setClipBoardContent) - .executeWith(taskExecutor); + .onFailure(ex -> LOGGER.error("Error while copying citations to the clipboard", ex)) + .onSuccess(this::setClipBoardContent) + .executeWith(taskExecutor); } private ClipboardContent generateCitations() throws IOException { - return clipboardContentGenerator.generate(selectedEntries, outputFormat, stateManager.getActiveDatabase().get()); + return clipboardContentGenerator.generate(selectedEntries, outputFormat, + stateManager.getActiveDatabase().get()); } private void setClipBoardContent(ClipboardContent clipBoardContent) { clipBoardManager.setContent(clipBoardContent); dialogService.notify(Localization.lang("Copied %0 citations.", String.valueOf(selectedEntries.size()))); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preview/PreviewControls.java b/jabgui/src/main/java/org/jabref/gui/preview/PreviewControls.java index 9b1eae12f3d..bf3479136f4 100644 --- a/jabgui/src/main/java/org/jabref/gui/preview/PreviewControls.java +++ b/jabgui/src/main/java/org/jabref/gui/preview/PreviewControls.java @@ -1,7 +1,9 @@ package org.jabref.gui.preview; public interface PreviewControls { + void nextPreviewStyle(); void previousPreviewStyle(); + } diff --git a/jabgui/src/main/java/org/jabref/gui/preview/PreviewPanel.java b/jabgui/src/main/java/org/jabref/gui/preview/PreviewPanel.java index dda389197a1..a509085d5fe 100644 --- a/jabgui/src/main/java/org/jabref/gui/preview/PreviewPanel.java +++ b/jabgui/src/main/java/org/jabref/gui/preview/PreviewPanel.java @@ -41,28 +41,32 @@ public class PreviewPanel extends VBox implements PreviewControls { private final ExternalFilesEntryLinker fileLinker; + private final KeyBindingRepository keyBindingRepository; + private final PreviewViewer previewView; + private final PreviewPreferences previewPreferences; + private final DialogService dialogService; + private final StateManager stateManager; private BibEntry entry; - public PreviewPanel(DialogService dialogService, - KeyBindingRepository keyBindingRepository, - GuiPreferences preferences, - ThemeManager themeManager, - TaskExecutor taskExecutor, - StateManager stateManager) { + public PreviewPanel(DialogService dialogService, KeyBindingRepository keyBindingRepository, + GuiPreferences preferences, ThemeManager themeManager, TaskExecutor taskExecutor, + StateManager stateManager) { this.keyBindingRepository = keyBindingRepository; this.dialogService = dialogService; this.previewPreferences = preferences.getPreviewPreferences(); - this.fileLinker = new ExternalFilesEntryLinker(preferences.getExternalApplicationsPreferences(), preferences.getFilePreferences(), dialogService, stateManager); + this.fileLinker = new ExternalFilesEntryLinker(preferences.getExternalApplicationsPreferences(), + preferences.getFilePreferences(), dialogService, stateManager); this.stateManager = stateManager; PreviewPreferences previewPreferences = preferences.getPreviewPreferences(); - previewView = new PreviewViewer(dialogService, preferences, themeManager, taskExecutor, stateManager.searchQueryProperty()); + previewView = new PreviewViewer(dialogService, preferences, themeManager, taskExecutor, + stateManager.searchQueryProperty()); previewView.setLayout(previewPreferences.getSelectedPreviewLayout()); previewView.setContextMenu(createPopupMenu()); previewView.setOnDragDetected(this::onDragDetected); @@ -119,7 +123,8 @@ private void createKeyBindings() { } private ContextMenu createPopupMenu() { - MenuItem copyCitationHtml = new MenuItem(Localization.lang("Copy citation (html)"), IconTheme.JabRefIcons.COPY.getGraphicNode()); + MenuItem copyCitationHtml = new MenuItem(Localization.lang("Copy citation (html)"), + IconTheme.JabRefIcons.COPY.getGraphicNode()); keyBindingRepository.getKeyCombination(KeyBinding.COPY_PREVIEW).ifPresent(copyCitationHtml::setAccelerator); copyCitationHtml.setOnAction(_ -> previewView.copyPreviewHtmlToClipBoard()); MenuItem copyCitationText = new MenuItem(Localization.lang("Copy citation (text)")); @@ -128,13 +133,16 @@ private ContextMenu createPopupMenu() { exportToClipboard.setOnAction(_ -> previewView.exportToClipBoard(stateManager)); MenuItem copySelection = new MenuItem(Localization.lang("Copy selection")); copySelection.setOnAction(_ -> previewView.copySelectionToClipBoard()); - MenuItem printEntryPreview = new MenuItem(Localization.lang("Print entry preview"), IconTheme.JabRefIcons.PRINTED.getGraphicNode()); + MenuItem printEntryPreview = new MenuItem(Localization.lang("Print entry preview"), + IconTheme.JabRefIcons.PRINTED.getGraphicNode()); printEntryPreview.setOnAction(_ -> previewView.print()); MenuItem previousPreviewLayout = new MenuItem(Localization.lang("Previous preview layout")); - keyBindingRepository.getKeyCombination(KeyBinding.PREVIOUS_PREVIEW_LAYOUT).ifPresent(previousPreviewLayout::setAccelerator); + keyBindingRepository.getKeyCombination(KeyBinding.PREVIOUS_PREVIEW_LAYOUT) + .ifPresent(previousPreviewLayout::setAccelerator); previousPreviewLayout.setOnAction(_ -> this.previousPreviewStyle()); MenuItem nextPreviewLayout = new MenuItem(Localization.lang("Next preview layout")); - keyBindingRepository.getKeyCombination(KeyBinding.NEXT_PREVIEW_LAYOUT).ifPresent(nextPreviewLayout::setAccelerator); + keyBindingRepository.getKeyCombination(KeyBinding.NEXT_PREVIEW_LAYOUT) + .ifPresent(nextPreviewLayout::setAccelerator); nextPreviewLayout.setOnAction(_ -> this.nextPreviewStyle()); ContextMenu menu = new ContextMenu(); @@ -181,4 +189,5 @@ private void cyclePreview(int newPosition) { previewView.setLayout(layout); dialogService.notify(Localization.lang("Preview style changed to: %0", layout.getDisplayName())); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preview/PreviewPreferences.java b/jabgui/src/main/java/org/jabref/gui/preview/PreviewPreferences.java index 527349401ff..76147e18c30 100644 --- a/jabgui/src/main/java/org/jabref/gui/preview/PreviewPreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/preview/PreviewPreferences.java @@ -20,20 +20,22 @@ public class PreviewPreferences { private final ObservableList layoutCycle; + private final IntegerProperty layoutCyclePosition; + private final ObjectProperty customPreviewLayout; + private final StringProperty defaultCustomPreviewLayout; + private final BooleanProperty showPreviewAsExtraTab; + private final BooleanProperty showPreviewEntryTableTooltip; + private final ObservableList bstPreviewLayoutPaths; - public PreviewPreferences(List layoutCycle, - int layoutCyclePosition, - TextBasedPreviewLayout customPreviewLayout, - String defaultCustomPreviewLayout, - boolean showPreviewAsExtraTab, - boolean showPreviewEntryTableTooltip, - List bstPreviewLayoutPaths) { + public PreviewPreferences(List layoutCycle, int layoutCyclePosition, + TextBasedPreviewLayout customPreviewLayout, String defaultCustomPreviewLayout, + boolean showPreviewAsExtraTab, boolean showPreviewEntryTableTooltip, List bstPreviewLayoutPaths) { this.layoutCycle = FXCollections.observableArrayList(layoutCycle); this.layoutCyclePosition = new SimpleIntegerProperty(layoutCyclePosition); this.customPreviewLayout = new SimpleObjectProperty<>(customPreviewLayout); @@ -58,7 +60,8 @@ public IntegerProperty layoutCyclePositionProperty() { public void setLayoutCyclePosition(int position) { if (layoutCycle.isEmpty()) { this.layoutCyclePosition.setValue(0); - } else { + } + else { int previewCyclePosition = position; while (previewCyclePosition < 0) { previewCyclePosition += layoutCycle.size(); @@ -68,11 +71,11 @@ public void setLayoutCyclePosition(int position) { } public PreviewLayout getSelectedPreviewLayout() { - if (layoutCycle.isEmpty() - || layoutCyclePosition.getValue() < 0 + if (layoutCycle.isEmpty() || layoutCyclePosition.getValue() < 0 || layoutCyclePosition.getValue() >= layoutCycle.size()) { return getCustomPreviewLayout(); - } else { + } + else { return layoutCycle.get(layoutCyclePosition.getValue()); } } @@ -124,4 +127,5 @@ public ObservableList getBstPreviewLayoutPaths() { public void setBstPreviewLayoutPaths(List bstPreviewLayoutPaths) { this.bstPreviewLayoutPaths.setAll(bstPreviewLayoutPaths); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java index d57d436f4cc..a950308d8bb 100644 --- a/jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java +++ b/jabgui/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -55,51 +55,54 @@ public class PreviewViewer extends ScrollPane implements InvalidationListener { // https://stackoverflow.com/questions/5669448/get-selected-texts-html-in-div/5670825#5670825 private static final String JS_GET_SELECTION_HTML_SCRIPT = """ - function getSelectionHtml() { - var html = ""; - if (typeof window.getSelection != "undefined") { - var sel = window.getSelection(); - if (sel.rangeCount) { - var container = document.createElement("div"); - for (var i = 0, len = sel.rangeCount; i < len; ++i) { - container.appendChild(sel.getRangeAt(i).cloneContents()); + function getSelectionHtml() { + var html = ""; + if (typeof window.getSelection != "undefined") { + var sel = window.getSelection(); + if (sel.rangeCount) { + var container = document.createElement("div"); + for (var i = 0, len = sel.rangeCount; i < len; ++i) { + container.appendChild(sel.getRangeAt(i).cloneContents()); + } + html = container.innerHTML; + } + } else if (typeof document.selection != "undefined") { + if (document.selection.type == "Text") { + html = document.selection.createRange().htmlText; } - html = container.innerHTML; - } - } else if (typeof document.selection != "undefined") { - if (document.selection.type == "Text") { - html = document.selection.createRange().htmlText; } + return html; } - return html; - } - getSelectionHtml(); - """; + getSelectionHtml(); + """; private final ClipBoardManager clipBoardManager; + private final DialogService dialogService; + private final TaskExecutor taskExecutor; + private final WebView previewView; + private final StringProperty searchQueryProperty; + private final GuiPreferences preferences; private @Nullable BibDatabaseContext databaseContext; + private @Nullable BibEntry entry; + private PreviewLayout layout; + private String layoutText; - public PreviewViewer(DialogService dialogService, - GuiPreferences preferences, - ThemeManager themeManager, - TaskExecutor taskExecutor) { + public PreviewViewer(DialogService dialogService, GuiPreferences preferences, ThemeManager themeManager, + TaskExecutor taskExecutor) { this(dialogService, preferences, themeManager, taskExecutor, new SimpleStringProperty()); } - public PreviewViewer(DialogService dialogService, - GuiPreferences preferences, - ThemeManager themeManager, - TaskExecutor taskExecutor, - StringProperty searchQueryProperty) { + public PreviewViewer(DialogService dialogService, GuiPreferences preferences, ThemeManager themeManager, + TaskExecutor taskExecutor, StringProperty searchQueryProperty) { this.dialogService = dialogService; this.clipBoardManager = Injector.instantiateModelOrService(ClipBoardManager.class); this.taskExecutor = taskExecutor; @@ -137,9 +140,11 @@ private void configurePreviewView(ThemeManager themeManager) { if (href != null) { try { NativeDesktop.openBrowser(href, preferences.getExternalApplicationsPreferences()); - } catch (MalformedURLException exception) { + } + catch (MalformedURLException exception) { LOGGER.error("Invalid URL", exception); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Could not open URL: {}", href, e); } } @@ -150,7 +155,8 @@ private void configurePreviewView(ThemeManager themeManager) { } public void setLayout(PreviewLayout newLayout) { - // Change listeners might set the layout to null while the update method is executing, therefore, we need to prevent this here + // Change listeners might set the layout to null while the update method is + // executing, therefore, we need to prevent this here if ((newLayout == null) || newLayout.equals(layout)) { return; } @@ -193,8 +199,7 @@ public void setDatabaseContext(BibDatabaseContext newDatabaseContext) { private void update() { if ((databaseContext == null) || (entry == null) || (layout == null)) { LOGGER.debug("Missing components - Database: {}, Entry: {}, Layout: {}", - databaseContext == null ? "null" : databaseContext, - entry == null ? "null" : entry, + databaseContext == null ? "null" : databaseContext, entry == null ? "null" : entry, layout == null ? "null" : layout); setPreviewText(""); return; @@ -204,19 +209,16 @@ private void update() { BibEntry currentEntry = entry; BackgroundTask.wrap(() -> layout.generatePreview(currentEntry, databaseContext)) - .onSuccess(this::setPreviewText) - .onFailure(e -> setPreviewText(formatError(currentEntry, e))) - .executeWith(taskExecutor); + .onSuccess(this::setPreviewText) + .onFailure(e -> setPreviewText(formatError(currentEntry, e))) + .executeWith(taskExecutor); } private String formatError(BibEntry entry, Throwable exception) { StringWriter sw = new StringWriter(); exception.printStackTrace(new PrintWriter(sw)); return "%s\n\n%s\n\nBibTeX (internal):\n%s\n\nStack Trace:\n%s".formatted( - Localization.lang("Error while generating citation style"), - exception.getLocalizedMessage(), - entry, - sw); + Localization.lang("Error while generating citation style"), exception.getLocalizedMessage(), entry, sw); } private void setPreviewText(String text) { @@ -241,7 +243,8 @@ private void highlightLayoutText() { SearchQuery searchQuery = new SearchQuery(queryText); String highlighted = Highlighter.highlightHtml(layoutText, searchQuery); UiTaskExecutor.runInJavaFXThread(() -> previewView.getEngine().loadContent(highlighted)); - } else { + } + else { UiTaskExecutor.runInJavaFXThread(() -> previewView.getEngine().loadContent(layoutText)); } } @@ -253,12 +256,12 @@ public void print() { } BackgroundTask.wrap(() -> { - job.getJobSettings().setJobName(entry.getCitationKey().orElse("NO CITATION KEY")); - previewView.getEngine().print(job); - job.endJob(); - }) - .onFailure(e -> dialogService.showErrorDialogAndWait(Localization.lang("Could not print preview"), e)) - .executeWith(taskExecutor); + job.getJobSettings().setJobName(entry.getCitationKey().orElse("NO CITATION KEY")); + previewView.getEngine().print(job); + job.endJob(); + }) + .onFailure(e -> dialogService.showErrorDialogAndWait(Localization.lang("Could not print preview"), e)) + .executeWith(taskExecutor); } public void copyPreviewHtmlToClipBoard() { @@ -297,12 +300,8 @@ public void copySelectionToClipBoard() { } public void exportToClipBoard(StateManager stateManager) { - ExportToClipboardAction exportToClipboardAction = new ExportToClipboardAction( - dialogService, - stateManager, - clipBoardManager, - taskExecutor, - preferences); + ExportToClipboardAction exportToClipboardAction = new ExportToClipboardAction(dialogService, stateManager, + clipBoardManager, taskExecutor, preferences); exportToClipboardAction.execute(); } @@ -314,4 +313,5 @@ public void invalidated(Observable observable) { public String getSelectionHtmlContent() { return (String) previewView.getEngine().executeScript(JS_GET_SELECTION_HTML_SCRIPT); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplication.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplication.java index d776f344f57..0977ef61457 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplication.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplication.java @@ -19,7 +19,6 @@ default String getTooltip() { /** * Gets the icon associated with the application. - * * @return The icon for the application. */ JabRefIcon getApplicationIcon(); @@ -28,7 +27,10 @@ default Action getAction() { return new GuiPushToApplicationAction(getDisplayName(), getApplicationIcon()); } - default GuiPushToApplicationSettings getSettings(PushToApplication application, DialogService dialogService, FilePreferences filePreferences, PushToApplicationPreferences pushToApplicationPreferences) { - return new GuiPushToApplicationSettings(application, dialogService, filePreferences, pushToApplicationPreferences); + default GuiPushToApplicationSettings getSettings(PushToApplication application, DialogService dialogService, + FilePreferences filePreferences, PushToApplicationPreferences pushToApplicationPreferences) { + return new GuiPushToApplicationSettings(application, dialogService, filePreferences, + pushToApplicationPreferences); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplicationAction.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplicationAction.java index 8e574e3c1bb..3eebb1a475a 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplicationAction.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplicationAction.java @@ -10,6 +10,7 @@ public class GuiPushToApplicationAction implements Action { private final String displayName; + private final JabRefIcon applicationIcon; public GuiPushToApplicationAction(String displayName, JabRefIcon applicationIcon) { @@ -31,4 +32,5 @@ public Optional getIcon() { public Optional getKeyBinding() { return Optional.of(KeyBinding.PUSH_TO_APPLICATION); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplicationCommand.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplicationCommand.java index 5523e11cc16..40379f63fd3 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplicationCommand.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplicationCommand.java @@ -33,30 +33,32 @@ public class GuiPushToApplicationCommand extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(GuiPushToApplicationCommand.class); private final StateManager stateManager; + private final DialogService dialogService; + private final GuiPreferences preferences; private final List reconfigurableControls = new ArrayList<>(); + private final TaskExecutor taskExecutor; private GuiPushToApplication application; - public GuiPushToApplicationCommand(StateManager stateManager, DialogService dialogService, GuiPreferences preferences, TaskExecutor taskExecutor) { + public GuiPushToApplicationCommand(StateManager stateManager, DialogService dialogService, + GuiPreferences preferences, TaskExecutor taskExecutor) { this.stateManager = stateManager; this.dialogService = dialogService; this.preferences = preferences; this.taskExecutor = taskExecutor; - setApplication(preferences.getPushToApplicationPreferences() - .getActiveApplicationName()); + setApplication(preferences.getPushToApplicationPreferences().getActiveApplicationName()); EasyBind.subscribe(preferences.getPushToApplicationPreferences().activeApplicationNameProperty(), this::setApplication); - this.executable.bind(ActionHelper.needsDatabase(stateManager).and(ActionHelper.needsEntriesSelected(stateManager))); - this.statusMessage.bind(BindingsHelper.ifThenElse( - this.executable, - "", + this.executable + .bind(ActionHelper.needsDatabase(stateManager).and(ActionHelper.needsEntriesSelected(stateManager))); + this.statusMessage.bind(BindingsHelper.ifThenElse(this.executable, "", Localization.lang("This operation requires one or more entries to be selected."))); } @@ -71,11 +73,9 @@ public void registerReconfigurable(Object node) { private void setApplication(String applicationName) { final ActionFactory factory = new ActionFactory(); - GuiPushToApplication application = GuiPushToApplications.getGUIApplicationByName( - applicationName, - dialogService, - preferences.getPushToApplicationPreferences()) - .orElseGet(() -> new GuiPushToEmacs(dialogService, preferences.getPushToApplicationPreferences())); + GuiPushToApplication application = GuiPushToApplications + .getGUIApplicationByName(applicationName, dialogService, preferences.getPushToApplicationPreferences()) + .orElseGet(() -> new GuiPushToEmacs(dialogService, preferences.getPushToApplicationPreferences())); preferences.getPushToApplicationPreferences().setActiveApplicationName(application.getDisplayName()); this.application = Objects.requireNonNull(application); @@ -83,7 +83,8 @@ private void setApplication(String applicationName) { reconfigurableControls.forEach(object -> { if (object instanceof MenuItem item) { factory.configureMenuItem(application.getAction(), this, item); - } else if (object instanceof ButtonBase base) { + } + else if (object instanceof ButtonBase base) { factory.configureIconButton(application.getAction(), this, base); } }); @@ -99,9 +100,8 @@ public void execute() { if (application.requiresCitationKeys()) { for (BibEntry entry : stateManager.getSelectedEntries()) { if (StringUtil.isBlank(entry.getCitationKey())) { - dialogService.showErrorDialogAndWait( - application.getDisplayName(), - Localization.lang("This operation requires all selected entries to have citation keys defined.")); + dialogService.showErrorDialogAndWait(application.getDisplayName(), Localization + .lang("This operation requires all selected entries to have citation keys defined.")); return; } } @@ -109,12 +109,13 @@ public void execute() { // All set, call the operation in a new thread: BackgroundTask.wrap(this::pushEntries) - .onSuccess(s -> application.onOperationCompleted()) - .onFailure(ex -> LOGGER.error("Error pushing citation", ex)) - .executeWith(taskExecutor); + .onSuccess(s -> application.onOperationCompleted()) + .onFailure(ex -> LOGGER.error("Error pushing citation", ex)) + .executeWith(taskExecutor); } private void pushEntries() { application.pushEntries(stateManager.getSelectedEntries()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplicationSettings.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplicationSettings.java index 3d49fe154eb..5c1f6a566f3 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplicationSettings.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplicationSettings.java @@ -23,15 +23,17 @@ public class GuiPushToApplicationSettings { protected final Label commandLabel; + protected final TextField path; + protected final GridPane settingsPane; + protected final PushToApplicationPreferences preferences; + protected final AbstractPushToApplication application; - public GuiPushToApplicationSettings(PushToApplication application, - DialogService dialogService, - FilePreferences filePreferences, - PushToApplicationPreferences preferences) { + public GuiPushToApplicationSettings(PushToApplication application, DialogService dialogService, + FilePreferences filePreferences, PushToApplicationPreferences preferences) { this.application = (AbstractPushToApplication) application; this.preferences = preferences; @@ -49,11 +51,13 @@ public GuiPushToApplicationSettings(PushToApplication application, browse.setPrefHeight(20.0); browse.setPrefWidth(20.0); - // In case the application name and the actual command is not the same, add the command in brackets + // In case the application name and the actual command is not the same, add the + // command in brackets StringBuilder commandLine = new StringBuilder(Localization.lang("Path to %0", application.getDisplayName())); if (this.application.getCommandName() == null) { commandLine.append(':'); - } else { + } + else { commandLine.append(" (").append(this.application.getCommandName()).append("):"); } commandLabel.setText(commandLine.toString()); @@ -63,9 +67,10 @@ public GuiPushToApplicationSettings(PushToApplication application, settingsPane.add(path, 1, 0); FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .withInitialDirectory(filePreferences.getWorkingDirectory()).build(); + .withInitialDirectory(filePreferences.getWorkingDirectory()) + .build(); browse.setOnAction(e -> dialogService.showFileOpenDialog(fileDialogConfiguration) - .ifPresent(f -> path.setText(f.toAbsolutePath().toString()))); + .ifPresent(f -> path.setText(f.toAbsolutePath().toString()))); settingsPane.add(browse, 2, 0); ColumnConstraints textConstraints = new ColumnConstraints(); @@ -77,9 +82,10 @@ public GuiPushToApplicationSettings(PushToApplication application, } /** - * This method is called to indicate that the settings panel returned from the getSettingsPanel() method has been - * shown to the user and that the user has indicated that the settings should be stored. This method must store the - * state of the widgets in the settings panel to Globals.prefs. + * This method is called to indicate that the settings panel returned from the + * getSettingsPanel() method has been shown to the user and that the user has + * indicated that the settings should be stored. This method must store the state of + * the widgets in the settings panel to Globals.prefs. */ public void storeSettings() { Map commandPaths = new HashMap<>(preferences.getCommandPaths()); @@ -90,4 +96,5 @@ public void storeSettings() { public GridPane getSettingsPane() { return this.settingsPane; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplications.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplications.java index 8cd5668f394..4f617380499 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplications.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToApplications.java @@ -15,30 +15,29 @@ public class GuiPushToApplications { private GuiPushToApplications() { } - public static List getAllGUIApplications(DialogService dialogService, PushToApplicationPreferences preferences) { + public static List getAllGUIApplications(DialogService dialogService, + PushToApplicationPreferences preferences) { if (!APPLICATIONS.isEmpty()) { return Collections.unmodifiableList(APPLICATIONS); } - APPLICATIONS.addAll(List.of( - new GuiPushToEmacs(dialogService, preferences), - new GuiPushToLyx(dialogService, preferences), - new GuiPushToSublimeText(dialogService, preferences), - new GuiPushToTexmaker(dialogService, preferences), - new GuiPushToTeXstudio(dialogService, preferences), - new GuiPushToTeXworks(dialogService, preferences), - new GuiPushToVim(dialogService, preferences), - new GuiPushToWinEdt(dialogService, preferences), - new GuiPushToTexShop(dialogService, preferences), + APPLICATIONS.addAll(List.of(new GuiPushToEmacs(dialogService, preferences), + new GuiPushToLyx(dialogService, preferences), new GuiPushToSublimeText(dialogService, preferences), + new GuiPushToTexmaker(dialogService, preferences), new GuiPushToTeXstudio(dialogService, preferences), + new GuiPushToTeXworks(dialogService, preferences), new GuiPushToVim(dialogService, preferences), + new GuiPushToWinEdt(dialogService, preferences), new GuiPushToTexShop(dialogService, preferences), new GuiPushToVScode(dialogService, preferences))); return Collections.unmodifiableList(APPLICATIONS); } - /// In case object "preferences" changes for itself, that update won't be propagated. Does not harm as the current callers do not change that reference itself. - public static Optional getGUIApplicationByName(String applicationName, DialogService dialogService, PushToApplicationPreferences preferences) { + /// In case object "preferences" changes for itself, that update won't be propagated. + /// Does not harm as the current callers do not change that reference itself. + public static Optional getGUIApplicationByName(String applicationName, + DialogService dialogService, PushToApplicationPreferences preferences) { return getAllGUIApplications(dialogService, preferences).stream() - .filter(application -> application.getDisplayName().equalsIgnoreCase(applicationName)) - .findAny(); + .filter(application -> application.getDisplayName().equalsIgnoreCase(applicationName)) + .findAny(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToEmacs.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToEmacs.java index 70cc3dc93ca..16a3859f9fa 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToEmacs.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToEmacs.java @@ -18,7 +18,8 @@ public GuiPushToEmacs(DialogService dialogService, PushToApplicationPreferences } @Override - public GuiPushToApplicationSettings getSettings(PushToApplication application, DialogService dialogService, FilePreferences filePreferences, PushToApplicationPreferences pushToApplicationPreferences) { + public GuiPushToApplicationSettings getSettings(PushToApplication application, DialogService dialogService, + FilePreferences filePreferences, PushToApplicationPreferences pushToApplicationPreferences) { return new GuiPushToEmacsSettings(application, dialogService, filePreferences, pushToApplicationPreferences); } @@ -31,4 +32,5 @@ public JabRefIcon getApplicationIcon() { public void sendErrorNotification(String title, String message) { dialogService.showErrorDialogAndWait(title, message); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToEmacsSettings.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToEmacsSettings.java index 22aa24e5692..452c41208ec 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToEmacsSettings.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToEmacsSettings.java @@ -13,10 +13,8 @@ public class GuiPushToEmacsSettings extends GuiPushToApplicationSettings { private final TextField additionalParams = new TextField(); - public GuiPushToEmacsSettings(PushToApplication application, - DialogService dialogService, - FilePreferences filePreferences, - PushToApplicationPreferences preferences) { + public GuiPushToEmacsSettings(PushToApplication application, DialogService dialogService, + FilePreferences filePreferences, PushToApplicationPreferences preferences) { super(application, dialogService, filePreferences, preferences); settingsPane.add(new Label(Localization.lang("Additional parameters") + ":"), 0, 1); @@ -29,4 +27,5 @@ public void storeSettings() { super.storeSettings(); preferences.setEmacsArguments(additionalParams.getText()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToLyx.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToLyx.java index ef2694d5c42..cdb0a5da968 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToLyx.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToLyx.java @@ -18,7 +18,8 @@ public GuiPushToLyx(DialogService dialogService, PushToApplicationPreferences pr } @Override - public GuiPushToApplicationSettings getSettings(PushToApplication application, DialogService dialogService, FilePreferences filePreferences, PushToApplicationPreferences pushToApplicationPreferences) { + public GuiPushToApplicationSettings getSettings(PushToApplication application, DialogService dialogService, + FilePreferences filePreferences, PushToApplicationPreferences pushToApplicationPreferences) { return new GuiPushToLyxSettings(application, dialogService, filePreferences, pushToApplicationPreferences); } @@ -31,4 +32,5 @@ public JabRefIcon getApplicationIcon() { public void sendErrorNotification(String title, String message) { dialogService.showErrorDialogAndWait(title, message); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToLyxSettings.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToLyxSettings.java index 737b7aa36e9..4dd271bea3f 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToLyxSettings.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToLyxSettings.java @@ -8,12 +8,11 @@ public class GuiPushToLyxSettings extends GuiPushToApplicationSettings { - public GuiPushToLyxSettings(PushToApplication application, - DialogService dialogService, - FilePreferences filePreferences, - PushToApplicationPreferences preferences) { + public GuiPushToLyxSettings(PushToApplication application, DialogService dialogService, + FilePreferences filePreferences, PushToApplicationPreferences preferences) { super(application, dialogService, filePreferences, preferences); commandLabel.setText(Localization.lang("Path to LyX pipe") + ":"); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToSublimeText.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToSublimeText.java index 0f7d2287a7f..c65a2630baa 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToSublimeText.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToSublimeText.java @@ -24,4 +24,5 @@ public JabRefIcon getApplicationIcon() { public void sendErrorNotification(String title, String message) { dialogService.showErrorDialogAndWait(title, message); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToTeXstudio.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToTeXstudio.java index 89db38554dc..a3b6c24a69c 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToTeXstudio.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToTeXstudio.java @@ -24,4 +24,5 @@ public JabRefIcon getApplicationIcon() { public void sendErrorNotification(String title, String message) { dialogService.showErrorDialogAndWait(title, message); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToTeXworks.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToTeXworks.java index 21b06756a6f..efed665d3a7 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToTeXworks.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToTeXworks.java @@ -24,4 +24,5 @@ public JabRefIcon getApplicationIcon() { public void sendErrorNotification(String title, String message) { dialogService.showErrorDialogAndWait(title, message); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToTexShop.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToTexShop.java index 347a1473f84..5e66b967256 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToTexShop.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToTexShop.java @@ -24,4 +24,5 @@ public JabRefIcon getApplicationIcon() { public void sendErrorNotification(String title, String message) { dialogService.showErrorDialogAndWait(title, message); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToTexmaker.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToTexmaker.java index d3001508836..103f001550f 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToTexmaker.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToTexmaker.java @@ -24,4 +24,5 @@ public JabRefIcon getApplicationIcon() { public void sendErrorNotification(String title, String message) { dialogService.showErrorDialogAndWait(title, message); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToVScode.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToVScode.java index d9fdf339c3e..11057b2613f 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToVScode.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToVScode.java @@ -24,4 +24,5 @@ public JabRefIcon getApplicationIcon() { public void sendErrorNotification(String title, String message) { dialogService.showErrorDialogAndWait(title, message); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToVim.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToVim.java index e2d08ecad89..a8502c8d5c5 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToVim.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToVim.java @@ -18,7 +18,8 @@ public GuiPushToVim(DialogService dialogService, PushToApplicationPreferences pr } @Override - public GuiPushToApplicationSettings getSettings(PushToApplication application, DialogService dialogService, FilePreferences filePreferences, PushToApplicationPreferences preferences) { + public GuiPushToApplicationSettings getSettings(PushToApplication application, DialogService dialogService, + FilePreferences filePreferences, PushToApplicationPreferences preferences) { return new GuiPushToVimSettings(application, dialogService, filePreferences, preferences); } @@ -31,4 +32,5 @@ public JabRefIcon getApplicationIcon() { public void sendErrorNotification(String title, String message) { dialogService.showErrorDialogAndWait(title, message); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToVimSettings.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToVimSettings.java index 7222115c516..5ca8fb87396 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToVimSettings.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToVimSettings.java @@ -13,10 +13,8 @@ public class GuiPushToVimSettings extends GuiPushToApplicationSettings { private final TextField vimServer = new TextField(); - public GuiPushToVimSettings(PushToApplication application, - DialogService dialogService, - FilePreferences filePreferences, - PushToApplicationPreferences preferences) { + public GuiPushToVimSettings(PushToApplication application, DialogService dialogService, + FilePreferences filePreferences, PushToApplicationPreferences preferences) { super(application, dialogService, filePreferences, preferences); settingsPane.add(new Label(Localization.lang("Vim server name") + ":"), 0, 1); @@ -29,4 +27,5 @@ public void storeSettings() { super.storeSettings(); preferences.setVimServer(vimServer.getText()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToWinEdt.java b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToWinEdt.java index e13dcd5d94e..b2a03375b5e 100644 --- a/jabgui/src/main/java/org/jabref/gui/push/GuiPushToWinEdt.java +++ b/jabgui/src/main/java/org/jabref/gui/push/GuiPushToWinEdt.java @@ -24,4 +24,5 @@ public JabRefIcon getApplicationIcon() { public void sendErrorNotification(String title, String message) { dialogService.showErrorDialogAndWait(title, message); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/remote/CLIMessageHandler.java b/jabgui/src/main/java/org/jabref/gui/remote/CLIMessageHandler.java index 4e39a0e684e..a25395f5be5 100644 --- a/jabgui/src/main/java/org/jabref/gui/remote/CLIMessageHandler.java +++ b/jabgui/src/main/java/org/jabref/gui/remote/CLIMessageHandler.java @@ -15,13 +15,14 @@ import org.slf4j.LoggerFactory; public class CLIMessageHandler implements RemoteMessageHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(CLIMessageHandler.class); private final GuiPreferences preferences; + private final UiMessageHandler uiMessageHandler; - public CLIMessageHandler(UiMessageHandler uiMessageHandler, - GuiPreferences preferences) { + public CLIMessageHandler(UiMessageHandler uiMessageHandler, GuiPreferences preferences) { this.uiMessageHandler = uiMessageHandler; this.preferences = preferences; } @@ -29,9 +30,7 @@ public CLIMessageHandler(UiMessageHandler uiMessageHandler, @Override public void handleCommandLineArguments(String[] message) { LOGGER.info("Processing message {}", Arrays.stream(message).toList()); - ArgumentProcessor argumentProcessor = new ArgumentProcessor( - message, - ArgumentProcessor.Mode.REMOTE_START, + ArgumentProcessor argumentProcessor = new ArgumentProcessor(message, ArgumentProcessor.Mode.REMOTE_START, preferences); List uiCommands = argumentProcessor.processArguments(); Platform.runLater(() -> uiMessageHandler.handleUiCommands(uiCommands)); @@ -41,4 +40,5 @@ public void handleCommandLineArguments(String[] message) { public void handleFocus() { Platform.runLater(() -> uiMessageHandler.handleUiCommands(List.of(new UiCommand.Focus()))); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/search/GlobalSearchBar.java b/jabgui/src/main/java/org/jabref/gui/search/GlobalSearchBar.java index 028c66f9558..1cfc12284ff 100644 --- a/jabgui/src/main/java/org/jabref/gui/search/GlobalSearchBar.java +++ b/jabgui/src/main/java/org/jabref/gui/search/GlobalSearchBar.java @@ -78,35 +78,51 @@ public class GlobalSearchBar extends HBox { private static final Logger LOGGER = LoggerFactory.getLogger(GlobalSearchBar.class); + private static final int SEARCH_DELAY = 400; + private static final PseudoClass ILLEGAL_SEARCH = PseudoClass.getPseudoClass("illegal-search"); private final CustomTextField searchField; + private final Button openGlobalSearchButton; + private final ToggleButton fulltextButton; + private final ToggleButton keepSearchString; + private final ToggleButton filterModeButton; + private final ToggleButton regexButton; + private final ToggleButton caseSensitiveButton; + private final Tooltip searchFieldTooltip = new Tooltip(); + private final StateManager stateManager; + private final GuiPreferences preferences; + private final UndoManager undoManager; + private final LibraryTabContainer tabContainer; + private final SearchPreferences searchPreferences; + private final DialogService dialogService; + private final BooleanProperty globalSearchActive = new SimpleBooleanProperty(false); + private final BooleanProperty illegalSearch = new SimpleBooleanProperty(false); + private final FilePreferences filePreferences; + private GlobalSearchResultDialog globalSearchResultDialog; + private final SearchType searchType; - public GlobalSearchBar(LibraryTabContainer tabContainer, - StateManager stateManager, - GuiPreferences preferences, - UndoManager undoManager, - DialogService dialogService, - SearchType searchType) { + public GlobalSearchBar(LibraryTabContainer tabContainer, StateManager stateManager, GuiPreferences preferences, + UndoManager undoManager, DialogService dialogService, SearchType searchType) { super(); this.stateManager = stateManager; this.preferences = preferences; @@ -124,24 +140,27 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, stateManager.searchQueryProperty().bind(searchField.textProperty()); Label currentResults = new Label(); - // fits the standard "found x entries"-message thus hinders the searchbar to jump around while searching if the tabContainer width is too small + // fits the standard "found x entries"-message thus hinders the searchbar to jump + // around while searching if the tabContainer width is too small currentResults.setPrefWidth(150); - currentResults.textProperty().bind(EasyBind.combine( - stateManager.activeSearchQuery(searchType), stateManager.searchResultSize(searchType), illegalSearch, - (searchQuery, matched, illegal) -> { - searchField.pseudoClassStateChanged(ILLEGAL_SEARCH, illegal); - if (illegal) { - return Localization.lang("Illegal search expression"); - } else if (searchQuery.isEmpty()) { - return ""; - } else if (matched.intValue() == 0) { - return Localization.lang("No results found."); - } else { - return Localization.lang("Found %0 results.", String.valueOf(matched)); - } - } - )); + currentResults.textProperty() + .bind(EasyBind.combine(stateManager.activeSearchQuery(searchType), + stateManager.searchResultSize(searchType), illegalSearch, (searchQuery, matched, illegal) -> { + searchField.pseudoClassStateChanged(ILLEGAL_SEARCH, illegal); + if (illegal) { + return Localization.lang("Illegal search expression"); + } + else if (searchQuery.isEmpty()) { + return ""; + } + else if (matched.intValue() == 0) { + return Localization.lang("No results found."); + } + else { + return Localization.lang("Found %0 results.", String.valueOf(matched)); + } + })); searchField.setTooltip(searchFieldTooltip); searchFieldTooltip.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); @@ -166,7 +185,9 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, searchField.setContextMenu(SearchFieldRightClickMenu.create(stateManager, searchField)); stateManager.getWholeSearchHistory().addListener((ListChangeListener.Change change) -> { searchField.getContextMenu().getItems().removeLast(); - searchField.getContextMenu().getItems().add(SearchFieldRightClickMenu.createSearchFromHistorySubMenu(stateManager, searchField)); + searchField.getContextMenu() + .getItems() + .add(SearchFieldRightClickMenu.createSearchFromHistorySubMenu(stateManager, searchField)); }); regexButton = IconTheme.JabRefIcons.REG_EX.asToggleButton(); @@ -179,12 +200,12 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, initSearchModifierButtons(); BooleanBinding focusedOrActive = searchField.focusedProperty() - .or(regexButton.focusedProperty()) - .or(caseSensitiveButton.focusedProperty()) - .or(fulltextButton.focusedProperty()) - .or(keepSearchString.focusedProperty()) - .or(filterModeButton.focusedProperty()) - .or(searchField.textProperty().isNotEmpty()); + .or(regexButton.focusedProperty()) + .or(caseSensitiveButton.focusedProperty()) + .or(fulltextButton.focusedProperty()) + .or(keepSearchString.focusedProperty()) + .or(filterModeButton.focusedProperty()) + .or(searchField.textProperty().isNotEmpty()); regexButton.visibleProperty().unbind(); regexButton.visibleProperty().bind(focusedOrActive); @@ -199,8 +220,10 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, StackPane modifierButtons; if (searchType == SearchType.NORMAL_SEARCH) { - modifierButtons = new StackPane(new HBox(regexButton, caseSensitiveButton, fulltextButton, keepSearchString, filterModeButton)); - } else { + modifierButtons = new StackPane( + new HBox(regexButton, caseSensitiveButton, fulltextButton, keepSearchString, filterModeButton)); + } + else { modifierButtons = new StackPane(new HBox(regexButton, caseSensitiveButton, fulltextButton)); } @@ -212,7 +235,8 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, if (searchType == SearchType.NORMAL_SEARCH) { this.getChildren().addAll(searchField, openGlobalSearchButton, currentResults); - } else { + } + else { this.getChildren().addAll(searchField, currentResults); } @@ -220,23 +244,21 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, this.setAlignment(Pos.CENTER_LEFT); Timer searchTask = FxTimer.create(Duration.ofMillis(SEARCH_DELAY), this::updateSearchQuery); - BindingsHelper.bindBidirectional( - stateManager.activeSearchQuery(searchType), - searchField.textProperty(), + BindingsHelper.bindBidirectional(stateManager.activeSearchQuery(searchType), searchField.textProperty(), searchTerm -> { // Async update searchTask.restart(); - }, - query -> setSearchTerm(query.orElseGet(() -> new SearchQuery("")))); + }, query -> setSearchTerm(query.orElseGet(() -> new SearchQuery("")))); /* - * The listener tracks a change on the focus property value. - * This happens, from active (user types a query) to inactive / focus - * lost (e.g., user selects an entry or triggers the search). - * The search history should only be filled, if focus is lost. + * The listener tracks a change on the focus property value. This happens, from + * active (user types a query) to inactive / focus lost (e.g., user selects an + * entry or triggers the search). The search history should only be filled, if + * focus is lost. */ searchField.focusedProperty().addListener((obs, oldValue, newValue) -> { - // Focus lost can be derived by checking that there is no newValue (or the text is empty) + // Focus lost can be derived by checking that there is no newValue (or the + // text is empty) if (oldValue && !(newValue || searchField.getText().isBlank())) { this.stateManager.addSearchHistory(searchField.textProperty().get()); } @@ -250,14 +272,18 @@ private void initSearchModifierButtons() { EasyBind.subscribe(filePreferences.fulltextIndexLinkedFilesProperty(), enabled -> { if (!enabled) { fulltextButton.setSelected(false); - } else if (searchPreferences.isFulltext()) { + } + else if (searchPreferences.isFulltext()) { fulltextButton.setSelected(true); } }); fulltextButton.selectedProperty().addListener((obs, oldVal, newVal) -> { if (!filePreferences.shouldFulltextIndexLinkedFiles() && newVal) { - boolean enableFulltextSearch = dialogService.showConfirmationDialogAndWait(Localization.lang("Fulltext search"), Localization.lang("Fulltext search requires the setting 'Automatically index all linked files for fulltext search' to be enabled. Do you want to enable indexing now?"), Localization.lang("Enable indexing"), Localization.lang("Keep disabled")); + boolean enableFulltextSearch = dialogService + .showConfirmationDialogAndWait(Localization.lang("Fulltext search"), Localization.lang( + "Fulltext search requires the setting 'Automatically index all linked files for fulltext search' to be enabled. Do you want to enable indexing now?"), + Localization.lang("Enable indexing"), Localization.lang("Keep disabled")); LibraryTab libraryTab = tabContainer.getCurrentLibraryTab(); if (libraryTab != null && enableFulltextSearch) { @@ -268,7 +294,8 @@ private void initSearchModifierButtons() { fulltextButton.setSelected(false); searchPreferences.setSearchFlag(SearchFlags.FULLTEXT, false); } - } else { + } + else { searchPreferences.setSearchFlag(SearchFlags.FULLTEXT, newVal); } searchField.requestFocus(); @@ -276,7 +303,8 @@ private void initSearchModifierButtons() { }); regexButton.setSelected(searchPreferences.isRegularExpression()); - regexButton.setTooltip(new Tooltip(Localization.lang("Regular expression") + "\n" + Localization.lang("This only affects unfielded terms. For using RegEx in a fielded term, use =~ operator."))); + regexButton.setTooltip(new Tooltip(Localization.lang("Regular expression") + "\n" + Localization + .lang("This only affects unfielded terms. For using RegEx in a fielded term, use =~ operator."))); initSearchModifierButton(regexButton); regexButton.setOnAction(event -> { searchPreferences.setSearchFlag(SearchFlags.REGULAR_EXPRESSION, regexButton.isSelected()); @@ -285,7 +313,8 @@ private void initSearchModifierButtons() { }); caseSensitiveButton.setSelected(searchPreferences.isCaseSensitive()); - caseSensitiveButton.setTooltip(new Tooltip(Localization.lang("Case sensitive") + "\n" + Localization.lang("This only affects unfielded terms. For using case-sensitive in a fielded term, use =! operator."))); + caseSensitiveButton.setTooltip(new Tooltip(Localization.lang("Case sensitive") + "\n" + Localization + .lang("This only affects unfielded terms. For using case-sensitive in a fielded term, use =! operator."))); initSearchModifierButton(caseSensitiveButton); caseSensitiveButton.setOnAction(event -> { searchPreferences.setSearchFlag(SearchFlags.CASE_SENSITIVE, caseSensitiveButton.isSelected()); @@ -305,7 +334,8 @@ private void initSearchModifierButtons() { filterModeButton.setTooltip(new Tooltip(Localization.lang("Filter search results"))); initSearchModifierButton(filterModeButton); filterModeButton.setOnAction(event -> { - searchPreferences.setSearchDisplayMode(filterModeButton.isSelected() ? SearchDisplayMode.FILTER : SearchDisplayMode.FLOAT); + searchPreferences.setSearchDisplayMode( + filterModeButton.isSelected() ? SearchDisplayMode.FILTER : SearchDisplayMode.FLOAT); searchField.requestFocus(); }); @@ -314,11 +344,12 @@ private void initSearchModifierButtons() { initSearchModifierButton(openGlobalSearchButton); openGlobalSearchButton.setOnAction(evt -> openGlobalSearchDialog()); - searchPreferences.getObservableSearchFlags().addListener((SetChangeListener.Change change) -> { - regexButton.setSelected(searchPreferences.isRegularExpression()); - caseSensitiveButton.setSelected(searchPreferences.isCaseSensitive()); - fulltextButton.setSelected(searchPreferences.isFulltext()); - }); + searchPreferences.getObservableSearchFlags() + .addListener((SetChangeListener.Change change) -> { + regexButton.setSelected(searchPreferences.isRegularExpression()); + caseSensitiveButton.setSelected(searchPreferences.isCaseSensitive()); + fulltextButton.setSelected(searchPreferences.isFulltext()); + }); } public void openGlobalSearchDialog() { @@ -329,8 +360,9 @@ public void openGlobalSearchDialog() { if (globalSearchResultDialog == null) { globalSearchResultDialog = new GlobalSearchResultDialog(undoManager, tabContainer); } - stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH).get().ifPresent(query -> - stateManager.activeSearchQuery(SearchType.GLOBAL_SEARCH).set(Optional.of(query))); + stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH) + .get() + .ifPresent(query -> stateManager.activeSearchQuery(SearchType.GLOBAL_SEARCH).set(Optional.of(query))); updateSearchQuery(); dialogService.showCustomDialogAndWait(globalSearchResultDialog); globalSearchActive.setValue(false); @@ -376,8 +408,8 @@ public void updateSearchQuery() { public void setAutoCompleter(SuggestionProvider searchCompleter) { if (preferences.getAutoCompletePreferences().shouldAutoComplete()) { - AutoCompletionTextInputBinding autoComplete = AutoCompletionTextInputBinding.autoComplete(searchField, - searchCompleter::provideSuggestions, + AutoCompletionTextInputBinding autoComplete = AutoCompletionTextInputBinding.autoComplete( + searchField, searchCompleter::provideSuggestions, new PersonNameStringConverter(false, false, AutoCompleteFirstNameMode.BOTH), new AppendPersonNamesStrategy()); AutoCompletePopup popup = getPopup(autoComplete); @@ -386,7 +418,8 @@ public void setAutoCompleter(SuggestionProvider searchCompleter) { } /** - * The popup has private access in {@link AutoCompletionBinding}, so we use reflection to access it. + * The popup has private access in {@link AutoCompletionBinding}, so we use reflection + * to access it. */ @SuppressWarnings("unchecked") private AutoCompletePopup getPopup(AutoCompletionBinding autoCompletionBinding) { @@ -395,7 +428,8 @@ private AutoCompletePopup getPopup(AutoCompletionBinding autoCompletio Field privatePopup = AutoCompletionBinding.class.getDeclaredField("autoCompletionPopup"); privatePopup.setAccessible(true); return (AutoCompletePopup) privatePopup.get(autoCompletionBinding); - } catch (IllegalAccessException | NoSuchFieldException e) { + } + catch (IllegalAccessException | NoSuchFieldException e) { LOGGER.error("Could not get access to auto completion popup", e); return new AutoCompletePopup<>(); } @@ -403,7 +437,8 @@ private AutoCompletePopup getPopup(AutoCompletionBinding autoCompletio public void setSearchBarHint() { if (preferences.getWorkspacePreferences().shouldShowAdvancedHints()) { - String genericDescription = Localization.lang("Hint:\n\nTo search all fields for Smith, enter:\nsmith\n\nTo search the field author for Smith and the field title for electrical, enter:\nauthor=Smith AND title=electrical"); + String genericDescription = Localization.lang( + "Hint:\n\nTo search all fields for Smith, enter:\nsmith\n\nTo search the field author for Smith and the field title for electrical, enter:\nauthor=Smith AND title=electrical"); List genericDescriptionTexts = TooltipTextUtil.createTextsFromHtml(genericDescription); TextFlow emptyHintTooltip = new TextFlow(); @@ -424,15 +459,22 @@ public void setSearchTerm(SearchQuery searchQuery) { private static class SearchPopupSkin implements Skin> { private final AutoCompletePopup control; + private final ListView suggestionList; + private final BorderPane container; public SearchPopupSkin(AutoCompletePopup control) { this.control = control; this.suggestionList = new ListView<>(control.getSuggestions()); this.suggestionList.getStyleClass().add("auto-complete-popup"); - this.suggestionList.getStylesheets().add(Objects.requireNonNull(AutoCompletionBinding.class.getResource("autocompletion.css")).toExternalForm()); - this.suggestionList.prefHeightProperty().bind(Bindings.min(control.visibleRowCountProperty(), Bindings.size(this.suggestionList.getItems())).multiply(24).add(18)); + this.suggestionList.getStylesheets() + .add(Objects.requireNonNull(AutoCompletionBinding.class.getResource("autocompletion.css")) + .toExternalForm()); + this.suggestionList.prefHeightProperty() + .bind(Bindings.min(control.visibleRowCountProperty(), Bindings.size(this.suggestionList.getItems())) + .multiply(24) + .add(18)); this.suggestionList.setCellFactory(TextFieldListCell.forListView(control.getConverter())); this.suggestionList.prefWidthProperty().bind(control.prefWidthProperty()); this.suggestionList.maxWidthProperty().bind(control.maxWidthProperty()); @@ -487,5 +529,7 @@ public AutoCompletePopup getSkinnable() { public void dispose() { // empty } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/search/GlobalSearchResultDialog.java b/jabgui/src/main/java/org/jabref/gui/search/GlobalSearchResultDialog.java index 450f9f40b16..05cd8f35c40 100644 --- a/jabgui/src/main/java/org/jabref/gui/search/GlobalSearchResultDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/search/GlobalSearchResultDialog.java @@ -30,54 +30,75 @@ public class GlobalSearchResultDialog extends BaseDialog { - @FXML private SplitPane container; - @FXML private ToggleButton keepOnTop; - @FXML private HBox searchBarContainer; + @FXML + private SplitPane container; + + @FXML + private ToggleButton keepOnTop; + + @FXML + private HBox searchBarContainer; private final UndoManager undoManager; + private final LibraryTabContainer libraryTabContainer; - // Reference needs to be kept, since java garbage collection would otherwise destroy the subscription - @SuppressWarnings("FieldCanBeLocal") private Subscription keepOnTopSubscription; + // Reference needs to be kept, since java garbage collection would otherwise destroy + // the subscription + @SuppressWarnings("FieldCanBeLocal") + private Subscription keepOnTopSubscription; + + @Inject + private GuiPreferences preferences; - @Inject private GuiPreferences preferences; - @Inject private StateManager stateManager; - @Inject private DialogService dialogService; - @Inject private ThemeManager themeManager; - @Inject private TaskExecutor taskExecutor; + @Inject + private StateManager stateManager; + + @Inject + private DialogService dialogService; + + @Inject + private ThemeManager themeManager; + + @Inject + private TaskExecutor taskExecutor; public GlobalSearchResultDialog(UndoManager undoManager, LibraryTabContainer libraryTabContainer) { this.undoManager = undoManager; this.libraryTabContainer = libraryTabContainer; setTitle(Localization.lang("Search results from open libraries")); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); initModality(Modality.NONE); } @FXML private void initialize() { - GlobalSearchResultDialogViewModel viewModel = new GlobalSearchResultDialogViewModel(preferences.getSearchPreferences()); + GlobalSearchResultDialogViewModel viewModel = new GlobalSearchResultDialogViewModel( + preferences.getSearchPreferences()); - GlobalSearchBar searchBar = new GlobalSearchBar(libraryTabContainer, stateManager, preferences, undoManager, dialogService, SearchType.GLOBAL_SEARCH); + GlobalSearchBar searchBar = new GlobalSearchBar(libraryTabContainer, stateManager, preferences, undoManager, + dialogService, SearchType.GLOBAL_SEARCH); searchBarContainer.getChildren().addFirst(searchBar); HBox.setHgrow(searchBar, Priority.ALWAYS); - PreviewViewer previewViewer = new PreviewViewer(dialogService, preferences, themeManager, taskExecutor, stateManager.searchQueryProperty()); + PreviewViewer previewViewer = new PreviewViewer(dialogService, preferences, themeManager, taskExecutor, + stateManager.searchQueryProperty()); previewViewer.setLayout(preferences.getPreviewPreferences().getSelectedPreviewLayout()); previewViewer.setDatabaseContext(viewModel.getSearchDatabaseContext()); - SearchResultsTableDataModel model = new SearchResultsTableDataModel(viewModel.getSearchDatabaseContext(), preferences, stateManager, taskExecutor); - SearchResultsTable resultsTable = new SearchResultsTable(model, viewModel.getSearchDatabaseContext(), preferences, undoManager, dialogService, stateManager, taskExecutor); + SearchResultsTableDataModel model = new SearchResultsTableDataModel(viewModel.getSearchDatabaseContext(), + preferences, stateManager, taskExecutor); + SearchResultsTable resultsTable = new SearchResultsTable(model, viewModel.getSearchDatabaseContext(), + preferences, undoManager, dialogService, stateManager, taskExecutor); resultsTable.getColumns().removeIf(SpecialFieldColumn.class::isInstance); resultsTable.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) -> { if (newValue != null) { previewViewer.setEntry(newValue.getEntry()); - } else { + } + else { previewViewer.setEntry(oldValue.getEntry()); } }); @@ -90,12 +111,14 @@ private void initialize() { if (selectedEntry == null) { return; } - libraryTabContainer.getLibraryTabs().stream() - .filter(tab -> tab.getBibDatabaseContext().equals(selectedEntry.getBibDatabaseContext())) - .findFirst() - .ifPresent(libraryTabContainer::showLibraryTab); - - stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH).set(stateManager.activeSearchQuery(SearchType.GLOBAL_SEARCH).get()); + libraryTabContainer.getLibraryTabs() + .stream() + .filter(tab -> tab.getBibDatabaseContext().equals(selectedEntry.getBibDatabaseContext())) + .findFirst() + .ifPresent(libraryTabContainer::showLibraryTab); + + stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH) + .set(stateManager.activeSearchQuery(SearchType.GLOBAL_SEARCH).get()); stateManager.activeTabProperty().get().ifPresent(tab -> tab.clearAndSelect(selectedEntry.getEntry())); stage.close(); } @@ -107,8 +130,7 @@ private void initialize() { keepOnTopSubscription = EasyBind.subscribe(viewModel.keepOnTop(), value -> { stage.setAlwaysOnTop(value); - keepOnTop.setGraphic(value - ? IconTheme.JabRefIcons.KEEP_ON_TOP.getGraphicNode() + keepOnTop.setGraphic(value ? IconTheme.JabRefIcons.KEEP_ON_TOP.getGraphicNode() : IconTheme.JabRefIcons.KEEP_ON_TOP_OFF.getGraphicNode()); }); @@ -122,7 +144,9 @@ private void initialize() { stage.setOnHidden(event -> { preferences.getSearchPreferences().setSearchWindowHeight(getHeight()); preferences.getSearchPreferences().setSearchWindowWidth(getWidth()); - preferences.getSearchPreferences().setSearchWindowDividerPosition(container.getDividers().getFirst().getPosition()); + preferences.getSearchPreferences() + .setSearchWindowDividerPosition(container.getDividers().getFirst().getPosition()); }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/search/GlobalSearchResultDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/search/GlobalSearchResultDialogViewModel.java index 7c267c2396c..3d0e1c9d09f 100644 --- a/jabgui/src/main/java/org/jabref/gui/search/GlobalSearchResultDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/search/GlobalSearchResultDialogViewModel.java @@ -9,7 +9,9 @@ import com.tobiasdiez.easybind.EasyBind; public class GlobalSearchResultDialogViewModel { + private final BibDatabaseContext searchDatabaseContext = new BibDatabaseContext(); + private final BooleanProperty keepOnTop = new SimpleBooleanProperty(); public GlobalSearchResultDialogViewModel(SearchPreferences searchPreferences) { @@ -25,4 +27,5 @@ public BibDatabaseContext getSearchDatabaseContext() { public BooleanProperty keepOnTop() { return this.keepOnTop; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/search/Highlighter.java b/jabgui/src/main/java/org/jabref/gui/search/Highlighter.java index aedb59c05e7..51c0dff0088 100644 --- a/jabgui/src/main/java/org/jabref/gui/search/Highlighter.java +++ b/jabgui/src/main/java/org/jabref/gui/search/Highlighter.java @@ -28,13 +28,16 @@ import org.slf4j.LoggerFactory; public class Highlighter { + private static final Logger LOGGER = LoggerFactory.getLogger(Highlighter.class); /** * Functions defined in {@link PostgreConstants#POSTGRES_FUNCTIONS} */ private static final String REGEXP_MARK = "SELECT regexp_mark(?, ?)"; + private static final String REGEXP_POSITIONS = "SELECT * FROM regexp_positions(?, ?)"; + private static Connection connection; private Highlighter() { @@ -58,7 +61,8 @@ private static void highlightTextNodes(Element element, String searchPattern) { String highlightedText = highlightNode(textNode.text(), searchPattern); textNode.text(""); textNode.after(highlightedText); - } else if (node instanceof Element element1) { + } + else if (node instanceof Element element1) { highlightTextNodes(element1, searchPattern); } } @@ -78,7 +82,8 @@ private static String highlightNode(String text, String searchPattern) { return resultSet.getString(1); } } - } catch (SQLException e) { + } + catch (SQLException e) { LOGGER.error("Error highlighting search terms in text", e); } return text; @@ -100,7 +105,8 @@ public static List findMatchPositions(String text, String pattern) { } return positions; } - } catch (SQLException e) { + } + catch (SQLException e) { LOGGER.error("Error getting match positions in text", e); } return List.of(); @@ -124,9 +130,7 @@ private static Optional buildSearchPattern(SearchQuery searchQuery) { return Optional.empty(); } - List terms = getSearchQueryNodes(searchQuery).stream() - .map(SearchQueryNode::term) - .toList(); + List terms = getSearchQueryNodes(searchQuery).stream().map(SearchQueryNode::term).toList(); return buildSearchPattern(terms); } @@ -137,4 +141,5 @@ private static List getSearchQueryNodes(SearchQuery searchQuery public static Optional buildSearchPattern(List terms) { return terms.isEmpty() ? Optional.empty() : Optional.of(String.join("|", terms)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/search/MatchCategory.java b/jabgui/src/main/java/org/jabref/gui/search/MatchCategory.java index c07fe9a30d9..ea8795d8dc1 100644 --- a/jabgui/src/main/java/org/jabref/gui/search/MatchCategory.java +++ b/jabgui/src/main/java/org/jabref/gui/search/MatchCategory.java @@ -1,11 +1,11 @@ package org.jabref.gui.search; /** - * @implNote The order of constants in this enum is important because it is used for sorting the main table. + * @implNote The order of constants in this enum is important because it is used for + * sorting the main table. */ public enum MatchCategory { - MATCHING_SEARCH_AND_GROUPS, - MATCHING_SEARCH_NOT_GROUPS, - MATCHING_GROUPS_NOT_SEARCH, - NOT_MATCHING_SEARCH_AND_GROUPS + + MATCHING_SEARCH_AND_GROUPS, MATCHING_SEARCH_NOT_GROUPS, MATCHING_GROUPS_NOT_SEARCH, NOT_MATCHING_SEARCH_AND_GROUPS + } diff --git a/jabgui/src/main/java/org/jabref/gui/search/RebuildFulltextSearchIndexAction.java b/jabgui/src/main/java/org/jabref/gui/search/RebuildFulltextSearchIndexAction.java index 1e1ce28a72c..284bb7cd0b5 100644 --- a/jabgui/src/main/java/org/jabref/gui/search/RebuildFulltextSearchIndexAction.java +++ b/jabgui/src/main/java/org/jabref/gui/search/RebuildFulltextSearchIndexAction.java @@ -14,18 +14,20 @@ public class RebuildFulltextSearchIndexAction extends SimpleCommand { private final StateManager stateManager; + private final DialogService dialogService; + private final Supplier tabSupplier; + private boolean shouldContinue = true; - public RebuildFulltextSearchIndexAction(StateManager stateManager, - Supplier tabSupplier, - DialogService dialogService, - CliPreferences preferences) { + public RebuildFulltextSearchIndexAction(StateManager stateManager, Supplier tabSupplier, + DialogService dialogService, CliPreferences preferences) { this.stateManager = stateManager; this.dialogService = dialogService; this.tabSupplier = tabSupplier; - this.executable.bind(needsDatabase(stateManager).and(preferences.getFilePreferences().fulltextIndexLinkedFilesProperty())); + this.executable + .bind(needsDatabase(stateManager).and(preferences.getFilePreferences().fulltextIndexLinkedFilesProperty())); } @Override @@ -55,4 +57,5 @@ private void rebuildIndex() { } tabSupplier.get().getIndexManager().rebuildFullTextIndex(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/search/SearchFieldRightClickMenu.java b/jabgui/src/main/java/org/jabref/gui/search/SearchFieldRightClickMenu.java index 1d2a64ad0b5..437e57bf5d8 100644 --- a/jabgui/src/main/java/org/jabref/gui/search/SearchFieldRightClickMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/search/SearchFieldRightClickMenu.java @@ -17,20 +17,27 @@ import org.controlsfx.control.textfield.CustomTextField; public class SearchFieldRightClickMenu { + public static ContextMenu create(StateManager stateManager, CustomTextField searchField) { ActionFactory factory = new ActionFactory(); ContextMenu contextMenu = new ContextMenu(); - contextMenu.getItems().addAll( - factory.createMenuItem(StandardActions.UNDO, new EditorContextAction(StandardActions.UNDO, searchField)), - factory.createMenuItem(StandardActions.REDO, new EditorContextAction(StandardActions.REDO, searchField)), - factory.createMenuItem(StandardActions.CUT, new EditorContextAction(StandardActions.CUT, searchField)), - factory.createMenuItem(StandardActions.COPY, new EditorContextAction(StandardActions.COPY, searchField)), - factory.createMenuItem(StandardActions.PASTE, new EditorContextAction(StandardActions.PASTE, searchField)), - factory.createMenuItem(StandardActions.DELETE, new EditorContextAction(StandardActions.DELETE, searchField)), - factory.createMenuItem(StandardActions.SELECT_ALL, new EditorContextAction(StandardActions.SELECT_ALL, searchField)), - new SeparatorMenuItem(), - createSearchFromHistorySubMenu(stateManager, searchField)); + contextMenu.getItems() + .addAll(factory.createMenuItem(StandardActions.UNDO, + new EditorContextAction(StandardActions.UNDO, searchField)), + factory.createMenuItem(StandardActions.REDO, + new EditorContextAction(StandardActions.REDO, searchField)), + factory.createMenuItem(StandardActions.CUT, + new EditorContextAction(StandardActions.CUT, searchField)), + factory.createMenuItem(StandardActions.COPY, + new EditorContextAction(StandardActions.COPY, searchField)), + factory.createMenuItem(StandardActions.PASTE, + new EditorContextAction(StandardActions.PASTE, searchField)), + factory.createMenuItem(StandardActions.DELETE, + new EditorContextAction(StandardActions.DELETE, searchField)), + factory.createMenuItem(StandardActions.SELECT_ALL, + new EditorContextAction(StandardActions.SELECT_ALL, searchField)), + new SeparatorMenuItem(), createSearchFromHistorySubMenu(stateManager, searchField)); return contextMenu; } @@ -43,7 +50,8 @@ public static Menu createSearchFromHistorySubMenu(StateManager stateManager, Cus if (searchHistory.isEmpty()) { MenuItem item = new MenuItem(Localization.lang("your search history is empty")); searchFromHistorySubMenu.getItems().add(item); - } else { + } + else { for (String query : searchHistory) { MenuItem item = factory.createMenuItem(() -> query, new SimpleCommand() { @Override @@ -63,4 +71,5 @@ public void execute() { } return searchFromHistorySubMenu; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/search/SearchResultsTable.java b/jabgui/src/main/java/org/jabref/gui/search/SearchResultsTable.java index 76ebb2cff26..e74d430cdcd 100644 --- a/jabgui/src/main/java/org/jabref/gui/search/SearchResultsTable.java +++ b/jabgui/src/main/java/org/jabref/gui/search/SearchResultsTable.java @@ -25,27 +25,18 @@ @AllowedToUseClassGetResource("JavaFX internally handles the passed URLs properly.") public class SearchResultsTable extends TableView { - public SearchResultsTable(SearchResultsTableDataModel model, - BibDatabaseContext database, - GuiPreferences preferences, - UndoManager undoManager, - DialogService dialogService, - StateManager stateManager, - TaskExecutor taskExecutor) { + public SearchResultsTable(SearchResultsTableDataModel model, BibDatabaseContext database, + GuiPreferences preferences, UndoManager undoManager, DialogService dialogService, StateManager stateManager, + TaskExecutor taskExecutor) { super(); this.getStyleClass().add("main-table"); MainTablePreferences mainTablePreferences = preferences.getMainTablePreferences(); - List> allCols = new MainTableColumnFactory( - database, - preferences, - preferences.getSearchDialogColumnPreferences(), - undoManager, - dialogService, - stateManager, - taskExecutor).createColumns(); + List> allCols = new MainTableColumnFactory(database, preferences, + preferences.getSearchDialogColumnPreferences(), undoManager, dialogService, stateManager, taskExecutor) + .createColumns(); if (allCols.stream().noneMatch(LibraryColumn.class::isInstance)) { allCols.addFirst(new LibraryColumn()); @@ -53,12 +44,14 @@ public SearchResultsTable(SearchResultsTableDataModel model, this.getColumns().addAll(allCols); this.getSortOrder().clear(); - preferences.getSearchDialogColumnPreferences().getColumnSortOrder().forEach(columnModel -> - this.getColumns().stream() - .map(column -> (MainTableColumn) column) - .filter(column -> column.getModel().equals(columnModel)) - .findFirst() - .ifPresent(column -> this.getSortOrder().add(column))); + preferences.getSearchDialogColumnPreferences() + .getColumnSortOrder() + .forEach(columnModel -> this.getColumns() + .stream() + .map(column -> (MainTableColumn) column) + .filter(column -> column.getModel().equals(columnModel)) + .findFirst() + .ifPresent(column -> this.getSortOrder().add(column))); if (mainTablePreferences.getResizeColumnsToFit()) { this.setColumnResizePolicy(new SmartConstrainedResizePolicy()); @@ -74,5 +67,5 @@ public SearchResultsTable(SearchResultsTableDataModel model, database.getDatabase().registerListener(this); } -} +} diff --git a/jabgui/src/main/java/org/jabref/gui/search/SearchResultsTableDataModel.java b/jabgui/src/main/java/org/jabref/gui/search/SearchResultsTableDataModel.java index be678bf11a4..14a8efec1f1 100644 --- a/jabgui/src/main/java/org/jabref/gui/search/SearchResultsTableDataModel.java +++ b/jabgui/src/main/java/org/jabref/gui/search/SearchResultsTableDataModel.java @@ -29,26 +29,35 @@ public class SearchResultsTableDataModel { private final ObservableList entriesViewModel = FXCollections.observableArrayList(); + private final SortedList entriesSorted; + private final ObjectProperty fieldValueFormatter; + private final StateManager stateManager; + private final FilteredList entriesFiltered; + private final TaskExecutor taskExecutor; - public SearchResultsTableDataModel(BibDatabaseContext bibDatabaseContext, GuiPreferences preferences, StateManager stateManager, TaskExecutor taskExecutor) { + public SearchResultsTableDataModel(BibDatabaseContext bibDatabaseContext, GuiPreferences preferences, + StateManager stateManager, TaskExecutor taskExecutor) { NameDisplayPreferences nameDisplayPreferences = preferences.getNameDisplayPreferences(); this.stateManager = stateManager; this.taskExecutor = taskExecutor; - this.fieldValueFormatter = new SimpleObjectProperty<>(new MainTableFieldValueFormatter(nameDisplayPreferences, bibDatabaseContext)); + this.fieldValueFormatter = new SimpleObjectProperty<>( + new MainTableFieldValueFormatter(nameDisplayPreferences, bibDatabaseContext)); populateEntriesViewModel(); - stateManager.getOpenDatabases().addListener((ListChangeListener) change -> populateEntriesViewModel()); + stateManager.getOpenDatabases() + .addListener((ListChangeListener) change -> populateEntriesViewModel()); entriesFiltered = new FilteredList<>(entriesViewModel, BibEntryTableViewModel::isVisible); // We need to wrap the list since otherwise sorting in the table does not work entriesSorted = new SortedList<>(entriesFiltered); - EasyBind.listen(stateManager.activeSearchQuery(SearchType.GLOBAL_SEARCH), (observable, oldValue, newValue) -> updateSearchMatches(newValue)); + EasyBind.listen(stateManager.activeSearchQuery(SearchType.GLOBAL_SEARCH), + (observable, oldValue, newValue) -> updateSearchMatches(newValue)); stateManager.searchResultSize(SearchType.GLOBAL_SEARCH).bind(Bindings.size(entriesFiltered)); } @@ -56,7 +65,8 @@ private void populateEntriesViewModel() { entriesViewModel.clear(); for (BibDatabaseContext context : stateManager.getOpenDatabases()) { ObservableList entriesForDb = context.getDatabase().getEntries(); - ObservableList viewModelForDb = EasyBind.mapBacked(entriesForDb, entry -> new BibEntryTableViewModel(entry, context, fieldValueFormatter), false); + ObservableList viewModelForDb = EasyBind.mapBacked(entriesForDb, + entry -> new BibEntryTableViewModel(entry, context, fieldValueFormatter), false); entriesViewModel.addAll(viewModelForDb); } } @@ -66,13 +76,15 @@ private void updateSearchMatches(Optional query) { if (query.isPresent()) { SearchResults searchResults = new SearchResults(); for (BibDatabaseContext context : stateManager.getOpenDatabases()) { - stateManager.getIndexManager(context).ifPresent(indexManager -> searchResults.mergeSearchResults(indexManager.search(query.get()))); + stateManager.getIndexManager(context) + .ifPresent(indexManager -> searchResults.mergeSearchResults(indexManager.search(query.get()))); } for (BibEntryTableViewModel entry : entriesViewModel) { entry.hasFullTextResultsProperty().set(searchResults.hasFulltextResults(entry.getEntry())); entry.isVisibleBySearch().set(searchResults.isMatched(entry.getEntry())); } - } else { + } + else { for (BibEntryTableViewModel entry : entriesViewModel) { entry.hasFullTextResultsProperty().set(false); entry.isVisibleBySearch().set(true); @@ -84,4 +96,5 @@ private void updateSearchMatches(Optional query) { public SortedList getEntriesFilteredAndSorted() { return entriesSorted; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/search/SearchTextField.java b/jabgui/src/main/java/org/jabref/gui/search/SearchTextField.java index 4cb2b060ba6..e4d5f80b4a0 100644 --- a/jabgui/src/main/java/org/jabref/gui/search/SearchTextField.java +++ b/jabgui/src/main/java/org/jabref/gui/search/SearchTextField.java @@ -24,16 +24,19 @@ public static CustomTextField create(KeyBindingRepository keyBindingRepository) textField.setLeft(graphicNode); textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> { - // Other key bindings are handled at org.jabref.gui.keyboard.TextInputKeyBindings + // Other key bindings are handled at + // org.jabref.gui.keyboard.TextInputKeyBindings // We need to handle clear search here to have the code "more clean" - // Otherwise, we would have to add a new class for this and handle the case hitting that class in TextInputKeyBindings + // Otherwise, we would have to add a new class for this and handle the case + // hitting that class in TextInputKeyBindings if (keyBindingRepository.matches(event, KeyBinding.CLEAR_SEARCH)) { - textField.clear(); - event.consume(); + textField.clear(); + event.consume(); } }); return textField; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/search/SearchType.java b/jabgui/src/main/java/org/jabref/gui/search/SearchType.java index b4309d88ac1..5e1421241ea 100644 --- a/jabgui/src/main/java/org/jabref/gui/search/SearchType.java +++ b/jabgui/src/main/java/org/jabref/gui/search/SearchType.java @@ -1,6 +1,7 @@ package org.jabref.gui.search; public enum SearchType { - NORMAL_SEARCH, - GLOBAL_SEARCH + + NORMAL_SEARCH, GLOBAL_SEARCH + } diff --git a/jabgui/src/main/java/org/jabref/gui/shared/ConnectToSharedDatabaseCommand.java b/jabgui/src/main/java/org/jabref/gui/shared/ConnectToSharedDatabaseCommand.java index 16b11a60cae..80ed18e4af1 100644 --- a/jabgui/src/main/java/org/jabref/gui/shared/ConnectToSharedDatabaseCommand.java +++ b/jabgui/src/main/java/org/jabref/gui/shared/ConnectToSharedDatabaseCommand.java @@ -10,6 +10,7 @@ public class ConnectToSharedDatabaseCommand extends SimpleCommand { private final LibraryTabContainer tabContainer; + private final DialogService dialogService; public ConnectToSharedDatabaseCommand(LibraryTabContainer tabContainer, DialogService dialogService) { @@ -21,4 +22,5 @@ public ConnectToSharedDatabaseCommand(LibraryTabContainer tabContainer, DialogSe public void execute() { dialogService.showCustomDialogAndWait(new SharedDatabaseLoginDialogView(tabContainer)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/shared/PullChangesFromSharedAction.java b/jabgui/src/main/java/org/jabref/gui/shared/PullChangesFromSharedAction.java index d19090cf367..154cfb6b947 100644 --- a/jabgui/src/main/java/org/jabref/gui/shared/PullChangesFromSharedAction.java +++ b/jabgui/src/main/java/org/jabref/gui/shared/PullChangesFromSharedAction.java @@ -12,7 +12,8 @@ public class PullChangesFromSharedAction extends SimpleCommand { public PullChangesFromSharedAction(StateManager stateManager) { this.stateManager = stateManager; - this.executable.bind(ActionHelper.needsDatabase(stateManager).and(ActionHelper.needsSharedDatabase(stateManager))); + this.executable + .bind(ActionHelper.needsDatabase(stateManager).and(ActionHelper.needsSharedDatabase(stateManager))); } public void execute() { @@ -21,4 +22,5 @@ public void execute() { dbmsSynchronizer.pullChanges(); }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogView.java b/jabgui/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogView.java index ed30a868a2b..2f1add37568 100644 --- a/jabgui/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogView.java +++ b/jabgui/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogView.java @@ -33,56 +33,111 @@ import jakarta.inject.Inject; /** - * This offers the user to connect to a remove SQL database. - * Moreover, it directly opens the shared database after successful connection. + * This offers the user to connect to a remove SQL database. Moreover, it directly opens + * the shared database after successful connection. */ public class SharedDatabaseLoginDialogView extends BaseDialog { - @FXML private ComboBox databaseType; - @FXML private TextField host; - @FXML private TextField database; - @FXML private TextField port; - @FXML private TextField user; - @FXML private PasswordField password; - @FXML private CheckBox rememberPassword; - @FXML private TextField folder; - @FXML private Button browseButton; - @FXML private CheckBox autosave; - @FXML private ButtonType connectButton; - @FXML private CheckBox useSSL; - @FXML private TextField fileKeystore; - @FXML private PasswordField passwordKeystore; - @FXML private Button browseKeystore; - @FXML private TextField serverTimezone; - @FXML private TextField jdbcUrl; - @FXML private CheckBox expertMode; - - @Inject private DialogService dialogService; - @Inject private GuiPreferences preferences; - @Inject private AiService aiService; - @Inject private StateManager stateManager; - @Inject private BibEntryTypesManager entryTypesManager; - @Inject private FileUpdateMonitor fileUpdateMonitor; - @Inject private UndoManager undoManager; - @Inject private ClipBoardManager clipBoardManager; - @Inject private TaskExecutor taskExecutor; + + @FXML + private ComboBox databaseType; + + @FXML + private TextField host; + + @FXML + private TextField database; + + @FXML + private TextField port; + + @FXML + private TextField user; + + @FXML + private PasswordField password; + + @FXML + private CheckBox rememberPassword; + + @FXML + private TextField folder; + + @FXML + private Button browseButton; + + @FXML + private CheckBox autosave; + + @FXML + private ButtonType connectButton; + + @FXML + private CheckBox useSSL; + + @FXML + private TextField fileKeystore; + + @FXML + private PasswordField passwordKeystore; + + @FXML + private Button browseKeystore; + + @FXML + private TextField serverTimezone; + + @FXML + private TextField jdbcUrl; + + @FXML + private CheckBox expertMode; + + @Inject + private DialogService dialogService; + + @Inject + private GuiPreferences preferences; + + @Inject + private AiService aiService; + + @Inject + private StateManager stateManager; + + @Inject + private BibEntryTypesManager entryTypesManager; + + @Inject + private FileUpdateMonitor fileUpdateMonitor; + + @Inject + private UndoManager undoManager; + + @Inject + private ClipBoardManager clipBoardManager; + + @Inject + private TaskExecutor taskExecutor; private final LibraryTabContainer tabContainer; + private SharedDatabaseLoginDialogViewModel viewModel; + private final ControlsFxVisualizer visualizer = new ControlsFxVisualizer(); public SharedDatabaseLoginDialogView(LibraryTabContainer tabContainer) { this.tabContainer = tabContainer; this.setTitle(Localization.lang("Connect to shared database")); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); ControlHelper.setAction(connectButton, this.getDialogPane(), event -> openDatabase()); Button btnConnect = (Button) this.getDialogPane().lookupButton(connectButton); // must be set here, because in initialize the button is still null btnConnect.disableProperty().bind(viewModel.formValidation().validProperty().not()); - btnConnect.textProperty().bind(EasyBind.map(viewModel.loadingProperty(), loading -> loading ? Localization.lang("Connecting...") : Localization.lang("Connect"))); + btnConnect.textProperty() + .bind(EasyBind.map(viewModel.loadingProperty(), + loading -> loading ? Localization.lang("Connecting...") : Localization.lang("Connect"))); } @FXML @@ -98,17 +153,8 @@ private void openDatabase() { private void initialize() { visualizer.setDecoration(new IconValidationDecorator()); - viewModel = new SharedDatabaseLoginDialogViewModel( - tabContainer, - dialogService, - preferences, - aiService, - stateManager, - entryTypesManager, - fileUpdateMonitor, - undoManager, - clipBoardManager, - taskExecutor); + viewModel = new SharedDatabaseLoginDialogViewModel(tabContainer, dialogService, preferences, aiService, + stateManager, entryTypesManager, fileUpdateMonitor, undoManager, clipBoardManager, taskExecutor); databaseType.getItems().addAll(DBMSType.values()); databaseType.getSelectionModel().select(0); @@ -138,18 +184,19 @@ private void initialize() { passwordKeystore.textProperty().bindBidirectional(viewModel.keyStorePasswordProperty()); rememberPassword.selectedProperty().bindBidirectional(viewModel.rememberPasswordProperty()); - // Must be executed after the initialization of the view, otherwise it doesn't work + // Must be executed after the initialization of the view, otherwise it doesn't + // work Platform.runLater(() -> { visualizer.initVisualization(viewModel.dbValidation(), database, true); visualizer.initVisualization(viewModel.hostValidation(), host, true); visualizer.initVisualization(viewModel.portValidation(), port, true); visualizer.initVisualization(viewModel.userValidation(), user, true); - EasyBind.subscribe(autosave.selectedProperty(), selected -> - visualizer.initVisualization(viewModel.folderValidation(), folder, true)); + EasyBind.subscribe(autosave.selectedProperty(), + selected -> visualizer.initVisualization(viewModel.folderValidation(), folder, true)); - EasyBind.subscribe(useSSL.selectedProperty(), selected -> - visualizer.initVisualization(viewModel.keystoreValidation(), fileKeystore, true)); + EasyBind.subscribe(useSSL.selectedProperty(), + selected -> visualizer.initVisualization(viewModel.keystoreValidation(), fileKeystore, true)); }); } @@ -162,4 +209,5 @@ private void showSaveDbToFileDialog(ActionEvent event) { private void showOpenKeystoreFileDialog(ActionEvent event) { viewModel.showOpenKeystoreFileDialog(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java index a39331be998..7e63084c72a 100644 --- a/jabgui/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java @@ -66,51 +66,75 @@ public class SharedDatabaseLoginDialogViewModel extends AbstractViewModel { private final ObjectProperty selectedDBMSType = new SimpleObjectProperty<>(DBMSType.values()[0]); private final StringProperty database = new SimpleStringProperty(""); + private final StringProperty host = new SimpleStringProperty(""); + private final StringProperty port = new SimpleStringProperty(""); + private final StringProperty user = new SimpleStringProperty(""); + private final StringProperty password = new SimpleStringProperty(""); + private final StringProperty folder = new SimpleStringProperty(""); + private final BooleanProperty autosave = new SimpleBooleanProperty(); + private final BooleanProperty rememberPassword = new SimpleBooleanProperty(); + private final BooleanProperty loading = new SimpleBooleanProperty(); + private final StringProperty keystore = new SimpleStringProperty(""); + private final BooleanProperty useSSL = new SimpleBooleanProperty(); + private final StringProperty keyStorePasswordProperty = new SimpleStringProperty(""); + private final StringProperty serverTimezone = new SimpleStringProperty(""); + private final BooleanProperty expertMode = new SimpleBooleanProperty(); + private final StringProperty jdbcUrl = new SimpleStringProperty(""); private final LibraryTabContainer tabContainer; + private final DialogService dialogService; + private final GuiPreferences preferences; + private final AiService aiService; + private final SharedDatabasePreferences sharedDatabasePreferences = new SharedDatabasePreferences(); + private final StateManager stateManager; + private final BibEntryTypesManager entryTypesManager; + private final FileUpdateMonitor fileUpdateMonitor; + private final UndoManager undoManager; + private final ClipBoardManager clipBoardManager; + private final TaskExecutor taskExecutor; private final Validator databaseValidator; + private final Validator hostValidator; + private final Validator portValidator; + private final Validator userValidator; + private final Validator folderValidator; + private final Validator keystoreValidator; + private final CompositeValidator formValidator; - public SharedDatabaseLoginDialogViewModel(LibraryTabContainer tabContainer, - DialogService dialogService, - GuiPreferences preferences, - AiService aiService, - StateManager stateManager, - BibEntryTypesManager entryTypesManager, - FileUpdateMonitor fileUpdateMonitor, - UndoManager undoManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor) { + public SharedDatabaseLoginDialogViewModel(LibraryTabContainer tabContainer, DialogService dialogService, + GuiPreferences preferences, AiService aiService, StateManager stateManager, + BibEntryTypesManager entryTypesManager, FileUpdateMonitor fileUpdateMonitor, UndoManager undoManager, + ClipBoardManager clipBoardManager, TaskExecutor taskExecutor) { this.tabContainer = tabContainer; this.dialogService = dialogService; this.preferences = preferences; @@ -128,12 +152,18 @@ public SharedDatabaseLoginDialogViewModel(LibraryTabContainer tabContainer, Predicate fileExists = input -> Files.exists(Path.of(input)); Predicate notEmptyAndfilesExist = notEmpty.and(fileExists); - databaseValidator = new FunctionBasedValidator<>(database, notEmpty, ValidationMessage.error(Localization.lang("Required field \"%0\" is empty.", Localization.lang("Library")))); - hostValidator = new FunctionBasedValidator<>(host, notEmpty, ValidationMessage.error(Localization.lang("Required field \"%0\" is empty.", Localization.lang("Port")))); - portValidator = new FunctionBasedValidator<>(port, notEmpty, ValidationMessage.error(Localization.lang("Required field \"%0\" is empty.", Localization.lang("Host")))); - userValidator = new FunctionBasedValidator<>(user, notEmpty, ValidationMessage.error(Localization.lang("Required field \"%0\" is empty.", Localization.lang("User")))); - folderValidator = new FunctionBasedValidator<>(folder, notEmptyAndfilesExist, ValidationMessage.error(Localization.lang("Please enter a valid file path."))); - keystoreValidator = new FunctionBasedValidator<>(keystore, notEmptyAndfilesExist, ValidationMessage.error(Localization.lang("Please enter a valid file path."))); + databaseValidator = new FunctionBasedValidator<>(database, notEmpty, ValidationMessage + .error(Localization.lang("Required field \"%0\" is empty.", Localization.lang("Library")))); + hostValidator = new FunctionBasedValidator<>(host, notEmpty, ValidationMessage + .error(Localization.lang("Required field \"%0\" is empty.", Localization.lang("Port")))); + portValidator = new FunctionBasedValidator<>(port, notEmpty, ValidationMessage + .error(Localization.lang("Required field \"%0\" is empty.", Localization.lang("Host")))); + userValidator = new FunctionBasedValidator<>(user, notEmpty, ValidationMessage + .error(Localization.lang("Required field \"%0\" is empty.", Localization.lang("User")))); + folderValidator = new FunctionBasedValidator<>(folder, notEmptyAndfilesExist, + ValidationMessage.error(Localization.lang("Please enter a valid file path."))); + keystoreValidator = new FunctionBasedValidator<>(keystore, notEmptyAndfilesExist, + ValidationMessage.error(Localization.lang("Please enter a valid file path."))); formValidator = new CompositeValidator(); formValidator.addValidators(databaseValidator, hostValidator, portValidator, userValidator); @@ -143,20 +173,22 @@ public SharedDatabaseLoginDialogViewModel(LibraryTabContainer tabContainer, public boolean openDatabase() { DBMSConnectionProperties connectionProperties = new DBMSConnectionPropertiesBuilder() - .setType(selectedDBMSType.getValue()) - .setHost(host.getValue()) - .setPort(Integer.parseInt(port.getValue())) - .setDatabase(database.getValue()) - .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()) - .setExpertMode(expertMode.getValue()) - .setJdbcUrl(jdbcUrl.getValue()) - .createDBMSConnectionProperties(); + .setType(selectedDBMSType.getValue()) + .setHost(host.getValue()) + .setPort(Integer.parseInt(port.getValue())) + .setDatabase(database.getValue()) + .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()) + .setExpertMode(expertMode.getValue()) + .setJdbcUrl(jdbcUrl.getValue()) + .createDBMSConnectionProperties(); setupKeyStore(); return openSharedDatabase(connectionProperties); @@ -179,10 +211,10 @@ private boolean openSharedDatabase(DBMSConnectionProperties connectionProperties Path localFilePath = Path.of(folder.getValue()); if (Files.exists(localFilePath) && !Files.isDirectory(localFilePath)) { - boolean overwriteFilePressed = dialogService.showConfirmationDialogAndWait(Localization.lang("Existing file"), + boolean overwriteFilePressed = dialogService.showConfirmationDialogAndWait( + Localization.lang("Existing file"), Localization.lang("'%0' exists. Overwrite file?", localFilePath.getFileName().toString()), - Localization.lang("Overwrite file"), - Localization.lang("Cancel")); + Localization.lang("Overwrite file"), Localization.lang("Cancel")); if (!overwriteFilePressed) { return true; } @@ -192,50 +224,42 @@ private boolean openSharedDatabase(DBMSConnectionProperties connectionProperties loading.set(true); try { - SharedDatabaseUIManager manager = new SharedDatabaseUIManager( - tabContainer, - dialogService, - preferences, - aiService, - stateManager, - entryTypesManager, - fileUpdateMonitor, - undoManager, - clipBoardManager, + SharedDatabaseUIManager manager = new SharedDatabaseUIManager(tabContainer, dialogService, preferences, + aiService, stateManager, entryTypesManager, fileUpdateMonitor, undoManager, clipBoardManager, taskExecutor); LibraryTab libraryTab = manager.openNewSharedDatabaseTab(connectionProperties); setPreferences(); if (!folder.getValue().isEmpty() && autosave.get()) { try { - new SaveDatabaseAction( - libraryTab, - dialogService, - preferences, - Injector.instantiateModelOrService(BibEntryTypesManager.class), - stateManager - ).saveAs(Path.of(folder.getValue())); - } catch (Throwable e) { + new SaveDatabaseAction(libraryTab, dialogService, preferences, + Injector.instantiateModelOrService(BibEntryTypesManager.class), stateManager) + .saveAs(Path.of(folder.getValue())); + } + catch (Throwable e) { LOGGER.error("Error while saving the database", e); } } return true; - } catch (SQLException | InvalidDBMSConnectionPropertiesException exception) { + } + catch (SQLException | InvalidDBMSConnectionPropertiesException exception) { dialogService.showErrorDialogAndWait(Localization.lang("Connection error"), exception); - } catch (DatabaseNotSupportedException exception) { + } + catch (DatabaseNotSupportedException exception) { ButtonType openHelp = new ButtonType("Open Help", ButtonData.OTHER); Optional result = dialogService.showCustomButtonDialogAndWait(AlertType.INFORMATION, Localization.lang("Migration help information"), - Localization.lang("Entered database has obsolete structure and is no longer supported.") - + "\n" + - Localization.lang("Click help to learn about the migration of pre-3.6 databases.") - + "\n" + - Localization.lang("However, a new database was created alongside the pre-3.6 one."), + Localization.lang("Entered database has obsolete structure and is no longer supported.") + "\n" + + Localization.lang("Click help to learn about the migration of pre-3.6 databases.") + "\n" + + Localization.lang("However, a new database was created alongside the pre-3.6 one."), ButtonType.OK, openHelp); - result.filter(btn -> btn.equals(openHelp)).ifPresent(btn -> new HelpAction(HelpFile.SQL_DATABASE_MIGRATION, dialogService, preferences.getExternalApplicationsPreferences()).execute()); + result.filter(btn -> btn.equals(openHelp)) + .ifPresent(btn -> new HelpAction(HelpFile.SQL_DATABASE_MIGRATION, dialogService, + preferences.getExternalApplicationsPreferences()) + .execute()); result.filter(ButtonType.OK::equals).ifPresent(btn -> openSharedDatabase(connectionProperties)); } loading.set(false); @@ -255,11 +279,14 @@ private void setPreferences() { if (rememberPassword.get()) { try { sharedDatabasePreferences.setPassword(new Password(password.getValue(), user.getValue()).encrypt()); - } catch (GeneralSecurityException | UnsupportedEncodingException e) { + } + catch (GeneralSecurityException | UnsupportedEncodingException e) { LOGGER.error("Could not store the password due to encryption problems.", e); } - } else { - sharedDatabasePreferences.clearPassword(); // for the case that the password is already set + } + else { + sharedDatabasePreferences.clearPassword(); // for the case that the password + // is already set } sharedDatabasePreferences.setRememberPassword(rememberPassword.get()); @@ -297,8 +324,10 @@ private void applyPreferences() { if (sharedDatabasePassword.isPresent() && sharedDatabaseUser.isPresent()) { try { - password.setValue(new Password(sharedDatabasePassword.get().toCharArray(), sharedDatabaseUser.get()).decrypt()); - } catch (GeneralSecurityException | UnsupportedEncodingException e) { + password.setValue( + new Password(sharedDatabasePassword.get().toCharArray(), sharedDatabaseUser.get()).decrypt()); + } + catch (GeneralSecurityException | UnsupportedEncodingException e) { LOGGER.error("Could not read the password due to decryption problems.", e); } } @@ -314,28 +343,28 @@ private boolean isSharedDatabaseAlreadyPresent(DBMSConnectionProperties connecti return libraryTabs.parallelStream().anyMatch(panel -> { BibDatabaseContext context = panel.getBibDatabaseContext(); - return (context.getLocation() == DatabaseLocation.SHARED) && - connectionProperties.equals(context.getDBMSSynchronizer().getConnectionProperties()); + return (context.getLocation() == DatabaseLocation.SHARED) + && connectionProperties.equals(context.getDBMSSynchronizer().getConnectionProperties()); }); } public void showSaveDbToFileDialog() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.BIBTEX_DB) - .withDefaultExtension(StandardFileType.BIBTEX_DB) - .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) - .build(); + .addExtensionFilter(StandardFileType.BIBTEX_DB) + .withDefaultExtension(StandardFileType.BIBTEX_DB) + .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) + .build(); Optional exportPath = dialogService.showFileSaveDialog(fileDialogConfiguration); exportPath.ifPresent(path -> folder.setValue(path.toString())); } public void showOpenKeystoreFileDialog() { FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(FileFilterConverter.ANY_FILE) - .addExtensionFilter(StandardFileType.JAVA_KEYSTORE) - .withDefaultExtension(StandardFileType.JAVA_KEYSTORE) - .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) - .build(); + .addExtensionFilter(FileFilterConverter.ANY_FILE) + .addExtensionFilter(StandardFileType.JAVA_KEYSTORE) + .withDefaultExtension(StandardFileType.JAVA_KEYSTORE) + .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) + .build(); Optional keystorePath = dialogService.showFileOpenDialog(fileDialogConfiguration); keystorePath.ifPresent(path -> keystore.setValue(path.toString())); } @@ -431,4 +460,5 @@ public BooleanProperty expertModeProperty() { public StringProperty jdbcUrlProperty() { return jdbcUrl; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java b/jabgui/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java index 8a8c0aa504c..ad017bd48e4 100644 --- a/jabgui/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java +++ b/jabgui/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java @@ -46,27 +46,31 @@ public class SharedDatabaseUIManager { private final LibraryTabContainer tabContainer; + private DatabaseSynchronizer dbmsSynchronizer; + private final DialogService dialogService; + private final GuiPreferences preferences; + private final AiService aiService; + private final StateManager stateManager; + private final BibEntryTypesManager entryTypesManager; + private final FileUpdateMonitor fileUpdateMonitor; + private final UndoManager undoManager; + private final ClipBoardManager clipBoardManager; + private final TaskExecutor taskExecutor; - public SharedDatabaseUIManager(LibraryTabContainer tabContainer, - DialogService dialogService, - GuiPreferences preferences, - AiService aiService, - StateManager stateManager, - BibEntryTypesManager entryTypesManager, - FileUpdateMonitor fileUpdateMonitor, - UndoManager undoManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor) { + public SharedDatabaseUIManager(LibraryTabContainer tabContainer, DialogService dialogService, + GuiPreferences preferences, AiService aiService, StateManager stateManager, + BibEntryTypesManager entryTypesManager, FileUpdateMonitor fileUpdateMonitor, UndoManager undoManager, + ClipBoardManager clipBoardManager, TaskExecutor taskExecutor) { this.tabContainer = tabContainer; this.dialogService = dialogService; this.preferences = preferences; @@ -87,21 +91,21 @@ public void listen(ConnectionLostEvent connectionLostEvent) { Optional answer = dialogService.showCustomButtonDialogAndWait(AlertType.WARNING, Localization.lang("Connection lost"), - Localization.lang("The connection to the server has been terminated."), - reconnect, - workOffline, + Localization.lang("The connection to the server has been terminated."), reconnect, workOffline, closeLibrary); if (answer.isPresent()) { if (answer.get().equals(reconnect)) { tabContainer.closeTab(tabContainer.getCurrentLibraryTab()); dialogService.showCustomDialogAndWait(new SharedDatabaseLoginDialogView(tabContainer)); - } else if (answer.get().equals(workOffline)) { + } + else if (answer.get().equals(workOffline)) { connectionLostEvent.bibDatabaseContext().convertToLocalDatabase(); tabContainer.getLibraryTabs().forEach(tab -> tab.updateTabTitle(tab.isModified())); dialogService.notify(Localization.lang("Working offline.")); } - } else { + } + else { tabContainer.closeTab(tabContainer.getCurrentLibraryTab()); } } @@ -113,24 +117,30 @@ public void listen(UpdateRefusedEvent updateRefusedEvent) { BibEntry localBibEntry = updateRefusedEvent.localBibEntry(); BibEntry sharedBibEntry = updateRefusedEvent.sharedBibEntry(); - String message = Localization.lang("Update could not be performed due to existing change conflicts.") + "\r\n" + - Localization.lang("You are not working on the newest version of the entry.") + "\r\n" + - Localization.lang("Shared version: %0", String.valueOf(sharedBibEntry.getSharedBibEntryData().getVersion())) + "\r\n" + - Localization.lang("Local version: %0", String.valueOf(localBibEntry.getSharedBibEntryData().getVersion())) + "\r\n" + - Localization.lang("Press \"Merge entries\" to merge the changes and resolve this problem.") + "\r\n" + - Localization.lang("Canceling this operation will leave your changes unsynchronized."); + String message = Localization.lang("Update could not be performed due to existing change conflicts.") + "\r\n" + + Localization.lang("You are not working on the newest version of the entry.") + "\r\n" + + Localization + .lang("Shared version: %0", String.valueOf(sharedBibEntry.getSharedBibEntryData().getVersion())) + + "\r\n" + + Localization.lang("Local version: %0", + String.valueOf(localBibEntry.getSharedBibEntryData().getVersion())) + + "\r\n" + Localization.lang("Press \"Merge entries\" to merge the changes and resolve this problem.") + + "\r\n" + Localization.lang("Canceling this operation will leave your changes unsynchronized."); ButtonType merge = new ButtonType(Localization.lang("Merge entries"), ButtonBar.ButtonData.YES); - Optional response = dialogService.showCustomButtonDialogAndWait(AlertType.CONFIRMATION, Localization.lang("Update refused"), message, ButtonType.CANCEL, merge); + Optional response = dialogService.showCustomButtonDialogAndWait(AlertType.CONFIRMATION, + Localization.lang("Update refused"), message, ButtonType.CANCEL, merge); if (response.isPresent() && response.get().equals(merge)) { MergeEntriesDialog dialog = new MergeEntriesDialog(localBibEntry, sharedBibEntry, preferences); dialog.setTitle(Localization.lang("Update refused")); - Optional mergedEntry = dialogService.showCustomDialogAndWait(dialog).map(EntriesMergeResult::mergedEntry); + Optional mergedEntry = dialogService.showCustomDialogAndWait(dialog) + .map(EntriesMergeResult::mergedEntry); mergedEntry.ifPresent(mergedBibEntry -> { - mergedBibEntry.getSharedBibEntryData().setSharedID(sharedBibEntry.getSharedBibEntryData().getSharedID()); + mergedBibEntry.getSharedBibEntryData() + .setSharedID(sharedBibEntry.getSharedBibEntryData().getSharedID()); mergedBibEntry.getSharedBibEntryData().setVersion(sharedBibEntry.getSharedBibEntryData().getVersion()); dbmsSynchronizer.synchronizeSharedEntry(mergedBibEntry); @@ -147,8 +157,7 @@ public void listen(SharedEntriesNotPresentEvent event) { undoManager.addEdit(new UndoableRemoveEntries(libraryTab.getDatabase(), event.bibEntries())); dialogService.showInformationDialogAndWait(Localization.lang("Shared entry is no longer present"), - Localization.lang("The entry you currently work on has been deleted on the shared side.") - + "\n" + Localization.lang("The entry you currently work on has been deleted on the shared side.") + "\n" + Localization.lang("You can restore the entry using the \"Undo\" operation.")); stateManager.setSelectedEntries(List.of()); @@ -157,7 +166,6 @@ public void listen(SharedEntriesNotPresentEvent event) { /** * Opens a new shared database tab with the given {@link DBMSConnectionProperties}. - * * @param dbmsConnectionProperties Connection data * @return BasePanel which also used by {@link SaveDatabaseAction} */ @@ -169,27 +177,18 @@ public LibraryTab openNewSharedDatabaseTab(DBMSConnectionProperties dbmsConnecti dbmsSynchronizer = bibDatabaseContext.getDBMSSynchronizer(); dbmsSynchronizer.openSharedDatabase(new DBMSConnection(dbmsConnectionProperties)); dbmsSynchronizer.registerListener(this); - dialogService.notify(Localization.lang("Connection to %0 server established.", dbmsConnectionProperties.getType().toString())); - - LibraryTab libraryTab = LibraryTab.createLibraryTab( - bibDatabaseContext, - tabContainer, - dialogService, - aiService, - preferences, - stateManager, - fileUpdateMonitor, - entryTypesManager, - undoManager, - clipBoardManager, + dialogService.notify(Localization.lang("Connection to %0 server established.", + dbmsConnectionProperties.getType().toString())); + + LibraryTab libraryTab = LibraryTab.createLibraryTab(bibDatabaseContext, tabContainer, dialogService, aiService, + preferences, stateManager, fileUpdateMonitor, entryTypesManager, undoManager, clipBoardManager, taskExecutor); tabContainer.addTab(libraryTab, true); return libraryTab; } - public void openSharedDatabaseFromParserResult(ParserResult parserResult) - throws SQLException, DatabaseNotSupportedException, InvalidDBMSConnectionPropertiesException, - NotASharedDatabaseException { + public void openSharedDatabaseFromParserResult(ParserResult parserResult) throws SQLException, + DatabaseNotSupportedException, InvalidDBMSConnectionPropertiesException, NotASharedDatabaseException { Optional sharedDatabaseIDOptional = parserResult.getDatabase().getSharedDatabaseID(); @@ -198,7 +197,8 @@ public void openSharedDatabaseFromParserResult(ParserResult parserResult) } String sharedDatabaseID = sharedDatabaseIDOptional.get(); - DBMSConnectionProperties dbmsConnectionProperties = new DBMSConnectionProperties(new SharedDatabasePreferences(sharedDatabaseID)); + DBMSConnectionProperties dbmsConnectionProperties = new DBMSConnectionProperties( + new SharedDatabasePreferences(sharedDatabaseID)); BibDatabaseContext bibDatabaseContext = getBibDatabaseContextForSharedDatabase(); @@ -208,7 +208,8 @@ public void openSharedDatabaseFromParserResult(ParserResult parserResult) dbmsSynchronizer = bibDatabaseContext.getDBMSSynchronizer(); dbmsSynchronizer.openSharedDatabase(new DBMSConnection(dbmsConnectionProperties)); dbmsSynchronizer.registerListener(this); - dialogService.notify(Localization.lang("Connection to %0 server established.", dbmsConnectionProperties.getType().toString())); + dialogService.notify(Localization.lang("Connection to %0 server established.", + dbmsConnectionProperties.getType().toString())); parserResult.setDatabaseContext(bibDatabaseContext); } @@ -216,13 +217,11 @@ public void openSharedDatabaseFromParserResult(ParserResult parserResult) private BibDatabaseContext getBibDatabaseContextForSharedDatabase() { BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(); bibDatabaseContext.setMode(preferences.getLibraryPreferences().getDefaultBibDatabaseMode()); - DBMSSynchronizer synchronizer = new DBMSSynchronizer( - bibDatabaseContext, - preferences.getBibEntryPreferences().getKeywordSeparator(), - preferences.getFieldPreferences(), - preferences.getCitationKeyPatternPreferences().getKeyPatterns(), - fileUpdateMonitor); + DBMSSynchronizer synchronizer = new DBMSSynchronizer(bibDatabaseContext, + preferences.getBibEntryPreferences().getKeywordSeparator(), preferences.getFieldPreferences(), + preferences.getCitationKeyPatternPreferences().getKeyPatterns(), fileUpdateMonitor); bibDatabaseContext.convertToSharedDatabase(synchronizer); return bibDatabaseContext; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/sidepane/GroupsSidePaneComponent.java b/jabgui/src/main/java/org/jabref/gui/sidepane/GroupsSidePaneComponent.java index e5f48ae21f4..5ffb9028396 100644 --- a/jabgui/src/main/java/org/jabref/gui/sidepane/GroupsSidePaneComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/sidepane/GroupsSidePaneComponent.java @@ -16,18 +16,20 @@ import org.jabref.logic.l10n.Localization; public class GroupsSidePaneComponent extends SidePaneComponent { + private final GroupsPreferences groupsPreferences; + private final DialogService dialogService; + private final Button intersectionUnionToggle = IconTheme.JabRefIcons.GROUP_INTERSECTION.asButton(); + private final ToggleButton filterToggle = IconTheme.JabRefIcons.FILTER.asToggleButton(); + private final ToggleButton invertToggle = IconTheme.JabRefIcons.INVERT.asToggleButton(); - public GroupsSidePaneComponent(SimpleCommand closeCommand, - SimpleCommand moveUpCommand, - SimpleCommand moveDownCommand, - SidePaneContentFactory contentFactory, - GroupsPreferences groupsPreferences, - DialogService dialogService) { + public GroupsSidePaneComponent(SimpleCommand closeCommand, SimpleCommand moveUpCommand, + SimpleCommand moveDownCommand, SidePaneContentFactory contentFactory, GroupsPreferences groupsPreferences, + DialogService dialogService) { super(SidePaneType.GROUPS, closeCommand, moveUpCommand, moveDownCommand, contentFactory); this.groupsPreferences = groupsPreferences; this.dialogService = dialogService; @@ -52,14 +54,18 @@ private void setupFilterToggle() { addExtraNodeToHeader(filterToggle, 0); filterToggle.setTooltip(new Tooltip(Localization.lang("Filter by groups"))); filterToggle.setSelected(groupsPreferences.groupViewModeProperty().contains(GroupViewMode.FILTER)); - filterToggle.selectedProperty().addListener((observable, oldValue, newValue) -> groupsPreferences.setGroupViewMode(GroupViewMode.FILTER, newValue)); + filterToggle.selectedProperty() + .addListener((observable, oldValue, newValue) -> groupsPreferences.setGroupViewMode(GroupViewMode.FILTER, + newValue)); } private void setupInvertToggle() { addExtraNodeToHeader(invertToggle, 0); invertToggle.setTooltip(new Tooltip(Localization.lang("Invert groups"))); invertToggle.setSelected(groupsPreferences.groupViewModeProperty().contains(GroupViewMode.INVERT)); - invertToggle.selectedProperty().addListener((observable, oldValue, newValue) -> groupsPreferences.setGroupViewMode(GroupViewMode.INVERT, newValue)); + invertToggle.selectedProperty() + .addListener((observable, oldValue, newValue) -> groupsPreferences.setGroupViewMode(GroupViewMode.INVERT, + newValue)); } private class ToggleUnionIntersectionAction extends SimpleCommand { @@ -71,10 +77,13 @@ public void execute() { if (!mode.contains(GroupViewMode.INTERSECTION)) { groupsPreferences.setGroupViewMode(GroupViewMode.INTERSECTION, true); dialogService.notify(Localization.lang("Group view mode set to intersection")); - } else { + } + else { groupsPreferences.setGroupViewMode(GroupViewMode.INTERSECTION, false); dialogService.notify(Localization.lang("Group view mode set to union")); } } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/sidepane/SidePane.java b/jabgui/src/main/java/org/jabref/gui/sidepane/SidePane.java index 1a9ccd5e2c8..9c556d6b66c 100644 --- a/jabgui/src/main/java/org/jabref/gui/sidepane/SidePane.java +++ b/jabgui/src/main/java/org/jabref/gui/sidepane/SidePane.java @@ -24,53 +24,39 @@ import org.jabref.model.util.FileUpdateMonitor; public class SidePane extends VBox { + private final SidePaneViewModel viewModel; + private final GuiPreferences preferences; + private final StateManager stateManager; // These bindings need to be stored, otherwise they are garbage collected @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") private final Map visibleBindings = new HashMap<>(); - public SidePane(LibraryTabContainer tabContainer, - GuiPreferences preferences, - JournalAbbreviationRepository abbreviationRepository, - TaskExecutor taskExecutor, - DialogService dialogService, - AiService aiService, - StateManager stateManager, - AdaptVisibleTabs adaptVisibleTabs, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager, - ClipBoardManager clipBoardManager, - UndoManager undoManager) { + public SidePane(LibraryTabContainer tabContainer, GuiPreferences preferences, + JournalAbbreviationRepository abbreviationRepository, TaskExecutor taskExecutor, + DialogService dialogService, AiService aiService, StateManager stateManager, + AdaptVisibleTabs adaptVisibleTabs, FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager, ClipBoardManager clipBoardManager, UndoManager undoManager) { this.stateManager = stateManager; this.preferences = preferences; - this.viewModel = new SidePaneViewModel( - tabContainer, - preferences, - abbreviationRepository, - stateManager, - taskExecutor, - adaptVisibleTabs, - dialogService, - aiService, - fileUpdateMonitor, - entryTypesManager, - clipBoardManager, - undoManager); + this.viewModel = new SidePaneViewModel(tabContainer, preferences, abbreviationRepository, stateManager, + taskExecutor, adaptVisibleTabs, dialogService, aiService, fileUpdateMonitor, entryTypesManager, + clipBoardManager, undoManager); stateManager.getVisibleSidePaneComponents().addListener((ListChangeListener) c -> updateView()); updateView(); } - private void updateView() { + private void updateView() { getChildren().clear(); - for (SidePaneType type : stateManager.getVisibleSidePaneComponents()) { - SidePaneComponent view = viewModel.getSidePaneComponent(type); - getChildren().add(view); - } - } + for (SidePaneType type : stateManager.getVisibleSidePaneComponents()) { + SidePaneComponent view = viewModel.getSidePaneComponent(type); + getChildren().add(view); + } + } public BooleanBinding paneVisibleBinding(SidePaneType pane) { BooleanBinding visibility = Bindings.createBooleanBinding( @@ -87,4 +73,5 @@ public SimpleCommand getToggleCommandFor(SidePaneType sidePane) { public SidePaneComponent getSidePaneComponent(SidePaneType type) { return viewModel.getSidePaneComponent(type); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/sidepane/SidePaneComponent.java b/jabgui/src/main/java/org/jabref/gui/sidepane/SidePaneComponent.java index 6260a908123..450928ffe97 100644 --- a/jabgui/src/main/java/org/jabref/gui/sidepane/SidePaneComponent.java +++ b/jabgui/src/main/java/org/jabref/gui/sidepane/SidePaneComponent.java @@ -15,19 +15,21 @@ import org.jabref.logic.l10n.Localization; public class SidePaneComponent extends BorderPane { + private final SidePaneType sidePaneType; + private final SimpleCommand closeCommand; + private final SimpleCommand moveUpCommand; + private final SimpleCommand moveDownCommand; + private final SidePaneContentFactory contentFactory; private HBox buttonContainer; - public SidePaneComponent(SidePaneType sidePaneType, - SimpleCommand closeCommand, - SimpleCommand moveUpCommand, - SimpleCommand moveDownCommand, - SidePaneContentFactory contentFactory) { + public SidePaneComponent(SidePaneType sidePaneType, SimpleCommand closeCommand, SimpleCommand moveUpCommand, + SimpleCommand moveDownCommand, SidePaneContentFactory contentFactory) { this.sidePaneType = sidePaneType; this.closeCommand = closeCommand; this.moveUpCommand = moveUpCommand; @@ -81,4 +83,5 @@ public void requestFocus() { } } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/sidepane/SidePaneContentFactory.java b/jabgui/src/main/java/org/jabref/gui/sidepane/SidePaneContentFactory.java index b8d1dfdc967..bb417453621 100644 --- a/jabgui/src/main/java/org/jabref/gui/sidepane/SidePaneContentFactory.java +++ b/jabgui/src/main/java/org/jabref/gui/sidepane/SidePaneContentFactory.java @@ -23,31 +23,36 @@ import com.airhacks.afterburner.injection.Injector; public class SidePaneContentFactory { + private final LibraryTabContainer tabContainer; + private final GuiPreferences preferences; + private final JournalAbbreviationRepository abbreviationRepository; + private final TaskExecutor taskExecutor; + private final DialogService dialogService; + private final AiService aiService; + private final StateManager stateManager; + private final AdaptVisibleTabs adaptVisibleTabs; + private final FileUpdateMonitor fileUpdateMonitor; + private final BibEntryTypesManager entryTypesManager; + private final ClipBoardManager clipBoardManager; + private final UndoManager undoManager; - public SidePaneContentFactory(LibraryTabContainer tabContainer, - GuiPreferences preferences, - JournalAbbreviationRepository abbreviationRepository, - TaskExecutor taskExecutor, - DialogService dialogService, - AiService aiService, - StateManager stateManager, - AdaptVisibleTabs adaptVisibleTabs, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager, - ClipBoardManager clipBoardManager, - UndoManager undoManager) { + public SidePaneContentFactory(LibraryTabContainer tabContainer, GuiPreferences preferences, + JournalAbbreviationRepository abbreviationRepository, TaskExecutor taskExecutor, + DialogService dialogService, AiService aiService, StateManager stateManager, + AdaptVisibleTabs adaptVisibleTabs, FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager, ClipBoardManager clipBoardManager, UndoManager undoManager) { this.tabContainer = tabContainer; this.preferences = preferences; this.abbreviationRepository = abbreviationRepository; @@ -64,35 +69,18 @@ public SidePaneContentFactory(LibraryTabContainer tabContainer, public Node create(SidePaneType sidePaneType) { return switch (sidePaneType) { - case GROUPS -> new GroupTreeView( - taskExecutor, - stateManager, - adaptVisibleTabs, - preferences, - dialogService, - aiService, - undoManager, - fileUpdateMonitor); - case OPEN_OFFICE -> new OpenOfficePanel( - tabContainer, - preferences, - preferences.getOpenOfficePreferences(Injector.instantiateModelOrService(JournalAbbreviationRepository.class)), - preferences.getExternalApplicationsPreferences(), - preferences.getLayoutFormatterPreferences(), - preferences.getCitationKeyPatternPreferences(), - abbreviationRepository, - (UiTaskExecutor) taskExecutor, - dialogService, - aiService, - stateManager, - fileUpdateMonitor, - entryTypesManager, - clipBoardManager, - undoManager).getContent(); - case WEB_SEARCH -> new WebSearchPaneView( - preferences, - dialogService, - stateManager); + case GROUPS -> new GroupTreeView(taskExecutor, stateManager, adaptVisibleTabs, preferences, dialogService, + aiService, undoManager, fileUpdateMonitor); + case OPEN_OFFICE -> new OpenOfficePanel(tabContainer, preferences, + preferences.getOpenOfficePreferences( + Injector.instantiateModelOrService(JournalAbbreviationRepository.class)), + preferences.getExternalApplicationsPreferences(), preferences.getLayoutFormatterPreferences(), + preferences.getCitationKeyPatternPreferences(), abbreviationRepository, + (UiTaskExecutor) taskExecutor, dialogService, aiService, stateManager, fileUpdateMonitor, + entryTypesManager, clipBoardManager, undoManager) + .getContent(); + case WEB_SEARCH -> new WebSearchPaneView(preferences, dialogService, stateManager); }; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/sidepane/SidePaneType.java b/jabgui/src/main/java/org/jabref/gui/sidepane/SidePaneType.java index ebfd8f0d8b7..6627c310ab1 100644 --- a/jabgui/src/main/java/org/jabref/gui/sidepane/SidePaneType.java +++ b/jabgui/src/main/java/org/jabref/gui/sidepane/SidePaneType.java @@ -10,12 +10,15 @@ * Definition of all possible components in the side pane. */ public enum SidePaneType { + OPEN_OFFICE("OpenOffice/LibreOffice", IconTheme.JabRefIcons.FILE_OPENOFFICE, StandardActions.TOGGLE_OO), WEB_SEARCH(Localization.lang("Web search"), IconTheme.JabRefIcons.WWW, StandardActions.TOGGLE_WEB_SEARCH), GROUPS(Localization.lang("Groups"), IconTheme.JabRefIcons.TOGGLE_GROUPS, StandardActions.TOGGLE_GROUPS); private final String title; + private final JabRefIcon icon; + private final Action toggleAction; SidePaneType(String title, JabRefIcon icon, Action toggleAction) { @@ -35,4 +38,5 @@ public JabRefIcon getIcon() { public Action getToggleAction() { return toggleAction; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/sidepane/SidePaneViewModel.java b/jabgui/src/main/java/org/jabref/gui/sidepane/SidePaneViewModel.java index ecbeac0e2b9..7498ab2f276 100644 --- a/jabgui/src/main/java/org/jabref/gui/sidepane/SidePaneViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/sidepane/SidePaneViewModel.java @@ -32,52 +32,41 @@ import org.slf4j.LoggerFactory; public class SidePaneViewModel extends AbstractViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(SidePaneViewModel.class); private final Map sidePaneComponentLookup = new HashMap<>(); private final GuiPreferences preferences; + private final StateManager stateManager; + private final SidePaneContentFactory sidePaneContentFactory; + private final AdaptVisibleTabs adaptVisibleTabs; + private final DialogService dialogService; - public SidePaneViewModel(LibraryTabContainer tabContainer, - GuiPreferences preferences, - JournalAbbreviationRepository abbreviationRepository, - StateManager stateManager, - TaskExecutor taskExecutor, - AdaptVisibleTabs adaptVisibleTabs, - DialogService dialogService, - AiService aiService, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager, - ClipBoardManager clipBoardManager, - UndoManager undoManager) { + public SidePaneViewModel(LibraryTabContainer tabContainer, GuiPreferences preferences, + JournalAbbreviationRepository abbreviationRepository, StateManager stateManager, TaskExecutor taskExecutor, + AdaptVisibleTabs adaptVisibleTabs, DialogService dialogService, AiService aiService, + FileUpdateMonitor fileUpdateMonitor, BibEntryTypesManager entryTypesManager, + ClipBoardManager clipBoardManager, UndoManager undoManager) { this.preferences = preferences; this.stateManager = stateManager; this.adaptVisibleTabs = adaptVisibleTabs; this.dialogService = dialogService; - this.sidePaneContentFactory = new SidePaneContentFactory( - tabContainer, - preferences, - abbreviationRepository, - taskExecutor, - dialogService, - aiService, - stateManager, - adaptVisibleTabs, - fileUpdateMonitor, - entryTypesManager, - clipBoardManager, - undoManager); + this.sidePaneContentFactory = new SidePaneContentFactory(tabContainer, preferences, abbreviationRepository, + taskExecutor, dialogService, aiService, stateManager, adaptVisibleTabs, fileUpdateMonitor, + entryTypesManager, clipBoardManager, undoManager); preferences.getSidePanePreferences().visiblePanes().forEach(this::show); getPanes().addListener((ListChangeListener) change -> { while (change.next()) { if (change.wasAdded()) { preferences.getSidePanePreferences().visiblePanes().add(change.getAddedSubList().getFirst()); - } else if (change.wasRemoved()) { + } + else if (change.wasRemoved()) { preferences.getSidePanePreferences().visiblePanes().remove(change.getRemoved().getFirst()); } } @@ -88,18 +77,11 @@ protected SidePaneComponent getSidePaneComponent(SidePaneType pane) { SidePaneComponent sidePaneComponent = sidePaneComponentLookup.get(pane); if (sidePaneComponent == null) { sidePaneComponent = switch (pane) { - case GROUPS -> new GroupsSidePaneComponent( - new ClosePaneAction(pane), - new MoveUpAction(pane), - new MoveDownAction(pane), - sidePaneContentFactory, - preferences.getGroupsPreferences(), + case GROUPS -> new GroupsSidePaneComponent(new ClosePaneAction(pane), new MoveUpAction(pane), + new MoveDownAction(pane), sidePaneContentFactory, preferences.getGroupsPreferences(), dialogService); - case WEB_SEARCH, OPEN_OFFICE -> new SidePaneComponent(pane, - new ClosePaneAction(pane), - new MoveUpAction(pane), - new MoveDownAction(pane), - sidePaneContentFactory); + case WEB_SEARCH, OPEN_OFFICE -> new SidePaneComponent(pane, new ClosePaneAction(pane), + new MoveUpAction(pane), new MoveDownAction(pane), sidePaneContentFactory); }; sidePaneComponentLookup.put(pane, sidePaneComponent); } @@ -107,12 +89,12 @@ protected SidePaneComponent getSidePaneComponent(SidePaneType pane) { } /** - * Stores the current configuration of visible panes in the preferences, so that we show panes at the preferred - * position next time. + * Stores the current configuration of visible panes in the preferences, so that we + * show panes at the preferred position next time. */ private void updatePreferredPositions() { - Map preferredPositions = new HashMap<>(preferences.getSidePanePreferences() - .getPreferredPositions()); + Map preferredPositions = new HashMap<>( + preferences.getSidePanePreferences().getPreferredPositions()); IntStream.range(0, getPanes().size()).forEach(i -> preferredPositions.put(getPanes().get(i), i)); preferences.getSidePanePreferences().setPreferredPositions(preferredPositions); } @@ -124,10 +106,12 @@ public void moveUp(SidePaneType pane) { int newPosition = currentPosition - 1; swap(getPanes(), currentPosition, newPosition); updatePreferredPositions(); - } else { + } + else { LOGGER.debug("SidePaneComponent is already at the bottom"); } - } else { + } + else { LOGGER.warn("SidePaneComponent {} not visible", pane.getTitle()); } } @@ -139,10 +123,12 @@ public void moveDown(SidePaneType pane) { int newPosition = currentPosition + 1; swap(getPanes(), currentPosition, newPosition); updatePreferredPositions(); - } else { + } + else { LOGGER.debug("SidePaneComponent {} is already at the top", pane.getTitle()); } - } else { + } + else { LOGGER.warn("SidePaneComponent {} not visible", pane.getTitle()); } } @@ -151,7 +137,8 @@ private void show(SidePaneType pane) { if (!getPanes().contains(pane)) { getPanes().add(pane); getPanes().sort(new PreferredIndexSort(preferences.getSidePanePreferences())); - } else { + } + else { LOGGER.warn("SidePaneComponent {} not visible", pane.getTitle()); } } @@ -183,9 +170,11 @@ public int compare(SidePaneType type1, SidePaneType type2) { int pos2 = preferredPositions.getOrDefault(type2, 0); return Integer.compare(pos1, pos2); } + } private class MoveUpAction extends SimpleCommand { + private final SidePaneType toMoveUpPane; public MoveUpAction(SidePaneType toMoveUpPane) { @@ -196,9 +185,11 @@ public MoveUpAction(SidePaneType toMoveUpPane) { public void execute() { moveUp(toMoveUpPane); } + } private class MoveDownAction extends SimpleCommand { + private final SidePaneType toMoveDownPane; public MoveDownAction(SidePaneType toMoveDownPane) { @@ -209,9 +200,11 @@ public MoveDownAction(SidePaneType toMoveDownPane) { public void execute() { moveDown(toMoveDownPane); } + } public class ClosePaneAction extends SimpleCommand { + private final SidePaneType toClosePane; public ClosePaneAction(SidePaneType toClosePane) { @@ -222,5 +215,7 @@ public ClosePaneAction(SidePaneType toClosePane) { public void execute() { stateManager.getVisibleSidePaneComponents().remove(toClosePane); } + } + } diff --git a/jabgui/src/main/java/org/jabref/gui/sidepane/TogglePaneAction.java b/jabgui/src/main/java/org/jabref/gui/sidepane/TogglePaneAction.java index 864a3e70591..7fc111722b4 100644 --- a/jabgui/src/main/java/org/jabref/gui/sidepane/TogglePaneAction.java +++ b/jabgui/src/main/java/org/jabref/gui/sidepane/TogglePaneAction.java @@ -5,8 +5,11 @@ import org.jabref.gui.frame.SidePanePreferences; public class TogglePaneAction extends SimpleCommand { + private final StateManager stateManager; + private final SidePaneType pane; + private final SidePanePreferences sidePanePreferences; public TogglePaneAction(StateManager stateManager, SidePaneType pane, SidePanePreferences sidePanePreferences) { @@ -19,9 +22,12 @@ public TogglePaneAction(StateManager stateManager, SidePaneType pane, SidePanePr public void execute() { if (!stateManager.getVisibleSidePaneComponents().contains(pane)) { stateManager.getVisibleSidePaneComponents().add(pane); - stateManager.getVisibleSidePaneComponents().sort(new SidePaneViewModel.PreferredIndexSort(sidePanePreferences)); - } else { + stateManager.getVisibleSidePaneComponents() + .sort(new SidePaneViewModel.PreferredIndexSort(sidePanePreferences)); + } + else { stateManager.getVisibleSidePaneComponents().remove(pane); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/slr/EditExistingStudyAction.java b/jabgui/src/main/java/org/jabref/gui/slr/EditExistingStudyAction.java index f86eae44389..44da5f5afe3 100644 --- a/jabgui/src/main/java/org/jabref/gui/slr/EditExistingStudyAction.java +++ b/jabgui/src/main/java/org/jabref/gui/slr/EditExistingStudyAction.java @@ -17,9 +17,11 @@ import org.slf4j.LoggerFactory; public class EditExistingStudyAction extends SimpleCommand { + private static final Logger LOGGER = LoggerFactory.getLogger(EditExistingStudyAction.class); private final DialogService dialogService; + private final StateManager stateManager; public EditExistingStudyAction(DialogService dialogService, StateManager stateManager) { @@ -51,13 +53,17 @@ public void execute() { Study study; try { - study = new StudyYamlParser().parseStudyYamlFile(studyDirectory.resolve(StudyRepository.STUDY_DEFINITION_FILE_NAME)); - } catch (IOException e) { + study = new StudyYamlParser() + .parseStudyYamlFile(studyDirectory.resolve(StudyRepository.STUDY_DEFINITION_FILE_NAME)); + } + catch (IOException e) { dialogService.showErrorDialogAndWait(Localization.lang("Error opening file"), e); return; } - // When the dialog returns, the study.yml file is updated (or kept unmodified at Cancel) + // When the dialog returns, the study.yml file is updated (or kept unmodified at + // Cancel) dialogService.showCustomDialogAndWait(new ManageStudyDefinitionView(study, studyDirectory)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/slr/ExistingStudySearchAction.java b/jabgui/src/main/java/org/jabref/gui/slr/ExistingStudySearchAction.java index 809d7ba7a76..e9e1c9a46a4 100644 --- a/jabgui/src/main/java/org/jabref/gui/slr/ExistingStudySearchAction.java +++ b/jabgui/src/main/java/org/jabref/gui/slr/ExistingStudySearchAction.java @@ -27,50 +27,42 @@ import org.slf4j.LoggerFactory; public class ExistingStudySearchAction extends SimpleCommand { + private static final Logger LOGGER = LoggerFactory.getLogger(ExistingStudySearchAction.class); protected final DialogService dialogService; protected Path studyDirectory; + protected final CliPreferences preferences; + protected final StateManager stateManager; private final FileUpdateMonitor fileUpdateMonitor; + private final TaskExecutor taskExecutor; + private final LibraryTabContainer tabContainer; + private final Supplier openDatabaseActionSupplier; /** * @param tabContainer Required to close the tab before the study is updated - * @param openDatabaseActionSupplier Required to open the tab after the study is executed + * @param openDatabaseActionSupplier Required to open the tab after the study is + * executed */ - public ExistingStudySearchAction( - LibraryTabContainer tabContainer, - Supplier openDatabaseActionSupplier, - DialogService dialogService, - FileUpdateMonitor fileUpdateMonitor, - TaskExecutor taskExecutor, - CliPreferences preferences, + public ExistingStudySearchAction(LibraryTabContainer tabContainer, + Supplier openDatabaseActionSupplier, DialogService dialogService, + FileUpdateMonitor fileUpdateMonitor, TaskExecutor taskExecutor, CliPreferences preferences, StateManager stateManager) { - this(tabContainer, - openDatabaseActionSupplier, - dialogService, - fileUpdateMonitor, - taskExecutor, - preferences, - stateManager, - false); + this(tabContainer, openDatabaseActionSupplier, dialogService, fileUpdateMonitor, taskExecutor, preferences, + stateManager, false); } - protected ExistingStudySearchAction( - LibraryTabContainer tabContainer, - Supplier openDatabaseActionSupplier, - DialogService dialogService, - FileUpdateMonitor fileUpdateMonitor, - TaskExecutor taskExecutor, - CliPreferences preferences, - StateManager stateManager, - boolean isNew) { + protected ExistingStudySearchAction(LibraryTabContainer tabContainer, + Supplier openDatabaseActionSupplier, DialogService dialogService, + FileUpdateMonitor fileUpdateMonitor, TaskExecutor taskExecutor, CliPreferences preferences, + StateManager stateManager, boolean isNew) { this.tabContainer = tabContainer; this.openDatabaseActionSupplier = openDatabaseActionSupplier; this.dialogService = dialogService; @@ -104,39 +96,37 @@ public void execute() { protected void crawl() { try { crawlPreparation(this.studyDirectory); - } catch (IOException | GitAPIException e) { + } + catch (IOException | GitAPIException e) { dialogService.showErrorDialogAndWait(Localization.lang("Study repository could not be created"), e); return; } final Crawler crawler; try { - crawler = new Crawler( - this.studyDirectory, - new SlrGitHandler(this.studyDirectory), - preferences, - new BibEntryTypesManager(), - fileUpdateMonitor); - } catch (IOException | ParseException | JabRefException e) { + crawler = new Crawler(this.studyDirectory, new SlrGitHandler(this.studyDirectory), preferences, + new BibEntryTypesManager(), fileUpdateMonitor); + } + catch (IOException | ParseException | JabRefException e) { LOGGER.error("Error during reading of study definition file.", e); - dialogService.showErrorDialogAndWait(Localization.lang("Error during reading of study definition file."), e); + dialogService.showErrorDialogAndWait(Localization.lang("Error during reading of study definition file."), + e); return; } dialogService.notify(Localization.lang("Searching...")); BackgroundTask.wrap(() -> { - crawler.performCrawl(); - return 0; // Return any value to make this a callable instead of a runnable. This allows throwing exceptions. - }) - .onFailure(e -> { - LOGGER.error("Error during persistence of crawling results."); - dialogService.showErrorDialogAndWait(Localization.lang("Error during persistence of crawling results."), e); - }) - .onSuccess(unused -> { - dialogService.notify(Localization.lang("Finished Searching")); - openDatabaseActionSupplier.get().openFile(Path.of(this.studyDirectory.toString(), Crawler.FILENAME_STUDY_RESULT_BIB)); - }) - .executeWith(taskExecutor); + crawler.performCrawl(); + return 0; // Return any value to make this a callable instead of a runnable. + // This allows throwing exceptions. + }).onFailure(e -> { + LOGGER.error("Error during persistence of crawling results."); + dialogService.showErrorDialogAndWait(Localization.lang("Error during persistence of crawling results."), e); + }).onSuccess(unused -> { + dialogService.notify(Localization.lang("Finished Searching")); + openDatabaseActionSupplier.get() + .openFile(Path.of(this.studyDirectory.toString(), Crawler.FILENAME_STUDY_RESULT_BIB)); + }).executeWith(taskExecutor); } /** @@ -150,4 +140,5 @@ protected void crawlPreparation(Path studyRepositoryRoot) throws IOException, Gi // Future work: Properly close the tab (with saving, ...) tabContainer.closeTab(tabContainer.getCurrentLibraryTab()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionView.java b/jabgui/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionView.java index 15dba31fdad..37a0c22e883 100644 --- a/jabgui/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionView.java +++ b/jabgui/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionView.java @@ -41,43 +41,84 @@ import org.slf4j.LoggerFactory; /** - * This class controls the user interface of the study definition management dialog. The UI elements and their layout - * are defined in the FXML file. + * This class controls the user interface of the study definition management dialog. The + * UI elements and their layout are defined in the FXML file. */ public class ManageStudyDefinitionView extends BaseDialog { + private static final Logger LOGGER = LoggerFactory.getLogger(ManageStudyDefinitionView.class); - @FXML private TextField studyTitle; - @FXML private TextField addAuthor; - @FXML private TextField addResearchQuestion; - @FXML private TextField addQuery; - @FXML private TextField studyDirectory; - @FXML private Button selectStudyDirectory; + @FXML + private TextField studyTitle; + + @FXML + private TextField addAuthor; + + @FXML + private TextField addResearchQuestion; + + @FXML + private TextField addQuery; + + @FXML + private TextField studyDirectory; + + @FXML + private Button selectStudyDirectory; + + @FXML + private ButtonType saveSurveyButtonType; + + @FXML + private Label helpIcon; + + @FXML + private TableView authorTableView; + + @FXML + private TableColumn authorsColumn; + + @FXML + private TableColumn authorsActionColumn; + + @FXML + private TableView questionTableView; + + @FXML + private TableColumn questionsColumn; + + @FXML + private TableColumn questionsActionColumn; + + @FXML + private TableView queryTableView; + + @FXML + private TableColumn queriesColumn; + + @FXML + private TableColumn queriesActionColumn; - @FXML private ButtonType saveSurveyButtonType; - @FXML private Label helpIcon; + @FXML + private TableView catalogTable; - @FXML private TableView authorTableView; - @FXML private TableColumn authorsColumn; - @FXML private TableColumn authorsActionColumn; + @FXML + private TableColumn catalogEnabledColumn; - @FXML private TableView questionTableView; - @FXML private TableColumn questionsColumn; - @FXML private TableColumn questionsActionColumn; + @FXML + private TableColumn catalogColumn; - @FXML private TableView queryTableView; - @FXML private TableColumn queriesColumn; - @FXML private TableColumn queriesActionColumn; + @FXML + private Label directoryWarning; - @FXML private TableView catalogTable; - @FXML private TableColumn catalogEnabledColumn; - @FXML private TableColumn catalogColumn; + @Inject + private DialogService dialogService; - @FXML private Label directoryWarning; + @Inject + private GuiPreferences preferences; - @Inject private DialogService dialogService; - @Inject private GuiPreferences preferences; - @Inject private ThemeManager themeManager; + @Inject + private ThemeManager themeManager; private ManageStudyDefinitionViewModel viewModel; @@ -91,7 +132,6 @@ public class ManageStudyDefinitionView extends BaseDialog /** * This is used to create a new study - * * @param pathToStudyDataDirectory This directory is proposed in the file chooser */ public ManageStudyDefinitionView(Path pathToStudyDataDirectory) { @@ -99,9 +139,7 @@ public ManageStudyDefinitionView(Path pathToStudyDataDirectory) { this.setTitle(Localization.lang("Define study parameters")); this.study = Optional.empty(); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); setupSaveSurveyButton(false); @@ -110,7 +148,6 @@ public ManageStudyDefinitionView(Path pathToStudyDataDirectory) { /** * This is used to edit an existing study. - * * @param study the study to edit * @param studyDirectory the directory of the study */ @@ -119,9 +156,7 @@ public ManageStudyDefinitionView(Study study, Path studyDirectory) { this.setTitle(Localization.lang("Manage study definition")); this.study = Optional.of(study); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); setupSaveSurveyButton(true); @@ -135,13 +170,13 @@ private void setupSaveSurveyButton(boolean isEdit) { saveSurveyButton.setText(Localization.lang("Start survey")); } - saveSurveyButton.disableProperty().bind(Bindings.or(Bindings.or(Bindings.or(Bindings.or(Bindings.or( - Bindings.isEmpty(viewModel.getQueries()), - Bindings.isEmpty(viewModel.getCatalogs())), - Bindings.isEmpty(viewModel.getAuthors())), - viewModel.getTitle().isEmpty()), - viewModel.getDirectory().isEmpty()), - directoryWarning.visibleProperty())); + saveSurveyButton.disableProperty() + .bind(Bindings.or(Bindings.or( + Bindings.or(Bindings.or( + Bindings.or(Bindings.isEmpty(viewModel.getQueries()), + Bindings.isEmpty(viewModel.getCatalogs())), + Bindings.isEmpty(viewModel.getAuthors())), viewModel.getTitle().isEmpty()), + viewModel.getDirectory().isEmpty()), directoryWarning.visibleProperty())); setResultConverter(button -> { if (button == saveSurveyButtonType) { @@ -156,26 +191,21 @@ private void setupSaveSurveyButton(boolean isEdit) { @FXML private void initialize() { if (study.isEmpty()) { - viewModel = new ManageStudyDefinitionViewModel( - preferences.getImportFormatPreferences(), - preferences.getImporterPreferences(), - preferences.getWorkspacePreferences(), - dialogService); - } else { - viewModel = new ManageStudyDefinitionViewModel( - study.get(), - pathToStudyDataDirectory, - preferences.getImportFormatPreferences(), - preferences.getImporterPreferences(), - preferences.getWorkspacePreferences(), - dialogService); + viewModel = new ManageStudyDefinitionViewModel(preferences.getImportFormatPreferences(), + preferences.getImporterPreferences(), preferences.getWorkspacePreferences(), dialogService); + } + else { + viewModel = new ManageStudyDefinitionViewModel(study.get(), pathToStudyDataDirectory, + preferences.getImportFormatPreferences(), preferences.getImporterPreferences(), + preferences.getWorkspacePreferences(), dialogService); // The directory of the study cannot be changed studyDirectory.setEditable(false); selectStudyDirectory.setDisable(true); } - // Listen whether any catalogs are removed from selection -> Add back to the catalog selector + // Listen whether any catalogs are removed from selection -> Add back to the + // catalog selector studyTitle.textProperty().bindBidirectional(viewModel.titleProperty()); studyDirectory.textProperty().bindBidirectional(viewModel.getDirectory()); @@ -189,15 +219,18 @@ private void updateDirectoryWarning(Path directory) { if (!Files.isDirectory(directory)) { directoryWarning.setText(Localization.lang("Warning: The selected directory is not a valid directory.")); directoryWarning.setVisible(true); - } else { + } + else { try (Stream entries = Files.list(directory)) { if (entries.findAny().isPresent()) { directoryWarning.setText(Localization.lang("Warning: The selected directory is not empty.")); directoryWarning.setVisible(true); - } else { + } + else { directoryWarning.setVisible(false); } - } catch (IOException e) { + } + catch (IOException e) { directoryWarning.setText(Localization.lang("Warning: Failed to check if the directory is empty.")); directoryWarning.setVisible(true); } @@ -211,7 +244,8 @@ private void initAuthorTab() { } private void initQuestionsTab() { - setupCommonPropertiesForTables(addResearchQuestion, this::addResearchQuestion, questionsColumn, questionsActionColumn); + setupCommonPropertiesForTables(addResearchQuestion, this::addResearchQuestion, questionsColumn, + questionsActionColumn); setupCellFactories(questionsColumn, questionsActionColumn, viewModel::deleteQuestion); questionTableView.setItems(viewModel.getResearchQuestions()); } @@ -223,21 +257,19 @@ private void initQueriesTab() { // TODO: Keep until PR #7279 is merged helpIcon.setTooltip(new Tooltip(new StringJoiner("\n") - .add(Localization.lang("Query terms are separated by spaces.")) - .add(Localization.lang("All query terms are joined using the logical AND, and OR operators") + ".") - .add(Localization.lang("If the sequence of terms is relevant wrap them in double quotes") + "(\").") - .add(Localization.lang("An example:") + " rain AND (clouds OR drops) AND \"precipitation distribution\"") - .toString())); + .add(Localization.lang("Query terms are separated by spaces.")) + .add(Localization.lang("All query terms are joined using the logical AND, and OR operators") + ".") + .add(Localization.lang("If the sequence of terms is relevant wrap them in double quotes") + "(\").") + .add(Localization.lang("An example:") + " rain AND (clouds OR drops) AND \"precipitation distribution\"") + .toString())); } private void initCatalogsTab() { - new ViewModelTableRowFactory() - .withOnMouseClickedEvent((entry, event) -> { - if (event.getButton() == MouseButton.PRIMARY) { - entry.setEnabled(!entry.isEnabled()); - } - }) - .install(catalogTable); + new ViewModelTableRowFactory().withOnMouseClickedEvent((entry, event) -> { + if (event.getButton() == MouseButton.PRIMARY) { + entry.setEnabled(!entry.isEnabled()); + } + }).install(catalogTable); if (study.isEmpty()) { viewModel.initializeSelectedCatalogs(); @@ -257,10 +289,8 @@ private void initCatalogsTab() { catalogTable.setItems(viewModel.getCatalogs()); } - private void setupCommonPropertiesForTables(Node addControl, - Runnable addAction, - TableColumn contentColumn, - TableColumn actionColumn) { + private void setupCommonPropertiesForTables(Node addControl, Runnable addAction, + TableColumn contentColumn, TableColumn actionColumn) { addControl.setOnKeyPressed(event -> { if (event.getCode() == KeyCode.ENTER) { addAction.run(); @@ -273,17 +303,15 @@ private void setupCommonPropertiesForTables(Node addControl, actionColumn.setResizable(false); } - private void setupCellFactories(TableColumn contentColumn, - TableColumn actionColumn, - Consumer removeAction) { + private void setupCellFactories(TableColumn contentColumn, TableColumn actionColumn, + Consumer removeAction) { contentColumn.setCellValueFactory(param -> new SimpleStringProperty(param.getValue())); actionColumn.setCellValueFactory(param -> new SimpleStringProperty(param.getValue())); new ValueTableCellFactory() - .withGraphic(item -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) - .withTooltip(name -> Localization.lang("Remove")) - .withOnMouseClickedEvent(item -> evt -> - removeAction.accept(item)) - .install(actionColumn); + .withGraphic(item -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) + .withTooltip(name -> Localization.lang("Remove")) + .withOnMouseClickedEvent(item -> evt -> removeAction.accept(item)) + .install(actionColumn); } @FXML @@ -307,13 +335,15 @@ private void addQuery() { @FXML public void selectStudyDirectory() { DirectoryDialogConfiguration directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(pathToStudyDataDirectory) - .build(); + .withInitialDirectory(pathToStudyDataDirectory) + .build(); - Optional selectedDirectoryOptional = dialogService.showDirectorySelectionDialog(directoryDialogConfiguration); + Optional selectedDirectoryOptional = dialogService + .showDirectorySelectionDialog(directoryDialogConfiguration); selectedDirectoryOptional.ifPresent(selectedDirectory -> { viewModel.setStudyDirectory(Optional.of(selectedDirectory)); updateDirectoryWarning(selectedDirectory); }); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionViewModel.java b/jabgui/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionViewModel.java index 165d2335688..501af666ed5 100644 --- a/jabgui/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionViewModel.java @@ -39,22 +39,24 @@ import org.slf4j.LoggerFactory; /** - * This class provides a model for managing study definitions. - * To visualize the model one can bind the properties to UI elements. + * This class provides a model for managing study definitions. To visualize the model one + * can bind the properties to UI elements. */ public class ManageStudyDefinitionViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(ManageStudyDefinitionViewModel.class); - private static final Set DEFAULT_SELECTION = Set.of( - ACMPortalFetcher.FETCHER_NAME, - IEEE.FETCHER_NAME, - SpringerFetcher.FETCHER_NAME, - DBLPFetcher.FETCHER_NAME); + private static final Set DEFAULT_SELECTION = Set.of(ACMPortalFetcher.FETCHER_NAME, IEEE.FETCHER_NAME, + SpringerFetcher.FETCHER_NAME, DBLPFetcher.FETCHER_NAME); private final StringProperty title = new SimpleStringProperty(); + private final ObservableList authors = FXCollections.observableArrayList(); + private final ObservableList researchQuestions = FXCollections.observableArrayList(); + private final ObservableList queries = FXCollections.observableArrayList(); + private final ObservableList databases = FXCollections.observableArrayList(); // Hold the complement of databases for the selector @@ -68,36 +70,32 @@ public class ManageStudyDefinitionViewModel { * Constructor for a new study */ public ManageStudyDefinitionViewModel(ImportFormatPreferences importFormatPreferences, - ImporterPreferences importerPreferences, - WorkspacePreferences workspacePreferences, - DialogService dialogService) { + ImporterPreferences importerPreferences, WorkspacePreferences workspacePreferences, + DialogService dialogService) { databases.addAll(WebFetchers.getSearchBasedFetchers(importFormatPreferences, importerPreferences) - .stream() - .map(SearchBasedFetcher::getName) - // The user wants to select specific fetchers - // The fetcher summarizing ALL fetchers can be emulated by selecting ALL fetchers (which happens rarely when doing an SLR) - .filter(name -> !CompositeSearchBasedFetcher.FETCHER_NAME.equals(name)) - .map(name -> { - boolean enabled = DEFAULT_SELECTION.contains(name); - return new StudyCatalogItem(name, enabled); - }) - .toList()); + .stream() + .map(SearchBasedFetcher::getName) + // The user wants to select specific fetchers + // The fetcher summarizing ALL fetchers can be emulated by selecting ALL + // fetchers (which happens rarely when doing an SLR) + .filter(name -> !CompositeSearchBasedFetcher.FETCHER_NAME.equals(name)) + .map(name -> { + boolean enabled = DEFAULT_SELECTION.contains(name); + return new StudyCatalogItem(name, enabled); + }) + .toList()); this.dialogService = Objects.requireNonNull(dialogService); this.workspacePreferences = Objects.requireNonNull(workspacePreferences); } /** * Constructor for an existing study - * - * @param study The study to initialize the UI from + * @param study The study to initialize the UI from * @param studyDirectory The path where the study resides */ - public ManageStudyDefinitionViewModel(Study study, - Path studyDirectory, - ImportFormatPreferences importFormatPreferences, - ImporterPreferences importerPreferences, - WorkspacePreferences workspacePreferences, - DialogService dialogService) { + public ManageStudyDefinitionViewModel(Study study, Path studyDirectory, + ImportFormatPreferences importFormatPreferences, ImporterPreferences importerPreferences, + WorkspacePreferences workspacePreferences, DialogService dialogService) { // copy the content of the study object into the UI fields authors.addAll(Objects.requireNonNull(study).getAuthors()); title.setValue(study.getTitle()); @@ -105,16 +103,17 @@ public ManageStudyDefinitionViewModel(Study study, queries.addAll(study.getQueries().stream().map(StudyQuery::getQuery).toList()); List studyDatabases = study.getDatabases(); databases.addAll(WebFetchers.getSearchBasedFetchers(importFormatPreferences, importerPreferences) - .stream() - .map(SearchBasedFetcher::getName) - // The user wants to select specific fetchers - // The fetcher summarizing ALL fetchers can be emulated by selecting ALL fetchers (which happens rarely when doing an SLR) - .filter(name -> !CompositeSearchBasedFetcher.FETCHER_NAME.equals(name)) - .map(name -> { - boolean enabled = studyDatabases.contains(new StudyDatabase(name, true)); - return new StudyCatalogItem(name, enabled); - }) - .toList()); + .stream() + .map(SearchBasedFetcher::getName) + // The user wants to select specific fetchers + // The fetcher summarizing ALL fetchers can be emulated by selecting ALL + // fetchers (which happens rarely when doing an SLR) + .filter(name -> !CompositeSearchBasedFetcher.FETCHER_NAME.equals(name)) + .map(name -> { + boolean enabled = studyDatabases.contains(new StudyDatabase(name, true)); + return new StudyCatalogItem(name, enabled); + }) + .toList()); this.directory.set(Objects.requireNonNull(studyDirectory).toString()); this.dialogService = Objects.requireNonNull(dialogService); @@ -167,17 +166,19 @@ public void addQuery(String query) { } public SlrStudyAndDirectory saveStudy() { - Study study = new Study( - authors, - title.getValueSafe(), - researchQuestions, + Study study = new Study(authors, title.getValueSafe(), researchQuestions, queries.stream().map(StudyQuery::new).collect(Collectors.toList()), - databases.stream().map(studyDatabaseItem -> new StudyDatabase(studyDatabaseItem.getName(), studyDatabaseItem.isEnabled())).filter(StudyDatabase::isEnabled).collect(Collectors.toList())); + databases.stream() + .map(studyDatabaseItem -> new StudyDatabase(studyDatabaseItem.getName(), + studyDatabaseItem.isEnabled())) + .filter(StudyDatabase::isEnabled) + .collect(Collectors.toList())); Path studyDirectory; final String studyDirectoryAsString = directory.getValueSafe(); try { studyDirectory = Path.of(studyDirectoryAsString); - } catch (InvalidPathException e) { + } + catch (InvalidPathException e) { LOGGER.error("Invalid path was provided: {}", studyDirectoryAsString); dialogService.notify(Localization.lang("Unable to write to %0.", studyDirectoryAsString)); // We do not assume another path - we return that there is an invalid object. @@ -186,20 +187,20 @@ public SlrStudyAndDirectory saveStudy() { Path studyDefinitionFile = studyDirectory.resolve(StudyRepository.STUDY_DEFINITION_FILE_NAME); try { new StudyYamlParser().writeStudyYamlFile(study, studyDefinitionFile); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Could not write study file {}", studyDefinitionFile, e); - dialogService.notify(Localization.lang("Please enter a valid file path.") + - ": " + studyDirectoryAsString); + dialogService.notify(Localization.lang("Please enter a valid file path.") + ": " + studyDirectoryAsString); // We do not assume another path - we return that there is an invalid object. return null; } try { new GitHandler(studyDirectory).createCommitOnCurrentBranch("Update study definition", false); - } catch (IOException | GitAPIException e) { + } + catch (IOException | GitAPIException e) { LOGGER.error("Could not commit study definition file in directory {}", studyDirectory, e); - dialogService.notify(Localization.lang("Please enter a valid file path.") + - ": " + studyDirectory); + dialogService.notify(Localization.lang("Please enter a valid file path.") + ": " + studyDirectory); // We continue nevertheless as the directory itself could be valid } @@ -235,10 +236,11 @@ public void initializeSelectedCatalogs() { public void updateSelectedCatalogs() { List selectedCatalogsList = databases.stream() - .filter(StudyCatalogItem::isEnabled) - .map(StudyCatalogItem::getName) - .collect(Collectors.toList()); + .filter(StudyCatalogItem::isEnabled) + .map(StudyCatalogItem::getName) + .collect(Collectors.toList()); workspacePreferences.setSelectedSlrCatalogs(selectedCatalogsList); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/slr/SlrStudyAndDirectory.java b/jabgui/src/main/java/org/jabref/gui/slr/SlrStudyAndDirectory.java index 223d99d5224..e90932b7585 100644 --- a/jabgui/src/main/java/org/jabref/gui/slr/SlrStudyAndDirectory.java +++ b/jabgui/src/main/java/org/jabref/gui/slr/SlrStudyAndDirectory.java @@ -6,7 +6,9 @@ import org.jabref.model.study.Study; public class SlrStudyAndDirectory { + private final Study study; + private final Path studyDirectory; public SlrStudyAndDirectory(Study study, Path studyDirectory) { @@ -21,4 +23,5 @@ public Path getStudyDirectory() { public Study getStudy() { return study; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/slr/StartNewStudyAction.java b/jabgui/src/main/java/org/jabref/gui/slr/StartNewStudyAction.java index 3b2b34f105b..e7df16abd22 100644 --- a/jabgui/src/main/java/org/jabref/gui/slr/StartNewStudyAction.java +++ b/jabgui/src/main/java/org/jabref/gui/slr/StartNewStudyAction.java @@ -25,43 +25,39 @@ /** * Used to start a new study: *
      - *
    1. Let the user input meta data for the study.
    2. - *
    3. Let JabRef do the crawling afterwards.
    4. + *
    5. Let the user input meta data for the study.
    6. + *
    7. Let JabRef do the crawling afterwards.
    8. *
    * - * Needs to inherit {@link ExistingStudySearchAction}, because that action implements the real crawling. + * Needs to inherit {@link ExistingStudySearchAction}, because that action implements the + * real crawling. * - * There is the hook {@link StartNewStudyAction#crawlPreparation(Path)}, which is used by {@link ExistingStudySearchAction#crawl()}. + * There is the hook {@link StartNewStudyAction#crawlPreparation(Path)}, which is used by + * {@link ExistingStudySearchAction#crawl()}. */ public class StartNewStudyAction extends ExistingStudySearchAction { + private static final Logger LOGGER = LoggerFactory.getLogger(StartNewStudyAction.class); Study newStudy; public StartNewStudyAction(LibraryTabContainer tabContainer, - Supplier openDatabaseActionSupplier, - FileUpdateMonitor fileUpdateMonitor, - TaskExecutor taskExecutor, - CliPreferences preferences, - StateManager stateManager, - DialogService dialogService) { - super(tabContainer, - openDatabaseActionSupplier, - dialogService, - fileUpdateMonitor, - taskExecutor, - preferences, - stateManager, - true); + Supplier openDatabaseActionSupplier, FileUpdateMonitor fileUpdateMonitor, + TaskExecutor taskExecutor, CliPreferences preferences, StateManager stateManager, + DialogService dialogService) { + super(tabContainer, openDatabaseActionSupplier, dialogService, fileUpdateMonitor, taskExecutor, preferences, + stateManager, true); } @Override protected void crawlPreparation(Path studyRepositoryRoot) throws IOException, GitAPIException { StudyYamlParser studyYAMLParser = new StudyYamlParser(); - studyYAMLParser.writeStudyYamlFile(newStudy, studyRepositoryRoot.resolve(StudyRepository.STUDY_DEFINITION_FILE_NAME)); + studyYAMLParser.writeStudyYamlFile(newStudy, + studyRepositoryRoot.resolve(StudyRepository.STUDY_DEFINITION_FILE_NAME)); // When execution reaches this point, the user created a new study. - // The GitHandler is already called to initialize the repository with one single commit "Initial commit". + // The GitHandler is already called to initialize the repository with one single + // commit "Initial commit". // The "Initial commit" should also contain the created YAML. // Thus, we append to that commit. new GitHandler(studyRepositoryRoot).createCommitOnCurrentBranch("Initial commit", true); @@ -90,4 +86,5 @@ public void execute() { crawl(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/slr/StudyCatalogItem.java b/jabgui/src/main/java/org/jabref/gui/slr/StudyCatalogItem.java index b379d373bf9..bed7002e5fd 100644 --- a/jabgui/src/main/java/org/jabref/gui/slr/StudyCatalogItem.java +++ b/jabgui/src/main/java/org/jabref/gui/slr/StudyCatalogItem.java @@ -13,7 +13,9 @@ * View representation of {@link StudyDatabase} */ public class StudyCatalogItem { + private final StringProperty name; + private final BooleanProperty enabled; public StudyCatalogItem(String name, boolean enabled) { @@ -47,10 +49,7 @@ public BooleanProperty enabledProperty() { @Override public String toString() { - return "StudyCatalogItem{" + - "name=" + name.get() + - ", enabled=" + enabled.get() + - '}'; + return "StudyCatalogItem{" + "name=" + name.get() + ", enabled=" + enabled.get() + '}'; } @Override @@ -69,4 +68,5 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(getName(), isEnabled()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java b/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java index a38baf4c6be..6b14ee37de1 100644 --- a/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java +++ b/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java @@ -28,28 +28,32 @@ public class SpecialFieldAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(SpecialFieldAction.class); + private final Supplier tabSupplier; + private final SpecialField specialField; + private final String value; + private final boolean nullFieldIfValueIsTheSame; + private final String undoText; + private final DialogService dialogService; + private final CliPreferences preferences; + private final UndoManager undoManager; + private final StateManager stateManager; /** - * @param nullFieldIfValueIsTheSame - false also causes that doneTextPattern has two place holders %0 for the value and %1 for the sum of entries + * @param nullFieldIfValueIsTheSame - false also causes that doneTextPattern has two + * place holders %0 for the value and %1 for the sum of entries */ - public SpecialFieldAction(Supplier tabSupplier, - SpecialField specialField, - String value, - boolean nullFieldIfValueIsTheSame, - String undoText, - DialogService dialogService, - CliPreferences preferences, - UndoManager undoManager, - StateManager stateManager) { + public SpecialFieldAction(Supplier tabSupplier, SpecialField specialField, String value, + boolean nullFieldIfValueIsTheSame, String undoText, DialogService dialogService, CliPreferences preferences, + UndoManager undoManager, StateManager stateManager) { this.tabSupplier = tabSupplier; this.specialField = specialField; this.value = value; @@ -73,8 +77,10 @@ public void execute() { NamedCompound ce = new NamedCompound(undoText); List besCopy = new ArrayList<>(bes); for (BibEntry bibEntry : besCopy) { - // if (value==null) and then call nullField has been omitted as updatefield also handles value==null - Optional change = UpdateField.updateField(bibEntry, specialField, value, nullFieldIfValueIsTheSame); + // if (value==null) and then call nullField has been omitted as + // updatefield also handles value==null + Optional change = UpdateField.updateField(bibEntry, specialField, value, + nullFieldIfValueIsTheSame); change.ifPresent(fieldChange -> ce.addEdit(new UndoableFieldChange(fieldChange))); } @@ -85,14 +91,17 @@ public void execute() { String outText; if (nullFieldIfValueIsTheSame || value == null) { outText = getTextDone(specialField, Integer.toString(bes.size())); - } else { + } + else { outText = getTextDone(specialField, value, Integer.toString(bes.size())); } dialogService.notify(outText); } - // if user does not change anything with his action, we do not do anything either, even no output message - } catch (Throwable ex) { + // if user does not change anything with his action, we do not do anything + // either, even no output message + } + catch (Throwable ex) { LOGGER.error("Problem setting special fields", ex); } } @@ -105,16 +114,21 @@ private String getTextDone(SpecialField field, String... params) { if (field.isSingleValueField() && (params.length == 1) && (params[0] != null)) { // Single value fields can be toggled only return Localization.lang("Toggled '%0' for %1 entries", viewModel.getLocalization(), params[0]); - } else if (!field.isSingleValueField() && (params.length == 2) && (params[0] != null) && (params[1] != null)) { + } + else if (!field.isSingleValueField() && (params.length == 2) && (params[0] != null) && (params[1] != null)) { // setting a multi value special field - the set value is displayed, too - return Localization.lang("Set '%0' to '%1' for %2 entries", viewModel.getLocalization(), params[0], params[1]); - } else if (!field.isSingleValueField() && (params.length == 1) && (params[0] != null)) { + return Localization.lang("Set '%0' to '%1' for %2 entries", viewModel.getLocalization(), params[0], + params[1]); + } + else if (!field.isSingleValueField() && (params.length == 1) && (params[0] != null)) { // clearing a multi value specialfield return Localization.lang("Cleared '%0' for %1 entries", viewModel.getLocalization(), params[0]); - } else { + } + else { // invalid usage LOGGER.info("Creation of special field status change message failed: illegal argument combination."); return ""; } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldMenuItemFactory.java b/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldMenuItemFactory.java index 63ac0bbf84f..54ae13b46e8 100644 --- a/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldMenuItemFactory.java +++ b/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldMenuItemFactory.java @@ -19,48 +19,40 @@ import de.saxsys.mvvmfx.utils.commands.Command; public class SpecialFieldMenuItemFactory { - public static MenuItem getSpecialFieldSingleItem(SpecialField field, - ActionFactory factory, - Supplier tabSupplier, - DialogService dialogService, - GuiPreferences preferences, - UndoManager undoManager, - StateManager stateManager) { + + public static MenuItem getSpecialFieldSingleItem(SpecialField field, ActionFactory factory, + Supplier tabSupplier, DialogService dialogService, GuiPreferences preferences, + UndoManager undoManager, StateManager stateManager) { SpecialFieldValueViewModel specialField = new SpecialFieldValueViewModel(field.getValues().getFirst()); MenuItem menuItem = factory.createMenuItem(specialField.getAction(), new SpecialFieldViewModel(field, preferences, undoManager) - .getSpecialFieldAction(field.getValues().getFirst(), tabSupplier, dialogService, stateManager)); + .getSpecialFieldAction(field.getValues().getFirst(), tabSupplier, dialogService, stateManager)); menuItem.visibleProperty().bind(preferences.getSpecialFieldsPreferences().specialFieldsEnabledProperty()); return menuItem; } - public static Menu createSpecialFieldMenu(SpecialField field, - ActionFactory factory, - Supplier tabSupplier, - DialogService dialogService, - GuiPreferences preferences, - UndoManager undoManager, - StateManager stateManager) { + public static Menu createSpecialFieldMenu(SpecialField field, ActionFactory factory, + Supplier tabSupplier, DialogService dialogService, GuiPreferences preferences, + UndoManager undoManager, StateManager stateManager) { - return createSpecialFieldMenu(field, factory, preferences, undoManager, specialField -> - new SpecialFieldViewModel(field, preferences, undoManager) - .getSpecialFieldAction(specialField.getValue(), tabSupplier, dialogService, stateManager)); + return createSpecialFieldMenu(field, factory, preferences, undoManager, + specialField -> new SpecialFieldViewModel(field, preferences, undoManager) + .getSpecialFieldAction(specialField.getValue(), tabSupplier, dialogService, stateManager)); } - public static Menu createSpecialFieldMenu(SpecialField field, - ActionFactory factory, - GuiPreferences preferences, - UndoManager undoManager, - Function commandFactory) { + public static Menu createSpecialFieldMenu(SpecialField field, ActionFactory factory, GuiPreferences preferences, + UndoManager undoManager, Function commandFactory) { SpecialFieldViewModel viewModel = new SpecialFieldViewModel(field, preferences, undoManager); Menu menu = factory.createMenu(viewModel.getAction()); for (SpecialFieldValue Value : field.getValues()) { SpecialFieldValueViewModel valueViewModel = new SpecialFieldValueViewModel(Value); - menu.getItems().add(factory.createMenuItem(valueViewModel.getAction(), commandFactory.apply(valueViewModel))); + menu.getItems() + .add(factory.createMenuItem(valueViewModel.getAction(), commandFactory.apply(valueViewModel))); } menu.visibleProperty().bind(preferences.getSpecialFieldsPreferences().specialFieldsEnabledProperty()); return menu; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldValueViewModel.java b/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldValueViewModel.java index e2d3ffda9a6..a334be31fb6 100644 --- a/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldValueViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldValueViewModel.java @@ -71,4 +71,5 @@ public Action getAction() { case RELEVANT -> StandardActions.RELEVANT; }; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java b/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java index e2f4387bb39..d2bf183fade 100644 --- a/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java @@ -25,7 +25,9 @@ public class SpecialFieldViewModel { private final SpecialField field; + private final CliPreferences preferences; + private final UndoManager undoManager; public SpecialFieldViewModel(SpecialField field, CliPreferences preferences, UndoManager undoManager) { @@ -38,20 +40,12 @@ public SpecialField getField() { return field; } - public SpecialFieldAction getSpecialFieldAction(SpecialFieldValue value, - Supplier tabSupplier, - DialogService dialogService, - StateManager stateManager) { - return new SpecialFieldAction( - tabSupplier, - field, - value.getFieldValue().orElse(null), - // if field contains only one value, it has to be nulled, as another setting does not empty the field - field.getValues().size() == 1, - getLocalization(), - dialogService, - preferences, - undoManager, + public SpecialFieldAction getSpecialFieldAction(SpecialFieldValue value, Supplier tabSupplier, + DialogService dialogService, StateManager stateManager) { + return new SpecialFieldAction(tabSupplier, field, value.getFieldValue().orElse(null), + // if field contains only one value, it has to be nulled, as another + // setting does not empty the field + field.getValues().size() == 1, getLocalization(), dialogService, preferences, undoManager, stateManager); } @@ -79,13 +73,12 @@ public JabRefIcon getEmptyIcon() { } public List getValues() { - return field.getValues().stream() - .map(SpecialFieldValueViewModel::new) - .collect(Collectors.toList()); + return field.getValues().stream().map(SpecialFieldValueViewModel::new).collect(Collectors.toList()); } public void setSpecialFieldValue(BibEntry bibEntry, SpecialFieldValue value) { - Optional change = UpdateField.updateField(bibEntry, getField(), value.getFieldValue().orElse(null), getField().isSingleValueField()); + Optional change = UpdateField.updateField(bibEntry, getField(), value.getFieldValue().orElse(null), + getField().isSingleValueField()); change.ifPresent(fieldChange -> undoManager.addEdit(new UndoableFieldChange(fieldChange))); } @@ -93,4 +86,5 @@ public void setSpecialFieldValue(BibEntry bibEntry, SpecialFieldValue value) { public void toggle(BibEntry entry) { setSpecialFieldValue(entry, getField().getValues().getFirst()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldsPreferences.java b/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldsPreferences.java index ee015786a0d..9ab11dd2a39 100644 --- a/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldsPreferences.java +++ b/jabgui/src/main/java/org/jabref/gui/specialfields/SpecialFieldsPreferences.java @@ -24,4 +24,5 @@ public BooleanProperty specialFieldsEnabledProperty() { public void setSpecialFieldsEnabled(boolean specialFieldsEnabled) { this.specialFieldsEnabled.set(specialFieldsEnabled); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/texparser/CitationsDisplay.java b/jabgui/src/main/java/org/jabref/gui/texparser/CitationsDisplay.java index 8c312f5b042..06a79e47945 100644 --- a/jabgui/src/main/java/org/jabref/gui/texparser/CitationsDisplay.java +++ b/jabgui/src/main/java/org/jabref/gui/texparser/CitationsDisplay.java @@ -28,8 +28,8 @@ public class CitationsDisplay extends ListView { public CitationsDisplay() { this.basePath = new SimpleObjectProperty<>(null); new ViewModelListCellFactory().withGraphic(this::getDisplayGraphic) - .withTooltip(this::getDisplayTooltip) - .install(this); + .withTooltip(this::getDisplayTooltip) + .install(this); this.getStyleClass().add("citationsList"); } @@ -90,4 +90,5 @@ private Tooltip getDisplayTooltip(Citation item) { return tooltip; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexAction.java b/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexAction.java index 637feed20fb..773e5b7bc52 100644 --- a/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexAction.java +++ b/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexAction.java @@ -23,4 +23,5 @@ public void execute() { BibDatabaseContext database = stateManager.getActiveDatabase().orElseThrow(NullPointerException::new); dialogService.showCustomDialogAndWait(new ParseLatexDialogView(database)); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexDialogView.java b/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexDialogView.java index bf1a88fe785..b52172a2395 100644 --- a/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexDialogView.java +++ b/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexDialogView.java @@ -32,20 +32,48 @@ public class ParseLatexDialogView extends BaseDialog { private final BibDatabaseContext databaseContext; + private final ControlsFxVisualizer validationVisualizer; - @FXML private TextField latexDirectoryField; - @FXML private Button browseButton; - @FXML private Button searchButton; - @FXML private ProgressIndicator progressIndicator; - @FXML private CheckTreeView fileTreeView; - @FXML private Button selectAllButton; - @FXML private Button unselectAllButton; - @FXML private ButtonType parseButtonType; - @Inject private DialogService dialogService; - @Inject private TaskExecutor taskExecutor; - @Inject private CliPreferences preferences; - @Inject private FileUpdateMonitor fileMonitor; - @Inject private ThemeManager themeManager; + + @FXML + private TextField latexDirectoryField; + + @FXML + private Button browseButton; + + @FXML + private Button searchButton; + + @FXML + private ProgressIndicator progressIndicator; + + @FXML + private CheckTreeView fileTreeView; + + @FXML + private Button selectAllButton; + + @FXML + private Button unselectAllButton; + + @FXML + private ButtonType parseButtonType; + + @Inject + private DialogService dialogService; + + @Inject + private TaskExecutor taskExecutor; + + @Inject + private CliPreferences preferences; + + @Inject + private FileUpdateMonitor fileMonitor; + + @Inject + private ThemeManager themeManager; + private ParseLatexDialogViewModel viewModel; public ParseLatexDialogView(BibDatabaseContext databaseContext) { @@ -58,24 +86,25 @@ public ParseLatexDialogView(BibDatabaseContext databaseContext) { ControlHelper.setAction(parseButtonType, getDialogPane(), event -> viewModel.parseButtonClicked()); Button parseButton = (Button) getDialogPane().lookupButton(parseButtonType); - parseButton.disableProperty().bind(viewModel.noFilesFoundProperty().or( - Bindings.isEmpty(viewModel.getCheckedFileList()))); + parseButton.disableProperty() + .bind(viewModel.noFilesFoundProperty().or(Bindings.isEmpty(viewModel.getCheckedFileList()))); themeManager.updateFontStyle(getDialogPane().getScene()); } @FXML private void initialize() { - viewModel = new ParseLatexDialogViewModel(databaseContext, dialogService, taskExecutor, preferences, fileMonitor); + viewModel = new ParseLatexDialogViewModel(databaseContext, dialogService, taskExecutor, preferences, + fileMonitor); fileTreeView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); fileTreeView.showRootProperty().bindBidirectional(viewModel.successfulSearchProperty()); - fileTreeView.rootProperty().bind(EasyBind.map(viewModel.rootProperty(), fileNode -> - new RecursiveTreeItem<>(fileNode, FileNodeViewModel::getChildren))); + fileTreeView.rootProperty() + .bind(EasyBind.map(viewModel.rootProperty(), + fileNode -> new RecursiveTreeItem<>(fileNode, FileNodeViewModel::getChildren))); - new ViewModelTreeCellFactory() - .withText(FileNodeViewModel::getDisplayText) - .install(fileTreeView); + new ViewModelTreeCellFactory().withText(FileNodeViewModel::getDisplayText) + .install(fileTreeView); EasyBind.subscribe(fileTreeView.rootProperty(), root -> { ((CheckBoxTreeItem) root).setSelected(true); @@ -112,4 +141,5 @@ private void selectAll() { private void unselectAll() { fileTreeView.getCheckModel().clearChecks(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexDialogViewModel.java b/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexDialogViewModel.java index 92909584765..b45dc591606 100644 --- a/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexDialogViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexDialogViewModel.java @@ -45,33 +45,46 @@ public class ParseLatexDialogViewModel extends AbstractViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(ParseLatexDialogViewModel.class); + private static final String TEX_EXT = ".tex"; + private final BibDatabaseContext databaseContext; + private final DialogService dialogService; + private final TaskExecutor taskExecutor; + private final CliPreferences preferences; + private final FileUpdateMonitor fileMonitor; + private final StringProperty latexFileDirectory; + private final Validator latexDirectoryValidator; + private final ObjectProperty root; + private final ObservableList> checkedFileList; + private final BooleanProperty noFilesFound; + private final BooleanProperty searchInProgress; + private final BooleanProperty successfulSearch; - public ParseLatexDialogViewModel(BibDatabaseContext databaseContext, - DialogService dialogService, - TaskExecutor taskExecutor, - CliPreferences preferences, - FileUpdateMonitor fileMonitor) { + public ParseLatexDialogViewModel(BibDatabaseContext databaseContext, DialogService dialogService, + TaskExecutor taskExecutor, CliPreferences preferences, FileUpdateMonitor fileMonitor) { this.databaseContext = databaseContext; this.dialogService = dialogService; this.taskExecutor = taskExecutor; this.preferences = preferences; this.fileMonitor = fileMonitor; - this.latexFileDirectory = new SimpleStringProperty(databaseContext.getMetaData().getLatexFileDirectory(preferences.getFilePreferences().getUserAndHost()) - .orElse(FileUtil.getInitialDirectory(databaseContext, preferences.getFilePreferences().getWorkingDirectory())) - .toAbsolutePath().toString()); + this.latexFileDirectory = new SimpleStringProperty(databaseContext.getMetaData() + .getLatexFileDirectory(preferences.getFilePreferences().getUserAndHost()) + .orElse(FileUtil.getInitialDirectory(databaseContext, + preferences.getFilePreferences().getWorkingDirectory())) + .toAbsolutePath() + .toString()); this.root = new SimpleObjectProperty<>(); this.checkedFileList = FXCollections.observableArrayList(); this.noFilesFound = new SimpleBooleanProperty(true); @@ -113,7 +126,8 @@ public BooleanProperty successfulSearchProperty() { public void browseButtonClicked() { DirectoryDialogConfiguration directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(Path.of(latexFileDirectory.get())).build(); + .withInitialDirectory(Path.of(latexFileDirectory.get())) + .build(); dialogService.showDirectorySelectionDialog(directoryDialogConfiguration).ifPresent(selectedDirectory -> { latexFileDirectory.set(selectedDirectory.toAbsolutePath().toString()); @@ -125,28 +139,27 @@ public void browseButtonClicked() { * Run a recursive search in a background task. */ public void searchButtonClicked() { - BackgroundTask.wrap(() -> searchDirectory(Path.of(latexFileDirectory.get()))) - .onRunning(() -> { - root.set(null); - noFilesFound.set(true); - searchInProgress.set(true); - successfulSearch.set(false); - }) - .onFinished(() -> searchInProgress.set(false)) - .onSuccess(newRoot -> { - root.set(newRoot); - noFilesFound.set(false); - successfulSearch.set(true); - }) - .onFailure(this::handleFailure) - .executeWith(taskExecutor); + BackgroundTask.wrap(() -> searchDirectory(Path.of(latexFileDirectory.get()))).onRunning(() -> { + root.set(null); + noFilesFound.set(true); + searchInProgress.set(true); + successfulSearch.set(false); + }).onFinished(() -> searchInProgress.set(false)).onSuccess(newRoot -> { + root.set(newRoot); + noFilesFound.set(false); + successfulSearch.set(true); + }).onFailure(this::handleFailure).executeWith(taskExecutor); } private void handleFailure(Exception exception) { - final boolean permissionProblem = (exception instanceof IOException) && (exception.getCause() instanceof FileSystemException) && exception.getCause().getMessage().endsWith("Operation not permitted"); + final boolean permissionProblem = (exception instanceof IOException) + && (exception.getCause() instanceof FileSystemException) + && exception.getCause().getMessage().endsWith("Operation not permitted"); if (permissionProblem) { - dialogService.showErrorDialogAndWait(Localization.lang("JabRef does not have permission to access %s").formatted(exception.getCause().getMessage())); - } else { + dialogService.showErrorDialogAndWait(Localization.lang("JabRef does not have permission to access %s") + .formatted(exception.getCause().getMessage())); + } + else { dialogService.showErrorDialogAndWait(exception); } } @@ -161,16 +174,17 @@ private FileNodeViewModel searchDirectory(Path directory) throws IOException { try (Stream filesStream = Files.list(directory)) { fileListPartition = filesStream.collect(Collectors.partitioningBy(path -> path.toFile().isDirectory())); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.error("Error searching files", e); return parent; } List subDirectories = fileListPartition.get(true); List files = fileListPartition.get(false) - .stream() - .filter(path -> path.toString().endsWith(TEX_EXT)) - .toList(); + .stream() + .filter(path -> path.toString().endsWith(TEX_EXT)) + .toList(); int fileCount = 0; for (Path subDirectory : subDirectories) { @@ -183,9 +197,7 @@ private FileNodeViewModel searchDirectory(Path directory) throws IOException { } parent.setFileCount(files.size() + fileCount); - parent.getChildren().addAll(files.stream() - .map(FileNodeViewModel::new) - .toList()); + parent.getChildren().addAll(files.stream().map(FileNodeViewModel::new).toList()); return parent; } @@ -194,25 +206,24 @@ private FileNodeViewModel searchDirectory(Path directory) throws IOException { */ public void parseButtonClicked() { List fileList = checkedFileList.stream() - .map(item -> item.getValue().getPath()) - .filter(path -> path.toFile().isFile()) - .toList(); + .map(item -> item.getValue().getPath()) + .filter(path -> path.toFile().isFile()) + .toList(); if (fileList.isEmpty()) { LOGGER.warn("There are no valid files checked"); return; } - TexBibEntriesResolver entriesResolver = new TexBibEntriesResolver( - databaseContext.getDatabase(), - preferences.getImportFormatPreferences(), - fileMonitor); + TexBibEntriesResolver entriesResolver = new TexBibEntriesResolver(databaseContext.getDatabase(), + preferences.getImportFormatPreferences(), fileMonitor); BackgroundTask.wrap(() -> entriesResolver.resolve(new DefaultLatexParser().parse(fileList))) - .onRunning(() -> searchInProgress.set(true)) - .onFinished(() -> searchInProgress.set(false)) - .onSuccess(result -> dialogService.showCustomDialogAndWait( - new ParseLatexResultView(result, databaseContext, Path.of(latexFileDirectory.get())))) - .onFailure(dialogService::showErrorDialogAndWait) - .executeWith(taskExecutor); + .onRunning(() -> searchInProgress.set(true)) + .onFinished(() -> searchInProgress.set(false)) + .onSuccess(result -> dialogService.showCustomDialogAndWait( + new ParseLatexResultView(result, databaseContext, Path.of(latexFileDirectory.get())))) + .onFailure(dialogService::showErrorDialogAndWait) + .executeWith(taskExecutor); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexResultView.java b/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexResultView.java index 7fc00e8b7eb..072ef0b4171 100644 --- a/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexResultView.java +++ b/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexResultView.java @@ -23,15 +23,27 @@ public class ParseLatexResultView extends BaseDialog { private final LatexBibEntriesResolverResult resolverResult; + private final BibDatabaseContext databaseContext; + private final Path basePath; - @FXML private ListView referenceListView; - @FXML private CitationsDisplay citationsDisplay; - @FXML private ButtonType importButtonType; - @Inject private ThemeManager themeManager; + + @FXML + private ListView referenceListView; + + @FXML + private CitationsDisplay citationsDisplay; + + @FXML + private ButtonType importButtonType; + + @Inject + private ThemeManager themeManager; + private ParseLatexResultViewModel viewModel; - public ParseLatexResultView(LatexBibEntriesResolverResult resolverResult, BibDatabaseContext databaseContext, Path basePath) { + public ParseLatexResultView(LatexBibEntriesResolverResult resolverResult, BibDatabaseContext databaseContext, + Path basePath) { this.resolverResult = resolverResult; this.databaseContext = databaseContext; this.basePath = basePath; @@ -56,15 +68,13 @@ private void initialize() { referenceListView.setItems(viewModel.getReferenceList()); referenceListView.getSelectionModel().selectFirst(); - new ViewModelListCellFactory() - .withGraphic(reference -> { - Text referenceText = new Text(reference.getDisplayText()); - if (reference.isHighlighted()) { - referenceText.setStyle("-fx-fill: -fx-accent"); - } - return referenceText; - }) - .install(referenceListView); + new ViewModelListCellFactory().withGraphic(reference -> { + Text referenceText = new Text(reference.getDisplayText()); + if (reference.isHighlighted()) { + referenceText.setStyle("-fx-fill: -fx-accent"); + } + return referenceText; + }).install(referenceListView); EasyBind.subscribe(referenceListView.getSelectionModel().selectedItemProperty(), viewModel::activeReferenceChanged); @@ -72,4 +82,5 @@ private void initialize() { citationsDisplay.basePathProperty().set(basePath); citationsDisplay.setItems(viewModel.getCitationListByReference()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexResultViewModel.java b/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexResultViewModel.java index ba72bc933b3..28a28e0b570 100644 --- a/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexResultViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/texparser/ParseLatexResultViewModel.java @@ -26,9 +26,13 @@ public class ParseLatexResultViewModel extends AbstractViewModel { private final LatexBibEntriesResolverResult resolverResult; + private final BibDatabaseContext databaseContext; + private final ObservableList referenceList; + private final ObservableList citationList; + private final BooleanProperty importButtonDisabled; public ParseLatexResultViewModel(LatexBibEntriesResolverResult resolverResult, BibDatabaseContext databaseContext) { @@ -37,13 +41,17 @@ public ParseLatexResultViewModel(LatexBibEntriesResolverResult resolverResult, B this.referenceList = FXCollections.observableArrayList(); this.citationList = FXCollections.observableArrayList(); - Set newEntryKeys = resolverResult.getNewEntries().stream().map(entry -> entry.getCitationKey().orElse("")).collect(Collectors.toSet()); + Set newEntryKeys = resolverResult.getNewEntries() + .stream() + .map(entry -> entry.getCitationKey().orElse("")) + .collect(Collectors.toSet()); for (Map.Entry> entry : resolverResult.getCitations().asMap().entrySet()) { String key = entry.getKey(); referenceList.add(new ReferenceViewModel(key, newEntryKeys.contains(key), entry.getValue())); } - this.importButtonDisabled = new SimpleBooleanProperty(referenceList.stream().noneMatch(ReferenceViewModel::isHighlighted)); + this.importButtonDisabled = new SimpleBooleanProperty( + referenceList.stream().noneMatch(ReferenceViewModel::isHighlighted)); } public ObservableList getReferenceList() { @@ -64,7 +72,8 @@ public BooleanProperty importButtonDisabledProperty() { public void activeReferenceChanged(ReferenceViewModel reference) { if (reference == null) { citationList.clear(); - } else { + } + else { citationList.setAll(reference.getCitationList()); } } @@ -74,8 +83,10 @@ public void activeReferenceChanged(ReferenceViewModel reference) { */ public void importButtonClicked() { DialogService dialogService = Injector.instantiateModelOrService(DialogService.class); - ImportEntriesDialog dialog = new ImportEntriesDialog(databaseContext, BackgroundTask.wrap(() -> new ParserResult(resolverResult.getNewEntries()))); + ImportEntriesDialog dialog = new ImportEntriesDialog(databaseContext, + BackgroundTask.wrap(() -> new ParserResult(resolverResult.getNewEntries()))); dialog.setTitle(Localization.lang("Import entries from LaTeX files")); dialogService.showCustomDialogAndWait(dialog); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/texparser/ReferenceViewModel.java b/jabgui/src/main/java/org/jabref/gui/texparser/ReferenceViewModel.java index 665f13dd55e..dff14161156 100644 --- a/jabgui/src/main/java/org/jabref/gui/texparser/ReferenceViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/texparser/ReferenceViewModel.java @@ -11,7 +11,9 @@ public class ReferenceViewModel { private final String entry; + private final boolean highlighted; + private final ObservableList citationList; public ReferenceViewModel(String entry, boolean highlighted, Collection citationColl) { @@ -39,9 +41,8 @@ public String getDisplayText() { @Override public String toString() { - return "ReferenceViewModel{entry='%s', highlighted=%s, citationList=%s}".formatted( - this.entry, - this.highlighted, + return "ReferenceViewModel{entry='%s', highlighted=%s, citationList=%s}".formatted(this.entry, this.highlighted, this.citationList); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/theme/StyleSheet.java b/jabgui/src/main/java/org/jabref/gui/theme/StyleSheet.java index d922ce43a44..39dc8645db5 100644 --- a/jabgui/src/main/java/org/jabref/gui/theme/StyleSheet.java +++ b/jabgui/src/main/java/org/jabref/gui/theme/StyleSheet.java @@ -40,34 +40,43 @@ static Optional create(String name) { if (styleSheetUrl.isEmpty()) { try { styleSheetUrl = Optional.of(Path.of(name).toUri().toURL()); - } catch (InvalidPathException e) { - LOGGER.warn("Cannot load additional css {} because it is an invalid path: {}", name, e.getLocalizedMessage(), e); - } catch (MalformedURLException e) { - LOGGER.warn("Cannot load additional css url {} because it is a malformed url: {}", name, e.getLocalizedMessage(), e); + } + catch (InvalidPathException e) { + LOGGER.warn("Cannot load additional css {} because it is an invalid path: {}", name, + e.getLocalizedMessage(), e); + } + catch (MalformedURLException e) { + LOGGER.warn("Cannot load additional css url {} because it is a malformed url: {}", name, + e.getLocalizedMessage(), e); } } if (styleSheetUrl.isEmpty()) { try { return Optional.of(new StyleSheetDataUrl(new URI(EMPTY_WEBENGINE_CSS).toURL())); - } catch (URISyntaxException | MalformedURLException e) { + } + catch (URISyntaxException | MalformedURLException e) { return Optional.empty(); } - } else if ("file".equals(styleSheetUrl.get().getProtocol())) { + } + else if ("file".equals(styleSheetUrl.get().getProtocol())) { StyleSheet styleSheet = new StyleSheetFile(styleSheetUrl.get()); if (Files.isDirectory(styleSheet.getWatchPath())) { - LOGGER.warn("Failed to loadCannot load additional css {} because it is a directory.", styleSheet.getWatchPath()); + LOGGER.warn("Failed to loadCannot load additional css {} because it is a directory.", + styleSheet.getWatchPath()); return Optional.empty(); } if (!Files.exists(styleSheet.getWatchPath())) { - LOGGER.warn("Cannot load additional css {} because the file does not exist.", styleSheet.getWatchPath()); + LOGGER.warn("Cannot load additional css {} because the file does not exist.", + styleSheet.getWatchPath()); // Should not return empty, since the user can create the file later. } return Optional.of(new StyleSheetFile(styleSheetUrl.get())); - } else { + } + else { return Optional.of(new StyleSheetResource(styleSheetUrl.get())); } } @@ -76,4 +85,5 @@ static Optional create(String name) { public String toString() { return "StyleSheet{" + getSceneStylesheet() + "}"; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/theme/StyleSheetDataUrl.java b/jabgui/src/main/java/org/jabref/gui/theme/StyleSheetDataUrl.java index 04045faa41b..7799ddf680e 100644 --- a/jabgui/src/main/java/org/jabref/gui/theme/StyleSheetDataUrl.java +++ b/jabgui/src/main/java/org/jabref/gui/theme/StyleSheetDataUrl.java @@ -25,11 +25,13 @@ public String getWebEngineStylesheet() { @Override void reload() { - StyleSheetFile.getDataUrl(url).ifPresentOrElse(createdUrl -> dataUrl = createdUrl, () -> dataUrl = EMPTY_WEBENGINE_CSS); + StyleSheetFile.getDataUrl(url) + .ifPresentOrElse(createdUrl -> dataUrl = createdUrl, () -> dataUrl = EMPTY_WEBENGINE_CSS); } @Override public String toString() { return "StyleSheet{" + getSceneStylesheet() + "}"; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/theme/StyleSheetFile.java b/jabgui/src/main/java/org/jabref/gui/theme/StyleSheetFile.java index 515f37854ed..9e3d0302018 100644 --- a/jabgui/src/main/java/org/jabref/gui/theme/StyleSheetFile.java +++ b/jabgui/src/main/java/org/jabref/gui/theme/StyleSheetFile.java @@ -19,17 +19,21 @@ final class StyleSheetFile extends StyleSheet { /** - * A size limit above which Theme will not attempt to keep a data-embedded URL in memory for the CSS. + * A size limit above which Theme will not attempt to keep a data-embedded URL in + * memory for the CSS. * - * It's tolerable for CSS to exceed this limit; the functional benefit of the encoded CSS is in some edge - * case error handling. Specifically, having a reference to a data-embedded URL means that the Preview Viewer - * isn't impacted if the source CSS file is removed while the application is running. + * It's tolerable for CSS to exceed this limit; the functional benefit of the encoded + * CSS is in some edge case error handling. Specifically, having a reference to a + * data-embedded URL means that the Preview Viewer isn't impacted if the source CSS + * file is removed while the application is running. * - * If the CSS is over this limit, then the user won't see any functional impact, as long as the file exists. Only if - * it becomes unavailable, might there be some impact. First, the Preview Viewer when created might not be themed. - * Second, there is a very small chance of uncaught exceptions. Theme makes a best effort to avoid this: - * it checks for CSS file existence before passing it to the Preview Viewer for theming. Still, as file existence - * checks are immediately out of date, it can't be perfectly ruled out. + * If the CSS is over this limit, then the user won't see any functional impact, as + * long as the file exists. Only if it becomes unavailable, might there be some + * impact. First, the Preview Viewer when created might not be themed. Second, there + * is a very small chance of uncaught exceptions. Theme makes a best effort to avoid + * this: it checks for CSS file existence before passing it to the Preview Viewer for + * theming. Still, as file existence checks are immediately out of date, it can't be + * perfectly ruled out. * * At the time of writing this comment: * @@ -40,15 +44,18 @@ final class StyleSheetFile extends StyleSheet { * Custom themes * * - * So realistic custom themes will fit comfortably within 48k, even if they are modified copies of the base theme. + * So realistic custom themes will fit comfortably within 48k, even if they are + * modified copies of the base theme. * - * Note that Base-64 encoding will increase the memory footprint of the URL by a third. + * Note that Base-64 encoding will increase the memory footprint of the URL by a + * third. */ static final int MAX_IN_MEMORY_CSS_LENGTH = 48000; private static final Logger LOGGER = LoggerFactory.getLogger(StyleSheetFile.class); private final URL url; + private final Path path; private final AtomicReference dataUrl = new AtomicReference<>(); @@ -86,10 +93,10 @@ public URL getSceneStylesheet() { /** * This method allows callers to obtain the theme's additional stylesheet. - * - * @return the stylesheet location if there is an additional stylesheet present and available. The - * location will be a local URL. Typically, it will be a {@code 'data:'} URL where the CSS is embedded. However, for - * large themes it can be {@code 'file:'}. + * @return the stylesheet location if there is an additional stylesheet present and + * available. The location will be a local URL. Typically, it will be a + * {@code 'data:'} URL where the CSS is embedded. However, for large themes it can be + * {@code 'file:'}. */ @Override public String getWebEngineStylesheet() { @@ -116,11 +123,13 @@ static Optional getDataUrl(URL url) { String embeddedDataUrl = DATA_URL_PREFIX + Base64.getEncoder().encodeToString(data); LOGGER.trace("Embedded css in data URL of length {}", embeddedDataUrl.length()); return Optional.of(embeddedDataUrl); - } else { + } + else { LOGGER.trace("Not embedding css in data URL as the length is >= {}", MAX_IN_MEMORY_CSS_LENGTH); } } - } catch (IOException e) { + } + catch (IOException e) { LOGGER.warn("Could not load css url {}", url, e); } @@ -131,4 +140,5 @@ static Optional getDataUrl(URL url) { public String toString() { return "StyleSheet{" + getSceneStylesheet() + "}"; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/theme/StyleSheetResource.java b/jabgui/src/main/java/org/jabref/gui/theme/StyleSheetResource.java index 7aadcdaa86c..1065addefa9 100644 --- a/jabgui/src/main/java/org/jabref/gui/theme/StyleSheetResource.java +++ b/jabgui/src/main/java/org/jabref/gui/theme/StyleSheetResource.java @@ -29,4 +29,5 @@ void reload() { public String toString() { return "StyleSheet{" + getSceneStylesheet() + "}"; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/theme/Theme.java b/jabgui/src/main/java/org/jabref/gui/theme/Theme.java index 03e6e414aed..9f93da47c55 100644 --- a/jabgui/src/main/java/org/jabref/gui/theme/Theme.java +++ b/jabgui/src/main/java/org/jabref/gui/theme/Theme.java @@ -6,20 +6,26 @@ /** * Represents one of three types of a css based Theme for JabRef: *

    - * The Default type of theme is the light theme (which is in fact the absence of any theme), the dark theme is currently - * the only embedded theme and the custom themes, that can be created by loading a proper css file. + * The Default type of theme is the light theme (which is in fact the absence of any + * theme), the dark theme is currently the only embedded theme and the custom themes, that + * can be created by loading a proper css file. */ public class Theme { public enum Type { + DEFAULT, EMBEDDED, CUSTOM + } public static final String BASE_CSS = "Base.css"; + public static final String EMBEDDED_DARK_CSS = "Dark.css"; private final Type type; + private final String name; + private final Optional additionalStylesheet; public Theme(String name) { @@ -29,21 +35,25 @@ public Theme(String name) { this.additionalStylesheet = Optional.empty(); this.type = Type.DEFAULT; this.name = ""; - } else if (EMBEDDED_DARK_CSS.equalsIgnoreCase(name)) { + } + else if (EMBEDDED_DARK_CSS.equalsIgnoreCase(name)) { this.additionalStylesheet = StyleSheet.create(EMBEDDED_DARK_CSS); if (this.additionalStylesheet.isPresent()) { this.type = Type.EMBEDDED; this.name = EMBEDDED_DARK_CSS; - } else { + } + else { this.type = Type.DEFAULT; this.name = ""; } - } else { + } + else { this.additionalStylesheet = StyleSheet.create(name); - if (this.additionalStylesheet.isPresent()) { + if (this.additionalStylesheet.isPresent()) { this.type = Type.CUSTOM; this.name = name; - } else { + } + else { this.type = Type.DEFAULT; this.name = ""; } @@ -70,11 +80,11 @@ public Type getType() { } /** - * Provides the name of the CSS, either for a built in theme, or for a raw, configured custom CSS location. - * This should be a file system path, but the raw string is - * returned even if it is not valid in some way. For this reason, the main use case for this getter is to - * storing or display the user preference, rather than to read and use the CSS file. - * + * Provides the name of the CSS, either for a built in theme, or for a raw, configured + * custom CSS location. This should be a file system path, but the raw string is + * returned even if it is not valid in some way. For this reason, the main use case + * for this getter is to storing or display the user preference, rather than to read + * and use the CSS file. * @return the raw configured CSS location. Guaranteed to be non-null. */ public String getName() { @@ -83,10 +93,10 @@ public String getName() { /** * This method allows callers to obtain the theme's additional stylesheet. - * - * @return called with the stylesheet location if there is an additional stylesheet present and available. The - * location will be a local URL. Typically it will be a {@code 'data:'} URL where the CSS is embedded. However for - * large themes it can be {@code 'file:'}. + * @return called with the stylesheet location if there is an additional stylesheet + * present and available. The location will be a local URL. Typically it will be a + * {@code 'data:'} URL where the CSS is embedded. However for large themes it can be + * {@code 'file:'}. */ public Optional getAdditionalStylesheet() { return additionalStylesheet; @@ -111,9 +121,7 @@ public int hashCode() { @Override public String toString() { - return "Theme{" + - "type=" + type + - ", name='" + name + '\'' + - '}'; + return "Theme{" + "type=" + type + ", name='" + name + '\'' + '}'; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/theme/ThemeManager.java b/jabgui/src/main/java/org/jabref/gui/theme/ThemeManager.java index 9a7ad3eb642..5467ec17105 100644 --- a/jabgui/src/main/java/org/jabref/gui/theme/ThemeManager.java +++ b/jabgui/src/main/java/org/jabref/gui/theme/ThemeManager.java @@ -35,45 +35,53 @@ import org.slf4j.LoggerFactory; /** - * Installs and manages style files and provides live reloading. JabRef provides two inbuilt themes and a user - * customizable one: Light, Dark and Custom. The Light theme is basically the base.css theme. Every other theme is - * loaded as an addition to base.css. + * Installs and manages style files and provides live reloading. JabRef provides two + * inbuilt themes and a user customizable one: Light, Dark and Custom. The Light theme is + * basically the base.css theme. Every other theme is loaded as an addition to base.css. *

    - * For type Custom, Theme will protect against removal of the CSS file, degrading as gracefully as possible. If the file - * becomes unavailable while the application is running, some Scenes that have not yet had the CSS installed may not be - * themed. The PreviewViewer, which uses WebEngine, supports data URLs and so generally is not affected by removal of - * the file; however Theme package will not attempt to URL-encode large style sheets so as to protect memory usage (see + * For type Custom, Theme will protect against removal of the CSS file, degrading as + * gracefully as possible. If the file becomes unavailable while the application is + * running, some Scenes that have not yet had the CSS installed may not be themed. The + * PreviewViewer, which uses WebEngine, supports data URLs and so generally is not + * affected by removal of the file; however Theme package will not attempt to URL-encode + * large style sheets so as to protect memory usage (see * {@link StyleSheetFile#MAX_IN_MEMORY_CSS_LENGTH}). * - * @see Custom themes in the Jabref documentation. + * @see Custom themes in the + * Jabref documentation. */ public class ThemeManager { - public static Map getDownloadIconTitleMap = Map.of( - Localization.lang("Downloading"), IconTheme.JabRefIcons.DOWNLOAD.getGraphicNode() - ); + public static Map getDownloadIconTitleMap = Map.of(Localization.lang("Downloading"), + IconTheme.JabRefIcons.DOWNLOAD.getGraphicNode()); private static final Logger LOGGER = LoggerFactory.getLogger(ThemeManager.class); private final WorkspacePreferences workspacePreferences; + private final FileUpdateMonitor fileUpdateMonitor; + private final Consumer updateRunner; + private final ThemeWindowManager themeWindowManager; private final StyleSheet baseStyleSheet; + private Theme theme; + private boolean isDarkMode; private Scene mainWindowScene; + private final Set webEngines = Collections.newSetFromMap(new WeakHashMap<>()); - public ThemeManager(WorkspacePreferences workspacePreferences, - FileUpdateMonitor fileUpdateMonitor, - Consumer updateRunner) { + public ThemeManager(WorkspacePreferences workspacePreferences, FileUpdateMonitor fileUpdateMonitor, + Consumer updateRunner) { this.workspacePreferences = Objects.requireNonNull(workspacePreferences); this.fileUpdateMonitor = Objects.requireNonNull(fileUpdateMonitor); this.updateRunner = Objects.requireNonNull(updateRunner); - // Always returns something even if the native library is not available - see https://github.com/dukke/FXThemes/issues/15 + // Always returns something even if the native library is not available - see + // https://github.com/dukke/FXThemes/issues/15 this.themeWindowManager = ThemeWindowManagerFactory.create(); this.baseStyleSheet = StyleSheet.create(Theme.BASE_CSS).get(); @@ -82,16 +90,20 @@ public ThemeManager(WorkspacePreferences workspacePreferences, initializeWindowThemeUpdater(this.isDarkMode); - // Watching base CSS only works in development and test scenarios, where the build system exposes the CSS as a - // file (e.g. for Gradle run task it will be in build/resources/main/org/jabref/gui/Base.css) + // Watching base CSS only works in development and test scenarios, where the build + // system exposes the CSS as a + // file (e.g. for Gradle run task it will be in + // build/resources/main/org/jabref/gui/Base.css) addStylesheetToWatchlist(this.baseStyleSheet, this::baseCssLiveUpdate); baseCssLiveUpdate(); BindingsHelper.subscribeFuture(workspacePreferences.themeProperty(), theme -> updateThemeSettings()); BindingsHelper.subscribeFuture(workspacePreferences.themeSyncOsProperty(), theme -> updateThemeSettings()); - BindingsHelper.subscribeFuture(workspacePreferences.shouldOverrideDefaultFontSizeProperty(), should -> updateFontSettings()); + BindingsHelper.subscribeFuture(workspacePreferences.shouldOverrideDefaultFontSizeProperty(), + should -> updateFontSettings()); BindingsHelper.subscribeFuture(workspacePreferences.mainFontSizeProperty(), size -> updateFontSettings()); - BindingsHelper.subscribeFuture(Platform.getPreferences().colorSchemeProperty(), colorScheme -> updateThemeSettings()); + BindingsHelper.subscribeFuture(Platform.getPreferences().colorSchemeProperty(), + colorScheme -> updateThemeSettings()); updateThemeSettings(); } @@ -103,11 +115,12 @@ private void initializeWindowThemeUpdater(boolean darkMode) { if (!change.wasAdded()) { continue; } - change.getAddedSubList().stream() - .filter(Stage.class::isInstance) - .map(Stage.class::cast) - .forEach(stage -> stage.showingProperty() - .addListener(_ -> applyDarkModeToWindow(stage, isDarkMode))); + change.getAddedSubList() + .stream() + .filter(Stage.class::isInstance) + .map(Stage.class::cast) + .forEach(stage -> stage.showingProperty() + .addListener(_ -> applyDarkModeToWindow(stage, isDarkMode))); } }; @@ -125,20 +138,25 @@ private void applyDarkModeToWindow(Stage stage, boolean darkMode) { try { themeWindowManager.setDarkModeForWindowFrame(stage, darkMode); LOGGER.debug("Applied {} mode to window: {}", darkMode ? "dark" : "light", stage); - } catch (NoClassDefFoundError | UnsatisfiedLinkError e) { - // We need to handle these exceptions because the native library may not be available on all platforms (e.g., x86). + } + catch (NoClassDefFoundError | UnsatisfiedLinkError e) { + // We need to handle these exceptions because the native library may not be + // available on all platforms (e.g., x86). // See https://github.com/dukke/FXThemes/issues/13 for details. - LOGGER.debug("Failed to set dark mode for window frame (likely due to native library compatibility issues on intel)", e); + LOGGER.debug( + "Failed to set dark mode for window frame (likely due to native library compatibility issues on intel)", + e); } } private void applyDarkModeToAllWindows(boolean darkMode) { this.isDarkMode = darkMode; - Window.getWindows().stream() - .filter(Window::isShowing) - .filter(window -> window instanceof Stage) - .map(window -> (Stage) window) - .forEach(stage -> applyDarkModeToWindow(stage, darkMode)); + Window.getWindows() + .stream() + .filter(Window::isShowing) + .filter(window -> window instanceof Stage) + .map(window -> (Stage) window) + .forEach(stage -> applyDarkModeToWindow(stage, darkMode)); } private void updateThemeSettings() { @@ -147,14 +165,16 @@ private void updateThemeSettings() { if (workspacePreferences.themeSyncOsProperty().getValue()) { if (Platform.getPreferences().getColorScheme() == ColorScheme.DARK) { newTheme = Theme.dark(); - } else { + } + else { newTheme = Theme.light(); } } if (newTheme.equals(theme)) { LOGGER.info("Not updating theme because it hasn't changed"); - } else { + } + else { theme.getAdditionalStylesheet().ifPresent(this::removeStylesheetFromWatchList); } @@ -167,8 +187,8 @@ private void updateThemeSettings() { applyDarkModeToAllWindows(isDarkTheme); } - this.theme.getAdditionalStylesheet().ifPresent( - styleSheet -> addStylesheetToWatchlist(styleSheet, this::additionalCssLiveUpdate)); + this.theme.getAdditionalStylesheet() + .ifPresent(styleSheet -> addStylesheetToWatchlist(styleSheet, this::additionalCssLiveUpdate)); additionalCssLiveUpdate(); updateFontSettings(); @@ -192,7 +212,8 @@ private void addStylesheetToWatchlist(StyleSheet styleSheet, FileUpdateListener try { fileUpdateMonitor.addListenerForFile(watchPath, updateMethod); LOGGER.info("Watching css {} for live updates", watchPath); - } catch (IOException e) { + } + catch (IOException e) { LOGGER.warn("Cannot watch css path {} for live updates", watchPath, e); } } @@ -202,7 +223,8 @@ private void baseCssLiveUpdate() { baseStyleSheet.reload(); if (baseStyleSheet.getSceneStylesheet() == null) { LOGGER.error("Base stylesheet does not exist."); - } else { + } + else { LOGGER.debug("Updating base CSS for main window scene"); } @@ -217,19 +239,17 @@ private void additionalCssLiveUpdate() { LOGGER.debug("Updating additional CSS for main window scene and {} web engines", webEngines.size()); - UiTaskExecutor.runInJavaFXThread(() -> - updateRunner.accept(() -> { - updateAdditionalCss(); + UiTaskExecutor.runInJavaFXThread(() -> updateRunner.accept(() -> { + updateAdditionalCss(); - webEngines.forEach(webEngine -> { - // force refresh by unloading style sheet, if the location hasn't changed - if (newStyleSheetLocation.equals(webEngine.getUserStyleSheetLocation())) { - webEngine.setUserStyleSheetLocation(null); - } - webEngine.setUserStyleSheetLocation(newStyleSheetLocation); - }); - }) - ); + webEngines.forEach(webEngine -> { + // force refresh by unloading style sheet, if the location hasn't changed + if (newStyleSheetLocation.equals(webEngine.getUserStyleSheetLocation())) { + webEngine.setUserStyleSheetLocation(null); + } + webEngine.setUserStyleSheetLocation(newStyleSheetLocation); + }); + })); } private void updateBaseCss() { @@ -250,24 +270,22 @@ private void updateAdditionalCss() { return; } - mainWindowScene.getStylesheets().setAll(List.of( - baseStyleSheet.getSceneStylesheet().toExternalForm(), - theme.getAdditionalStylesheet().map(styleSheet -> { - URL stylesheetUrl = styleSheet.getSceneStylesheet(); - if (stylesheetUrl != null) { - return stylesheetUrl.toExternalForm(); - } else { - return ""; - } - }) - .orElse("") - )); + mainWindowScene.getStylesheets() + .setAll(List.of(baseStyleSheet.getSceneStylesheet().toExternalForm(), + theme.getAdditionalStylesheet().map(styleSheet -> { + URL stylesheetUrl = styleSheet.getSceneStylesheet(); + if (stylesheetUrl != null) { + return stylesheetUrl.toExternalForm(); + } + else { + return ""; + } + }).orElse(""))); } /** - * Installs the base css file as a stylesheet in the given scene. Changes in the css file lead to a redraw of the - * scene using the new css file. - * + * Installs the base css file as a stylesheet in the given scene. Changes in the css + * file lead to a redraw of the scene using the new css file. * @param mainWindowScene the scene to install the css into */ public void installCss(Scene mainWindowScene) { @@ -280,24 +298,23 @@ public void installCss(Scene mainWindowScene) { } /** - * Installs the css file as a stylesheet in the given web engine. Changes in the css file lead to a redraw of the - * web engine using the new css file. - * + * Installs the css file as a stylesheet in the given web engine. Changes in the css + * file lead to a redraw of the web engine using the new css file. * @param webEngine the web engine to install the css into */ public void installCss(WebEngine webEngine) { updateRunner.accept(() -> { if (this.webEngines.add(webEngine)) { - webEngine.setUserStyleSheetLocation(this.theme.getAdditionalStylesheet().isPresent() ? - this.theme.getAdditionalStylesheet().get().getWebEngineStylesheet() : ""); + webEngine.setUserStyleSheetLocation(this.theme.getAdditionalStylesheet().isPresent() + ? this.theme.getAdditionalStylesheet().get().getWebEngineStylesheet() : ""); } }); } /** - * Updates the font size settings of a scene. This method needs to be called from every custom dialog constructor, - * since javafx overwrites the style if applied before showing the dialog - * + * Updates the font size settings of a scene. This method needs to be called from + * every custom dialog constructor, since javafx overwrites the style if applied + * before showing the dialog * @param scene is the scene, the font size should be applied to */ public void updateFontStyle(Scene scene) { @@ -307,7 +324,8 @@ public void updateFontStyle(Scene scene) { if (workspacePreferences.shouldOverrideDefaultFontSize()) { scene.getRoot().setStyle("-fx-font-size: " + workspacePreferences.getMainFontSize() + "pt;"); - } else { + } + else { scene.getRoot().setStyle("-fx-font-size: " + workspacePreferences.getDefaultFontSize() + "pt;"); } } @@ -319,4 +337,5 @@ public void updateFontStyle(Scene scene) { Theme getActiveTheme() { return this.theme; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/theme/ThemeTypes.java b/jabgui/src/main/java/org/jabref/gui/theme/ThemeTypes.java index 73523ae9c47..fa781ce9e29 100644 --- a/jabgui/src/main/java/org/jabref/gui/theme/ThemeTypes.java +++ b/jabgui/src/main/java/org/jabref/gui/theme/ThemeTypes.java @@ -4,17 +4,16 @@ public enum ThemeTypes { - LIGHT(Localization.lang("Light")), - DARK(Localization.lang("Dark")), - CUSTOM(Localization.lang("Custom...")); + LIGHT(Localization.lang("Light")), DARK(Localization.lang("Dark")), CUSTOM(Localization.lang("Custom...")); - private final String displayName; + private final String displayName; - ThemeTypes(String displayName) { - this.displayName = displayName; - } + ThemeTypes(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } - public String getDisplayName() { - return displayName; - } } diff --git a/jabgui/src/main/java/org/jabref/gui/undo/AbstractUndoableJabRefEdit.java b/jabgui/src/main/java/org/jabref/gui/undo/AbstractUndoableJabRefEdit.java index d5f4fa0663f..fe9473ed8fb 100644 --- a/jabgui/src/main/java/org/jabref/gui/undo/AbstractUndoableJabRefEdit.java +++ b/jabgui/src/main/java/org/jabref/gui/undo/AbstractUndoableJabRefEdit.java @@ -15,4 +15,5 @@ public String getUndoPresentationName() { public String getRedoPresentationName() { return "" + Localization.lang("Redo") + ": " + getPresentationName() + ""; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/undo/CountingUndoManager.java b/jabgui/src/main/java/org/jabref/gui/undo/CountingUndoManager.java index 6a0c4a557e5..9865dd28460 100644 --- a/jabgui/src/main/java/org/jabref/gui/undo/CountingUndoManager.java +++ b/jabgui/src/main/java/org/jabref/gui/undo/CountingUndoManager.java @@ -17,10 +17,13 @@ public class CountingUndoManager extends UndoManager { private int unchangedPoint; /** - * Indicates the number of edits aka balance of edits on the stack +1 when an edit is added/redone and -1 when an edit is undoed. + * Indicates the number of edits aka balance of edits on the stack +1 when an edit is + * added/redone and -1 when an edit is undoed. */ private final IntegerProperty balanceProperty = new SimpleIntegerProperty(0); + private final BooleanProperty undoableProperty = new SimpleBooleanProperty(false); + private final BooleanProperty redoableProperty = new SimpleBooleanProperty(false); @Override @@ -31,7 +34,8 @@ public synchronized boolean addEdit(UndoableEdit edit) { updateUndoableStatus(); updateRedoableStatus(); return true; - } else { + } + else { return false; } } @@ -83,4 +87,5 @@ public ReadOnlyBooleanProperty getUndoableProperty() { public ReadOnlyBooleanProperty getRedoableProperty() { return redoableProperty; } + } diff --git a/jabgui/src/main/java/org/jabref/gui/undo/NamedCompound.java b/jabgui/src/main/java/org/jabref/gui/undo/NamedCompound.java index 55be19a96e3..c1056c9bb24 100644 --- a/jabgui/src/main/java/org/jabref/gui/undo/NamedCompound.java +++ b/jabgui/src/main/java/org/jabref/gui/undo/NamedCompound.java @@ -8,6 +8,7 @@ public class NamedCompound extends CompoundEdit { private final String name; + private boolean hasEdits; public NamedCompound(String name) { @@ -43,4 +44,5 @@ public String getPresentationName() { } return sb.toString(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/undo/RedoAction.java b/jabgui/src/main/java/org/jabref/gui/undo/RedoAction.java index d5d3170a993..5ce09e59c8f 100644 --- a/jabgui/src/main/java/org/jabref/gui/undo/RedoAction.java +++ b/jabgui/src/main/java/org/jabref/gui/undo/RedoAction.java @@ -18,11 +18,15 @@ * @implNote See also {@link UndoAction} */ public class RedoAction extends SimpleCommand { + private final Supplier tabSupplier; + private final DialogService dialogService; + private final CountingUndoManager undoManager; - public RedoAction(Supplier tabSupplier, CountingUndoManager undoManager, DialogService dialogService, StateManager stateManager) { + public RedoAction(Supplier tabSupplier, CountingUndoManager undoManager, DialogService dialogService, + StateManager stateManager) { this.tabSupplier = tabSupplier; this.dialogService = dialogService; this.undoManager = undoManager; @@ -37,12 +41,15 @@ public void execute() { if (undoManager.canRedo()) { undoManager.redo(); dialogService.notify(Localization.lang("Redo")); - } else { + } + else { throw new CannotRedoException(); } - } catch (CannotRedoException ex) { + } + catch (CannotRedoException ex) { dialogService.notify(Localization.lang("Nothing to redo") + '.'); } libraryTab.markChangedOrUnChanged(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/undo/UndoAction.java b/jabgui/src/main/java/org/jabref/gui/undo/UndoAction.java index 5eab905a9f8..6a8731cbfd6 100644 --- a/jabgui/src/main/java/org/jabref/gui/undo/UndoAction.java +++ b/jabgui/src/main/java/org/jabref/gui/undo/UndoAction.java @@ -18,11 +18,15 @@ * @implNote See also {@link RedoAction} */ public class UndoAction extends SimpleCommand { + private final Supplier tabSupplier; + private final DialogService dialogService; + private final CountingUndoManager undoManager; - public UndoAction(Supplier tabSupplier, CountingUndoManager undoManager, DialogService dialogService, StateManager stateManager) { + public UndoAction(Supplier tabSupplier, CountingUndoManager undoManager, DialogService dialogService, + StateManager stateManager) { this.tabSupplier = tabSupplier; this.dialogService = dialogService; this.undoManager = undoManager; @@ -37,12 +41,15 @@ public void execute() { if (undoManager.canUndo()) { undoManager.undo(); dialogService.notify(Localization.lang("Undo")); - } else { + } + else { throw new CannotUndoException(); } - } catch (CannotUndoException ex) { + } + catch (CannotUndoException ex) { dialogService.notify(Localization.lang("Nothing to undo") + '.'); } libraryTab.markChangedOrUnChanged(); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/undo/UndoableChangeType.java b/jabgui/src/main/java/org/jabref/gui/undo/UndoableChangeType.java index 05d29f902e8..62f048eb6ac 100644 --- a/jabgui/src/main/java/org/jabref/gui/undo/UndoableChangeType.java +++ b/jabgui/src/main/java/org/jabref/gui/undo/UndoableChangeType.java @@ -11,12 +11,16 @@ * This class represents the change of type for an entry. */ public class UndoableChangeType extends AbstractUndoableJabRefEdit { + private final EntryType oldType; + private final EntryType newType; + private final BibEntry entry; public UndoableChangeType(FieldChange change) { - this(change.getEntry(), EntryTypeFactory.parse(change.getOldValue()), EntryTypeFactory.parse(change.getNewValue())); + this(change.getEntry(), EntryTypeFactory.parse(change.getOldValue()), + EntryTypeFactory.parse(change.getNewValue())); } public UndoableChangeType(BibEntry entry, EntryType oldType, EntryType newType) { @@ -44,4 +48,5 @@ public void redo() { super.redo(); entry.setType(newType); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/undo/UndoableFieldChange.java b/jabgui/src/main/java/org/jabref/gui/undo/UndoableFieldChange.java index efb96c70020..0d067d2c64c 100644 --- a/jabgui/src/main/java/org/jabref/gui/undo/UndoableFieldChange.java +++ b/jabgui/src/main/java/org/jabref/gui/undo/UndoableFieldChange.java @@ -10,16 +10,19 @@ import org.slf4j.LoggerFactory; /** - * This class represents a change in any field value. The relevant - * information is the BibEntry, the field name, the old and the - * new value. Old/new values can be null. + * This class represents a change in any field value. The relevant information is the + * BibEntry, the field name, the old and the new value. Old/new values can be null. */ public class UndoableFieldChange extends AbstractUndoableJabRefEdit { + private static final Logger LOGGER = LoggerFactory.getLogger(UndoableFieldChange.class); private final BibEntry entry; + private final Field field; + private final String oldValue; + private final String newValue; public UndoableFieldChange(BibEntry entry, Field field, String oldValue, String newValue) { @@ -35,7 +38,8 @@ public UndoableFieldChange(FieldChange change) { @Override public String getPresentationName() { - return Localization.lang("change field %0 of entry %1 from %2 to %3", StringUtil.boldHTML(field.getDisplayName()), + return Localization.lang("change field %0 of entry %1 from %2 to %3", + StringUtil.boldHTML(field.getDisplayName()), StringUtil.boldHTML(entry.getCitationKey().orElse(Localization.lang("undefined"))), StringUtil.boldHTML(oldValue, Localization.lang("undefined")), StringUtil.boldHTML(newValue, Localization.lang("undefined"))); @@ -49,12 +53,14 @@ public void undo() { try { if (oldValue == null) { entry.clearField(field); - } else { + } + else { entry.setField(field, oldValue); } // this is the only exception explicitly thrown here - } catch (IllegalArgumentException ex) { + } + catch (IllegalArgumentException ex) { LOGGER.info("Cannot perform undo", ex); } } @@ -67,11 +73,14 @@ public void redo() { try { if (newValue == null) { entry.clearField(field); - } else { + } + else { entry.setField(field, newValue); } - } catch (IllegalArgumentException ex) { + } + catch (IllegalArgumentException ex) { LOGGER.info("Cannot perform redo", ex); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/undo/UndoableInsertEntries.java b/jabgui/src/main/java/org/jabref/gui/undo/UndoableInsertEntries.java index 6d02fa37523..f88b136d803 100644 --- a/jabgui/src/main/java/org/jabref/gui/undo/UndoableInsertEntries.java +++ b/jabgui/src/main/java/org/jabref/gui/undo/UndoableInsertEntries.java @@ -11,15 +11,17 @@ import org.slf4j.LoggerFactory; /** - * This class represents the removal of entries. The constructor needs - * references to the database, entries, and a boolean marked true if the undo - * is from a call to paste(). + * This class represents the removal of entries. The constructor needs references to the + * database, entries, and a boolean marked true if the undo is from a call to paste(). */ public class UndoableInsertEntries extends AbstractUndoableJabRefEdit { private static final Logger LOGGER = LoggerFactory.getLogger(UndoableInsertEntries.class); + private final BibDatabase database; + private final List entries; + private final boolean paste; public UndoableInsertEntries(BibDatabase database, BibEntry entry) { @@ -41,19 +43,24 @@ public String getPresentationName() { if (paste) { if (entries.size() > 1) { return Localization.lang("paste entries"); - } else if (entries.size() == 1) { - return Localization.lang("paste entry %0", - StringUtil.boldHTML(entries.getFirst().getCitationKey().orElse(Localization.lang("undefined")))); - } else { + } + else if (entries.size() == 1) { + return Localization.lang("paste entry %0", StringUtil + .boldHTML(entries.getFirst().getCitationKey().orElse(Localization.lang("undefined")))); + } + else { return null; } - } else { + } + else { if (entries.size() > 1) { return Localization.lang("insert entries"); - } else if (entries.size() == 1) { - return Localization.lang("insert entry %0", - StringUtil.boldHTML(entries.getFirst().getCitationKey().orElse(Localization.lang("undefined")))); - } else { + } + else if (entries.size() == 1) { + return Localization.lang("insert entry %0", StringUtil + .boldHTML(entries.getFirst().getCitationKey().orElse(Localization.lang("undefined")))); + } + else { return null; } } @@ -65,7 +72,8 @@ public void undo() { try { database.removeEntries(entries); - } catch (Throwable ex) { + } + catch (Throwable ex) { LOGGER.warn("Problem undoing `insert entries`", ex); } } @@ -75,4 +83,5 @@ public void redo() { super.redo(); database.insertEntries(entries); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/undo/UndoableInsertString.java b/jabgui/src/main/java/org/jabref/gui/undo/UndoableInsertString.java index 6485861abbe..dfa5982201d 100644 --- a/jabgui/src/main/java/org/jabref/gui/undo/UndoableInsertString.java +++ b/jabgui/src/main/java/org/jabref/gui/undo/UndoableInsertString.java @@ -14,6 +14,7 @@ public class UndoableInsertString extends AbstractUndoableJabRefEdit { private static final Logger LOGGER = LoggerFactory.getLogger(UndoableInsertString.class); private final BibDatabase base; + private final BibtexString string; public UndoableInsertString(BibDatabase base, BibtexString string) { @@ -41,8 +42,10 @@ public void redo() { // Redo the change. try { base.addString(string); - } catch (KeyCollisionException ex) { + } + catch (KeyCollisionException ex) { LOGGER.warn("Problem to redo `insert entry`", ex); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/undo/UndoableKeyChange.java b/jabgui/src/main/java/org/jabref/gui/undo/UndoableKeyChange.java index 8b37869d6a8..e0b0bcbd2fc 100644 --- a/jabgui/src/main/java/org/jabref/gui/undo/UndoableKeyChange.java +++ b/jabgui/src/main/java/org/jabref/gui/undo/UndoableKeyChange.java @@ -6,14 +6,15 @@ import org.jabref.model.strings.StringUtil; /** - * This class represents a change in any field value. The relevant - * information is the BibEntry, the field name, the old and the - * new value. Old/new values can be null. + * This class represents a change in any field value. The relevant information is the + * BibEntry, the field name, the old and the new value. Old/new values can be null. */ public class UndoableKeyChange extends AbstractUndoableJabRefEdit { private final BibEntry entry; + private final String oldValue; + private final String newValue; public UndoableKeyChange(FieldChange change) { @@ -44,4 +45,5 @@ public void redo() { super.redo(); entry.setCitationKey(newValue); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/undo/UndoablePreambleChange.java b/jabgui/src/main/java/org/jabref/gui/undo/UndoablePreambleChange.java index 10d89179eaf..d601d7d3130 100644 --- a/jabgui/src/main/java/org/jabref/gui/undo/UndoablePreambleChange.java +++ b/jabgui/src/main/java/org/jabref/gui/undo/UndoablePreambleChange.java @@ -4,16 +4,18 @@ import org.jabref.model.database.BibDatabase; /** - * This class represents a change in any field value. The relevant information is the BibEntry, the field name, the old and the new value. Old/new values can be null. + * This class represents a change in any field value. The relevant information is the + * BibEntry, the field name, the old and the new value. Old/new values can be null. */ public class UndoablePreambleChange extends AbstractUndoableJabRefEdit { private final BibDatabase base; + private final String oldValue; + private final String newValue; - public UndoablePreambleChange(BibDatabase base, - String oldValue, String newValue) { + public UndoablePreambleChange(BibDatabase base, String oldValue, String newValue) { this.base = base; this.oldValue = oldValue; this.newValue = newValue; @@ -39,4 +41,5 @@ public void redo() { // Redo the change. base.setPreamble(newValue); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java b/jabgui/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java index c095f740f3b..a2d1487ac31 100644 --- a/jabgui/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java +++ b/jabgui/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java @@ -12,17 +12,19 @@ import org.slf4j.LoggerFactory; /** - * This class represents the removal of entries. The constructor needs - * references to the database, the entries, and the map of open entry editors. - * TODO is this map still being used? - * The latter to be able to close the entry's editor if it is opened after - * an undo, and the removal is then undone. + * This class represents the removal of entries. The constructor needs references to the + * database, the entries, and the map of open entry editors. TODO is this map still being + * used? The latter to be able to close the entry's editor if it is opened after an undo, + * and the removal is then undone. */ public class UndoableRemoveEntries extends AbstractUndoableJabRefEdit { private static final Logger LOGGER = LoggerFactory.getLogger(UndoableRemoveEntries.class); + private final BibDatabase base; + private final List entries; + private final boolean cut; public UndoableRemoveEntries(BibDatabase base, BibEntry entry) { @@ -44,19 +46,24 @@ public String getPresentationName() { if (cut) { if (entries.size() > 1) { return Localization.lang("cut entries"); - } else if (entries.size() == 1) { - return Localization.lang("cut entry %0", - StringUtil.boldHTML(entries.getFirst().getCitationKey().orElse(Localization.lang("undefined")))); - } else { + } + else if (entries.size() == 1) { + return Localization.lang("cut entry %0", StringUtil + .boldHTML(entries.getFirst().getCitationKey().orElse(Localization.lang("undefined")))); + } + else { return null; } - } else { + } + else { if (entries.size() > 1) { return Localization.lang("remove entries"); - } else if (entries.size() == 1) { - return Localization.lang("remove entry %0", - StringUtil.boldHTML(entries.getFirst().getCitationKey().orElse(Localization.lang("undefined")))); - } else { + } + else if (entries.size() == 1) { + return Localization.lang("remove entry %0", StringUtil + .boldHTML(entries.getFirst().getCitationKey().orElse(Localization.lang("undefined")))); + } + else { return null; } } @@ -75,8 +82,10 @@ public void redo() { // Redo the change. try { base.removeEntries(entries); - } catch (Throwable ex) { + } + catch (Throwable ex) { LOGGER.warn("Problem to redo `remove entries`", ex); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/undo/UndoableRemoveString.java b/jabgui/src/main/java/org/jabref/gui/undo/UndoableRemoveString.java index 2c086ed3cf0..404e85fb349 100644 --- a/jabgui/src/main/java/org/jabref/gui/undo/UndoableRemoveString.java +++ b/jabgui/src/main/java/org/jabref/gui/undo/UndoableRemoveString.java @@ -12,7 +12,9 @@ public class UndoableRemoveString extends AbstractUndoableJabRefEdit { private static final Logger LOGGER = LoggerFactory.getLogger(UndoableRemoveString.class); + private final BibDatabase base; + private final BibtexString string; public UndoableRemoveString(BibDatabase base, BibtexString string) { @@ -31,7 +33,8 @@ public void undo() { // Revert the change. try { base.addString(string); - } catch (KeyCollisionException ex) { + } + catch (KeyCollisionException ex) { LOGGER.warn("Problem to undo `remove string`", ex); } } @@ -42,4 +45,5 @@ public void redo() { // Redo the change. base.removeString(string.getId()); } + } diff --git a/jabgui/src/main/java/org/jabref/gui/undo/UndoableStringChange.java b/jabgui/src/main/java/org/jabref/gui/undo/UndoableStringChange.java index 883f1675750..d4ff0ebe29f 100644 --- a/jabgui/src/main/java/org/jabref/gui/undo/UndoableStringChange.java +++ b/jabgui/src/main/java/org/jabref/gui/undo/UndoableStringChange.java @@ -7,8 +7,11 @@ public class UndoableStringChange extends AbstractUndoableJabRefEdit { private final BibtexString string; + private final String oldValue; + private final String newValue; + private final boolean nameChange; public UndoableStringChange(BibtexString string, boolean nameChange, String oldValue, String newValue) { @@ -20,10 +23,11 @@ public UndoableStringChange(BibtexString string, boolean nameChange, String oldV @Override public String getPresentationName() { - return nameChange ? Localization.lang("change string name %0 to %1", StringUtil.boldHTML(oldValue), - StringUtil.boldHTML(newValue)) : - Localization.lang("change string content %0 to %1", - StringUtil.boldHTML(oldValue), StringUtil.boldHTML(newValue)); + return nameChange + ? Localization.lang("change string name %0 to %1", StringUtil.boldHTML(oldValue), + StringUtil.boldHTML(newValue)) + : Localization.lang("change string content %0 to %1", StringUtil.boldHTML(oldValue), + StringUtil.boldHTML(newValue)); } @Override @@ -33,7 +37,8 @@ public void undo() { // Revert the change. if (nameChange) { string.setName(oldValue); - } else { + } + else { string.setContent(oldValue); } } @@ -45,8 +50,10 @@ public void redo() { // Redo the change. if (nameChange) { string.setName(newValue); - } else { + } + else { string.setContent(newValue); } } + } diff --git a/jabgui/src/main/java/org/jabref/gui/util/BaseDialog.java b/jabgui/src/main/java/org/jabref/gui/util/BaseDialog.java index 3e2d4c4c182..0c8ff08c970 100644 --- a/jabgui/src/main/java/org/jabref/gui/util/BaseDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/util/BaseDialog.java @@ -22,7 +22,8 @@ protected BaseDialog() { KeyBindingRepository keyBindingRepository = Injector.instantiateModelOrService(KeyBindingRepository.class); if (keyBindingRepository.checkKeyCombinationEquality(KeyBinding.CLOSE, event)) { close(); - } else if (keyBindingRepository.checkKeyCombinationEquality(KeyBinding.DEFAULT_DIALOG_ACTION, event)) { + } + else if (keyBindingRepository.checkKeyCombinationEquality(KeyBinding.DEFAULT_DIALOG_ACTION, event)) { getDefaultButton().ifPresent(Button::fire); } @@ -44,14 +45,16 @@ private Optional